diff options
author | Gerd Knorr <devnull@localhost> | 2004-02-22 01:59:34 +0000 |
---|---|---|
committer | Gerd Knorr <devnull@localhost> | 2004-02-22 01:59:34 +0000 |
commit | 229b130306c830b9f1c410ccd6060b65aa68dcec (patch) | |
tree | 0ad687a6c45ad1481a208419b017f2c7cf6c520c | |
parent | 0f99a89ea0a04d80bd283fdfd2c8141a763e2de3 (diff) | |
download | mediapointer-dvb-s2-229b130306c830b9f1c410ccd6060b65aa68dcec.tar.gz mediapointer-dvb-s2-229b130306c830b9f1c410ccd6060b65aa68dcec.tar.bz2 |
Initial revision
95 files changed, 42767 insertions, 0 deletions
diff --git a/linux/Documentation/video4linux/CARDLIST.bttv b/linux/Documentation/video4linux/CARDLIST.bttv new file mode 100644 index 000000000..64229aee1 --- /dev/null +++ b/linux/Documentation/video4linux/CARDLIST.bttv @@ -0,0 +1,115 @@ +card=0 - *** UNKNOWN/GENERIC *** +card=1 - MIRO PCTV +card=2 - Hauppauge (bt848) +card=3 - STB, Gateway P/N 6000699 (bt848) +card=4 - Intel Create and Share PCI/ Smart Video Recorder III +card=5 - Diamond DTV2000 +card=6 - AVerMedia TVPhone +card=7 - MATRIX-Vision MV-Delta +card=8 - Lifeview FlyVideo II (Bt848) LR26 / MAXI TV Video PCI2 LR26 +card=9 - IMS/IXmicro TurboTV +card=10 - Hauppauge (bt878) +card=11 - MIRO PCTV pro +card=12 - ADS Technologies Channel Surfer TV (bt848) +card=13 - AVerMedia TVCapture 98 +card=14 - Aimslab Video Highway Xtreme (VHX) +card=15 - Zoltrix TV-Max +card=16 - Prolink Pixelview PlayTV (bt878) +card=17 - Leadtek WinView 601 +card=18 - AVEC Intercapture +card=19 - Lifeview FlyVideo II EZ /FlyKit LR38 Bt848 (capture only) +card=20 - CEI Raffles Card +card=21 - Lifeview FlyVideo 98/ Lucky Star Image World ConferenceTV LR50 +card=22 - Askey CPH050/ Phoebe Tv Master + FM +card=23 - Modular Technology MM201/MM202/MM205/MM210/MM215 PCTV, bt878 +card=24 - Askey CPH05X/06X (bt878) [many vendors] +card=25 - Terratec TerraTV+ Version 1.0 (Bt848)/ Terra TValue Version 1.0/ Vobis TV-Boostar +card=26 - Hauppauge WinCam newer (bt878) +card=27 - Lifeview FlyVideo 98/ MAXI TV Video PCI2 LR50 +card=28 - Terratec TerraTV+ Version 1.1 (bt878) +card=29 - Imagenation PXC200 +card=30 - Lifeview FlyVideo 98 LR50 +card=31 - Formac iProTV, Formac ProTV I (bt848) +card=32 - Intel Create and Share PCI/ Smart Video Recorder III +card=33 - Terratec TerraTValue Version Bt878 +card=34 - Leadtek WinFast 2000/ WinFast 2000 XP +card=35 - Lifeview FlyVideo 98 LR50 / Chronos Video Shuttle II +card=36 - Lifeview FlyVideo 98FM LR50 / Typhoon TView TV/FM Tuner +card=37 - Prolink PixelView PlayTV pro +card=38 - Askey CPH06X TView99 +card=39 - Pinnacle PCTV Studio/Rave +card=40 - STB TV PCI FM, Gateway P/N 6000704 (bt878), 3Dfx VoodooTV 100 +card=41 - AVerMedia TVPhone 98 +card=42 - ProVideo PV951 +card=43 - Little OnAir TV +card=44 - Sigma TVII-FM +card=45 - MATRIX-Vision MV-Delta 2 +card=46 - Zoltrix Genie TV/FM +card=47 - Terratec TV/Radio+ +card=48 - Askey CPH03x/ Dynalink Magic TView +card=49 - IODATA GV-BCTV3/PCI +card=50 - Prolink PV-BT878P+4E / PixelView PlayTV PAK / Lenco MXTV-9578 CP +card=51 - Eagle Wireless Capricorn2 (bt878A) +card=52 - Pinnacle PCTV Studio Pro +card=53 - Typhoon TView RDS + FM Stereo / KNC1 TV Station RDS +card=54 - Lifeview FlyVideo 2000 /FlyVideo A2/ Lifetec LT 9415 TV [LR90] +card=55 - Askey CPH031/ BESTBUY Easy TV +card=56 - Lifeview FlyVideo 98FM LR50 +card=57 - GrandTec 'Grand Video Capture' (Bt848) +card=58 - Askey CPH060/ Phoebe TV Master Only (No FM) +card=59 - Askey CPH03x TV Capturer +card=60 - Modular Technology MM100PCTV +card=61 - AG Electronics GMV1 +card=62 - Askey CPH061/ BESTBUY Easy TV (bt878) +card=63 - ATI TV-Wonder +card=64 - ATI TV-Wonder VE +card=65 - Lifeview FlyVideo 2000S LR90 +card=66 - Terratec TValueRadio +card=67 - IODATA GV-BCTV4/PCI +card=68 - 3Dfx VoodooTV FM (Euro), VoodooTV 200 (USA) +card=69 - Active Imaging AIMMS +card=70 - Prolink Pixelview PV-BT878P+ (Rev.4C,8E) +card=71 - Lifeview FlyVideo 98EZ (capture only) LR51 +card=72 - Prolink Pixelview PV-BT878P+9B (PlayTV Pro rev.9B FM+NICAM) +card=73 - Sensoray 311 +card=74 - RemoteVision MX (RV605) +card=75 - Powercolor MTV878/ MTV878R/ MTV878F +card=76 - Canopus WinDVR PCI (COMPAQ Presario 3524JP, 5112JP) +card=77 - GrandTec Multi Capture Card (Bt878) +card=78 - Jetway TV/Capture JW-TV878-FBK, Kworld KW-TV878RF +card=79 - DSP Design TCVIDEO +card=80 - Hauppauge WinTV PVR +card=81 - IODATA GV-BCTV5/PCI +card=82 - Osprey 100/150 (878) +card=83 - Osprey 100/150 (848) +card=84 - Osprey 101 (848) +card=85 - Osprey 101/151 +card=86 - Osprey 101/151 w/ svid +card=87 - Osprey 200/201/250/251 +card=88 - Osprey 200/250 +card=89 - Osprey 210/220 +card=90 - Osprey 500 +card=91 - Osprey 540 +card=92 - Osprey 2000 +card=93 - IDS Eagle +card=94 - Pinnacle PCTV Sat +card=95 - Formac ProTV II (bt878) +card=96 - MachTV +card=97 - Euresys Picolo +card=98 - ProVideo PV150 +card=99 - AD-TVK503 +card=100 - Hercules Smart TV Stereo +card=101 - Pace TV & Radio Card +card=102 - IVC-200 +card=103 - Grand X-Guard / Trust 814PCI +card=104 - Nebula Electronics DigiTV +card=105 - ProVideo PV143 +card=106 - PHYTEC VD-009-X1 MiniDIN (bt878) +card=107 - PHYTEC VD-009-X1 Combi (bt878) +card=108 - PHYTEC VD-009 MiniDIN (bt878) +card=109 - PHYTEC VD-009 Combi (bt878) +card=110 - IVC-100 +card=111 - IVC-120G +card=112 - pcHDTV HD-2000 TV +card=113 - Twinhan DST + clones +card=114 - Winfast VC100 diff --git a/linux/Documentation/video4linux/CARDLIST.saa7134 b/linux/Documentation/video4linux/CARDLIST.saa7134 new file mode 100644 index 000000000..619e07a8d --- /dev/null +++ b/linux/Documentation/video4linux/CARDLIST.saa7134 @@ -0,0 +1,23 @@ + 0 -> UNKNOWN/GENERIC + 1 -> Proteus Pro [philips reference design] [1131:2001,1131:2001] + 2 -> LifeView FlyVIDEO3000 [5168:0138] + 3 -> LifeView FlyVIDEO2000 [5168:0138] + 4 -> EMPRESS [1131:6752] + 5 -> SKNet Monster TV [1131:4e85] + 6 -> Tevion MD 9717 + 7 -> KNC One TV-Station RDS / Typhoon TV+Radio 90031 [1131:fe01] + 8 -> Terratec Cinergy 400 TV [153B:1142] + 9 -> Medion 5044 + 10 -> Kworld/KuroutoShikou SAA7130-TVPCI + 11 -> Terratec Cinergy 600 TV [153B:1143] + 12 -> Medion 7134 [16be:0003] + 13 -> ELSA EX-VISION 300TV [1048:226b] + 14 -> ELSA EX-VISION 500TV [1048:226b] + 15 -> ASUS TV-FM 7134 [PCI_VENDOR_ID_ASUSTEK:4842,PCI_VENDOR_ID_ASUSTEK:4830] + 16 -> AOPEN VA1000 POWER [1131:7133] + 17 -> 10MOONS PCI TV CAPTURE CARD [1131:2001] + 18 -> BMK MPEX No Tuner + 19 -> Compro VideoMate TV [185b:c100] + 20 -> Matrox CronosPlus [PCI_VENDOR_ID_MATROX:48d0] + 21 -> Medion 2819 [1461:a70b] + 22 -> BMK MPEX Tuner diff --git a/linux/Documentation/video4linux/CARDLIST.tuner b/linux/Documentation/video4linux/CARDLIST.tuner new file mode 100644 index 000000000..64e79fb87 --- /dev/null +++ b/linux/Documentation/video4linux/CARDLIST.tuner @@ -0,0 +1,43 @@ +tuner=0 - Temic PAL (4002 FH5) +tuner=1 - Philips PAL_I (FI1246 and compatibles) +tuner=2 - Philips NTSC (FI1236,FM1236 and compatibles) +tuner=3 - Philips (SECAM+PAL_BG) (FI1216MF, FM1216MF, FR1216MF) +tuner=4 - NoTuner +tuner=5 - Philips PAL_BG (FI1216 and compatibles) +tuner=6 - Temic NTSC (4032 FY5) +tuner=7 - Temic PAL_I (4062 FY5) +tuner=8 - Temic NTSC (4036 FY5) +tuner=9 - Alps HSBH1 +tuner=10 - Alps TSBE1 +tuner=11 - Alps TSBB5 +tuner=12 - Alps TSBE5 +tuner=13 - Alps TSBC5 +tuner=14 - Temic PAL_BG (4006FH5) +tuner=15 - Alps TSCH6 +tuner=16 - Temic PAL_DK (4016 FY5) +tuner=17 - Philips NTSC_M (MK2) +tuner=18 - Temic PAL_I (4066 FY5) +tuner=19 - Temic PAL* auto (4006 FN5) +tuner=20 - Temic PAL_BG (4009 FR5) or PAL_I (4069 FR5) +tuner=21 - Temic NTSC (4039 FR5) +tuner=22 - Temic PAL/SECAM multi (4046 FM5) +tuner=23 - Philips PAL_DK (FI1256 and compatibles) +tuner=24 - Philips PAL/SECAM multi (FQ1216ME) +tuner=25 - LG PAL_I+FM (TAPC-I001D) +tuner=26 - LG PAL_I (TAPC-I701D) +tuner=27 - LG NTSC+FM (TPI8NSR01F) +tuner=28 - LG PAL_BG+FM (TPI8PSB01D) +tuner=29 - LG PAL_BG (TPI8PSB11D) +tuner=30 - Temic PAL* auto + FM (4009 FN5) +tuner=31 - SHARP NTSC_JP (2U5JF5540) +tuner=32 - Samsung PAL TCPM9091PD27 +tuner=33 - MT2032 universal +tuner=34 - Temic PAL_BG (4106 FH5) +tuner=35 - Temic PAL_DK/SECAM_L (4012 FY5) +tuner=36 - Temic NTSC (4136 FY5) +tuner=37 - LG PAL (newer TAPC series) +tuner=38 - Philips PAL/SECAM multi (FM1216ME MK3) +tuner=39 - LG NTSC (newer TAPC series) +tuner=40 - HITACHI V7-J180AT +tuner=41 - Philips PAL_MK (FI1216 MK) +tuner=42 - Philips 1236D ATSC/NTSC daul in diff --git a/linux/Documentation/video4linux/README.cx88 b/linux/Documentation/video4linux/README.cx88 new file mode 100644 index 000000000..f338ce694 --- /dev/null +++ b/linux/Documentation/video4linux/README.cx88 @@ -0,0 +1,69 @@ + +cx8800 release notes +==================== + +This is a v4l2 device driver for the cx2388x chip. + + +current status +============== + +video + - Basically works. + - Some minor image quality glitches. + - Eed and blue are swapped sometimes for not-yet known + reasons (seems to depend on the image size, try to resize + your tv app window as workaround ...). + - For now only capture, overlay support isn't completed yet. + +audio + - The chip specs for the on-chip TV sound decoder are next + to useless :-/ + - Neverless the builtin TV sound decoder starts working now. + - Most tuner chips do provide mono sound, which may or may not + be useable depending on the board design. With the Hauppauge + cards it works, so there is mono sound available as fallback. + - audio data dma (i.e. recording without loopback cable to the + sound card), but there is no code yet ... + +vbi + - some code present. Doesn't crash any more, but also doesn't + work yet ... + + +how to add support for new cards +================================ + +The driver needs some config info for the TV cards. This stuff is in +cx88-cards.c. If the driver doesn't work well you likely need a new +entry for your card in that file. Check the kernel log (using dmesg) +to see whenever the driver knows your card or not. There is a line +like this one: + + cx8800[0]: subsystem: 0070:3400, board: Hauppauge WinTV \ + 34xxx models [card=1,autodetected] + +If your card is listed as "board: UNKNOWN/GENERIC" it is unknown to +the driver. What to do then? + + (1) Try upgrading to the latest snapshot, maybe it has been added + meanwhile. + (2) You can try to create a new entry yourself, have a look at + cx88-cards.c. If that worked, mail me your changes as unified + diff ("diff -u"). + (3) Or you can mail me the config information. I need at least the + following informations to add the card: + + * the PCI Subsystem ID ("0070:3400" from the line above, + "lspci -v" output is fine too). + * the tuner type used by the card. You can try to find one by + trial-and-error using the tuner=<n> insmod option. If you + know which one the card has you can also have a look at the + list in CARDLIST.tuner + +Have fun, + + Gerd + +-- +Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] diff --git a/linux/Documentation/video4linux/README.saa7134 b/linux/Documentation/video4linux/README.saa7134 new file mode 100644 index 000000000..1a446c653 --- /dev/null +++ b/linux/Documentation/video4linux/README.saa7134 @@ -0,0 +1,73 @@ + + +What is it? +=========== + +This is a v4l2/oss device driver for saa7130/33/34/35 based capture / TV +boards. See http://www.semiconductors.philips.com/pip/saa7134hl for a +description. + + +Status +====== + +Almost everything is working. video, sound, tuner, radio, mpeg ts, ... + +As with bttv, card-specific tweaks are needed. Check CARDLIST for a +list of known TV cards and saa7134-cards.c for the drivers card +configuration info. + + +Build +===== + +Pick up videodev + v4l2 patches from http://bytesex.org/patches/. +Configure, build, install + boot the new kernel. You'll need at least +these config options: + + CONFIG_I2C=m + CONFIG_VIDEO_DEV=m + +Type "make" to build the driver now. "make install" installs the +driver. "modprobe saa7134" should load it. Depending on the card you +might have to pass card=<nr> as insmod option, check CARDLIST for +valid choices. + + +Changes / Fixes +=============== + +Please mail me unified diffs ("diff -u") with your changes, and don't +forget to tell me what it changes / which problem it fixes / whatever +it is good for ... + + +Known Problems +============== + +* The tuner for the flyvideos isn't detected automatically and the + default might not work for you depending on which version you have. + There is a tuner= insmod option to override the driver's default. + +Card Variations: +================ + +Cards can use either of these two crystals (xtal): + - 32.11 MHz -> .audio_clock=0x187de7 + - 24.576MHz -> .audio_clock=0x200000 +(xtal * .audio_clock = 51539600) + + +Credits +======= + +andrew.stevens@philips.com + werner.leeb@philips.com for providing +saa7134 hardware specs and sample board. + + +Have fun, + + Gerd + +-- +Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] diff --git a/linux/Documentation/video4linux/bttv/Cards b/linux/Documentation/video4linux/bttv/Cards new file mode 100644 index 000000000..7865e56c5 --- /dev/null +++ b/linux/Documentation/video4linux/bttv/Cards @@ -0,0 +1,961 @@ + +Gunther Mayer's bttv card gallery (graphical version of this text file :-) +is available at: http://mayerg.gmxhome.de/bttv/bttv-gallery.html + + +Suppported cards: +Bt848/Bt848a/Bt849/Bt878/Bt879 cards +------------------------------------ + +All cards with Bt848/Bt848a/Bt849/Bt878/Bt879 and normal +Composite/S-VHS inputs are supported. Teletext and Intercast support +(PAL only) for ALL cards via VBI sample decoding in software. + +Some cards with additional multiplexing of inputs or other additional +fancy chips are only partially supported (unless specifications by the +card manufacturer are given). When a card is listed here it isn't +necessarily fully supported. + +All other cards only differ by additional components as tuners, sound +decoders, EEPROMs, teletext decoders ... + + +Unsupported Cards: +------------------ + +Cards with Zoran (ZR) or Philips (SAA) or ISA are not supported by +this driver. + + +MATRIX Vision +------------- + +MV-Delta +- Bt848A +- 4 Composite inputs, 1 S-VHS input (shared with 4th composite) +- EEPROM + +http://www.matrix-vision.de/ + +This card has no tuner but supports all 4 composite (1 shared with an +S-VHS input) of the Bt848A. +Very nice card if you only have satellite TV but several tuners connected +to the card via composite. + +Many thanks to Matrix-Vision for giving us 2 cards for free which made +Bt848a/Bt849 single crytal operation support possible!!! + + + +Miro/Pinnacle PCTV +------------------ + +- Bt848 + some (all??) come with 2 crystals for PAL/SECAM and NTSC +- PAL, SECAM or NTSC TV tuner (Philips or TEMIC) +- MSP34xx sound decoder on add on board + decoder is supported but AFAIK does not yet work + (other sound MUX setting in GPIO port needed??? somebody who fixed this???) +- 1 tuner, 1 composite and 1 S-VHS input +- tuner type is autodetected + +http://www.miro.de/ +http://www.miro.com/ + + +Many thanks for the free card which made first NTSC support possible back +in 1997! + + +Hauppauge Win/TV pci +-------------------- + +There are many different versions of the Hauppauge cards with different +tuners (TV+Radio ...), teletext decoders. +Note that even cards with same model numbers have (depending on the revision) +different chips on it. + +- Bt848 (and others but always in 2 crystal operation???) + newer cards have a Bt878 +- PAL, SECAM, NTSC or tuner with or without Radio support + +e.g.: + PAL: + TDA5737: VHF, hyperband and UHF mixer/oscillator for TV and VCR 3-band tuners + TSA5522: 1.4 GHz I2C-bus controlled synthesizer, I2C 0xc2-0xc3 + + NTSC: + TDA5731: VHF, hyperband and UHF mixer/oscillator for TV and VCR 3-band tuners + TSA5518: no datasheet available on Philips site +- Philips SAA5246 or SAA5284 ( or no) Teletext decoder chip + with buffer RAM (e.g. Winbond W24257AS-35: 32Kx8 CMOS static RAM) + SAA5246 (I2C 0x22) is supported +- 256 bytes EEPROM: Microchip 24LC02B or Philips 8582E2Y + with configuration information + I2C address 0xa0 (24LC02B also responds to 0xa2-0xaf) +- 1 tuner, 1 composite and (depending on model) 1 S-VHS input +- 14052B: mux for selection of sound source +- sound decoder: TDA9800, MSP34xx (stereo cards) + + +Askey CPH-Series +---------------- +Developed by TelSignal(?), OEMed by many vendors (Typhoon, Anubis, Dynalink) + + Card series: + CPH01x: BT848 capture only + CPH03x: BT848 + CPH05x: BT878 with FM + CPH06x: BT878 (w/o FM) + CPH07x: BT878 capture only + + TV standards: + CPH0x0: NTSC-M/M + CPH0x1: PAL-B/G + CPH0x2: PAL-I/I + CPH0x3: PAL-D/K + CPH0x4: SECAM-L/L + CPH0x5: SECAM-B/G + CPH0x6: SECAM-D/K + CPH0x7: PAL-N/N + CPH0x8: PAL-B/H + CPH0x9: PAL-M/M + + CPH03x was often sold as "TV capturer". + + Identifying: + 1) 878 cards can be identified by PCI Subsystem-ID: + 144f:3000 = CPH06x + 144F:3002 = CPH05x w/ FM + 144F:3005 = CPH06x_LC (w/o remote control) + 1) The cards have a sticker with "CPH"-model on the back. + 2) These cards have a number printed on the PCB just above the tuner metal box: + "80-CP2000300-x" = CPH03X + "80-CP2000500-x" = CPH05X + "80-CP2000600-x" = CPH06X / CPH06x_LC + + Askey sells these cards as "Magic TView series", Brand "MagicXpress". + Other OEM often call these "Tview", "TView99" or else. + +Lifeview Flyvideo Series: +------------------------- + The naming of these series differs in time and space. + + Identifying: + 1) Some models can be identified by PCI subsystem ID: + 1852:1852 = Flyvideo 98 FM + 1851:1850 = Flyvideo 98 + 1851:1851 = Flyvideo 98 EZ (capture only) + 2) There is a print on the PCB: + LR25 = Flyvideo (Zoran ZR36120, SAA7110A) + LR26 Rev.N = Flyvideo II (Bt848) + Rev.O = Flyvideo II (Bt878) + LR37 Rev.C = Flyvideo EZ (Capture only, ZR36120 + SAA7110) + LR38 Rev.A1= Flyvideo II EZ (Bt848 capture only) + LR50 Rev.Q = Flyvideo 98 (w/eeprom and PCI subsystem ID) + Rev.W = Flyvideo 98 (no eeprom) + LR51 Rev.E = Flyvideo 98 EZ (capture only) + LR90 = Flyvideo 2000 (Bt878) + Flyvideo 2000S (Bt878) w/Stereo TV (Package incl. LR91 daughterboard) + LR91 = Stereo daughter card for LR90 + LR97 = Flyvideo DVBS + LR99 Rev.E = Low profile card for OEM integration (only internal audio!) bt878 + LR136 = Flyvideo 2100/3100 (Low profile, SAA7130/SAA7134) + LR137 = Flyvideo DV2000/DV3000 (SAA7130/SAA7134 + IEEE1394) + LR138 Rev.C= Flyvideo 2000 (SAA7130) + or Flyvideo 3000 (SAA7134) w/Stereo TV + These exist in variations w/FM and w/Remote sometimes denoted + by suffixes "FM" and "R". + + Lifeview.com.tw states (Feb. 2002): + "The FlyVideo2000 and FlyVideo2000s product name have renamed to FlyVideo98." + Their Bt8x8 cards are listed as discontinued. + Flyvideo 2000S was probably sold as Flyvideo 3000 in some contries(Europe?). + The new Flyvideo 2000/3000 are SAA7130/SAA7134 based. + + "Flyvideo II" had been the name for the 848 cards, nowadays (in Germany) + this name is re-used for LR50 Rev.W. + The Lifeview website mentioned Flyvideo III at some time, but such a card + has not yet been seen (perhaps it was the german name for LR90 [stereo]). + These cards are sold by many OEMs too. + + FlyVideo A2 (Elta 8680)= LR90 Rev.F (w/Remote, w/o FM, stereo TV by tda9821) {Germany} + Lifeview 3000 (Elta 8681) as sold by Plus(April 2002), Germany = LR138 w/ saa7134 + + +Typhoon TV card series: +----------------------- + These can be CPH, Flyvideo, Pixelview or KNC1 series. + Typhoon is the brand of Anubis. + Model 50680 got re-used, some model no. had different contents over time. + + Models: + 50680 "TV Tuner PCI Pal BG"(old,red package)=can be CPH03x(bt848) or CPH06x(bt878) + 50680 "TV Tuner Pal BG" (blue package)= Pixelview PV-BT878P+ (Rev 9B) + 50681 "TV Tuner PCI Pal I" (variant of 50680) + 50682 "TView TV/FM Tuner Pal BG" = Flyvideo 98FM (LR50 Rev.Q) + Note: The package has a picture of CPH05x (which would be a real TView) + 50683 "TV Tuner PCI SECAM" (variant of 50680) + 50684 "TV Tuner Pal BG" = Pixelview 878TV(Rev.3D) + 50686 "TV Tuner" = KNC1 TV Station + 50687 "TV Tuner stereo" = KNC1 TV Station pro + 50688 "TV Tuner RDS" (black package) = KNC1 TV Station RDS + 50689 TV SAT DVB-S CARD CI PCI (SAA7146AH, SU1278?) = "KNC1 TV Station DVB-S" + 50692 "TV/FM Tuner" (small PCB) + 50694 TV TUNER CARD RDS (PHILIPS CHIPSET SAA7134HL) + 50696 TV TUNER STEREO (PHILIPS CHIPSET SAA7134HL, MK3ME Tuner) + 50804 PC-SAT TV/Audio Karte = Techni-PC-Sat (ZORAN 36120PQC, Tuner:Alps) + 50866 TVIEW SAT RECEIVER+ADR + 50868 "TV/FM Tuner Pal I" (variant of 50682) + 50999 "TV/FM Tuner Secam" (variant of 50682) + + +Guillemot +--------- + Maxi-TV PCI (ZR36120) + Maxi TV Video 2 = LR50 Rev.Q (FI1216MF, PAL BG+SECAM) + Maxi TV Video 3 = CPH064 (PAL BG + SECAM) + +Mentor +------ + Mentor TV card ("55-878TV-U1") = Pixelview 878TV(Rev.3F) (w/FM w/Remote) + +Prolink +------- + TV cards: + PixelView Play TV pro - (Model: PV-BT878P+ REV 8E) + PixelView Play TV pro - (Model: PV-BT878P+ REV 9D) + PixelView Play TV pro - (Model: PV-BT878P+ REV 4C / 8D / 10A ) + PixelView Play TV - (Model: PV-BT848P+) + 878TV - (Model: PV-BT878TV) + + Multimedia TV packages (card + software pack): + PixelView Play TV Theater - (Model: PV-M4200) = PixelView Play TV pro + Software + PixelView Play TV PAK - (Model: PV-BT878P+ REV 4E) + PixelView Play TV/VCR - (Model: PV-M3200 REV 4C / 8D / 10A ) + PixelView Studio PAK - (Model: M2200 REV 4C / 8D / 10A ) + PixelView PowerStudio PAK - (Model: PV-M3600 REV 4E) + PixelView DigitalVCR PAK - (Model: PV-M2400 REV 4C / 8D / 10A ) + + PixelView PlayTV PAK II (TV/FM card + usb camera) PV-M3800 + PixelView PlayTV XP PV-M4700,PV-M4700(w/FM) + PixelView PlayTV DVR PV-M4600 package contents:PixelView PlayTV pro, windvr & videoMail s/w + + Further Cards: + PV-BT878P+rev.9B (Play TV Pro, opt. w/FM w/NICAM) + PV-BT878P+rev.2F + PV-BT878P Rev.1D (bt878, capture only) + + XCapture PV-CX881P (cx23881) + PlayTV HD PV-CX881PL+, PV-CX881PL+(w/FM) (cx23881) + + DTV3000 PV-DTV3000P+ DVB-S CI = Twinhan VP-1030 + DTV2000 DVB-S = Twinhan VP-1020 + + Video Conferencing: + PixelView Meeting PAK - (Model: PV-BT878P) + PixelView Meeting PAK Lite - (Model: PV-BT878P) + PixelView Meeting PAK plus - (Model: PV-BT878P+rev 4C/8D/10A) + PixelView Capture - (Model: PV-BT848P) + + PixelView PlayTV USB pro + Model No. PV-NT1004+, PV-NT1004+ (w/FM) = NT1004 USB decoder chip + SAA7113 video decoder chip + +Dynalink +-------- + These are CPH series. + +Phoebemicro +----------- + TV Master = CPH030 or CPH060 + TV Master FM = CPH050 + +Genius/Kye +---------- + Video Wonder/Genius Internet Video Kit = LR37 Rev.C + Video Wonder Pro II (848 or 878) = LR26 + +Tekram +------ + VideoCap C205 (Bt848) + VideoCap C210 (zr36120 +Philips) + CaptureTV M200 (ISA) + CaptureTV M205 (Bt848) + +Lucky Star +---------- + Image World Conference TV = LR50 Rev. Q + +Leadtek +------- + WinView 601 (Bt848) + WinView 610 (Zoran) + WinFast2000 + WinFast2000 XP + +KNC One +------- + TV-Station + TV-Station SE (+Software Bundle) + TV-Station pro (+TV stereo) + TV-Station FM (+Radio) + TV-Station RDS (+RDS) + TV Station SAT (analog satellite) + TV-Station DVB-S + + newer Cards have saa7134, but model name stayed the same? + +Provideo +-------- + PV951 or PV-951 (also are sold as: + Boeder TV-FM Video Capture Card + Titanmedia Supervision TV-2400 + Provideo PV951 TF + 3DeMon PV951 + MediaForte TV-Vision PV951 + Yoko PV951 + Vivanco Tuner Card PCI Art.-Nr.: 68404 + ) now named PV-951T + + Surveillance Series + PV-141 + PV-143 + PV-147 + PV-148 (capture only) + PV-150 + PV-151 + + TV-FM Tuner Series + PV-951TDV (tv tuner + 1394) + PV-951T/TF + PV-951PT/TF + PV-956T/TF Low Profile + PV-911 + +Highscreen +---------- + TV Karte = LR50 Rev.S + TV-Boostar = Terratec Terra TV+ Version 1.0 (Bt848, tda9821) "ceb105.pcb" + +Zoltrix +------- + Face to Face Capture (Bt848 capture only) (PCB "VP-2848") + Face To Face TV MAX (Bt848) (PCB "VP-8482 Rev1.3") + Genie TV (Bt878) (PCB "VP-8790 Rev 2.1") + Genie Wonder Pro + +AVerMedia +--------- + AVer FunTV Lite (ISA, AV3001 chipset) "M101.C" + AVerTV + AVerTV Stereo + AVerTV Studio (w/FM) + AVerMedia TV98 with Remote + AVerMedia TV/FM98 Stereo + AVerMedia TVCAM98 + TVCapture (Bt848) + TVPhone (Bt848) + TVCapture98 (="AVerMedia TV98" in USA) (Bt878) + TVPhone98 (Bt878, w/FM) + + PCB PCI-ID Model-Name Eeprom Tuner Sound Country + -------------------------------------------------------------------- + M101.C ISA ! + M108-B Bt848 -- FR1236 US (2),(3) + M1A8-A Bt848 AVer TV-Phone FM1216 -- + M168-T 1461:0003 AVerTV Studio 48:17 FM1216 TDA9840T D (1) w/FM w/Remote + M168-U 1461:0004 TVCapture98 40:11 FI1216 -- D w/Remote + M168II-B 1461:0003 Medion MD9592 48:16 FM1216 TDA9873H D w/FM + + (1) Daughterboard MB68-A with TDA9820T and TDA9840T + (2) Sony NE41S soldered (stereo sound?) + (3) Daughterboard M118-A w/ pic 16c54 and 4 MHz quartz + + US site has different drivers for (as of 09/2002): + EZ Capture/InterCam PCI (BT-848 chip) + EZ Capture/InterCam PCI (BT-878 chip) + TV-Phone (BT-848 chip) + TV98 (BT-848 chip) + TV98 With Remote (BT-848 chip) + TV98 (BT-878 chip) + TV98 With Remote (BT-878) + TV/FM98 (BT-878 chip) + AVerTV + AverTV Stereo + AVerTV Studio + + DE hat diverse Treiber fuer diese Modelle (Stand 09/2002): + TVPhone (848) mit Philips tuner FR12X6 (w/ FM radio) + TVPhone (848) mit Philips tuner FM12X6 (w/ FM radio) + TVCapture (848) w/Philips tuner FI12X6 + TVCapture (848) non-Philips tuner + TVCapture98 (Bt878) + TVPhone98 (Bt878) + AVerTV und TVCapture98 w/VCR (Bt 878) + AVerTVStudio und TVPhone98 w/VCR (Bt878) + AVerTV GO Serie (Kein SVideo Input) + AVerTV98 (BT-878 chip) + AVerTV98 mit Fernbedienung (BT-878 chip) + AVerTV/FM98 (BT-878 chip) + + VDOmate (www.averm.com.cn) = M168U ? + +Aimslab +------- + Video Highway or "Video Highway TR200" (ISA) + Video Highway Xtreme (aka "VHX") (Bt848, FM w/ TEA5757) + +IXMicro (former: IMS=Integrated Micro Solutions) +------- + IXTV BT848 (=TurboTV) + IXTV BT878 + IMS TurboTV (Bt848) + +Lifetec/Medion/Tevion/Aldi +-------------------------- + LT9306/MD9306 = CPH061 + LT9415/MD9415 = LR90 Rev.F or Rev.G + MD9592 = Avermedia TVphone98 (PCI_ID=1461:0003), PCB-Rev=M168II-B (w/TDA9873H) + MD9717 = KNC One (Rev D4, saa7134, FM1216 MK2 tuner) + MD5044 = KNC One (Rev D4, saa7134, FM1216ME MK3 tuner) + +Modular Technologies (www.modulartech.com) UK +--------------------------------------------- + MM100 PCTV (Bt848) + MM201 PCTV (Bt878, Bt832) w/ Quartzsight camera + MM202 PCTV (Bt878, Bt832, tda9874) + MM205 PCTV (Bt878) + MM210 PCTV (Bt878) (Galaxy TV, Galaxymedia ?) + +Terratec +-------- + Terra TV+ Version 1.0 (Bt848), "ceb105.PCB" printed on the PCB, TDA9821 + Terra TV+ Version 1.1 (Bt878), "LR74 Rev.E" printed on the PCB, TDA9821 + Terra TValueRadio, "LR102 Rev.C" printed on the PCB + Terra TV/Radio+ Version 1.0, "80-CP2830100-0" TTTV3 printed on the PCB, + "CPH010-E83" on the back, SAA6588T, TDA9873H + Terra TValue Version BT878, "80-CP2830110-0 TTTV4" printed on the PCB, + "CPH011-D83" on back + Terra TValue Version 1.0 "ceb105.PCB" (really identical to Terra TV+ Version 1.0) + Terra TValue New Revision "LR102 Rec.C" + Terra Active Radio Upgrade (tea5757h, saa6588t) + + LR74 is a newer PCB revision of ceb105 (both incl. connector for Active Radio Upgrade) + + Cinergy 400 (saa7134), "E877 11(S)", "PM820092D" printed on PCB + Cinergy 600 (saa7134) + +Technisat +--------- + Discos ADR PC-Karte ISA (no TV!) + Discos ADR PC-Karte PCI (probably no TV?) + Techni-PC-Sat (Sat. analog) + Rev 1.2 (zr36120, vpx3220, stv0030, saa5246, BSJE3-494A) + Mediafocus I (zr36120/zr36125, drp3510, Sat. analog + ADR Radio) + Mediafocus II (saa7146, Sat. analog) + SatADR Rev 2.1 (saa7146a, saa7113h, stv0056a, msp3400c, drp3510a, BSKE3-307A) + SkyStar 1 DVB (AV7110) = Technotrend Premium + SkyStar 2 DVB (B2C2) (=Sky2PC) + +Siemens +------- + Multimedia eXtension Board (MXB) (SAA7146, SAA7111) + +Stradis +------- + SDM275,SDM250,SDM026,SDM025 (SAA7146, IBMMPEG2): MPEG2 decoder only + +Powercolor +---------- + MTV878 + Package comes with different contents: + a) pcb "MTV878" (CARD=75) + b) Pixelview Rev. 4_ + MTV878R w/Remote Control + MTV878F w/Remote Control w/FM radio + +Pinnacle +-------- + Mirovideo PCTV (Bt848) + Mirovideo PCTV SE (Bt848) + Mirovideo PCTV Pro (Bt848 + Daughterboard for TV Stereo and FM) + Studio PCTV Rave (Bt848 Version = Mirovideo PCTV) + Studio PCTV Rave (Bt878 package w/o infrared) + Studio PCTV (Bt878) + Studio PCTV Pro (Bt878 stereo w/ FM) + Pinnacle PCTV (Bt878, MT2032) + Pinnacle PCTV Pro (Bt878, MT2032) + Pinncale PCTV Sat (bt878a, HM1821/1221) ["Conexant CX24110 with CX24108 tuner, aka HM1221/HM1811"] + Pinnacle PCTV Sat XE + + M(J)PEG capture and playback: + DC1+ (ISA) + DC10 (zr36057, zr36060, saa7110, adv7176) + DC10+ (zr36067, zr36060, saa7110, adv7176) + DC20 (ql16x24b,zr36050, zr36016, saa7110, saa7187 ...) + DC30 (zr36057, zr36050, zr36016, vpx3220, adv7176, ad1843, tea6415, miro FST97A1) + DC30+ (zr36067, zr36050, zr36016, vpx3220, adv7176) + DC50 (zr36067, zr36050, zr36016, saa7112, adv7176 (2 pcs.?), ad1843, miro FST97A1, Lattice ???) + +Lenco +----- + MXR-9565 (=Technisat Mediafocus?) + MXR-9571 (Bt848) (=CPH031?) + MXR-9575 + MXR-9577 (Bt878) (=Prolink 878TV Rev.3x) + MXTV-9578CP (Bt878) (= Prolink PV-BT878P+4E) + +Iomega +------ + Buz (zr36067, zr36060, saa7111, saa7185) + +LML +--- + LML33 (zr36067, zr36060, bt819, bt856) + +Grandtec +-------- + Grand Video Capture (Bt848) + Multi Capture Card (Bt878) + +Koutech +------- + KW-606 (Bt848) + KW-607 (Bt848 capture only) + KW-606RSF + KW-607A (capture only) + KW-608 (Zoran capture only) + +IODATA (jp) +------ + GV-BCTV/PCI + GV-BCTV2/PCI + GV-BCTV3/PCI + GV-BCTV4/PCI + GV-VCP/PCI (capture only) + GV-VCP2/PCI (capture only) + +Canopus (jp) +------- + WinDVR = Kworld "KW-TVL878RF" + +www.sigmacom.co.kr +------------------ + Sigma Cyber TV II + +www.sasem.co.kr +--------------- + Litte OnAir TV + +hama +---- + TV/Radio-Tuner Card, PCI (Model 44677) = CPH051 + +Sigma Designs +------------- + Hollywood plus (em8300, em9010, adv7175), (PCB "M340-10") MPEG DVD decoder + +Formac +------ + iProTV (Card for iMac Mezzanine slot, Bt848+SCSI) + ProTV (Bt848) + ProTV II = ProTV Stereo (Bt878) ["stereo" means FM stereo, tv is still mono] + +ATI +--- + TV-Wonder + TV-Wonder VE + +Diamond Multimedia +------------------ + DTV2000 (Bt848, tda9875) + +Aopen +----- + VA1000 Plus (w/ Stereo) + VA1000 Lite + VA1000 (=LR90) + +Intel +----- + Smart Video Recorder (ISA full-length) + Smart Video Recorder pro (ISA half-length) + Smart Video Recorder III (Bt848) + +STB +--- + STB Gateway 6000704 (bt878) + STB Gateway 6000699 (bt848) + STB Gateway 6000402 (bt848) + STB TV130 PCI + +Videologic +---------- + Captivator Pro/TV (ISA?) + Captivator PCI/VC (Bt848 bundled with camera) (capture only) + +Technotrend +------------ + TT-SAT PCI (PCB "Sat-PCI Rev.:1.3.1"; zr36125, vpx3225d, stc0056a, Tuner:BSKE6-155A + TT-DVB-Sat + revisions 1.1, 1.3, 1.5, 1.6 and 2.1 + This card is sold as OEM from: + Siemens DVB-s Card + Hauppauge WinTV DVB-S + Technisat SkyStar 1 DVB + Galaxis DVB Sat + Now this card is called TT-PCline Premium Family + TT-Budget (saa7146, bsru6-701a) + This card is sold as OEM from: + Hauppauge WinTV Nova + Satelco Standard PCI (DVB-S) + TT-DVB-C PCI + +Teles +----- + DVB-s (Rev. 2.2, BSRV2-301A, data only?) + +Remote Vision +------------- + MX RV605 (Bt848 capture only) + +Boeder +------ + PC ChatCam (Model 68252) (Bt848 capture only) + Tv/Fm Capture Card (Model 68404) = PV951 + +Media-Surfer (esc-kathrein.de) +------------------------------- + Sat-Surfer (ISA) + Sat-Surfer PCI = Techni-PC-Sat + Cable-Surfer 1 + Cable-Surfer 2 + Cable-Surfer PCI (zr36120) + Audio-Surfer (ISA Radio card) + +Jetway (www.jetway.com.tw) +-------------------------- + JW-TV 878M + JW-TV 878 = KWorld KW-TV878RF + +Galaxis +------- + Galaxis DVB Card S CI + Galaxis DVB Card C CI + Galaxis DVB Card S + Galaxis DVB Card C + Galaxis plug.in S [neuer Name: Galaxis DVB Card S CI + +Hauppauge +--------- + many many WinTV models ... + WinTV DVBs = Technotrend Premium 1.3 + WinTV NOVA = Technotrend Budget 1.1 "S-DVB DATA" + WinTV NOVA-CI "SDVBACI" + WinTV Nova USB (=Technotrend USB 1.0) + WinTV-Nexus-s (=Technotrend Premium 2.1 or 2.2) + WinTV PVR + WinTV PVR 250 + WinTV PVR 450 + + US models + 990 WinTV-PVR-350 (249USD) (iTVC15 chipset + radio) + 980 WinTV-PVR-250 (149USD) (iTVC15 chipset) + 880 WinTV-PVR-PCI (199USD) (KFIR chipset + bt878) + 881 WinTV-PVR-USB + 190 WinTV-GO + 191 WinTV-GO-FM + 404 WinTV + 401 WinTV-radio + 495 WinTV-Theater + 602 WinTV-USB + 621 WinTV-USB-FM + 600 USB-Live + 698 WinTV-HD + 697 WinTV-D + 564 WinTV-Nexus-S + + Deutsche Modelle + 603 WinTV GO + 719 WinTV Primio-FM + 718 WinTV PCI-FM + 497 WinTV Theater + 569 WinTV USB + 568 WinTV USB-FM + 882 WinTV PVR + 981 WinTV PVR 250 + 891 WinTV-PVR-USB + 541 WinTV Nova + 488 WinTV Nova-Ci + 564 WinTV-Nexus-s + 727 WinTV-DVB-c + 545 Common Interface + 898 WinTV-Nova-USB + + UK models + 607 WinTV Go + 693,793 WinTV Primio FM + 647,747 WinTV PCI FM + 498 WinTV Theater + 883 WinTV PVR + 893 WinTV PVR USB (Duplicate entry) + 566 WinTV USB (UK) + 573 WinTV USB FM + 429 Impact VCB (bt848) + 600 USB Live (Video-In 1x Comp, 1xSVHS) + 542 WinTV Nova + 717 WinTV DVB-S + 909 Nova-t PCI + 893 Nova-t USB (Duplicate entry) + 802 MyTV + 804 MyView + 809 MyVideo + 872 MyTV2Go FM + + + 546 WinTV Nova-S CI + 543 WinTV Nova + 907 Nova-S USB + 908 Nova-T USB + 717 WinTV Nexus-S + 157 DEC3000-s Standalone + USB + + Spain + 685 WinTV-Go + 690 WinTV-PrimioFM + 416 WinTV-PCI Nicam Estereo + 677 WinTV-PCI-FM + 699 WinTV-Theater + 683 WinTV-USB + 678 WinTV-USB-FM + 983 WinTV-PVR-250 + 883 WinTV-PVR-PCI + 993 WinTV-PVR-350 + 893 WinTV-PVR-USB + 728 WinTV-DVB-C PCI + 832 MyTV2Go + 869 MyTV2Go-FM + 805 MyVideo (USB) + + +Matrix-Vision +------------- + MATRIX-Vision MV-Delta + MATRIX-Vision MV-Delta 2 + MVsigma-SLC (Bt848) + +Conceptronic (.net) +------------ + TVCON FM, TV card w/ FM = CPH05x + TVCON = CPH06x + +BestData +-------- + HCC100 = VCC100rev1 + camera + VCC100 rev1 (bt848) + VCC100 rev2 (bt878) + +Gallant (www.gallantcom.com) www.minton.com.tw +----------------------------------------------- + Intervision IV-510 (capture only bt8x8) + Intervision IV-550 (bt8x8) + Intervision IV-100 (zoran) + Intervision IV-1000 (bt8x8) + +Asonic (www.asonic.com.cn) (website down) +----------------------------------------- + SkyEye tv 878 + +Hoontech +-------- + 878TV/FM + +Teppro (www.itcteppro.com.tw) +----------------------------- + ITC PCITV (Card Ver 1.0) "Teppro TV1/TVFM1 Card" + ITC PCITV (Card Ver 2.0) + ITC PCITV (Card Ver 3.0) = "PV-BT878P+ (REV.9D)" + ITC PCITV (Card Ver 4.0) + TEPPRO IV-550 (For BT848 Main Chip) + ITC DSTTV (bt878, satellite) + ITC VideoMaker (saa7146, StreamMachine sm2110, tvtuner) "PV-SM2210P+ (REV:1C)" + +Kworld (www.kworld.com.tw) +-------------------------- + PC TV Station + KWORLD KW-TV878R TV (no radio) + KWORLD KW-TV878RF TV (w/ radio) + + KWORLD KW-TVL878RF (low profile) + + KWORLD KW-TV713XRF (saa7134) + + + MPEG TV Station (same cards as above plus WinDVR Software MPEG en/decoder) + KWORLD KW-TV878R -Pro TV (no Radio) + KWORLD KW-TV878RF-Pro TV (w/ Radio) + KWORLD KW-TV878R -Ultra TV (no Radio) + KWORLD KW-TV878RF-Ultra TV (w/ Radio) + + + +JTT/ Justy Corp.http://www.justy.co.jp/ (www.jtt.com.jp website down) +--------------------------------------------------------------------- + JTT-02 (JTT TV) "TV watchmate pro" (bt848) + +ADS www.adstech.com +------------------- + Channel Surfer TV ( CHX-950 ) + Channel Surfer TV+FM ( CHX-960FM ) + +AVEC www.prochips.com +--------------------- + AVEC Intercapture (bt848, tea6320) + +NoBrand +------- + TV Excel = Australian Name for "PV-BT878P+ 8E" or "878TV Rev.3_" + +Mach www.machspeed.com +---- + Mach TV 878 + +Eline www.eline-net.com/ +----- + Eline Vision TVMaster / TVMaster FM (ELV-TVM/ ELV-TVM-FM) = LR26 (bt878) + Eline Vision TVMaster-2000 (ELV-TVM-2000, ELV-TVM-2000-FM)= LR138 (saa713x) + +Spirit http://www.spiritmodems.com.au/ +------ + Spirit TV Tuner/Video Capture Card (bt848) + +Boser www.boser.com.tw +----- + HS-878 Mini PCI Capture Add-on Card + HS-879 Mini PCI 3D Audio and Capture Add-on Card (w/ ES1938 Solo-1) + +Satelco www.citycom-gmbh.de, www.satelco.de +------- + TV-FM =KNC1 saa7134 + Standard PCI (DVB-S) = Technotrend Budget + Standard PCI (DVB-S) w/ CI + Satelco Highend PCI (DVB-S) = Technotrend Premium + + +Sensoray www.sensoray.com +-------- + Sensoray 311 (PC/104 bus) + Sensoray 611 (PCI) + +CEI (Chartered Electronics Industries Pte Ltd [CEI] [FCC ID HBY]) +--- + TV Tuner - HBY-33A-RAFFLES Brooktree Bt848KPF + Philips + TV Tuner MG9910 - HBY33A-TVO CEI + Philips SAA7110 + OKI M548262 + ST STV8438CV + Primetime TV (ISA) + acquired by Singapore Technologies + now operating as Chartered Semiconductor Manufacturing + Manufacturer of video cards is listed as: + Cogent Electronics Industries [CEI] + +AITech +------ + Wavewatcher TV (ISA) + AITech WaveWatcher TV-PCI = can be LR26 (Bt848) or LR50 (BT878) + WaveWatcher TVR-202 TV/FM Radio Card (ISA) + +MAXRON +------ + Maxron MaxTV/FM Radio (KW-TV878-FNT) = Kworld or JW-TV878-FBK + +www.ids-imaging.de +------------------ + Falcon Series (capture only) + In USA: http://www.theimagingsource.com/ + DFG/LC1 + +www.sknet-web.co.jp +------------------- + SKnet Monster TV (saa7134) + +A-Max www.amaxhk.com (Colormax, Amax, Napa) +------------------- + APAC Viewcomp 878 + +Cybertainment +------------- + CyberMail AV Video Email Kit w/ PCI Capture Card (capture only) + CyberMail Xtreme + These are Flyvideo + +VCR (http://www.vcrinc.com/) +--- + Video Catcher 16 + +Twinhan +------- + DST Card/DST-IP (bt878, twinhan asic) VP-1020 + Sold as: + KWorld DVBS Satellite TV-Card + Powercolor DSTV Satellite Tuner Card + Prolink Pixelview DTV2000 + Provideo PV-911 Digital Satellite TV Tuner Card With Common Interface ? + DST-CI Card (DVB Satellite) VP-1030 + DCT Card (DVB cable) + +MSI +--- + MSI TV@nywhere Tuner Card (MS-8876) (CX23881/883) Not Bt878 compatible. + MS-8401 DVB-S + +Focus www.focusinfo.com +----- + InVideo PCI (bt878) + +Sdisilk www.sdisilk.com/ +------- + SDI Silk 100 + SDI Silk 200 SDI Input Card + +www.euresys.com + PICOLO series + +PMC/Pace +www.pacecom.co.uk website closed + +Mercury www.kobian.com (UK and FR) + LR50 + LR138RBG-Rx == LR138 + +TEC sound (package and manuals don't have any other manufacturer info) TecSound + Though educated googling found: www.techmakers.com + TV-Mate = Zoltrix VP-8482 + +Lorenzen www.lorenzen.de +-------- + SL DVB-S PCI = Technotrend Budget PCI (su1278 or bsru version) + +Origo (.uk) www.origo2000.com + PC TV Card = LR50 + +I/O Magic www.iomagic.com +--------- + PC PVR - Desktop TV Personal Video Recorder DR-PCTV100 = Pinnacle ROB2D-51009464 4.0 + Cyberlink PowerVCR II + +Arowana +------- + TV-Karte / Poso Power TV (?) = Zoltrix VP-8482 (?) + +iTVC15 boards: +------------- +kuroutoshikou.com ITVC15 +yuan.com MPG160 PCI TV (Internal PCI MPEG2 encoder card plus TV-tuner) + +Asus www.asuscom.com + Asus TV Tuner Card 880 NTSC (low profile, cx23880) + Asus TV (saa7134) + +Hoontech +-------- +http://www.hoontech.com/korean/download/down_driver_list03.html + HART Vision 848 (H-ART Vision 848) + HART Vision 878 (H-Art Vision 878) diff --git a/linux/Documentation/video4linux/bttv/Insmod-options b/linux/Documentation/video4linux/bttv/Insmod-options new file mode 100644 index 000000000..7bb5a50b0 --- /dev/null +++ b/linux/Documentation/video4linux/bttv/Insmod-options @@ -0,0 +1,173 @@ + +Note: "modinfo <module>" prints various informations about a kernel +module, among them a complete and up-to-date list of insmod options. +This list tends to be outdated because it is updated manually ... + +========================================================================== + +bttv.o + the bt848/878 (grabber chip) driver + + insmod args: + card=n card type, see CARDLIST for a list. + tuner=n tuner type, see CARDLIST for a list. + radio=0/1 card supports radio + pll=0/1/2 pll settings + 0: don't use PLL + 1: 28 MHz crystal installed + 2: 35 MHz crystal installed + + triton1=0/1 for Triton1 (+others) compatibility + vsfx=0/1 yet another chipset bug compatibility bit + see README.quirks for details on these two. + + bigendian=n Set the endianness of the gfx framebuffer. + Default is native endian. + fieldnr=0/1 Count fields. Some TV descrambling software + needs this, for others it only generates + 50 useless IRQs/sec. default is 0 (off). + autoload=0/1 autoload helper modules (tuner, audio). + default is 1 (on). + bttv_verbose=0/1/2 verbose level (at insmod time, while + looking at the hardware). default is 1. + bttv_debug=0/1 debug messages (for capture). + default is 0 (off). + irq_debug=0/1 irq handler debug messages. + default is 0 (off). + gbuffers=2-32 number of capture buffers for mmap'ed capture. + default is 4. + gbufsize= size of capture buffers. default and + maximum value is 0x208000 (~2MB) + no_overlay=0 Enable overlay on broken hardware. There + are some chipsets (SIS for example) which + are known to have problems with the PCI DMA + push used by bttv. bttv will disable overlay + by default on this hardware to avoid crashes. + With this insmod option you can override this. + automute=0/1 Automatically mutes the sound if there is + no TV signal, on by default. You might try + to disable this if you have bad input signal + quality which leading to unwanted sound + dropouts. + chroma_agc=0/1 AGC of chroma signal, off by default. + adc_crush=0/1 Luminance ADC crush, on by default. + + bttv_gpio=0/1 + gpiomask= + audioall= + audiomux= + See Sound-FAQ for a detailed description. + + remap, card, radio and pll accept up to four comma-separated arguments + (for multiple boards). + +tuner.o + The tuner driver. You need this unless you want to use only + with a camera or external tuner ... + + insmod args: + debug=1 print some debug info to the syslog + type=n type of the tuner chip. n as follows: + see CARDLIST for a complete list. + pal=[bdgil] select PAL variant (used for some tuners + only, important for the audio carrier). + +tvmixer.o + registers a mixer device for the TV card's volume/bass/treble + controls (requires a i2c audio control chip like the msp3400). + + insmod args: + debug=1 print some debug info to the syslog. + devnr=n allocate device #n (0 == /dev/mixer, + 1 = /dev/mixer1, ...), default is to + use the first free one. + +tvaudio.o + new, experimental module which is supported to provide a single + driver for all simple i2c audio control chips (tda/tea*). + + insmod args: + tda8425 = 1 enable/disable the support for the + tda9840 = 1 various chips. + tda9850 = 1 The tea6300 can't be autodetected and is + tda9855 = 1 therefore off by default, if you have + tda9873 = 1 this one on your card (STB uses these) + tda9874a = 1 you have to enable it explicitly. + tea6300 = 0 The two tda985x chips use the same i2c + tea6420 = 1 address and can't be disturgished from + pic16c54 = 1 each other, you might have to disable + the wrong one. + debug = 1 print debug messages + + insmod args for tda9874a: + tda9874a_SIF=1/2 select sound IF input pin (1 or 2) + (default is pin 1) + tda9874a_AMSEL=0/1 auto-mute select for NICAM (default=0) + Please read note 3 below! + tda9874a_STD=n select TV sound standard (0..8): + 0 - A2, B/G + 1 - A2, M (Korea) + 2 - A2, D/K (1) + 3 - A2, D/K (2) + 4 - A2, D/K (3) + 5 - NICAM, I + 6 - NICAM, B/G + 7 - NICAM, D/K (default) + 8 - NICAM, L + + Note 1: tda9874a supports both tda9874h (old) and tda9874a (new) chips. + Note 2: tda9874h/a and tda9875 (which is supported separately by + tda9875.o) use the same i2c address so both modules should not be + used at the same time. + Note 3: Using tda9874a_AMSEL option depends on your TV card design! + AMSEL=0: auto-mute will switch between NICAM sound + and the sound on 1st carrier (i.e. FM mono or AM). + AMSEL=1: auto-mute will switch between NICAM sound + and the analog mono input (MONOIN pin). + If tda9874a decoder on your card has MONOIN pin not connected, then + use only tda9874_AMSEL=0 or don't specify this option at all. + For example: + card=65 (FlyVideo 2000S) - set AMSEL=1 or AMSEL=0 + card=72 (Prolink PV-BT878P rev.9B) - set AMSEL=0 only + +msp3400.o + The driver for the msp34xx sound processor chips. If you have a + stereo card, you probably want to insmod this one. + + insmod args: + debug=1/2 print some debug info to the syslog, + 2 is more verbose. + simple=1 Use the "short programming" method. Newer + msp34xx versions support this. You need this + for dbx stereo. Default is on if supported by + the chip. + once=1 Don't check the TV-stations Audio mode + every few seconds, but only once after + channel switches. + amsound=1 Audio carrier is AM/NICAM at 6.5 Mhz. This + should improve things for french people, the + carrier autoscan seems to work with FM only... + +tea6300.o - OBSOLETE (use tvaudio instead) + The driver for the tea6300 fader chip. If you have a stereo + card and the msp3400.o doesn't work, you might want to try this + one. This chip is seen on most STB TV/FM cards (usually from + Gateway OEM sold surplus on auction sites). + + insmod args: + debug=1 print some debug info to the syslog. + +tda8425.o - OBSOLETE (use tvaudio instead) + The driver for the tda8425 fader chip. This driver used to be + part of bttv.c, so if your sound used to work but does not + anymore, try loading this module. + + insmod args: + debug=1 print some debug info to the syslog. + +tda985x.o - OBSOLETE (use tvaudio instead) + The driver for the tda9850/55 audio chips. + + insmod args: + debug=1 print some debug info to the syslog. + chip=9850/9855 set the chip type. diff --git a/linux/Documentation/video4linux/bttv/README b/linux/Documentation/video4linux/bttv/README new file mode 100644 index 000000000..a72f4c94f --- /dev/null +++ b/linux/Documentation/video4linux/bttv/README @@ -0,0 +1,90 @@ + +Release notes for bttv +====================== + +You'll need at least these config options for bttv: + CONFIG_I2C=m + CONFIG_I2C_ALGOBIT=m + CONFIG_VIDEO_DEV=m + +The latest bttv version is available from http://bytesex.org/bttv/ + + +Make bttv work with your card +----------------------------- + +Just try "modprobe bttv" and see if that works. + +If it doesn't bttv likely could not autodetect your card and needs some +insmod options. The most important insmod option for bttv is "card=n" +to select the correct card type. If you get video but no sound you've +very likely specified the wrong (or no) card type. A list of supported +cards is in CARDLIST.bttv + +If bttv takes very long to load (happens sometimes with the cheap +cards which have no tuner), try adding this to your modules.conf: + options i2c-algo-bit bit_test=1 + +For the WinTV/PVR you need one firmware file from the driver CD: +hcwamc.rbf. The file is in the pvr45xxx.exe archive (self-extracting +zip file, unzip can unpack it). Put it into the /etc/pvr directory or +use the firm_altera=<path> insmod option to point the driver to the +location of the file. + +If your card isn't listed in CARDLIST.bttv or if you have trouble making +audio work, you should read the Sound-FAQ. + + +Autodetecting cards +------------------- + +bttv uses the PCI Subsystem ID to autodetect the card type. lspci lists +the Subsystem ID in the second line, looks like this: + +00:0a.0 Multimedia video controller: Brooktree Corporation Bt878 (rev 02) + Subsystem: Hauppauge computer works Inc. WinTV/GO + Flags: bus master, medium devsel, latency 32, IRQ 5 + Memory at e2000000 (32-bit, prefetchable) [size=4K] + +only bt878-based cards can have a subsystem ID (which does not mean +that every card really has one). bt848 cards can't have a Subsystem +ID and therefore can't be autodetected. There is a list with the ID's +in bttv-cards.c (in case you are intrested or want to mail patches +with updates). + + +Still doesn't work? +------------------- + +I do NOT have a lab with 30+ different grabber boards and a +PAL/NTSC/SECAM test signal generator at home, so I often can't +reproduce your problems. This makes debugging very difficult for me. +If you have some knowledge and spare time, please try to fix this +yourself (patches very welcome of course...) You know: The linux +slogan is "Do it yourself". + +There is a mailing list: video4linux-list@redhat.com. +https://listman.redhat.com/mailman/listinfo/video4linux-list + +If you have trouble with some specific TV card, try to ask there +instead of mailing me directly. The chance that someone with the +same card listens there is much higher... + +For problems with sound: There are alot of different systems used +for TV sound all over the world. And there are also different chips +which decode the audio signal. Reports about sound problems ("stereo +does'nt work") are pretty useless unless you include some details +about your hardware and the TV sound scheme used in your country (or +at least the country you are living in). + + +Finally: If you mail some patches for bttv around the world (to +linux-kernel/Alan/Linus/...), please Cc: me. + + +Have fun with bttv, + + Gerd + +-- +Gerd Knorr <kraxel@bytesex.org> diff --git a/linux/Documentation/video4linux/bttv/Sound-FAQ b/linux/Documentation/video4linux/bttv/Sound-FAQ new file mode 100644 index 000000000..b8c9c2605 --- /dev/null +++ b/linux/Documentation/video4linux/bttv/Sound-FAQ @@ -0,0 +1,148 @@ + +bttv and sound mini howto +========================= + +There are alot of different bt848/849/878/879 based boards available. +Making video work often is not a big deal, because this is handled +completely by the bt8xx chip, which is common on all boards. But +sound is handled in slightly different ways on each board. + +To handle the grabber boards correctly, there is a array tvcards[] in +bttv-cards.c, which holds the informations required for each board. +Sound will work only, if the correct entry is used (for video it often +makes no difference). The bttv driver prints a line to the kernel +log, telling which card type is used. Like this one: + + bttv0: model: BT848(Hauppauge old) [autodetected] + +You should verify this is correct. If it isn't, you have to pass the +correct board type as insmod argument, "insmod bttv card=2" for +example. The file CARDLIST has a list of valid arguments for card. +If your card isn't listed there, you might check the source code for +new entries which are not listed yet. If there isn't one for your +card, you can check if one of the existing entries does work for you +(just trial and error...). + +Some boards have an extra processor for sound to do stereo decoding +and other nice features. The msp34xx chips are used by Hauppauge for +example. If your board has one, you might have to load a helper +module like msp3400.o to make sound work. If there isn't one for the +chip used on your board: Bad luck. Start writing a new one. Well, +you might want to check the video4linux mailing list archive first... + +Of course you need a correctly installed soundcard unless you have the +speakers connected directly to the grabber board. Hint: check the +mixer settings too. ALSA for example has everything muted by default. + + +How sound works in detail +========================= + +Still doesn't work? Looks like some driver hacking is required. +Below is a do-it-yourself description for you. + +The bt8xx chips have 32 general purpose pins, and registers to control +these pins. One register is the output enable register +(BT848_GPIO_OUT_EN), it says which pins are actively driven by the +bt848 chip. Another one is the data register (BT848_GPIO_DATA), where +you can get/set the status if these pins. They can be used for input +and output. + +Most grabber board vendors use these pins to control an external chip +which does the sound routing. But every board is a little different. +These pins are also used by some companies to drive remote control +receiver chips. Some boards use the i2c bus instead of the gpio pins +to connect the mux chip. + +As mentioned above, there is a array which holds the required +informations for each known board. You basically have to create a new +line for your board. The important fields are these two: + +struct tvcard +{ + [ ... ] + u32 gpiomask; + u32 audiomux[6]; /* Tuner, Radio, external, internal, mute, stereo */ +}; + +gpiomask specifies which pins are used to control the audio mux chip. +The corresponding bits in the output enable register +(BT848_GPIO_OUT_EN) will be set as these pins must be driven by the +bt848 chip. + +The audiomux[] array holds the data values for the different inputs +(i.e. which pins must be high/low for tuner/mute/...). This will be +written to the data register (BT848_GPIO_DATA) to switch the audio +mux. + + +What you have to do is figure out the correct values for gpiomask and +the audiomux array. If you have Windows and the drivers four your +card installed, you might to check out if you can read these registers +values used by the windows driver. A tool to do this is available +from ftp://telepresence.dmem.strath.ac.uk/pub/bt848/winutil, but it +does'nt work with bt878 boards according to some reports I received. +Another one with bt878 suport is available from +http://btwincap.sourceforge.net/Files/btspy2.00.zip + +You might also dig around in the *.ini files of the Windows applications. +You can have a look at the board to see which of the gpio pins are +connected at all and then start trial-and-error ... + + +Starting with release 0.7.41 bttv has a number of insmod options to +make the gpio debugging easier: + +bttv_gpio=0/1 enable/disable gpio debug messages +gpiomask=n set the gpiomask value +audiomux=i,j,... set the values of the audiomux array +audioall=a set the values of the audiomux array (one + value for all array elements, useful to check + out which effect the particular value has). + +The messages printed with bttv_gpio=1 look like this: + + bttv0: gpio: en=00000027, out=00000024 in=00ffffd8 [audio: off] + +en = output _en_able register (BT848_GPIO_OUT_EN) +out = _out_put bits of the data register (BT848_GPIO_DATA), + i.e. BT848_GPIO_DATA & BT848_GPIO_OUT_EN +in = _in_put bits of the data register, + i.e. BT848_GPIO_DATA & ~BT848_GPIO_OUT_EN + + + +Other elements of the tvcards array +=================================== + +If you are trying to make a new card work you might find it useful to +know what the other elements in the tvcards array are good for: + +video_inputs - # of video inputs the card has +audio_inputs - historical cruft, not used any more. +tuner - which input is the tuner +svhs - which input is svhs (all others are labeled composite) +muxsel - video mux, input->registervalue mapping +pll - same as pll= insmod option +tuner_type - same as tuner= insmod option +*_modulename - hint whenever some card needs this or that audio + module loaded to work properly. +has_radio - whenever this TV card has a radio tuner. +no_msp34xx - "1" disables loading of msp3400.o module +no_tda9875 - "1" disables loading of tda9875.o module +needs_tvaudio - set to "1" to load tvaudio.o module + +If some config item is specified both from the tvcards array and as +insmod option, the insmod option takes precedence. + + + +Good luck, + + Gerd + + +PS: If you have a new working entry, mail it to me. + +-- +Gerd Knorr <kraxel@bytesex.org> diff --git a/linux/Documentation/video4linux/bttv/Tuners b/linux/Documentation/video4linux/bttv/Tuners new file mode 100644 index 000000000..d18fbc70c --- /dev/null +++ b/linux/Documentation/video4linux/bttv/Tuners @@ -0,0 +1,115 @@ +1) Tuner Programming +==================== +There are some flavors of Tuner programming APIs. +These differ mainly by the bandswitch byte. + + L= LG_API (VHF_LO=0x01, VHF_HI=0x02, UHF=0x08, radio=0x04) + P= PHILIPS_API (VHF_LO=0xA0, VHF_HI=0x90, UHF=0x30, radio=0x04) + T= TEMIC_API (VHF_LO=0x02, VHF_HI=0x04, UHF=0x01) + A= ALPS_API (VHF_LO=0x14, VHF_HI=0x12, UHF=0x11) + M= PHILIPS_MK3 (VHF_LO=0x01, VHF_HI=0x02, UHF=0x04, radio=0x19) + +2) Tuner Manufacturers +====================== + +SAMSUNG Tuner identification: (e.g. TCPM9091PD27) + TCP [ABCJLMNQ] 90[89][125] [DP] [ACD] 27 [ABCD] + [ABCJLMNQ]: + A= BG+DK + B= BG + C= I+DK + J= NTSC-Japan + L= Secam LL + M= BG+I+DK + N= NTSC + Q= BG+I+DK+LL + [89]: ? + [125]: + 2: No FM + 5: With FM + [DP]: + D= NTSC + P= PAL + [ACD]: + A= F-connector + C= Phono connector + D= Din Jack + [ABCD]: + 3-wire/I2C tuning, 2-band/3-band + + These Tuners are PHILIPS_API compatible. + +Philips Tuner identification: (e.g. FM1216MF) + F[IRMQ]12[1345]6{MF|ME|MP} + F[IRMQ]: + FI12x6: Tuner Series + FR12x6: Tuner + Radio IF + FM12x6: Tuner + FM + FQ12x6: special + FMR12x6: special + TD15xx: Digital Tuner ATSC + 12[1345]6: + 1216: PAL BG + 1236: NTSC + 1246: PAL I + 1256: Pal DK + {MF|ME|MP} + MF: BG LL w/ Secam (Multi France) + ME: BG DK I LL (Multi Europe) + MP: BG DK I (Multi PAL) + MR: BG DK M (?) + MG: BG DKI M (?) + MK2 series PHILIPS_API, most tuners are compatible to this one ! + MK3 series introduced in 2002 w/ PHILIPS_MK3_API + +Temic Tuner identification: (.e.g 4006FH5) + 4[01][0136][269]F[HYNR]5 + 40x2: Tuner (5V/33V), TEMIC_API. + 40x6: Tuner 5V + 41xx: Tuner compact + 40x9: Tuner+FM compact + [0136] + xx0x: PAL BG + xx1x: Pal DK, Secam LL + xx3x: NTSC + xx6x: PAL I + F[HYNR]5 + FH5: Pal BG + FY5: others + FN5: multistandard + FR5: w/ FM radio + 3X xxxx: order number with specific connector + Note: Only 40x2 series has TEMIC_API, all newer tuners have PHILIPS_API. + +LG Innotek Tuner: + TPI8NSR11 : NTSC J/M (TPI8NSR01 w/FM) (P,210/497) + TPI8PSB11 : PAL B/G (TPI8PSB01 w/FM) (P,170/450) + TAPC-I701 : PAL I (TAPC-I001 w/FM) (P,170/450) + TPI8PSB12 : PAL D/K+B/G (TPI8PSB02 w/FM) (P,170/450) + TAPC-H701P: NTSC_JP (TAPC-H001P w/FM) (L,170/450) + TAPC-G701P: PAL B/G (TAPC-G001P w/FM) (L,170/450) + TAPC-W701P: PAL I (TAPC-W001P w/FM) (L,170/450) + TAPC-Q703P: PAL D/K (TAPC-Q001P w/FM) (L,170/450) + TAPC-Q704P: PAL D/K+I (L,170/450) + TAPC-G702P: PAL D/K+B/G (L,170/450) + + TADC-H002F: NTSC (L,175/410?; 2-B, C-W+11, W+12-69) + TADC-M201D: PAL D/K+B/G+I (L,143/425) (sound control at I2C address 0xc8) + TADC-T003F: NTSC Taiwan (L,175/410?; 2-B, C-W+11, W+12-69) + Suffix: + P= Standard phono female socket + D= IEC female socket + F= F-connector + +Other Tuners: +TCL2002MB-1 : PAL BG + DK =TUNER_LG_PAL_NEW_TAPC +TCL2002MB-1F: PAL BG + DK w/FM =PHILIPS_PAL +TCL2002MI-2 : PAL I = ?? + +ALPS Tuners: + Most are LG_API compatible + TSCH6 has ALPS_API (TSCH5 ?) + TSBE1 has extra API 05,02,08 Control_byte=0xCB Source:(1) + +Lit. +(1) conexant100029b-PCI-Decoder-ApplicationNote.pdf diff --git a/linux/Documentation/video4linux/not-in-cx2388x-datasheet.txt b/linux/Documentation/video4linux/not-in-cx2388x-datasheet.txt new file mode 100644 index 000000000..96b638b5b --- /dev/null +++ b/linux/Documentation/video4linux/not-in-cx2388x-datasheet.txt @@ -0,0 +1,37 @@ +================================================================================= +MO_OUTPUT_FORMAT (0x310164) + + Previous default from DScaler: 0x1c1f0008 + Digit 8: 31-28 + 28: PREVREMOD = 1 + + Digit 7: 27-24 (0xc = 12 = b1100 ) + 27: COMBALT = 1 + 26: PAL_INV_PHASE + (DScaler apparently set this to 1, resulted in sucky picture) + + Digits 6,5: 23-16 + 25-16: COMB_RANGE = 0x1f [default] (9 bits -> max 512) + + Digit 4: 15-12 + 15: DISIFX = 0 + 14: INVCBF = 0 + 13: DISADAPT = 0 + 12: NARROWADAPT = 0 + + Digit 3: 11-8 + 11: FORCE2H + 10: FORCEREMD + 9: NCHROMAEN + 8: NREMODEN + + Digit 2: 7-4 + 7-6: YCORE + 5-4: CCORE + + Digit 1: 3-0 + 3: RANGE = 1 + 2: HACTEXT + 1: HSFMT + +================================================================================= diff --git a/linux/drivers/media/common/ir-common.c b/linux/drivers/media/common/ir-common.c new file mode 100644 index 000000000..78c3590f8 --- /dev/null +++ b/linux/drivers/media/common/ir-common.c @@ -0,0 +1,227 @@ +/* + * some common structs and functions to handle infrared remotes via + * input layer ... + * + * (c) 2003 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/version.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "ir-common.h" +#else +#include <media/ir-common.h> +#endif + +/* -------------------------------------------------------------------------- */ + +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +static int repeat = 1; +MODULE_PARM(repeat,"i"); +MODULE_PARM_DESC(repeat,"auto-repeat for IR keys (default: on)"); + +static int debug = 0; /* debug level (0,1,2) */ +MODULE_PARM(debug,"i"); + +#define dprintk(level, fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG fmt , ## arg) + +/* -------------------------------------------------------------------------- */ + +/* generic RC5 keytable */ +/* see http://users.pandora.be/nenya/electronics/rc5/codes00.htm */ +IR_KEYTAB_TYPE ir_codes_rc5_tv[IR_KEYTAB_SIZE] = { + [ 0x00 ] = KEY_KP0, // 0 + [ 0x01 ] = KEY_KP1, // 1 + [ 0x02 ] = KEY_KP2, // 2 + [ 0x03 ] = KEY_KP3, // 3 + [ 0x04 ] = KEY_KP4, // 4 + [ 0x05 ] = KEY_KP5, // 5 + [ 0x06 ] = KEY_KP6, // 6 + [ 0x07 ] = KEY_KP7, // 7 + [ 0x08 ] = KEY_KP8, // 8 + [ 0x09 ] = KEY_KP9, // 9 + + [ 0x0b ] = KEY_CHANNEL, // channel / program (japan: 11) + [ 0x0c ] = KEY_POWER, // standby + [ 0x0d ] = KEY_MUTE, // mute / demute + [ 0x0f ] = KEY_TV, // display + [ 0x10 ] = KEY_VOLUMEUP, // volume + + [ 0x11 ] = KEY_VOLUMEDOWN, // volume - + [ 0x12 ] = KEY_BRIGHTNESSUP, // brightness + + [ 0x13 ] = KEY_BRIGHTNESSDOWN, // brightness - + [ 0x1e ] = KEY_SEARCH, // search + + [ 0x20 ] = KEY_CHANNELUP, // channel / program + + [ 0x21 ] = KEY_CHANNELDOWN, // channel / program - + [ 0x22 ] = KEY_CHANNEL, // alt / channel + [ 0x23 ] = KEY_LANGUAGE, // 1st / 2nd language + [ 0x26 ] = KEY_SLEEP, // sleeptimer + [ 0x2e ] = KEY_MENU, // 2nd controls (USA: menu) + [ 0x30 ] = KEY_PAUSE, // pause + [ 0x32 ] = KEY_REWIND, // rewind + [ 0x33 ] = KEY_GOTO, // go to + [ 0x35 ] = KEY_PLAY, // play + [ 0x36 ] = KEY_STOP, // stop + [ 0x37 ] = KEY_RECORD, // recording + +#if 0 /* FIXME */ + [ 0x0a ] = KEY_RESERVED, // 1/2/3 digits (japan: 10) + [ 0x0e ] = KEY_RESERVED, // P.P. (personal preference) + [ 0x14 ] = KEY_RESERVED, // colour saturation + + [ 0x15 ] = KEY_RESERVED, // colour saturation - + [ 0x16 ] = KEY_RESERVED, // bass + + [ 0x17 ] = KEY_RESERVED, // bass - + [ 0x18 ] = KEY_RESERVED, // treble + + [ 0x19 ] = KEY_RESERVED, // treble - + [ 0x1a ] = KEY_RESERVED, // balance right + [ 0x1b ] = KEY_RESERVED, // balance left + [ 0x1c ] = KEY_RESERVED, // contrast + + [ 0x1d ] = KEY_RESERVED, // contrast - + [ 0x1f ] = KEY_RESERVED, // tint/hue + + [ 0x24 ] = KEY_RESERVED, // spacial stereo on/off + [ 0x25 ] = KEY_RESERVED, // mono / stereo (USA) + [ 0x27 ] = KEY_RESERVED, // tint / hue - + [ 0x28 ] = KEY_RESERVED, // RF switch/PIP select + [ 0x29 ] = KEY_RESERVED, // vote + [ 0x2a ] = KEY_RESERVED, // timed page/channel clck + [ 0x2b ] = KEY_RESERVED, // increment (USA) + [ 0x2c ] = KEY_RESERVED, // decrement (USA) + [ 0x2d ] = KEY_RESERVED, // + [ 0x2f ] = KEY_RESERVED, // PIP shift + [ 0x31 ] = KEY_RESERVED, // erase + [ 0x34 ] = KEY_RESERVED, // wind + [ 0x38 ] = KEY_RESERVED, // external 1 + [ 0x39 ] = KEY_RESERVED, // external 2 + [ 0x3a ] = KEY_RESERVED, // PIP display mode + [ 0x3b ] = KEY_RESERVED, // view data mode / advance + [ 0x3c ] = KEY_RESERVED, // teletext submode (Japan: 12) + [ 0x3d ] = KEY_RESERVED, // system standby + [ 0x3e ] = KEY_RESERVED, // crispener on/off + [ 0x3f ] = KEY_RESERVED, // system select +#endif +}; +EXPORT_SYMBOL_GPL(ir_codes_rc5_tv); + +/* empty keytable, can be used as placeholder for not-yet created keytables */ +IR_KEYTAB_TYPE ir_codes_empty[IR_KEYTAB_SIZE] = { + [ 42 ] = KEY_COFFEE, +}; +EXPORT_SYMBOL_GPL(ir_codes_empty); + +/* -------------------------------------------------------------------------- */ + +static void ir_input_key_event(struct input_dev *dev, struct ir_input_state *ir) +{ + if (KEY_RESERVED == ir->keycode) { + printk(KERN_INFO "%s: unknown key: key=0x%02x raw=0x%02x down=%d\n", + dev->name,ir->ir_key,ir->ir_raw,ir->keypressed); + return; + } + dprintk(1,"%s: key event code=%d down=%d\n", + dev->name,ir->keycode,ir->keypressed); + input_report_key(dev,ir->keycode,ir->keypressed); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + input_sync(dev); +#endif +} + +/* -------------------------------------------------------------------------- */ + +void ir_input_init(struct input_dev *dev, struct ir_input_state *ir, + int ir_type, IR_KEYTAB_TYPE *ir_codes) +{ + int i; + + ir->ir_type = ir_type; + if (ir_codes) + memcpy(ir->ir_codes, ir_codes, sizeof(ir->ir_codes)); + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + init_input_dev(dev); +#endif + dev->keycode = ir->ir_codes; + dev->keycodesize = sizeof(IR_KEYTAB_TYPE); + dev->keycodemax = IR_KEYTAB_SIZE; + for (i = 0; i < IR_KEYTAB_SIZE; i++) + set_bit(ir->ir_codes[i], dev->keybit); + clear_bit(0, dev->keybit); + + set_bit(EV_KEY, dev->evbit); + if (repeat) + set_bit(EV_REP, dev->evbit); +} + +void ir_input_nokey(struct input_dev *dev, struct ir_input_state *ir) +{ + if (ir->keypressed) { + ir->keypressed = 0; + ir_input_key_event(dev,ir); + } +} + +void ir_input_keydown(struct input_dev *dev, struct ir_input_state *ir, + u32 ir_key, u32 ir_raw) +{ + u32 keycode = IR_KEYCODE(ir->ir_codes, ir_key); + + if (ir->keypressed && ir->keycode != keycode) { + ir->keypressed = 0; + ir_input_key_event(dev,ir); + } + if (!ir->keypressed) { + ir->ir_key = ir_key; + ir->ir_raw = ir_raw; + ir->keycode = keycode; + ir->keypressed = 1; + ir_input_key_event(dev,ir); + } +#if 0 + /* maybe do something like this ??? */ + input_event(a, EV_IR, ir->ir_type, ir->ir_raw); +#endif +} + +u32 ir_extract_bits(u32 data, u32 mask) +{ + int mbit, vbit; + u32 value; + + value = 0; + vbit = 0; + for (mbit = 0; mbit < 32; mbit++) { + if (!(mask & ((u32)1 << mbit))) + continue; + if (data & ((u32)1 << mbit)) + value |= (1 << vbit); + vbit++; + } + return value; +} + +EXPORT_SYMBOL_GPL(ir_input_init); +EXPORT_SYMBOL_GPL(ir_input_nokey); +EXPORT_SYMBOL_GPL(ir_input_keydown); +EXPORT_SYMBOL_GPL(ir_extract_bits); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/bt832.c b/linux/drivers/media/video/bt832.c new file mode 100644 index 000000000..2022c4080 --- /dev/null +++ b/linux/drivers/media/video/bt832.c @@ -0,0 +1,301 @@ +/* Driver for Bt832 CMOS Camera Video Processor + i2c-addresses: 0x88 or 0x8a + + The BT832 interfaces to a Quartzsight Digital Camera (352x288, 25 or 30 fps) + via a 9 pin connector ( 4-wire SDATA, 2-wire i2c, SCLK, VCC, GND). + It outputs an 8-bit 4:2:2 YUV or YCrCb video signal which can be directly + connected to bt848/bt878 GPIO pins on this purpose. + (see: VLSI Vision Ltd. www.vvl.co.uk for camera datasheets) + + Supported Cards: + - Pixelview Rev.4E: 0x8a + GPIO 0x400000 toggles Bt832 RESET, and the chip changes to i2c 0x88 ! + + (c) Gunther Mayer, 2002 + + STATUS: + - detect chip and hexdump + - reset chip and leave low power mode + - detect camera present + + TODO: + - make it work (find correct setup for Bt832 and Bt878) +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/slab.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "audiochip.h" +#include "id.h" +#include "i2c-compat.h" +#else +#include <media/audiochip.h> +#include <media/id.h> +#endif +#include "bttv.h" +#include "bt832.h" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_BT832_ALT1>>1,I2C_BT832_ALT2>>1,I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +/* ---------------------------------------------------------------------- */ + +#define dprintk if (debug) printk + +static int bt832_detach(struct i2c_client *client); + + +static struct i2c_driver driver; +static struct i2c_client client_template; + +struct bt832 { + struct i2c_client client; +}; + +int bt832_hexdump(struct i2c_client *i2c_client_s, unsigned char *buf) +{ + int i,rc; + buf[0]=0x80; // start at register 0 with auto-increment + if (1 != (rc = i2c_master_send(i2c_client_s,buf,1))) + printk("bt832: i2c i/o error: rc == %d (should be 1)\n",rc); + + for(i=0;i<65;i++) + buf[i]=0; + if (65 != (rc=i2c_master_recv(i2c_client_s,buf,65))) + printk("bt832: i2c i/o error: rc == %d (should be 65)\n",rc); + + // Note: On READ the first byte is the current index + // (e.g. 0x80, what we just wrote) + + if(1) { + int i; + printk("BT832 hexdump:\n"); + for(i=1;i<65;i++) { + if(i!=1) { + if(((i-1)%8)==0) printk(" "); + if(((i-1)%16)==0) printk("\n"); + } + printk(" %02x",buf[i]); + } + printk("\n"); + } + return 0; +} + +// Return: 1 (is a bt832), 0 (No bt832 here) +int bt832_init(struct i2c_client *i2c_client_s) +{ + unsigned char *buf; + int rc; + + buf=kmalloc(65,GFP_KERNEL); + bt832_hexdump(i2c_client_s,buf); + + if(buf[0x40] != 0x31) { + printk("bt832: this i2c chip is no bt832 (id=%02x). Detaching.\n",buf[0x40]); + kfree(buf); + return 0; + } + + printk("Write 0 tp VPSTATUS\n"); + buf[0]=BT832_VP_STATUS; // Reg.52 + buf[1]= 0x00; + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error VPS: rc == %d (should be 2)\n",rc); + + bt832_hexdump(i2c_client_s,buf); + + + // Leave low power mode: + printk("Bt832: leave low power mode.\n"); + buf[0]=BT832_CAM_SETUP0; //0x39 57 + buf[1]=0x08; + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error LLPM: rc == %d (should be 2)\n",rc); + + bt832_hexdump(i2c_client_s,buf); + + printk("Write 0 tp VPSTATUS\n"); + buf[0]=BT832_VP_STATUS; // Reg.52 + buf[1]= 0x00; + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error VPS: rc == %d (should be 2)\n",rc); + + bt832_hexdump(i2c_client_s,buf); + + + // Enable Output + printk("Enable Output\n"); + buf[0]=BT832_VP_CONTROL1; // Reg.40 + buf[1]= 0x27 & (~0x01); // Default | !skip + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error EO: rc == %d (should be 2)\n",rc); + + bt832_hexdump(i2c_client_s,buf); + +#if 0 + // Full 30/25 Frame rate + printk("Full 30/25 Frame rate\n"); + buf[0]=BT832_VP_CONTROL0; // Reg.39 + buf[1]= 0x00; + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error FFR: rc == %d (should be 2)\n",rc); + + bt832_hexdump(i2c_client_s,buf); +#endif + +#if 1 + // for testing (even works when no camera attached) + printk("bt832: *** Generate NTSC M Bars *****\n"); + buf[0]=BT832_VP_TESTCONTROL0; // Reg. 42 + buf[1]=3; // Generate NTSC System M bars, Generate Frame timing internally + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error MBAR: rc == %d (should be 2)\n",rc); +#endif + + printk("Bt832: Camera Present: %s\n", + (buf[1+BT832_CAM_STATUS] & BT832_56_CAMERA_PRESENT) ? "yes":"no"); + + bt832_hexdump(i2c_client_s,buf); + kfree(buf); + return 1; +} + + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int bt832_attach(struct i2c_adapter *adap, int addr, int kind) +#else +static int bt832_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +#endif +{ + struct bt832 *t; + + printk("bt832_attach\n"); + + client_template.adapter = adap; + client_template.addr = addr; + + printk("bt832: chip found @ 0x%x\n", addr<<1); + + if (NULL == (t = kmalloc(sizeof(*t), GFP_KERNEL))) + return -ENOMEM; + memset(t,0,sizeof(*t)); + t->client = client_template; + i2c_set_clientdata(&t->client, t); + i2c_attach_client(&t->client); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_INC_USE_COUNT; +#endif + if(! bt832_init(&t->client)) { + bt832_detach(&t->client); + return -1; + } + + return 0; +} + +static int bt832_probe(struct i2c_adapter *adap) +{ +#ifdef I2C_ADAP_CLASS_TV_ANALOG + if (adap->class & I2C_ADAP_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, bt832_attach); +#else + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, bt832_attach); +#endif + return 0; +} + +static int bt832_detach(struct i2c_client *client) +{ + struct bt832 *t = i2c_get_clientdata(client); + + printk("bt832: detach.\n"); + i2c_detach_client(client); + kfree(t); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_DEC_USE_COUNT; +#endif + return 0; +} + +static int +bt832_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct bt832 *t = i2c_get_clientdata(client); + + printk("bt832: command %x\n",cmd); + + switch (cmd) { + case BT832_HEXDUMP: { + unsigned char *buf; + buf=kmalloc(65,GFP_KERNEL); + bt832_hexdump(&t->client,buf); + kfree(buf); + } + break; + case BT832_REATTACH: + printk("bt832: re-attach\n"); + i2c_del_driver(&driver); + i2c_add_driver(&driver); + break; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif + .name = "i2c bt832 driver", + .id = -1, /* FIXME */ + .flags = I2C_DF_NOTIFY, + .attach_adapter = bt832_probe, + .detach_client = bt832_detach, + .command = bt832_command, +}; +static struct i2c_client client_template = +{ + I2C_DEVNAME("bt832"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + + +int bt832_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void bt832_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(bt832_init_module); +module_exit(bt832_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/bt832.h b/linux/drivers/media/video/bt832.h new file mode 100644 index 000000000..7a98c06e0 --- /dev/null +++ b/linux/drivers/media/video/bt832.h @@ -0,0 +1,305 @@ +/* Bt832 CMOS Camera Video Processor (VP) + + The Bt832 CMOS Camera Video Processor chip connects a Quartsight CMOS + color digital camera directly to video capture devices via an 8-bit, + 4:2:2 YUV or YCrCb video interface. + + i2c addresses: 0x88 or 0x8a + */ + +/* The 64 registers: */ + +// Input Processor +#define BT832_OFFSET 0 +#define BT832_RCOMP 1 +#define BT832_G1COMP 2 +#define BT832_G2COMP 3 +#define BT832_BCOMP 4 +// Exposures: +#define BT832_FINEH 5 +#define BT832_FINEL 6 +#define BT832_COARSEH 7 +#define BT832_COARSEL 8 +#define BT832_CAMGAIN 9 +// Main Processor: +#define BT832_M00 10 +#define BT832_M01 11 +#define BT832_M02 12 +#define BT832_M10 13 +#define BT832_M11 14 +#define BT832_M12 15 +#define BT832_M20 16 +#define BT832_M21 17 +#define BT832_M22 18 +#define BT832_APCOR 19 +#define BT832_GAMCOR 20 +// Level Accumulator Inputs +#define BT832_VPCONTROL2 21 +#define BT832_ZONECODE0 22 +#define BT832_ZONECODE1 23 +#define BT832_ZONECODE2 24 +#define BT832_ZONECODE3 25 +// Level Accumulator Outputs: +#define BT832_RACC 26 +#define BT832_GACC 27 +#define BT832_BACC 28 +#define BT832_BLACKACC 29 +#define BT832_EXP_AGC 30 +#define BT832_LACC0 31 +#define BT832_LACC1 32 +#define BT832_LACC2 33 +#define BT832_LACC3 34 +#define BT832_LACC4 35 +#define BT832_LACC5 36 +#define BT832_LACC6 37 +#define BT832_LACC7 38 +// System: +#define BT832_VP_CONTROL0 39 +#define BT832_VP_CONTROL1 40 +#define BT832_THRESH 41 +#define BT832_VP_TESTCONTROL0 42 +#define BT832_VP_DMCODE 43 +#define BT832_ACB_CONFIG 44 +#define BT832_ACB_GNBASE 45 +#define BT832_ACB_MU 46 +#define BT832_CAM_TEST0 47 +#define BT832_AEC_CONFIG 48 +#define BT832_AEC_TL 49 +#define BT832_AEC_TC 50 +#define BT832_AEC_TH 51 +// Status: +#define BT832_VP_STATUS 52 +#define BT832_VP_LINECOUNT 53 +#define BT832_CAM_DEVICEL 54 // e.g. 0x19 +#define BT832_CAM_DEVICEH 55 // e.g. 0x40 == 0x194 Mask0, 0x194 = 404 decimal (VVL-404 camera) +#define BT832_CAM_STATUS 56 + #define BT832_56_CAMERA_PRESENT 0x20 +//Camera Setups: +#define BT832_CAM_SETUP0 57 +#define BT832_CAM_SETUP1 58 +#define BT832_CAM_SETUP2 59 +#define BT832_CAM_SETUP3 60 +// System: +#define BT832_DEFCOR 61 +#define BT832_VP_TESTCONTROL1 62 +#define BT832_DEVICE_ID 63 +# define BT832_DEVICE_ID__31 0x31 // Bt832 has ID 0x31 + +/* STMicroelectronivcs VV5404 camera module + i2c: 0x20: sensor address + i2c: 0xa0: eeprom for ccd defect map + */ +#define VV5404_device_h 0x00 // 0x19 +#define VV5404_device_l 0x01 // 0x40 +#define VV5404_status0 0x02 +#define VV5404_linecountc 0x03 // current line counter +#define VV5404_linecountl 0x04 +#define VV5404_setup0 0x10 +#define VV5404_setup1 0x11 +#define VV5404_setup2 0x12 +#define VV5404_setup4 0x14 +#define VV5404_setup5 0x15 +#define VV5404_fine_h 0x20 // fine exposure +#define VV5404_fine_l 0x21 +#define VV5404_coarse_h 0x22 //coarse exposure +#define VV5404_coarse_l 0x23 +#define VV5404_gain 0x24 // ADC pre-amp gain setting +#define VV5404_clk_div 0x25 +#define VV5404_cr 0x76 // control register +#define VV5404_as0 0x77 // ADC setup register + + +// IOCTL +#define BT832_HEXDUMP _IOR('b',1,int) +#define BT832_REATTACH _IOR('b',2,int) + +/* from BT8x8VXD/capdrv/dialogs.cpp */ + +/* +typedef enum { SVI, Logitech, Rockwell } CAMERA; + +static COMBOBOX_ENTRY gwCameraOptions[] = +{ + { SVI, "Silicon Vision 512N" }, + { Logitech, "Logitech VideoMan 1.3" }, + { Rockwell, "Rockwell QuartzSight PCI 1.0" } +}; + +// SRAM table values +//=========================================================================== +typedef enum { TGB_NTSC624, TGB_NTSC780, TGB_NTSC858, TGB_NTSC392 } TimeGenByte; + +BYTE SRAMTable[][ 60 ] = +{ + // TGB_NTSC624 + { + 0x33, // size of table = 51 + 0x0E, 0xC0, 0x00, 0x00, 0x90, 0x02, 0x03, 0x10, 0x03, 0x06, + 0x10, 0x04, 0x12, 0x12, 0x05, 0x02, 0x13, 0x04, 0x19, 0x00, + 0x04, 0x39, 0x00, 0x06, 0x59, 0x08, 0x03, 0x85, 0x08, 0x07, + 0x03, 0x50, 0x00, 0x91, 0x40, 0x00, 0x11, 0x01, 0x01, 0x4D, + 0x0D, 0x02, 0x03, 0x11, 0x01, 0x05, 0x37, 0x00, 0x37, 0x21, 0x00 + }, + // TGB_NTSC780 + { + 0x33, // size of table = 51 + 0x0e, 0xc0, 0x00, 0x00, 0x90, 0xe2, 0x03, 0x10, 0x03, 0x06, + 0x10, 0x34, 0x12, 0x12, 0x65, 0x02, 0x13, 0x24, 0x19, 0x00, + 0x24, 0x39, 0x00, 0x96, 0x59, 0x08, 0x93, 0x85, 0x08, 0x97, + 0x03, 0x50, 0x50, 0xaf, 0x40, 0x30, 0x5f, 0x01, 0xf1, 0x7f, + 0x0d, 0xf2, 0x03, 0x11, 0xf1, 0x05, 0x37, 0x30, 0x85, 0x21, 0x50 + }, + // TGB_NTSC858 + { + 0x33, // size of table = 51 + 0x0c, 0xc0, 0x00, 0x00, 0x90, 0xc2, 0x03, 0x10, 0x03, 0x06, + 0x10, 0x34, 0x12, 0x12, 0x65, 0x02, 0x13, 0x24, 0x19, 0x00, + 0x24, 0x39, 0x00, 0x96, 0x59, 0x08, 0x93, 0x83, 0x08, 0x97, + 0x03, 0x50, 0x30, 0xc0, 0x40, 0x30, 0x86, 0x01, 0x01, 0xa6, + 0x0d, 0x62, 0x03, 0x11, 0x61, 0x05, 0x37, 0x30, 0xac, 0x21, 0x50 + }, + // TGB_NTSC392 + // This table has been modified to be used for Fusion Rev D + { + 0x2A, // size of table = 42 + 0x06, 0x08, 0x04, 0x0a, 0xc0, 0x00, 0x18, 0x08, 0x03, 0x24, + 0x08, 0x07, 0x02, 0x90, 0x02, 0x08, 0x10, 0x04, 0x0c, 0x10, + 0x05, 0x2c, 0x11, 0x04, 0x55, 0x48, 0x00, 0x05, 0x50, 0x00, + 0xbf, 0x0c, 0x02, 0x2f, 0x3d, 0x00, 0x2f, 0x3f, 0x00, 0xc3, + 0x20, 0x00 + } +}; + +//=========================================================================== +// This is the structure of the camera specifications +//=========================================================================== +typedef struct tag_cameraSpec +{ + SignalFormat signal; // which digital signal format the camera has + VideoFormat vidFormat; // video standard + SyncVideoRef syncRef; // which sync video reference is used + State syncOutput; // enable sync output for sync video input? + DecInputClk iClk; // which input clock is used + TimeGenByte tgb; // which timing generator byte does the camera use + int HReset; // select 64, 48, 32, or 16 CLKx1 for HReset + PLLFreq pllFreq; // what synthesized frequency to set PLL to + VSIZEPARMS vSize; // video size the camera produces + int lineCount; // expected total number of half-line per frame - 1 + BOOL interlace; // interlace signal? +} CameraSpec; + +//=========================================================================== +// <UPDATE REQUIRED> +// Camera specifications database. Update this table whenever camera spec +// has been changed or added/deleted supported camera models +//=========================================================================== +static CameraSpec dbCameraSpec[ N_CAMERAOPTIONS ] = +{ // Silicon Vision 512N + { Signal_CCIR656, VFormat_NTSC, VRef_alignedCb, Off, DecClk_GPCLK, TGB_NTSC624, 64, KHz19636, + // Clkx1_HACTIVE, Clkx1_HDELAY, VActive, VDelay, linesPerField; lineCount, Interlace + { 512, 0x64, 480, 0x13, 240 }, 0, TRUE + }, + // Logitech VideoMan 1.3 + { Signal_CCIR656, VFormat_NTSC, VRef_alignedCb, Off, DecClk_GPCLK, TGB_NTSC780, 64, KHz24545, + // Clkx1_HACTIVE, Clkx1_HDELAY, VActive, VDelay, linesPerField; lineCount, Interlace + { 640, 0x80, 480, 0x1A, 240 }, 0, TRUE + }, + // Rockwell QuartzSight + // Note: Fusion Rev D (rev ID 0x02) and later supports 16 pixels for HReset which is preferable. + // Use 32 for earlier version of hardware. Clkx1_HDELAY also changed from 0x27 to 0x20. + { Signal_CCIR656, VFormat_NTSC, VRef_alignedCb, Off, DecClk_GPCLK, TGB_NTSC392, 16, KHz28636, + // Clkx1_HACTIVE, Clkx1_HDELAY, VActive, VDelay, linesPerField; lineCount, Interlace + { 352, 0x20, 576, 0x08, 288 }, 607, FALSE + } +}; +*/ + +/* +The corresponding APIs required to be invoked are: +SetConnector( ConCamera, TRUE/FALSE ); +SetSignalFormat( spec.signal ); +SetVideoFormat( spec.vidFormat ); +SetSyncVideoRef( spec.syncRef ); +SetEnableSyncOutput( spec.syncOutput ); +SetTimGenByte( SRAMTable[ spec.tgb ], SRAMTableSize[ spec.tgb ] ); +SetHReset( spec.HReset ); +SetPLL( spec.pllFreq ); +SetDecInputClock( spec.iClk ); +SetVideoInfo( spec.vSize ); +SetTotalLineCount( spec.lineCount ); +SetInterlaceMode( spec.interlace ); +*/ + +/* from web: + Video Sampling +Digital video is a sampled form of analog video. The most common sampling schemes in use today are: + Pixel Clock Horiz Horiz Vert + Rate Total Active +NTSC square pixel 12.27 MHz 780 640 525 +NTSC CCIR-601 13.5 MHz 858 720 525 +NTSC 4FSc 14.32 MHz 910 768 525 +PAL square pixel 14.75 MHz 944 768 625 +PAL CCIR-601 13.5 MHz 864 720 625 +PAL 4FSc 17.72 MHz 1135 948 625 + +For the CCIR-601 standards, the sampling is based on a static orthogonal sampling grid. The luminance component (Y) is sampled at 13.5 MHz, while the two color difference signals, Cr and Cb are sampled at half that, or 6.75 MHz. The Cr and Cb samples are colocated with alternate Y samples, and they are taken at the same position on each line, such that one sample is coincident with the 50% point of the falling edge of analog sync. The samples are coded to either 8 or 10 bits per component. +*/ + +/* from DScaler:*/ +/* +//=========================================================================== +// CCIR656 Digital Input Support: The tables were taken from DScaler proyect +// +// 13 Dec 2000 - Michael Eskin, Conexant Systems - Initial version +// + +//=========================================================================== +// Timing generator SRAM table values for CCIR601 720x480 NTSC +//=========================================================================== +// For NTSC CCIR656 +BYTE BtCard::SRAMTable_NTSC[] = +{ + // SRAM Timing Table for NTSC + 0x0c, 0xc0, 0x00, + 0x00, 0x90, 0xc2, + 0x03, 0x10, 0x03, + 0x06, 0x10, 0x34, + 0x12, 0x12, 0x65, + 0x02, 0x13, 0x24, + 0x19, 0x00, 0x24, + 0x39, 0x00, 0x96, + 0x59, 0x08, 0x93, + 0x83, 0x08, 0x97, + 0x03, 0x50, 0x30, + 0xc0, 0x40, 0x30, + 0x86, 0x01, 0x01, + 0xa6, 0x0d, 0x62, + 0x03, 0x11, 0x61, + 0x05, 0x37, 0x30, + 0xac, 0x21, 0x50 +}; + +//=========================================================================== +// Timing generator SRAM table values for CCIR601 720x576 NTSC +//=========================================================================== +// For PAL CCIR656 +BYTE BtCard::SRAMTable_PAL[] = +{ + // SRAM Timing Table for PAL + 0x36, 0x11, 0x01, + 0x00, 0x90, 0x02, + 0x05, 0x10, 0x04, + 0x16, 0x14, 0x05, + 0x11, 0x00, 0x04, + 0x12, 0xc0, 0x00, + 0x31, 0x00, 0x06, + 0x51, 0x08, 0x03, + 0x89, 0x08, 0x07, + 0xc0, 0x44, 0x00, + 0x81, 0x01, 0x01, + 0xa9, 0x0d, 0x02, + 0x02, 0x50, 0x03, + 0x37, 0x3d, 0x00, + 0xaf, 0x21, 0x00, +}; +*/ diff --git a/linux/drivers/media/video/bt848.h b/linux/drivers/media/video/bt848.h new file mode 100644 index 000000000..a1a3a0dad --- /dev/null +++ b/linux/drivers/media/video/bt848.h @@ -0,0 +1,366 @@ +/* + bt848.h - Bt848 register offsets + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _BT848_H_ +#define _BT848_H_ + +#ifndef PCI_VENDOR_ID_BROOKTREE +#define PCI_VENDOR_ID_BROOKTREE 0x109e +#endif +#ifndef PCI_DEVICE_ID_BT848 +#define PCI_DEVICE_ID_BT848 0x350 +#endif +#ifndef PCI_DEVICE_ID_BT849 +#define PCI_DEVICE_ID_BT849 0x351 +#endif +#ifndef PCI_DEVICE_ID_BT878 +#define PCI_DEVICE_ID_BT878 0x36e +#endif +#ifndef PCI_DEVICE_ID_BT879 +#define PCI_DEVICE_ID_BT879 0x36f +#endif + + +/* Brooktree 848 registers */ + +#define BT848_DSTATUS 0x000 +#define BT848_DSTATUS_PRES (1<<7) +#define BT848_DSTATUS_HLOC (1<<6) +#define BT848_DSTATUS_FIELD (1<<5) +#define BT848_DSTATUS_NUML (1<<4) +#define BT848_DSTATUS_CSEL (1<<3) +#define BT848_DSTATUS_PLOCK (1<<2) +#define BT848_DSTATUS_LOF (1<<1) +#define BT848_DSTATUS_COF (1<<0) + +#define BT848_IFORM 0x004 +#define BT848_IFORM_HACTIVE (1<<7) +#define BT848_IFORM_MUXSEL (3<<5) +#define BT848_IFORM_MUX0 (2<<5) +#define BT848_IFORM_MUX1 (3<<5) +#define BT848_IFORM_MUX2 (1<<5) +#define BT848_IFORM_XTSEL (3<<3) +#define BT848_IFORM_XT0 (1<<3) +#define BT848_IFORM_XT1 (2<<3) +#define BT848_IFORM_XTAUTO (3<<3) +#define BT848_IFORM_XTBOTH (3<<3) +#define BT848_IFORM_NTSC 1 +#define BT848_IFORM_NTSC_J 2 +#define BT848_IFORM_PAL_BDGHI 3 +#define BT848_IFORM_PAL_M 4 +#define BT848_IFORM_PAL_N 5 +#define BT848_IFORM_SECAM 6 +#define BT848_IFORM_PAL_NC 7 +#define BT848_IFORM_AUTO 0 +#define BT848_IFORM_NORM 7 + +#define BT848_TDEC 0x008 +#define BT848_TDEC_DEC_FIELD (1<<7) +#define BT848_TDEC_FLDALIGN (1<<6) +#define BT848_TDEC_DEC_RAT (0x1f) + +#define BT848_E_CROP 0x00C +#define BT848_O_CROP 0x08C + +#define BT848_E_VDELAY_LO 0x010 +#define BT848_O_VDELAY_LO 0x090 + +#define BT848_E_VACTIVE_LO 0x014 +#define BT848_O_VACTIVE_LO 0x094 + +#define BT848_E_HDELAY_LO 0x018 +#define BT848_O_HDELAY_LO 0x098 + +#define BT848_E_HACTIVE_LO 0x01C +#define BT848_O_HACTIVE_LO 0x09C + +#define BT848_E_HSCALE_HI 0x020 +#define BT848_O_HSCALE_HI 0x0A0 + +#define BT848_E_HSCALE_LO 0x024 +#define BT848_O_HSCALE_LO 0x0A4 + +#define BT848_BRIGHT 0x028 + +#define BT848_E_CONTROL 0x02C +#define BT848_O_CONTROL 0x0AC +#define BT848_CONTROL_LNOTCH (1<<7) +#define BT848_CONTROL_COMP (1<<6) +#define BT848_CONTROL_LDEC (1<<5) +#define BT848_CONTROL_CBSENSE (1<<4) +#define BT848_CONTROL_CON_MSB (1<<2) +#define BT848_CONTROL_SAT_U_MSB (1<<1) +#define BT848_CONTROL_SAT_V_MSB (1<<0) + +#define BT848_CONTRAST_LO 0x030 +#define BT848_SAT_U_LO 0x034 +#define BT848_SAT_V_LO 0x038 +#define BT848_HUE 0x03C + +#define BT848_E_SCLOOP 0x040 +#define BT848_O_SCLOOP 0x0C0 +#define BT848_SCLOOP_CAGC (1<<6) +#define BT848_SCLOOP_CKILL (1<<5) +#define BT848_SCLOOP_HFILT_AUTO (0<<3) +#define BT848_SCLOOP_HFILT_CIF (1<<3) +#define BT848_SCLOOP_HFILT_QCIF (2<<3) +#define BT848_SCLOOP_HFILT_ICON (3<<3) + +#define BT848_SCLOOP_PEAK (1<<7) +#define BT848_SCLOOP_HFILT_MINP (1<<3) +#define BT848_SCLOOP_HFILT_MEDP (2<<3) +#define BT848_SCLOOP_HFILT_MAXP (3<<3) + + +#define BT848_OFORM 0x048 +#define BT848_OFORM_RANGE (1<<7) +#define BT848_OFORM_CORE0 (0<<5) +#define BT848_OFORM_CORE8 (1<<5) +#define BT848_OFORM_CORE16 (2<<5) +#define BT848_OFORM_CORE32 (3<<5) + +#define BT848_E_VSCALE_HI 0x04C +#define BT848_O_VSCALE_HI 0x0CC +#define BT848_VSCALE_YCOMB (1<<7) +#define BT848_VSCALE_COMB (1<<6) +#define BT848_VSCALE_INT (1<<5) +#define BT848_VSCALE_HI 15 + +#define BT848_E_VSCALE_LO 0x050 +#define BT848_O_VSCALE_LO 0x0D0 +#define BT848_TEST 0x054 +#define BT848_ADELAY 0x060 +#define BT848_BDELAY 0x064 + +#define BT848_ADC 0x068 +#define BT848_ADC_RESERVED (2<<6) +#define BT848_ADC_SYNC_T (1<<5) +#define BT848_ADC_AGC_EN (1<<4) +#define BT848_ADC_CLK_SLEEP (1<<3) +#define BT848_ADC_Y_SLEEP (1<<2) +#define BT848_ADC_C_SLEEP (1<<1) +#define BT848_ADC_CRUSH (1<<0) + +#define BT848_WC_UP 0x044 +#define BT848_WC_DOWN 0x078 + +#define BT848_E_VTC 0x06C +#define BT848_O_VTC 0x0EC +#define BT848_VTC_HSFMT (1<<7) +#define BT848_VTC_VFILT_2TAP 0 +#define BT848_VTC_VFILT_3TAP 1 +#define BT848_VTC_VFILT_4TAP 2 +#define BT848_VTC_VFILT_5TAP 3 + +#define BT848_SRESET 0x07C + +#define BT848_COLOR_FMT 0x0D4 +#define BT848_COLOR_FMT_O_RGB32 (0<<4) +#define BT848_COLOR_FMT_O_RGB24 (1<<4) +#define BT848_COLOR_FMT_O_RGB16 (2<<4) +#define BT848_COLOR_FMT_O_RGB15 (3<<4) +#define BT848_COLOR_FMT_O_YUY2 (4<<4) +#define BT848_COLOR_FMT_O_BtYUV (5<<4) +#define BT848_COLOR_FMT_O_Y8 (6<<4) +#define BT848_COLOR_FMT_O_RGB8 (7<<4) +#define BT848_COLOR_FMT_O_YCrCb422 (8<<4) +#define BT848_COLOR_FMT_O_YCrCb411 (9<<4) +#define BT848_COLOR_FMT_O_RAW (14<<4) +#define BT848_COLOR_FMT_E_RGB32 0 +#define BT848_COLOR_FMT_E_RGB24 1 +#define BT848_COLOR_FMT_E_RGB16 2 +#define BT848_COLOR_FMT_E_RGB15 3 +#define BT848_COLOR_FMT_E_YUY2 4 +#define BT848_COLOR_FMT_E_BtYUV 5 +#define BT848_COLOR_FMT_E_Y8 6 +#define BT848_COLOR_FMT_E_RGB8 7 +#define BT848_COLOR_FMT_E_YCrCb422 8 +#define BT848_COLOR_FMT_E_YCrCb411 9 +#define BT848_COLOR_FMT_E_RAW 14 + +#define BT848_COLOR_FMT_RGB32 0x00 +#define BT848_COLOR_FMT_RGB24 0x11 +#define BT848_COLOR_FMT_RGB16 0x22 +#define BT848_COLOR_FMT_RGB15 0x33 +#define BT848_COLOR_FMT_YUY2 0x44 +#define BT848_COLOR_FMT_BtYUV 0x55 +#define BT848_COLOR_FMT_Y8 0x66 +#define BT848_COLOR_FMT_RGB8 0x77 +#define BT848_COLOR_FMT_YCrCb422 0x88 +#define BT848_COLOR_FMT_YCrCb411 0x99 +#define BT848_COLOR_FMT_RAW 0xee + +#define BT848_VTOTAL_LO 0xB0 +#define BT848_VTOTAL_HI 0xB4 + +#define BT848_COLOR_CTL 0x0D8 +#define BT848_COLOR_CTL_EXT_FRMRATE (1<<7) +#define BT848_COLOR_CTL_COLOR_BARS (1<<6) +#define BT848_COLOR_CTL_RGB_DED (1<<5) +#define BT848_COLOR_CTL_GAMMA (1<<4) +#define BT848_COLOR_CTL_WSWAP_ODD (1<<3) +#define BT848_COLOR_CTL_WSWAP_EVEN (1<<2) +#define BT848_COLOR_CTL_BSWAP_ODD (1<<1) +#define BT848_COLOR_CTL_BSWAP_EVEN (1<<0) + +#define BT848_CAP_CTL 0x0DC +#define BT848_CAP_CTL_DITH_FRAME (1<<4) +#define BT848_CAP_CTL_CAPTURE_VBI_ODD (1<<3) +#define BT848_CAP_CTL_CAPTURE_VBI_EVEN (1<<2) +#define BT848_CAP_CTL_CAPTURE_ODD (1<<1) +#define BT848_CAP_CTL_CAPTURE_EVEN (1<<0) + +#define BT848_VBI_PACK_SIZE 0x0E0 + +#define BT848_VBI_PACK_DEL 0x0E4 +#define BT848_VBI_PACK_DEL_VBI_HDELAY 0xfc +#define BT848_VBI_PACK_DEL_EXT_FRAME 2 +#define BT848_VBI_PACK_DEL_VBI_PKT_HI 1 + + +#define BT848_INT_STAT 0x100 +#define BT848_INT_MASK 0x104 + +#define BT848_INT_ETBF (1<<23) + +#define BT848_INT_RISCS (0xf<<28) +#define BT848_INT_RISC_EN (1<<27) +#define BT848_INT_RACK (1<<25) +#define BT848_INT_FIELD (1<<24) +#define BT848_INT_SCERR (1<<19) +#define BT848_INT_OCERR (1<<18) +#define BT848_INT_PABORT (1<<17) +#define BT848_INT_RIPERR (1<<16) +#define BT848_INT_PPERR (1<<15) +#define BT848_INT_FDSR (1<<14) +#define BT848_INT_FTRGT (1<<13) +#define BT848_INT_FBUS (1<<12) +#define BT848_INT_RISCI (1<<11) +#define BT848_INT_GPINT (1<<9) +#define BT848_INT_I2CDONE (1<<8) +#define BT848_INT_VPRES (1<<5) +#define BT848_INT_HLOCK (1<<4) +#define BT848_INT_OFLOW (1<<3) +#define BT848_INT_HSYNC (1<<2) +#define BT848_INT_VSYNC (1<<1) +#define BT848_INT_FMTCHG (1<<0) + + +#define BT848_GPIO_DMA_CTL 0x10C +#define BT848_GPIO_DMA_CTL_GPINTC (1<<15) +#define BT848_GPIO_DMA_CTL_GPINTI (1<<14) +#define BT848_GPIO_DMA_CTL_GPWEC (1<<13) +#define BT848_GPIO_DMA_CTL_GPIOMODE (3<<11) +#define BT848_GPIO_DMA_CTL_GPCLKMODE (1<<10) +#define BT848_GPIO_DMA_CTL_PLTP23_4 (0<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_8 (1<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_16 (2<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_32 (3<<6) +#define BT848_GPIO_DMA_CTL_PLTP1_4 (0<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_8 (1<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_16 (2<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_32 (3<<4) +#define BT848_GPIO_DMA_CTL_PKTP_4 (0<<2) +#define BT848_GPIO_DMA_CTL_PKTP_8 (1<<2) +#define BT848_GPIO_DMA_CTL_PKTP_16 (2<<2) +#define BT848_GPIO_DMA_CTL_PKTP_32 (3<<2) +#define BT848_GPIO_DMA_CTL_RISC_ENABLE (1<<1) +#define BT848_GPIO_DMA_CTL_FIFO_ENABLE (1<<0) + +#define BT848_I2C 0x110 +#define BT878_I2C_MODE (1<<7) +#define BT878_I2C_RATE (1<<6) +#define BT878_I2C_NOSTOP (1<<5) +#define BT878_I2C_NOSTART (1<<4) +#define BT848_I2C_DIV (0xf<<4) +#define BT848_I2C_SYNC (1<<3) +#define BT848_I2C_W3B (1<<2) +#define BT848_I2C_SCL (1<<1) +#define BT848_I2C_SDA (1<<0) + +#define BT848_RISC_STRT_ADD 0x114 +#define BT848_GPIO_OUT_EN 0x118 +#define BT848_GPIO_REG_INP 0x11C +#define BT848_RISC_COUNT 0x120 +#define BT848_GPIO_DATA 0x200 + + +/* Bt848 RISC commands */ + +/* only for the SYNC RISC command */ +#define BT848_FIFO_STATUS_FM1 0x06 +#define BT848_FIFO_STATUS_FM3 0x0e +#define BT848_FIFO_STATUS_SOL 0x02 +#define BT848_FIFO_STATUS_EOL4 0x01 +#define BT848_FIFO_STATUS_EOL3 0x0d +#define BT848_FIFO_STATUS_EOL2 0x09 +#define BT848_FIFO_STATUS_EOL1 0x05 +#define BT848_FIFO_STATUS_VRE 0x04 +#define BT848_FIFO_STATUS_VRO 0x0c +#define BT848_FIFO_STATUS_PXV 0x00 + +#define BT848_RISC_RESYNC (1<<15) + +/* WRITE and SKIP */ +/* disable which bytes of each DWORD */ +#define BT848_RISC_BYTE0 (1U<<12) +#define BT848_RISC_BYTE1 (1U<<13) +#define BT848_RISC_BYTE2 (1U<<14) +#define BT848_RISC_BYTE3 (1U<<15) +#define BT848_RISC_BYTE_ALL (0x0fU<<12) +#define BT848_RISC_BYTE_NONE 0 +/* cause RISCI */ +#define BT848_RISC_IRQ (1U<<24) +/* RISC command is last one in this line */ +#define BT848_RISC_EOL (1U<<26) +/* RISC command is first one in this line */ +#define BT848_RISC_SOL (1U<<27) + +#define BT848_RISC_WRITE (0x01U<<28) +#define BT848_RISC_SKIP (0x02U<<28) +#define BT848_RISC_WRITEC (0x05U<<28) +#define BT848_RISC_JUMP (0x07U<<28) +#define BT848_RISC_SYNC (0x08U<<28) + +#define BT848_RISC_WRITE123 (0x09U<<28) +#define BT848_RISC_SKIP123 (0x0aU<<28) +#define BT848_RISC_WRITE1S23 (0x0bU<<28) + + +/* Bt848A and higher only !! */ +#define BT848_TGLB 0x080 +#define BT848_TGCTRL 0x084 +#define BT848_FCAP 0x0E8 +#define BT848_PLL_F_LO 0x0F0 +#define BT848_PLL_F_HI 0x0F4 + +#define BT848_PLL_XCI 0x0F8 +#define BT848_PLL_X (1<<7) +#define BT848_PLL_C (1<<6) + +#define BT848_DVSIF 0x0FC + +/* Bt878 register */ + +#define BT878_DEVCTRL 0x40 +#define BT878_EN_TBFX 0x02 +#define BT878_EN_VSFX 0x04 + +#endif diff --git a/linux/drivers/media/video/btcx-risc.c b/linux/drivers/media/video/btcx-risc.c new file mode 100644 index 000000000..c6e635a07 --- /dev/null +++ b/linux/drivers/media/video/btcx-risc.c @@ -0,0 +1,258 @@ +/* + btcx-risc.c + + bt848/bt878/cx2388x risc code generator. + + (c) 2000-03 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/videodev2.h> +#include <asm/page.h> +#include <asm/pgtable.h> + +#include "btcx-risc.h" + +MODULE_DESCRIPTION("some code shared by bttv and cx88xx drivers"); +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("GPL"); + +static unsigned int debug = 0; +MODULE_PARM(debug,"i"); +MODULE_PARM_DESC(debug,"debug messages, default is 0 (no)"); + +/* ---------------------------------------------------------- */ +/* allocate/free risc memory */ + +static int memcnt; + +void btcx_riscmem_free(struct pci_dev *pci, + struct btcx_riscmem *risc) +{ + if (NULL == risc->cpu) + return; + pci_free_consistent(pci, risc->size, risc->cpu, risc->dma); + memset(risc,0,sizeof(*risc)); + if (debug) { + memcnt--; + printk("btcx: riscmem free [%d]\n",memcnt); + } +} + +int btcx_riscmem_alloc(struct pci_dev *pci, + struct btcx_riscmem *risc, + unsigned int size) +{ + u32 *cpu; + dma_addr_t dma; + + if (NULL != risc->cpu && risc->size < size) + btcx_riscmem_free(pci,risc); + if (NULL == risc->cpu) { + cpu = pci_alloc_consistent(pci, size, &dma); + if (NULL == cpu) + return -ENOMEM; + risc->cpu = cpu; + risc->dma = dma; + risc->size = size; + if (debug) { + memcnt++; + printk("btcx: riscmem alloc size=%d [%d]\n",size,memcnt); + } + } + memset(risc->cpu,0,risc->size); + return 0; +} + +/* ---------------------------------------------------------- */ +/* screen overlay helpers */ + +int +btcx_screen_clips(int swidth, int sheight, struct v4l2_rect *win, + struct v4l2_clip *clips, unsigned int n) +{ + if (win->left < 0) { + /* left */ + clips[n].c.left = 0; + clips[n].c.top = 0; + clips[n].c.width = -win->left; + clips[n].c.height = win->height; + n++; + } + if (win->left + win->width > swidth) { + /* right */ + clips[n].c.left = swidth - win->left; + clips[n].c.top = 0; + clips[n].c.width = win->width - clips[n].c.left; + clips[n].c.height = win->height; + n++; + } + if (win->top < 0) { + /* top */ + clips[n].c.left = 0; + clips[n].c.top = 0; + clips[n].c.width = win->width; + clips[n].c.height = -win->top; + n++; + } + if (win->top + win->height > sheight) { + /* bottom */ + clips[n].c.left = 0; + clips[n].c.top = sheight - win->top; + clips[n].c.width = win->width; + clips[n].c.height = win->height - clips[n].c.top; + n++; + } + return n; +} + +int +btcx_align(struct v4l2_rect *win, struct v4l2_clip *clips, unsigned int n, int mask) +{ + s32 nx,nw,dx; + unsigned int i; + + /* fixup window */ + nx = (win->left + mask) & ~mask; + nw = (win->width) & ~mask; + if (nx + nw > win->left + win->width) + nw -= mask+1; + dx = nx - win->left; + win->left = nx; + win->width = nw; + if (debug) + printk(KERN_DEBUG "btcx: window align %dx%d+%d+%d [dx=%d]\n", + win->width, win->height, win->left, win->top, dx); + + /* fixup clips */ + for (i = 0; i < n; i++) { + nx = (clips[i].c.left-dx) & ~mask; + nw = (clips[i].c.width) & ~mask; + if (nx + nw < clips[i].c.left-dx + clips[i].c.width) + nw += mask+1; + clips[i].c.left = nx; + clips[i].c.width = nw; + if (debug) + printk(KERN_DEBUG "btcx: clip align %dx%d+%d+%d\n", + clips[i].c.width, clips[i].c.height, + clips[i].c.left, clips[i].c.top); + } + return 0; +} + +void +btcx_sort_clips(struct v4l2_clip *clips, unsigned int nclips) +{ + struct v4l2_clip swap; + int i,j,n; + + if (nclips < 2) + return; + for (i = nclips-2; i >= 0; i--) { + for (n = 0, j = 0; j <= i; j++) { + if (clips[j].c.left > clips[j+1].c.left) { + swap = clips[j]; + clips[j] = clips[j+1]; + clips[j+1] = swap; + n++; + } + } + if (0 == n) + break; + } +} + +void +btcx_calc_skips(int line, int width, unsigned int *maxy, + struct btcx_skiplist *skips, unsigned int *nskips, + const struct v4l2_clip *clips, unsigned int nclips) +{ + unsigned int clip,skip; + int end,maxline; + + skip=0; + maxline = 9999; + for (clip = 0; clip < nclips; clip++) { + + /* sanity checks */ + if (clips[clip].c.left + clips[clip].c.width <= 0) + continue; + if (clips[clip].c.left > (signed)width) + break; + + /* vertical range */ + if (line > clips[clip].c.top+clips[clip].c.height-1) + continue; + if (line < clips[clip].c.top) { + if (maxline > clips[clip].c.top-1) + maxline = clips[clip].c.top-1; + continue; + } + if (maxline > clips[clip].c.top+clips[clip].c.height-1) + maxline = clips[clip].c.top+clips[clip].c.height-1; + + /* horizontal range */ + if (0 == skip || clips[clip].c.left > skips[skip-1].end) { + /* new one */ + skips[skip].start = clips[clip].c.left; + if (skips[skip].start < 0) + skips[skip].start = 0; + skips[skip].end = clips[clip].c.left + clips[clip].c.width; + if (skips[skip].end > width) + skips[skip].end = width; + skip++; + } else { + /* overlaps -- expand last one */ + end = clips[clip].c.left + clips[clip].c.width; + if (skips[skip-1].end < end) + skips[skip-1].end = end; + if (skips[skip-1].end > width) + skips[skip-1].end = width; + } + } + *nskips = skip; + *maxy = maxline; + + if (debug) { + printk(KERN_DEBUG "btcx: skips line %d-%d:",line,maxline); + for (skip = 0; skip < *nskips; skip++) { + printk(" %d-%d",skips[skip].start,skips[skip].end); + } + printk("\n"); + } +} + +/* ---------------------------------------------------------- */ + +EXPORT_SYMBOL(btcx_riscmem_alloc); +EXPORT_SYMBOL(btcx_riscmem_free); + +EXPORT_SYMBOL(btcx_screen_clips); +EXPORT_SYMBOL(btcx_align); +EXPORT_SYMBOL(btcx_sort_clips); +EXPORT_SYMBOL(btcx_calc_skips); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/btcx-risc.h b/linux/drivers/media/video/btcx-risc.h new file mode 100644 index 000000000..49daaadb0 --- /dev/null +++ b/linux/drivers/media/video/btcx-risc.h @@ -0,0 +1,33 @@ + +struct btcx_riscmem { + unsigned int size; + u32 *cpu; + u32 *jmp; + dma_addr_t dma; +}; + +struct btcx_skiplist { + int start; + int end; +}; + +int btcx_riscmem_alloc(struct pci_dev *pci, + struct btcx_riscmem *risc, + unsigned int size); +void btcx_riscmem_free(struct pci_dev *pci, + struct btcx_riscmem *risc); + +int btcx_screen_clips(int swidth, int sheight, struct v4l2_rect *win, + struct v4l2_clip *clips, unsigned int n); +int btcx_align(struct v4l2_rect *win, struct v4l2_clip *clips, + unsigned int n, int mask); +void btcx_sort_clips(struct v4l2_clip *clips, unsigned int nclips); +void btcx_calc_skips(int line, int width, unsigned int *maxy, + struct btcx_skiplist *skips, unsigned int *nskips, + const struct v4l2_clip *clips, unsigned int nclips); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/bttv-cards.c b/linux/drivers/media/video/bttv-cards.c new file mode 100644 index 000000000..3b73c6198 --- /dev/null +++ b/linux/drivers/media/video/bttv-cards.c @@ -0,0 +1,3935 @@ +/* + bttv-cards.c + + this file has configuration informations - card-specific stuff + like the big tvcards array for the most part + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2001 Gerd Knorr <kraxel@goldbach.in-berlin.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/config.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/kmod.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/vmalloc.h> +#ifdef CONFIG_FW_LOADER +# include <linux/firmware.h> +#endif + +#include <asm/io.h> + +#include "bttvp.h" +#include "bt832.h" + +/* fwd decl */ +static void boot_msp34xx(struct bttv *btv, int pin); +static void boot_bt832(struct bttv *btv); +static void hauppauge_eeprom(struct bttv *btv); +static void avermedia_eeprom(struct bttv *btv); +static void osprey_eeprom(struct bttv *btv); +static void modtec_eeprom(struct bttv *btv); +static void init_PXC200(struct bttv *btv); + +static void winview_audio(struct bttv *btv, struct video_audio *v, int set); +static void lt9415_audio(struct bttv *btv, struct video_audio *v, int set); +static void avermedia_tvphone_audio(struct bttv *btv, struct video_audio *v, + int set); +static void avermedia_tv_stereo_audio(struct bttv *btv, struct video_audio *v, + int set); +static void terratv_audio(struct bttv *btv, struct video_audio *v, int set); +static void gvbctv3pci_audio(struct bttv *btv, struct video_audio *v, int set); +static void gvbctv5pci_audio(struct bttv *btv, struct video_audio *v, int set); +static void winfast2000_audio(struct bttv *btv, struct video_audio *v, int set); +static void pvbt878p9b_audio(struct bttv *btv, struct video_audio *v, int set); +static void fv2000s_audio(struct bttv *btv, struct video_audio *v, int set); +static void windvr_audio(struct bttv *btv, struct video_audio *v, int set); +static void adtvk503_audio(struct bttv *btv, struct video_audio *v, int set); +static void rv605_muxsel(struct bttv *btv, unsigned int input); +static void eagle_muxsel(struct bttv *btv, unsigned int input); +static void xguard_muxsel(struct bttv *btv, unsigned int input); +static void ivc120_muxsel(struct bttv *btv, unsigned int input); +static void gvc1100_muxsel(struct bttv *btv, unsigned int input); + +static int terratec_active_radio_upgrade(struct bttv *btv); +static int tea5757_read(struct bttv *btv); +static int tea5757_write(struct bttv *btv, int value); +static void identify_by_eeprom(struct bttv *btv, + unsigned char eeprom_data[256]); + +/* config variables */ +static unsigned int triton1=0; +static unsigned int vsfx=0; +static unsigned int latency = UNSET; +unsigned int no_overlay=-1; + +static unsigned int card[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int pll[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int tuner[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int svhs[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int remote[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +#ifdef MODULE +static unsigned int autoload = 1; +#else +static unsigned int autoload = 0; +#endif +static unsigned int gpiomask = UNSET; +static unsigned int audioall = UNSET; +static unsigned int audiomux[5] = { [ 0 ... 4 ] = UNSET }; + +/* insmod options */ +MODULE_PARM(triton1,"i"); +MODULE_PARM_DESC(triton1,"set ETBF pci config bit " + "[enable bug compatibility for triton1 + others]"); +MODULE_PARM(vsfx,"i"); +MODULE_PARM_DESC(vsfx,"set VSFX pci config bit " + "[yet another chipset flaw workaround]"); +MODULE_PARM(no_overlay,"i"); +MODULE_PARM(latency,"i"); +MODULE_PARM_DESC(latency,"pci latency timer"); +MODULE_PARM(card,"1-" __stringify(BTTV_MAX) "i"); +MODULE_PARM_DESC(card,"specify TV/grabber card model, see CARDLIST file for a list"); +MODULE_PARM(pll,"1-" __stringify(BTTV_MAX) "i"); +MODULE_PARM_DESC(pll,"specify installed crystal (0=none, 28=28 MHz, 35=35 MHz)"); +MODULE_PARM(tuner,"1-" __stringify(BTTV_MAX) "i"); +MODULE_PARM_DESC(tuner,"specify installed tuner type"); +MODULE_PARM(autoload,"i"); +MODULE_PARM_DESC(autoload,"automatically load i2c modules like tuner.o, default is 1 (yes)"); + +MODULE_PARM(svhs,"1-" __stringify(BTTV_MAX) "i"); +MODULE_PARM(remote,"1-" __stringify(BTTV_MAX) "i"); + +MODULE_PARM(gpiomask,"i"); +MODULE_PARM(audioall,"i"); +MODULE_PARM(audiomux,"1-5i"); + +/* kernel args */ +#ifndef MODULE +static int __init p_card(char *str) { return bttv_parse(str,BTTV_MAX,card); } +static int __init p_pll(char *str) { return bttv_parse(str,BTTV_MAX,pll); } +static int __init p_tuner(char *str) { return bttv_parse(str,BTTV_MAX,tuner); } +__setup("bttv.card=", p_card); +__setup("bttv.pll=", p_pll); +__setup("bttv.tuner=", p_tuner); + +int __init bttv_parse(char *str, int max, int *vals) +{ + int i,number,res = 2; + + for (i = 0; res == 2 && i < max; i++) { + res = get_option(&str,&number); + if (res) + vals[i] = number; + } + return 1; +} +#endif + +/* ----------------------------------------------------------------------- */ +/* list of card IDs for bt878+ cards */ + +static struct CARD { + unsigned id; + int cardnr; + char *name; +} cards[] __devinitdata = { + { 0x13eb0070, BTTV_HAUPPAUGE878, "Hauppauge WinTV" }, + { 0x39000070, BTTV_HAUPPAUGE878, "Hauppauge WinTV-D" }, + { 0x45000070, BTTV_HAUPPAUGEPVR, "Hauppauge WinTV/PVR" }, + { 0xff000070, BTTV_OSPREY1x0, "Osprey-100" }, + { 0xff010070, BTTV_OSPREY2x0_SVID,"Osprey-200" }, + { 0xff020070, BTTV_OSPREY500, "Osprey-500" }, + { 0xff030070, BTTV_OSPREY2000, "Osprey-2000" }, + { 0xff040070, BTTV_OSPREY540, "Osprey-540" }, + + { 0x00011002, BTTV_ATI_TVWONDER, "ATI TV Wonder" }, + { 0x00031002, BTTV_ATI_TVWONDERVE,"ATI TV Wonder/VE" }, + + { 0x6606107d, BTTV_WINFAST2000, "Leadtek WinFast TV 2000" }, + { 0x6607107d, BTTV_WINFASTVC100, "Leadtek WinFast VC 100" }, + { 0x263610b4, BTTV_STB2, "STB TV PCI FM, Gateway P/N 6000704" }, + { 0x264510b4, BTTV_STB2, "STB TV PCI FM, Gateway P/N 6000704" }, + { 0x402010fc, BTTV_GVBCTV3PCI, "I-O Data Co. GV-BCTV3/PCI" }, + { 0x405010fc, BTTV_GVBCTV4PCI, "I-O Data Co. GV-BCTV4/PCI" }, + { 0x407010fc, BTTV_GVBCTV5PCI, "I-O Data Co. GV-BCTV5/PCI" }, + { 0xd01810fc, BTTV_GVBCTV3PCI, "I-O Data Co. GV-BCTV3/PCI" }, + + { 0x001211bd, BTTV_PINNACLE, "Pinnacle PCTV" }, + { 0x001c11bd, BTTV_PINNACLESAT, "Pinnacle PCTV Sat" }, + // some cards ship with byteswapped IDs ... + { 0x1200bd11, BTTV_PINNACLE, "Pinnacle PCTV [bswap]" }, + { 0xff00bd11, BTTV_PINNACLE, "Pinnacle PCTV [bswap]" }, + + { 0x3000121a, BTTV_VOODOOTV_FM, "3Dfx VoodooTV FM/ VoodooTV 200" }, + { 0x3060121a, BTTV_STB2, "3Dfx VoodooTV 100/ STB OEM" }, + + { 0x3000144f, BTTV_MAGICTVIEW063, "(Askey Magic/others) TView99 CPH06x" }, + { 0x3002144f, BTTV_MAGICTVIEW061, "(Askey Magic/others) TView99 CPH05x" }, + { 0x3005144f, BTTV_MAGICTVIEW061, "(Askey Magic/others) TView99 CPH061/06L (T1/LC)" }, + { 0x5000144f, BTTV_MAGICTVIEW061, "Askey CPH050" }, + { 0x300014ff, BTTV_MAGICTVIEW061, "TView 99 (CPH061)" }, + { 0x300214ff, BTTV_PHOEBE_TVMAS, "Phoebe TV Master (CPH060)" }, + + { 0x00011461, BTTV_AVPHONE98, "AVerMedia TVPhone98" }, + { 0x00021461, BTTV_AVERMEDIA98, "AVermedia TVCapture 98" }, + { 0x00031461, BTTV_AVPHONE98, "AVerMedia TVPhone98" }, + { 0x00041461, BTTV_AVERMEDIA98, "AVerMedia TVCapture 98" }, + { 0x03001461, BTTV_AVERMEDIA98, "VDOMATE TV TUNER CARD" }, + + { 0x1117153b, BTTV_TERRATVALUE, "Terratec TValue (Philips PAL B/G)" }, + { 0x1118153b, BTTV_TERRATVALUE, "Terratec TValue (Temic PAL B/G)" }, + { 0x1119153b, BTTV_TERRATVALUE, "Terratec TValue (Philips PAL I)" }, + { 0x111a153b, BTTV_TERRATVALUE, "Terratec TValue (Temic PAL I)" }, + + { 0x1123153b, BTTV_TERRATVRADIO, "Terratec TV Radio+" }, + { 0x1127153b, BTTV_TERRATV, "Terratec TV+ (V1.05)" }, + // clashes with FlyVideo + //{ 0x18521852, BTTV_TERRATV, "Terratec TV+ (V1.10)" }, + { 0x1134153b, BTTV_TERRATVALUE, "Terratec TValue (LR102)" }, + { 0x1135153b, BTTV_TERRATVALUER, "Terratec TValue Radio" }, // LR102 + { 0x5018153b, BTTV_TERRATVALUE, "Terratec TValue" }, // ?? + + { 0x400015b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV" }, + { 0x400a15b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV" }, + { 0x400d15b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV / Radio" }, + { 0x401015b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV / Radio" }, + { 0x401615b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV / Radio" }, + + { 0x1430aa00, BTTV_PV143, "Provideo PV143A" }, + { 0x1431aa00, BTTV_PV143, "Provideo PV143B" }, + { 0x1432aa00, BTTV_PV143, "Provideo PV143C" }, + { 0x1433aa00, BTTV_PV143, "Provideo PV143D" }, + + { 0x1460aa00, BTTV_PV150, "Provideo PV150A-1" }, + { 0x1461aa01, BTTV_PV150, "Provideo PV150A-2" }, + { 0x1462aa02, BTTV_PV150, "Provideo PV150A-3" }, + { 0x1463aa03, BTTV_PV150, "Provideo PV150A-4" }, + + { 0x1464aa04, BTTV_PV150, "Provideo PV150B-1" }, + { 0x1465aa05, BTTV_PV150, "Provideo PV150B-2" }, + { 0x1466aa06, BTTV_PV150, "Provideo PV150B-3" }, + { 0x1467aa07, BTTV_PV150, "Provideo PV150B-4" }, + + { 0xa132ff00, BTTV_IVC100, "IVC-100" }, + { 0xa1550000, BTTV_IVC200, "IVC-200" }, + { 0xa1550001, BTTV_IVC200, "IVC-200" }, + { 0xa1550002, BTTV_IVC200, "IVC-200" }, + { 0xa1550003, BTTV_IVC200, "IVC-200" }, + { 0xa1550100, BTTV_IVC200, "IVC-200G" }, + { 0xa1550101, BTTV_IVC200, "IVC-200G" }, + { 0xa1550102, BTTV_IVC200, "IVC-200G" }, + { 0xa1550103, BTTV_IVC200, "IVC-200G" }, + { 0xa182ff00, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff01, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff02, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff03, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff04, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff05, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff06, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff07, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff08, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff09, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0a, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0b, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0c, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0d, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0e, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0f, BTTV_IVC120, "IVC-120G" }, + + { 0x41424344, BTTV_GRANDTEC, "GrandTec Multi Capture" }, + { 0x01020304, BTTV_XGUARD, "Grandtec Grand X-Guard" }, + + { 0x010115cb, BTTV_GMV1, "AG GMV1" }, + { 0x010114c7, BTTV_MODTEC_205, "Modular Technology MM201/MM202/MM205/MM210/MM215 PCTV" }, + { 0x18501851, BTTV_CHRONOS_VS2, "FlyVideo 98 (LR50)/ Chronos Video Shuttle II" }, + { 0x18511851, BTTV_FLYVIDEO98EZ, "FlyVideo 98EZ (LR51)/ CyberMail AV" }, + { 0x18521852, BTTV_TYPHOON_TVIEW, "FlyVideo 98FM (LR50)/ Typhoon TView TV/FM Tuner" }, + { 0x41a0a051, BTTV_FLYVIDEO_98FM, "Lifeview FlyVideo 98 LR50 Rev Q" }, + { 0x10b42636, BTTV_HAUPPAUGE878, "STB ???" }, + { 0x217d6606, BTTV_WINFAST2000, "Leadtek WinFast TV 2000" }, + { 0xfff6f6ff, BTTV_WINFAST2000, "Leadtek WinFast TV 2000" }, + { 0x03116000, BTTV_SENSORAY311, "Sensoray 311" }, + { 0x00790e11, BTTV_WINDVR, "Canopus WinDVR PCI" }, + { 0xa0fca1a0, BTTV_ZOLTRIX, "Face to Face Tvmax" }, + { 0x20007063, BTTV_PC_HDTV, "pcHDTV HD-2000 TV"}, + { 0x82b2aa6a, BTTV_SIMUS_GVC1100, "SIMUS GVC1100" }, + { 0x146caa0c, BTTV_PV951, "ituner spectra8" }, + + { 0x40111554, BTTV_PV_BT878P_9B, "Prolink Pixelview PV-BT" }, + { 0x17de0a01, BTTV_KWORLD, "Mecer TV/FM/Video Tuner" }, + + // likely broken, vendor id doesn't match the other magic views ... + //{ 0xa0fca04f, BTTV_MAGICTVIEW063, "Guillemot Maxi TV Video 3" }, + + // DVB cards (using pci function .1 for mpeg data xfer) + { 0x01010071, BTTV_NEBULA_DIGITV, "Nebula Electronics DigiTV" }, + { 0x002611bd, BTTV_TWINHAN_DST, "Pinnacle PCTV SAT CI" }, + { 0x00011822, BTTV_TWINHAN_DST, "Twinhan VisionPlus DVB-T" }, + { 0xfc00270f, BTTV_TWINHAN_DST, "ChainTech digitop DST-1000 DVB-S" }, + + { 0, -1, NULL } +}; + +/* ----------------------------------------------------------------------- */ +/* array with description for bt848 / bt878 tv/grabber cards */ + +struct tvcard bttv_tvcards[] = { +{ +/* ---- card 0x00 ---------------------------------- */ + .name = " *** UNKNOWN/GENERIC *** ", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .muxsel = { 2, 3, 1, 0}, + .tuner_type = -1, +},{ + .name = "MIRO PCTV", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 2, 0, 0, 0, 10}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "Hauppauge (bt848)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 1, 2, 3, 4}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "STB, Gateway P/N 6000699 (bt848)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 4, 0, 2, 3, 1}, + .no_msp34xx = 1, + .needs_tvaudio = 1, + .tuner_type = TUNER_PHILIPS_NTSC, + .pll = PLL_28, + .has_radio = 1, +},{ + +/* ---- card 0x04 ---------------------------------- */ + .name = "Intel Create and Share PCI/ Smart Video Recorder III", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = 2, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .tuner_type = 4, +},{ + .name = "Diamond DTV2000", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 3, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 0, 1, 0, 1, 3}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "AVerMedia TVPhone", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .muxsel = { 2, 3, 1, 1}, + .gpiomask = 0x0f, + .audiomux = { 0x0c, 0x04, 0x08, 0x04, 0}, + /* 0x04 for some cards ?? */ + .needs_tvaudio = 1, + .tuner_type = -1, + .audio_hook = avermedia_tvphone_audio, + .has_remote = 1, +},{ + .name = "MATRIX-Vision MV-Delta", + .video_inputs = 5, + .audio_inputs = 1, + .tuner = -1, + .svhs = 3, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0, 0}, + .audiomux = {0 }, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + +/* ---- card 0x08 ---------------------------------- */ + .name = "Lifeview FlyVideo II (Bt848) LR26 / MAXI TV Video PCI2 LR26", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xc00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0xc00, 0x800, 0x400, 0xc00, 0}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "IMS/IXmicro TurboTV", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 3, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 1, 1, 2, 3, 0}, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_PAL, +},{ + .name = "Hauppauge (bt878)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x0f, /* old: 7 */ + .muxsel = { 2, 0, 1, 1}, + .audiomux = { 0, 1, 2, 3, 4}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "MIRO PCTV pro", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x3014f, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x20001,0x10001, 0, 0,10}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + +/* ---- card 0x0c ---------------------------------- */ + .name = "ADS Technologies Channel Surfer TV (bt848)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 13, 14, 11, 7, 0, 0}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "AVerMedia TVCapture 98", + .video_inputs = 3, + .audio_inputs = 4, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 13, 14, 11, 7, 0, 0}, + .needs_tvaudio = 1, + .msp34xx_alt = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .audio_hook = avermedia_tv_stereo_audio, +},{ + .name = "Aimslab Video Highway Xtreme (VHX)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 2, 1, 3, 4}, /* old: { 0, 1, 2, 3, 4} */ + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Zoltrix TV-Max", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = {0 , 0, 1 , 0, 10}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + +/* ---- card 0x10 ---------------------------------- */ + .name = "Prolink Pixelview PlayTV (bt878)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x01fe00, + .muxsel = { 2, 3, 1, 1}, +#if 0 + // old + .audiomux = { 0x01c000, 0, 0x018000, 0x014000, 0x002000, 0 }, +#else + // 2003-10-20 by "Anton A. Arapov" <arapov@mail.ru> + .audiomux = { 0x001e00, 0, 0x018000, 0x014000, 0x002000, 0 }, +#endif + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Leadtek WinView 601", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x8300f8, + .muxsel = { 2, 3, 1, 1,0}, + .audiomux = { 0x4fa007,0xcfa007,0xcfa007,0xcfa007,0xcfa007,0xcfa007}, + .needs_tvaudio = 1, + .tuner_type = -1, + .audio_hook = winview_audio, + .has_radio = 1, +},{ + .name = "AVEC Intercapture", + .video_inputs = 3, + .audio_inputs = 2, + .tuner = 0, + .svhs = 2, + .gpiomask = 0, + .muxsel = {2, 3, 1, 1}, + .audiomux = {1, 0, 0, 0, 0}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "Lifeview FlyVideo II EZ /FlyKit LR38 Bt848 (capture only)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = -1, + .svhs = -1, + .gpiomask = 0x8dff00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0 }, + .no_msp34xx = 1, + .tuner_type = -1, +},{ + +/* ---- card 0x14 ---------------------------------- */ + .name = "CEI Raffles Card", + .video_inputs = 3, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .muxsel = {2, 3, 1, 1}, + .tuner_type = -1, +},{ + .name = "Lifeview FlyVideo 98/ Lucky Star Image World ConferenceTV LR50", + .video_inputs = 4, + .audio_inputs = 2, // tuner, line in + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x800, 0x1000, 0x1000, 0x1800}, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL_I, +},{ + .name = "Askey CPH050/ Phoebe Tv Master + FM", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xc00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = {0, 1, 0x800, 0x400, 0xc00, 0}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Modular Technology MM201/MM202/MM205/MM210/MM215 PCTV, bt878", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = -1, + .gpiomask = 7, + .muxsel = { 2, 3, -1 }, + .digital_mode = DIGITAL_MODE_CAMERA, + .audiomux = { 0, 0, 0, 0, 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ALPS_TSBB5_PAL_I, +},{ + +/* ---- card 0x18 ---------------------------------- */ + .name = "Askey CPH05X/06X (bt878) [many vendors]", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xe00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = {0x400, 0x400, 0x400, 0x400, 0xc00}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, + .has_remote = 1, +},{ + .name = "Terratec TerraTV+ Version 1.0 (Bt848)/ Terra TValue Version 1.0/ Vobis TV-Boostar", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1f0fff, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x20000, 0x30000, 0x10000, 0, 0x40000}, + .needs_tvaudio = 0, + .tuner_type = TUNER_PHILIPS_PAL, + .audio_hook = terratv_audio, +},{ + .name = "Hauppauge WinCam newer (bt878)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .gpiomask = 7, + .muxsel = { 2, 0, 1, 1}, + .audiomux = { 0, 1, 2, 3, 4}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "Lifeview FlyVideo 98/ MAXI TV Video PCI2 LR50", + .video_inputs = 4, + .audio_inputs = 2, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x800, 0x1000, 0x1000, 0x1800}, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_SECAM, +},{ + +/* ---- card 0x1c ---------------------------------- */ + .name = "Terratec TerraTV+ Version 1.1 (bt878)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1f0fff, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x20000, 0x30000, 0x10000, 0x00000, 0x40000}, + .needs_tvaudio = 0, + .tuner_type = TUNER_PHILIPS_PAL, + .audio_hook = terratv_audio, + /* GPIO wiring: + External 20 pin connector (for Active Radio Upgrade board) + gpio00: i2c-sda + gpio01: i2c-scl + gpio02: om5610-data + gpio03: om5610-clk + gpio04: om5610-wre + gpio05: om5610-stereo + gpio06: rds6588-davn + gpio07: Pin 7 n.c. + gpio08: nIOW + gpio09+10: nIOR, nSEL ?? (bt878) + gpio09: nIOR (bt848) + gpio10: nSEL (bt848) + Sound Routing: + gpio16: u2-A0 (1st 4052bt) + gpio17: u2-A1 + gpio18: u2-nEN + gpio19: u4-A0 (2nd 4052) + gpio20: u4-A1 + u4-nEN - GND + Btspy: + 00000 : Cdrom (internal audio input) + 10000 : ext. Video audio input + 20000 : TV Mono + a0000 : TV Mono/2 + 1a0000 : TV Stereo + 30000 : Radio + 40000 : Mute + */ + +},{ + /* Jannik Fritsch <jannik@techfak.uni-bielefeld.de> */ + .name = "Imagenation PXC200", + .video_inputs = 5, + .audio_inputs = 1, + .tuner = -1, + .svhs = 1, /* was: 4 */ + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0, 0}, + .audiomux = { 0 }, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "Lifeview FlyVideo 98 LR50", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, //0x8dfe00 + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x0800, 0x1000, 0x1000, 0x1800, 0 }, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Formac iProTV, Formac ProTV I (bt848)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .gpiomask = 1, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 1, 0, 0, 0, 0 }, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, +},{ + +/* ---- card 0x20 ---------------------------------- */ + .name = "Intel Create and Share PCI/ Smart Video Recorder III", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = 2, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .tuner_type = 4, +},{ + .name = "Terratec TerraTValue Version Bt878", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xffff00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x500, 0, 0x300, 0x900, 0x900}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, +},{ + .name = "Leadtek WinFast 2000/ WinFast 2000 XP", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xc33000, + .muxsel = { 2, 3, 1, 1, 0}, // TV, CVid, SVid, CVid over SVid connector + .audiomux = { 0x422000,0x1000,0x0000,0x620000,0x800000}, + /* Audio Routing for "WinFast 2000 XP" (no tv stereo !) + gpio23 -- hef4052:nEnable (0x800000) + gpio12 -- hef4052:A1 + gpio13 -- hef4052:A0 + 0x0000: external audio + 0x1000: FM + 0x2000: TV + 0x3000: n.c. + Note: There exists another variant "Winfast 2000" with tv stereo !? + Note: eeprom only contains FF and pci subsystem id 107d:6606 + */ + .needs_tvaudio = 0, + .pll = PLL_28, + .has_radio = 1, + .tuner_type = 5, // default for now, gpio reads BFFF06 for Pal bg+dk + .audio_hook = winfast2000_audio, + .has_remote = 1, +},{ + .name = "Lifeview FlyVideo 98 LR50 / Chronos Video Shuttle II", + .video_inputs = 4, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x800, 0x1000, 0x1000, 0x1800}, + .pll = PLL_28, + .tuner_type = -1, +},{ + +/* ---- card 0x24 ---------------------------------- */ + .name = "Lifeview FlyVideo 98FM LR50 / Typhoon TView TV/FM Tuner", + .video_inputs = 4, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x800, 0x1000, 0x1000, 0x1800, 0 }, + .pll = PLL_28, + .tuner_type = -1, + .has_radio = 1, +},{ + .name = "Prolink PixelView PlayTV pro", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xff, + .muxsel = { 2, 3, 1, 1 }, + .audiomux = { 0x21, 0x20, 0x24, 0x2c, 0x29, 0x29 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Askey CPH06X TView99", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x551e00, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 0x551400, 0x551200, 0, 0, 0x551c00, 0x551200 }, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = 1, + .has_remote = 1, +},{ + .name = "Pinnacle PCTV Studio/Rave", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x03000F, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 2, 0, 0, 0, 1}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + +/* ---- card 0x28 ---------------------------------- */ + .name = "STB TV PCI FM, Gateway P/N 6000704 (bt878), 3Dfx VoodooTV 100", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 4, 0, 2, 3, 1}, + .no_msp34xx = 1, + .needs_tvaudio = 1, + .tuner_type = TUNER_PHILIPS_NTSC, + .pll = PLL_28, + .has_radio = 1, +},{ + .name = "AVerMedia TVPhone 98", + .video_inputs = 3, + .audio_inputs = 4, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 13, 4, 11, 7, 0, 0}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, + .has_radio = 1, + .audio_hook = avermedia_tvphone_audio, +},{ + .name = "ProVideo PV951", /* pic16c54 */ + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0, 0, 0, 0}, + .needs_tvaudio = 1, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = 1, +},{ + .name = "Little OnAir TV", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xe00b, + .muxsel = {2, 3, 1, 1}, + .audiomux = {0xff9ff6, 0xff9ff6, 0xff1ff7, 0, 0xff3ffc}, + .no_msp34xx = 1, + .tuner_type = -1, +},{ + +/* ---- card 0x2c ---------------------------------- */ + .name = "Sigma TVII-FM", + .video_inputs = 2, + .audio_inputs = 1, + .tuner = 0, + .svhs = -1, + .gpiomask = 3, + .muxsel = {2, 3, 1, 1}, + .audiomux = {1, 1, 0, 2, 3}, + .no_msp34xx = 1, + .pll = PLL_NONE, + .tuner_type = -1, +},{ + .name = "MATRIX-Vision MV-Delta 2", + .video_inputs = 5, + .audio_inputs = 1, + .tuner = -1, + .svhs = 3, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0, 0}, + .audiomux = {0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Zoltrix Genie TV/FM", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xbcf03f, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0xbc803f, 0xbc903f, 0xbcb03f, 0, 0xbcb03f}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = 21, +},{ + .name = "Terratec TV/Radio+", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x70000, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x20000, 0x30000, 0x10000, 0, 0x40000, 0x20000 }, + .needs_tvaudio = 1, + .no_msp34xx = 1, + .pll = PLL_35, + .tuner_type = 1, + .has_radio = 1, +},{ + +/* ---- card 0x30 ---------------------------------- */ + .name = "Askey CPH03x/ Dynalink Magic TView", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = {2,0,0,0,1}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "IODATA GV-BCTV3/PCI", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x010f00, + .muxsel = {2, 3, 0, 0}, + .audiomux = {0x10000, 0, 0x10000, 0, 0, 0}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ALPS_TSHC6_NTSC, + .audio_hook = gvbctv3pci_audio, +},{ + .name = "Prolink PV-BT878P+4E / PixelView PlayTV PAK / Lenco MXTV-9578 CP", + .video_inputs = 5, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .gpiomask = 0xAA0000, + .muxsel = { 2,3,1,1,-1 }, + .digital_mode = DIGITAL_MODE_CAMERA, + .audiomux = { 0x20000, 0, 0x80000, 0x80000, 0xa8000, 0x46000 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL_I, + .has_remote = 1, + /* GPIO wiring: (different from Rev.4C !) + GPIO17: U4.A0 (first hef4052bt) + GPIO19: U4.A1 + GPIO20: U5.A1 (second hef4052bt) + GPIO21: U4.nEN + GPIO22: BT832 Reset Line + GPIO23: A5,A0, U5,nEN + Note: At i2c=0x8a is a Bt832 chip, which changes to 0x88 after being reset via GPIO22 + */ +},{ + .name = "Eagle Wireless Capricorn2 (bt878A)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 0, 1, 1}, + .audiomux = { 0, 1, 2, 3, 4}, + .pll = PLL_28, + .tuner_type = -1 /* TUNER_ALPS_TMDH2_NTSC */, +},{ + +/* ---- card 0x34 ---------------------------------- */ + /* David Härdeman <david@2gen.com> */ + .name = "Pinnacle PCTV Studio Pro", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .gpiomask = 0x03000F, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 1, 0xd0001, 0, 0, 10}, + /* sound path (5 sources): + MUX1 (mask 0x03), Enable Pin 0x08 (0=enable, 1=disable) + 0= ext. Audio IN + 1= from MUX2 + 2= Mono TV sound from Tuner + 3= not connected + MUX2 (mask 0x30000): + 0,2,3= from MSP34xx + 1= FM stereo Radio from Tuner */ + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + /* Claas Langbehn <claas@bigfoot.com>, + Sven Grothklags <sven@upb.de> */ + .name = "Typhoon TView RDS + FM Stereo / KNC1 TV Station RDS", + .video_inputs = 4, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1c, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0, 0x10, 8, 4 }, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .has_radio = 1, +},{ + /* Tim Röstermundt <rosterm@uni-muenster.de> + in de.comp.os.unix.linux.hardware: + options bttv card=0 pll=1 radio=1 gpiomask=0x18e0 + audiomux=0x44c71f,0x44d71f,0,0x44d71f,0x44dfff + options tuner type=5 */ + .name = "Lifeview FlyVideo 2000 /FlyVideo A2/ Lifetec LT 9415 TV [LR90]", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x18e0, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x0000,0x0800,0x1000,0x1000,0x18e0 }, + /* For cards with tda9820/tda9821: + 0x0000: Tuner normal stereo + 0x0080: Tuner A2 SAP (second audio program = Zweikanalton) + 0x0880: Tuner A2 stereo */ + .pll = PLL_28, + .tuner_type = -1, +},{ + /* Miguel Angel Alvarez <maacruz@navegalia.com> + old Easy TV BT848 version (model CPH031) */ + .name = "Askey CPH031/ BESTBUY Easy TV", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xF, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 2, 0, 0, 0, 10}, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_PAL, +},{ + +/* ---- card 0x38 ---------------------------------- */ + /* Gordon Heydon <gjheydon@bigfoot.com ('98) */ + .name = "Lifeview FlyVideo 98FM LR50", + .video_inputs = 4, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x800, 0x1000, 0x1000, 0x1800, 0 }, + .pll = PLL_28, + .tuner_type = 5, +},{ + /* This is the ultimate cheapo capture card + * just a BT848A on a small PCB! + * Steve Hosgood <steve@equiinet.com> */ + .name = "GrandTec 'Grand Video Capture' (Bt848)", + .video_inputs = 2, + .audio_inputs = 0, + .tuner = -1, + .svhs = 1, + .gpiomask = 0, + .muxsel = { 3, 1 }, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_35, + .tuner_type = -1, +},{ + /* Daniel Herrington <daniel.herrington@home.com> */ + .name = "Askey CPH060/ Phoebe TV Master Only (No FM)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xe00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x400, 0x400, 0x400, 0x400, 0x800, 0x400 }, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_4036FY5_NTSC, +},{ + /* Matti Mottus <mottus@physic.ut.ee> */ + .name = "Askey CPH03x TV Capturer", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x03000F, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 2,0,0,0,1 }, + .pll = PLL_28, + .tuner_type = 0, +},{ + +/* ---- card 0x3c ---------------------------------- */ + /* Philip Blundell <philb@gnu.org> */ + .name = "Modular Technology MM100PCTV", + .video_inputs = 2, + .audio_inputs = 2, + .tuner = 0, + .svhs = -1, + .gpiomask = 11, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 2, 0, 0, 1, 8}, + .pll = PLL_35, + .tuner_type = TUNER_TEMIC_PAL, + +},{ + /* Adrian Cox <adrian@humboldt.co.uk */ + .name = "AG Electronics GMV1", + .video_inputs = 2, + .audio_inputs = 0, + .tuner = -1, + .svhs = 1, + .gpiomask = 0xF, + .muxsel = { 2, 2}, + .audiomux = { }, + .no_msp34xx = 1, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = -1, +},{ + /* Miguel Angel Alvarez <maacruz@navegalia.com> + new Easy TV BT878 version (model CPH061) + special thanks to Informatica Mieres for providing the card */ + .name = "Askey CPH061/ BESTBUY Easy TV (bt878)", + .video_inputs = 3, + .audio_inputs = 2, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xFF, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 1, 0, 4, 4, 9}, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, +},{ + /* Lukas Gebauer <geby@volny.cz> */ + .name = "ATI TV-Wonder", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xf03f, + .muxsel = { 2, 3, 1, 0 }, + .audiomux = { 0xbffe, 0, 0xbfff, 0, 0xbffe}, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_4006FN5_MULTI_PAL, +},{ + +/* ---- card 0x40 ---------------------------------- */ + /* Lukas Gebauer <geby@volny.cz> */ + .name = "ATI TV-Wonder VE", + .video_inputs = 2, + .audio_inputs = 1, + .tuner = 0, + .svhs = -1, + .gpiomask = 1, + .muxsel = { 2, 3, 0, 1}, + .audiomux = { 0, 0, 1, 0, 0}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_4006FN5_MULTI_PAL, +},{ + /* DeeJay <deejay@westel900.net (2000S) */ + .name = "Lifeview FlyVideo 2000S LR90", + .video_inputs = 3, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x18e0, + .muxsel = { 2, 3, 0, 1}, + /* Radio changed from 1e80 to 0x800 to make + FlyVideo2000S in .hu happy (gm)*/ + /* -dk-???: set mute=0x1800 for tda9874h daughterboard */ + .audiomux = { 0x0000,0x0800,0x1000,0x1000,0x1800, 0x1080 }, + .audio_hook = fv2000s_audio, + .no_msp34xx = 1, + .no_tda9875 = 1, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = 5, +},{ + .name = "Terratec TValueRadio", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xffff00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x500, 0x500, 0x300, 0x900, 0x900}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .has_radio = 1, +},{ + /* TANAKA Kei <peg00625@nifty.com> */ + .name = "IODATA GV-BCTV4/PCI", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x010f00, + .muxsel = {2, 3, 0, 0}, + .audiomux = {0x10000, 0, 0x10000, 0, 0, 0}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_SHARP_2U5JF5540_NTSC, + .audio_hook = gvbctv3pci_audio, +},{ + +/* ---- card 0x44 ---------------------------------- */ + .name = "3Dfx VoodooTV FM (Euro), VoodooTV 200 (USA)", + // try "insmod msp3400 simple=0" if you have + // sound problems with this card. + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = -1, + .gpiomask = 0x4f8a00, + // 0x100000: 1=MSP enabled (0=disable again) + // 0x010000: Connected to "S0" on tda9880 (0=Pal/BG, 1=NTSC) + .audiomux = {0x947fff, 0x987fff,0x947fff,0x947fff, 0x947fff}, + // tvtuner, radio, external,internal, mute, stereo + /* tuner, Composit, SVid, Composit-on-Svid-adapter*/ + .muxsel = { 2, 3 ,0 ,1}, + .tuner_type = TUNER_MT2032, + .pll = PLL_28, + .has_radio = 1, +},{ + /* Philip Blundell <pb@nexus.co.uk> */ + .name = "Active Imaging AIMMS", + .video_inputs = 1, + .audio_inputs = 0, + .tuner = -1, + .tuner_type = -1, + .pll = PLL_28, + .muxsel = { 2 }, + .gpiomask = 0 +},{ + /* Tomasz Pyra <hellfire@sedez.iq.pl> */ + .name = "Prolink Pixelview PV-BT878P+ (Rev.4C,8E)", + .video_inputs = 3, + .audio_inputs = 4, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0, 11, 7, 13, 0}, // TV and Radio with same GPIO ! + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = 25, + .has_remote = 1, + /* GPIO wiring: + GPIO0: U4.A0 (hef4052bt) + GPIO1: U4.A1 + GPIO2: U4.A1 (second hef4052bt) + GPIO3: U4.nEN, U5.A0, A5.nEN + GPIO8-15: vrd866b ? + */ +},{ + .name = "Lifeview FlyVideo 98EZ (capture only) LR51", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = 2, + .muxsel = { 2, 3, 1, 1}, // AV1, AV2, SVHS, CVid adapter on SVHS + .pll = PLL_28, + .no_msp34xx = 1, +},{ + +/* ---- card 0x48 ---------------------------------- */ + /* Dariusz Kowalewski <darekk@automex.pl> */ + .name = "Prolink Pixelview PV-BT878P+9B (PlayTV Pro rev.9B FM+NICAM)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x3f, + .muxsel = { 2, 3, 1, 1 }, + .audiomux = { 0x01, 0x00, 0x03, 0x03, 0x09, 0x02 }, + .needs_tvaudio = 1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .pll = PLL_28, + .tuner_type = 5, + .audio_hook = pvbt878p9b_audio, // Note: not all cards have stereo + .has_radio = 1, // Note: not all cards have radio + .has_remote = 1, + /* GPIO wiring: + GPIO0: A0 hef4052 + GPIO1: A1 hef4052 + GPIO3: nEN hef4052 + GPIO8-15: vrd866b + GPIO20,22,23: R30,R29,R28 + */ +},{ + /* Clay Kunz <ckunz@mail.arc.nasa.gov> */ + /* you must jumper JP5 for the card to work */ + .name = "Sensoray 311", + .video_inputs = 5, + .audio_inputs = 0, + .tuner = -1, + .svhs = 4, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0, 0}, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .tuner_type = -1, +},{ + /* Miguel Freitas <miguel@cetuc.puc-rio.br> */ + .name = "RemoteVision MX (RV605)", + .video_inputs = 16, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .gpiomask = 0x00, + .gpiomask2 = 0x07ff, + .muxsel = { 0x33, 0x13, 0x23, 0x43, 0xf3, 0x73, 0xe3, 0x03, + 0xd3, 0xb3, 0xc3, 0x63, 0x93, 0x53, 0x83, 0xa3 }, + .no_msp34xx = 1, + .no_tda9875 = 1, + .tuner_type = -1, + .muxsel_hook = rv605_muxsel, +},{ + .name = "Powercolor MTV878/ MTV878R/ MTV878F", + .video_inputs = 3, + .audio_inputs = 2, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1C800F, // Bit0-2: Audio select, 8-12:remote control 14:remote valid 15:remote reset + .muxsel = { 2, 1, 1, }, + .audiomux = { 0, 1, 2, 2, 4 }, + .needs_tvaudio = 0, + .tuner_type = TUNER_PHILIPS_PAL, + .pll = PLL_28, + .has_radio = 1, +},{ + +/* ---- card 0x4c ---------------------------------- */ + /* Masaki Suzuki <masaki@btree.org> */ + .name = "Canopus WinDVR PCI (COMPAQ Presario 3524JP, 5112JP)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x140007, + .muxsel = { 2, 3, 1, 1 }, + .audiomux = { 0, 1, 2, 3, 4, 0 }, + .tuner_type = TUNER_PHILIPS_NTSC, + .audio_hook = windvr_audio, +},{ + .name = "GrandTec Multi Capture Card (Bt878)", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0 }, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Jetway TV/Capture JW-TV878-FBK, Kworld KW-TV878RF", + .video_inputs = 4, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1 }, // Tuner, SVid, SVHS, SVid to SVHS connector + .audiomux = { 0 ,0 ,4, 4,4,4},// Yes, this tuner uses the same audio output for TV and FM radio! + // This card lacks external Audio In, so we mute it on Ext. & Int. + // The PCB can take a sbx1637/sbx1673, wiring unknown. + // This card lacks PCI subsystem ID, sigh. + // audiomux=1: lower volume, 2+3: mute + // btwincap uses 0x80000/0x80003 + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = 5, // Samsung TCPA9095PC27A (BG+DK), philips compatible, w/FM, stereo and + // radio signal strength indicators work fine. + .has_radio = 1, + /* GPIO Info: + GPIO0,1: HEF4052 A0,A1 + GPIO2: HEF4052 nENABLE + GPIO3-7: n.c. + GPIO8-13: IRDC357 data0-5 (data6 n.c. ?) [chip not present on my card] + GPIO14,15: ?? + GPIO16-21: n.c. + GPIO22,23: ?? + ?? : mtu8b56ep microcontroller for IR (GPIO wiring unknown)*/ +},{ + /* Arthur Tetzlaff-Deas, DSP Design Ltd <software@dspdesign.com> */ + .name = "DSP Design TCVIDEO", + .video_inputs = 4, + .svhs = -1, + .muxsel = { 2, 3, 1, 0}, + .pll = PLL_28, + .tuner_type = -1, +},{ + + /* ---- card 0x50 ---------------------------------- */ + .name = "Hauppauge WinTV PVR", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .muxsel = { 2, 0, 1, 1}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, + + .gpiomask = 7, + .audiomux = {7}, +},{ + .name = "IODATA GV-BCTV5/PCI", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x0f0f80, + .muxsel = {2, 3, 1, 0}, + .audiomux = {0x030000, 0x010000, 0, 0, 0x020000, 0}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .audio_hook = gvbctv5pci_audio, + .has_radio = 1, +},{ + .name = "Osprey 100/150 (878)", /* 0x1(2|3)-45C6-C1 */ + .video_inputs = 4, /* id-inputs-clock */ + .audio_inputs = 0, + .tuner = -1, + .svhs = 3, + .muxsel = { 3, 2, 0, 1 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 100/150 (848)", /* 0x04-54C0-C1 & older boards */ + .video_inputs = 3, + .audio_inputs = 0, + .tuner = -1, + .svhs = 2, + .muxsel = { 2, 3, 1 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + + /* ---- card 0x54 ---------------------------------- */ + .name = "Osprey 101 (848)", /* 0x05-40C0-C1 */ + .video_inputs = 2, + .audio_inputs = 0, + .tuner = -1, + .svhs = 1, + .muxsel = { 3, 1 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 101/151", /* 0x1(4|5)-0004-C4 */ + .video_inputs = 1, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .muxsel = { 0 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 101/151 w/ svid", /* 0x(16|17|20)-00C4-C1 */ + .video_inputs = 2, + .audio_inputs = 0, + .tuner = -1, + .svhs = 1, + .muxsel = { 0, 1 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 200/201/250/251", /* 0x1(8|9|E|F)-0004-C4 */ + .video_inputs = 1, + .audio_inputs = 1, + .tuner = -1, + .svhs = -1, + .muxsel = { 0 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + + /* ---- card 0x58 ---------------------------------- */ + .name = "Osprey 200/250", /* 0x1(A|B)-00C4-C1 */ + .video_inputs = 2, + .audio_inputs = 1, + .tuner = -1, + .svhs = 1, + .muxsel = { 0, 1 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 210/220", /* 0x1(A|B)-04C0-C1 */ + .video_inputs = 2, + .audio_inputs = 1, + .tuner = -1, + .svhs = 1, + .muxsel = { 2, 3 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 500", /* 500 */ + .video_inputs = 2, + .audio_inputs = 1, + .tuner = -1, + .svhs = 1, + .muxsel = { 2, 3 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 540", /* 540 */ + .video_inputs = 4, + .audio_inputs = 1, + .tuner = -1, +#if 0 /* TODO ... */ + .svhs = OSPREY540_SVID_ANALOG, + .muxsel = { [OSPREY540_COMP_ANALOG] = 2, + [OSPREY540_SVID_ANALOG] = 3, }, +#endif + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +#if 0 /* TODO ... */ + .muxsel_hook = osprey_540_muxsel, + .picture_hook = osprey_540_set_picture, +#endif +},{ + + /* ---- card 0x5C ---------------------------------- */ + .name = "Osprey 2000", /* 2000 */ + .video_inputs = 2, + .audio_inputs = 1, + .tuner = -1, + .svhs = 1, + .muxsel = { 2, 3 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, /* must avoid, conflicts with the bt860 */ +},{ + /* M G Berberich <berberic@forwiss.uni-passau.de> */ + .name = "IDS Eagle", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .tuner_type = -1, + .svhs = -1, + .gpiomask = 0, + .muxsel = { 0, 1, 2, 3 }, + .muxsel_hook = eagle_muxsel, + .no_msp34xx = 1, + .no_tda9875 = 1, + .pll = PLL_28, +},{ + .name = "Pinnacle PCTV Sat", + .video_inputs = 2, + .audio_inputs = 0, + .svhs = 1, + .tuner = -1, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .gpiomask = 0x01, + .audiomux = { 0, 0, 0, 0, 1 }, + .muxsel = { 3, 0, 1, 2}, + .needs_tvaudio = 0, + .pll = PLL_28, +},{ + .name = "Formac ProTV II (bt878)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .gpiomask = 2, + // TV, Comp1, Composite over SVID con, SVID + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 2, 2, 0, 0, 0 }, + .pll = PLL_28, + .has_radio = 1, + .tuner_type = TUNER_PHILIPS_PAL, + /* sound routing: + GPIO=0x00,0x01,0x03: mute (?) + 0x02: both TV and radio (tuner: FM1216/I) + The card has onboard audio connectors labeled "cdrom" and "board", + not soldered here, though unknown wiring. + Card lacks: external audio in, pci subsystem id. + */ +},{ + + /* ---- card 0x60 ---------------------------------- */ + .name = "MachTV", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = -1, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 1, 2, 3, 4}, + .needs_tvaudio = 1, + .tuner_type = 5, + .pll = 1, +},{ + .name = "Euresys Picolo", + .video_inputs = 3, + .audio_inputs = 0, + .tuner = -1, + .svhs = 2, + .gpiomask = 0, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .muxsel = { 2, 0, 1}, + .pll = PLL_28, +},{ + /* Luc Van Hoeylandt <luc@e-magic.be> */ + .name = "ProVideo PV150", /* 0x4f */ + .video_inputs = 2, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .gpiomask = 0, + .muxsel = { 2, 3 }, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + /* Hiroshi Takekawa <sian@big.or.jp> */ + /* This card lacks subsystem ID */ + .name = "AD-TVK503", /* 0x63 */ + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x001e8007, + .muxsel = { 2, 3, 1, 0 }, + /* Tuner, Radio, external, internal, off, on */ + .audiomux = { 0x08, 0x0f, 0x0a, 0x08, 0x0f, 0x08 }, + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = 2, + .audio_hook = adtvk503_audio, +},{ + + /* ---- card 0x64 ---------------------------------- */ + .name = "Hercules Smart TV Stereo", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x00, + .muxsel = { 2, 3, 1, 1 }, + .needs_tvaudio = 1, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = 5, + /* Notes: + - card lacks subsystem ID + - stereo variant w/ daughter board with tda9874a @0xb0 + - Audio Routing: + always from tda9874 independent of GPIO (?) + external line in: unknown + - Other chips: em78p156elp @ 0x96 (probably IR remote control) + hef4053 (instead 4052) for unknown function + */ +},{ + .name = "Pace TV & Radio Card", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .muxsel = { 2, 3, 1, 1}, // Tuner, CVid, SVid, CVid over SVid connector + .gpiomask = 0, + .no_tda9875 = 1, + .no_tda7432 = 1, + .tuner_type = 1, + .has_radio = 1, + .pll = PLL_28, + /* Bt878, Bt832, FI1246 tuner; no pci subsystem id + only internal line out: (4pin header) RGGL + Radio must be decoded by msp3410d (not routed through)*/ + // .digital_mode = DIGITAL_MODE_CAMERA, // todo! +},{ + /* Chris Willing <chris@vislab.usyd.edu.au> */ + .name = "IVC-200", + .video_inputs = 1, + .audio_inputs = 0, + .tuner = -1, + .tuner_type = -1, + .svhs = -1, + .gpiomask = 0xdf, + .muxsel = { 2 }, + .pll = PLL_28, +},{ + .name = "Grand X-Guard / Trust 814PCI", + .video_inputs = 16, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .tuner_type = 4, + .gpiomask2 = 0xff, + .muxsel = { 2,2,2,2, 3,3,3,3, 1,1,1,1, 0,0,0,0 }, + .muxsel_hook = xguard_muxsel, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .pll = PLL_28, +},{ + + /* ---- card 0x68 ---------------------------------- */ + .name = "Nebula Electronics DigiTV", + .video_inputs = 1, + .tuner = -1, + .svhs = -1, + .muxsel = { 2, 3, 1, 0}, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .pll = PLL_28, + .tuner_type = -1, + .has_dvb = 1, + .no_gpioirq = 1, +},{ + /* Jorge Boncompte - DTI2 <jorge@dti2.net> */ + .name = "ProVideo PV143", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0 }, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + /* M.Klahr@phytec.de */ + .name = "PHYTEC VD-009-X1 MiniDIN (bt878)", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, /* card has no tuner */ + .svhs = 3, + .gpiomask = 0x00, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 0, 0, 0, 0, 0, 0 }, /* card has no audio */ + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "PHYTEC VD-009-X1 Combi (bt878)", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, /* card has no tuner */ + .svhs = 3, + .gpiomask = 0x00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0, 0, 0, 0, 0 }, /* card has no audio */ + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + + /* ---- card 0x6c ---------------------------------- */ + .name = "PHYTEC VD-009 MiniDIN (bt878)", + .video_inputs = 10, + .audio_inputs = 0, + .tuner = -1, /* card has no tuner */ + .svhs = 9, + .gpiomask = 0x00, + .gpiomask2 = 0x03, /* gpiomask2 defines the bits used to switch audio + via the upper nibble of muxsel. here: used for + xternal video-mux */ + .muxsel = { 0x02, 0x12, 0x22, 0x32, 0x03, 0x13, 0x23, 0x33, 0x01, 0x00 }, + .audiomux = { 0, 0, 0, 0, 0, 0 }, /* card has no audio */ + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "PHYTEC VD-009 Combi (bt878)", + .video_inputs = 10, + .audio_inputs = 0, + .tuner = -1, /* card has no tuner */ + .svhs = 9, + .gpiomask = 0x00, + .gpiomask2 = 0x03, /* gpiomask2 defines the bits used to switch audio + via the upper nibble of muxsel. here: used for + xternal video-mux */ + .muxsel = { 0x02, 0x12, 0x22, 0x32, 0x03, 0x13, 0x23, 0x33, 0x01, 0x01 }, + .audiomux = { 0, 0, 0, 0, 0, 0 }, /* card has no audio */ + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "IVC-100", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .tuner_type = -1, + .svhs = -1, + .gpiomask = 0xdf, + .muxsel = { 2, 3, 1, 0 }, + .pll = PLL_28, +},{ + /* IVC-120G - Alan Garfield <alan@fromorbit.com> */ + .name = "IVC-120G", + .video_inputs = 16, + .audio_inputs = 0, /* card has no audio */ + .tuner = -1, /* card has no tuner */ + .tuner_type = -1, + .svhs = -1, /* card has no svhs */ + .needs_tvaudio = 0, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .gpiomask = 0x00, + .muxsel = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 }, + .muxsel_hook = ivc120_muxsel, + .pll = PLL_28, +},{ + + /* ---- card 0x70 ---------------------------------- */ + .name = "pcHDTV HD-2000 TV", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .muxsel = { 2, 3, 1, 0}, + .tuner_type = TUNER_PHILIPS_ATSC, +},{ + .name = "Twinhan DST + clones", + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .tuner_type = TUNER_ABSENT, + .no_video = 1, + .has_dvb = 1, +},{ + .name = "Winfast VC100", + .video_inputs = 3, + .audio_inputs = 0, + .svhs = 1, + .tuner = -1, // no tuner + .muxsel = { 3, 1, 1, 3}, // Vid In, SVid In, Vid over SVid in connector + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .tuner_type = TUNER_ABSENT, + .no_video = 1, + .pll = PLL_28, +},{ + .name = "Teppro TEV-560/InterVision IV-560", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 3, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 1, 1, 1, 1, 0}, + .needs_tvaudio = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .pll = PLL_35, +},{ + + /* ---- card 0x74 ---------------------------------- */ + .name = "SIMUS GVC1100", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .tuner_type = -1, + .pll = PLL_28, + .muxsel = { 2, 2, 2, 2}, + .gpiomask = 0x3F, + .muxsel_hook = gvc1100_muxsel, +},{ + /* Carlos Silva r3pek@r3pek.homelinux.org || card 0x75 */ + .name = "NGS NGSTV+", + .video_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x008007, + .muxsel = {2, 3, 0, 0}, + .audiomux = {0, 0, 0, 0, 0x000003, 0}, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .has_remote = 1, +},{ + /* http://linuxmedialabs.com */ + .name = "LMLBT4", + .video_inputs = 4, /* IN1,IN2,IN3,IN4 */ + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .muxsel = { 2, 3, 1, 0 }, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .needs_tvaudio = 0, +},{ + /* Helmroos Harri <harri.helmroos@pp.inet.fi> */ + .name = "Tekram M205 PRO", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .tuner_type = TUNER_PHILIPS_PAL, + .svhs = 2, + .needs_tvaudio = 0, + .gpiomask = 0x68, + .muxsel = { 2, 3, 1}, + .audiomux = { 0x68, 0x68, 0x61, 0x61, 0x00 }, + .pll = PLL_28, +},{ + + /* ---- card 0x78 ---------------------------------- */ + /* Javier Cendan Ares <jcendan@lycos.es> */ + /* bt878 TV + FM without subsystem ID */ + .name = "Conceptronic CONTVFMi", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x008007, + .muxsel = { 2, 3, 1, 1 }, + .audiomux = { 0, 1, 2, 2, 3 }, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .has_remote = 1, + .has_radio = 1, +}}; + +const unsigned int bttv_num_tvcards = ARRAY_SIZE(bttv_tvcards); + +/* ----------------------------------------------------------------------- */ + +static unsigned char eeprom_data[256]; + +/* + * identify card + */ +void __devinit bttv_idcard(struct bttv *btv) +{ + unsigned int gpiobits; + int i,type; + unsigned short tmp; + + /* read PCI subsystem ID */ + pci_read_config_word(btv->c.pci, PCI_SUBSYSTEM_ID, &tmp); + btv->cardid = tmp << 16; + pci_read_config_word(btv->c.pci, PCI_SUBSYSTEM_VENDOR_ID, &tmp); + btv->cardid |= tmp; + + if (0 != btv->cardid && 0xffffffff != btv->cardid) { + /* look for the card */ + for (type = -1, i = 0; cards[i].id != 0; i++) + if (cards[i].id == btv->cardid) + type = i; + + if (type != -1) { + /* found it */ + printk(KERN_INFO "bttv%d: detected: %s [card=%d], " + "PCI subsystem ID is %04x:%04x\n", + btv->c.nr,cards[type].name,cards[type].cardnr, + btv->cardid & 0xffff, + (btv->cardid >> 16) & 0xffff); + btv->c.type = cards[type].cardnr; + } else { + /* 404 */ + printk(KERN_INFO "bttv%d: subsystem: %04x:%04x (UNKNOWN)\n", + btv->c.nr, btv->cardid & 0xffff, + (btv->cardid >> 16) & 0xffff); + printk(KERN_DEBUG "please mail id, board name and " + "the correct card= insmod option to kraxel@bytesex.org\n"); + } + } + + /* let the user override the autodetected type */ + if (card[btv->c.nr] < bttv_num_tvcards) + btv->c.type=card[btv->c.nr]; + + /* print which card config we are using */ + printk(KERN_INFO "bttv%d: using: %s [card=%d,%s]\n",btv->c.nr, + bttv_tvcards[btv->c.type].name, btv->c.type, + card[btv->c.nr] < bttv_num_tvcards + ? "insmod option" : "autodetected"); + + /* overwrite gpio stuff ?? */ + if (UNSET == audioall && UNSET == audiomux[0]) + return; + + if (UNSET != audiomux[0]) { + gpiobits = 0; + for (i = 0; i < 5; i++) { + bttv_tvcards[btv->c.type].audiomux[i] = audiomux[i]; + gpiobits |= audiomux[i]; + } + } else { + gpiobits = audioall; + for (i = 0; i < 5; i++) { + bttv_tvcards[btv->c.type].audiomux[i] = audioall; + } + } + bttv_tvcards[btv->c.type].gpiomask = (UNSET != gpiomask) ? gpiomask : gpiobits; + printk(KERN_INFO "bttv%d: gpio config override: mask=0x%x, mux=", + btv->c.nr,bttv_tvcards[btv->c.type].gpiomask); + for (i = 0; i < 5; i++) { + printk("%s0x%x", i ? "," : "", bttv_tvcards[btv->c.type].audiomux[i]); + } + printk("\n"); +} + +/* + * (most) board specific initialisations goes here + */ + +/* Some Modular Technology cards have an eeprom, but no subsystem ID */ +void identify_by_eeprom(struct bttv *btv, unsigned char eeprom_data[256]) +{ + int type = -1; + + if (0 == strncmp(eeprom_data,"GET MM20xPCTV",13)) + type = BTTV_MODTEC_205; + else if (0 == strncmp(eeprom_data+20,"Picolo",7)) + type = BTTV_EURESYS_PICOLO; + else if (eeprom_data[0] == 0x84 && eeprom_data[2]== 0) + type = BTTV_HAUPPAUGE; /* old bt848 */ + + if (-1 != type) { + btv->c.type = type; + printk("bttv%d: detected by eeprom: %s [card=%d]\n", + btv->c.nr, bttv_tvcards[btv->c.type].name, btv->c.type); + } +} + +static void flyvideo_gpio(struct bttv *btv) +{ + int gpio,has_remote,has_radio,is_capture_only,is_lr90,has_tda9820_tda9821; + int tuner=-1,ttype; + + gpio_inout(0xffffff, 0); + udelay(8); // without this we would see the 0x1800 mask + gpio = gpio_read(); + /* FIXME: must restore OUR_EN ??? */ + + // all cards provide GPIO info, some have an additional eeprom + // LR50: GPIO coding can be found lower right CP1 .. CP9 + // CP9=GPIO23 .. CP1=GPIO15; when OPEN, the corresponding GPIO reads 1. + // GPIO14-12: n.c. + // LR90: GP9=GPIO23 .. GP1=GPIO15 (right above the bt878) + + // lowest 3 bytes are remote control codes (no handshake needed) + // xxxFFF: No remote control chip soldered + // xxxF00(LR26/LR50), xxxFE0(LR90): Remote control chip (LVA001 or CF45) soldered + // Note: Some bits are Audio_Mask ! + + ttype=(gpio&0x0f0000)>>16; + switch(ttype) { + case 0x0: tuner=2; // NTSC, e.g. TPI8NSR11P + break; + case 0x2: tuner=39;// LG NTSC (newer TAPC series) TAPC-H701P + break; + case 0x4: tuner=5; // Philips PAL TPI8PSB02P, TPI8PSB12P, TPI8PSB12D or FI1216, FM1216 + break; + case 0x6: tuner=37; // LG PAL (newer TAPC series) TAPC-G702P + break; + case 0xC: tuner=3; // Philips SECAM(+PAL) FQ1216ME or FI1216MF + break; + default: + printk(KERN_INFO "bttv%d: FlyVideo_gpio: unknown tuner type.\n", btv->c.nr); + } + + has_remote = gpio & 0x800000; + has_radio = gpio & 0x400000; + // unknown 0x200000; + // unknown2 0x100000; + is_capture_only = !(gpio & 0x008000); //GPIO15 + has_tda9820_tda9821 = !(gpio & 0x004000); + is_lr90 = !(gpio & 0x002000); // else LR26/LR50 (LR38/LR51 f. capture only) + // gpio & 0x001000 // output bit for audio routing + + if(is_capture_only) + tuner=4; // No tuner present + + printk(KERN_INFO "bttv%d: FlyVideo Radio=%s RemoteControl=%s Tuner=%d gpio=0x%06x\n", + btv->c.nr, has_radio? "yes":"no ", has_remote? "yes":"no ", tuner, gpio); + printk(KERN_INFO "bttv%d: FlyVideo LR90=%s tda9821/tda9820=%s capture_only=%s\n", + btv->c.nr, is_lr90?"yes":"no ", has_tda9820_tda9821?"yes":"no ", + is_capture_only?"yes":"no "); + + if(tuner!= -1) // only set if known tuner autodetected, else let insmod option through + btv->tuner_type = tuner; + btv->has_radio = has_radio; + + // LR90 Audio Routing is done by 2 hef4052, so Audio_Mask has 4 bits: 0x001c80 + // LR26/LR50 only has 1 hef4052, Audio_Mask 0x000c00 + // Audio options: from tuner, from tda9821/tda9821(mono,stereo,sap), from tda9874, ext., mute + if(has_tda9820_tda9821) btv->audio_hook = lt9415_audio; + //todo: if(has_tda9874) btv->audio_hook = fv2000s_audio; +} + +int miro_tunermap[] = { 0,6,2,3, 4,5,6,0, 3,0,4,5, 5,2,16,1, + 14,2,17,1, 4,1,4,3, 1,2,16,1, 4,4,4,4 }; +int miro_fmtuner[] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, + 1,1,1,1, 1,1,1,0, 0,0,0,0, 0,1,0,0 }; + +static void miro_pinnacle_gpio(struct bttv *btv) +{ + int id,msp,gpio; + char *info; + + gpio_inout(0xffffff, 0); + gpio = gpio_read(); + id = ((gpio>>10) & 63) -1; + msp = bttv_I2CRead(btv, I2C_MSP3400, "MSP34xx"); + if (id < 32) { + btv->tuner_type = miro_tunermap[id]; + if (0 == (gpio & 0x20)) { + btv->has_radio = 1; + if (!miro_fmtuner[id]) { + btv->has_matchbox = 1; + btv->mbox_we = (1<<6); + btv->mbox_most = (1<<7); + btv->mbox_clk = (1<<8); + btv->mbox_data = (1<<9); + btv->mbox_mask = (1<<6)|(1<<7)|(1<<8)|(1<<9); + } + } else { + btv->has_radio = 0; + } + if (-1 != msp) { + if (btv->c.type == BTTV_MIRO) + btv->c.type = BTTV_MIROPRO; + if (btv->c.type == BTTV_PINNACLE) + btv->c.type = BTTV_PINNACLEPRO; + } + printk(KERN_INFO + "bttv%d: miro: id=%d tuner=%d radio=%s stereo=%s\n", + btv->c.nr, id+1, btv->tuner_type, + !btv->has_radio ? "no" : + (btv->has_matchbox ? "matchbox" : "fmtuner"), + (-1 == msp) ? "no" : "yes"); + } else { + /* new cards with microtune tuner */ + id = 63 - id; + btv->has_radio = 0; + switch (id) { + case 1: + info = "PAL / mono"; + break; + case 2: + info = "PAL+SECAM / stereo"; + btv->has_radio = 1; + break; + case 3: + info = "NTSC / stereo"; + btv->has_radio = 1; + break; + case 4: + info = "PAL+SECAM / mono"; + break; + case 5: + info = "NTSC / mono"; + break; + case 6: + info = "NTSC / stereo"; + break; + case 7: + info = "PAL / stereo"; + break; + default: + info = "oops: unknown card"; + break; + } + if (-1 != msp) + btv->c.type = BTTV_PINNACLEPRO; + printk(KERN_INFO + "bttv%d: pinnacle/mt: id=%d info=\"%s\" radio=%s\n", + btv->c.nr, id, info, btv->has_radio ? "yes" : "no"); + btv->tuner_type = 33; + btv->pinnacle_id = id; + } +} + +/* GPIO21 L: Buffer aktiv, H: Buffer inaktiv */ +#define LM1882_SYNC_DRIVE 0x200000L + +static void init_ids_eagle(struct bttv *btv) +{ + gpio_inout(0xffffff,0xFFFF37); + gpio_write(0x200020); + + /* flash strobe inverter ?! */ + gpio_write(0x200024); + + /* switch sync drive off */ + gpio_bits(LM1882_SYNC_DRIVE,LM1882_SYNC_DRIVE); + + /* set BT848 muxel to 2 */ + btaor((2)<<5, ~(2<<5), BT848_IFORM); +} + +/* Muxsel helper for the IDS Eagle. + * the eagles does not use the standard muxsel-bits but + * has its own multiplexer */ +static void eagle_muxsel(struct bttv *btv, unsigned int input) +{ + btaor((2)<<5, ~(3<<5), BT848_IFORM); + gpio_bits(3,bttv_tvcards[btv->c.type].muxsel[input&7]); + +#if 0 + /* svhs */ + /* wake chroma ADC */ + btand(~BT848_ADC_C_SLEEP, BT848_ADC); + /* set to YC video */ + btor(BT848_CONTROL_COMP, BT848_E_CONTROL); + btor(BT848_CONTROL_COMP, BT848_O_CONTROL); +#else + /* composite */ + /* set chroma ADC to sleep */ + btor(BT848_ADC_C_SLEEP, BT848_ADC); + /* set to composite video */ + btand(~BT848_CONTROL_COMP, BT848_E_CONTROL); + btand(~BT848_CONTROL_COMP, BT848_O_CONTROL); +#endif + + /* switch sync drive off */ + gpio_bits(LM1882_SYNC_DRIVE,LM1882_SYNC_DRIVE); +} + +static void gvc1100_muxsel(struct bttv *btv, unsigned int input) +{ + static const int masks[] = {0x30, 0x01, 0x12, 0x23}; + gpio_write(masks[input%4]); +} + +/* LMLBT4x initialization - to allow access to GPIO bits for sensors input and + alarms output + + GPIObit | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + assignment | TI | O3|INx| O2| O1|IN4|IN3|IN2|IN1| | | + + IN - sensor inputs, INx - sensor inputs and TI XORed together + O1,O2,O3 - alarm outputs (relays) + + OUT ENABLE 1 1 0 . 1 1 0 0 . 0 0 0 0 = 0x6C0 + +*/ + +static void init_lmlbt4x(struct bttv *btv) +{ + printk(KERN_DEBUG "LMLBT4x init\n"); + btwrite(0x000000, BT848_GPIO_REG_INP); + gpio_inout(0xffffff, 0x0006C0); + gpio_write(0x000000); +} + + +/* ----------------------------------------------------------------------- */ + +void bttv_reset_audio(struct bttv *btv) +{ + /* + * BT878A has a audio-reset register. + * 1. This register is an audio reset function but it is in + * function-0 (video capture) address space. + * 2. It is enough to do this once per power-up of the card. + * 3. There is a typo in the Conexant doc -- it is not at + * 0x5B, but at 0x058. (B is an odd-number, obviously a typo!). + * --//Shrikumar 030609 + */ + if (btv->id != 878) + return; + + if (bttv_debug) + printk("bttv%d: BT878A ARESET\n",btv->c.nr); + btwrite((1<<7), 0x058); + udelay(10); + btwrite( 0, 0x058); +} + +/* initialization part one -- before registering i2c bus */ +void __devinit bttv_init_card1(struct bttv *btv) +{ + switch (btv->c.type) { + case BTTV_HAUPPAUGE: + case BTTV_HAUPPAUGE878: + boot_msp34xx(btv,5); + break; + case BTTV_VOODOOTV_FM: + boot_msp34xx(btv,20); + break; + case BTTV_AVERMEDIA98: + boot_msp34xx(btv,11); + break; + case BTTV_HAUPPAUGEPVR: + pvr_boot(btv); + break; + } +} + +/* initialization part two -- after registering i2c bus */ +void __devinit bttv_init_card2(struct bttv *btv) +{ + btv->tuner_type = -1; + + if (BTTV_UNKNOWN == btv->c.type) { + bttv_readee(btv,eeprom_data,0xa0); + identify_by_eeprom(btv,eeprom_data); + } + + switch (btv->c.type) { + case BTTV_MIRO: + case BTTV_MIROPRO: + case BTTV_PINNACLE: + case BTTV_PINNACLEPRO: + /* miro/pinnacle */ + miro_pinnacle_gpio(btv); + break; + case BTTV_FLYVIDEO_98: + case BTTV_MAXI: + case BTTV_LIFE_FLYKIT: + case BTTV_FLYVIDEO: + case BTTV_TYPHOON_TVIEW: + case BTTV_CHRONOS_VS2: + case BTTV_FLYVIDEO_98FM: + case BTTV_FLYVIDEO2000: + case BTTV_FLYVIDEO98EZ: + case BTTV_CONFERENCETV: + case BTTV_LIFETEC_9415: + flyvideo_gpio(btv); + break; + case BTTV_HAUPPAUGE: + case BTTV_HAUPPAUGE878: + case BTTV_HAUPPAUGEPVR: + /* pick up some config infos from the eeprom */ + bttv_readee(btv,eeprom_data,0xa0); + hauppauge_eeprom(btv); + break; + case BTTV_AVERMEDIA98: + case BTTV_AVPHONE98: + bttv_readee(btv,eeprom_data,0xa0); + avermedia_eeprom(btv); + break; + case BTTV_PXC200: + init_PXC200(btv); + break; + case BTTV_VHX: + btv->has_radio = 1; + btv->has_matchbox = 1; + btv->mbox_we = 0x20; + btv->mbox_most = 0; + btv->mbox_clk = 0x08; + btv->mbox_data = 0x10; + btv->mbox_mask = 0x38; + break; + case BTTV_VOBIS_BOOSTAR: + case BTTV_TERRATV: + terratec_active_radio_upgrade(btv); + break; + case BTTV_MAGICTVIEW061: + if (btv->cardid == 0x3002144f) { + btv->has_radio=1; + printk("bttv%d: radio detected by subsystem id (CPH05x)\n",btv->c.nr); + } + break; + case BTTV_STB2: + if (btv->cardid == 0x3060121a) { + /* Fix up entry for 3DFX VoodooTV 100, + which is an OEM STB card variant. */ + btv->has_radio=0; + btv->tuner_type=TUNER_TEMIC_NTSC; + } + break; + case BTTV_OSPREY1x0: + case BTTV_OSPREY1x0_848: + case BTTV_OSPREY101_848: + case BTTV_OSPREY1x1: + case BTTV_OSPREY1x1_SVID: + case BTTV_OSPREY2xx: + case BTTV_OSPREY2x0_SVID: + case BTTV_OSPREY2x0: + case BTTV_OSPREY500: + case BTTV_OSPREY540: + case BTTV_OSPREY2000: + bttv_readee(btv,eeprom_data,0xa0); + osprey_eeprom(btv); + break; + case BTTV_IDS_EAGLE: + init_ids_eagle(btv); + break; + case BTTV_MODTEC_205: + bttv_readee(btv,eeprom_data,0xa0); + modtec_eeprom(btv); + break; + case BTTV_LMLBT4: + init_lmlbt4x(btv); + break; + } + + /* pll configuration */ + if (!(btv->id==848 && btv->revision==0x11)) { + /* defaults from card list */ + if (PLL_28 == bttv_tvcards[btv->c.type].pll) { + btv->pll.pll_ifreq=28636363; + btv->pll.pll_crystal=BT848_IFORM_XT0; + } + if (PLL_35 == bttv_tvcards[btv->c.type].pll) { + btv->pll.pll_ifreq=35468950; + btv->pll.pll_crystal=BT848_IFORM_XT1; + } + /* insmod options can override */ + switch (pll[btv->c.nr]) { + case 0: /* none */ + btv->pll.pll_crystal = 0; + btv->pll.pll_ifreq = 0; + btv->pll.pll_ofreq = 0; + break; + case 1: /* 28 MHz */ + case 28: + btv->pll.pll_ifreq = 28636363; + btv->pll.pll_ofreq = 0; + btv->pll.pll_crystal = BT848_IFORM_XT0; + break; + case 2: /* 35 MHz */ + case 35: + btv->pll.pll_ifreq = 35468950; + btv->pll.pll_ofreq = 0; + btv->pll.pll_crystal = BT848_IFORM_XT1; + break; + } + } + btv->pll.pll_current = -1; + + /* tuner configuration (from card list / autodetect / insmod option) */ + if (UNSET != bttv_tvcards[btv->c.type].tuner_type) + if(UNSET == btv->tuner_type) + btv->tuner_type = bttv_tvcards[btv->c.type].tuner_type; + if (UNSET != tuner[btv->c.nr]) + btv->tuner_type = tuner[btv->c.nr]; + printk("bttv%d: using tuner=%d\n",btv->c.nr,btv->tuner_type); + if (btv->pinnacle_id != UNSET) + bttv_call_i2c_clients(btv,AUDC_CONFIG_PINNACLE, + &btv->pinnacle_id); + if (btv->tuner_type != UNSET) + bttv_call_i2c_clients(btv,TUNER_SET_TYPE,&btv->tuner_type); + btv->svhs = bttv_tvcards[btv->c.type].svhs; + if (svhs[btv->c.nr] != UNSET) + btv->svhs = svhs[btv->c.nr]; + if (remote[btv->c.nr] != UNSET) + btv->has_remote = remote[btv->c.nr]; + + if (bttv_tvcards[btv->c.type].has_radio) + btv->has_radio=1; + if (bttv_tvcards[btv->c.type].has_remote) + btv->has_remote=1; + if (bttv_tvcards[btv->c.type].no_gpioirq) + btv->gpioirq=0; + if (bttv_tvcards[btv->c.type].audio_hook) + btv->audio_hook=bttv_tvcards[btv->c.type].audio_hook; + + if (bttv_tvcards[btv->c.type].digital_mode == DIGITAL_MODE_CAMERA) { + /* detect Bt832 chip for quartzsight digital camera */ + if ((bttv_I2CRead(btv, I2C_BT832_ALT1, "Bt832") >=0) || + (bttv_I2CRead(btv, I2C_BT832_ALT2, "Bt832") >=0)) + boot_bt832(btv); + } + + /* try to detect audio/fader chips */ + if (!bttv_tvcards[btv->c.type].no_msp34xx && + bttv_I2CRead(btv, I2C_MSP3400, "MSP34xx") >=0) { + if (autoload) + request_module("msp3400"); + } + + if (bttv_tvcards[btv->c.type].msp34xx_alt && + bttv_I2CRead(btv, I2C_MSP3400_ALT, "MSP34xx (alternate address)") >=0) { + if (autoload) + request_module("msp3400"); + } + + if (!bttv_tvcards[btv->c.type].no_tda9875 && + bttv_I2CRead(btv, I2C_TDA9875, "TDA9875") >=0) { + if (autoload) + request_module("tda9875"); + } + + if (!bttv_tvcards[btv->c.type].no_tda7432 && + bttv_I2CRead(btv, I2C_TDA7432, "TDA7432") >=0) { + if (autoload) + request_module("tda7432"); + } + + if (bttv_tvcards[btv->c.type].needs_tvaudio) { + if (autoload) + request_module("tvaudio"); + } + + /* tuner modules */ + if (btv->pinnacle_id != UNSET) { + if (autoload) + request_module("tda9887"); + } + if (btv->tuner_type != UNSET) { + if (autoload) + request_module("tuner"); + } +} + + +/* ----------------------------------------------------------------------- */ +/* some hauppauge specific stuff */ + +static struct HAUPPAUGE_TUNER +{ + int id; + char *name; +} +hauppauge_tuner[] __devinitdata = +{ + { TUNER_ABSENT, "" }, + { TUNER_ABSENT, "External" }, + { TUNER_ABSENT, "Unspecified" }, + { TUNER_PHILIPS_PAL, "Philips FI1216" }, + { TUNER_PHILIPS_SECAM, "Philips FI1216MF" }, + { TUNER_PHILIPS_NTSC, "Philips FI1236" }, + { TUNER_PHILIPS_PAL_I, "Philips FI1246" }, + { TUNER_PHILIPS_PAL_DK,"Philips FI1256" }, + { TUNER_PHILIPS_PAL, "Philips FI1216 MK2" }, + { TUNER_PHILIPS_SECAM, "Philips FI1216MF MK2" }, + { TUNER_PHILIPS_NTSC, "Philips FI1236 MK2" }, + { TUNER_PHILIPS_PAL_I, "Philips FI1246 MK2" }, + { TUNER_PHILIPS_PAL_DK,"Philips FI1256 MK2" }, + { TUNER_TEMIC_NTSC, "Temic 4032FY5" }, + { TUNER_TEMIC_PAL, "Temic 4002FH5" }, + { TUNER_TEMIC_PAL_I, "Temic 4062FY5" }, + { TUNER_PHILIPS_PAL, "Philips FR1216 MK2" }, + { TUNER_PHILIPS_SECAM, "Philips FR1216MF MK2" }, + { TUNER_PHILIPS_NTSC, "Philips FR1236 MK2" }, + { TUNER_PHILIPS_PAL_I, "Philips FR1246 MK2" }, + { TUNER_PHILIPS_PAL_DK,"Philips FR1256 MK2" }, + { TUNER_PHILIPS_PAL, "Philips FM1216" }, + { TUNER_PHILIPS_SECAM, "Philips FM1216MF" }, + { TUNER_PHILIPS_NTSC, "Philips FM1236" }, + { TUNER_PHILIPS_PAL_I, "Philips FM1246" }, + { TUNER_PHILIPS_PAL_DK,"Philips FM1256" }, + { TUNER_TEMIC_4036FY5_NTSC, "Temic 4036FY5" }, + { TUNER_ABSENT, "Samsung TCPN9082D" }, + { TUNER_ABSENT, "Samsung TCPM9092P" }, + { TUNER_TEMIC_4006FH5_PAL, "Temic 4006FH5" }, + { TUNER_ABSENT, "Samsung TCPN9085D" }, + { TUNER_ABSENT, "Samsung TCPB9085P" }, + { TUNER_ABSENT, "Samsung TCPL9091P" }, + { TUNER_TEMIC_4039FR5_NTSC, "Temic 4039FR5" }, + { TUNER_PHILIPS_FQ1216ME, "Philips FQ1216 ME" }, + { TUNER_TEMIC_4066FY5_PAL_I, "Temic 4066FY5" }, + { TUNER_PHILIPS_NTSC, "Philips TD1536" }, + { TUNER_PHILIPS_NTSC, "Philips TD1536D" }, + { TUNER_PHILIPS_NTSC, "Philips FMR1236" }, /* mono radio */ + { TUNER_ABSENT, "Philips FI1256MP" }, + { TUNER_ABSENT, "Samsung TCPQ9091P" }, + { TUNER_TEMIC_4006FN5_MULTI_PAL, "Temic 4006FN5" }, + { TUNER_TEMIC_4009FR5_PAL, "Temic 4009FR5" }, + { TUNER_TEMIC_4046FM5, "Temic 4046FM5" }, + { TUNER_TEMIC_4009FN5_MULTI_PAL_FM, "Temic 4009FN5" }, + { TUNER_ABSENT, "Philips TD1536D_FH_44"}, + { TUNER_LG_NTSC_FM, "LG TPI8NSR01F"}, + { TUNER_LG_PAL_FM, "LG TPI8PSB01D"}, + { TUNER_LG_PAL, "LG TPI8PSB11D"}, + { TUNER_LG_PAL_I_FM, "LG TAPC-I001D"}, + { TUNER_LG_PAL_I, "LG TAPC-I701D"} +}; + +static void modtec_eeprom(struct bttv *btv) +{ + if( strncmp(&(eeprom_data[0x1e]),"Temic 4066 FY5",14) ==0) { + btv->tuner_type=TUNER_TEMIC_4066FY5_PAL_I; + printk("bttv%d: Modtec: Tuner autodetected by eeprom: %s\n", + btv->c.nr,&eeprom_data[0x1e]); + } else if (strncmp(&(eeprom_data[0x1e]),"Alps TSBB5",10) ==0) { + btv->tuner_type=TUNER_ALPS_TSBB5_PAL_I; + printk("bttv%d: Modtec: Tuner autodetected by eeprom: %s\n", + btv->c.nr,&eeprom_data[0x1e]); + } else { + printk("bttv%d: Modtec: Unknown TunerString: %s\n", + btv->c.nr,&eeprom_data[0x1e]); + } +} + +static void __devinit hauppauge_eeprom(struct bttv *btv) +{ + unsigned int blk2,tuner,radio,model; + + if (eeprom_data[0] != 0x84 || eeprom_data[2] != 0) + printk(KERN_WARNING "bttv%d: Hauppauge eeprom: invalid\n", + btv->c.nr); + + /* Block 2 starts after len+3 bytes header */ + blk2 = eeprom_data[1] + 3; + + /* decode + use some config infos */ + model = eeprom_data[12] << 8 | eeprom_data[11]; + tuner = eeprom_data[9]; + radio = eeprom_data[blk2-1] & 0x01; + + if (tuner < ARRAY_SIZE(hauppauge_tuner)) + btv->tuner_type = hauppauge_tuner[tuner].id; + if (radio) + btv->has_radio = 1; + + if (bttv_verbose) + printk(KERN_INFO "bttv%d: Hauppauge eeprom: model=%d, " + "tuner=%s (%d), radio=%s\n", + btv->c.nr, model, hauppauge_tuner[tuner].name, + btv->tuner_type, radio ? "yes" : "no"); +} + +static int terratec_active_radio_upgrade(struct bttv *btv) +{ + int freq; + + btv->has_radio = 1; + btv->has_matchbox = 1; + btv->mbox_we = 0x10; + btv->mbox_most = 0x20; + btv->mbox_clk = 0x08; + btv->mbox_data = 0x04; + btv->mbox_mask = 0x3c; + + btv->mbox_iow = 1 << 8; + btv->mbox_ior = 1 << 9; + btv->mbox_csel = 1 << 10; + + freq=88000/62.5; + tea5757_write(btv, 5 * freq + 0x358); // write 0x1ed8 + if (0x1ed8 == tea5757_read(btv)) { + printk("bttv%d: Terratec Active Radio Upgrade found.\n", + btv->c.nr); + btv->has_radio = 1; + btv->has_matchbox = 1; + } else { + btv->has_radio = 0; + btv->has_matchbox = 0; + } + return 0; +} + + +/* ----------------------------------------------------------------------- */ + +/* + * minimal bootstrap for the WinTV/PVR -- upload altera firmware. + * + * The hcwamc.rbf firmware file is on the Hauppauge driver CD. Have + * a look at Pvr/pvr45xxx.EXE (self-extracting zip archive, can be + * unpacked with unzip). + */ +#define PVR_GPIO_DELAY 10 + +#define BTTV_ALT_DATA 0x000001 +#define BTTV_ALT_DCLK 0x100000 +#define BTTV_ALT_NCONFIG 0x800000 + +static int __devinit pvr_altera_load(struct bttv *btv, u8 *micro, u32 microlen) +{ + u32 n; + u8 bits; + int i; + + gpio_inout(0xffffff,BTTV_ALT_DATA|BTTV_ALT_DCLK|BTTV_ALT_NCONFIG); + gpio_write(0); + udelay(PVR_GPIO_DELAY); + + gpio_write(BTTV_ALT_NCONFIG); + udelay(PVR_GPIO_DELAY); + + for (n = 0; n < microlen; n++) { + bits = micro[n]; + for ( i = 0 ; i < 8 ; i++ ) { + gpio_bits(BTTV_ALT_DCLK,0); + if (bits & 0x01) + gpio_bits(BTTV_ALT_DATA,BTTV_ALT_DATA); + else + gpio_bits(BTTV_ALT_DATA,0); + gpio_bits(BTTV_ALT_DCLK,BTTV_ALT_DCLK); + bits >>= 1; + } + } + gpio_bits(BTTV_ALT_DCLK,0); + udelay(PVR_GPIO_DELAY); + + /* begin Altera init loop (Not necessary,but doesn't hurt) */ + for (i = 0 ; i < 30 ; i++) { + gpio_bits(BTTV_ALT_DCLK,0); + gpio_bits(BTTV_ALT_DCLK,BTTV_ALT_DCLK); + } + gpio_bits(BTTV_ALT_DCLK,0); + return 0; +} + +#ifndef CONFIG_FW_LOADER +/* old 2.4.x way -- via soundcore's mod_firmware_load */ + +static char *firm_altera = "/usr/lib/video4linux/hcwamc.rbf"; +MODULE_PARM(firm_altera,"s"); +MODULE_PARM_DESC(firm_altera,"WinTV/PVR firmware " + "(driver CD => unzip pvr45xxx.exe => hcwamc.rbf)"); + +extern int mod_firmware_load(const char *fn, char **fp); + +int __devinit pvr_boot(struct bttv *btv) +{ + u32 microlen; + u8 *micro; + int result; + + microlen = mod_firmware_load(firm_altera, (char**) µ); + if (!microlen) { + printk(KERN_WARNING "bttv%d: altera firmware not found [%s]\n", + btv->c.nr, firm_altera); + return -1; + } + + printk(KERN_INFO "bttv%d: uploading altera firmware [%s] ...\n", + btv->c.nr, firm_altera); + result = pvr_altera_load(btv, micro, microlen); + printk(KERN_INFO "bttv%d: ... upload %s\n", + btv->c.nr, (result < 0) ? "failed" : "ok"); + vfree(micro); + return result; +} +#else +/* new 2.5.x way -- via hotplug firmware loader */ + +int __devinit pvr_boot(struct bttv *btv) +{ + const struct firmware *fw_entry; + int rc; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + rc = request_firmware(&fw_entry, "hcwamc.rbf", &btv->c.pci->dev); +#else + rc = request_firmware(&fw_entry, "hcwamc.rbf", pci_name(btv->c.pci)); +#endif + if (rc != 0) { + printk(KERN_WARNING "bttv%d: no altera firmware [via hotplug]\n", + btv->c.nr); + return rc; + } + rc = pvr_altera_load(btv, fw_entry->data, fw_entry->size); + printk(KERN_INFO "bttv%d: altera firmware upload %s\n", + btv->c.nr, (rc < 0) ? "failed" : "ok"); + release_firmware(fw_entry); + return rc; +} +#endif + +/* ----------------------------------------------------------------------- */ +/* some osprey specific stuff */ + +static void __devinit osprey_eeprom(struct bttv *btv) +{ + int i = 0; + unsigned char *ee = eeprom_data; + unsigned long serial = 0; + + if (btv->c.type == 0) { + /* this might be an antique... check for MMAC label in eeprom */ + if ((ee[0]=='M') && (ee[1]=='M') && (ee[2]=='A') && (ee[3]=='C')) { + unsigned char checksum = 0; + for (i =0; i<21; i++) + checksum += ee[i]; + if (checksum != ee[21]) + return; + btv->c.type = BTTV_OSPREY1x0_848; + for (i = 12; i < 21; i++) + serial *= 10, serial += ee[i] - '0'; + } + } else { + unsigned short type; + int offset = 4*16; + + for(; offset < 8*16; offset += 16) { + unsigned short checksum = 0; + /* verify the checksum */ + for(i = 0; i<14; i++) checksum += ee[i+offset]; + checksum = ~checksum; /* no idea why */ + if ((((checksum>>8)&0x0FF) == ee[offset+14]) && + ((checksum & 0x0FF) == ee[offset+15])) { + break; + } + } + + if (offset >= 8*16) + return; + + /* found a valid descriptor */ + type = (ee[offset+4]<<8) | (ee[offset+5]); + + switch(type) { + + /* 848 based */ + case 0x0004: + btv->c.type = BTTV_OSPREY1x0_848; + break; + case 0x0005: + btv->c.type = BTTV_OSPREY101_848; + break; + + /* 878 based */ + case 0x0012: + case 0x0013: + btv->c.type = BTTV_OSPREY1x0; + break; + case 0x0014: + case 0x0015: + btv->c.type = BTTV_OSPREY1x1; + break; + case 0x0016: + case 0x0017: + case 0x0020: + btv->c.type = BTTV_OSPREY1x1_SVID; + break; + case 0x0018: + case 0x0019: + case 0x001E: + case 0x001F: + btv->c.type = BTTV_OSPREY2xx; + break; + case 0x001A: + case 0x001B: + btv->c.type = BTTV_OSPREY2x0_SVID; + break; + case 0x0040: + btv->c.type = BTTV_OSPREY500; + break; + case 0x0050: + case 0x0056: + btv->c.type = BTTV_OSPREY540; + /* bttv_osprey_540_init(btv); */ + break; + case 0x0060: + case 0x0070: + btv->c.type = BTTV_OSPREY2x0; + //enable output on select control lines + gpio_inout(0xffffff,0x000303); + break; + default: + /* unknown...leave generic, but get serial # */ + break; + } + serial = (ee[offset+6] << 24) + | (ee[offset+7] << 16) + | (ee[offset+8] << 8) + | (ee[offset+9]); + } + + printk(KERN_INFO "bttv%d: osprey eeprom: card=%d name=%s serial=%ld\n", + btv->c.nr, btv->c.type, bttv_tvcards[btv->c.type].name,serial); +} + +/* ----------------------------------------------------------------------- */ +/* AVermedia specific stuff, from bktr_card.c */ + +int tuner_0_table[] = { + TUNER_PHILIPS_NTSC, TUNER_PHILIPS_PAL /* PAL-BG*/, + TUNER_PHILIPS_PAL, TUNER_PHILIPS_PAL /* PAL-I*/, + TUNER_PHILIPS_PAL, TUNER_PHILIPS_PAL, + TUNER_PHILIPS_SECAM, TUNER_PHILIPS_SECAM, + TUNER_PHILIPS_SECAM, TUNER_PHILIPS_PAL}; +#if 0 +int tuner_0_fm_table[] = { + PHILIPS_FR1236_NTSC, PHILIPS_FR1216_PAL, + PHILIPS_FR1216_PAL, PHILIPS_FR1216_PAL, + PHILIPS_FR1216_PAL, PHILIPS_FR1216_PAL, + PHILIPS_FR1236_SECAM, PHILIPS_FR1236_SECAM, + PHILIPS_FR1236_SECAM, PHILIPS_FR1216_PAL}; +#endif + +int tuner_1_table[] = { + TUNER_TEMIC_NTSC, TUNER_TEMIC_PAL, + TUNER_TEMIC_PAL, TUNER_TEMIC_PAL, + TUNER_TEMIC_PAL, TUNER_TEMIC_PAL, + TUNER_TEMIC_4012FY5, TUNER_TEMIC_4012FY5, //TUNER_TEMIC_SECAM + TUNER_TEMIC_4012FY5, TUNER_TEMIC_PAL}; + +static void __devinit avermedia_eeprom(struct bttv *btv) +{ + int tuner_make,tuner_tv_fm,tuner_format,tuner=0; + + tuner_make = (eeprom_data[0x41] & 0x7); + tuner_tv_fm = (eeprom_data[0x41] & 0x18) >> 3; + tuner_format = (eeprom_data[0x42] & 0xf0) >> 4; + btv->has_remote = (eeprom_data[0x42] & 0x01); + + if (tuner_make == 0 || tuner_make == 2) + if(tuner_format <=9) + tuner = tuner_0_table[tuner_format]; + if (tuner_make == 1) + if(tuner_format <=9) + tuner = tuner_1_table[tuner_format]; + + printk(KERN_INFO "bttv%d: Avermedia eeprom[0x%02x%02x]: tuner=", + btv->c.nr,eeprom_data[0x41],eeprom_data[0x42]); + if(tuner) { + btv->tuner_type=tuner; + printk("%d",tuner); + } else + printk("Unknown type"); + printk(" radio:%s remote control:%s\n", + tuner_tv_fm ? "yes" : "no", + btv->has_remote ? "yes" : "no"); +} + +/* used on Voodoo TV/FM (Voodoo 200), S0 wired to 0x10000 */ +void bttv_tda9880_setnorm(struct bttv *btv, int norm) +{ + // fix up our card entry + if(norm==VIDEO_MODE_NTSC) { + bttv_tvcards[BTTV_VOODOOTV_FM].audiomux[0]=0x957fff; + bttv_tvcards[BTTV_VOODOOTV_FM].audiomux[4]=0x957fff; + dprintk("bttv_tda9880_setnorm to NTSC\n"); + } + else { + bttv_tvcards[BTTV_VOODOOTV_FM].audiomux[0]=0x947fff; + bttv_tvcards[BTTV_VOODOOTV_FM].audiomux[4]=0x947fff; + dprintk("bttv_tda9880_setnorm to PAL\n"); + } + // set GPIO according + gpio_bits(bttv_tvcards[btv->c.type].gpiomask, + bttv_tvcards[btv->c.type].audiomux[btv->audio]); +} + + +/* + * reset/enable the MSP on some Hauppauge cards + * Thanks to Kyösti Mälkki (kmalkki@cc.hut.fi)! + * + * Hauppauge: pin 5 + * Voodoo: pin 20 + */ +static void __devinit boot_msp34xx(struct bttv *btv, int pin) +{ + int mask = (1 << pin); + + gpio_inout(mask,mask); + gpio_bits(mask,0); + udelay(2500); + gpio_bits(mask,mask); + + if (bttv_gpio) + bttv_gpio_tracking(btv,"msp34xx"); + if (bttv_verbose) + printk(KERN_INFO "bttv%d: Hauppauge/Voodoo msp34xx: reset line " + "init [%d]\n", btv->c.nr, pin); +} + +static void __devinit boot_bt832(struct bttv *btv) +{ + int resetbit=0; + + switch (btv->c.type) { + case BTTV_PXELVWPLTVPAK: + resetbit = 0x400000; + break; + case BTTV_MODTEC_205: + resetbit = 1<<9; + break; + default: + BUG(); + } + + request_module("bt832"); + bttv_call_i2c_clients(btv, BT832_HEXDUMP, NULL); + + printk("bttv%d: Reset Bt832 [line=0x%x]\n",btv->c.nr,resetbit); + gpio_write(0); + gpio_inout(resetbit, resetbit); + udelay(5); + gpio_bits(resetbit, resetbit); + udelay(5); + gpio_bits(resetbit, 0); + udelay(5); + + // bt832 on pixelview changes from i2c 0x8a to 0x88 after + // being reset as above. So we must follow by this: + bttv_call_i2c_clients(btv, BT832_REATTACH, NULL); +} + +/* ----------------------------------------------------------------------- */ +/* Imagenation L-Model PXC200 Framegrabber */ +/* This is basically the same procedure as + * used by Alessandro Rubini in his pxc200 + * driver, but using BTTV functions */ + +static void __devinit init_PXC200(struct bttv *btv) +{ + static int vals[] __devinitdata = { 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0d, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x00 }; + unsigned int i; + int tmp; + u32 val; + + /* Initialise GPIO-connevted stuff */ + gpio_inout(0xffffff, (1<<13)); + gpio_write(0); + udelay(3); + gpio_write(1<<13); + /* GPIO inputs are pulled up, so no need to drive + * reset pin any longer */ + gpio_bits(0xffffff, 0); + if (bttv_gpio) + bttv_gpio_tracking(btv,"pxc200"); + + /* we could/should try and reset/control the AD pots? but + right now we simply turned off the crushing. Without + this the AGC drifts drifts + remember the EN is reverse logic --> + setting BT848_ADC_AGC_EN disable the AGC + tboult@eecs.lehigh.edu + */ + + btwrite(BT848_ADC_RESERVED|BT848_ADC_AGC_EN, BT848_ADC); + + /* Initialise MAX517 DAC */ + printk(KERN_INFO "Setting DAC reference voltage level ...\n"); + bttv_I2CWrite(btv,0x5E,0,0x80,1); + + /* Initialise 12C508 PIC */ + /* The I2CWrite and I2CRead commmands are actually to the + * same chips - but the R/W bit is included in the address + * argument so the numbers are different */ + + + printk(KERN_INFO "Initialising 12C508 PIC chip ...\n"); + + /* First of all, enable the clock line. This is used in the PXC200-F */ + val = btread(BT848_GPIO_DMA_CTL); + val |= BT848_GPIO_DMA_CTL_GPCLKMODE; + btwrite(val, BT848_GPIO_DMA_CTL); + + /* Then, push to 0 the reset pin long enough to reset the * + * device same as above for the reset line, but not the same + * value sent to the GPIO-connected stuff + * which one is the good one? */ + gpio_inout(0xffffff,(1<<2)); + gpio_write(0); + udelay(10); + gpio_write(1<<2); + + for (i = 0; i < ARRAY_SIZE(vals); i++) { + tmp=bttv_I2CWrite(btv,0x1E,0,vals[i],1); + if (tmp != -1) { + printk(KERN_INFO + "I2C Write(%2.2x) = %i\nI2C Read () = %2.2x\n\n", + vals[i],tmp,bttv_I2CRead(btv,0x1F,NULL)); + } + } + + printk(KERN_INFO "PXC200 Initialised.\n"); +} + + +/* ----------------------------------------------------------------------- */ +/* Miro Pro radio stuff -- the tea5757 is connected to some GPIO ports */ +/* + * Copyright (c) 1999 Csaba Halasz <qgehali@uni-miskolc.hu> + * This code is placed under the terms of the GNU General Public License + * + * Brutally hacked by Dan Sheridan <dan.sheridan@contact.org.uk> djs52 8/3/00 + */ + +void bus_low(struct bttv *btv, int bit) +{ + if (btv->mbox_ior) { + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + udelay(5); + } + + gpio_bits(bit,0); + udelay(5); + + if (btv->mbox_ior) { + gpio_bits(btv->mbox_iow | btv->mbox_csel, 0); + udelay(5); + } +} + +void bus_high(struct bttv *btv, int bit) +{ + if (btv->mbox_ior) { + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + udelay(5); + } + + gpio_bits(bit,bit); + udelay(5); + + if (btv->mbox_ior) { + gpio_bits(btv->mbox_iow | btv->mbox_csel, 0); + udelay(5); + } +} + +int bus_in(struct bttv *btv, int bit) +{ + if (btv->mbox_ior) { + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + udelay(5); + + gpio_bits(btv->mbox_iow | btv->mbox_csel, 0); + udelay(5); + } + return gpio_read() & (bit); +} + +/* TEA5757 register bits */ +#define TEA_FREQ 0:14 +#define TEA_BUFFER 15:15 + +#define TEA_SIGNAL_STRENGTH 16:17 + +#define TEA_PORT1 18:18 +#define TEA_PORT0 19:19 + +#define TEA_BAND 20:21 +#define TEA_BAND_FM 0 +#define TEA_BAND_MW 1 +#define TEA_BAND_LW 2 +#define TEA_BAND_SW 3 + +#define TEA_MONO 22:22 +#define TEA_ALLOW_STEREO 0 +#define TEA_FORCE_MONO 1 + +#define TEA_SEARCH_DIRECTION 23:23 +#define TEA_SEARCH_DOWN 0 +#define TEA_SEARCH_UP 1 + +#define TEA_STATUS 24:24 +#define TEA_STATUS_TUNED 0 +#define TEA_STATUS_SEARCHING 1 + +/* Low-level stuff */ +static int tea5757_read(struct bttv *btv) +{ + unsigned long timeout; + int value = 0; + int i; + + /* better safe than sorry */ + gpio_inout(btv->mbox_mask, btv->mbox_clk | btv->mbox_we); + + if (btv->mbox_ior) { + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + udelay(5); + } + + if (bttv_gpio) + bttv_gpio_tracking(btv,"tea5757 read"); + + bus_low(btv,btv->mbox_we); + bus_low(btv,btv->mbox_clk); + + udelay(10); + timeout= jiffies + HZ; + + // wait for DATA line to go low; error if it doesn't + while (bus_in(btv,btv->mbox_data) && time_before(jiffies, timeout)) + schedule(); + if (bus_in(btv,btv->mbox_data)) { + printk(KERN_WARNING "bttv%d: tea5757: read timeout\n",btv->c.nr); + return -1; + } + + dprintk("bttv%d: tea5757:",btv->c.nr); + for(i = 0; i < 24; i++) + { + udelay(5); + bus_high(btv,btv->mbox_clk); + udelay(5); + dprintk("%c",(bus_in(btv,btv->mbox_most) == 0)?'T':'-'); + bus_low(btv,btv->mbox_clk); + value <<= 1; + value |= (bus_in(btv,btv->mbox_data) == 0)?0:1; /* MSB first */ + dprintk("%c", (bus_in(btv,btv->mbox_most) == 0)?'S':'M'); + } + dprintk("\nbttv%d: tea5757: read 0x%X\n", btv->c.nr, value); + return value; +} + +static int tea5757_write(struct bttv *btv, int value) +{ + int i; + int reg = value; + + gpio_inout(btv->mbox_mask, btv->mbox_clk | btv->mbox_we | btv->mbox_data); + + if (btv->mbox_ior) { + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + udelay(5); + } + if (bttv_gpio) + bttv_gpio_tracking(btv,"tea5757 write"); + + dprintk("bttv%d: tea5757: write 0x%X\n", btv->c.nr, value); + bus_low(btv,btv->mbox_clk); + bus_high(btv,btv->mbox_we); + for(i = 0; i < 25; i++) + { + if (reg & 0x1000000) + bus_high(btv,btv->mbox_data); + else + bus_low(btv,btv->mbox_data); + reg <<= 1; + bus_high(btv,btv->mbox_clk); + udelay(10); + bus_low(btv,btv->mbox_clk); + udelay(10); + } + bus_low(btv,btv->mbox_we); /* unmute !!! */ + return 0; +} + +void tea5757_set_freq(struct bttv *btv, unsigned short freq) +{ + dprintk("tea5757_set_freq %d\n",freq); + tea5757_write(btv, 5 * freq + 0x358); /* add 10.7MHz (see docs) */ +#if 0 + /* breaks Miro PCTV */ + value = tea5757_read(btv); + dprintk("bttv%d: tea5757 readback=0x%x\n",btv->c.nr,value); +#endif +} + + +/* ----------------------------------------------------------------------- */ +/* winview */ + +void winview_audio(struct bttv *btv, struct video_audio *v, int set) +{ + /* PT2254A programming Jon Tombs, jon@gte.esi.us.es */ + int bits_out, loops, vol, data; + + if (!set) { + /* Fixed by Leandro Lucarella <luca@linuxmendoza.org.ar (07/31/01) */ + v->flags |= VIDEO_AUDIO_VOLUME; + return; + } + + /* 32 levels logarithmic */ + vol = 32 - ((v->volume>>11)); + /* units */ + bits_out = (PT2254_DBS_IN_2>>(vol%5)); + /* tens */ + bits_out |= (PT2254_DBS_IN_10>>(vol/5)); + bits_out |= PT2254_L_CHANNEL | PT2254_R_CHANNEL; + data = gpio_read(); + data &= ~(WINVIEW_PT2254_CLK| WINVIEW_PT2254_DATA| + WINVIEW_PT2254_STROBE); + for (loops = 17; loops >= 0 ; loops--) { + if (bits_out & (1<<loops)) + data |= WINVIEW_PT2254_DATA; + else + data &= ~WINVIEW_PT2254_DATA; + gpio_write(data); + udelay(5); + data |= WINVIEW_PT2254_CLK; + gpio_write(data); + udelay(5); + data &= ~WINVIEW_PT2254_CLK; + gpio_write(data); + } + data |= WINVIEW_PT2254_STROBE; + data &= ~WINVIEW_PT2254_DATA; + gpio_write(data); + udelay(10); + data &= ~WINVIEW_PT2254_STROBE; + gpio_write(data); +} + +/* ----------------------------------------------------------------------- */ +/* mono/stereo control for various cards (which don't use i2c chips but */ +/* connect something to the GPIO pins */ + +static void +gvbctv3pci_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int con = 0; + + if (set) { + gpio_inout(0x300, 0x300); + if (v->mode & VIDEO_SOUND_LANG1) + con = 0x000; + if (v->mode & VIDEO_SOUND_LANG2) + con = 0x300; + if (v->mode & VIDEO_SOUND_STEREO) + con = 0x200; +// if (v->mode & VIDEO_SOUND_MONO) +// con = 0x100; + gpio_bits(0x300, con); + } else { + v->mode = VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +static void +gvbctv5pci_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int val, con; + +#if BTTV_VERSION_CODE > KERNEL_VERSION(0,8,0) + if (btv->radio_user) + return; +#else + if (btv->radio) + return; +#endif + + val = gpio_read(); + if (set) { + con = 0x000; + if (v->mode & VIDEO_SOUND_LANG2) { + if (v->mode & VIDEO_SOUND_LANG1) { + /* LANG1 + LANG2 */ + con = 0x100; + } + else { + /* LANG2 */ + con = 0x300; + } + } + if (con != (val & 0x300)) { + gpio_bits(0x300, con); + if (bttv_gpio) + bttv_gpio_tracking(btv,"gvbctv5pci"); + } + } else { + switch (val & 0x70) { + case 0x10: + v->mode = VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + break; + case 0x30: + v->mode = VIDEO_SOUND_LANG2; + break; + case 0x50: + v->mode = VIDEO_SOUND_LANG1; + break; + case 0x60: + v->mode = VIDEO_SOUND_STEREO; + break; + case 0x70: + v->mode = VIDEO_SOUND_MONO; + break; + default: + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } + } +} + +/* + * Mario Medina Nussbaum <medisoft@alohabbs.org.mx> + * I discover that on BT848_GPIO_DATA address a byte 0xcce enable stereo, + * 0xdde enables mono and 0xccd enables sap + * + * Petr Vandrovec <VANDROVE@vc.cvut.cz> + * P.S.: At least mask in line above is wrong - GPIO pins 3,2 select + * input/output sound connection, so both must be set for output mode. + * + * Looks like it's needed only for the "tvphone", the "tvphone 98" + * handles this with a tda9840 + * + */ +static void +avermedia_tvphone_audio(struct bttv *btv, struct video_audio *v, int set) +{ + int val = 0; + + if (set) { + if (v->mode & VIDEO_SOUND_LANG2) /* SAP */ + val = 0x02; + if (v->mode & VIDEO_SOUND_STEREO) + val = 0x01; + if (val) { + gpio_bits(0x03,val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"avermedia"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1; + return; + } +} + +static void +avermedia_tv_stereo_audio(struct bttv *btv, struct video_audio *v, int set) +{ + int val = 0; + + if (set) { + if (v->mode & VIDEO_SOUND_LANG2) /* SAP */ + val = 0x01; + if (v->mode & VIDEO_SOUND_STEREO) /* STEREO */ + val = 0x02; + btaor(val, ~0x03, BT848_GPIO_DATA); + if (bttv_gpio) + bttv_gpio_tracking(btv,"avermedia"); + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + return; + } +} + +/* Lifetec 9415 handling */ +static void +lt9415_audio(struct bttv *btv, struct video_audio *v, int set) +{ + int val = 0; + + if (gpio_read() & 0x4000) { + v->mode = VIDEO_SOUND_MONO; + return; + } + + if (set) { + if (v->mode & VIDEO_SOUND_LANG2) /* A2 SAP */ + val = 0x0080; + if (v->mode & VIDEO_SOUND_STEREO) /* A2 stereo */ + val = 0x0880; + if ((v->mode & VIDEO_SOUND_LANG1) || + (v->mode & VIDEO_SOUND_MONO)) + val = 0; + gpio_bits(0x0880, val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"lt9415"); + } else { + /* autodetect doesn't work with this card :-( */ + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + return; + } +} + +// TDA9821 on TerraTV+ Bt848, Bt878 +static void +terratv_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int con = 0; + + if (set) { + gpio_inout(0x180000,0x180000); + if (v->mode & VIDEO_SOUND_LANG2) + con = 0x080000; + if (v->mode & VIDEO_SOUND_STEREO) + con = 0x180000; + gpio_bits(0x180000, con); + if (bttv_gpio) + bttv_gpio_tracking(btv,"terratv"); + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +static void +winfast2000_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned long val = 0; + + if (set) { + /*btor (0xc32000, BT848_GPIO_OUT_EN);*/ + if (v->mode & VIDEO_SOUND_MONO) /* Mono */ + val = 0x420000; + if (v->mode & VIDEO_SOUND_LANG1) /* Mono */ + val = 0x420000; + if (v->mode & VIDEO_SOUND_LANG2) /* SAP */ + val = 0x410000; + if (v->mode & VIDEO_SOUND_STEREO) /* Stereo */ + val = 0x020000; + if (val) { + gpio_bits(0x430000, val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"winfast2000"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +/* + * Dariusz Kowalewski <darekk@automex.pl> + * sound control for Prolink PV-BT878P+9B (PixelView PlayTV Pro FM+NICAM + * revision 9B has on-board TDA9874A sound decoder). + * + * Note: There are card variants without tda9874a. Forcing the "stereo sound route" + * will mute this cards. + */ +static void +pvbt878p9b_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int val = 0; + +#if BTTV_VERSION_CODE > KERNEL_VERSION(0,8,0) + if (btv->radio_user) + return; +#else + if (btv->radio) + return; +#endif + + if (set) { + if (v->mode & VIDEO_SOUND_MONO) { + val = 0x01; + } + if ((v->mode & (VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2)) + || (v->mode & VIDEO_SOUND_STEREO)) { + val = 0x02; + } + if (val) { + gpio_bits(0x03,val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"pvbt878p9b"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +/* + * Dariusz Kowalewski <darekk@automex.pl> + * sound control for FlyVideo 2000S (with tda9874 decoder) + * based on pvbt878p9b_audio() - this is not tested, please fix!!! + */ +static void +fv2000s_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int val = 0xffff; + +#if BTTV_VERSION_CODE > KERNEL_VERSION(0,8,0) + if (btv->radio_user) + return; +#else + if (btv->radio) + return; +#endif + if (set) { + if (v->mode & VIDEO_SOUND_MONO) { + val = 0x0000; + } + if ((v->mode & (VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2)) + || (v->mode & VIDEO_SOUND_STEREO)) { + val = 0x1080; //-dk-???: 0x0880, 0x0080, 0x1800 ... + } + if (val != 0xffff) { + gpio_bits(0x1800, val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"fv2000s"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +/* + * sound control for Canopus WinDVR PCI + * Masaki Suzuki <masaki@btree.org> + */ +static void +windvr_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned long val = 0; + + if (set) { + if (v->mode & VIDEO_SOUND_MONO) + val = 0x040000; + if (v->mode & VIDEO_SOUND_LANG1) + val = 0; + if (v->mode & VIDEO_SOUND_LANG2) + val = 0x100000; + if (v->mode & VIDEO_SOUND_STEREO) + val = 0; + if (val) { + gpio_bits(0x140000, val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"windvr"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +/* + * sound control for AD-TVK503 + * Hiroshi Takekawa <sian@big.or.jp> + */ +static void +adtvk503_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int con = 0xffffff; + + //btaor(0x1e0000, ~0x1e0000, BT848_GPIO_OUT_EN); + + if (set) { + //btor(***, BT848_GPIO_OUT_EN); + if (v->mode & VIDEO_SOUND_LANG1) + con = 0x00000000; + if (v->mode & VIDEO_SOUND_LANG2) + con = 0x00180000; + if (v->mode & VIDEO_SOUND_STEREO) + con = 0x00000000; + if (v->mode & VIDEO_SOUND_MONO) + con = 0x00060000; + if (con != 0xffffff) { + gpio_bits(0x1e0000,con); + if (bttv_gpio) + bttv_gpio_tracking(btv, "adtvk503"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +/* RemoteVision MX (rv605) muxsel helper [Miguel Freitas] + * + * This is needed because rv605 don't use a normal multiplex, but a crosspoint + * switch instead (CD22M3494E). This IC can have multiple active connections + * between Xn (input) and Yn (output) pins. We need to clear any existing + * connection prior to establish a new one, pulsing the STROBE pin. + * + * The board hardwire Y0 (xpoint) to MUX1 and MUXOUT to Yin. + * GPIO pins are wired as: + * GPIO[0:3] - AX[0:3] (xpoint) - P1[0:3] (microcontroler) + * GPIO[4:6] - AY[0:2] (xpoint) - P1[4:6] (microcontroler) + * GPIO[7] - DATA (xpoint) - P1[7] (microcontroler) + * GPIO[8] - - P3[5] (microcontroler) + * GPIO[9] - RESET (xpoint) - P3[6] (microcontroler) + * GPIO[10] - STROBE (xpoint) - P3[7] (microcontroler) + * GPINTR - - P3[4] (microcontroler) + * + * The microcontroler is a 80C32 like. It should be possible to change xpoint + * configuration either directly (as we are doing) or using the microcontroler + * which is also wired to I2C interface. I have no further info on the + * microcontroler features, one would need to disassembly the firmware. + * note: the vendor refused to give any information on this product, all + * that stuff was found using a multimeter! :) + */ +static void rv605_muxsel(struct bttv *btv, unsigned int input) +{ + /* reset all conections */ + gpio_bits(0x200,0x200); + mdelay(1); + gpio_bits(0x200,0x000); + mdelay(1); + + /* create a new conection */ + gpio_bits(0x480,0x080); + gpio_bits(0x480,0x480); + mdelay(1); + gpio_bits(0x480,0x080); + mdelay(1); +} + +// The Grandtec X-Guard framegrabber card uses two Dual 4-channel +// video multiplexers to provide up to 16 video inputs. These +// multiplexers are controlled by the lower 8 GPIO pins of the +// bt878. The multiplexers probably Pericom PI5V331Q or similar. + +// xxx0 is pin xxx of multiplexer U5, +// yyy1 is pin yyy of multiplexer U2 + +#define ENA0 0x01 +#define ENB0 0x02 +#define ENA1 0x04 +#define ENB1 0x08 + +#define IN10 0x10 +#define IN00 0x20 +#define IN11 0x40 +#define IN01 0x80 + +static void xguard_muxsel(struct bttv *btv, unsigned int input) +{ + static const int masks[] = { + ENB0, ENB0|IN00, ENB0|IN10, ENB0|IN00|IN10, + ENA0, ENA0|IN00, ENA0|IN10, ENA0|IN00|IN10, + ENB1, ENB1|IN01, ENB1|IN11, ENB1|IN01|IN11, + ENA1, ENA1|IN01, ENA1|IN11, ENA1|IN01|IN11, + }; + gpio_write(masks[input%16]); +} + +/* + * ivc120_muxsel [Added by Alan Garfield <alan@fromorbit.com>] + * + * The IVC120G security card has 4 i2c controlled TDA8540 matrix + * swichers to provide 16 channels to MUX0. The TDA8540's have + * 4 indepedant outputs and as such the IVC120G also has the + * optional "Monitor Out" bus. This allows the card to be looking + * at one input while the monitor is looking at another. + * + * Since I've couldn't be bothered figuring out how to add an + * independant muxsel for the monitor bus, I've just set it to + * whatever the card is looking at. + * + * OUT0 of the TDA8540's is connected to MUX0 (0x03) + * OUT1 of the TDA8540's is connected to "Monitor Out" (0x0C) + * + * TDA8540_ALT3 IN0-3 = Channel 13 - 16 (0x03) + * TDA8540_ALT4 IN0-3 = Channel 1 - 4 (0x03) + * TDA8540_ALT5 IN0-3 = Channel 5 - 8 (0x03) + * TDA8540_ALT6 IN0-3 = Channel 9 - 12 (0x03) + * + */ + +/* All 7 possible sub-ids for the TDA8540 Matrix Switcher */ +#define I2C_TDA8540 0x90 +#define I2C_TDA8540_ALT1 0x92 +#define I2C_TDA8540_ALT2 0x94 +#define I2C_TDA8540_ALT3 0x96 +#define I2C_TDA8540_ALT4 0x98 +#define I2C_TDA8540_ALT5 0x9a +#define I2C_TDA8540_ALT6 0x9c + +static void ivc120_muxsel(struct bttv *btv, unsigned int input) +{ + // Simple maths + int key = input % 4; + int matrix = input / 4; + + dprintk("bttv%d: ivc120_muxsel: Input - %02d | TDA - %02d | In - %02d\n", + btv->c.nr, input, matrix, key); + + // Handles the input selection on the TDA8540's + bttv_I2CWrite(btv, I2C_TDA8540_ALT3, 0x00, + ((matrix == 3) ? (key | key << 2) : 0x00), 1); + bttv_I2CWrite(btv, I2C_TDA8540_ALT4, 0x00, + ((matrix == 0) ? (key | key << 2) : 0x00), 1); + bttv_I2CWrite(btv, I2C_TDA8540_ALT5, 0x00, + ((matrix == 1) ? (key | key << 2) : 0x00), 1); + bttv_I2CWrite(btv, I2C_TDA8540_ALT6, 0x00, + ((matrix == 2) ? (key | key << 2) : 0x00), 1); + + // Handles the output enables on the TDA8540's + bttv_I2CWrite(btv, I2C_TDA8540_ALT3, 0x02, + ((matrix == 3) ? 0x03 : 0x00), 1); // 13 - 16 + bttv_I2CWrite(btv, I2C_TDA8540_ALT4, 0x02, + ((matrix == 0) ? 0x03 : 0x00), 1); // 1-4 + bttv_I2CWrite(btv, I2C_TDA8540_ALT5, 0x02, + ((matrix == 1) ? 0x03 : 0x00), 1); // 5-8 + bttv_I2CWrite(btv, I2C_TDA8540_ALT6, 0x02, + ((matrix == 2) ? 0x03 : 0x00), 1); // 9-12 + + // Selects MUX0 for input on the 878 + btaor((0)<<5, ~(3<<5), BT848_IFORM); +} + + +/* ----------------------------------------------------------------------- */ +/* motherboard chipset specific stuff */ + +void __devinit bttv_check_chipset(void) +{ + int pcipci_fail = 0; + struct pci_dev *dev = NULL; + + if (pci_pci_problems & PCIPCI_FAIL) + pcipci_fail = 1; + if (pci_pci_problems & (PCIPCI_TRITON|PCIPCI_NATOMA|PCIPCI_VIAETBF)) + triton1 = 1; + if (pci_pci_problems & PCIPCI_VSFX) + vsfx = 1; +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) + latency = 0x0A; +#endif + +#if 0 + /* print which chipset we have */ + while ((dev = pci_find_class(PCI_CLASS_BRIDGE_HOST << 8,dev))) + printk(KERN_INFO "bttv: Host bridge is %s\n",pci_name(dev)); +#endif + + /* print warnings about any quirks found */ + if (triton1) + printk(KERN_INFO "bttv: Host bridge needs ETBF enabled.\n"); + if (vsfx) + printk(KERN_INFO "bttv: Host bridge needs VSFX enabled.\n"); + if (pcipci_fail) { + printk(KERN_WARNING "bttv: BT848 and your chipset may not work together.\n"); + if (UNSET == no_overlay) { + printk(KERN_WARNING "bttv: going to disable overlay.\n"); + no_overlay = 1; + } + } + if (UNSET != latency) + printk(KERN_INFO "bttv: pci latency fixup [%d]\n",latency); + + while ((dev = pci_find_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_82441, dev))) { + unsigned char b; + pci_read_config_byte(dev, 0x53, &b); + if (bttv_debug) + printk(KERN_INFO "bttv: Host bridge: 82441FX Natoma, " + "bufcon=0x%02x\n",b); + } +} + +int __devinit bttv_handle_chipset(struct bttv *btv) +{ + unsigned char command; + + if (!triton1 && !vsfx && UNSET == latency) + return 0; + + if (bttv_verbose) { + if (triton1) + printk(KERN_INFO "bttv%d: enabling ETBF (430FX/VP3 compatibilty)\n",btv->c.nr); + if (vsfx && btv->id >= 878) + printk(KERN_INFO "bttv%d: enabling VSFX\n",btv->c.nr); + if (UNSET != latency) + printk(KERN_INFO "bttv%d: setting pci timer to %d\n", + btv->c.nr,latency); + } + + if (btv->id < 878) { + /* bt848 (mis)uses a bit in the irq mask for etbf */ + if (triton1) + btv->triton1 = BT848_INT_ETBF; + } else { + /* bt878 has a bit in the pci config space for it */ + pci_read_config_byte(btv->c.pci, BT878_DEVCTRL, &command); + if (triton1) + command |= BT878_EN_TBFX; + if (vsfx) + command |= BT878_EN_VSFX; + pci_write_config_byte(btv->c.pci, BT878_DEVCTRL, command); + } + if (UNSET != latency) + pci_write_config_byte(btv->c.pci, PCI_LATENCY_TIMER, latency); + return 0; +} + + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/bttv-driver.c b/linux/drivers/media/video/bttv-driver.c new file mode 100644 index 000000000..3641441fd --- /dev/null +++ b/linux/drivers/media/video/bttv-driver.c @@ -0,0 +1,3976 @@ +/* + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler <rjkm@thp.uni-koeln.de> + & Marcus Metzler <mocm@thp.uni-koeln.de> + (c) 1999-2002 Gerd Knorr <kraxel@bytesex.org> + + some v4l2 code lines are taken from Justin's bttv2 driver which is + (c) 2000 Justin Schoeman <justin@suntiger.ee.up.ac.za> + + 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/init.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/kdev_t.h> + +#include <asm/io.h> +#include <asm/byteorder.h> + +#include "bttvp.h" + +unsigned int bttv_num; /* number of Bt848s in use */ +struct bttv bttvs[BTTV_MAX]; + +unsigned int bttv_debug = 0; +unsigned int bttv_verbose = 1; +unsigned int bttv_gpio = 0; + +/* config variables */ +#ifdef __BIG_ENDIAN +static unsigned int bigendian=1; +#else +static unsigned int bigendian=0; +#endif +static unsigned int radio[BTTV_MAX]; +static unsigned int irq_debug = 0; +static unsigned int gbuffers = 8; +static unsigned int gbufsize = 0x208000; + +static int video_nr = -1; +static int radio_nr = -1; +static int vbi_nr = -1; + +static unsigned int fdsr = 0; + +/* options */ +static unsigned int combfilter = 0; +static unsigned int lumafilter = 0; +static unsigned int automute = 1; +static unsigned int chroma_agc = 0; +static unsigned int adc_crush = 1; +static unsigned int whitecrush_upper = 0xCF; +static unsigned int whitecrush_lower = 0x7F; +static unsigned int vcr_hack = 0; +static unsigned int irq_iswitch = 0; + +/* API features (turn on/off stuff for testing) */ +static unsigned int v4l2 = 1; + + +/* insmod args */ +MODULE_PARM(radio,"1-" __stringify(BTTV_MAX) "i"); +MODULE_PARM_DESC(radio,"The TV card supports radio, default is 0 (no)"); +MODULE_PARM(bigendian,"i"); +MODULE_PARM_DESC(bigendian,"byte order of the framebuffer, default is native endian"); +MODULE_PARM(bttv_verbose,"i"); +MODULE_PARM_DESC(bttv_verbose,"verbose startup messages, default is 1 (yes)"); +MODULE_PARM(bttv_gpio,"i"); +MODULE_PARM_DESC(bttv_gpio,"log gpio changes, default is 0 (no)"); +MODULE_PARM(bttv_debug,"i"); +MODULE_PARM_DESC(bttv_debug,"debug messages, default is 0 (no)"); +MODULE_PARM(irq_debug,"i"); +MODULE_PARM_DESC(irq_debug,"irq handler debug messages, default is 0 (no)"); +MODULE_PARM(gbuffers,"i"); +MODULE_PARM_DESC(gbuffers,"number of capture buffers. range 2-32, default 8"); +MODULE_PARM(gbufsize,"i"); +MODULE_PARM_DESC(gbufsize,"size of the capture buffers, default is 0x208000"); + +MODULE_PARM(video_nr,"i"); +MODULE_PARM(radio_nr,"i"); +MODULE_PARM(vbi_nr,"i"); + +MODULE_PARM(fdsr,"i"); + +MODULE_PARM(combfilter,"i"); +MODULE_PARM(lumafilter,"i"); +MODULE_PARM(automute,"i"); +MODULE_PARM_DESC(automute,"mute audio on bad/missing video signal, default is 1 (yes)"); +MODULE_PARM(chroma_agc,"i"); +MODULE_PARM_DESC(chroma_agc,"enables the AGC of chroma signal, default is 0 (no)"); +MODULE_PARM(adc_crush,"i"); +MODULE_PARM_DESC(adc_crush,"enables the luminance ADC crush, default is 1 (yes)"); +MODULE_PARM(whitecrush_upper,"i"); +MODULE_PARM_DESC(whitecrush_upper,"sets the white crush upper value, default is 207"); +MODULE_PARM(whitecrush_lower,"i"); +MODULE_PARM_DESC(whitecrush_lower,"sets the white crush lower value, default is 127"); +MODULE_PARM(vcr_hack,"i"); +MODULE_PARM_DESC(vcr_hack,"enables the VCR hack (improves synch on poor VCR tapes), default is 0 (no)"); +MODULE_PARM(irq_iswitch,"i"); +MODULE_PARM_DESC(irq_iswitch,"switch inputs in irq handler"); + +MODULE_PARM(v4l2,"i"); + +MODULE_DESCRIPTION("bttv - v4l/v4l2 driver module for bt848/878 based cards"); +MODULE_AUTHOR("Ralph Metzler & Marcus Metzler & Gerd Knorr"); +MODULE_LICENSE("GPL"); + +/* kernel args */ +#ifndef MODULE +static int __init p_radio(char *str) { return bttv_parse(str,BTTV_MAX,radio); } +__setup("bttv.radio=", p_radio); +#endif + +/* ----------------------------------------------------------------------- */ +/* sysfs */ + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) +static ssize_t show_card(struct class_device *cd, char *buf) +{ + struct video_device *vfd = to_video_device(cd); + struct bttv *btv = dev_get_drvdata(vfd->dev); + return sprintf(buf, "%d\n", btv ? btv->c.type : UNSET); +} +static CLASS_DEVICE_ATTR(card, S_IRUGO, show_card, NULL); +#endif + +/* ----------------------------------------------------------------------- */ +/* static data */ + +/* special timing tables from conexant... */ +static u8 SRAM_Table[][60] = +{ + /* PAL digital input over GPIO[7:0] */ + { + 45, // 45 bytes following + 0x36,0x11,0x01,0x00,0x90,0x02,0x05,0x10,0x04,0x16, + 0x12,0x05,0x11,0x00,0x04,0x12,0xC0,0x00,0x31,0x00, + 0x06,0x51,0x08,0x03,0x89,0x08,0x07,0xC0,0x44,0x00, + 0x81,0x01,0x01,0xA9,0x0D,0x02,0x02,0x50,0x03,0x37, + 0x37,0x00,0xAF,0x21,0x00 + }, + /* NTSC digital input over GPIO[7:0] */ + { + 51, // 51 bytes following + 0x0C,0xC0,0x00,0x00,0x90,0x02,0x03,0x10,0x03,0x06, + 0x10,0x04,0x12,0x12,0x05,0x02,0x13,0x04,0x19,0x00, + 0x04,0x39,0x00,0x06,0x59,0x08,0x03,0x83,0x08,0x07, + 0x03,0x50,0x00,0xC0,0x40,0x00,0x86,0x01,0x01,0xA6, + 0x0D,0x02,0x03,0x11,0x01,0x05,0x37,0x00,0xAC,0x21, + 0x00, + }, + // TGB_NTSC392 // quartzsight + // This table has been modified to be used for Fusion Rev D + { + 0x2A, // size of table = 42 + 0x06, 0x08, 0x04, 0x0a, 0xc0, 0x00, 0x18, 0x08, 0x03, 0x24, + 0x08, 0x07, 0x02, 0x90, 0x02, 0x08, 0x10, 0x04, 0x0c, 0x10, + 0x05, 0x2c, 0x11, 0x04, 0x55, 0x48, 0x00, 0x05, 0x50, 0x00, + 0xbf, 0x0c, 0x02, 0x2f, 0x3d, 0x00, 0x2f, 0x3f, 0x00, 0xc3, + 0x20, 0x00 + } +}; + +const struct bttv_tvnorm bttv_tvnorms[] = { + /* PAL-BDGHI */ + /* max. active video is actually 922, but 924 is divisible by 4 and 3! */ + /* actually, max active PAL with HSCALE=0 is 948, NTSC is 768 - nil */ + { + .v4l2_id = V4L2_STD_PAL, + .name = "PAL", + .Fsc = 35468950, + .swidth = 924, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 924, + .vdelay = 0x20, + .vbipack = 255, + .sram = 0, + },{ + .v4l2_id = V4L2_STD_NTSC_M, + .name = "NTSC", + .Fsc = 28636363, + .swidth = 768, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_NTSC|BT848_IFORM_XT0), + .scaledtwidth = 910, + .hdelayx1 = 128, + .hactivex1 = 910, + .vdelay = 0x1a, + .vbipack = 144, + .sram = 1, + },{ + .v4l2_id = V4L2_STD_SECAM, + .name = "SECAM", + .Fsc = 35468950, + .swidth = 924, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0xb0, + .iform = (BT848_IFORM_SECAM|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 922, + .vdelay = 0x20, + .vbipack = 255, + .sram = 0, /* like PAL, correct? */ + },{ + .v4l2_id = V4L2_STD_PAL_Nc, + .name = "PAL-Nc", + .Fsc = 28636363, + .swidth = 640, + .sheight = 576, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_PAL_NC|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 130, + .hactivex1 = 734, + .vdelay = 0x1a, + .vbipack = 144, + .sram = -1, + },{ + .v4l2_id = V4L2_STD_PAL_M, + .name = "PAL-M", + .Fsc = 28636363, + .swidth = 640, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_PAL_M|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 135, + .hactivex1 = 754, + .vdelay = 0x1a, + .vbipack = 144, + .sram = -1, + },{ + .v4l2_id = V4L2_STD_PAL_N, + .name = "PAL-N", + .Fsc = 35468950, + .swidth = 768, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_N|BT848_IFORM_XT1), + .scaledtwidth = 944, + .hdelayx1 = 186, + .hactivex1 = 922, + .vdelay = 0x20, + .vbipack = 144, + .sram = -1, + },{ + .v4l2_id = V4L2_STD_NTSC_M_JP, + .name = "NTSC-JP", + .Fsc = 28636363, + .swidth = 640, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_NTSC_J|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 135, + .hactivex1 = 754, + .vdelay = 0x16, + .vbipack = 144, + .sram = -1, + },{ + /* that one hopefully works with the strange timing + * which video recorders produce when playing a NTSC + * tape on a PAL TV ... */ + .v4l2_id = V4L2_STD_PAL_60, + .name = "PAL-60", + .Fsc = 35468950, + .swidth = 924, + .sheight = 480, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 924, + .vdelay = 0x1a, + .vbipack = 255, + .vtotal = 524, + .sram = -1, + } +}; +const unsigned int BTTV_TVNORMS = ARRAY_SIZE(bttv_tvnorms); + +/* ----------------------------------------------------------------------- */ +/* bttv format list + packed pixel formats must come first */ +const struct bttv_format bttv_formats[] = { + { + .name = "8 bpp, gray", + .palette = VIDEO_PALETTE_GREY, + .fourcc = V4L2_PIX_FMT_GREY, + .btformat = BT848_COLOR_FMT_Y8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "8 bpp, dithered color", + .palette = VIDEO_PALETTE_HI240, + .fourcc = V4L2_PIX_FMT_HI240, + .btformat = BT848_COLOR_FMT_RGB8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED | FORMAT_FLAGS_DITHER, + },{ + .name = "15 bpp RGB, le", + .palette = VIDEO_PALETTE_RGB555, + .fourcc = V4L2_PIX_FMT_RGB555, + .btformat = BT848_COLOR_FMT_RGB15, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "15 bpp RGB, be", + .palette = -1, + .fourcc = V4L2_PIX_FMT_RGB555X, + .btformat = BT848_COLOR_FMT_RGB15, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, le", + .palette = VIDEO_PALETTE_RGB565, + .fourcc = V4L2_PIX_FMT_RGB565, + .btformat = BT848_COLOR_FMT_RGB16, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, be", + .palette = -1, + .fourcc = V4L2_PIX_FMT_RGB565X, + .btformat = BT848_COLOR_FMT_RGB16, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "24 bpp RGB, le", + .palette = VIDEO_PALETTE_RGB24, + .fourcc = V4L2_PIX_FMT_BGR24, + .btformat = BT848_COLOR_FMT_RGB24, + .depth = 24, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, le", + .palette = VIDEO_PALETTE_RGB32, + .fourcc = V4L2_PIX_FMT_BGR32, + .btformat = BT848_COLOR_FMT_RGB32, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, be", + .palette = -1, + .fourcc = V4L2_PIX_FMT_RGB32, + .btformat = BT848_COLOR_FMT_RGB32, + .btswap = 0x0f, /* byte+word swap */ + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, YUYV", + .palette = VIDEO_PALETTE_YUV422, + .fourcc = V4L2_PIX_FMT_YUYV, + .btformat = BT848_COLOR_FMT_YUY2, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, YUYV", + .palette = VIDEO_PALETTE_YUYV, + .fourcc = V4L2_PIX_FMT_YUYV, + .btformat = BT848_COLOR_FMT_YUY2, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, UYVY", + .palette = VIDEO_PALETTE_UYVY, + .fourcc = V4L2_PIX_FMT_UYVY, + .btformat = BT848_COLOR_FMT_YUY2, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, planar, Y-Cb-Cr", + .palette = VIDEO_PALETTE_YUV422P, + .fourcc = V4L2_PIX_FMT_YUV422P, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 16, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 1, + .vshift = 0, + },{ + .name = "4:2:0, planar, Y-Cb-Cr", + .palette = VIDEO_PALETTE_YUV420P, + .fourcc = V4L2_PIX_FMT_YUV420, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 1, + .vshift = 1, + },{ + .name = "4:2:0, planar, Y-Cr-Cb", + .palette = -1, + .fourcc = V4L2_PIX_FMT_YVU420, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb, + .hshift = 1, + .vshift = 1, + },{ + .name = "4:1:1, planar, Y-Cb-Cr", + .palette = VIDEO_PALETTE_YUV411P, + .fourcc = V4L2_PIX_FMT_YUV411P, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 2, + .vshift = 0, + },{ + .name = "4:1:0, planar, Y-Cb-Cr", + .palette = VIDEO_PALETTE_YUV410P, + .fourcc = V4L2_PIX_FMT_YUV410, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 9, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 2, + .vshift = 2, + },{ + .name = "4:1:0, planar, Y-Cr-Cb", + .palette = -1, + .fourcc = V4L2_PIX_FMT_YVU410, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 9, + .flags = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb, + .hshift = 2, + .vshift = 2, + },{ + .name = "raw scanlines", + .palette = VIDEO_PALETTE_RAW, + .fourcc = -1, + .btformat = BT848_COLOR_FMT_RAW, + .depth = 8, + .flags = FORMAT_FLAGS_RAW, + } +}; +const unsigned int BTTV_FORMATS = ARRAY_SIZE(bttv_formats); + +/* ----------------------------------------------------------------------- */ + +#define V4L2_CID_PRIVATE_CHROMA_AGC (V4L2_CID_PRIVATE_BASE + 0) +#define V4L2_CID_PRIVATE_COMBFILTER (V4L2_CID_PRIVATE_BASE + 1) +#define V4L2_CID_PRIVATE_AUTOMUTE (V4L2_CID_PRIVATE_BASE + 2) +#define V4L2_CID_PRIVATE_LUMAFILTER (V4L2_CID_PRIVATE_BASE + 3) +#define V4L2_CID_PRIVATE_AGC_CRUSH (V4L2_CID_PRIVATE_BASE + 4) +#define V4L2_CID_PRIVATE_VCR_HACK (V4L2_CID_PRIVATE_BASE + 5) +#define V4L2_CID_PRIVATE_WHITECRUSH_UPPER (V4L2_CID_PRIVATE_BASE + 6) +#define V4L2_CID_PRIVATE_WHITECRUSH_LOWER (V4L2_CID_PRIVATE_BASE + 7) +#define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 8) + +static const struct v4l2_queryctrl no_ctl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; +static const struct v4l2_queryctrl bttv_ctls[] = { + /* --- video --- */ + { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = 0, + .maximum = 65535, + .step = 256, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 65535, + .step = 128, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 65535, + .step = 128, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = 0, + .maximum = 65535, + .step = 256, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + /* --- audio --- */ + { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_AUDIO_VOLUME, + .name = "Volume", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 65535, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_AUDIO_BALANCE, + .name = "Balance", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_AUDIO_BASS, + .name = "Bass", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_AUDIO_TREBLE, + .name = "Treble", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + /* --- private --- */ + { + .id = V4L2_CID_PRIVATE_CHROMA_AGC, + .name = "chroma agc", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_COMBFILTER, + .name = "combfilter", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_AUTOMUTE, + .name = "automute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_LUMAFILTER, + .name = "luma decimation filter", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_AGC_CRUSH, + .name = "agc crush", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_VCR_HACK, + .name = "vcr hack", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_WHITECRUSH_UPPER, + .name = "whitecrush upper", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 0xCF, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_PRIVATE_WHITECRUSH_LOWER, + .name = "whitecrush lower", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 0x7F, + .type = V4L2_CTRL_TYPE_INTEGER, + } + +}; +const int BTTV_CTLS = ARRAY_SIZE(bttv_ctls); + +/* ----------------------------------------------------------------------- */ +/* resource management */ + +static +int check_alloc_btres(struct bttv *btv, struct bttv_fh *fh, int bit) +{ + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + down(&btv->reslock); + if (btv->resources & bit) { + /* no, someone else uses it */ + up(&btv->reslock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + btv->resources |= bit; + up(&btv->reslock); + return 1; +} + +static +int check_btres(struct bttv_fh *fh, int bit) +{ + return (fh->resources & bit); +} + +static +int locked_btres(struct bttv *btv, int bit) +{ + return (btv->resources & bit); +} + +static +void free_btres(struct bttv *btv, struct bttv_fh *fh, int bits) +{ +#if 1 /* DEBUG */ + if ((fh->resources & bits) != bits) { + /* trying to free ressources not allocated by us ... */ + printk("bttv: BUG! (btres)\n"); + } +#endif + down(&btv->reslock); + fh->resources &= ~bits; + btv->resources &= ~bits; + up(&btv->reslock); +} + +/* ----------------------------------------------------------------------- */ +/* If Bt848a or Bt849, use PLL for PAL/SECAM and crystal for NTSC */ + +/* Frequency = (F_input / PLL_X) * PLL_I.PLL_F/PLL_C + PLL_X = Reference pre-divider (0=1, 1=2) + PLL_C = Post divider (0=6, 1=4) + PLL_I = Integer input + PLL_F = Fractional input + + F_input = 28.636363 MHz: + PAL (CLKx2 = 35.46895 MHz): PLL_X = 1, PLL_I = 0x0E, PLL_F = 0xDCF9, PLL_C = 0 +*/ + +static void set_pll_freq(struct bttv *btv, unsigned int fin, unsigned int fout) +{ + unsigned char fl, fh, fi; + + /* prevent overflows */ + fin/=4; + fout/=4; + + fout*=12; + fi=fout/fin; + + fout=(fout%fin)*256; + fh=fout/fin; + + fout=(fout%fin)*256; + fl=fout/fin; + + btwrite(fl, BT848_PLL_F_LO); + btwrite(fh, BT848_PLL_F_HI); + btwrite(fi|BT848_PLL_X, BT848_PLL_XCI); +} + +static void set_pll(struct bttv *btv) +{ + int i; + + if (!btv->pll.pll_crystal) + return; + + if (btv->pll.pll_ofreq == btv->pll.pll_current) { + dprintk("bttv%d: PLL: no change required\n",btv->c.nr); + return; + } + + if (btv->pll.pll_ifreq == btv->pll.pll_ofreq) { + /* no PLL needed */ + if (btv->pll.pll_current == 0) + return; + vprintk(KERN_INFO "bttv%d: PLL can sleep, using XTAL (%d).\n", + btv->c.nr,btv->pll.pll_ifreq); + btwrite(0x00,BT848_TGCTRL); + btwrite(0x00,BT848_PLL_XCI); + btv->pll.pll_current = 0; + return; + } + + vprintk(KERN_INFO "bttv%d: PLL: %d => %d ",btv->c.nr, + btv->pll.pll_ifreq, btv->pll.pll_ofreq); + set_pll_freq(btv, btv->pll.pll_ifreq, btv->pll.pll_ofreq); + + for (i=0; i<10; i++) { + /* Let other people run while the PLL stabilizes */ + vprintk("."); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/50); + + if (btread(BT848_DSTATUS) & BT848_DSTATUS_PLOCK) { + btwrite(0,BT848_DSTATUS); + } else { + btwrite(0x08,BT848_TGCTRL); + btv->pll.pll_current = btv->pll.pll_ofreq; + vprintk(" ok\n"); + return; + } + } + btv->pll.pll_current = -1; + vprintk("failed\n"); + return; +} + +/* used to switch between the bt848's analog/digital video capture modes */ +void bt848A_set_timing(struct bttv *btv) +{ + int i, len; + int table_idx = bttv_tvnorms[btv->tvnorm].sram; + int fsc = bttv_tvnorms[btv->tvnorm].Fsc; + + if (UNSET == bttv_tvcards[btv->c.type].muxsel[btv->input]) { + dprintk("bttv%d: load digital timing table (table_idx=%d)\n", + btv->c.nr,table_idx); + + /* timing change...reset timing generator address */ + btwrite(0x00, BT848_TGCTRL); + btwrite(0x02, BT848_TGCTRL); + btwrite(0x00, BT848_TGCTRL); + + len=SRAM_Table[table_idx][0]; + for(i = 1; i <= len; i++) + btwrite(SRAM_Table[table_idx][i],BT848_TGLB); + btv->pll.pll_ofreq = 27000000; + + set_pll(btv); + btwrite(0x11, BT848_TGCTRL); + btwrite(0x41, BT848_DVSIF); + } else { + btv->pll.pll_ofreq = fsc; + set_pll(btv); + btwrite(0x0, BT848_DVSIF); + } +} + +/* ----------------------------------------------------------------------- */ + +static void bt848_bright(struct bttv *btv, int bright) +{ + int value; + + btv->bright = bright; + + /* We want -128 to 127 we get 0-65535 */ + value = (bright >> 8) - 128; + btwrite(value & 0xff, BT848_BRIGHT); +} + +static void bt848_hue(struct bttv *btv, int hue) +{ + int value; + + btv->hue = hue; + + /* -128 to 127 */ + value = (hue >> 8) - 128; + btwrite(value & 0xff, BT848_HUE); +} + +static void bt848_contrast(struct bttv *btv, int cont) +{ + int value,hibit; + + btv->contrast = cont; + + /* 0-511 */ + value = (cont >> 7); + hibit = (value >> 6) & 4; + btwrite(value & 0xff, BT848_CONTRAST_LO); + btaor(hibit, ~4, BT848_E_CONTROL); + btaor(hibit, ~4, BT848_O_CONTROL); +} + +static void bt848_sat(struct bttv *btv, int color) +{ + int val_u,val_v,hibits; + + btv->saturation = color; + + /* 0-511 for the color */ + val_u = color >> 7; + val_v = ((color>>7)*180L)/254; + hibits = (val_u >> 7) & 2; + hibits |= (val_v >> 8) & 1; + btwrite(val_u & 0xff, BT848_SAT_U_LO); + btwrite(val_v & 0xff, BT848_SAT_V_LO); + btaor(hibits, ~3, BT848_E_CONTROL); + btaor(hibits, ~3, BT848_O_CONTROL); +} + +/* ----------------------------------------------------------------------- */ + +static int +video_mux(struct bttv *btv, unsigned int input) +{ + int mux,mask2; + + if (input >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + + /* needed by RemoteVideo MX */ + mask2 = bttv_tvcards[btv->c.type].gpiomask2; + if (mask2) + gpio_inout(mask2,mask2); + + if (input == btv->svhs) { + btor(BT848_CONTROL_COMP, BT848_E_CONTROL); + btor(BT848_CONTROL_COMP, BT848_O_CONTROL); + } else { + btand(~BT848_CONTROL_COMP, BT848_E_CONTROL); + btand(~BT848_CONTROL_COMP, BT848_O_CONTROL); + } + mux = bttv_tvcards[btv->c.type].muxsel[input] & 3; + btaor(mux<<5, ~(3<<5), BT848_IFORM); + dprintk(KERN_DEBUG "bttv%d: video mux: input=%d mux=%d\n", + btv->c.nr,input,mux); + + /* card specific hook */ + if(bttv_tvcards[btv->c.type].muxsel_hook) + bttv_tvcards[btv->c.type].muxsel_hook (btv, input); + return 0; +} + +static char *audio_modes[] = { + "audio: tuner", "audio: radio", "audio: extern", + "audio: intern", "audio: off" +}; + +static int +audio_mux(struct bttv *btv, int mode) +{ + int val,mux,i2c_mux,signal; + + gpio_inout(bttv_tvcards[btv->c.type].gpiomask, + bttv_tvcards[btv->c.type].gpiomask); + signal = btread(BT848_DSTATUS) & BT848_DSTATUS_HLOC; + + switch (mode) { + case AUDIO_MUTE: + btv->audio |= AUDIO_MUTE; + break; + case AUDIO_UNMUTE: + btv->audio &= ~AUDIO_MUTE; + break; + case AUDIO_TUNER: + case AUDIO_RADIO: + case AUDIO_EXTERN: + case AUDIO_INTERN: + btv->audio &= AUDIO_MUTE; + btv->audio |= mode; + } + i2c_mux = mux = (btv->audio & AUDIO_MUTE) ? AUDIO_OFF : btv->audio; + if (btv->opt_automute && !signal && !btv->radio_user) + mux = AUDIO_OFF; +#if 0 + printk("bttv%d: amux: mode=%d audio=%d signal=%s mux=%d/%d irq=%s\n", + btv->c.nr, mode, btv->audio, signal ? "yes" : "no", + mux, i2c_mux, in_interrupt() ? "yes" : "no"); +#endif + + val = bttv_tvcards[btv->c.type].audiomux[mux]; + gpio_bits(bttv_tvcards[btv->c.type].gpiomask,val); + if (bttv_gpio) + bttv_gpio_tracking(btv,audio_modes[mux]); + if (!in_interrupt()) + bttv_call_i2c_clients(btv,AUDC_SET_INPUT,&(i2c_mux)); + return 0; +} + +static void +i2c_vidiocschan(struct bttv *btv) +{ + struct video_channel c; + + memset(&c,0,sizeof(c)); + c.norm = btv->tvnorm; + c.channel = btv->input; + bttv_call_i2c_clients(btv,VIDIOCSCHAN,&c); + if (btv->c.type == BTTV_VOODOOTV_FM) + bttv_tda9880_setnorm(btv,c.norm); +} + +static int +set_tvnorm(struct bttv *btv, unsigned int norm) +{ + const struct bttv_tvnorm *tvnorm; + + if (norm < 0 || norm >= BTTV_TVNORMS) + return -EINVAL; + + btv->tvnorm = norm; + tvnorm = &bttv_tvnorms[norm]; + + btwrite(tvnorm->adelay, BT848_ADELAY); + btwrite(tvnorm->bdelay, BT848_BDELAY); + btaor(tvnorm->iform,~(BT848_IFORM_NORM|BT848_IFORM_XTBOTH), + BT848_IFORM); + btwrite(tvnorm->vbipack, BT848_VBI_PACK_SIZE); + btwrite(1, BT848_VBI_PACK_DEL); + bt848A_set_timing(btv); + + switch (btv->c.type) { + case BTTV_VOODOOTV_FM: + bttv_tda9880_setnorm(btv,norm); + break; +#if 0 + case BTTV_OSPREY540: + osprey_540_set_norm(btv,norm); + break; +#endif + } + return 0; +} + +static void +set_input(struct bttv *btv, unsigned int input) +{ + unsigned long flags; + + btv->input = input; + if (irq_iswitch) { + spin_lock_irqsave(&btv->s_lock,flags); + if (btv->curr.irqflags) { + /* active capture -> delayed input switch */ + btv->new_input = input; + } else { + video_mux(btv,input); + } + spin_unlock_irqrestore(&btv->s_lock,flags); + } else { + video_mux(btv,input); + } + audio_mux(btv,(input == bttv_tvcards[btv->c.type].tuner ? + AUDIO_TUNER : AUDIO_EXTERN)); + set_tvnorm(btv,btv->tvnorm); +} + +static void init_irqreg(struct bttv *btv) +{ + /* clear status */ + btwrite(0xfffffUL, BT848_INT_STAT); + + if (bttv_tvcards[btv->c.type].no_video) { + /* i2c only */ + btwrite(BT848_INT_I2CDONE, + BT848_INT_MASK); + } else { + /* full video */ + btwrite((btv->triton1) | + (btv->gpioirq ? BT848_INT_GPINT : 0) | + BT848_INT_SCERR | + (fdsr ? BT848_INT_FDSR : 0) | + BT848_INT_RISCI|BT848_INT_OCERR|BT848_INT_VPRES| + BT848_INT_FMTCHG|BT848_INT_HLOCK| + BT848_INT_I2CDONE, + BT848_INT_MASK); + } +} + +static void init_bt848(struct bttv *btv) +{ + int val; + + if (bttv_tvcards[btv->c.type].no_video) { + /* very basic init only */ + init_irqreg(btv); + return; + } + + btwrite(0x00, BT848_CAP_CTL); + btwrite(BT848_COLOR_CTL_GAMMA, BT848_COLOR_CTL); + btwrite(BT848_IFORM_XTAUTO | BT848_IFORM_AUTO, BT848_IFORM); + + /* set planar and packed mode trigger points and */ + /* set rising edge of inverted GPINTR pin as irq trigger */ + btwrite(BT848_GPIO_DMA_CTL_PKTP_32| + BT848_GPIO_DMA_CTL_PLTP1_16| + BT848_GPIO_DMA_CTL_PLTP23_16| + BT848_GPIO_DMA_CTL_GPINTC| + BT848_GPIO_DMA_CTL_GPINTI, + BT848_GPIO_DMA_CTL); + + val = btv->opt_chroma_agc ? BT848_SCLOOP_CAGC : 0; + btwrite(val, BT848_E_SCLOOP); + btwrite(val, BT848_O_SCLOOP); + + btwrite(0x20, BT848_E_VSCALE_HI); + btwrite(0x20, BT848_O_VSCALE_HI); + btwrite(BT848_ADC_RESERVED | (btv->opt_adc_crush ? BT848_ADC_CRUSH : 0), + BT848_ADC); + + btwrite(whitecrush_upper, BT848_WC_UP); + btwrite(whitecrush_lower, BT848_WC_DOWN); + + if (btv->opt_lumafilter) { + btwrite(0, BT848_E_CONTROL); + btwrite(0, BT848_O_CONTROL); + } else { + btwrite(BT848_CONTROL_LDEC, BT848_E_CONTROL); + btwrite(BT848_CONTROL_LDEC, BT848_O_CONTROL); + } + + /* interrupt */ + init_irqreg(btv); +} + +extern void bttv_reinit_bt848(struct bttv *btv) +{ + unsigned long flags; + + if (bttv_verbose) + printk(KERN_INFO "bttv%d: reset, reinitialize\n",btv->c.nr); + spin_lock_irqsave(&btv->s_lock,flags); + btv->errors=0; + bttv_set_dma(btv,0,0); + spin_unlock_irqrestore(&btv->s_lock,flags); + + init_bt848(btv); + btv->pll.pll_current = -1; + set_input(btv,btv->input); +} + +static int get_control(struct bttv *btv, struct v4l2_control *c) +{ + struct video_audio va; + int i; + + for (i = 0; i < BTTV_CTLS; i++) + if (bttv_ctls[i].id == c->id) + break; + if (i == BTTV_CTLS) + return -EINVAL; + if (i >= 4 && i <= 8) { + memset(&va,0,sizeof(va)); + bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,0); + } + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->value = btv->bright; + break; + case V4L2_CID_HUE: + c->value = btv->hue; + break; + case V4L2_CID_CONTRAST: + c->value = btv->contrast; + break; + case V4L2_CID_SATURATION: + c->value = btv->saturation; + break; + + case V4L2_CID_AUDIO_MUTE: + c->value = (VIDEO_AUDIO_MUTE & va.flags) ? 1 : 0; + break; + case V4L2_CID_AUDIO_VOLUME: + c->value = va.volume; + break; + case V4L2_CID_AUDIO_BALANCE: + c->value = va.balance; + break; + case V4L2_CID_AUDIO_BASS: + c->value = va.bass; + break; + case V4L2_CID_AUDIO_TREBLE: + c->value = va.treble; + break; + + case V4L2_CID_PRIVATE_CHROMA_AGC: + c->value = btv->opt_chroma_agc; + break; + case V4L2_CID_PRIVATE_COMBFILTER: + c->value = btv->opt_combfilter; + break; + case V4L2_CID_PRIVATE_LUMAFILTER: + c->value = btv->opt_lumafilter; + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + c->value = btv->opt_automute; + break; + case V4L2_CID_PRIVATE_AGC_CRUSH: + c->value = btv->opt_adc_crush; + break; + case V4L2_CID_PRIVATE_VCR_HACK: + c->value = btv->opt_vcr_hack; + break; + case V4L2_CID_PRIVATE_WHITECRUSH_UPPER: + c->value = btv->opt_whitecrush_upper; + break; + case V4L2_CID_PRIVATE_WHITECRUSH_LOWER: + c->value = btv->opt_whitecrush_lower; + break; + default: + return -EINVAL; + } + return 0; +} + +static int set_control(struct bttv *btv, struct v4l2_control *c) +{ + struct video_audio va; + int i,val; + + for (i = 0; i < BTTV_CTLS; i++) + if (bttv_ctls[i].id == c->id) + break; + if (i == BTTV_CTLS) + return -EINVAL; + if (i >= 4 && i <= 8) { + memset(&va,0,sizeof(va)); + bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,0); + } + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + bt848_bright(btv,c->value); + break; + case V4L2_CID_HUE: + bt848_hue(btv,c->value); + break; + case V4L2_CID_CONTRAST: + bt848_contrast(btv,c->value); + break; + case V4L2_CID_SATURATION: + bt848_sat(btv,c->value); + break; + case V4L2_CID_AUDIO_MUTE: + if (c->value) { + va.flags |= VIDEO_AUDIO_MUTE; + audio_mux(btv, AUDIO_MUTE); + } else { + va.flags &= ~VIDEO_AUDIO_MUTE; + audio_mux(btv, AUDIO_UNMUTE); + } + break; + + case V4L2_CID_AUDIO_VOLUME: + va.volume = c->value; + break; + case V4L2_CID_AUDIO_BALANCE: + va.balance = c->value; + break; + case V4L2_CID_AUDIO_BASS: + va.bass = c->value; + break; + case V4L2_CID_AUDIO_TREBLE: + va.treble = c->value; + break; + + case V4L2_CID_PRIVATE_CHROMA_AGC: + btv->opt_chroma_agc = c->value; + val = btv->opt_chroma_agc ? BT848_SCLOOP_CAGC : 0; + btwrite(val, BT848_E_SCLOOP); + btwrite(val, BT848_O_SCLOOP); + break; + case V4L2_CID_PRIVATE_COMBFILTER: + btv->opt_combfilter = c->value; + break; + case V4L2_CID_PRIVATE_LUMAFILTER: + btv->opt_lumafilter = c->value; + if (btv->opt_lumafilter) { + btand(~BT848_CONTROL_LDEC, BT848_E_CONTROL); + btand(~BT848_CONTROL_LDEC, BT848_O_CONTROL); + } else { + btor(BT848_CONTROL_LDEC, BT848_E_CONTROL); + btor(BT848_CONTROL_LDEC, BT848_O_CONTROL); + } + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + btv->opt_automute = c->value; + break; + case V4L2_CID_PRIVATE_AGC_CRUSH: + btv->opt_adc_crush = c->value; + btwrite(BT848_ADC_RESERVED | (btv->opt_adc_crush ? BT848_ADC_CRUSH : 0), + BT848_ADC); + break; + case V4L2_CID_PRIVATE_VCR_HACK: + btv->opt_vcr_hack = c->value; + break; + case V4L2_CID_PRIVATE_WHITECRUSH_UPPER: + btv->opt_whitecrush_upper = c->value; + btwrite(c->value, BT848_WC_UP); + break; + case V4L2_CID_PRIVATE_WHITECRUSH_LOWER: + btv->opt_whitecrush_lower = c->value; + btwrite(c->value, BT848_WC_DOWN); + break; + default: + return -EINVAL; + } + if (i >= 4 && i <= 8) { + bttv_call_i2c_clients(btv, VIDIOCSAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,1); + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +void bttv_gpio_tracking(struct bttv *btv, char *comment) +{ + unsigned int outbits, data; + outbits = btread(BT848_GPIO_OUT_EN); + data = btread(BT848_GPIO_DATA); + printk(KERN_DEBUG "bttv%d: gpio: en=%08x, out=%08x in=%08x [%s]\n", + btv->c.nr,outbits,data & outbits, data & ~outbits, comment); +} + +void bttv_field_count(struct bttv *btv) +{ + int need_count = 0; + + if (btv->users) + need_count++; + + if (need_count) { + /* start field counter */ + btor(BT848_INT_VSYNC,BT848_INT_MASK); + } else { + /* stop field counter */ + btand(~BT848_INT_VSYNC,BT848_INT_MASK); + btv->field_count = 0; + } +} + +static const struct bttv_format* +format_by_palette(int palette) +{ + unsigned int i; + + for (i = 0; i < BTTV_FORMATS; i++) { + if (-1 == bttv_formats[i].palette) + continue; + if (bttv_formats[i].palette == palette) + return bttv_formats+i; + } + return NULL; +} + +static const struct bttv_format* +format_by_fourcc(int fourcc) +{ + unsigned int i; + + for (i = 0; i < BTTV_FORMATS; i++) { + if (-1 == bttv_formats[i].fourcc) + continue; + if (bttv_formats[i].fourcc == fourcc) + return bttv_formats+i; + } + return NULL; +} + +/* ----------------------------------------------------------------------- */ +/* misc helpers */ + +static int +bttv_switch_overlay(struct bttv *btv, struct bttv_fh *fh, + struct bttv_buffer *new) +{ + struct bttv_buffer *old; + unsigned long flags; + int retval = 0; + + dprintk("switch_overlay: enter [new=%p]\n",new); + if (new) + new->vb.state = STATE_DONE; + spin_lock_irqsave(&btv->s_lock,flags); + old = btv->screen; + btv->screen = new; + bttv_set_dma(btv, 0x03, 1); + spin_unlock_irqrestore(&btv->s_lock,flags); + if (NULL == new) + free_btres(btv,fh,RESOURCE_OVERLAY); + if (NULL != old) { + dprintk("switch_overlay: old=%p state is %d\n",old,old->vb.state); + bttv_dma_free(btv, old); + kfree(old); + } + dprintk("switch_overlay: done\n"); + return retval; +} + +/* ----------------------------------------------------------------------- */ +/* video4linux (1) interface */ + +static int bttv_prepare_buffer(struct bttv *btv, struct bttv_buffer *buf, + const struct bttv_format *fmt, + unsigned int width, unsigned int height, + enum v4l2_field field) +{ + int redo_dma_risc = 0; + int rc; + + /* check settings */ + if (NULL == fmt) + return -EINVAL; + if (fmt->btformat == BT848_COLOR_FMT_RAW) { + width = RAW_BPL; + height = RAW_LINES*2; + if (width*height > buf->vb.bsize) + return -EINVAL; + buf->vb.size = buf->vb.bsize; + } else { + if (width < 48 || + height < 32 || + width > bttv_tvnorms[btv->tvnorm].swidth || + height > bttv_tvnorms[btv->tvnorm].sheight) + return -EINVAL; + buf->vb.size = (width * height * fmt->depth) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + } + + /* alloc + fill struct bttv_buffer (if changed) */ + if (buf->vb.width != width || buf->vb.height != height || + buf->vb.field != field || + buf->tvnorm != btv->tvnorm || buf->fmt != fmt) { + buf->vb.width = width; + buf->vb.height = height; + buf->vb.field = field; + buf->tvnorm = btv->tvnorm; + buf->fmt = fmt; + redo_dma_risc = 1; + } + + /* alloc risc memory */ + if (STATE_NEEDS_INIT == buf->vb.state) { + redo_dma_risc = 1; + if (0 != (rc = videobuf_iolock(btv->c.pci,&buf->vb,&btv->fbuf))) + goto fail; + } + + if (redo_dma_risc) + if (0 != (rc = bttv_buffer_risc(btv,buf))) + goto fail; + + buf->vb.state = STATE_PREPARED; + return 0; + + fail: + bttv_dma_free(btv,buf); + return rc; +} + +static int +buffer_setup(struct file *file, unsigned int *count, unsigned int *size) +{ + struct bttv_fh *fh = file->private_data; + + *size = fh->fmt->depth*fh->width*fh->height >> 3; + if (0 == *count) + *count = gbuffers; + while (*size * *count > gbuffers * gbufsize) + (*count)--; + return 0; +} + +static int +buffer_prepare(struct file *file, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct bttv_buffer *buf = (struct bttv_buffer*)vb; + struct bttv_fh *fh = file->private_data; + + return bttv_prepare_buffer(fh->btv, buf, fh->fmt, + fh->width, fh->height, field); +} + +static void +buffer_queue(struct file *file, struct videobuf_buffer *vb) +{ + struct bttv_buffer *buf = (struct bttv_buffer*)vb; + struct bttv_fh *fh = file->private_data; + + buf->vb.state = STATE_QUEUED; + list_add_tail(&buf->vb.queue,&fh->btv->capture); + bttv_set_dma(fh->btv, 0x03, 1); +} + +static void buffer_release(struct file *file, struct videobuf_buffer *vb) +{ + struct bttv_buffer *buf = (struct bttv_buffer*)vb; + struct bttv_fh *fh = file->private_data; + + bttv_dma_free(fh->btv,buf); +} + +static struct videobuf_queue_ops bttv_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +static const char *v4l1_ioctls[] = { + "?", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", "GPICT", "SPICT", + "CCAPTURE", "GWIN", "SWIN", "GFBUF", "SFBUF", "KEY", "GFREQ", + "SFREQ", "GAUDIO", "SAUDIO", "SYNC", "MCAPTURE", "GMBUF", "GUNIT", + "GCAPTURE", "SCAPTURE", "SPLAYMODE", "SWRITEMODE", "GPLAYINFO", + "SMICROCODE", "GVBIFMT", "SVBIFMT" }; +#define V4L1_IOCTLS ARRAY_SIZE(v4l1_ioctls) + +int bttv_common_ioctls(struct bttv *btv, unsigned int cmd, void *arg) +{ + switch (cmd) { + case BTTV_VERSION: + return BTTV_VERSION_CODE; + + /* *** v4l1 *** ************************************************ */ + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = btv->freq; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + down(&btv->lock); + btv->freq=*freq; + bttv_call_i2c_clients(btv,VIDIOCSFREQ,freq); + if (btv->has_matchbox && btv->radio_user) + tea5757_set_freq(btv,*freq); + up(&btv->lock); + return 0; + } + + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + + if (UNSET == bttv_tvcards[btv->c.type].tuner) + return -EINVAL; + if (v->tuner) /* Only tuner 0 */ + return -EINVAL; + strcpy(v->name, "Television"); + v->rangelow = 0; + v->rangehigh = 0x7FFFFFFF; + v->flags = VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM; + v->mode = btv->tvnorm; + v->signal = (btread(BT848_DSTATUS)&BT848_DSTATUS_HLOC) ? 0xFFFF : 0; + bttv_call_i2c_clients(btv,cmd,v); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + + if (v->tuner) /* Only tuner 0 */ + return -EINVAL; + if (v->mode >= BTTV_TVNORMS) + return -EINVAL; + + down(&btv->lock); + set_tvnorm(btv,v->mode); + bttv_call_i2c_clients(btv,cmd,v); + up(&btv->lock); + return 0; + } + + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + unsigned int channel = v->channel; + + if (channel >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + v->tuners=0; + v->flags = VIDEO_VC_AUDIO; + v->type = VIDEO_TYPE_CAMERA; + v->norm = btv->tvnorm; + if (channel == bttv_tvcards[btv->c.type].tuner) { + strcpy(v->name,"Television"); + v->flags|=VIDEO_VC_TUNER; + v->type=VIDEO_TYPE_TV; + v->tuners=1; + } else if (channel == btv->svhs) { + strcpy(v->name,"S-Video"); + } else { + sprintf(v->name,"Composite%d",channel); + } + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + unsigned int channel = v->channel; + + if (channel >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + if (v->norm >= BTTV_TVNORMS) + return -EINVAL; + + down(&btv->lock); + if (channel == btv->input && + v->norm == btv->tvnorm) { + /* nothing to do */ + up(&btv->lock); + return 0; + } + + btv->tvnorm = v->norm; + set_input(btv,v->channel); + up(&btv->lock); + return 0; + } + + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + + memset(v,0,sizeof(*v)); + strcpy(v->name,"Television"); + v->flags |= VIDEO_AUDIO_MUTABLE; + v->mode = VIDEO_SOUND_MONO; + + down(&btv->lock); + bttv_call_i2c_clients(btv,cmd,v); + + /* card specific hooks */ + if (btv->audio_hook) + btv->audio_hook(btv,v,0); + + up(&btv->lock); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + unsigned int audio = v->audio; + + if (audio >= bttv_tvcards[btv->c.type].audio_inputs) + return -EINVAL; + + down(&btv->lock); + audio_mux(btv, (v->flags&VIDEO_AUDIO_MUTE) ? AUDIO_MUTE : AUDIO_UNMUTE); + bttv_call_i2c_clients(btv,cmd,v); + + /* card specific hooks */ + if (btv->audio_hook) + btv->audio_hook(btv,v,1); + + up(&btv->lock); + return 0; + } + + /* *** v4l2 *** ************************************************ */ + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *e = arg; + unsigned int index = e->index; + + if (index >= BTTV_TVNORMS) + return -EINVAL; + v4l2_video_std_construct(e, bttv_tvnorms[e->index].v4l2_id, + bttv_tvnorms[e->index].name); + e->index = index; + return 0; + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + *id = bttv_tvnorms[btv->tvnorm].v4l2_id; + return 0; + } + case VIDIOC_S_STD: + { + v4l2_std_id *id = arg; + unsigned int i; + + for (i = 0; i < BTTV_TVNORMS; i++) + if (*id & bttv_tvnorms[i].v4l2_id) + break; + if (i == BTTV_TVNORMS) + return -EINVAL; + + down(&btv->lock); + set_tvnorm(btv,i); + i2c_vidiocschan(btv); + up(&btv->lock); + return 0; + } + case VIDIOC_QUERYSTD: + { + v4l2_std_id *id = arg; + + if (btread(BT848_DSTATUS) & BT848_DSTATUS_NUML) + *id = V4L2_STD_625_50; + else + *id = V4L2_STD_525_60; + return 0; + } + + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + unsigned int n; + + n = i->index; + if (n >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + memset(i,0,sizeof(*i)); + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + i->audioset = 1; + if (i->index == bttv_tvcards[btv->c.type].tuner) { + sprintf(i->name, "Television"); + i->type = V4L2_INPUT_TYPE_TUNER; + i->tuner = 0; + } else if (i->index == btv->svhs) { + sprintf(i->name, "S-Video"); + } else { + sprintf(i->name,"Composite%d",i->index); + } + if (i->index == btv->input) { + __u32 dstatus = btread(BT848_DSTATUS); + if (0 == (dstatus & BT848_DSTATUS_PRES)) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (0 == (dstatus & BT848_DSTATUS_HLOC)) + i->status |= V4L2_IN_ST_NO_H_LOCK; + } + for (n = 0; n < BTTV_TVNORMS; n++) + i->std |= bttv_tvnorms[n].v4l2_id; + return 0; + } + case VIDIOC_G_INPUT: + { + int *i = arg; + *i = btv->input; + return 0; + } + case VIDIOC_S_INPUT: + { + unsigned int *i = arg; + + if (*i > bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + down(&btv->lock); + set_input(btv,*i); + i2c_vidiocschan(btv); + up(&btv->lock); + return 0; + } + + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + + if (UNSET == bttv_tvcards[btv->c.type].tuner) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + down(&btv->lock); + memset(t,0,sizeof(*t)); + strcpy(t->name, "Television"); + t->type = V4L2_TUNER_ANALOG_TV; + t->rangehigh = 0xffffffffUL; + t->capability = V4L2_TUNER_CAP_NORM; + t->rxsubchans = V4L2_TUNER_SUB_MONO; + if (btread(BT848_DSTATUS)&BT848_DSTATUS_HLOC) + t->signal = 0xffff; + { + /* Hmmm ... */ + struct video_audio va; + memset(&va, 0, sizeof(struct video_audio)); + bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,0); + if(va.mode & VIDEO_SOUND_STEREO) { + t->audmode = V4L2_TUNER_MODE_STEREO; + t->rxsubchans |= V4L2_TUNER_SUB_STEREO; + } + if(va.mode & VIDEO_SOUND_LANG1) { + t->audmode = V4L2_TUNER_MODE_LANG1; + t->rxsubchans = V4L2_TUNER_SUB_LANG1 + | V4L2_TUNER_SUB_LANG2; + } + } + /* FIXME: fill capability+audmode */ + up(&btv->lock); + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *t = arg; + + if (UNSET == bttv_tvcards[btv->c.type].tuner) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + down(&btv->lock); + { + struct video_audio va; + memset(&va, 0, sizeof(struct video_audio)); + bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va); + if (t->audmode == V4L2_TUNER_MODE_MONO) + va.mode = VIDEO_SOUND_MONO; + else if (t->audmode == V4L2_TUNER_MODE_STEREO) + va.mode = VIDEO_SOUND_STEREO; + else if (t->audmode == V4L2_TUNER_MODE_LANG1) + va.mode = VIDEO_SOUND_LANG1; + else if (t->audmode == V4L2_TUNER_MODE_LANG2) + va.mode = VIDEO_SOUND_LANG2; + bttv_call_i2c_clients(btv, VIDIOCSAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,1); + } + up(&btv->lock); + return 0; + } + + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + memset(f,0,sizeof(*f)); + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = btv->freq; + return 0; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if (unlikely(f->tuner != 0)) + return -EINVAL; + if (unlikely(f->type != V4L2_TUNER_ANALOG_TV)) + return -EINVAL; + down(&btv->lock); + btv->freq = f->frequency; + bttv_call_i2c_clients(btv,VIDIOCSFREQ,&btv->freq); + if (btv->has_matchbox && btv->radio_user) + tea5757_set_freq(btv,btv->freq); + up(&btv->lock); + return 0; + } + + default: + return -ENOIOCTLCMD; + + } + return 0; +} + +static int verify_window(const struct bttv_tvnorm *tvn, + struct v4l2_window *win, int fixup) +{ + enum v4l2_field field; + int maxw, maxh; + + if (win->w.width < 48 || win->w.height < 32) + return -EINVAL; + if (win->clipcount > 2048) + return -EINVAL; + + field = win->field; + maxw = tvn->swidth; + maxh = tvn->sheight; + + if (V4L2_FIELD_ANY == field) { + field = (win->w.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + if (!fixup && (win->w.width > maxw || win->w.height > maxh)) + return -EINVAL; + + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + win->field = field; + return 0; +} + +static int setup_window(struct bttv_fh *fh, struct bttv *btv, + struct v4l2_window *win, int fixup) +{ + struct v4l2_clip *clips = NULL; + int n,size,retval = 0; + + if (NULL == fh->ovfmt) + return -EINVAL; + retval = verify_window(&bttv_tvnorms[btv->tvnorm],win,fixup); + if (0 != retval) + return retval; + + /* copy clips -- luckily v4l1 + v4l2 are binary + compatible here ...*/ + n = win->clipcount; + size = sizeof(*clips)*(n+4); + clips = kmalloc(size,GFP_KERNEL); + if (NULL == clips) + return -ENOMEM; + if (n > 0) { + if (copy_from_user(clips,win->clips,sizeof(struct v4l2_clip)*n)) { + kfree(clips); + return -EFAULT; + } + } + /* clip against screen */ + if (NULL != btv->fbuf.base) + n = btcx_screen_clips(btv->fbuf.fmt.width, btv->fbuf.fmt.height, + &win->w, clips, n); + btcx_sort_clips(clips,n); + + /* 4-byte alignments */ + switch (fh->ovfmt->depth) { + case 8: + case 24: + btcx_align(&win->w, clips, n, 3); + break; + case 16: + btcx_align(&win->w, clips, n, 1); + break; + case 32: + /* no alignment fixups needed */ + break; + default: + BUG(); + } + + down(&fh->cap.lock); + if (fh->ov.clips) + kfree(fh->ov.clips); + fh->ov.clips = clips; + fh->ov.nclips = n; + + fh->ov.w = win->w; + fh->ov.field = win->field; + fh->ov.setup_ok = 1; + btv->init.ov.w.width = win->w.width; + btv->init.ov.w.height = win->w.height; + btv->init.ov.field = win->field; + + /* update overlay if needed */ + retval = 0; + if (check_btres(fh, RESOURCE_OVERLAY)) { + struct bttv_buffer *new; + + new = videobuf_alloc(sizeof(*new)); + bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new); + retval = bttv_switch_overlay(btv,fh,new); + } + up(&fh->cap.lock); + return retval; +} + +/* ----------------------------------------------------------------------- */ + +static struct videobuf_queue* bttv_queue(struct bttv_fh *fh) +{ + struct videobuf_queue* q = NULL; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + q = &fh->cap; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + q = &fh->vbi; + break; + default: + BUG(); + } + return q; +} + +static int bttv_resource(struct bttv_fh *fh) +{ + int res = 0; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + res = RESOURCE_VIDEO; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + res = RESOURCE_VBI; + break; + default: + BUG(); + } + return res; +} + +static int bttv_switch_type(struct bttv_fh *fh, enum v4l2_buf_type type) +{ + struct videobuf_queue *q = bttv_queue(fh); + int res = bttv_resource(fh); + + if (check_btres(fh,res)) + return -EBUSY; + if (videobuf_queue_is_busy(q)) + return -EBUSY; + fh->type = type; + return 0; +} + +static int bttv_g_fmt(struct bttv_fh *fh, struct v4l2_format *f) +{ + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memset(&f->fmt.pix,0,sizeof(struct v4l2_pix_format)); + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->cap.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + memset(&f->fmt.win,0,sizeof(struct v4l2_window)); + f->fmt.win.w = fh->ov.w; + f->fmt.win.field = fh->ov.field; + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + bttv_vbi_get_fmt(fh,f); + return 0; + default: + return -EINVAL; + } +} + +static int bttv_try_fmt(struct bttv_fh *fh, struct bttv *btv, + struct v4l2_format *f) +{ + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + { + const struct bttv_format *fmt; + enum v4l2_field field; + unsigned int maxw,maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + /* fixup format */ + maxw = bttv_tvnorms[btv->tvnorm].swidth; + maxh = bttv_tvnorms[btv->tvnorm].sheight; + field = f->fmt.pix.field; + if (V4L2_FIELD_ANY == field) + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + if (V4L2_FIELD_SEQ_BT == field) + field = V4L2_FIELD_SEQ_TB; + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_ALTERNATE: + maxh = maxh/2; + break; + case V4L2_FIELD_INTERLACED: + break; + case V4L2_FIELD_SEQ_TB: + if (fmt->flags & FORMAT_FLAGS_PLANAR) + return -EINVAL; + break; + default: + return -EINVAL; + } + + /* update data for the application */ + f->fmt.pix.field = field; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + return verify_window(&bttv_tvnorms[btv->tvnorm], + &f->fmt.win, 1); + case V4L2_BUF_TYPE_VBI_CAPTURE: + bttv_vbi_try_fmt(fh,f); + return 0; + default: + return -EINVAL; + } +} + +static int bttv_s_fmt(struct bttv_fh *fh, struct bttv *btv, + struct v4l2_format *f) +{ + int retval; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + { + const struct bttv_format *fmt; + + retval = bttv_switch_type(fh,f->type); + if (0 != retval) + return retval; + retval = bttv_try_fmt(fh,btv,f); + if (0 != retval) + return retval; + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + + /* update our state informations */ + down(&fh->cap.lock); + fh->fmt = fmt; + fh->cap.field = f->fmt.pix.field; + fh->cap.last = V4L2_FIELD_NONE; + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + btv->init.fmt = fmt; + btv->init.width = f->fmt.pix.width; + btv->init.height = f->fmt.pix.height; + up(&fh->cap.lock); + + return 0; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + return setup_window(fh, btv, &f->fmt.win, 1); + case V4L2_BUF_TYPE_VBI_CAPTURE: + retval = bttv_switch_type(fh,f->type); + if (0 != retval) + return retval; + if (locked_btres(fh->btv, RESOURCE_VBI)) + return -EBUSY; + bttv_vbi_try_fmt(fh,f); + bttv_vbi_setlines(fh,btv,f->fmt.vbi.count[0]); + bttv_vbi_get_fmt(fh,f); + return 0; + default: + return -EINVAL; + } +} + +static int bttv_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + unsigned long flags; + int retval = 0; + + if (bttv_debug > 1) { + switch (_IOC_TYPE(cmd)) { + case 'v': + printk("bttv%d: ioctl 0x%x (v4l1, VIDIOC%s)\n", + btv->c.nr, cmd, (_IOC_NR(cmd) < V4L1_IOCTLS) ? + v4l1_ioctls[_IOC_NR(cmd)] : "???"); + break; + case 'V': + printk("bttv%d: ioctl 0x%x (v4l2, %s)\n", + btv->c.nr, cmd, v4l2_ioctl_names[_IOC_NR(cmd)]); + break; + default: + printk("bttv%d: ioctl 0x%x (???)\n", + btv->c.nr, cmd); + } + } + if (btv->errors) + bttv_reinit_bt848(btv); + +#ifdef VIDIOC_G_PRIORITY + switch (cmd) { + case VIDIOCSFREQ: + case VIDIOCSTUNER: + case VIDIOCSCHAN: + case VIDIOC_S_CTRL: + case VIDIOC_S_STD: + case VIDIOC_S_INPUT: + case VIDIOC_S_TUNER: + case VIDIOC_S_FREQUENCY: + retval = v4l2_prio_check(&btv->prio,&fh->prio); + if (0 != retval) + return retval; + }; +#endif + switch (cmd) { + + /* *** v4l1 *** ************************************************ */ + case VIDIOCGCAP: + { + struct video_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->name,btv->video_dev->name); + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) { + /* vbi */ + cap->type = VID_TYPE_TUNER|VID_TYPE_TELETEXT; + } else { + /* others */ + cap->type = VID_TYPE_CAPTURE| + VID_TYPE_TUNER| + VID_TYPE_OVERLAY| + VID_TYPE_CLIPPING| + VID_TYPE_SCALES; + cap->channels = bttv_tvcards[btv->c.type].video_inputs; + cap->audios = bttv_tvcards[btv->c.type].audio_inputs; + cap->maxwidth = bttv_tvnorms[btv->tvnorm].swidth; + cap->maxheight = bttv_tvnorms[btv->tvnorm].sheight; + cap->minwidth = 48; + cap->minheight = 32; + } + return 0; + } + + case VIDIOCGPICT: + { + struct video_picture *pic = arg; + + memset(pic,0,sizeof(*pic)); + pic->brightness = btv->bright; + pic->contrast = btv->contrast; + pic->hue = btv->hue; + pic->colour = btv->saturation; + if (fh->fmt) { + pic->depth = fh->fmt->depth; + pic->palette = fh->fmt->palette; + } + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *pic = arg; + const struct bttv_format *fmt; + + fmt = format_by_palette(pic->palette); + if (NULL == fmt) + return -EINVAL; + down(&fh->cap.lock); + if (fmt->depth != pic->depth) { + retval = -EINVAL; + goto fh_unlock_and_return; + } + fh->ovfmt = fmt; + fh->fmt = fmt; + btv->init.ovfmt = fmt; + btv->init.fmt = fmt; + if (bigendian) { + /* dirty hack time: swap bytes for overlay if the + display adaptor is big endian (insmod option) */ + if (fmt->palette == VIDEO_PALETTE_RGB555 || + fmt->palette == VIDEO_PALETTE_RGB565 || + fmt->palette == VIDEO_PALETTE_RGB32) { + fh->ovfmt = fmt+1; + } + } + bt848_bright(btv,pic->brightness); + bt848_contrast(btv,pic->contrast); + bt848_hue(btv,pic->hue); + bt848_sat(btv,pic->colour); + up(&fh->cap.lock); + return 0; + } + + case VIDIOCGWIN: + { + struct video_window *win = arg; + + memset(win,0,sizeof(*win)); + win->x = fh->ov.w.left; + win->y = fh->ov.w.top; + win->width = fh->ov.w.width; + win->height = fh->ov.w.height; + return 0; + } + case VIDIOCSWIN: + { + struct video_window *win = arg; + struct v4l2_window w2; + + w2.field = V4L2_FIELD_ANY; + w2.w.left = win->x; + w2.w.top = win->y; + w2.w.width = win->width; + w2.w.height = win->height; + w2.clipcount = win->clipcount; + w2.clips = (struct v4l2_clip*)win->clips; + retval = setup_window(fh, btv, &w2, 0); + if (0 == retval) { + /* on v4l1 this ioctl affects the read() size too */ + fh->width = fh->ov.w.width; + fh->height = fh->ov.w.height; + btv->init.width = fh->ov.w.width; + btv->init.height = fh->ov.w.height; + } + return retval; + } + + case VIDIOCGFBUF: + { + struct video_buffer *fbuf = arg; + + fbuf->base = btv->fbuf.base; + fbuf->width = btv->fbuf.fmt.width; + fbuf->height = btv->fbuf.fmt.height; + fbuf->bytesperline = btv->fbuf.fmt.bytesperline; + if (fh->ovfmt) + fbuf->depth = fh->ovfmt->depth; + return 0; + } + case VIDIOCSFBUF: + { + struct video_buffer *fbuf = arg; + const struct bttv_format *fmt; + unsigned long end; + + if(!capable(CAP_SYS_ADMIN) && + !capable(CAP_SYS_RAWIO)) + return -EPERM; + end = (unsigned long)fbuf->base + + fbuf->height * fbuf->bytesperline; + down(&fh->cap.lock); + retval = -EINVAL; + + switch (fbuf->depth) { + case 8: + fmt = format_by_palette(VIDEO_PALETTE_HI240); + break; + case 16: + fmt = format_by_palette(VIDEO_PALETTE_RGB565); + break; + case 24: + fmt = format_by_palette(VIDEO_PALETTE_RGB24); + break; + case 32: + fmt = format_by_palette(VIDEO_PALETTE_RGB32); + break; + case 15: + fbuf->depth = 16; + fmt = format_by_palette(VIDEO_PALETTE_RGB555); + break; + default: + fmt = NULL; + break; + } + if (NULL == fmt) + goto fh_unlock_and_return; + + fh->ovfmt = fmt; + fh->fmt = fmt; + btv->init.ovfmt = fmt; + btv->init.fmt = fmt; + btv->fbuf.base = fbuf->base; + btv->fbuf.fmt.width = fbuf->width; + btv->fbuf.fmt.height = fbuf->height; + if (fbuf->bytesperline) + btv->fbuf.fmt.bytesperline = fbuf->bytesperline; + else + btv->fbuf.fmt.bytesperline = btv->fbuf.fmt.width*fbuf->depth/8; + up(&fh->cap.lock); + return 0; + } + + case VIDIOCCAPTURE: + case VIDIOC_OVERLAY: + { + struct bttv_buffer *new; + int *on = arg; + + if (*on) { + /* verify args */ + if (NULL == btv->fbuf.base) + return -EINVAL; + if (!fh->ov.setup_ok) { + dprintk("bttv%d: overlay: !setup_ok\n",btv->c.nr); + return -EINVAL; + } + } + + if (!check_alloc_btres(btv,fh,RESOURCE_OVERLAY)) + return -EBUSY; + + down(&fh->cap.lock); + if (*on) { + fh->ov.tvnorm = btv->tvnorm; + new = videobuf_alloc(sizeof(*new)); + bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new); + } else { + new = NULL; + } + + /* switch over */ + retval = bttv_switch_overlay(btv,fh,new); + up(&fh->cap.lock); + return retval; + } + + case VIDIOCGMBUF: + { + struct video_mbuf *mbuf = arg; + unsigned int i; + + down(&fh->cap.lock); + retval = videobuf_mmap_setup(file,&fh->cap,gbuffers,gbufsize, + V4L2_MEMORY_MMAP); + if (retval < 0) + goto fh_unlock_and_return; + memset(mbuf,0,sizeof(*mbuf)); + mbuf->frames = gbuffers; + mbuf->size = gbuffers * gbufsize; + for (i = 0; i < gbuffers; i++) + mbuf->offsets[i] = i * gbufsize; + up(&fh->cap.lock); + return 0; + } + case VIDIOCMCAPTURE: + { + struct video_mmap *vm = arg; + struct bttv_buffer *buf; + enum v4l2_field field; + + if (vm->frame >= VIDEO_MAX_FRAME) + return -EINVAL; + + down(&fh->cap.lock); + retval = -EINVAL; + buf = (struct bttv_buffer *)fh->cap.bufs[vm->frame]; + if (NULL == buf) + goto fh_unlock_and_return; + if (0 == buf->vb.baddr) + goto fh_unlock_and_return; + if (buf->vb.state == STATE_QUEUED || + buf->vb.state == STATE_ACTIVE) + goto fh_unlock_and_return; + + field = (vm->height > bttv_tvnorms[btv->tvnorm].sheight/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + retval = bttv_prepare_buffer(btv,buf, + format_by_palette(vm->format), + vm->width,vm->height,field); + if (0 != retval) + goto fh_unlock_and_return; + spin_lock_irqsave(&btv->s_lock,flags); + buffer_queue(file,&buf->vb); + spin_unlock_irqrestore(&btv->s_lock,flags); + up(&fh->cap.lock); + return 0; + } + case VIDIOCSYNC: + { + int *frame = arg; + struct bttv_buffer *buf; + + if (*frame >= VIDEO_MAX_FRAME) + return -EINVAL; + + down(&fh->cap.lock); + retval = -EINVAL; + buf = (struct bttv_buffer *)fh->cap.bufs[*frame]; + if (NULL == buf) + goto fh_unlock_and_return; + retval = videobuf_waiton(&buf->vb,0,1); + if (0 != retval) + goto fh_unlock_and_return; + switch (buf->vb.state) { + case STATE_ERROR: + retval = -EIO; + /* fall through */ + case STATE_DONE: + videobuf_dma_pci_sync(btv->c.pci,&buf->vb.dma); + bttv_dma_free(btv,buf); + break; + default: + retval = -EINVAL; + break; + } + up(&fh->cap.lock); + return retval; + } + + case VIDIOCGVBIFMT: + { + struct vbi_format *fmt = (void *) arg; + struct v4l2_format fmt2; + + if (fh->type != V4L2_BUF_TYPE_VBI_CAPTURE) { + retval = bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE); + if (0 != retval) + return retval; + } + bttv_vbi_get_fmt(fh, &fmt2); + + memset(fmt,0,sizeof(*fmt)); + fmt->sampling_rate = fmt2.fmt.vbi.sampling_rate; + fmt->samples_per_line = fmt2.fmt.vbi.samples_per_line; + fmt->sample_format = VIDEO_PALETTE_RAW; + fmt->start[0] = fmt2.fmt.vbi.start[0]; + fmt->count[0] = fmt2.fmt.vbi.count[0]; + fmt->start[1] = fmt2.fmt.vbi.start[1]; + fmt->count[1] = fmt2.fmt.vbi.count[1]; + if (fmt2.fmt.vbi.flags & VBI_UNSYNC) + fmt->flags |= V4L2_VBI_UNSYNC; + if (fmt2.fmt.vbi.flags & VBI_INTERLACED) + fmt->flags |= V4L2_VBI_INTERLACED; + return 0; + } + case VIDIOCSVBIFMT: + { + struct vbi_format *fmt = (void *) arg; + struct v4l2_format fmt2; + + retval = bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE); + if (0 != retval) + return retval; + bttv_vbi_get_fmt(fh, &fmt2); + + if (fmt->sampling_rate != fmt2.fmt.vbi.sampling_rate || + fmt->samples_per_line != fmt2.fmt.vbi.samples_per_line || + fmt->sample_format != VIDEO_PALETTE_RAW || + fmt->start[0] != fmt2.fmt.vbi.start[0] || + fmt->start[1] != fmt2.fmt.vbi.start[1] || + fmt->count[0] != fmt->count[1] || + fmt->count[0] < 1 || + fmt->count[0] > 32 /* VBI_MAXLINES */) + return -EINVAL; + + bttv_vbi_setlines(fh,btv,fmt->count[0]); + return 0; + } + + case BTTV_VERSION: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGTUNER: + case VIDIOCSTUNER: + case VIDIOCGCHAN: + case VIDIOCSCHAN: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return bttv_common_ioctls(btv,cmd,arg); + + /* *** v4l2 *** ************************************************ */ + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + if (0 == v4l2) + return -EINVAL; + strcpy(cap->driver,"bttv"); + strlcpy(cap->card,btv->video_dev->name,sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s",pci_name(btv->c.pci)); + cap->version = BTTV_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_TUNER | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + return 0; + } + + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *f = arg; + enum v4l2_buf_type type; + unsigned int i; + int index; + + type = f->type; + if (V4L2_BUF_TYPE_VBI_CAPTURE == type) { + /* vbi */ + index = f->index; + if (0 != index) + return -EINVAL; + memset(f,0,sizeof(*f)); + f->index = index; + f->type = type; + f->pixelformat = V4L2_PIX_FMT_GREY; + strcpy(f->description,"vbi data"); + return 0; + } + + /* video capture + overlay */ + index = -1; + for (i = 0; i < BTTV_FORMATS; i++) { + if (bttv_formats[i].fourcc != -1) + index++; + if ((unsigned int)index == f->index) + break; + } + if (BTTV_FORMATS == i) + return -EINVAL; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + if (!(bttv_formats[i].flags & FORMAT_FLAGS_PACKED)) + return -EINVAL; + break; + default: + return -EINVAL; + } + memset(f,0,sizeof(*f)); + f->index = index; + f->type = type; + f->pixelformat = bttv_formats[i].fourcc; + strlcpy(f->description,bttv_formats[i].name,sizeof(f->description)); + return 0; + } + + case VIDIOC_TRY_FMT: + { + struct v4l2_format *f = arg; + return bttv_try_fmt(fh,btv,f); + } + case VIDIOC_G_FMT: + { + struct v4l2_format *f = arg; + return bttv_g_fmt(fh,f); + } + case VIDIOC_S_FMT: + { + struct v4l2_format *f = arg; + return bttv_s_fmt(fh,btv,f); + } + + case VIDIOC_G_FBUF: + { + struct v4l2_framebuffer *fb = arg; + + *fb = btv->fbuf; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + if (fh->ovfmt) + fb->fmt.pixelformat = fh->ovfmt->fourcc; + return 0; + } + case VIDIOC_S_FBUF: + { + struct v4l2_framebuffer *fb = arg; + const struct bttv_format *fmt; + + if(!capable(CAP_SYS_ADMIN) && + !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* check args */ + fmt = format_by_fourcc(fb->fmt.pixelformat); + if (NULL == fmt) + return -EINVAL; + if (0 == (fmt->flags & FORMAT_FLAGS_PACKED)) + return -EINVAL; + + down(&fh->cap.lock); + retval = -EINVAL; + if (fb->flags & V4L2_FBUF_FLAG_OVERLAY) { + if (fb->fmt.width > bttv_tvnorms[btv->tvnorm].swidth) + goto fh_unlock_and_return; + if (fb->fmt.height > bttv_tvnorms[btv->tvnorm].sheight) + goto fh_unlock_and_return; + } + + /* ok, accept it */ + btv->fbuf.base = fb->base; + btv->fbuf.fmt.width = fb->fmt.width; + btv->fbuf.fmt.height = fb->fmt.height; + if (0 != fb->fmt.bytesperline) + btv->fbuf.fmt.bytesperline = fb->fmt.bytesperline; + else + btv->fbuf.fmt.bytesperline = btv->fbuf.fmt.width*fmt->depth/8; + + retval = 0; + fh->ovfmt = fmt; + btv->init.ovfmt = fmt; + if (fb->flags & V4L2_FBUF_FLAG_OVERLAY) { + fh->ov.w.left = 0; + fh->ov.w.top = 0; + fh->ov.w.width = fb->fmt.width; + fh->ov.w.height = fb->fmt.height; + btv->init.ov.w.width = fb->fmt.width; + btv->init.ov.w.height = fb->fmt.height; + if (fh->ov.clips) + kfree(fh->ov.clips); + fh->ov.clips = NULL; + fh->ov.nclips = 0; + + if (check_btres(fh, RESOURCE_OVERLAY)) { + struct bttv_buffer *new; + + new = videobuf_alloc(sizeof(*new)); + bttv_overlay_risc(btv,&fh->ov,fh->ovfmt,new); + retval = bttv_switch_overlay(btv,fh,new); + } + } + up(&fh->cap.lock); + return retval; + } + + case VIDIOC_REQBUFS: + return videobuf_reqbufs(file,bttv_queue(fh),arg); + + case VIDIOC_QUERYBUF: + return videobuf_querybuf(bttv_queue(fh),arg); + + case VIDIOC_QBUF: + return videobuf_qbuf(file,bttv_queue(fh),arg); + + case VIDIOC_DQBUF: + return videobuf_dqbuf(file,bttv_queue(fh),arg); + + case VIDIOC_STREAMON: + { + int res = bttv_resource(fh); + + if (!check_alloc_btres(btv,fh,res)) + return -EBUSY; + return videobuf_streamon(file,bttv_queue(fh)); + } + case VIDIOC_STREAMOFF: + { + int res = bttv_resource(fh); + + retval = videobuf_streamoff(file,bttv_queue(fh)); + if (retval < 0) + return retval; + free_btres(btv,fh,res); + return 0; + } + + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *c = arg; + int i; + + if ((c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) && + (c->id < V4L2_CID_PRIVATE_BASE || + c->id >= V4L2_CID_PRIVATE_LASTP1)) + return -EINVAL; + for (i = 0; i < BTTV_CTLS; i++) + if (bttv_ctls[i].id == c->id) + break; + if (i == BTTV_CTLS) { + *c = no_ctl; + return 0; + } + *c = bttv_ctls[i]; + if (i >= 4 && i <= 8) { + struct video_audio va; + memset(&va,0,sizeof(va)); + bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,0); + switch (bttv_ctls[i].id) { + case V4L2_CID_AUDIO_VOLUME: + if (!(va.flags & VIDEO_AUDIO_VOLUME)) + *c = no_ctl; + break; + case V4L2_CID_AUDIO_BALANCE: + if (!(va.flags & VIDEO_AUDIO_BALANCE)) + *c = no_ctl; + break; + case V4L2_CID_AUDIO_BASS: + if (!(va.flags & VIDEO_AUDIO_BASS)) + *c = no_ctl; + break; + case V4L2_CID_AUDIO_TREBLE: + if (!(va.flags & VIDEO_AUDIO_TREBLE)) + *c = no_ctl; + break; + } + } + return 0; + } + case VIDIOC_G_CTRL: + return get_control(btv,arg); + case VIDIOC_S_CTRL: + return set_control(btv,arg); + case VIDIOC_G_PARM: + { + struct v4l2_streamparm *parm = arg; + struct v4l2_standard s; + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + memset(parm,0,sizeof(*parm)); + v4l2_video_std_construct(&s, bttv_tvnorms[btv->tvnorm].v4l2_id, + bttv_tvnorms[btv->tvnorm].name); + parm->parm.capture.timeperframe = s.frameperiod; + return 0; + } + +#ifdef VIDIOC_G_PRIORITY + case VIDIOC_G_PRIORITY: + { + enum v4l2_priority *p = arg; + + *p = v4l2_prio_max(&btv->prio); + return 0; + } + case VIDIOC_S_PRIORITY: + { + enum v4l2_priority *prio = arg; + + return v4l2_prio_change(&btv->prio, &fh->prio, *prio); + } +#endif + + + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + return bttv_common_ioctls(btv,cmd,arg); + + default: + return -ENOIOCTLCMD; + } + return 0; + + fh_unlock_and_return: + up(&fh->cap.lock); + return retval; +} + +static int bttv_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct bttv_fh *fh = file->private_data; + + switch (cmd) { + case BTTV_VBISIZE: + bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE); + return fh->lines * 2 * 2048; + default: + return video_usercopy(inode, file, cmd, arg, bttv_do_ioctl); + } +} + +static ssize_t bttv_read(struct file *file, char *data, + size_t count, loff_t *ppos) +{ + struct bttv_fh *fh = file->private_data; + int retval = 0; + + if (fh->btv->errors) + bttv_reinit_bt848(fh->btv); + dprintk("bttv%d: read count=%d type=%s\n", + fh->btv->c.nr,(int)count,v4l2_type_names[fh->type]); + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (locked_btres(fh->btv,RESOURCE_VIDEO)) + return -EBUSY; + retval = videobuf_read_one(file, &fh->cap, data, count, ppos); + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (!check_alloc_btres(fh->btv,fh,RESOURCE_VBI)) + return -EBUSY; + retval = videobuf_read_stream(file, &fh->vbi, data, count, ppos, 1); + break; + default: + BUG(); + } + return retval; +} + +static unsigned int bttv_poll(struct file *file, poll_table *wait) +{ + struct bttv_fh *fh = file->private_data; + struct bttv_buffer *buf; + enum v4l2_field field; + + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) { + if (!check_alloc_btres(fh->btv,fh,RESOURCE_VBI)) + return -EBUSY; + return videobuf_poll_stream(file, &fh->vbi, wait); + } + + if (check_btres(fh,RESOURCE_VIDEO)) { + /* streaming capture */ + if (list_empty(&fh->cap.stream)) + return POLLERR; + buf = list_entry(fh->cap.stream.next,struct bttv_buffer,vb.stream); + } else { + /* read() capture */ + down(&fh->cap.lock); + if (NULL == fh->cap.read_buf) { + /* need to capture a new frame */ + if (locked_btres(fh->btv,RESOURCE_VIDEO)) { + up(&fh->cap.lock); + return POLLERR; + } + fh->cap.read_buf = videobuf_alloc(fh->cap.msize); + if (NULL == fh->cap.read_buf) { + up(&fh->cap.lock); + return POLLERR; + } + fh->cap.read_buf->memory = V4L2_MEMORY_USERPTR; + field = videobuf_next_field(&fh->cap); + if (0 != fh->cap.ops->buf_prepare(file,fh->cap.read_buf,field)) { + up(&fh->cap.lock); + return POLLERR; + } + fh->cap.ops->buf_queue(file,fh->cap.read_buf); + fh->cap.read_off = 0; + } + up(&fh->cap.lock); + buf = (struct bttv_buffer*)fh->cap.read_buf; + } + + poll_wait(file, &buf->vb.done, wait); + if (buf->vb.state == STATE_DONE || + buf->vb.state == STATE_ERROR) + return POLLIN|POLLRDNORM; + return 0; +} + +static int bttv_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct bttv *btv = NULL; + struct bttv_fh *fh; + enum v4l2_buf_type type = 0; + unsigned int i; + + dprintk(KERN_DEBUG "bttv: open minor=%d\n",minor); + + for (i = 0; i < bttv_num; i++) { + if (bttvs[i].video_dev && + bttvs[i].video_dev->minor == minor) { + btv = &bttvs[i]; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + break; + } + if (bttvs[i].vbi_dev && + bttvs[i].vbi_dev->minor == minor) { + btv = &bttvs[i]; + type = V4L2_BUF_TYPE_VBI_CAPTURE; + break; + } + } + if (NULL == btv) + return -ENODEV; + + dprintk(KERN_DEBUG "bttv%d: open called (type=%s)\n", + btv->c.nr,v4l2_type_names[type]); + + /* allocate per filehandle data */ + fh = kmalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + file->private_data = fh; + *fh = btv->init; + fh->type = type; + fh->ov.setup_ok = 0; +#ifdef VIDIOC_G_PRIORITY + v4l2_prio_open(&btv->prio,&fh->prio); +#endif + + videobuf_queue_init(&fh->cap, &bttv_video_qops, + btv->c.pci, &btv->s_lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct bttv_buffer)); + videobuf_queue_init(&fh->vbi, &bttv_vbi_qops, + btv->c.pci, &btv->s_lock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct bttv_buffer)); + i2c_vidiocschan(btv); + + btv->users++; + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) + bttv_vbi_setlines(fh,btv,16); + bttv_field_count(btv); + return 0; +} + +static int bttv_release(struct inode *inode, struct file *file) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + + /* turn off overlay */ + if (check_btres(fh, RESOURCE_OVERLAY)) + bttv_switch_overlay(btv,fh,NULL); + + /* stop video capture */ + if (check_btres(fh, RESOURCE_VIDEO)) { + videobuf_streamoff(file,&fh->cap); + free_btres(btv,fh,RESOURCE_VIDEO); + } + if (fh->cap.read_buf) { + buffer_release(file,fh->cap.read_buf); + kfree(fh->cap.read_buf); + } + + /* stop vbi capture */ + if (check_btres(fh, RESOURCE_VBI)) { + if (fh->vbi.streaming) + videobuf_streamoff(file,&fh->vbi); + if (fh->vbi.reading) + videobuf_read_stop(file,&fh->vbi); + free_btres(btv,fh,RESOURCE_VBI); + } + +#ifdef VIDIOC_G_PRIORITY + v4l2_prio_close(&btv->prio,&fh->prio); +#endif + file->private_data = NULL; + kfree(fh); + + btv->users--; + bttv_field_count(btv); + return 0; +} + +static int +bttv_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct bttv_fh *fh = file->private_data; + + dprintk("bttv%d: mmap type=%s 0x%lx+%ld\n", + fh->btv->c.nr, v4l2_type_names[fh->type], + vma->vm_start, vma->vm_end - vma->vm_start); + return videobuf_mmap_mapper(vma,bttv_queue(fh)); +} + +static struct file_operations bttv_fops = +{ + .owner = THIS_MODULE, + .open = bttv_open, + .release = bttv_release, + .ioctl = bttv_ioctl, + .llseek = no_llseek, + .read = bttv_read, + .mmap = bttv_mmap, + .poll = bttv_poll, +}; + +static struct video_device bttv_video_template = +{ + .name = "UNSET", + .type = VID_TYPE_CAPTURE|VID_TYPE_TUNER|VID_TYPE_OVERLAY| + VID_TYPE_CLIPPING|VID_TYPE_SCALES, + .hardware = VID_HARDWARE_BT848, + .fops = &bttv_fops, + .minor = -1, +}; + +struct video_device bttv_vbi_template = +{ + .name = "bt848/878 vbi", + .type = VID_TYPE_TUNER|VID_TYPE_TELETEXT, + .hardware = VID_HARDWARE_BT848, + .fops = &bttv_fops, + .minor = -1, +}; + +/* ----------------------------------------------------------------------- */ +/* radio interface */ + +static int radio_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct bttv *btv = NULL; + unsigned int i; + + dprintk("bttv: open minor=%d\n",minor); + + for (i = 0; i < bttv_num; i++) { + if (bttvs[i].radio_dev->minor == minor) { + btv = &bttvs[i]; + break; + } + } + if (NULL == btv) + return -ENODEV; + + dprintk("bttv%d: open called (radio)\n",btv->c.nr); + down(&btv->lock); + if (btv->radio_user) { + up(&btv->lock); + return -EBUSY; + } + btv->radio_user++; + file->private_data = btv; + + i2c_vidiocschan(btv); + bttv_call_i2c_clients(btv,AUDC_SET_RADIO,&btv->tuner_type); + audio_mux(btv,AUDIO_RADIO); + + up(&btv->lock); + return 0; +} + +static int radio_release(struct inode *inode, struct file *file) +{ + struct bttv *btv = file->private_data; + + btv->radio_user--; + return 0; +} + +static int radio_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct bttv *btv = file->private_data; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->name,btv->radio_dev->name); + cap->type = VID_TYPE_TUNER; + cap->channels = 1; + cap->audios = 1; + return 0; + } + + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + + if(v->tuner) + return -EINVAL; + memset(v,0,sizeof(*v)); + strcpy(v->name, "Radio"); + /* japan: 76.0 MHz - 89.9 MHz + western europe: 87.5 MHz - 108.0 MHz + russia: 65.0 MHz - 108.0 MHz */ + v->rangelow=(int)(65*16); + v->rangehigh=(int)(108*16); + bttv_call_i2c_clients(btv,cmd,v); + return 0; + } + case VIDIOCSTUNER: + /* nothing to do */ + return 0; + + case BTTV_VERSION: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return bttv_common_ioctls(btv,cmd,arg); + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int radio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, radio_do_ioctl); +} + +static struct file_operations radio_fops = +{ + .owner = THIS_MODULE, + .open = radio_open, + .release = radio_release, + .ioctl = radio_ioctl, + .llseek = no_llseek, +}; + +static struct video_device radio_template = +{ + .name = "bt848/878 radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_BT848, + .fops = &radio_fops, + .minor = -1, +}; + +/* ----------------------------------------------------------------------- */ +/* irq handler */ + +static char *irq_name[] = { "FMTCHG", "VSYNC", "HSYNC", "OFLOW", "HLOCK", + "VPRES", "6", "7", "I2CDONE", "GPINT", "10", + "RISCI", "FBUS", "FTRGT", "FDSR", "PPERR", + "RIPERR", "PABORT", "OCERR", "SCERR" }; + +static void bttv_print_irqbits(u32 print, u32 mark) +{ + unsigned int i; + + printk("bits:"); + for (i = 0; i < ARRAY_SIZE(irq_name); i++) { + if (print & (1 << i)) + printk(" %s",irq_name[i]); + if (mark & (1 << i)) + printk("*"); + } +} + +static void bttv_print_riscaddr(struct bttv *btv) +{ + printk(" main: %08Lx\n", + (unsigned long long)btv->main.dma); + printk(" vbi : o=%08Lx e=%08Lx\n", + btv->curr.vbi ? (unsigned long long)btv->curr.vbi->top.dma : 0, + btv->curr.vbi ? (unsigned long long)btv->curr.vbi->bottom.dma : 0); + printk(" cap : o=%08Lx e=%08Lx\n", + btv->curr.top ? (unsigned long long)btv->curr.top->top.dma : 0, + btv->curr.bottom ? (unsigned long long)btv->curr.bottom->bottom.dma : 0); + printk(" scr : o=%08Lx e=%08Lx\n", + btv->screen ? (unsigned long long)btv->screen->top.dma : 0, + btv->screen ? (unsigned long long)btv->screen->bottom.dma : 0); +} + +static int +bttv_irq_next_set(struct bttv *btv, struct bttv_buffer_set *set) +{ + struct bttv_buffer *item; + + memset(set,0,sizeof(*set)); + + /* vbi request ? */ + if (!list_empty(&btv->vcapture)) { + set->irqflags = 1; + set->vbi = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue); + } + + /* capture request ? */ + if (!list_empty(&btv->capture)) { + set->irqflags = 1; + item = list_entry(btv->capture.next, struct bttv_buffer, vb.queue); + if (V4L2_FIELD_HAS_TOP(item->vb.field)) + set->top = item; + if (V4L2_FIELD_HAS_BOTTOM(item->vb.field)) + set->bottom = item; + + /* capture request for other field ? */ + if (!V4L2_FIELD_HAS_BOTH(item->vb.field) && + (item->vb.queue.next != &btv->capture)) { + item = list_entry(item->vb.queue.next, struct bttv_buffer, vb.queue); + if (!V4L2_FIELD_HAS_BOTH(item->vb.field)) { + if (NULL == set->top && + V4L2_FIELD_TOP == item->vb.field) { + set->top = item; + } + if (NULL == set->bottom && + V4L2_FIELD_BOTTOM == item->vb.field) { + set->bottom = item; + } + if (NULL != set->top && NULL != set->bottom) + set->topirq = 2; + } + } + } + + /* screen overlay ? */ + if (NULL != btv->screen) { + if (V4L2_FIELD_HAS_BOTH(btv->screen->vb.field)) { + if (NULL == set->top && NULL == set->bottom) { + set->top = btv->screen; + set->bottom = btv->screen; + } + } else { + if (V4L2_FIELD_TOP == btv->screen->vb.field && + NULL == set->top) { + set->top = btv->screen; + } + if (V4L2_FIELD_BOTTOM == btv->screen->vb.field && + NULL == set->bottom) { + set->bottom = btv->screen; + } + } + } + + dprintk("bttv%d: next set: top=%p bottom=%p vbi=%p " + "[screen=%p,irq=%d,%d]\n", + btv->c.nr,set->top, set->bottom, set->vbi, + btv->screen,set->irqflags,set->topirq); + return 0; +} + +static void +bttv_irq_wakeup_set(struct bttv *btv, struct bttv_buffer_set *wakeup, + struct bttv_buffer_set *curr, unsigned int state) +{ + struct timeval ts; + + do_gettimeofday(&ts); + + if (NULL != wakeup->vbi) { + wakeup->vbi->vb.ts = ts; + wakeup->vbi->vb.field_count = btv->field_count; + wakeup->vbi->vb.state = state; + wake_up(&wakeup->vbi->vb.done); + } + if (wakeup->top == wakeup->bottom) { + if (NULL != wakeup->top && curr->top != wakeup->top) { + if (irq_debug > 1) + printk("bttv%d: wakeup: both=%p\n",btv->c.nr,wakeup->top); + wakeup->top->vb.ts = ts; + wakeup->top->vb.field_count = btv->field_count; + wakeup->top->vb.state = state; + wake_up(&wakeup->top->vb.done); + } + } else { + if (NULL != wakeup->top && curr->top != wakeup->top) { + if (irq_debug > 1) + printk("bttv%d: wakeup: top=%p\n",btv->c.nr,wakeup->top); + wakeup->top->vb.ts = ts; + wakeup->top->vb.field_count = btv->field_count; + wakeup->top->vb.state = state; + wake_up(&wakeup->top->vb.done); + } + if (NULL != wakeup->bottom && curr->bottom != wakeup->bottom) { + if (irq_debug > 1) + printk("bttv%d: wakeup: bottom=%p\n",btv->c.nr,wakeup->bottom); + wakeup->bottom->vb.ts = ts; + wakeup->bottom->vb.field_count = btv->field_count; + wakeup->bottom->vb.state = state; + wake_up(&wakeup->bottom->vb.done); + } + } +} + +static void bttv_irq_timeout(unsigned long data) +{ + struct bttv *btv = (struct bttv *)data; + struct bttv_buffer_set old,new; + struct bttv_buffer *item; + unsigned long flags; + + if (bttv_verbose) { + printk(KERN_INFO "bttv%d: timeout: drop=%d irq=%d/%d, risc=%08x, ", + btv->c.nr, btv->framedrop, btv->irq_me, btv->irq_total, + btread(BT848_RISC_COUNT)); + bttv_print_irqbits(btread(BT848_INT_STAT),0); + printk("\n"); + } + + spin_lock_irqsave(&btv->s_lock,flags); + + /* deactivate stuff */ + memset(&new,0,sizeof(new)); + old = btv->curr; + btv->curr = new; + bttv_buffer_set_activate(btv, &new); + bttv_set_dma(btv, 0, 0); + + /* wake up */ + bttv_irq_wakeup_set(btv, &old, &new, STATE_ERROR); + + /* cancel all outstanding capture / vbi requests */ + while (!list_empty(&btv->capture)) { + item = list_entry(btv->capture.next, struct bttv_buffer, vb.queue); + list_del(&item->vb.queue); + item->vb.state = STATE_ERROR; + wake_up(&item->vb.done); + } + while (!list_empty(&btv->vcapture)) { + item = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue); + list_del(&item->vb.queue); + item->vb.state = STATE_ERROR; + wake_up(&item->vb.done); + } + + btv->errors++; + spin_unlock_irqrestore(&btv->s_lock,flags); +} + +static void +bttv_irq_wakeup_top(struct bttv *btv) +{ + struct bttv_buffer *wakeup = btv->curr.top; + + if (NULL == wakeup) + return; + + spin_lock(&btv->s_lock); + btv->curr.topirq = 0; + btv->curr.top = NULL; + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0); + + do_gettimeofday(&wakeup->vb.ts); + wakeup->vb.field_count = btv->field_count; + wakeup->vb.state = STATE_DONE; + wake_up(&wakeup->vb.done); + spin_unlock(&btv->s_lock); +} + +static void +bttv_irq_switch_fields(struct bttv *btv) +{ + struct bttv_buffer_set new; + struct bttv_buffer_set old; + dma_addr_t rc; + + spin_lock(&btv->s_lock); + + /* new buffer set */ + bttv_irq_next_set(btv, &new); + rc = btread(BT848_RISC_COUNT); + if (rc < btv->main.dma || rc > btv->main.dma + 0x100) { + btv->framedrop++; + if (irq_debug) + printk("bttv%d: skipped frame. no signal? high irq latency? " + "[main=%lx,o_vbi=%lx,o_field=%lx,rc=%lx]\n", + btv->c.nr, + (unsigned long)btv->main.dma, + (unsigned long)btv->main.cpu[RISC_SLOT_O_VBI+1], + (unsigned long)btv->main.cpu[RISC_SLOT_O_FIELD+1], + (unsigned long)rc); + spin_unlock(&btv->s_lock); + return; + } + + /* switch over */ + old = btv->curr; + btv->curr = new; + bttv_buffer_set_activate(btv, &new); + bttv_set_dma(btv, 0, new.irqflags); + + /* switch input */ + if (UNSET != btv->new_input) { + video_mux(btv,btv->new_input); + btv->new_input = UNSET; + } + + /* wake up finished buffers */ + bttv_irq_wakeup_set(btv, &old, &new, STATE_DONE); + spin_unlock(&btv->s_lock); +} + +static irqreturn_t bttv_irq(int irq, void *dev_id, struct pt_regs * regs) +{ + u32 stat,astat; + u32 dstat; + int count; + struct bttv *btv; + int handled = 0; + + btv=(struct bttv *)dev_id; + count=0; + while (1) { + /* get/clear interrupt status bits */ + stat=btread(BT848_INT_STAT); + astat=stat&btread(BT848_INT_MASK); + if (!astat) + break; + handled = 1; + btwrite(stat,BT848_INT_STAT); + + /* get device status bits */ + dstat=btread(BT848_DSTATUS); + + if (irq_debug) { + printk(KERN_DEBUG "bttv%d: irq loop=%d fc=%d " + "riscs=%x, riscc=%08x, ", + btv->c.nr, count, btv->field_count, + stat>>28, btread(BT848_RISC_COUNT)); + bttv_print_irqbits(stat,astat); + if (stat & BT848_INT_HLOCK) + printk(" HLOC => %s", (dstat & BT848_DSTATUS_HLOC) + ? "yes" : "no"); + if (stat & BT848_INT_VPRES) + printk(" PRES => %s", (dstat & BT848_DSTATUS_PRES) + ? "yes" : "no"); + if (stat & BT848_INT_FMTCHG) + printk(" NUML => %s", (dstat & BT848_DSTATUS_NUML) + ? "625" : "525"); + printk("\n"); + } + + if (astat&BT848_INT_VSYNC) + btv->field_count++; + + if (astat & BT848_INT_GPINT) { + wake_up(&btv->gpioq); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + bttv_gpio_irq(&btv->c); +#endif + } + + if (astat & BT848_INT_I2CDONE) { + btv->i2c_done = stat; + wake_up(&btv->i2c_queue); + } + + if ((astat & BT848_INT_RISCI) && (stat & (2<<28))) + bttv_irq_wakeup_top(btv); + + if ((astat & BT848_INT_RISCI) && (stat & (1<<28))) + bttv_irq_switch_fields(btv); + + if ((astat & BT848_INT_HLOCK) && btv->opt_automute) + audio_mux(btv, -1); + + if (astat & (BT848_INT_SCERR|BT848_INT_OCERR)) { + printk(KERN_INFO "bttv%d: %s%s @ %08x,",btv->c.nr, + (astat & BT848_INT_SCERR) ? "SCERR" : "", + (astat & BT848_INT_OCERR) ? "OCERR" : "", + btread(BT848_RISC_COUNT)); + bttv_print_irqbits(stat,astat); + printk("\n"); + if (bttv_debug) + bttv_print_riscaddr(btv); + } + if (fdsr && astat & BT848_INT_FDSR) { + printk(KERN_INFO "bttv%d: FDSR @ %08x\n", + btv->c.nr,btread(BT848_RISC_COUNT)); + if (bttv_debug) + bttv_print_riscaddr(btv); + } + + count++; + if (count > 4) { + btwrite(0, BT848_INT_MASK); + printk(KERN_ERR + "bttv%d: IRQ lockup, cleared int mask [", btv->c.nr); + bttv_print_irqbits(stat,astat); + printk("]\n"); + } + } + btv->irq_total++; + if (handled) + btv->irq_me++; + return IRQ_RETVAL(handled); +} + + +/* ----------------------------------------------------------------------- */ +/* initialitation */ + +static struct video_device *vdev_init(struct bttv *btv, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->minor = -1; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + vfd->dev = &btv->c.pci->dev; + vfd->release = video_device_release; +#endif + snprintf(vfd->name, sizeof(vfd->name), "BT%d%s %s (%s)", + btv->id, (btv->id==848 && btv->revision==0x12) ? "A" : "", + type, bttv_tvcards[btv->c.type].name); + return vfd; +} + +static void bttv_unregister_video(struct bttv *btv) +{ + if (btv->video_dev) { + if (-1 != btv->video_dev->minor) + video_unregister_device(btv->video_dev); + else + video_device_release(btv->video_dev); + btv->video_dev = NULL; + } + if (btv->vbi_dev) { + if (-1 != btv->vbi_dev->minor) + video_unregister_device(btv->vbi_dev); + else + video_device_release(btv->vbi_dev); + btv->vbi_dev = NULL; + } + if (btv->radio_dev) { + if (-1 != btv->radio_dev->minor) + video_unregister_device(btv->radio_dev); + else + video_device_release(btv->radio_dev); + btv->radio_dev = NULL; + } +} + +/* register video4linux devices */ +static int __devinit bttv_register_video(struct bttv *btv) +{ + /* video */ + btv->video_dev = vdev_init(btv, &bttv_video_template, "video"); + if (NULL == btv->video_dev) + goto err; + if (video_register_device(btv->video_dev,VFL_TYPE_GRABBER,video_nr)<0) + goto err; + printk(KERN_INFO "bttv%d: registered device video%d\n", + btv->c.nr,btv->video_dev->minor & 0x1f); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + video_device_create_file(btv->video_dev, &class_device_attr_card); +#endif + + /* vbi */ + btv->vbi_dev = vdev_init(btv, &bttv_vbi_template, "vbi"); + if (NULL == btv->vbi_dev) + goto err; + if (video_register_device(btv->vbi_dev,VFL_TYPE_VBI,vbi_nr)<0) + goto err; + printk(KERN_INFO "bttv%d: registered device vbi%d\n", + btv->c.nr,btv->vbi_dev->minor & 0x1f); + + if (!btv->has_radio) + return 0; + /* radio */ + btv->radio_dev = vdev_init(btv, &radio_template, "radio"); + if (NULL == btv->radio_dev) + goto err; + if (video_register_device(btv->radio_dev, VFL_TYPE_RADIO,radio_nr)<0) + goto err; + printk(KERN_INFO "bttv%d: registered device radio%d\n", + btv->c.nr,btv->radio_dev->minor & 0x1f); + + /* all done */ + return 0; + + err: + bttv_unregister_video(btv); + return -1; +} + + +/* on OpenFirmware machines (PowerMac at least), PCI memory cycle */ +/* response on cards with no firmware is not enabled by OF */ +static void pci_set_command(struct pci_dev *dev) +{ +#if defined(__powerpc__) + unsigned int cmd; + + pci_read_config_dword(dev, PCI_COMMAND, &cmd); + cmd = (cmd | PCI_COMMAND_MEMORY ); + pci_write_config_dword(dev, PCI_COMMAND, cmd); +#endif +} + +static int __devinit bttv_probe(struct pci_dev *dev, + const struct pci_device_id *pci_id) +{ + int result; + unsigned char lat; + struct bttv *btv; + + if (bttv_num == BTTV_MAX) + return -ENOMEM; + printk(KERN_INFO "bttv: Bt8xx card found (%d).\n", bttv_num); + btv=&bttvs[bttv_num]; + memset(btv,0,sizeof(*btv)); + btv->c.nr = bttv_num; + sprintf(btv->c.name,"bttv%d",btv->c.nr); + + /* initialize structs / fill in defaults */ + init_MUTEX(&btv->lock); + init_MUTEX(&btv->reslock); + btv->s_lock = SPIN_LOCK_UNLOCKED; + btv->gpio_lock = SPIN_LOCK_UNLOCKED; + init_waitqueue_head(&btv->gpioq); + init_waitqueue_head(&btv->i2c_queue); + INIT_LIST_HEAD(&btv->c.subs); + INIT_LIST_HEAD(&btv->capture); + INIT_LIST_HEAD(&btv->vcapture); +#ifdef VIDIOC_G_PRIORITY + v4l2_prio_init(&btv->prio); +#endif + + init_timer(&btv->timeout); + btv->timeout.function = bttv_irq_timeout; + btv->timeout.data = (unsigned long)btv; + + btv->i2c_rc = -1; + btv->tuner_type = UNSET; + btv->pinnacle_id = UNSET; + btv->new_input = UNSET; + btv->gpioirq = 1; + btv->has_radio=radio[btv->c.nr]; + + /* pci stuff (init, get irq/mmio, ... */ + btv->c.pci = dev; + btv->id = dev->device; + if (pci_enable_device(dev)) { + printk(KERN_WARNING "bttv%d: Can't enable device.\n", + btv->c.nr); + return -EIO; + } + if (pci_set_dma_mask(dev, 0xffffffff)) { + printk(KERN_WARNING "bttv%d: No suitable DMA available.\n", + btv->c.nr); + return -EIO; + } + if (!request_mem_region(pci_resource_start(dev,0), + pci_resource_len(dev,0), + btv->c.name)) { + printk(KERN_WARNING "bttv%d: can't request iomem (0x%lx).\n", + btv->c.nr, pci_resource_start(dev,0)); + return -EBUSY; + } + pci_set_master(dev); + pci_set_command(dev); + pci_set_drvdata(dev,btv); + if (!pci_dma_supported(dev,0xffffffff)) { + printk("bttv%d: Oops: no 32bit PCI DMA ???\n", btv->c.nr); + result = -EIO; + goto fail1; + } + + pci_read_config_byte(dev, PCI_CLASS_REVISION, &btv->revision); + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat); + printk(KERN_INFO "bttv%d: Bt%d (rev %d) at %s, ", + bttv_num,btv->id, btv->revision, pci_name(dev)); + printk("irq: %d, latency: %d, mmio: 0x%lx\n", + btv->c.pci->irq, lat, pci_resource_start(dev,0)); + schedule(); + + btv->bt848_mmio=ioremap(pci_resource_start(dev,0), 0x1000); + if (NULL == ioremap(pci_resource_start(dev,0), 0x1000)) { + printk("bttv%d: ioremap() failed\n", btv->c.nr); + result = -EIO; + goto fail1; + } + + /* identify card */ + bttv_idcard(btv); + + /* disable irqs, register irq handler */ + btwrite(0, BT848_INT_MASK); + result = request_irq(btv->c.pci->irq, bttv_irq, + SA_SHIRQ | SA_INTERRUPT,btv->c.name,(void *)btv); + if (result < 0) { + printk(KERN_ERR "bttv%d: can't get IRQ %d\n", + bttv_num,btv->c.pci->irq); + goto fail1; + } + + if (0 != bttv_handle_chipset(btv)) { + result = -EIO; + goto fail2; + } + + /* init options from insmod args */ + btv->opt_combfilter = combfilter; + btv->opt_lumafilter = lumafilter; + btv->opt_automute = automute; + btv->opt_chroma_agc = chroma_agc; + btv->opt_adc_crush = adc_crush; + btv->opt_vcr_hack = vcr_hack; + btv->opt_whitecrush_upper = whitecrush_upper; + btv->opt_whitecrush_lower = whitecrush_lower; + + /* fill struct bttv with some useful defaults */ + btv->init.btv = btv; + btv->init.ov.w.width = 320; + btv->init.ov.w.height = 240; + btv->init.fmt = format_by_palette(VIDEO_PALETTE_RGB24); + btv->init.width = 320; + btv->init.height = 240; + btv->init.lines = 16; + btv->input = 0; + + /* initialize hardware */ + if (bttv_gpio) + bttv_gpio_tracking(btv,"pre-init"); + + bttv_risc_init_main(btv); + init_bt848(btv); + + /* gpio */ + btwrite(0x00, BT848_GPIO_REG_INP); + btwrite(0x00, BT848_GPIO_OUT_EN); + if (bttv_verbose) + bttv_gpio_tracking(btv,"init"); + + /* needs to be done before i2c is registered */ + bttv_init_card1(btv); + + /* register i2c + gpio */ + init_bttv_i2c(btv); + + /* some card-specific stuff (needs working i2c) */ + bttv_init_card2(btv); + init_irqreg(btv); + + /* register video4linux + input */ + if (!bttv_tvcards[btv->c.type].no_video) { + bttv_register_video(btv); + bt848_bright(btv,32768); + bt848_contrast(btv,32768); + bt848_hue(btv,32768); + bt848_sat(btv,32768); + audio_mux(btv,AUDIO_MUTE); + set_input(btv,0); + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + /* add subdevices */ + if (btv->has_remote) + bttv_sub_add_device(&btv->c, "remote"); + if (bttv_tvcards[btv->c.type].has_dvb) + bttv_sub_add_device(&btv->c, "dvb"); +#endif + + /* everything is fine */ + bttv_num++; + return 0; + + fail2: + free_irq(btv->c.pci->irq,btv); + + fail1: + if (btv->bt848_mmio) + iounmap(btv->bt848_mmio); + release_mem_region(pci_resource_start(btv->c.pci,0), + pci_resource_len(btv->c.pci,0)); + pci_set_drvdata(dev,NULL); + return result; +} + +static void __devexit bttv_remove(struct pci_dev *pci_dev) +{ + struct bttv *btv = pci_get_drvdata(pci_dev); + + if (bttv_verbose) + printk("bttv%d: unloading\n",btv->c.nr); + + /* shutdown everything (DMA+IRQs) */ + btand(~15, BT848_GPIO_DMA_CTL); + btwrite(0, BT848_INT_MASK); + btwrite(~0x0, BT848_INT_STAT); + btwrite(0x0, BT848_GPIO_OUT_EN); + if (bttv_gpio) + bttv_gpio_tracking(btv,"cleanup"); + + /* tell gpio modules we are leaving ... */ + btv->shutdown=1; + wake_up(&btv->gpioq); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + bttv_sub_del_devices(&btv->c); +#endif + + /* unregister i2c_bus + input */ + fini_bttv_i2c(btv); + + /* unregister video4linux */ + bttv_unregister_video(btv); + + /* free allocated memory */ + btcx_riscmem_free(btv->c.pci,&btv->main); + + /* free ressources */ + free_irq(btv->c.pci->irq,btv); + iounmap(btv->bt848_mmio); + release_mem_region(pci_resource_start(btv->c.pci,0), + pci_resource_len(btv->c.pci,0)); + + pci_set_drvdata(pci_dev, NULL); + return; +} + +static int bttv_suspend(struct pci_dev *pci_dev, u32 state) +{ + struct bttv *btv = pci_get_drvdata(pci_dev); + struct bttv_buffer_set idle; + unsigned long flags; + + printk("bttv%d: suspend %d\n", btv->c.nr, state); + + /* stop dma + irqs */ + spin_lock_irqsave(&btv->s_lock,flags); + memset(&idle, 0, sizeof(idle)); + btv->state.set = btv->curr; + btv->curr = idle; + bttv_buffer_set_activate(btv, &idle); + bttv_set_dma(btv, 0, 0); + btwrite(0, BT848_INT_MASK); + spin_unlock_irqrestore(&btv->s_lock,flags); + + /* save bt878 state */ + btv->state.gpio_enable = btread(BT848_GPIO_OUT_EN); + btv->state.gpio_data = gpio_read(); + + /* save pci state */ + pci_save_state(pci_dev, btv->state.pci_cfg); + if (0 != pci_set_power_state(pci_dev, state)) { + pci_disable_device(pci_dev); + btv->state.disabled = 1; + } + return 0; +} + +static int bttv_resume(struct pci_dev *pci_dev) +{ + struct bttv *btv = pci_get_drvdata(pci_dev); + unsigned long flags; + + printk("bttv%d: resume\n", btv->c.nr); + + /* restore pci state */ + if (btv->state.disabled) { + pci_enable_device(pci_dev); + btv->state.disabled = 0; + } + pci_set_power_state(pci_dev, 0); + pci_restore_state(pci_dev, btv->state.pci_cfg); + + /* restore bt878 state */ + bttv_reinit_bt848(btv); + gpio_inout(0xffffff, btv->state.gpio_enable); + gpio_write(btv->state.gpio_data); + + bt848_bright(btv, btv->bright); + bt848_hue(btv, btv->hue); + bt848_contrast(btv, btv->contrast); + bt848_sat(btv, btv->saturation); + + /* restart dma */ + spin_lock_irqsave(&btv->s_lock,flags); + btv->curr = btv->state.set; + bttv_buffer_set_activate(btv, &btv->curr); + bttv_set_dma(btv, 0, btv->curr.irqflags); + spin_unlock_irqrestore(&btv->s_lock,flags); + return 0; +} + +static struct pci_device_id bttv_pci_tbl[] = { + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT848, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT849, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT878, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT879, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, bttv_pci_tbl); + +static struct pci_driver bttv_pci_driver = { + .name = "bttv", + .id_table = bttv_pci_tbl, + .probe = bttv_probe, + .remove = __devexit_p(bttv_remove), + + .suspend = bttv_suspend, + .resume = bttv_resume, +}; + +static int bttv_init_module(void) +{ + int rc; + bttv_num = 0; + + printk(KERN_INFO "bttv: driver version %d.%d.%d loaded\n", + (BTTV_VERSION_CODE >> 16) & 0xff, + (BTTV_VERSION_CODE >> 8) & 0xff, + BTTV_VERSION_CODE & 0xff); +#ifdef SNAPSHOT + printk(KERN_INFO "bttv: snapshot date %04d-%02d-%02d\n", + SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100); +#endif + if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) + gbuffers = 2; + if (gbufsize < 0 || gbufsize > BTTV_MAX_FBUF) + gbufsize = BTTV_MAX_FBUF; + gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK; + if (bttv_verbose) + printk(KERN_INFO "bttv: using %d buffers with %dk (%d pages) each for capture\n", + gbuffers, gbufsize >> 10, gbufsize >> PAGE_SHIFT); + + bttv_check_chipset(); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + bus_register(&bttv_sub_bus_type); +#endif + rc = pci_module_init(&bttv_pci_driver); + if (-ENODEV == rc) { + /* plenty of people trying to use bttv for the cx2388x ... */ + if (NULL != pci_find_device(0x14f1, 0x8800, NULL)) + printk("bttv doesn't support your Conexant 2388x card.\n"); + } + return rc; +} + +static void bttv_cleanup_module(void) +{ + pci_unregister_driver(&bttv_pci_driver); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + bus_unregister(&bttv_sub_bus_type); +#endif + return; +} + +module_init(bttv_init_module); +module_exit(bttv_cleanup_module); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/bttv-gpio.c b/linux/drivers/media/video/bttv-gpio.c new file mode 100644 index 000000000..01ce7bc32 --- /dev/null +++ b/linux/drivers/media/video/bttv-gpio.c @@ -0,0 +1,183 @@ +/* + bttv-gpio.c -- gpio sub drivers + + sysfs-based sub driver interface for bttv + mainly intented for gpio access + + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2003 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <asm/io.h> + +#include "bttvp.h" + +/* ----------------------------------------------------------------------- */ +/* internal: the bttv "bus" */ + +static int bttv_sub_bus_match(struct device *dev, struct device_driver *drv) +{ + struct bttv_sub_driver *sub = to_bttv_sub_drv(drv); + int len = strlen(sub->wanted); + + if (0 == strncmp(dev->bus_id, sub->wanted, len)) + return 1; + return 0; +} + +struct bus_type bttv_sub_bus_type = { + .name = "bttv-sub", + .match = &bttv_sub_bus_match, +}; +EXPORT_SYMBOL(bttv_sub_bus_type); + +static void release_sub_device(struct device *dev) +{ + struct bttv_sub_device *sub = to_bttv_sub_dev(dev); + kfree(sub); +} + +int bttv_sub_add_device(struct bttv_core *core, char *name) +{ + struct bttv_sub_device *sub; + + sub = kmalloc(sizeof(*sub),GFP_KERNEL); + if (NULL == sub) + return -ENOMEM; + memset(sub,0,sizeof(*sub)); + + sub->core = core; + sub->dev.parent = &core->pci->dev; + sub->dev.bus = &bttv_sub_bus_type; + sub->dev.release = release_sub_device; + snprintf(sub->dev.bus_id,sizeof(sub->dev.bus_id),"%s%d", + name, core->nr); + + printk("bttv%d: add subdevice \"%s\"\n", core->nr, sub->dev.bus_id); + list_add_tail(&sub->list,&core->subs); + device_register(&sub->dev); + return 0; +} + +int bttv_sub_del_devices(struct bttv_core *core) +{ + struct bttv_sub_device *sub; + struct list_head *item,*save; + + list_for_each_safe(item,save,&core->subs) { + sub = list_entry(item,struct bttv_sub_device,list); + device_unregister(&sub->dev); + } + return 0; +} + +void bttv_gpio_irq(struct bttv_core *core) +{ + struct bttv_sub_driver *drv; + struct bttv_sub_device *dev; + struct list_head *item; + + 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->gpio_irq) + drv->gpio_irq(dev); + } +} + +/* ----------------------------------------------------------------------- */ +/* external: sub-driver register/unregister */ + +int bttv_sub_register(struct bttv_sub_driver *sub, char *wanted) +{ + sub->drv.bus = &bttv_sub_bus_type; + snprintf(sub->wanted,sizeof(sub->wanted),"%s",wanted); + driver_register(&sub->drv); + return 0; +} +EXPORT_SYMBOL(bttv_sub_register); + +int bttv_sub_unregister(struct bttv_sub_driver *sub) +{ + driver_unregister(&sub->drv); + return 0; +} +EXPORT_SYMBOL(bttv_sub_unregister); + +/* ----------------------------------------------------------------------- */ +/* external: gpio access functions */ + +void bttv_gpio_inout(struct bttv_core *core, u32 mask, u32 outbits) +{ + struct bttv *btv = container_of(core, struct bttv, c); + unsigned long flags; + u32 data; + + spin_lock_irqsave(&btv->gpio_lock,flags); + data = btread(BT848_GPIO_OUT_EN); + data = data & ~mask; + data = data | (mask & outbits); + btwrite(data,BT848_GPIO_OUT_EN); + spin_unlock_irqrestore(&btv->gpio_lock,flags); +} +EXPORT_SYMBOL(bttv_gpio_inout); + +u32 bttv_gpio_read(struct bttv_core *core) +{ + struct bttv *btv = container_of(core, struct bttv, c); + u32 value; + + value = btread(BT848_GPIO_DATA); + return value; +} +EXPORT_SYMBOL(bttv_gpio_read); + +void bttv_gpio_write(struct bttv_core *core, u32 value) +{ + struct bttv *btv = container_of(core, struct bttv, c); + + btwrite(value,BT848_GPIO_DATA); +} +EXPORT_SYMBOL(bttv_gpio_write); + +void bttv_gpio_bits(struct bttv_core *core, u32 mask, u32 bits) +{ + struct bttv *btv = container_of(core, struct bttv, c); + unsigned long flags; + u32 data; + + spin_lock_irqsave(&btv->gpio_lock,flags); + data = btread(BT848_GPIO_DATA); + data = data & ~mask; + data = data | (mask & bits); + btwrite(data,BT848_GPIO_DATA); + spin_unlock_irqrestore(&btv->gpio_lock,flags); +} +EXPORT_SYMBOL(bttv_gpio_bits); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/bttv-i2c.c b/linux/drivers/media/video/bttv-i2c.c new file mode 100644 index 000000000..a405e5124 --- /dev/null +++ b/linux/drivers/media/video/bttv-i2c.c @@ -0,0 +1,473 @@ +/* + bttv-i2c.c -- all the i2c code is here + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2003 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <asm/io.h> + +#include "bttvp.h" + +static struct i2c_algo_bit_data bttv_i2c_algo_bit_template; +static struct i2c_adapter bttv_i2c_adap_sw_template; +static struct i2c_adapter bttv_i2c_adap_hw_template; +static struct i2c_client bttv_i2c_client_template; + +#ifndef I2C_PEC +static void bttv_inc_use(struct i2c_adapter *adap); +static void bttv_dec_use(struct i2c_adapter *adap); +#endif +static int attach_inform(struct i2c_client *client); + +static int i2c_debug = 0; +static int i2c_hw = 0; +MODULE_PARM(i2c_debug,"i"); +MODULE_PARM(i2c_hw,"i"); + +/* ----------------------------------------------------------------------- */ +/* I2C functions - bitbanging adapter (software i2c) */ + +void bttv_bit_setscl(void *data, int state) +{ + struct bttv *btv = (struct bttv*)data; + + if (state) + btv->i2c_state |= 0x02; + else + btv->i2c_state &= ~0x02; + btwrite(btv->i2c_state, BT848_I2C); + btread(BT848_I2C); +} + +void bttv_bit_setsda(void *data, int state) +{ + struct bttv *btv = (struct bttv*)data; + + if (state) + btv->i2c_state |= 0x01; + else + btv->i2c_state &= ~0x01; + btwrite(btv->i2c_state, BT848_I2C); + btread(BT848_I2C); +} + +static int bttv_bit_getscl(void *data) +{ + struct bttv *btv = (struct bttv*)data; + int state; + + state = btread(BT848_I2C) & 0x02 ? 1 : 0; + return state; +} + +static int bttv_bit_getsda(void *data) +{ + struct bttv *btv = (struct bttv*)data; + int state; + + state = btread(BT848_I2C) & 0x01; + return state; +} + +static struct i2c_algo_bit_data bttv_i2c_algo_bit_template = { + .setsda = bttv_bit_setsda, + .setscl = bttv_bit_setscl, + .getsda = bttv_bit_getsda, + .getscl = bttv_bit_getscl, + .udelay = 16, + .mdelay = 10, + .timeout = 200, +}; + +static struct i2c_adapter bttv_i2c_adap_sw_template = { +#ifdef I2C_PEC + .owner = THIS_MODULE, +#else + .inc_use = bttv_inc_use, + .dec_use = bttv_dec_use, +#endif +#ifdef I2C_ADAP_CLASS_TV_ANALOG + .class = I2C_ADAP_CLASS_TV_ANALOG, +#endif + I2C_DEVNAME("bt848"), + .id = I2C_HW_B_BT848, + .client_register = attach_inform, +}; + +/* ----------------------------------------------------------------------- */ +/* I2C functions - hardware i2c */ + +static int algo_control(struct i2c_adapter *adapter, + unsigned int cmd, unsigned long arg) +{ + return 0; +} + +static u32 functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +static int +bttv_i2c_wait_done(struct bttv *btv) +{ + DECLARE_WAITQUEUE(wait, current); + int rc = 0; + + add_wait_queue(&btv->i2c_queue, &wait); + set_current_state(TASK_INTERRUPTIBLE); + if (0 == btv->i2c_done) + schedule_timeout(HZ/50+1); + set_current_state(TASK_RUNNING); + remove_wait_queue(&btv->i2c_queue, &wait); + + if (0 == btv->i2c_done) + /* timeout */ + rc = -EIO; + if (btv->i2c_done & BT848_INT_RACK) + rc = 1; + btv->i2c_done = 0; + return rc; +} + +#define I2C_HW (BT878_I2C_MODE | BT848_I2C_SYNC |\ + BT848_I2C_SCL | BT848_I2C_SDA) + +static int +bttv_i2c_sendbytes(struct bttv *btv, const struct i2c_msg *msg, int last) +{ + u32 xmit; + int retval,cnt; + + /* sanity checks */ + if (0 == msg->len) + return -EINVAL; + + /* start, address + first byte */ + xmit = (msg->addr << 25) | (msg->buf[0] << 16) | I2C_HW; + if (msg->len > 1 || !last) + xmit |= BT878_I2C_NOSTOP; + btwrite(xmit, BT848_I2C); + retval = bttv_i2c_wait_done(btv); + if (retval < 0) + goto err; + if (retval == 0) + goto eio; + if (i2c_debug) { + printk(" <W %02x %02x", msg->addr << 1, msg->buf[0]); + if (!(xmit & BT878_I2C_NOSTOP)) + printk(" >\n"); + } + + for (cnt = 1; cnt < msg->len; cnt++ ) { + /* following bytes */ + xmit = (msg->buf[cnt] << 24) | I2C_HW | BT878_I2C_NOSTART; + if (cnt < msg->len-1 || !last) + xmit |= BT878_I2C_NOSTOP; + btwrite(xmit, BT848_I2C); + retval = bttv_i2c_wait_done(btv); + if (retval < 0) + goto err; + if (retval == 0) + goto eio; + if (i2c_debug) { + printk(" %02x", msg->buf[cnt]); + if (!(xmit & BT878_I2C_NOSTOP)) + printk(" >\n"); + } + } + return msg->len; + + eio: + retval = -EIO; + err: + if (i2c_debug) + printk(" ERR: %d\n",retval); + return retval; +} + +static int +bttv_i2c_readbytes(struct bttv *btv, const struct i2c_msg *msg, int last) +{ + u32 xmit; + u32 cnt; + int retval; + + for(cnt = 0; cnt < msg->len; cnt++) { + xmit = (msg->addr << 25) | (1 << 24) | I2C_HW; + if (cnt < msg->len-1) + xmit |= BT848_I2C_W3B; + if (cnt < msg->len-1 || !last) + xmit |= BT878_I2C_NOSTOP; + if (cnt) + xmit |= BT878_I2C_NOSTART; + btwrite(xmit, BT848_I2C); + retval = bttv_i2c_wait_done(btv); + if (retval < 0) + goto err; + if (retval == 0) + goto eio; + msg->buf[cnt] = ((u32)btread(BT848_I2C) >> 8) & 0xff; + if (i2c_debug) { + if (!(xmit & BT878_I2C_NOSTART)) + printk(" <R %02x", (msg->addr << 1) +1); + printk(" =%02x", msg->buf[cnt]); + if (!(xmit & BT878_I2C_NOSTOP)) + printk(" >\n"); + } + } + return msg->len; + + eio: + retval = -EIO; + err: + if (i2c_debug) + printk(" ERR: %d\n",retval); + return retval; +} + +int bttv_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num) +{ + struct bttv *btv = i2c_get_adapdata(i2c_adap); + int retval = 0; + int i; + + if (i2c_debug) + printk("bt-i2c:"); + btwrite(BT848_INT_I2CDONE|BT848_INT_RACK, BT848_INT_STAT); + for (i = 0 ; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) { + /* read */ + retval = bttv_i2c_readbytes(btv, &msgs[i], i+1 == num); + if (retval < 0) + goto err; + } else { + /* write */ + retval = bttv_i2c_sendbytes(btv, &msgs[i], i+1 == num); + if (retval < 0) + goto err; + } + } + return num; + + err: + return retval; +} + +static struct i2c_algorithm bttv_algo = { + .name = "bt878", + .id = I2C_ALGO_BIT | I2C_HW_B_BT848 /* FIXME */, + .master_xfer = bttv_i2c_xfer, + .algo_control = algo_control, + .functionality = functionality, +}; + +static struct i2c_adapter bttv_i2c_adap_hw_template = { +#ifdef I2C_PEC + .owner = THIS_MODULE, +#else + .inc_use = bttv_inc_use, + .dec_use = bttv_dec_use, +#endif +#ifdef I2C_ADAP_CLASS_TV_ANALOG + .class = I2C_ADAP_CLASS_TV_ANALOG, +#endif + I2C_DEVNAME("bt878"), + .id = I2C_ALGO_BIT | I2C_HW_B_BT848 /* FIXME */, + .algo = &bttv_algo, + .client_register = attach_inform, +}; + +/* ----------------------------------------------------------------------- */ +/* I2C functions - common stuff */ + +#ifndef I2C_PEC +static void bttv_inc_use(struct i2c_adapter *adap) +{ + MOD_INC_USE_COUNT; +} + +static void bttv_dec_use(struct i2c_adapter *adap) +{ + MOD_DEC_USE_COUNT; +} +#endif + +static int attach_inform(struct i2c_client *client) +{ + struct bttv *btv = i2c_get_adapdata(client->adapter); + + if (btv->tuner_type != UNSET) + bttv_call_i2c_clients(btv,TUNER_SET_TYPE,&btv->tuner_type); + if (btv->pinnacle_id != UNSET) + bttv_call_i2c_clients(btv,AUDC_CONFIG_PINNACLE, + &btv->pinnacle_id); + + if (bttv_debug) + printk("bttv%d: i2c attach [client=%s]\n", + btv->c.nr, i2c_clientname(client)); + return 0; +} + +void bttv_call_i2c_clients(struct bttv *btv, unsigned int cmd, void *arg) +{ + if (0 != btv->i2c_rc) + return; + i2c_clients_command(&btv->c.i2c_adap, cmd, arg); +} + +void bttv_i2c_call(unsigned int card, unsigned int cmd, void *arg) +{ + if (card >= bttv_num) + return; + bttv_call_i2c_clients(&bttvs[card], cmd, arg); +} + +static struct i2c_client bttv_i2c_client_template = { + I2C_DEVNAME("bttv internal"), + .id = -1, +}; + + +/* read I2C */ +int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for) +{ + unsigned char buffer = 0; + + if (0 != btv->i2c_rc) + return -1; + if (bttv_verbose && NULL != probe_for) + printk(KERN_INFO "bttv%d: i2c: checking for %s @ 0x%02x... ", + btv->c.nr,probe_for,addr); + btv->i2c_client.addr = addr >> 1; + if (1 != i2c_master_recv(&btv->i2c_client, &buffer, 1)) { + if (NULL != probe_for) { + if (bttv_verbose) + printk("not found\n"); + } else + printk(KERN_WARNING "bttv%d: i2c read 0x%x: error\n", + btv->c.nr,addr); + return -1; + } + if (bttv_verbose && NULL != probe_for) + printk("found\n"); + return buffer; +} + +/* write I2C */ +int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1, + unsigned char b2, int both) +{ + unsigned char buffer[2]; + int bytes = both ? 2 : 1; + + if (0 != btv->i2c_rc) + return -1; + btv->i2c_client.addr = addr >> 1; + buffer[0] = b1; + buffer[1] = b2; + if (bytes != i2c_master_send(&btv->i2c_client, buffer, bytes)) + return -1; + return 0; +} + +/* read EEPROM content */ +void __devinit bttv_readee(struct bttv *btv, unsigned char *eedata, int addr) +{ + int i; + + if (bttv_I2CWrite(btv, addr, 0, -1, 0)<0) { + printk(KERN_WARNING "bttv: readee error\n"); + return; + } + btv->i2c_client.addr = addr >> 1; + for (i=0; i<256; i+=16) { + if (16 != i2c_master_recv(&btv->i2c_client,eedata+i,16)) { + printk(KERN_WARNING "bttv: readee error\n"); + break; + } + } +} + +/* init + register i2c algo-bit adapter */ +int __devinit init_bttv_i2c(struct bttv *btv) +{ + int use_hw = (btv->id == 878) && i2c_hw; + + memcpy(&btv->i2c_client, &bttv_i2c_client_template, + sizeof(bttv_i2c_client_template)); + + if (use_hw) { + /* bt878 */ + memcpy(&btv->c.i2c_adap, &bttv_i2c_adap_hw_template, + sizeof(bttv_i2c_adap_hw_template)); + } else { + /* bt848 */ + memcpy(&btv->c.i2c_adap, &bttv_i2c_adap_sw_template, + sizeof(bttv_i2c_adap_sw_template)); + memcpy(&btv->i2c_algo, &bttv_i2c_algo_bit_template, + sizeof(bttv_i2c_algo_bit_template)); + btv->i2c_algo.data = btv; + btv->c.i2c_adap.algo_data = &btv->i2c_algo; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,66) + btv->c.i2c_adap.dev.parent = &btv->c.pci->dev; +#endif + snprintf(btv->c.i2c_adap.name, sizeof(btv->c.i2c_adap.name), + "bt%d #%d [%s]", btv->id, btv->c.nr, use_hw ? "hw" : "sw"); + + i2c_set_adapdata(&btv->c.i2c_adap, btv); + btv->i2c_client.adapter = &btv->c.i2c_adap; + + if (use_hw) { + btv->i2c_rc = i2c_add_adapter(&btv->c.i2c_adap); + } else { + bttv_bit_setscl(btv,1); + bttv_bit_setsda(btv,1); + btv->i2c_rc = i2c_bit_add_bus(&btv->c.i2c_adap); + } + return btv->i2c_rc; +} + +int __devexit fini_bttv_i2c(struct bttv *btv) +{ + int use_hw = (btv->id == 878) && i2c_hw; + + if (0 != btv->i2c_rc) + return 0; + + if (use_hw) { + return i2c_del_adapter(&btv->c.i2c_adap); + } else { + return i2c_bit_del_bus(&btv->c.i2c_adap); + } +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/bttv-if.c b/linux/drivers/media/video/bttv-if.c new file mode 100644 index 000000000..54a52fdcf --- /dev/null +++ b/linux/drivers/media/video/bttv-if.c @@ -0,0 +1,159 @@ +/* + bttv-if.c -- old gpio interface to other kernel modules + don't use in new code, will go away in 2.7 + have a look at bttv-gpio.c instead. + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2003 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <asm/io.h> + +#include "bttvp.h" + +EXPORT_SYMBOL(bttv_get_cardinfo); +EXPORT_SYMBOL(bttv_get_pcidev); +EXPORT_SYMBOL(bttv_get_id); +EXPORT_SYMBOL(bttv_gpio_enable); +EXPORT_SYMBOL(bttv_read_gpio); +EXPORT_SYMBOL(bttv_write_gpio); +EXPORT_SYMBOL(bttv_get_gpio_queue); +EXPORT_SYMBOL(bttv_i2c_call); + +/* ----------------------------------------------------------------------- */ +/* Exported functions - for other modules which want to access the */ +/* gpio ports (IR for example) */ +/* see bttv.h for comments */ + +int bttv_get_cardinfo(unsigned int card, int *type, unsigned *cardid) +{ + if (card >= bttv_num) { + return -1; + } + *type = bttvs[card].c.type; + *cardid = bttvs[card].cardid; + return 0; +} + +struct pci_dev* bttv_get_pcidev(unsigned int card) +{ + if (card >= bttv_num) + return NULL; + return bttvs[card].c.pci; +} + +int bttv_get_id(unsigned int card) +{ + printk("bttv_get_id is obsolete, use bttv_get_cardinfo instead\n"); + if (card >= bttv_num) { + return -1; + } + return bttvs[card].c.type; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +/* with 2.6.x not needed thanks to the driver model + sysfs */ +struct i2c_adapter *bttv_get_i2c_adap(unsigned int card) +{ + if (card >= bttv_num) { + return NULL; + } + return &bttvs[card].c.i2c_adap; +} +EXPORT_SYMBOL(bttv_get_i2c_adap); +#endif + +int bttv_gpio_enable(unsigned int card, unsigned long mask, unsigned long data) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = &bttvs[card]; + gpio_inout(mask,data); + if (bttv_gpio) + bttv_gpio_tracking(btv,"extern enable"); + return 0; +} + +int bttv_read_gpio(unsigned int card, unsigned long *data) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = &bttvs[card]; + + if(btv->shutdown) { + return -ENODEV; + } + +/* prior setting BT848_GPIO_REG_INP is (probably) not needed + because we set direct input on init */ + *data = gpio_read(); + return 0; +} + +int bttv_write_gpio(unsigned int card, unsigned long mask, unsigned long data) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = &bttvs[card]; + +/* prior setting BT848_GPIO_REG_INP is (probably) not needed + because direct input is set on init */ + gpio_bits(mask,data); + if (bttv_gpio) + bttv_gpio_tracking(btv,"extern write"); + return 0; +} + +wait_queue_head_t* bttv_get_gpio_queue(unsigned int card) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return NULL; + } + + btv = &bttvs[card]; + if (bttvs[card].shutdown) { + return NULL; + } + return &btv->gpioq; +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/bttv-risc.c b/linux/drivers/media/video/bttv-risc.c new file mode 100644 index 000000000..c512416a0 --- /dev/null +++ b/linux/drivers/media/video/bttv-risc.c @@ -0,0 +1,770 @@ +/* + bttv-risc.c -- interfaces to other kernel modules + + bttv risc code handling + - memory management + - generation + + (c) 2000-2003 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/vmalloc.h> +#include <linux/interrupt.h> +#include <asm/page.h> +#include <asm/pgtable.h> + +#include "bttvp.h" + +#define VCR_HACK_LINES 4 + +/* ---------------------------------------------------------- */ +/* risc code generators */ + +int +bttv_risc_packed(struct bttv *btv, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int offset, unsigned int bpl, + unsigned int padding, unsigned int lines) +{ + u32 instructions,line,todo; + struct scatterlist *sg; + u32 *rp; + int rc; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + sync + jump (all 2 dwords) */ + instructions = (bpl * lines) / PAGE_SIZE + lines; + instructions += 2; + if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions*8)) < 0) + return rc; + + /* sync instruction */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(rp++) = cpu_to_le32(0); + + /* scan lines */ + sg = sglist; + for (line = 0; line < lines; line++) { + if ((btv->opt_vcr_hack) && + (line >= (lines - VCR_HACK_LINES))) + continue; + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg++; + } + if (bpl <= sg_dma_len(sg)-offset) { + /* fits into current chunk */ + *(rp++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL| + BT848_RISC_EOL|bpl); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + offset+=bpl; + } else { + /* scanline needs to be splitted */ + todo = bpl; + *(rp++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL| + (sg_dma_len(sg)-offset)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + todo -= (sg_dma_len(sg)-offset); + offset = 0; + sg++; + while (todo > sg_dma_len(sg)) { + *(rp++)=cpu_to_le32(BT848_RISC_WRITE| + sg_dma_len(sg)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + todo -= sg_dma_len(sg); + sg++; + } + *(rp++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_EOL| + todo); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + offset += todo; + } + offset += padding; + } + dprintk("bttv%d: risc planar: %d sglist elems\n", btv->c.nr, (int)(sg-sglist)); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + return 0; +} + +int +bttv_risc_planar(struct bttv *btv, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int yoffset, unsigned int ybpl, + unsigned int ypadding, unsigned int ylines, + unsigned int uoffset, unsigned int voffset, + unsigned int hshift, unsigned int vshift, + unsigned int cpadding) +{ + unsigned int instructions,line,todo,ylen,chroma; + u32 *rp,ri; + struct scatterlist *ysg; + struct scatterlist *usg; + struct scatterlist *vsg; + int rc; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line (5 dwords) + plus sync + jump (2 dwords) */ + instructions = (ybpl * ylines * 2) / PAGE_SIZE + ylines; + instructions += 2; + if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions*4*5)) < 0) + return rc; + + /* sync instruction */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM3); + *(rp++) = cpu_to_le32(0); + + /* scan lines */ + ysg = sglist; + usg = sglist; + vsg = sglist; + for (line = 0; line < ylines; line++) { + if ((btv->opt_vcr_hack) && + (line >= (ylines - VCR_HACK_LINES))) + continue; + switch (vshift) { + case 0: chroma = 1; break; + case 1: chroma = !(line & 1); break; + case 2: chroma = !(line & 3); break; + default: chroma = 0; + } + for (todo = ybpl; todo > 0; todo -= ylen) { + /* go to next sg entry if needed */ + while (yoffset && yoffset >= sg_dma_len(ysg)) { + yoffset -= sg_dma_len(ysg); + ysg++; + } + while (uoffset && uoffset >= sg_dma_len(usg)) { + uoffset -= sg_dma_len(usg); + usg++; + } + while (voffset && voffset >= sg_dma_len(vsg)) { + voffset -= sg_dma_len(vsg); + vsg++; + } + + /* calculate max number of bytes we can write */ + ylen = todo; + if (yoffset + ylen > sg_dma_len(ysg)) + ylen = sg_dma_len(ysg) - yoffset; + if (chroma) { + if (uoffset + (ylen>>hshift) > sg_dma_len(usg)) + ylen = (sg_dma_len(usg) - uoffset) << hshift; + if (voffset + (ylen>>hshift) > sg_dma_len(vsg)) + ylen = (sg_dma_len(vsg) - voffset) << hshift; + ri = BT848_RISC_WRITE123; + } else { + ri = BT848_RISC_WRITE1S23; + } + if (ybpl == todo) + ri |= BT848_RISC_SOL; + if (ylen == todo) + ri |= BT848_RISC_EOL; + + /* write risc instruction */ + *(rp++)=cpu_to_le32(ri | ylen); + *(rp++)=cpu_to_le32(((ylen >> hshift) << 16) | + (ylen >> hshift)); + *(rp++)=cpu_to_le32(sg_dma_address(ysg)+yoffset); + yoffset += ylen; + if (chroma) { + *(rp++)=cpu_to_le32(sg_dma_address(usg)+uoffset); + uoffset += ylen >> hshift; + *(rp++)=cpu_to_le32(sg_dma_address(vsg)+voffset); + voffset += ylen >> hshift; + } + } + yoffset += ypadding; + if (chroma) { + uoffset += cpadding; + voffset += cpadding; + } + } + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + return 0; +} + +int +bttv_risc_overlay(struct bttv *btv, struct btcx_riscmem *risc, + const struct bttv_format *fmt, struct bttv_overlay *ov, + int skip_even, int skip_odd) +{ + int instructions,rc,line,maxy,start,end,skip,nskips; + struct btcx_skiplist *skips; + u32 *rp,ri,ra; + u32 addr; + + /* skip list for window clipping */ + if (NULL == (skips = kmalloc(sizeof(*skips) * ov->nclips,GFP_KERNEL))) + return -ENOMEM; + + /* estimate risc mem: worst case is (clip+1) * lines instructions + + sync + jump (all 2 dwords) */ + instructions = (ov->nclips + 1) * + ((skip_even || skip_odd) ? ov->w.height>>1 : ov->w.height); + instructions += 2; + if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions*8)) < 0) { + kfree(skips); + return rc; + } + + /* sync instruction */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(rp++) = cpu_to_le32(0); + + addr = (unsigned long)btv->fbuf.base; + addr += btv->fbuf.fmt.bytesperline * ov->w.top; + addr += (fmt->depth >> 3) * ov->w.left; + + /* scan lines */ + for (maxy = -1, line = 0; line < ov->w.height; + line++, addr += btv->fbuf.fmt.bytesperline) { + if ((btv->opt_vcr_hack) && + (line >= (ov->w.height - VCR_HACK_LINES))) + continue; + if ((line%2) == 0 && skip_even) + continue; + if ((line%2) == 1 && skip_odd) + continue; + + /* calculate clipping */ + if (line > maxy) + btcx_calc_skips(line, ov->w.width, &maxy, + skips, &nskips, ov->clips, ov->nclips); + + /* write out risc code */ + for (start = 0, skip = 0; start < ov->w.width; start = end) { + if (skip >= nskips) { + ri = BT848_RISC_WRITE; + end = ov->w.width; + } else if (start < skips[skip].start) { + ri = BT848_RISC_WRITE; + end = skips[skip].start; + } else { + ri = BT848_RISC_SKIP; + end = skips[skip].end; + skip++; + } + if (BT848_RISC_WRITE == ri) + ra = addr + (fmt->depth>>3)*start; + else + ra = 0; + + if (0 == start) + ri |= BT848_RISC_SOL; + if (ov->w.width == end) + ri |= BT848_RISC_EOL; + ri |= (fmt->depth>>3) * (end-start); + + *(rp++)=cpu_to_le32(ri); + if (0 != ra) + *(rp++)=cpu_to_le32(ra); + } + } + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + kfree(skips); + return 0; +} + +/* ---------------------------------------------------------- */ + +void +bttv_calc_geo(struct bttv *btv, struct bttv_geometry *geo, + int width, int height, int interleaved, int norm) +{ + const struct bttv_tvnorm *tvnorm = &bttv_tvnorms[norm]; + u32 xsf, sr; + int vdelay; + + int swidth = tvnorm->swidth; + int totalwidth = tvnorm->totalwidth; + int scaledtwidth = tvnorm->scaledtwidth; + + if (bttv_tvcards[btv->c.type].muxsel[btv->input] < 0) { + swidth = 720; + totalwidth = 858; + scaledtwidth = 858; + } + + vdelay = tvnorm->vdelay; +#if 0 /* FIXME */ + if (vdelay < btv->vbi.lines*2) + vdelay = btv->vbi.lines*2; +#endif + + xsf = (width*scaledtwidth)/swidth; + geo->hscale = ((totalwidth*4096UL)/xsf-4096); + geo->hdelay = tvnorm->hdelayx1; + geo->hdelay = (geo->hdelay*width)/swidth; + geo->hdelay &= 0x3fe; + sr = ((tvnorm->sheight >> (interleaved?0:1))*512)/height - 512; + geo->vscale = (0x10000UL-sr) & 0x1fff; + geo->crop = ((width>>8)&0x03) | ((geo->hdelay>>6)&0x0c) | + ((tvnorm->sheight>>4)&0x30) | ((vdelay>>2)&0xc0); + geo->vscale |= interleaved ? (BT848_VSCALE_INT<<8) : 0; + geo->vdelay = vdelay; + geo->width = width; + geo->sheight = tvnorm->sheight; + geo->vtotal = tvnorm->vtotal; + + if (btv->opt_combfilter) { + geo->vtc = (width < 193) ? 2 : ((width < 385) ? 1 : 0); + geo->comb = (width < 769) ? 1 : 0; + } else { + geo->vtc = 0; + geo->comb = 0; + } +} + +void +bttv_apply_geo(struct bttv *btv, struct bttv_geometry *geo, int odd) +{ + int off = odd ? 0x80 : 0x00; + + if (geo->comb) + btor(BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off); + else + btand(~BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off); + + btwrite(geo->vtc, BT848_E_VTC+off); + btwrite(geo->hscale >> 8, BT848_E_HSCALE_HI+off); + btwrite(geo->hscale & 0xff, BT848_E_HSCALE_LO+off); + btaor((geo->vscale>>8), 0xe0, BT848_E_VSCALE_HI+off); + btwrite(geo->vscale & 0xff, BT848_E_VSCALE_LO+off); + btwrite(geo->width & 0xff, BT848_E_HACTIVE_LO+off); + btwrite(geo->hdelay & 0xff, BT848_E_HDELAY_LO+off); + btwrite(geo->sheight & 0xff, BT848_E_VACTIVE_LO+off); + btwrite(geo->vdelay & 0xff, BT848_E_VDELAY_LO+off); + btwrite(geo->crop, BT848_E_CROP+off); + btwrite(geo->vtotal>>8, BT848_VTOTAL_HI); + btwrite(geo->vtotal & 0xff, BT848_VTOTAL_LO); +} + +/* ---------------------------------------------------------- */ +/* risc group / risc main loop / dma management */ + +void +bttv_set_dma(struct bttv *btv, int override, int irqflags) +{ + unsigned long cmd; + int capctl; + + btv->cap_ctl = 0; + if (NULL != btv->curr.top) btv->cap_ctl |= 0x02; + if (NULL != btv->curr.bottom) btv->cap_ctl |= 0x01; + if (NULL != btv->curr.vbi) btv->cap_ctl |= 0x0c; + + capctl = 0; + capctl |= (btv->cap_ctl & 0x03) ? 0x03 : 0x00; /* capture */ + capctl |= (btv->cap_ctl & 0x0c) ? 0x0c : 0x00; /* vbi data */ + capctl |= override; + + d2printk(KERN_DEBUG + "bttv%d: capctl=%x irq=%d top=%08Lx/%08Lx even=%08Lx/%08Lx\n", + btv->c.nr,capctl,irqflags, + btv->curr.vbi ? (unsigned long long)btv->curr.vbi->top.dma : 0, + btv->curr.top ? (unsigned long long)btv->curr.top->top.dma : 0, + btv->curr.vbi ? (unsigned long long)btv->curr.vbi->bottom.dma : 0, + btv->curr.bottom ? (unsigned long long)btv->curr.bottom->bottom.dma : 0); + + cmd = BT848_RISC_JUMP; + if (irqflags) { + cmd |= BT848_RISC_IRQ; + cmd |= (irqflags & 0x0f) << 16; + cmd |= (~irqflags & 0x0f) << 20; + mod_timer(&btv->timeout, jiffies+BTTV_TIMEOUT); + } else { + del_timer(&btv->timeout); + } + btv->main.cpu[RISC_SLOT_LOOP] = cpu_to_le32(cmd); + + btaor(capctl, ~0x0f, BT848_CAP_CTL); + if (capctl) { + if (btv->dma_on) + return; + btwrite(btv->main.dma, BT848_RISC_STRT_ADD); + btor(3, BT848_GPIO_DMA_CTL); + btv->dma_on = 1; + } else { + if (!btv->dma_on) + return; + btand(~3, BT848_GPIO_DMA_CTL); + btv->dma_on = 0; + } + return; +} + +int +bttv_risc_init_main(struct bttv *btv) +{ + int rc; + + if ((rc = btcx_riscmem_alloc(btv->c.pci,&btv->main,PAGE_SIZE)) < 0) + return rc; + dprintk(KERN_DEBUG "bttv%d: risc main @ %08Lx\n", + btv->c.nr,(unsigned long long)btv->main.dma); + + btv->main.cpu[0] = cpu_to_le32(BT848_RISC_SYNC | BT848_RISC_RESYNC | + BT848_FIFO_STATUS_VRE); + btv->main.cpu[1] = cpu_to_le32(0); + btv->main.cpu[2] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[3] = cpu_to_le32(btv->main.dma + (4<<2)); + + /* top field */ + btv->main.cpu[4] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[5] = cpu_to_le32(btv->main.dma + (6<<2)); + btv->main.cpu[6] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[7] = cpu_to_le32(btv->main.dma + (8<<2)); + + btv->main.cpu[8] = cpu_to_le32(BT848_RISC_SYNC | BT848_RISC_RESYNC | + BT848_FIFO_STATUS_VRO); + btv->main.cpu[9] = cpu_to_le32(0); + + /* bottom field */ + btv->main.cpu[10] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[11] = cpu_to_le32(btv->main.dma + (12<<2)); + btv->main.cpu[12] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[13] = cpu_to_le32(btv->main.dma + (14<<2)); + + /* jump back to top field */ + btv->main.cpu[14] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[15] = cpu_to_le32(btv->main.dma + (0<<2)); + + return 0; +} + +int +bttv_risc_hook(struct bttv *btv, int slot, struct btcx_riscmem *risc, + int irqflags) +{ + unsigned long cmd; + unsigned long next = btv->main.dma + ((slot+2) << 2); + + if (NULL == risc) { + d2printk(KERN_DEBUG "bttv%d: risc=%p slot[%d]=NULL\n", + btv->c.nr,risc,slot); + btv->main.cpu[slot+1] = cpu_to_le32(next); + } else { + d2printk(KERN_DEBUG "bttv%d: risc=%p slot[%d]=%08Lx irq=%d\n", + btv->c.nr,risc,slot,(unsigned long long)risc->dma,irqflags); + cmd = BT848_RISC_JUMP; + if (irqflags) { + cmd |= BT848_RISC_IRQ; + cmd |= (irqflags & 0x0f) << 16; + cmd |= (~irqflags & 0x0f) << 20; + } + risc->jmp[0] = cpu_to_le32(cmd); + risc->jmp[1] = cpu_to_le32(next); + btv->main.cpu[slot+1] = cpu_to_le32(risc->dma); + } + return 0; +} + +void +bttv_dma_free(struct bttv *btv, struct bttv_buffer *buf) +{ + if (in_interrupt()) + BUG(); + videobuf_waiton(&buf->vb,0,0); + videobuf_dma_pci_unmap(btv->c.pci, &buf->vb.dma); + videobuf_dma_free(&buf->vb.dma); + btcx_riscmem_free(btv->c.pci,&buf->bottom); + btcx_riscmem_free(btv->c.pci,&buf->top); + buf->vb.state = STATE_NEEDS_INIT; +} + +int +bttv_buffer_set_activate(struct bttv *btv, + struct bttv_buffer_set *set) +{ + /* vbi capture */ + if (set->vbi) { + set->vbi->vb.state = STATE_ACTIVE; + list_del(&set->vbi->vb.queue); + bttv_risc_hook(btv, RISC_SLOT_O_VBI, &set->vbi->top, 0); + bttv_risc_hook(btv, RISC_SLOT_E_VBI, &set->vbi->bottom, 0); + } else { + bttv_risc_hook(btv, RISC_SLOT_O_VBI, NULL, 0); + bttv_risc_hook(btv, RISC_SLOT_E_VBI, NULL, 0); + } + + /* video capture */ + if (NULL != set->top && NULL != set->bottom) { + if (set->top == set->bottom) { + set->top->vb.state = STATE_ACTIVE; + if (set->top->vb.queue.next) + list_del(&set->top->vb.queue); + } else { + set->top->vb.state = STATE_ACTIVE; + set->bottom->vb.state = STATE_ACTIVE; + if (set->top->vb.queue.next) + list_del(&set->top->vb.queue); + if (set->bottom->vb.queue.next) + list_del(&set->bottom->vb.queue); + } + bttv_apply_geo(btv, &set->top->geo, 1); + bttv_apply_geo(btv, &set->bottom->geo,0); + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, &set->top->top, set->topirq); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, &set->bottom->bottom, 0); + btaor((set->top->btformat & 0xf0) | (set->bottom->btformat & 0x0f), + ~0xff, BT848_COLOR_FMT); + btaor((set->top->btswap & 0x0a) | (set->bottom->btswap & 0x05), + ~0x0f, BT848_COLOR_CTL); + } else if (NULL != set->top) { + set->top->vb.state = STATE_ACTIVE; + if (set->top->vb.queue.next) + list_del(&set->top->vb.queue); + bttv_apply_geo(btv, &set->top->geo,1); + bttv_apply_geo(btv, &set->top->geo,0); + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, &set->top->top, 0); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, NULL, 0); + btaor(set->top->btformat & 0xff, ~0xff, BT848_COLOR_FMT); + btaor(set->top->btswap & 0x0f, ~0x0f, BT848_COLOR_CTL); + } else if (NULL != set->bottom) { + set->bottom->vb.state = STATE_ACTIVE; + if (set->bottom->vb.queue.next) + list_del(&set->bottom->vb.queue); + bttv_apply_geo(btv, &set->bottom->geo,1); + bttv_apply_geo(btv, &set->bottom->geo,0); + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, &set->bottom->bottom, 0); + btaor(set->bottom->btformat & 0xff, ~0xff, BT848_COLOR_FMT); + btaor(set->bottom->btswap & 0x0f, ~0x0f, BT848_COLOR_CTL); + } else { + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, NULL, 0); + } + return 0; +} + +/* ---------------------------------------------------------- */ + +/* calculate geometry, build risc code */ +int +bttv_buffer_risc(struct bttv *btv, struct bttv_buffer *buf) +{ + const struct bttv_tvnorm *tvnorm = bttv_tvnorms + buf->tvnorm; + + dprintk(KERN_DEBUG + "bttv%d: buffer field: %s format: %s size: %dx%d\n", + btv->c.nr, v4l2_field_names[buf->vb.field], + buf->fmt->name, buf->vb.width, buf->vb.height); + + /* packed pixel modes */ + if (buf->fmt->flags & FORMAT_FLAGS_PACKED) { + int bpl = (buf->fmt->depth >> 3) * buf->vb.width; + int bpf = bpl * (buf->vb.height >> 1); + + bttv_calc_geo(btv,&buf->geo,buf->vb.width,buf->vb.height, + V4L2_FIELD_HAS_BOTH(buf->vb.field),buf->tvnorm); + + switch (buf->vb.field) { + case V4L2_FIELD_TOP: + bttv_risc_packed(btv,&buf->top,buf->vb.dma.sglist, + 0,bpl,0,buf->vb.height); + break; + case V4L2_FIELD_BOTTOM: + bttv_risc_packed(btv,&buf->bottom,buf->vb.dma.sglist, + 0,bpl,0,buf->vb.height); + break; + case V4L2_FIELD_INTERLACED: + bttv_risc_packed(btv,&buf->top,buf->vb.dma.sglist, + 0,bpl,bpl,buf->vb.height >> 1); + bttv_risc_packed(btv,&buf->bottom,buf->vb.dma.sglist, + bpl,bpl,bpl,buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + bttv_risc_packed(btv,&buf->top,buf->vb.dma.sglist, + 0,bpl,0,buf->vb.height >> 1); + bttv_risc_packed(btv,&buf->bottom,buf->vb.dma.sglist, + bpf,bpl,0,buf->vb.height >> 1); + break; + default: + BUG(); + } + } + + /* planar modes */ + if (buf->fmt->flags & FORMAT_FLAGS_PLANAR) { + int uoffset, voffset; + int ypadding, cpadding, lines; + + /* calculate chroma offsets */ + uoffset = buf->vb.width * buf->vb.height; + voffset = buf->vb.width * buf->vb.height; + if (buf->fmt->flags & FORMAT_FLAGS_CrCb) { + /* Y-Cr-Cb plane order */ + uoffset >>= buf->fmt->hshift; + uoffset >>= buf->fmt->vshift; + uoffset += voffset; + } else { + /* Y-Cb-Cr plane order */ + voffset >>= buf->fmt->hshift; + voffset >>= buf->fmt->vshift; + voffset += uoffset; + } + + switch (buf->vb.field) { + case V4L2_FIELD_TOP: + bttv_calc_geo(btv,&buf->geo,buf->vb.width, + buf->vb.height,0,buf->tvnorm); + bttv_risc_planar(btv, &buf->top, buf->vb.dma.sglist, + 0,buf->vb.width,0,buf->vb.height, + uoffset,voffset,buf->fmt->hshift, + buf->fmt->vshift,0); + break; + case V4L2_FIELD_BOTTOM: + bttv_calc_geo(btv,&buf->geo,buf->vb.width, + buf->vb.height,0,buf->tvnorm); + bttv_risc_planar(btv, &buf->bottom, buf->vb.dma.sglist, + 0,buf->vb.width,0,buf->vb.height, + uoffset,voffset,buf->fmt->hshift, + buf->fmt->vshift,0); + break; + case V4L2_FIELD_INTERLACED: + bttv_calc_geo(btv,&buf->geo,buf->vb.width, + buf->vb.height,1,buf->tvnorm); + lines = buf->vb.height >> 1; + ypadding = buf->vb.width; + cpadding = buf->vb.width >> buf->fmt->hshift; + bttv_risc_planar(btv,&buf->top, + buf->vb.dma.sglist, + 0,buf->vb.width,ypadding,lines, + uoffset,voffset, + buf->fmt->hshift, + buf->fmt->vshift, + cpadding); + bttv_risc_planar(btv,&buf->bottom, + buf->vb.dma.sglist, + ypadding,buf->vb.width,ypadding,lines, + uoffset+cpadding, + voffset+cpadding, + buf->fmt->hshift, + buf->fmt->vshift, + cpadding); + break; + case V4L2_FIELD_SEQ_TB: + bttv_calc_geo(btv,&buf->geo,buf->vb.width, + buf->vb.height,1,buf->tvnorm); + lines = buf->vb.height >> 1; + ypadding = buf->vb.width; + cpadding = buf->vb.width >> buf->fmt->hshift; + bttv_risc_planar(btv,&buf->top, + buf->vb.dma.sglist, + 0,buf->vb.width,0,lines, + uoffset >> 1, + voffset >> 1, + buf->fmt->hshift, + buf->fmt->vshift, + 0); + bttv_risc_planar(btv,&buf->bottom, + buf->vb.dma.sglist, + lines * ypadding,buf->vb.width,0,lines, + lines * ypadding + (uoffset >> 1), + lines * ypadding + (voffset >> 1), + buf->fmt->hshift, + buf->fmt->vshift, + 0); + break; + default: + BUG(); + } + } + + /* raw data */ + if (buf->fmt->flags & FORMAT_FLAGS_RAW) { + /* build risc code */ + buf->vb.field = V4L2_FIELD_SEQ_TB; + bttv_calc_geo(btv,&buf->geo,tvnorm->swidth,tvnorm->sheight, + 1,buf->tvnorm); + bttv_risc_packed(btv, &buf->top, buf->vb.dma.sglist, + 0, RAW_BPL, 0, RAW_LINES); + bttv_risc_packed(btv, &buf->bottom, buf->vb.dma.sglist, + buf->vb.size/2 , RAW_BPL, 0, RAW_LINES); + } + + /* copy format info */ + buf->btformat = buf->fmt->btformat; + buf->btswap = buf->fmt->btswap; + return 0; +} + +/* ---------------------------------------------------------- */ + +/* calculate geometry, build risc code */ +int +bttv_overlay_risc(struct bttv *btv, + struct bttv_overlay *ov, + const struct bttv_format *fmt, + struct bttv_buffer *buf) +{ + /* check interleave, bottom+top fields */ + dprintk(KERN_DEBUG + "bttv%d: overlay fields: %s format: %s size: %dx%d\n", + btv->c.nr, v4l2_field_names[buf->vb.field], + fmt->name,ov->w.width,ov->w.height); + + /* calculate geometry */ + bttv_calc_geo(btv,&buf->geo,ov->w.width,ov->w.height, + V4L2_FIELD_HAS_BOTH(ov->field), ov->tvnorm); + + /* build risc code */ + switch (ov->field) { + case V4L2_FIELD_TOP: + bttv_risc_overlay(btv, &buf->top, fmt, ov, 0, 0); + break; + case V4L2_FIELD_BOTTOM: + bttv_risc_overlay(btv, &buf->bottom, fmt, ov, 0, 0); + break; + case V4L2_FIELD_INTERLACED: +#if 0 + bttv_risc_overlay(btv, &buf->top, fmt, ov, 1, 0); + bttv_risc_overlay(btv, &buf->bottom, fmt, ov, 0, 1); +#else + bttv_risc_overlay(btv, &buf->top, fmt, ov, 0, 1); + bttv_risc_overlay(btv, &buf->bottom, fmt, ov, 1, 0); +#endif + break; + default: + BUG(); + } + + /* copy format info */ + buf->btformat = fmt->btformat; + buf->btswap = fmt->btswap; + buf->vb.field = ov->field; + return 0; +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/bttv-vbi.c b/linux/drivers/media/video/bttv-vbi.c new file mode 100644 index 000000000..f3d12cde0 --- /dev/null +++ b/linux/drivers/media/video/bttv-vbi.c @@ -0,0 +1,228 @@ +/* + bttv - Bt848 frame grabber driver + vbi interface + + (c) 2002 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/kdev_t.h> +#include <asm/io.h> +#include "bttvp.h" + +#define VBI_DEFLINES 16 +#define VBI_MAXLINES 32 + +static unsigned int vbibufs = 4; +static unsigned int vbi_debug = 0; + +MODULE_PARM(vbibufs,"i"); +MODULE_PARM_DESC(vbibufs,"number of vbi buffers, range 2-32, default 4"); +MODULE_PARM(vbi_debug,"i"); +MODULE_PARM_DESC(vbi_debug,"vbi code debug messages, default is 0 (no)"); + +#ifdef dprintk +# undef dprintk +#endif +#define dprintk(fmt, arg...) if (vbi_debug) \ + printk(KERN_DEBUG "bttv%d/vbi: " fmt, btv->c.nr , ## arg) + +/* ----------------------------------------------------------------------- */ +/* vbi risc code + mm */ + +static int +vbi_buffer_risc(struct bttv *btv, struct bttv_buffer *buf, int lines) +{ + int bpl = 2048; + + bttv_risc_packed(btv, &buf->top, buf->vb.dma.sglist, + 0, bpl-4, 4, lines); + bttv_risc_packed(btv, &buf->bottom, buf->vb.dma.sglist, + lines * bpl, bpl-4, 4, lines); + return 0; +} + +static int vbi_buffer_setup(struct file *file, + unsigned int *count, unsigned int *size) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + + if (0 == *count) + *count = vbibufs; + *size = fh->lines * 2 * 2048; + dprintk("setup: lines=%d\n",fh->lines); + return 0; +} + +static int vbi_buffer_prepare(struct file *file, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + struct bttv_buffer *buf = (struct bttv_buffer*)vb; + int rc; + + buf->vb.size = fh->lines * 2 * 2048; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (STATE_NEEDS_INIT == buf->vb.state) { + if (0 != (rc = videobuf_iolock(btv->c.pci, &buf->vb, NULL))) + goto fail; + if (0 != (rc = vbi_buffer_risc(btv,buf,fh->lines))) + goto fail; + } + buf->vb.state = STATE_PREPARED; + buf->vb.field = field; + dprintk("buf prepare %p: top=%p bottom=%p field=%s\n", + vb, &buf->top, &buf->bottom, + v4l2_field_names[buf->vb.field]); + return 0; + + fail: + bttv_dma_free(btv,buf); + return rc; +} + +static void +vbi_buffer_queue(struct file *file, struct videobuf_buffer *vb) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + struct bttv_buffer *buf = (struct bttv_buffer*)vb; + + dprintk("queue %p\n",vb); + buf->vb.state = STATE_QUEUED; + list_add_tail(&buf->vb.queue,&btv->vcapture); + bttv_set_dma(btv,0x0c,1); +} + +static void vbi_buffer_release(struct file *file, struct videobuf_buffer *vb) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + struct bttv_buffer *buf = (struct bttv_buffer*)vb; + + dprintk("free %p\n",vb); + bttv_dma_free(fh->btv,buf); +} + +struct videobuf_queue_ops bttv_vbi_qops = { + .buf_setup = vbi_buffer_setup, + .buf_prepare = vbi_buffer_prepare, + .buf_queue = vbi_buffer_queue, + .buf_release = vbi_buffer_release, +}; + +/* ----------------------------------------------------------------------- */ + +void bttv_vbi_setlines(struct bttv_fh *fh, struct bttv *btv, int lines) +{ + int vdelay; + + if (lines < 1) + lines = 1; + if (lines > VBI_MAXLINES) + lines = VBI_MAXLINES; + fh->lines = lines; + + vdelay = btread(BT848_E_VDELAY_LO); + if (vdelay < lines*2) { + vdelay = lines*2; + btwrite(vdelay,BT848_E_VDELAY_LO); + btwrite(vdelay,BT848_O_VDELAY_LO); + } +} + +void bttv_vbi_try_fmt(struct bttv_fh *fh, struct v4l2_format *f) +{ + const struct bttv_tvnorm *tvnorm; + u32 start0,start1; + s32 count0,count1,count; + + tvnorm = &bttv_tvnorms[fh->btv->tvnorm]; + f->type = V4L2_BUF_TYPE_VBI_CAPTURE; + f->fmt.vbi.sampling_rate = tvnorm->Fsc; + f->fmt.vbi.samples_per_line = 2048; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 244; + f->fmt.vbi.flags = 0; + switch (fh->btv->tvnorm) { + case 1: /* NTSC */ + start0 = 10; + start1 = 273; + break; + case 0: /* PAL */ + case 2: /* SECAM */ + default: + start0 = 7; + start1 = 319; + } + + count0 = (f->fmt.vbi.start[0] + f->fmt.vbi.count[0]) - start0; + count1 = (f->fmt.vbi.start[1] + f->fmt.vbi.count[1]) - start1; + count = max(count0,count1); + if (count > VBI_MAXLINES) + count = VBI_MAXLINES; + if (count < 1) + count = 1; + + f->fmt.vbi.start[0] = start0; + f->fmt.vbi.start[1] = start1; + f->fmt.vbi.count[0] = count; + f->fmt.vbi.count[1] = count; +} + +void bttv_vbi_get_fmt(struct bttv_fh *fh, struct v4l2_format *f) +{ + const struct bttv_tvnorm *tvnorm; + + tvnorm = &bttv_tvnorms[fh->btv->tvnorm]; + memset(f,0,sizeof(*f)); + f->type = V4L2_BUF_TYPE_VBI_CAPTURE; + f->fmt.vbi.sampling_rate = tvnorm->Fsc; + f->fmt.vbi.samples_per_line = 2048; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 244; + f->fmt.vbi.count[0] = fh->lines; + f->fmt.vbi.count[1] = fh->lines; + f->fmt.vbi.flags = 0; + switch (fh->btv->tvnorm) { + case 1: /* NTSC */ + f->fmt.vbi.start[0] = 10; + f->fmt.vbi.start[1] = 273; + break; + case 0: /* PAL */ + case 2: /* SECAM */ + default: + f->fmt.vbi.start[0] = 7; + f->fmt.vbi.start[1] = 319; + } +} + +/* ----------------------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/bttv.h b/linux/drivers/media/video/bttv.h new file mode 100644 index 000000000..864f9fcf1 --- /dev/null +++ b/linux/drivers/media/video/bttv.h @@ -0,0 +1,348 @@ +/* + * bttv - Bt848 frame grabber driver + * + * card ID's and external interfaces of the bttv driver + * basically stuff needed by other drivers (i2c, lirc, ...) + * and is supported not to change much over time. + * + * Copyright (C) 1996,97 Ralph Metzler (rjkm@thp.uni-koeln.de) + * (c) 1999,2000 Gerd Knorr <kraxel@goldbach.in-berlin.de> + * + */ + +#ifndef _BTTV_H_ +#define _BTTV_H_ + +#include <linux/videodev.h> +#include <linux/i2c.h> + +/* ---------------------------------------------------------- */ +/* exported by bttv-cards.c */ + +#define BTTV_UNKNOWN 0x00 +#define BTTV_MIRO 0x01 +#define BTTV_HAUPPAUGE 0x02 +#define BTTV_STB 0x03 +#define BTTV_INTEL 0x04 +#define BTTV_DIAMOND 0x05 +#define BTTV_AVERMEDIA 0x06 +#define BTTV_MATRIX_VISION 0x07 +#define BTTV_FLYVIDEO 0x08 +#define BTTV_TURBOTV 0x09 +#define BTTV_HAUPPAUGE878 0x0a +#define BTTV_MIROPRO 0x0b +#define BTTV_ADSTECH_TV 0x0c +#define BTTV_AVERMEDIA98 0x0d +#define BTTV_VHX 0x0e +#define BTTV_ZOLTRIX 0x0f +#define BTTV_PIXVIEWPLAYTV 0x10 +#define BTTV_WINVIEW_601 0x11 +#define BTTV_AVEC_INTERCAP 0x12 +#define BTTV_LIFE_FLYKIT 0x13 +#define BTTV_CEI_RAFFLES 0x14 +#define BTTV_CONFERENCETV 0x15 +#define BTTV_PHOEBE_TVMAS 0x16 +#define BTTV_MODTEC_205 0x17 +#define BTTV_MAGICTVIEW061 0x18 +#define BTTV_VOBIS_BOOSTAR 0x19 +#define BTTV_HAUPPAUG_WCAM 0x1a +#define BTTV_MAXI 0x1b +#define BTTV_TERRATV 0x1c +#define BTTV_PXC200 0x1d +#define BTTV_FLYVIDEO_98 0x1e +#define BTTV_IPROTV 0x1f +#define BTTV_INTEL_C_S_PCI 0x20 +#define BTTV_TERRATVALUE 0x21 +#define BTTV_WINFAST2000 0x22 +#define BTTV_CHRONOS_VS2 0x23 +#define BTTV_TYPHOON_TVIEW 0x24 +#define BTTV_PXELVWPLTVPRO 0x25 +#define BTTV_MAGICTVIEW063 0x26 +#define BTTV_PINNACLE 0x27 +#define BTTV_STB2 0x28 +#define BTTV_AVPHONE98 0x29 +#define BTTV_PV951 0x2a +#define BTTV_ONAIR_TV 0x2b +#define BTTV_SIGMA_TVII_FM 0x2c +#define BTTV_MATRIX_VISION2 0x2d +#define BTTV_ZOLTRIX_GENIE 0x2e +#define BTTV_TERRATVRADIO 0x2f +#define BTTV_DYNALINK 0x30 +#define BTTV_GVBCTV3PCI 0x31 +#define BTTV_PXELVWPLTVPAK 0x32 +#define BTTV_EAGLE 0x33 +#define BTTV_PINNACLEPRO 0x34 +#define BTTV_TVIEW_RDS_FM 0x35 +#define BTTV_LIFETEC_9415 0x36 +#define BTTV_BESTBUY_EASYTV 0x37 +#define BTTV_FLYVIDEO_98FM 0x38 +#define BTTV_GMV1 0x3d +#define BTTV_BESTBUY_EASYTV2 0x3e +#define BTTV_ATI_TVWONDER 0x3f +#define BTTV_ATI_TVWONDERVE 0x40 +#define BTTV_FLYVIDEO2000 0x41 +#define BTTV_TERRATVALUER 0x42 +#define BTTV_GVBCTV4PCI 0x43 +#define BTTV_VOODOOTV_FM 0x44 +#define BTTV_AIMMS 0x45 +#define BTTV_PV_BT878P_PLUS 0x46 +#define BTTV_FLYVIDEO98EZ 0x47 +#define BTTV_PV_BT878P_9B 0x48 +#define BTTV_SENSORAY311 0x49 +#define BTTV_RV605 0x4a +#define BTTV_WINDVR 0x4c +#define BTTV_GRANDTEC 0x4d +#define BTTV_KWORLD 0x4e +#define BTTV_HAUPPAUGEPVR 0x50 +#define BTTV_GVBCTV5PCI 0x51 +#define BTTV_OSPREY1x0 0x52 +#define BTTV_OSPREY1x0_848 0x53 +#define BTTV_OSPREY101_848 0x54 +#define BTTV_OSPREY1x1 0x55 +#define BTTV_OSPREY1x1_SVID 0x56 +#define BTTV_OSPREY2xx 0x57 +#define BTTV_OSPREY2x0_SVID 0x58 +#define BTTV_OSPREY2x0 0x59 +#define BTTV_OSPREY500 0x5a +#define BTTV_OSPREY540 0x5b +#define BTTV_OSPREY2000 0x5c +#define BTTV_IDS_EAGLE 0x5d +#define BTTV_PINNACLESAT 0x5e +#define BTTV_FORMAC_PROTV 0x5f +#define BTTV_EURESYS_PICOLO 0x61 +#define BTTV_PV150 0x62 +#define BTTV_AD_TVK503 0x63 +#define BTTV_IVC200 0x66 +#define BTTV_XGUARD 0x67 +#define BTTV_NEBULA_DIGITV 0x68 +#define BTTV_PV143 0x69 +#define BTTV_IVC100 0x6e +#define BTTV_IVC120 0x6f +#define BTTV_PC_HDTV 0x70 +#define BTTV_TWINHAN_DST 0x71 +#define BTTV_WINFASTVC100 0x72 +#define BTTV_SIMUS_GVC1100 0x74 +#define BTTV_NGSTV_PLUS 0x75 +#define BTTV_LMLBT4 0x76 + +/* i2c address list */ +#define I2C_TSA5522 0xc2 +#define I2C_TDA7432 0x8a +#define I2C_BT832_ALT1 0x88 +#define I2C_BT832_ALT2 0x8a // alternate setting +#define I2C_TDA8425 0x82 +#define I2C_TDA9840 0x84 +#define I2C_TDA9850 0xb6 /* also used by 9855,9873 */ +#define I2C_TDA9874 0xb0 /* also used by 9875 */ +#define I2C_TDA9875 0xb0 +#define I2C_HAUPEE 0xa0 +#define I2C_STBEE 0xae +#define I2C_VHX 0xc0 +#define I2C_MSP3400 0x80 +#define I2C_MSP3400_ALT 0x88 +#define I2C_TEA6300 0x80 +#define I2C_DPL3518 0x84 +#define I2C_TDA9887 0x86 + +/* more card-specific defines */ +#define PT2254_L_CHANNEL 0x10 +#define PT2254_R_CHANNEL 0x08 +#define PT2254_DBS_IN_2 0x400 +#define PT2254_DBS_IN_10 0x20000 +#define WINVIEW_PT2254_CLK 0x40 +#define WINVIEW_PT2254_DATA 0x20 +#define WINVIEW_PT2254_STROBE 0x80 + +/* digital_mode */ +#define DIGITAL_MODE_VIDEO 1 +#define DIGITAL_MODE_CAMERA 2 + +struct bttv_core { + /* device structs */ + struct pci_dev *pci; + struct i2c_adapter i2c_adap; + struct list_head subs; /* struct bttv_sub_device */ + + /* device config */ + unsigned int nr; /* dev nr (for printk("bttv%d: ..."); */ + unsigned int type; /* card type (pointer into tvcards[]) */ + char name[8]; /* dev name */ +}; + +struct bttv; + +struct tvcard +{ + char *name; + unsigned int video_inputs; + unsigned int audio_inputs; + unsigned int tuner; + unsigned int svhs; + unsigned int digital_mode; // DIGITAL_MODE_CAMERA or DIGITAL_MODE_VIDEO + u32 gpiomask; + u32 muxsel[16]; + u32 audiomux[6]; /* Tuner, Radio, external, internal, mute, stereo */ + u32 gpiomask2; /* GPIO MUX mask */ + + /* i2c audio flags */ + unsigned int no_msp34xx:1; + unsigned int no_tda9875:1; + unsigned int no_tda7432:1; + unsigned int needs_tvaudio:1; + unsigned int msp34xx_alt:1; + + /* flag: video pci function is unused */ + unsigned int no_video:1; + unsigned int has_dvb:1; + unsigned int has_remote:1; + unsigned int no_gpioirq:1; + + /* other settings */ + unsigned int pll; +#define PLL_NONE 0 +#define PLL_28 1 +#define PLL_35 2 + + unsigned int tuner_type; + unsigned int has_radio; + void (*audio_hook)(struct bttv *btv, struct video_audio *v, int set); + void (*muxsel_hook)(struct bttv *btv, unsigned int input); +}; + +extern struct tvcard bttv_tvcards[]; +extern const unsigned int bttv_num_tvcards; + +/* identification / initialization of the card */ +extern void bttv_idcard(struct bttv *btv); +extern void bttv_init_card1(struct bttv *btv); +extern void bttv_init_card2(struct bttv *btv); + +/* card-specific funtions */ +extern void tea5757_set_freq(struct bttv *btv, unsigned short freq); +extern void bttv_tda9880_setnorm(struct bttv *btv, int norm); + +/* kernel cmd line parse helper */ +extern int bttv_parse(char *str, int max, int *vals); + +/* extra tweaks for some chipsets */ +extern void bttv_check_chipset(void); +extern int bttv_handle_chipset(struct bttv *btv); + +/* ---------------------------------------------------------- */ +/* exported by bttv-if.c */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,7,0) +/* this obsolete -- please use the sysfs-based + interface below for new code */ + +/* returns card type + card ID (for bt878-based ones) + for possible values see lines below beginning with #define BTTV_UNKNOWN + returns negative value if error occurred +*/ +extern int bttv_get_cardinfo(unsigned int card, int *type, + unsigned int *cardid); +extern struct pci_dev* bttv_get_pcidev(unsigned int card); + +/* obsolete, use bttv_get_cardinfo instead */ +extern int bttv_get_id(unsigned int card); + +/* sets GPOE register (BT848_GPIO_OUT_EN) to new value: + data | (current_GPOE_value & ~mask) + returns negative value if error occurred +*/ +extern int bttv_gpio_enable(unsigned int card, + unsigned long mask, unsigned long data); + +/* fills data with GPDATA register contents + returns negative value if error occurred +*/ +extern int bttv_read_gpio(unsigned int card, unsigned long *data); + +/* sets GPDATA register to new value: + (data & mask) | (current_GPDATA_value & ~mask) + returns negative value if error occurred +*/ +extern int bttv_write_gpio(unsigned int card, + unsigned long mask, unsigned long data); + +/* returns pointer to task queue which can be used as parameter to + interruptible_sleep_on + in interrupt handler if BT848_INT_GPINT bit is set - this queue is activated + (wake_up_interruptible) and following call to the function bttv_read_gpio + should return new value of GPDATA, + returns NULL value if error occurred or queue is not available + WARNING: because there is no buffer for GPIO data, one MUST + process data ASAP +*/ +extern wait_queue_head_t* bttv_get_gpio_queue(unsigned int card); + +/* call i2c clients +*/ +extern void bttv_i2c_call(unsigned int card, unsigned int cmd, void *arg); +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +/* with 2.6.x not needed thanks to the driver model + sysfs, see below */ +extern struct i2c_adapter *bttv_get_i2c_adap(unsigned int card); +#endif + + +/* ---------------------------------------------------------- */ +/* sysfs/driver-moded based gpio access interface */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + +struct bttv_sub_device { + struct device dev; + struct bttv_core *core; + struct list_head list; +}; +#define to_bttv_sub_dev(x) container_of((x), struct bttv_sub_device, dev) + +struct bttv_sub_driver { + struct device_driver drv; + char wanted[BUS_ID_SIZE]; + void (*gpio_irq)(struct bttv_sub_device *sub); +}; +#define to_bttv_sub_drv(x) container_of((x), struct bttv_sub_driver, drv) + +int bttv_sub_register(struct bttv_sub_driver *drv, char *wanted); +int bttv_sub_unregister(struct bttv_sub_driver *drv); + +/* gpio access functions */ +void bttv_gpio_inout(struct bttv_core *core, u32 mask, u32 outbits); +u32 bttv_gpio_read(struct bttv_core *core); +void bttv_gpio_write(struct bttv_core *core, u32 value); +void bttv_gpio_bits(struct bttv_core *core, u32 mask, u32 bits); + +#define gpio_inout(mask,bits) bttv_gpio_inout(&btv->c, mask, bits) +#define gpio_read() bttv_gpio_read(&btv->c) +#define gpio_write(value) bttv_gpio_write(&btv->c, value) +#define gpio_bits(mask,bits) bttv_gpio_bits(&btv->c, mask, bits) + +#else + +#define gpio_inout(mask,bits) btaor((mask)&(bits),~(mask),BT848_GPIO_OUT_EN) +#define gpio_read() btread(BT848_GPIO_DATA) +#define gpio_write(value) btwrite((value),BT848_GPIO_DATA) +#define gpio_bits(mask,bits) btaor((mask)&(bits),~(mask),BT848_GPIO_DATA) + +#endif + +/* ---------------------------------------------------------- */ +/* i2c */ + +extern void bttv_bit_setscl(void *data, int state); +extern void bttv_bit_setsda(void *data, int state); +extern void bttv_call_i2c_clients(struct bttv *btv, unsigned int cmd, void *arg); +extern int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for); +extern int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1, + unsigned char b2, int both); +extern void bttv_readee(struct bttv *btv, unsigned char *eedata, int addr); + +#endif /* _BTTV_H_ */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/bttvp.h b/linux/drivers/media/video/bttvp.h new file mode 100644 index 000000000..7d1636bf2 --- /dev/null +++ b/linux/drivers/media/video/bttvp.h @@ -0,0 +1,433 @@ +/* + bttv - Bt848 frame grabber driver + + bttv's *private* header file -- nobody other than bttv itself + should ever include this file. + + (c) 2000-2002 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _BTTVP_H_ +#define _BTTVP_H_ + +#include <linux/version.h> +#define BTTV_VERSION_CODE KERNEL_VERSION(0,9,12) + +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/videodev.h> +#include <linux/pci.h> +#include <linux/input.h> +#include <asm/scatterlist.h> +#include <asm/io.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "video-buf.h" +#include "audiochip.h" +#include "tuner.h" +#include "i2c-compat.h" +#include "ir-common.h" +#define strlcpy(dest,src,len) strncpy(dest,src,(len)-1) +#else +#include <linux/device.h> +#include <media/video-buf.h> +#include <media/audiochip.h> +#include <media/tuner.h> +#include <media/ir-common.h> +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,23) +#define irqreturn_t void +#define IRQ_RETVAL(foobar) +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +#define iminor(inode) minor(inode->i_rdev) +#endif + +#include "bt848.h" +#include "bttv.h" +#include "btcx-risc.h" + +#ifdef __KERNEL__ + +#define FORMAT_FLAGS_DITHER 0x01 +#define FORMAT_FLAGS_PACKED 0x02 +#define FORMAT_FLAGS_PLANAR 0x04 +#define FORMAT_FLAGS_RAW 0x08 +#define FORMAT_FLAGS_CrCb 0x10 + +#define RISC_SLOT_O_VBI 4 +#define RISC_SLOT_O_FIELD 6 +#define RISC_SLOT_E_VBI 10 +#define RISC_SLOT_E_FIELD 12 +#define RISC_SLOT_LOOP 14 + +#define RESOURCE_OVERLAY 1 +#define RESOURCE_VIDEO 2 +#define RESOURCE_VBI 4 + +#define RAW_LINES 640 +#define RAW_BPL 1024 + +#define UNSET (-1U) + +/* ---------------------------------------------------------- */ + +struct bttv_tvnorm { + int v4l2_id; + char *name; + u32 Fsc; + u16 swidth, sheight; /* scaled standard width, height */ + u16 totalwidth; + u8 adelay, bdelay, iform; + u32 scaledtwidth; + u16 hdelayx1, hactivex1; + u16 vdelay; + u8 vbipack; + u16 vtotal; + int sram; +}; +extern const struct bttv_tvnorm bttv_tvnorms[]; +extern const unsigned int BTTV_TVNORMS; + +struct bttv_format { + char *name; + int palette; /* video4linux 1 */ + int fourcc; /* video4linux 2 */ + int btformat; /* BT848_COLOR_FMT_* */ + int btswap; /* BT848_COLOR_CTL_* */ + int depth; /* bit/pixel */ + int flags; + int hshift,vshift; /* for planar modes */ +}; +extern const struct bttv_format bttv_formats[]; +extern const unsigned int BTTV_FORMATS; + +/* ---------------------------------------------------------- */ + +struct bttv_geometry { + u8 vtc,crop,comb; + u16 width,hscale,hdelay; + u16 sheight,vscale,vdelay,vtotal; +}; + +struct bttv_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* bttv specific */ + const struct bttv_format *fmt; + int tvnorm; + int btformat; + int btswap; + struct bttv_geometry geo; + struct btcx_riscmem top; + struct btcx_riscmem bottom; +}; + +struct bttv_buffer_set { + struct bttv_buffer *top; /* top field buffer */ + struct bttv_buffer *bottom; /* bottom field buffer */ + struct bttv_buffer *vbi; /* vbi buffer */ + unsigned int irqflags; + unsigned int topirq; +}; + +struct bttv_overlay { + int tvnorm; + struct v4l2_rect w; + enum v4l2_field field; + struct v4l2_clip *clips; + int nclips; + int setup_ok; +}; + +struct bttv_fh { + struct bttv *btv; + int resources; +#ifdef VIDIOC_G_PRIORITY + enum v4l2_priority prio; +#endif + enum v4l2_buf_type type; + + /* video capture */ + struct videobuf_queue cap; + const struct bttv_format *fmt; + int width; + int height; + + /* current settings */ + const struct bttv_format *ovfmt; + struct bttv_overlay ov; + + /* video overlay */ + struct videobuf_queue vbi; + int lines; +}; + +/* ---------------------------------------------------------- */ +/* bttv-risc.c */ + +/* risc code generators - capture */ +int bttv_risc_packed(struct bttv *btv, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int offset, unsigned int bpl, + unsigned int pitch, unsigned int lines); +int bttv_risc_planar(struct bttv *btv, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int yoffset, unsigned int ybpl, + unsigned int ypadding, unsigned int ylines, + unsigned int uoffset, unsigned int voffset, + unsigned int hshift, unsigned int vshift, + unsigned int cpadding); +int bttv_risc_overlay(struct bttv *btv, struct btcx_riscmem *risc, + const struct bttv_format *fmt, + struct bttv_overlay *ov, + int skip_top, int skip_bottom); + +/* calculate / apply geometry settings */ +void bttv_calc_geo(struct bttv *btv, struct bttv_geometry *geo, + int width, int height, int interleaved, int norm); +void bttv_apply_geo(struct bttv *btv, struct bttv_geometry *geo, int top); + +/* control dma register + risc main loop */ +void bttv_set_dma(struct bttv *btv, int override, int irqflags); +int bttv_risc_init_main(struct bttv *btv); +int bttv_risc_hook(struct bttv *btv, int slot, struct btcx_riscmem *risc, + int irqflags); + +/* capture buffer handling */ +int bttv_buffer_risc(struct bttv *btv, struct bttv_buffer *buf); +int bttv_buffer_set_activate(struct bttv *btv, + struct bttv_buffer_set *set); +void bttv_dma_free(struct bttv *btv, struct bttv_buffer *buf); + +/* overlay handling */ +int bttv_overlay_risc(struct bttv *btv, struct bttv_overlay *ov, + const struct bttv_format *fmt, + struct bttv_buffer *buf); + + +/* ---------------------------------------------------------- */ +/* bttv-vbi.c */ + +void bttv_vbi_try_fmt(struct bttv_fh *fh, struct v4l2_format *f); +void bttv_vbi_get_fmt(struct bttv_fh *fh, struct v4l2_format *f); +void bttv_vbi_setlines(struct bttv_fh *fh, struct bttv *btv, int lines); + +extern struct videobuf_queue_ops bttv_vbi_qops; + +/* ---------------------------------------------------------- */ +/* bttv-gpio.c */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + +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); + +#endif + +/* ---------------------------------------------------------- */ +/* bttv-driver.c */ + +/* insmod options */ +extern unsigned int bttv_verbose; +extern unsigned int bttv_debug; +extern unsigned int bttv_gpio; +extern void bttv_gpio_tracking(struct bttv *btv, char *comment); +extern int init_bttv_i2c(struct bttv *btv); +extern int fini_bttv_i2c(struct bttv *btv); +extern int pvr_boot(struct bttv *btv); + +extern int bttv_common_ioctls(struct bttv *btv, unsigned int cmd, void *arg); +extern void bttv_reinit_bt848(struct bttv *btv); +extern void bttv_field_count(struct bttv *btv); + +#define vprintk if (bttv_verbose) printk +#define dprintk if (bttv_debug >= 1) printk +#define d2printk if (bttv_debug >= 2) printk + +/* our devices */ +#define BTTV_MAX 16 +extern unsigned int bttv_num; +extern struct bttv bttvs[BTTV_MAX]; + +#define BTTV_MAX_FBUF 0x208000 +#define VBIBUF_SIZE (2048*VBI_MAXLINES*2) +#define BTTV_TIMEOUT (HZ/2) /* 0.5 seconds */ +#define BTTV_FREE_IDLE (HZ) /* one second */ + + +struct bttv_pll_info { + unsigned int pll_ifreq; /* PLL input frequency */ + unsigned int pll_ofreq; /* PLL output frequency */ + unsigned int pll_crystal; /* Crystal used for input */ + unsigned int pll_current; /* Currently programmed ofreq */ +}; + +/* for gpio-connected remote control */ +struct bttv_input { + struct input_dev dev; + struct ir_input_state ir; + char name[32]; + char phys[32]; + u32 mask_keycode; + u32 mask_keydown; +}; + +struct bttv_suspend_state { + u32 pci_cfg[64 / sizeof(u32)]; + u32 gpio_enable; + u32 gpio_data; + int disabled; + struct bttv_buffer_set set; +}; + +struct bttv { + struct bttv_core c; + + /* pci device config */ + unsigned short id; + unsigned char revision; + unsigned char *bt848_mmio; /* pointer to mmio */ + + /* card configuration info */ + unsigned int cardid; /* pci subsystem id (bt878 based ones) */ + unsigned int tuner_type; /* tuner chip type */ + unsigned int pinnacle_id; + unsigned int svhs; + struct bttv_pll_info pll; + int triton1; + int gpioirq; + + /* old gpio interface */ + wait_queue_head_t gpioq; + int shutdown; + void (*audio_hook)(struct bttv *btv, struct video_audio *v, int set); + + /* new gpio interface */ + spinlock_t gpio_lock; + + /* i2c layer */ + struct i2c_algo_bit_data i2c_algo; + struct i2c_client i2c_client; + int i2c_state, i2c_rc; + int i2c_done; + wait_queue_head_t i2c_queue; + + /* video4linux (1) */ + struct video_device *video_dev; + struct video_device *radio_dev; + struct video_device *vbi_dev; + + /* infrared remote */ + int has_remote; + struct bttv_input *remote; + + /* locking */ + spinlock_t s_lock; + struct semaphore lock; + int resources; + struct semaphore reslock; +#ifdef VIDIOC_G_PRIORITY + struct v4l2_prio_state prio; +#endif + + /* video state */ + unsigned int input; + unsigned int audio; + unsigned long freq; + int tvnorm,hue,contrast,bright,saturation; + struct v4l2_framebuffer fbuf; + unsigned int field_count; + + /* various options */ + int opt_combfilter; + int opt_lumafilter; + int opt_automute; + int opt_chroma_agc; + int opt_adc_crush; + int opt_vcr_hack; + int opt_whitecrush_upper; + int opt_whitecrush_lower; + + /* radio data/state */ + int has_radio; + int radio_user; + + /* miro/pinnacle + Aimslab VHX + philips matchbox (tea5757 radio tuner) support */ + int has_matchbox; + int mbox_we; + int mbox_data; + int mbox_clk; + int mbox_most; + int mbox_mask; + + /* ISA stuff (Terratec Active Radio Upgrade) */ + int mbox_ior; + int mbox_iow; + int mbox_csel; + + /* risc memory management data + - must aquire s_lock before changing these + - only the irq handler is supported to touch top + bottom + vcurr */ + struct btcx_riscmem main; + struct bttv_buffer *screen; /* overlay */ + struct list_head capture; /* video capture queue */ + struct list_head vcapture; /* vbi capture queue */ + struct bttv_buffer_set curr; /* active buffers */ + int new_input; + + unsigned long cap_ctl; + unsigned long dma_on; + struct timer_list timeout; + struct bttv_suspend_state state; + + /* stats */ + unsigned int errors; + unsigned int framedrop; + unsigned int irq_total; + unsigned int irq_me; + + unsigned int users; + struct bttv_fh init; +}; + +/* private ioctls */ +#define BTTV_VERSION _IOR('v' , BASE_VIDIOCPRIVATE+6, int) +#define BTTV_VBISIZE _IOR('v' , BASE_VIDIOCPRIVATE+8, int) + +#endif + +#define btwrite(dat,adr) writel((dat), (char *) (btv->bt848_mmio+(adr))) +#define btread(adr) readl(btv->bt848_mmio+(adr)) + +#define btand(dat,adr) btwrite((dat) & btread(adr), adr) +#define btor(dat,adr) btwrite((dat) | btread(adr), adr) +#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr) + +#endif /* _BTTVP_H_ */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/cx88/cx88-cards.c b/linux/drivers/media/video/cx88/cx88-cards.c new file mode 100644 index 000000000..9def4f412 --- /dev/null +++ b/linux/drivers/media/video/cx88/cx88-cards.c @@ -0,0 +1,431 @@ +/* + * device driver for Conexant 2388x based TV cards + * card-specific stuff. + * + * (c) 2003 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * 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/init.h> +#include <linux/module.h> +#include <linux/pci.h> + +#include "cx88.h" + +/* ------------------------------------------------------------------ */ +/* board config info */ + +struct cx88_board cx88_boards[] = { + [CX88_BOARD_UNKNOWN] = { + .name = "UNKNOWN/GENERIC", + .tuner_type = UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE2, + .vmux = 1, + },{ + .type = CX88_VMUX_COMPOSITE3, + .vmux = 2, + },{ + .type = CX88_VMUX_COMPOSITE4, + .vmux = 3, + }}, + }, + [CX88_BOARD_HAUPPAUGE] = { + .name = "Hauppauge WinTV 34xxx models", + .tuner_type = UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + //.gpio0 = 0xff03, + .gpio0 = 0xff00, + },{ + .type = CX88_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0xff01, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xff02, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xff01, + }, + }, + [CX88_BOARD_GDI] = { + .name = "GDI Black Gold", + .tuner_type = UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + }}, + }, + [CX88_BOARD_PIXELVIEW] = { + .name = "PixelView", + .tuner_type = UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + }, + [CX88_BOARD_ATI_WONDER_PRO] = { + .name = "ATI TV Wonder Pro", + .tuner_type = 44, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + + }}, + }, + [CX88_BOARD_WINFAST2000XP] = { + .name = "Leadtek Winfast 2000XP Expert", + .tuner_type = 38, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + .radio = { + .type = CX88_RADIO, + }, + }, + [CX88_BOARD_AVERTV_303] = { + .name = "AverTV Studio 303 (M126)", + .tuner_type = TUNER_PHILIPS_PAL_DK, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + }}, + }, + [CX88_BOARD_MSI_TVANYWHERE] = { + .name = "MSI TV-@nywhere Master", + .tuner_type = 33, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + .radio = { + .type = CX88_RADIO, + }, + }, + [CX88_BOARD_WINFAST_DV2000] = { + .name = "Leadtek Winfast DV2000", + .tuner_type = 38, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + }}, + .radio = { + .type = CX88_RADIO, + }, + }, + [CX88_BOARD_LEADTEK_PVR2000] = { + .name = "Leadtek PVR 2000", + .tuner_type = 38, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + .radio = { + .type = CX88_RADIO, + }, + }, + + +}; +const unsigned int cx88_bcount = ARRAY_SIZE(cx88_boards); + +/* ------------------------------------------------------------------ */ +/* PCI subsystem IDs */ + +struct cx88_subid cx88_subids[] = { + { + .subvendor = 0x0070, + .subdevice = 0x3400, + .card = CX88_BOARD_HAUPPAUGE, + },{ + .subvendor = 0x0070, + .subdevice = 0x3401, + .card = CX88_BOARD_HAUPPAUGE, + },{ + .subvendor = 0x14c7, + .subdevice = 0x0106, + .card = CX88_BOARD_GDI, + },{ + .subvendor = 0x14c7, + .subdevice = 0x0107, /* with mpeg encoder */ + .card = CX88_BOARD_GDI, + },{ + .subvendor = PCI_VENDOR_ID_ATI, + .subdevice = 0x00f8, + .card = CX88_BOARD_ATI_WONDER_PRO, + },{ + .subvendor = 0x107d, + .subdevice = 0x6611, + .card = CX88_BOARD_WINFAST2000XP, + },{ + .subvendor = 0x107d, + .subdevice = 0x6620, + .card = CX88_BOARD_WINFAST_DV2000, + },{ + .subvendor = 0x107d, + .subdevice = 0x663C, + .card = CX88_BOARD_LEADTEK_PVR2000, + },{ + .subvendor = 0x1461, + .subdevice = 0x000b, + .card = CX88_BOARD_AVERTV_303, + },{ + .subvendor = 0x1462, + .subdevice = 0x8606, + .card = CX88_BOARD_MSI_TVANYWHERE, + } +}; +const unsigned int cx88_idcount = ARRAY_SIZE(cx88_subids); + +/* ----------------------------------------------------------------------- */ +/* some hauppauge specific stuff */ + +static struct { + int id; + char *name; +} hauppauge_tuner[] __devinitdata = { + { TUNER_ABSENT, "" }, + { TUNER_ABSENT, "External" }, + { TUNER_ABSENT, "Unspecified" }, + { TUNER_PHILIPS_PAL, "Philips FI1216" }, + { TUNER_PHILIPS_SECAM, "Philips FI1216MF" }, + { TUNER_PHILIPS_NTSC, "Philips FI1236" }, + { TUNER_PHILIPS_PAL_I, "Philips FI1246" }, + { TUNER_PHILIPS_PAL_DK,"Philips FI1256" }, + { TUNER_PHILIPS_PAL, "Philips FI1216 MK2" }, + { TUNER_PHILIPS_SECAM, "Philips FI1216MF MK2" }, + { TUNER_PHILIPS_NTSC, "Philips FI1236 MK2" }, + { TUNER_PHILIPS_PAL_I, "Philips FI1246 MK2" }, + { TUNER_PHILIPS_PAL_DK,"Philips FI1256 MK2" }, + { TUNER_TEMIC_NTSC, "Temic 4032FY5" }, + { TUNER_TEMIC_PAL, "Temic 4002FH5" }, + { TUNER_TEMIC_PAL_I, "Temic 4062FY5" }, + { TUNER_PHILIPS_PAL, "Philips FR1216 MK2" }, + { TUNER_PHILIPS_SECAM, "Philips FR1216MF MK2" }, + { TUNER_PHILIPS_NTSC, "Philips FR1236 MK2" }, + { TUNER_PHILIPS_PAL_I, "Philips FR1246 MK2" }, + { TUNER_PHILIPS_PAL_DK,"Philips FR1256 MK2" }, + { TUNER_PHILIPS_PAL, "Philips FM1216" }, + { TUNER_PHILIPS_SECAM, "Philips FM1216MF" }, + { TUNER_PHILIPS_NTSC, "Philips FM1236" }, + { TUNER_PHILIPS_PAL_I, "Philips FM1246" }, + { TUNER_PHILIPS_PAL_DK,"Philips FM1256" }, + { TUNER_TEMIC_4036FY5_NTSC, "Temic 4036FY5" }, + { TUNER_ABSENT, "Samsung TCPN9082D" }, + { TUNER_ABSENT, "Samsung TCPM9092P" }, + { TUNER_TEMIC_4006FH5_PAL, "Temic 4006FH5" }, + { TUNER_ABSENT, "Samsung TCPN9085D" }, + { TUNER_ABSENT, "Samsung TCPB9085P" }, + { TUNER_ABSENT, "Samsung TCPL9091P" }, + { TUNER_TEMIC_4039FR5_NTSC, "Temic 4039FR5" }, + { TUNER_PHILIPS_FQ1216ME, "Philips FQ1216 ME" }, + { TUNER_TEMIC_4066FY5_PAL_I, "Temic 4066FY5" }, + { TUNER_PHILIPS_NTSC, "Philips TD1536" }, + { TUNER_PHILIPS_NTSC, "Philips TD1536D" }, + { TUNER_PHILIPS_NTSC, "Philips FMR1236" }, /* mono radio */ + { TUNER_ABSENT, "Philips FI1256MP" }, + { TUNER_ABSENT, "Samsung TCPQ9091P" }, + { TUNER_TEMIC_4006FN5_MULTI_PAL, "Temic 4006FN5" }, + { TUNER_TEMIC_4009FR5_PAL, "Temic 4009FR5" }, + { TUNER_TEMIC_4046FM5, "Temic 4046FM5" }, + { TUNER_TEMIC_4009FN5_MULTI_PAL_FM, "Temic 4009FN5" }, + { TUNER_ABSENT, "Philips TD1536D_FH_44"}, + { TUNER_LG_NTSC_FM, "LG TPI8NSR01F"}, + { TUNER_LG_PAL_FM, "LG TPI8PSB01D"}, + { TUNER_LG_PAL, "LG TPI8PSB11D"}, + { TUNER_LG_PAL_I_FM, "LG TAPC-I001D"}, + { TUNER_LG_PAL_I, "LG TAPC-I701D"} +}; + +static void __devinit hauppauge_eeprom(struct cx8800_dev *dev, u8 *eeprom_data) +{ + unsigned int blk2,tuner,radio,model; + + if (eeprom_data[0] != 0x84 || eeprom_data[2] != 0) { + printk(KERN_WARNING "%s: Hauppauge eeprom: invalid\n", + dev->name); + return; + } + + /* Block 2 starts after len+3 bytes header */ + blk2 = eeprom_data[1] + 3; + + /* decode + use some config infos */ + model = eeprom_data[12] << 8 | eeprom_data[11]; + tuner = eeprom_data[9]; + radio = eeprom_data[blk2-1] & 0x01; + + if (tuner < ARRAY_SIZE(hauppauge_tuner)) + dev->tuner_type = hauppauge_tuner[tuner].id; + if (radio) + dev->has_radio = 1; + + printk(KERN_INFO "%s: hauppauge eeprom: model=%d, " + "tuner=%s (%d), radio=%s\n", + dev->name, model, hauppauge_tuner[tuner].name, + dev->tuner_type, radio ? "yes" : "no"); +} + +/* ----------------------------------------------------------------------- */ +/* some GDI (was: Modular Technology) specific stuff */ + +static struct { + int id; + int fm; + char *name; +} gdi_tuner[] = { + [ 0x01 ] = { .id = TUNER_ABSENT, + .name = "NTSC_M" }, + [ 0x02 ] = { .id = TUNER_ABSENT, + .name = "PAL_B" }, + [ 0x03 ] = { .id = TUNER_ABSENT, + .name = "BAL_I" }, + [ 0x04 ] = { .id = TUNER_ABSENT, + .name = "PAL_D" }, + [ 0x05 ] = { .id = TUNER_ABSENT, + .name = "SECAM" }, + + [ 0x10 ] = { .id = TUNER_ABSENT, .fm = 1, + .name = "TEMIC_4049" }, + [ 0x11 ] = { .id = TUNER_TEMIC_4136FY5, + .name = "TEMIC_4136" }, + [ 0x12 ] = { .id = TUNER_ABSENT, + .name = "TEMIC_4146" }, + + [ 0x20 ] = { .id = TUNER_PHILIPS_FQ1216ME, .fm = 1, + .name = "PHILIPS_FQ1216_MK3" }, + [ 0x21 ] = { .id = TUNER_ABSENT, .fm = 1, + .name = "PHILIPS_FQ1236_MK3" }, + [ 0x22 ] = { .id = TUNER_ABSENT, + .name = "PHILIPS_FI1236_MK3" }, + [ 0x23 ] = { .id = TUNER_ABSENT, + .name = "PHILIPS_FI1216_MK3" }, +}; + +static void __devinit gdi_eeprom(struct cx8800_dev *dev, u8 *eeprom_data) +{ + char *name = (eeprom_data[0x0d] < ARRAY_SIZE(gdi_tuner)) + ? gdi_tuner[eeprom_data[0x0d]].name : NULL; + + printk(KERN_INFO "%s: GDI: tuner=%s\n", dev->name, + name ? name : "unknown"); + if (NULL == name) + return; + dev->tuner_type = gdi_tuner[eeprom_data[0x0d]].id; + dev->has_radio = gdi_tuner[eeprom_data[0x0d]].fm; +} + +/* ----------------------------------------------------------------------- */ + +static int +i2c_eeprom(struct i2c_client *c, unsigned char *eedata, int len) +{ + unsigned char buf; + int err; + + c->addr = 0xa0 >> 1; + buf = 0; + if (1 != (err = i2c_master_send(c,&buf,1))) { + printk(KERN_INFO "cx88: Huh, no eeprom present (err=%d)?\n", + err); + return -1; + } + if (len != (err = i2c_master_recv(c,eedata,len))) { + printk(KERN_WARNING "cx88: i2c eeprom read error (err=%d)\n", + err); + return -1; + } +#if 0 + for (i = 0; i < len; i++) { + if (0 == (i % 16)) + printk(KERN_INFO "cx88 ee: %02x:",i); + printk(" %02x",eedata[i]); + if (15 == (i % 16)) + printk("\n"); + } +#endif + return 0; +} + +void __devinit cx88_card_setup(struct cx8800_dev *dev) +{ + static u8 eeprom[128]; + + switch (dev->board) { + case CX88_BOARD_HAUPPAUGE: + if (0 == dev->i2c_rc) + i2c_eeprom(&dev->i2c_client,eeprom,sizeof(eeprom)); + hauppauge_eeprom(dev,eeprom+8); + break; + case CX88_BOARD_GDI: + if (0 == dev->i2c_rc) + i2c_eeprom(&dev->i2c_client,eeprom,sizeof(eeprom)); + gdi_eeprom(dev,eeprom); + break; + } +} + +/* ------------------------------------------------------------------ */ + +EXPORT_SYMBOL(cx88_boards); +EXPORT_SYMBOL(cx88_bcount); +EXPORT_SYMBOL(cx88_subids); +EXPORT_SYMBOL(cx88_idcount); +EXPORT_SYMBOL(cx88_card_setup); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/cx88/cx88-core.c b/linux/drivers/media/video/cx88/cx88-core.c new file mode 100644 index 000000000..800556791 --- /dev/null +++ b/linux/drivers/media/video/cx88/cx88-core.c @@ -0,0 +1,600 @@ +/* + * device driver for Conexant 2388x based TV cards + * driver core + * + * (c) 2003 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * 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/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/kmod.h> +#include <linux/sound.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/videodev.h> + +#include "cx88.h" + +MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------------ */ + +#if 0 +static unsigned int gpio_tracking = 0; +MODULE_PARM(gpio_tracking,"i"); +MODULE_PARM_DESC(gpio_tracking,"enable debug messages [gpio]"); + +static unsigned int ts_nr = -1; +MODULE_PARM(ts_nr,"i"); +MODULE_PARM_DESC(ts_nr,"ts device number"); + +static unsigned int vbi_nr = -1; +MODULE_PARM(vbi_nr,"i"); +MODULE_PARM_DESC(vbi_nr,"vbi device number"); + +static unsigned int radio_nr = -1; +MODULE_PARM(radio_nr,"i"); +MODULE_PARM_DESC(radio_nr,"radio device number"); + +static unsigned int oss = 0; +MODULE_PARM(oss,"i"); +MODULE_PARM_DESC(oss,"register oss devices (default: no)"); + +static unsigned int dsp_nr = -1; +MODULE_PARM(dsp_nr,"i"); +MODULE_PARM_DESC(dsp_nr,"oss dsp device number"); + +static unsigned int mixer_nr = -1; +MODULE_PARM(mixer_nr,"i"); +MODULE_PARM_DESC(mixer_nr,"oss mixer device number"); +#endif + +static unsigned int core_debug = 0; +MODULE_PARM(core_debug,"i"); +MODULE_PARM_DESC(core_debug,"enable debug messages [core]"); + +#define dprintk(fmt, arg...) if (core_debug) \ + printk(KERN_DEBUG "%s/core: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ +/* debug help functions */ + +static const char *v4l1_ioctls[] = { + "0", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", "GPICT", "SPICT", + "CCAPTURE", "GWIN", "SWIN", "GFBUF", "SFBUF", "KEY", "GFREQ", + "SFREQ", "GAUDIO", "SAUDIO", "SYNC", "MCAPTURE", "GMBUF", "GUNIT", + "GCAPTURE", "SCAPTURE", "SPLAYMODE", "SWRITEMODE", "GPLAYINFO", + "SMICROCODE", "GVBIFMT", "SVBIFMT" }; +#define V4L1_IOCTLS ARRAY_SIZE(v4l1_ioctls) + +static const char *v4l2_ioctls[] = { + "QUERYCAP", "1", "ENUM_PIXFMT", "ENUM_FBUFFMT", "G_FMT", "S_FMT", + "G_COMP", "S_COMP", "REQBUFS", "QUERYBUF", "G_FBUF", "S_FBUF", + "G_WIN", "S_WIN", "PREVIEW", "QBUF", "16", "DQBUF", "STREAMON", + "STREAMOFF", "G_PERF", "G_PARM", "S_PARM", "G_STD", "S_STD", + "ENUMSTD", "ENUMINPUT", "G_CTRL", "S_CTRL", "G_TUNER", "S_TUNER", + "G_FREQ", "S_FREQ", "G_AUDIO", "S_AUDIO", "35", "QUERYCTRL", + "QUERYMENU", "G_INPUT", "S_INPUT", "ENUMCVT", "41", "42", "43", + "44", "45", "G_OUTPUT", "S_OUTPUT", "ENUMOUTPUT", "G_AUDOUT", + "S_AUDOUT", "ENUMFX", "G_EFFECT", "S_EFFECT", "G_MODULATOR", + "S_MODULATOR" +}; +#define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls) + +void cx88_print_ioctl(char *name, unsigned int cmd) +{ + char *dir; + + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: dir = "--"; break; + case _IOC_READ: dir = "r-"; break; + case _IOC_WRITE: dir = "-w"; break; + case _IOC_READ | _IOC_WRITE: dir = "rw"; break; + default: dir = "??"; break; + } + switch (_IOC_TYPE(cmd)) { + case 'v': + printk(KERN_DEBUG "%s: ioctl 0x%08x (v4l1, %s, VIDIOC%s)\n", + name, cmd, dir, (_IOC_NR(cmd) < V4L1_IOCTLS) ? + v4l1_ioctls[_IOC_NR(cmd)] : "???"); + break; + case 'V': + printk(KERN_DEBUG "%s: ioctl 0x%08x (v4l2, %s, VIDIOC_%s)\n", + name, cmd, dir, (_IOC_NR(cmd) < V4L2_IOCTLS) ? + v4l2_ioctls[_IOC_NR(cmd)] : "???"); + break; + default: + printk(KERN_DEBUG "%s: ioctl 0x%08x (???, %s, #%d)\n", + name, cmd, dir, _IOC_NR(cmd)); + } +} + +/* ------------------------------------------------------------------ */ + +static u32* cx88_risc_field(u32 *rp, struct scatterlist *sglist, + unsigned int offset, u32 sync_line, + unsigned int bpl, unsigned int padding, + unsigned int lines) +{ + struct scatterlist *sg; + unsigned int line,todo; + + /* sync instruction */ + *(rp++) = cpu_to_le32(RISC_RESYNC | sync_line); + + /* scan lines */ + sg = sglist; + for (line = 0; line < lines; line++) { + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg++; + } + if (bpl <= sg_dma_len(sg)-offset) { + /* fits into current chunk */ + *(rp++)=cpu_to_le32(RISC_WRITE|RISC_SOL|RISC_EOL|bpl); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + offset+=bpl; + } else { + /* scanline needs to be splitted */ + todo = bpl; + *(rp++)=cpu_to_le32(RISC_WRITE|RISC_SOL| + (sg_dma_len(sg)-offset)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + todo -= (sg_dma_len(sg)-offset); + offset = 0; + sg++; + while (todo > sg_dma_len(sg)) { + *(rp++)=cpu_to_le32(RISC_WRITE| + sg_dma_len(sg)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + todo -= sg_dma_len(sg); + sg++; + } + *(rp++)=cpu_to_le32(RISC_WRITE|RISC_EOL|todo); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + offset += todo; + } + offset += padding; + } + + return rp; +} + +int cx88_risc_buffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines) +{ + u32 instructions,fields; + u32 *rp; + int rc; + + fields = 0; + if (UNSET != top_offset) + fields++; + if (UNSET != bottom_offset) + fields++; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + syncs + jump (all 2 dwords) */ + instructions = (bpl * lines * fields) / PAGE_SIZE + lines * fields; + instructions += 3 + 4; + if ((rc = btcx_riscmem_alloc(pci,risc,instructions*8)) < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + if (UNSET != top_offset) + rp = cx88_risc_field(rp, sglist, top_offset, 0, + bpl, padding, lines); + if (UNSET != bottom_offset) + rp = cx88_risc_field(rp, sglist, bottom_offset, 0x200, + bpl, padding, lines); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + return 0; +} + +int cx88_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc, + u32 reg, u32 mask, u32 value) +{ + u32 *rp; + int rc; + + if ((rc = btcx_riscmem_alloc(pci, risc, 4*16)) < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(RISC_WRITECR | RISC_IRQ2 | RISC_IMM); + *(rp++) = cpu_to_le32(reg); + *(rp++) = cpu_to_le32(value); + *(rp++) = cpu_to_le32(mask); + *(rp++) = cpu_to_le32(RISC_JUMP); + *(rp++) = cpu_to_le32(risc->dma); + return 0; +} + +void +cx88_free_buffer(struct pci_dev *pci, struct cx88_buffer *buf) +{ + if (in_interrupt()) + BUG(); + videobuf_waiton(&buf->vb,0,0); + videobuf_dma_pci_unmap(pci, &buf->vb.dma); + videobuf_dma_free(&buf->vb.dma); + btcx_riscmem_free(pci, &buf->risc); + buf->vb.state = STATE_NEEDS_INIT; +} + +/* ------------------------------------------------------------------ */ +/* our SRAM memory layout */ + +/* we are going to put all thr risc programs into host memory, so we + * can use the whole SDRAM for the DMA fifos. To simplify things, we + * use a static memory layout. That surely will waste memory in case + * we don't use all DMA channels at the same time (which will be the + * case most of the time). But that still gives us enougth FIFO space + * to be able to deal with insane long pci latencies ... + * + * FIFO space allocations: + * channel 21 (y video) - 10.0k + * channel 24 (vbi) - 4.0k + * channels 25+26 (audio) - 0.5k + * everything else - 2.0k + * TOTAL = 29.0k + * + * Every channel has 160 bytes control data (64 bytes instruction + * queue and 6 CDT entries), which is close to 2k total. + * + * Address layout: + * 0x0000 - 0x03ff CMDs / reserved + * 0x0400 - 0x0bff instruction queues + CDs + * 0x0c00 - FIFOs + */ + +struct sram_channel cx88_sram_channels[] = { + [SRAM_CH21] = { + .name = "video y / packed", + .cmds_start = 0x180040, + .ctrl_start = 0x180400, + .cdt = 0x180400 + 64, + .fifo_start = 0x180c00, + .fifo_size = 0x002800, + .ptr1_reg = MO_DMA21_PTR1, + .ptr2_reg = MO_DMA21_PTR2, + .cnt1_reg = MO_DMA21_CNT1, + .cnt2_reg = MO_DMA21_CNT2, + }, + [SRAM_CH22] = { + .name = "video u", + .cmds_start = 0x180080, + .ctrl_start = 0x1804a0, + .cdt = 0x1804a0 + 64, + .fifo_start = 0x183400, + .fifo_size = 0x000800, + .ptr1_reg = MO_DMA22_PTR1, + .ptr2_reg = MO_DMA22_PTR2, + .cnt1_reg = MO_DMA22_CNT1, + .cnt2_reg = MO_DMA22_CNT2, + }, + [SRAM_CH23] = { + .name = "video v", + .cmds_start = 0x1800c0, + .ctrl_start = 0x180540, + .cdt = 0x180540 + 64, + .fifo_start = 0x183c00, + .fifo_size = 0x000800, + .ptr1_reg = MO_DMA23_PTR1, + .ptr2_reg = MO_DMA23_PTR2, + .cnt1_reg = MO_DMA23_CNT1, + .cnt2_reg = MO_DMA23_CNT2, + }, + [SRAM_CH24] = { + .name = "vbi", + .cmds_start = 0x180100, + .ctrl_start = 0x1805e0, + .cdt = 0x1805e0 + 64, + .fifo_start = 0x184400, + .fifo_size = 0x001000, + .ptr1_reg = MO_DMA24_PTR1, + .ptr2_reg = MO_DMA24_PTR2, + .cnt1_reg = MO_DMA24_CNT1, + .cnt2_reg = MO_DMA24_CNT2, + }, + [SRAM_CH25] = { + .name = "audio from", + .cmds_start = 0x180140, + .ctrl_start = 0x180680, + .cdt = 0x180680 + 64, + .fifo_start = 0x185400, + .fifo_size = 0x000200, + .ptr1_reg = MO_DMA25_PTR1, + .ptr2_reg = MO_DMA25_PTR2, + .cnt1_reg = MO_DMA25_CNT1, + .cnt2_reg = MO_DMA25_CNT2, + }, + [SRAM_CH26] = { + .name = "audio to", + .cmds_start = 0x180180, + .ctrl_start = 0x180720, + .cdt = 0x180680 + 64, /* same as audio IN */ + .fifo_start = 0x185400, /* same as audio IN */ + .fifo_size = 0x000200, /* same as audio IN */ + .ptr1_reg = MO_DMA26_PTR1, + .ptr2_reg = MO_DMA26_PTR2, + .cnt1_reg = MO_DMA26_CNT1, + .cnt2_reg = MO_DMA26_CNT2, + }, +}; + +int cx88_sram_channel_setup(struct cx8800_dev *dev, + struct sram_channel *ch, + unsigned int bpl, u32 risc) +{ + unsigned int i,lines; + u32 cdt; + + bpl = (bpl + 7) & ~7; /* alignment */ + cdt = ch->cdt; + lines = ch->fifo_size / bpl; + if (lines > 6) + lines = 6; + BUG_ON(lines < 2); + + /* write CDT */ + for (i = 0; i < lines; i++) + cx_write(cdt + 16*i, ch->fifo_start + bpl*i); + + /* write CMDS */ + cx_write(ch->cmds_start + 0, risc); + cx_write(ch->cmds_start + 4, cdt); + cx_write(ch->cmds_start + 8, (lines*16) >> 3); + cx_write(ch->cmds_start + 12, ch->ctrl_start); + cx_write(ch->cmds_start + 16, 64 >> 2); + for (i = 20; i < 64; i += 4) + cx_write(ch->cmds_start + i, 0); + + /* fill registers */ + cx_write(ch->ptr1_reg, ch->fifo_start); + cx_write(ch->ptr2_reg, cdt); + cx_write(ch->cnt1_reg, bpl >> 3); + cx_write(ch->cnt2_reg, (lines*16) >> 3); + + dprintk("sram setup %s: bpl=%d lines=%d\n", ch->name, bpl, lines); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* debug helper code */ + +int cx88_risc_decode(u32 risc) +{ + static char *instr[16] = { + [ RISC_SYNC >> 28 ] = "sync", + [ RISC_WRITE >> 28 ] = "write", + [ RISC_WRITEC >> 28 ] = "writec", + [ RISC_READ >> 28 ] = "read", + [ RISC_READC >> 28 ] = "readc", + [ RISC_JUMP >> 28 ] = "jump", + [ RISC_SKIP >> 28 ] = "skip", + [ RISC_WRITERM >> 28 ] = "writerm", + [ RISC_WRITECM >> 28 ] = "writecm", + [ RISC_WRITECR >> 28 ] = "writecr", + }; + static int incr[16] = { + [ RISC_WRITE >> 28 ] = 2, + [ RISC_JUMP >> 28 ] = 2, + [ RISC_WRITERM >> 28 ] = 3, + [ RISC_WRITECM >> 28 ] = 3, + [ RISC_WRITECR >> 28 ] = 4, + }; + static char *bits[] = { + "12", "13", "14", "resync", + "cnt0", "cnt1", "18", "19", + "20", "21", "22", "23", + "irq1", "irq2", "eol", "sol", + }; + int i; + + printk("0x%08x [ %s", risc, + instr[risc >> 28] ? instr[risc >> 28] : "INVALID"); + for (i = ARRAY_SIZE(bits)-1; i >= 0; i--) + if (risc & (1 << (i + 12))) + printk(" %s",bits[i]); + printk(" count=%d ]\n", risc & 0xfff); + return incr[risc >> 28] ? incr[risc >> 28] : 1; +} + +void cx88_risc_disasm(struct cx8800_dev *dev, + struct btcx_riscmem *risc) +{ + unsigned int i,j,n; + + printk("%s: risc disasm: %p [dma=0x%08lx]\n", + dev->name, risc->cpu, (unsigned long)risc->dma); + for (i = 0; i < (risc->size >> 2); i += n) { + printk("%s: %04d: ", dev->name, i); + n = cx88_risc_decode(risc->cpu[i]); + for (j = 1; j < n; j++) + printk("%s: %04d: 0x%08x [ arg #%d ]\n", + dev->name, i+j, risc->cpu[i+j], j); + if (risc->cpu[i] == RISC_JUMP) + break; + } +} + +void cx88_sram_channel_dump(struct cx8800_dev *dev, + struct sram_channel *ch) +{ + static char *name[] = { + "initial risc", + "cdt base", + "cdt size", + "iq base", + "iq size", + "risc pc", + "iq wr ptr", + "iq rd ptr", + "cdt current", + "pci target", + "line / byte", + }; + u32 risc; + unsigned int i,j,n; + + printk("%s: %s - dma channel status dump\n",dev->name,ch->name); + for (i = 0; i < ARRAY_SIZE(name); i++) + printk("%s: cmds: %-12s: 0x%08x\n", + dev->name,name[i], + cx_read(ch->cmds_start + 4*i)); + for (i = 0; i < 4; i++) { + risc = cx_read(ch->cmds_start + 4 * (i+11)); + printk("%s: risc%d: ", dev->name, i); + cx88_risc_decode(risc); + } + for (i = 0; i < 16; i += n) { + risc = cx_read(ch->ctrl_start + 4 * i); + printk("%s: iq %x: ", dev->name, i); + n = cx88_risc_decode(risc); + for (j = 1; j < n; j++) { + risc = cx_read(ch->ctrl_start + 4 * (i+j)); + printk("%s: iq %x: 0x%08x [ arg #%d ]\n", + dev->name, i+j, risc, j); + } + } + + printk("%s: fifo: 0x%08x -> 0x%x\n", + dev->name, ch->fifo_start, ch->fifo_start+ch->fifo_size); + printk("%s: ctrl: 0x%08x -> 0x%x\n", + dev->name, ch->ctrl_start, ch->ctrl_start+6*16); + printk("%s: ptr1_reg: 0x%08x\n", + dev->name,cx_read(ch->ptr1_reg)); + printk("%s: ptr2_reg: 0x%08x\n", + dev->name,cx_read(ch->ptr2_reg)); + printk("%s: cnt1_reg: 0x%08x\n", + dev->name,cx_read(ch->cnt1_reg)); + printk("%s: cnt2_reg: 0x%08x\n", + dev->name,cx_read(ch->cnt2_reg)); +} + +char *cx88_pci_irqs[32] = { + "vid", "aud", "ts", "vip", "hst", "5", "6", "tm1", + "src_dma", "dst_dma", "risc_rd_err", "risc_wr_err", + "brdg_err", "src_dma_err", "dst_dma_err", "ipb_dma_err", + "i2c", "i2c_rack", "ir_smp", "gpio0", "gpio1" +}; +char *cx88_vid_irqs[32] = { + "y_risci1", "u_risci1", "v_risci1", "vbi_risc1", + "y_risci2", "u_risci2", "v_risci2", "vbi_risc2", + "y_oflow", "u_oflow", "v_oflow", "vbi_oflow", + "y_sync", "u_sync", "v_sync", "vbi_sync", + "opc_err", "par_err", "rip_err", "pci_abort", +}; + +void cx88_print_irqbits(char *name, char *tag, char **strings, + u32 bits, u32 mask) +{ + unsigned int i; + + printk(KERN_DEBUG "%s: %s [0x%x]", name, tag, bits); + for (i = 0; i < 32; i++) { + if (!(bits & (1 << i))) + continue; + printk(" %s",strings[i]); + if (!(mask & (1 << i))) + continue; + printk("*"); + } + printk("\n"); +} + +/* ------------------------------------------------------------------ */ + +int cx88_pci_quirks(char *name, struct pci_dev *pci, unsigned int *latency) +{ + u8 ctrl = 0; + u8 value; + + if (0 == pci_pci_problems) + return 0; + + if (pci_pci_problems & PCIPCI_TRITON) { + printk(KERN_INFO "%s: quirk: PCIPCI_TRITON -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_NATOMA) { + printk(KERN_INFO "%s: quirk: PCIPCI_NATOMA -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_VIAETBF) { + printk(KERN_INFO "%s: quirk: PCIPCI_VIAETBF -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_VSFX) { + printk(KERN_INFO "%s: quirk: PCIPCI_VSFX -- set VSFX\n", + name); + ctrl |= CX88X_EN_VSFX; + } +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) { + printk(KERN_INFO "%s: quirk: PCIPCI_ALIMAGIK -- latency fixup\n", + name); + *latency = 0x0A; + } +#endif + if (ctrl) { + pci_read_config_byte(pci, CX88X_DEVCTRL, &value); + value |= ctrl; + pci_write_config_byte(pci, CX88X_DEVCTRL, value); + } + return 0; +} + +/* ------------------------------------------------------------------ */ + +EXPORT_SYMBOL(cx88_print_ioctl); +EXPORT_SYMBOL(cx88_pci_irqs); +EXPORT_SYMBOL(cx88_vid_irqs); +EXPORT_SYMBOL(cx88_print_irqbits); + +EXPORT_SYMBOL(cx88_risc_buffer); +EXPORT_SYMBOL(cx88_risc_stopper); +EXPORT_SYMBOL(cx88_free_buffer); + +EXPORT_SYMBOL(cx88_risc_disasm); + +EXPORT_SYMBOL(cx88_sram_channels); +EXPORT_SYMBOL(cx88_sram_channel_setup); +EXPORT_SYMBOL(cx88_sram_channel_dump); + +EXPORT_SYMBOL(cx88_pci_quirks); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/cx88/cx88-i2c.c b/linux/drivers/media/video/cx88/cx88-i2c.c new file mode 100644 index 000000000..2de607e1f --- /dev/null +++ b/linux/drivers/media/video/cx88/cx88-i2c.c @@ -0,0 +1,174 @@ +/* + cx88-i2c.c -- all the i2c code is here + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 2002 Yurij Sysoev <yurij@naturesoft.net> + (c) 1999-2003 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#define __NO_VERSION__ 1 + +#include <linux/module.h> +#include <linux/init.h> + +#include <asm/io.h> + +#include "cx88.h" + +/* ----------------------------------------------------------------------- */ + +void cx8800_bit_setscl(void *data, int state) +{ + struct cx8800_dev *dev = data; + + if (state) + dev->i2c_state |= 0x02; + else + dev->i2c_state &= ~0x02; + cx_write(MO_I2C, dev->i2c_state); + cx_read(MO_I2C); +} + +void cx8800_bit_setsda(void *data, int state) +{ + struct cx8800_dev *dev = data; + + if (state) + dev->i2c_state |= 0x01; + else + dev->i2c_state &= ~0x01; + cx_write(MO_I2C, dev->i2c_state); + cx_read(MO_I2C); +} + +static int cx8800_bit_getscl(void *data) +{ + struct cx8800_dev *dev = data; + u32 state; + + state = cx_read(MO_I2C); + return state & 0x02 ? 1 : 0; +} + +static int cx8800_bit_getsda(void *data) +{ + struct cx8800_dev *dev = data; + u32 state; + + state = cx_read(MO_I2C); + return state & 0x01; +} + +/* ----------------------------------------------------------------------- */ + +#ifndef I2C_PEC +static void cx8800_inc_use(struct i2c_adapter *adap) +{ + MOD_INC_USE_COUNT; +} + +static void cx8800_dec_use(struct i2c_adapter *adap) +{ + MOD_DEC_USE_COUNT; +} +#endif + +static int attach_inform(struct i2c_client *client) +{ + struct cx8800_dev *dev = i2c_get_adapdata(client->adapter); + + if (dev->tuner_type != UNSET) + cx8800_call_i2c_clients(dev,TUNER_SET_TYPE,&dev->tuner_type); + + if (1 /* fixme: debug */) + printk("%s: i2c attach [client=%s]\n", + dev->name, i2c_clientname(client)); + return 0; +} + +void cx8800_call_i2c_clients(struct cx8800_dev *dev, unsigned int cmd, void *arg) +{ + if (0 != dev->i2c_rc) + return; + i2c_clients_command(&dev->i2c_adap, cmd, arg); +} + +static struct i2c_algo_bit_data cx8800_i2c_algo_template = { + .setsda = cx8800_bit_setsda, + .setscl = cx8800_bit_setscl, + .getsda = cx8800_bit_getsda, + .getscl = cx8800_bit_getscl, + .udelay = 16, + .mdelay = 10, + .timeout = 200, +}; + +/* ----------------------------------------------------------------------- */ + +static struct i2c_adapter cx8800_i2c_adap_template = { +#ifdef I2C_PEC + .owner = THIS_MODULE, +#else + .inc_use = cx8800_inc_use, + .dec_use = cx8800_dec_use, +#endif +#ifdef I2C_ADAP_CLASS_TV_ANALOG + .class = I2C_ADAP_CLASS_TV_ANALOG, +#endif + I2C_DEVNAME("cx2388x"), + .id = I2C_HW_B_BT848, + .client_register = attach_inform, +}; + +static struct i2c_client cx8800_i2c_client_template = { + I2C_DEVNAME("cx88xx internal"), + .id = -1, +}; + +/* init + register i2c algo-bit adapter */ +int __devinit cx8800_i2c_init(struct cx8800_dev *dev) +{ + memcpy(&dev->i2c_adap, &cx8800_i2c_adap_template, + sizeof(dev->i2c_adap)); + memcpy(&dev->i2c_algo, &cx8800_i2c_algo_template, + sizeof(dev->i2c_algo)); + memcpy(&dev->i2c_client, &cx8800_i2c_client_template, + sizeof(dev->i2c_client)); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,54) + dev->i2c_adap.dev.parent = &dev->pci->dev; +#endif + strcpy(dev->i2c_adap.name,dev->name); + dev->i2c_algo.data = dev; + i2c_set_adapdata(&dev->i2c_adap,dev); + dev->i2c_adap.algo_data = &dev->i2c_algo; + dev->i2c_client.adapter = &dev->i2c_adap; + + cx8800_bit_setscl(dev,1); + cx8800_bit_setsda(dev,1); + + dev->i2c_rc = i2c_bit_add_bus(&dev->i2c_adap); + return dev->i2c_rc; +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/cx88/cx88-reg.h b/linux/drivers/media/video/cx88/cx88-reg.h new file mode 100644 index 000000000..41e014e24 --- /dev/null +++ b/linux/drivers/media/video/cx88/cx88-reg.h @@ -0,0 +1,773 @@ +/* + cx88x-hw.h - CX2388x register offsets + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + 2001 Michael Eskin + 2002 Yurij Sysoev <yurij@naturesoft.net> + 2003 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _CX88_REG_H_ +#define _CX88_REG_H_ + +/* ---------------------------------------------------------------------- */ +/* PCI IDs and config space */ + +#ifndef PCI_VENDOR_ID_CONEXANT +# define PCI_VENDOR_ID_CONEXANT 0x14F1 +#endif +#ifndef PCI_DEVICE_ID_CX2300_VID +# define PCI_DEVICE_ID_CX2300_VID 0x8800 +#endif + +#define CX88X_DEVCTRL 0x40 +#define CX88X_EN_TBFX 0x02 +#define CX88X_EN_VSFX 0x04 + + +/* ---------------------------------------------------------------------- */ +/* DMA Controller registers */ + +#define MO_PDMA_STHRSH 0x200000 // Source threshold +#define MO_PDMA_STADRS 0x200004 // Source target address +#define MO_PDMA_SIADRS 0x200008 // Source internal address +#define MO_PDMA_SCNTRL 0x20000C // Source control +#define MO_PDMA_DTHRSH 0x200010 // Destination threshold +#define MO_PDMA_DTADRS 0x200014 // Destination target address +#define MO_PDMA_DIADRS 0x200018 // Destination internal address +#define MO_PDMA_DCNTRL 0x20001C // Destination control +#define MO_LD_SSID 0x200030 // Load subsystem ID +#define MO_DEV_CNTRL2 0x200034 // Device control +#define MO_PCI_INTMSK 0x200040 // PCI interrupt mask +#define MO_PCI_INTSTAT 0x200044 // PCI interrupt status +#define MO_PCI_INTMSTAT 0x200048 // PCI interrupt masked status +#define MO_VID_INTMSK 0x200050 // Video interrupt mask +#define MO_VID_INTSTAT 0x200054 // Video interrupt status +#define MO_VID_INTMSTAT 0x200058 // Video interrupt masked status +#define MO_VID_INTSSTAT 0x20005C // Video interrupt set status +#define MO_AUD_INTMSK 0x200060 // Audio interrupt mask +#define MO_AUD_INTSTAT 0x200064 // Audio interrupt status +#define MO_AUD_INTMSTAT 0x200068 // Audio interrupt masked status +#define MO_AUD_INTSSTAT 0x20006C // Audio interrupt set status +#define MO_TS_INTMSK 0x200070 // Transport stream interrupt mask +#define MO_TS_INTSTAT 0x200074 // Transport stream interrupt status +#define MO_TS_INTMSTAT 0x200078 // Transport stream interrupt mask status +#define MO_TS_INTSSTAT 0x20007C // Transport stream interrupt set status +#define MO_VIP_INTMSK 0x200080 // VIP interrupt mask +#define MO_VIP_INTSTAT 0x200084 // VIP interrupt status +#define MO_VIP_INTMSTAT 0x200088 // VIP interrupt masked status +#define MO_VIP_INTSSTAT 0x20008C // VIP interrupt set status +#define MO_GPHST_INTMSK 0x200090 // Host interrupt mask +#define MO_GPHST_INTSTAT 0x200094 // Host interrupt status +#define MO_GPHST_INTMSTAT 0x200098 // Host interrupt masked status +#define MO_GPHST_INTSSTAT 0x20009C // Host interrupt set status + +// DMA Channels 1-6 belong to SPIPE +#define MO_DMA7_PTR1 0x300018 // {24}RW* DMA Current Ptr : Ch#7 +#define MO_DMA8_PTR1 0x30001C // {24}RW* DMA Current Ptr : Ch#8 + +// DMA Channels 9-20 belong to SPIPE +#define MO_DMA21_PTR1 0x300080 // {24}R0* DMA Current Ptr : Ch#21 +#define MO_DMA22_PTR1 0x300084 // {24}R0* DMA Current Ptr : Ch#22 +#define MO_DMA23_PTR1 0x300088 // {24}R0* DMA Current Ptr : Ch#23 +#define MO_DMA24_PTR1 0x30008C // {24}R0* DMA Current Ptr : Ch#24 +#define MO_DMA25_PTR1 0x300090 // {24}R0* DMA Current Ptr : Ch#25 +#define MO_DMA26_PTR1 0x300094 // {24}R0* DMA Current Ptr : Ch#26 +#define MO_DMA27_PTR1 0x300098 // {24}R0* DMA Current Ptr : Ch#27 +#define MO_DMA28_PTR1 0x30009C // {24}R0* DMA Current Ptr : Ch#28 +#define MO_DMA29_PTR1 0x3000A0 // {24}R0* DMA Current Ptr : Ch#29 +#define MO_DMA30_PTR1 0x3000A4 // {24}R0* DMA Current Ptr : Ch#30 +#define MO_DMA31_PTR1 0x3000A8 // {24}R0* DMA Current Ptr : Ch#31 +#define MO_DMA32_PTR1 0x3000AC // {24}R0* DMA Current Ptr : Ch#32 + +#define MO_DMA21_PTR2 0x3000C0 // {24}RW* DMA Tab Ptr : Ch#21 +#define MO_DMA22_PTR2 0x3000C4 // {24}RW* DMA Tab Ptr : Ch#22 +#define MO_DMA23_PTR2 0x3000C8 // {24}RW* DMA Tab Ptr : Ch#23 +#define MO_DMA24_PTR2 0x3000CC // {24}RW* DMA Tab Ptr : Ch#24 +#define MO_DMA25_PTR2 0x3000D0 // {24}RW* DMA Tab Ptr : Ch#25 +#define MO_DMA26_PTR2 0x3000D4 // {24}RW* DMA Tab Ptr : Ch#26 +#define MO_DMA27_PTR2 0x3000D8 // {24}RW* DMA Tab Ptr : Ch#27 +#define MO_DMA28_PTR2 0x3000DC // {24}RW* DMA Tab Ptr : Ch#28 +#define MO_DMA29_PTR2 0x3000E0 // {24}RW* DMA Tab Ptr : Ch#29 +#define MO_DMA30_PTR2 0x3000E4 // {24}RW* DMA Tab Ptr : Ch#30 +#define MO_DMA31_PTR2 0x3000E8 // {24}RW* DMA Tab Ptr : Ch#31 +#define MO_DMA32_PTR2 0x3000EC // {24}RW* DMA Tab Ptr : Ch#32 + +#define MO_DMA21_CNT1 0x300100 // {11}RW* DMA Buffer Size : Ch#21 +#define MO_DMA22_CNT1 0x300104 // {11}RW* DMA Buffer Size : Ch#22 +#define MO_DMA23_CNT1 0x300108 // {11}RW* DMA Buffer Size : Ch#23 +#define MO_DMA24_CNT1 0x30010C // {11}RW* DMA Buffer Size : Ch#24 +#define MO_DMA25_CNT1 0x300110 // {11}RW* DMA Buffer Size : Ch#25 +#define MO_DMA26_CNT1 0x300114 // {11}RW* DMA Buffer Size : Ch#26 +#define MO_DMA27_CNT1 0x300118 // {11}RW* DMA Buffer Size : Ch#27 +#define MO_DMA28_CNT1 0x30011C // {11}RW* DMA Buffer Size : Ch#28 +#define MO_DMA29_CNT1 0x300120 // {11}RW* DMA Buffer Size : Ch#29 +#define MO_DMA30_CNT1 0x300124 // {11}RW* DMA Buffer Size : Ch#30 +#define MO_DMA31_CNT1 0x300128 // {11}RW* DMA Buffer Size : Ch#31 +#define MO_DMA32_CNT1 0x30012C // {11}RW* DMA Buffer Size : Ch#32 + +#define MO_DMA21_CNT2 0x300140 // {11}RW* DMA Table Size : Ch#21 +#define MO_DMA22_CNT2 0x300144 // {11}RW* DMA Table Size : Ch#22 +#define MO_DMA23_CNT2 0x300148 // {11}RW* DMA Table Size : Ch#23 +#define MO_DMA24_CNT2 0x30014C // {11}RW* DMA Table Size : Ch#24 +#define MO_DMA25_CNT2 0x300150 // {11}RW* DMA Table Size : Ch#25 +#define MO_DMA26_CNT2 0x300154 // {11}RW* DMA Table Size : Ch#26 +#define MO_DMA27_CNT2 0x300158 // {11}RW* DMA Table Size : Ch#27 +#define MO_DMA28_CNT2 0x30015C // {11}RW* DMA Table Size : Ch#28 +#define MO_DMA29_CNT2 0x300160 // {11}RW* DMA Table Size : Ch#29 +#define MO_DMA30_CNT2 0x300164 // {11}RW* DMA Table Size : Ch#30 +#define MO_DMA31_CNT2 0x300168 // {11}RW* DMA Table Size : Ch#31 +#define MO_DMA32_CNT2 0x30016C // {11}RW* DMA Table Size : Ch#32 + + +/* ---------------------------------------------------------------------- */ +/* Video registers */ + +#define MO_VIDY_DMA 0x310000 // {64}RWp Video Y +#define MO_VIDU_DMA 0x310008 // {64}RWp Video U +#define MO_VIDV_DMA 0x310010 // {64}RWp Video V +#define MO_VBI_DMA 0x310018 // {64}RWp VBI (Vertical blanking interval) + +#define MO_DEVICE_STATUS 0x310100 +#define MO_INPUT_FORMAT 0x310104 +#define MO_AGC_BURST 0x31010c +#define MO_CONTR_BRIGHT 0x310110 +#define MO_UV_SATURATION 0x310114 +#define MO_HUE 0x310118 +#define MO_HTOTAL 0x310120 +#define MO_HDELAY_EVEN 0x310124 +#define MO_HDELAY_ODD 0x310128 +#define MO_VDELAY_ODD 0x31012c +#define MO_VDELAY_EVEN 0x310130 +#define MO_HACTIVE_EVEN 0x31013c +#define MO_HACTIVE_ODD 0x310140 +#define MO_VACTIVE_EVEN 0x310144 +#define MO_VACTIVE_ODD 0x310148 +#define MO_HSCALE_EVEN 0x31014c +#define MO_HSCALE_ODD 0x310150 +#define MO_VSCALE_EVEN 0x310154 +#define MO_FILTER_EVEN 0x31015c +#define MO_VSCALE_ODD 0x310158 +#define MO_FILTER_ODD 0x310160 +#define MO_OUTPUT_FORMAT 0x310164 + +#define MO_PLL_REG 0x310168 // PLL register +#define MO_PLL_ADJ_CTRL 0x31016c // PLL adjust control register +#define MO_SCONV_REG 0x310170 // sample rate conversion register +#define MO_SCONV_FIFO 0x310174 // sample rate conversion fifo +#define MO_SUB_STEP 0x310178 // subcarrier step size +#define MO_SUB_STEP_DR 0x31017c // subcarrier step size for DR line + +#define MO_CAPTURE_CTRL 0x310180 // capture control +#define MO_COLOR_CTRL 0x310184 +#define MO_VBI_PACKET 0x310188 // vbi packet size / delay +#define MO_FIELD_COUNT 0x310190 // field counter +#define MO_VIP_CONFIG 0x310194 +#define MO_VBOS_CONTROL 0x3101a8 + +#define MO_AGC_BACK_VBI 0x310200 +#define MO_AGC_SYNC_TIP1 0x310208 + +#define MO_VIDY_GPCNT 0x31C020 // {16}RO Video Y general purpose counter +#define MO_VIDU_GPCNT 0x31C024 // {16}RO Video U general purpose counter +#define MO_VIDV_GPCNT 0x31C028 // {16}RO Video V general purpose counter +#define MO_VBI_GPCNT 0x31C02C // {16}RO VBI general purpose counter +#define MO_VIDY_GPCNTRL 0x31C030 // {2}WO Video Y general purpose control +#define MO_VIDU_GPCNTRL 0x31C034 // {2}WO Video U general purpose control +#define MO_VIDV_GPCNTRL 0x31C038 // {2}WO Video V general purpose control +#define MO_VBI_GPCNTRL 0x31C03C // {2}WO VBI general purpose counter +#define MO_VID_DMACNTRL 0x31C040 // {8}RW Video DMA control +#define MO_VID_XFR_STAT 0x31C044 // {1}RO Video transfer status + + +/* ---------------------------------------------------------------------- */ +/* audio registers */ + +#define MO_AUDD_DMA 0x320000 // {64}RWp Audio downstream +#define MO_AUDU_DMA 0x320008 // {64}RWp Audio upstream +#define MO_AUDR_DMA 0x320010 // {64}RWp Audio RDS (downstream) +#define MO_AUDD_GPCNT 0x32C020 // {16}RO Audio down general purpose counter +#define MO_AUDU_GPCNT 0x32C024 // {16}RO Audio up general purpose counter +#define MO_AUDR_GPCNT 0x32C028 // {16}RO Audio RDS general purpose counter +#define MO_AUDD_GPCNTRL 0x32C030 // {2}WO Audio down general purpose control +#define MO_AUDU_GPCNTRL 0x32C034 // {2}WO Audio up general purpose control +#define MO_AUDR_GPCNTRL 0x32C038 // {2}WO Audio RDS general purpose control +#define MO_AUD_DMACNTRL 0x32C040 // {6}RW Audio DMA control +#define MO_AUD_XFR_STAT 0x32C044 // {1}RO Audio transfer status +#define MO_AUDD_LNGTH 0x32C048 // {12}RW Audio down line length +#define MO_AUDR_LNGTH 0x32C04C // {12}RW Audio RDS line length + +#define AUD_INIT 0x320100 +#define AUD_INIT_LD 0x320104 +#define AUD_SOFT_RESET 0x320108 +#define AUD_I2SINPUTCNTL 0x320120 +#define AUD_BAUDRATE 0x320124 +#define AUD_I2SOUTPUTCNTL 0x320128 +#define AAGC_HYST 0x320134 +#define AAGC_GAIN 0x320138 +#define AAGC_DEF 0x32013c +#define AUD_IIR1_0_SEL 0x320150 +#define AUD_IIR1_0_SHIFT 0x320154 +#define AUD_IIR1_1_SEL 0x320158 +#define AUD_IIR1_1_SHIFT 0x32015c +#define AUD_IIR1_2_SEL 0x320160 +#define AUD_IIR1_2_SHIFT 0x320164 +#define AUD_IIR1_3_SEL 0x320168 +#define AUD_IIR1_3_SHIFT 0x32016c +#define AUD_IIR1_4_SEL 0x320170 +#define AUD_IIR1_4_SHIFT 0x32017c +#define AUD_IIR1_5_SEL 0x320180 +#define AUD_IIR1_5_SHIFT 0x320184 +#define AUD_IIR2_0_SEL 0x320190 +#define AUD_IIR2_0_SHIFT 0x320194 +#define AUD_IIR2_1_SEL 0x320198 +#define AUD_IIR2_1_SHIFT 0x32019c +#define AUD_IIR2_2_SEL 0x3201a0 +#define AUD_IIR2_2_SHIFT 0x3201a4 +#define AUD_IIR2_3_SEL 0x3201a8 +#define AUD_IIR2_3_SHIFT 0x3201ac +#define AUD_IIR3_0_SEL 0x3201c0 +#define AUD_IIR3_0_SHIFT 0x3201c4 +#define AUD_IIR3_1_SEL 0x3201c8 +#define AUD_IIR3_1_SHIFT 0x3201cc +#define AUD_IIR3_2_SEL 0x3201d0 +#define AUD_IIR3_2_SHIFT 0x3201d4 +#define AUD_IIR4_0_SEL 0x3201e0 +#define AUD_IIR4_0_SHIFT 0x3201e4 +#define AUD_IIR4_1_SEL 0x3201e8 +#define AUD_IIR4_1_SHIFT 0x3201ec +#define AUD_IIR4_2_SEL 0x3201f0 +#define AUD_IIR4_2_SHIFT 0x3201f4 +#define AUD_IIR4_0_CA0 0x320200 +#define AUD_IIR4_0_CA1 0x320204 +#define AUD_IIR4_0_CA2 0x320208 +#define AUD_IIR4_0_CB0 0x32020c +#define AUD_IIR4_0_CB1 0x320210 +#define AUD_IIR4_1_CA0 0x320214 +#define AUD_IIR4_1_CA1 0x320218 +#define AUD_IIR4_1_CA2 0x32021c +#define AUD_IIR4_1_CB0 0x320220 +#define AUD_IIR4_1_CB1 0x320224 +#define AUD_IIR4_2_CA0 0x320228 +#define AUD_IIR4_2_CA1 0x32022c +#define AUD_IIR4_2_CA2 0x320230 +#define AUD_IIR4_2_CB0 0x320234 +#define AUD_IIR4_2_CB1 0x320238 +#define AUD_HP_MD_IIR4_1 0x320250 +#define AUD_HP_PROG_IIR4_1 0x320254 +#define AUD_FM_MODE_ENABLE 0x320258 +#define AUD_POLY0_DDS_CONSTANT 0x320270 +#define AUD_DN0_FREQ 0x320274 +#define AUD_DN1_FREQ 0x320278 +#define AUD_DN1_FREQ_SHIFT 0x32027c +#define AUD_DN1_AFC 0x320280 +#define AUD_DN1_SRC_SEL 0x320284 +#define AUD_DN1_SHFT 0x320288 +#define AUD_DN2_FREQ 0x32028c +#define AUD_DN2_FREQ_SHIFT 0x320290 +#define AUD_DN2_AFC 0x320294 +#define AUD_DN2_SRC_SEL 0x320298 +#define AUD_DN2_SHFT 0x32029c +#define AUD_CRDC0_SRC_SEL 0x320300 +#define AUD_CRDC0_SHIFT 0x320304 +#define AUD_CORDIC_SHIFT_0 0x320308 +#define AUD_CRDC1_SRC_SEL 0x32030c +#define AUD_CRDC1_SHIFT 0x320310 +#define AUD_CORDIC_SHIFT_1 0x320314 +#define AUD_DCOC_0_SRC 0x320320 +#define AUD_DCOC0_SHIFT 0x320324 +#define AUD_DCOC_0_SHIFT_IN0 0x320328 +#define AUD_DCOC_0_SHIFT_IN1 0x32032c +#define AUD_DCOC_1_SRC 0x320330 +#define AUD_DCOC1_SHIFT 0x320334 +#define AUD_DCOC_1_SHIFT_IN0 0x320338 +#define AUD_DCOC_1_SHIFT_IN1 0x32033c +#define AUD_DCOC_2_SRC 0x320340 +#define AUD_DCOC2_SHIFT 0x320344 +#define AUD_DCOC_2_SHIFT_IN0 0x320348 +#define AUD_DCOC_2_SHIFT_IN1 0x32034c +#define AUD_DCOC_PASS_IN 0x320350 +#define AUD_PDET_SRC 0x320370 +#define AUD_PDET_SHIFT 0x320374 +#define AUD_PILOT_BQD_1_K0 0x320380 +#define AUD_PILOT_BQD_1_K1 0x320384 +#define AUD_PILOT_BQD_1_K2 0x320388 +#define AUD_PILOT_BQD_1_K3 0x32038c +#define AUD_PILOT_BQD_1_K4 0x320390 +#define AUD_PILOT_BQD_2_K0 0x320394 +#define AUD_PILOT_BQD_2_K1 0x320398 +#define AUD_PILOT_BQD_2_K2 0x32039c +#define AUD_PILOT_BQD_2_K3 0x3203a0 +#define AUD_PILOT_BQD_2_K4 0x3203a4 +#define AUD_THR_FR 0x3203c0 +#define AUD_X_PROG 0x3203c4 +#define AUD_Y_PROG 0x3203c8 +#define AUD_HARMONIC_MULT 0x3203cc +#define AUD_C1_UP_THR 0x3203d0 +#define AUD_C1_LO_THR 0x3203d4 +#define AUD_C2_UP_THR 0x3203d8 +#define AUD_C2_LO_THR 0x3203dc +#define AUD_PLL_EN 0x320400 +#define AUD_PLL_SRC 0x320404 +#define AUD_PLL_SHIFT 0x320408 +#define AUD_PLL_IF_SEL 0x32040c +#define AUD_PLL_IF_SHIFT 0x320410 +#define AUD_BIQUAD_PLL_K0 0x320414 +#define AUD_BIQUAD_PLL_K1 0x320418 +#define AUD_BIQUAD_PLL_K2 0x32041c +#define AUD_BIQUAD_PLL_K3 0x320420 +#define AUD_BIQUAD_PLL_K4 0x320424 +#define AUD_DEEMPH0_SRC_SEL 0x320440 +#define AUD_DEEMPH0_SHIFT 0x320444 +#define AUD_DEEMPH0_G0 0x320448 +#define AUD_DEEMPH0_A0 0x32044c +#define AUD_DEEMPH0_B0 0x320450 +#define AUD_DEEMPH0_A1 0x320454 +#define AUD_DEEMPH0_B1 0x320458 +#define AUD_DEEMPH1_SRC_SEL 0x32045c +#define AUD_DEEMPH1_SHIFT 0x320460 +#define AUD_DEEMPH1_G0 0x320464 +#define AUD_DEEMPH1_A0 0x320468 +#define AUD_DEEMPH1_B0 0x32046c +#define AUD_DEEMPH1_A1 0x320470 +#define AUD_DEEMPH1_B1 0x320474 +#define AUD_OUT0_SEL 0x320490 +#define AUD_OUT0_SHIFT 0x320494 +#define AUD_OUT1_SEL 0x320498 +#define AUD_OUT1_SHIFT 0x32049c +#define AUD_RDSI_SEL 0x3204a0 +#define AUD_RDSI_SHIFT 0x3204a4 +#define AUD_RDSQ_SEL 0x3204a8 +#define AUD_RDSQ_SHIFT 0x3204ac +#define AUD_DBX_IN_GAIN 0x320500 +#define AUD_DBX_WBE_GAIN 0x320504 +#define AUD_DBX_SE_GAIN 0x320508 +#define AUD_DBX_RMS_WBE 0x32050c +#define AUD_DBX_RMS_SE 0x320510 +#define AUD_DBX_SE_BYPASS 0x320514 +#define AUD_FAWDETCTL 0x320530 +#define AUD_FAWDETWINCTL 0x320534 +#define AUD_DEEMPHGAIN_R 0x320538 +#define AUD_DEEMPHNUMER1_R 0x32053c +#define AUD_DEEMPHNUMER2_R 0x320540 +#define AUD_DEEMPHDENOM1_R 0x320544 +#define AUD_DEEMPHDENOM2_R 0x320548 +#define AUD_ERRLOGPERIOD_R 0x32054c +#define AUD_ERRINTRPTTHSHLD1_R 0x320550 +#define AUD_ERRINTRPTTHSHLD2_R 0x320554 +#define AUD_ERRINTRPTTHSHLD3_R 0x320558 +#define AUD_NICAM_STATUS1 0x32055c +#define AUD_NICAM_STATUS2 0x320560 +#define AUD_ERRLOG1 0x320564 +#define AUD_ERRLOG2 0x320568 +#define AUD_ERRLOG3 0x32056c +#define AUD_DAC_BYPASS_L 0x320580 +#define AUD_DAC_BYPASS_R 0x320584 +#define AUD_DAC_BYPASS_CTL 0x320588 +#define AUD_CTL 0x32058c +#define AUD_STATUS 0x320590 +#define AUD_VOL_CTL 0x320594 +#define AUD_BAL_CTL 0x320598 +#define AUD_START_TIMER 0x3205b0 +#define AUD_MODE_CHG_TIMER 0x3205b4 +#define AUD_POLYPH80SCALEFAC 0x3205b8 +#define AUD_DMD_RA_DDS 0x3205bc +#define AUD_I2S_RA_DDS 0x3205c0 +#define AUD_RATE_THRES_DMD 0x3205d0 +#define AUD_RATE_THRES_I2S 0x3205d4 +#define AUD_RATE_ADJ1 0x3205d8 +#define AUD_RATE_ADJ2 0x3205dc +#define AUD_RATE_ADJ3 0x3205e0 +#define AUD_RATE_ADJ4 0x3205e4 +#define AUD_RATE_ADJ5 0x3205e8 +#define AUD_APB_IN_RATE_ADJ 0x3205ec +#define AUD_PHASE_FIX_CTL 0x3205f0 +#define AUD_PLL_PRESCALE 0x320600 +#define AUD_PLL_DDS 0x320604 +#define AUD_PLL_INT 0x320608 +#define AUD_PLL_FRAC 0x32060c +#define AUD_PLL_JTAG 0x320620 +#define AUD_PLL_SPMP 0x320624 +#define AUD_AFE_12DB_EN 0x320628 + +// Audio QAM Register Addresses +#define AUD_PDF_DDS_CNST_BYTE2 0x320d01 +#define AUD_PDF_DDS_CNST_BYTE1 0x320d02 +#define AUD_PDF_DDS_CNST_BYTE0 0x320d03 +#define AUD_PHACC_FREQ_8MSB 0x320d2a +#define AUD_PHACC_FREQ_8LSB 0x320d23 +#define AUD_QAM_MODE 0x320d04 + + +/* ---------------------------------------------------------------------- */ +/* transport stream registers */ + +#define MO_TS_DMA 0x330000 // {64}RWp Transport stream downstream +#define MO_TS_GPCNT 0x33C020 // {16}RO TS general purpose counter +#define MO_TS_GPCNTRL 0x33C030 // {2}WO TS general purpose control +#define MO_TS_DMACNTRL 0x33C040 // {6}RW TS DMA control +#define MO_TS_XFR_STAT 0x33C044 // {1}RO TS transfer status +#define MO_TS_LNGTH 0x33C048 // {12}RW TS line length + +#define TS_HW_SOP_CNTRL 0x33C04C +#define TS_GEN_CNTRL 0x33C050 +#define TS_BD_PKT_STAT 0x33C054 +#define TS_SOP_STAT 0x33C058 +#define TS_FIFO_OVFL_STAT 0x33C05C +#define TS_VALERR_CNTRL 0x33C060 + + +/* ---------------------------------------------------------------------- */ +/* VIP registers */ + +#define MO_VIPD_DMA 0x340000 // {64}RWp VIP downstream +#define MO_VIPU_DMA 0x340008 // {64}RWp VIP upstream +#define MO_VIPD_GPCNT 0x34C020 // {16}RO VIP down general purpose counter +#define MO_VIPU_GPCNT 0x34C024 // {16}RO VIP up general purpose counter +#define MO_VIPD_GPCNTRL 0x34C030 // {2}WO VIP down general purpose control +#define MO_VIPU_GPCNTRL 0x34C034 // {2}WO VIP up general purpose control +#define MO_VIP_DMACNTRL 0x34C040 // {6}RW VIP DMA control +#define MO_VIP_XFR_STAT 0x34C044 // {1}RO VIP transfer status +#define MO_VIP_CFG 0x340048 // VIP configuration +#define MO_VIPU_CNTRL 0x34004C // VIP upstream control #1 +#define MO_VIPD_CNTRL 0x340050 // VIP downstream control #2 +#define MO_VIPD_LNGTH 0x340054 // VIP downstream line length +#define MO_VIP_BRSTLN 0x340058 // VIP burst length +#define MO_VIP_INTCNTRL 0x34C05C // VIP Interrupt Control +#define MO_VIP_XFTERM 0x340060 // VIP transfer terminate + + +/* ---------------------------------------------------------------------- */ +/* misc registers */ + +#define MO_M2M_DMA 0x350000 // {64}RWp Mem2Mem DMA Bfr +#define MO_GP0_IO 0x350010 // {32}RW* GPIOoutput enablesdata I/O +#define MO_GP1_IO 0x350014 // {32}RW* GPIOoutput enablesdata I/O +#define MO_GP2_IO 0x350018 // {32}RW* GPIOoutput enablesdata I/O +#define MO_GP3_IO 0x35001C // {32}RW* GPIO Mode/Ctrloutput enables +#define MO_GPIO 0x350020 // {32}RW* GPIO I2C Ctrldata I/O +#define MO_GPOE 0x350024 // {32}RW GPIO I2C Ctrloutput enables +#define MO_GP_ISM 0x350028 // {16}WO GPIO Intr Sens/Pol + +#define MO_PLL_B 0x35C008 // {32}RW* PLL Control for ASB bus clks +#define MO_M2M_CNT 0x35C024 // {32}RW Mem2Mem DMA Cnt +#define MO_M2M_XSUM 0x35C028 // {32}RO M2M XOR-Checksum +#define MO_CRC 0x35C02C // {16}RW CRC16 init/result +#define MO_CRC_D 0x35C030 // {32}WO CRC16 new data in +#define MO_TM_CNT_LDW 0x35C034 // {32}RO Timer : Counter low dword +#define MO_TM_CNT_UW 0x35C038 // {16}RO Timer : Counter high word +#define MO_TM_LMT_LDW 0x35C03C // {32}RW Timer : Limit low dword +#define MO_TM_LMT_UW 0x35C040 // {32}RW Timer : Limit high word +#define MO_PINMUX_IO 0x35C044 // {8}RW Pin Mux Control +#define MO_TSTSEL_IO 0x35C048 // {2}RW Pin Mux Control +#define MO_AFECFG_IO 0x35C04C // AFE configuration reg +#define MO_DDS_IO 0x35C050 // DDS Increment reg +#define MO_DDSCFG_IO 0x35C054 // DDS Configuration reg +#define MO_SAMPLE_IO 0x35C058 // IRIn sample reg +#define MO_SRST_IO 0x35C05C // Output system reset reg + +#define MO_INT1_MSK 0x35C060 // DMA RISC interrupt mask +#define MO_INT1_STAT 0x35C064 // DMA RISC interrupt status +#define MO_INT1_MSTAT 0x35C068 // DMA RISC interrupt masked status + + +/* ---------------------------------------------------------------------- */ +/* i2c bus registers */ + +#define MO_I2C 0x368000 // I2C data/control +#define MO_I2C_DIV (0xf<<4) +#define MO_I2C_SYNC (1<<3) +#define MO_I2C_W3B (1<<2) +#define MO_I2C_SCL (1<<1) +#define MO_I2C_SDA (1<<0) + + +/* ---------------------------------------------------------------------- */ +/* general purpose host registers */ +/* FIXME: tyops? s/0x35/0x38/ ?? */ + +#define MO_GPHSTD_DMA 0x350000 // {64}RWp Host downstream +#define MO_GPHSTU_DMA 0x350008 // {64}RWp Host upstream +#define MO_GPHSTD_GPCNT 0x35C020 // Host down general purpose counter +#define MO_GPHSTU_GPCNT 0x35C024 // Host up general purpose counter +#define MO_GPHSTD_GPCNTRL 0x38C030 // Host down general purpose control +#define MO_GPHSTU_GPCNTRL 0x38C034 // Host up general purpose control +#define MO_GPHST_DMACNTRL 0x38C040 // Host DMA control +#define MO_GPHST_XFR_STAT 0x38C044 // Host transfer status +#define MO_GPHSTU_CNTRL 0x380048 // Host upstream control #1 +#define MO_GPHSTD_CNTRL 0x38004C // Host downstream control #2 +#define MO_GPHSTD_LNGTH 0x380050 // Host downstream line length +#define MO_GPHST_WSC 0x380054 // Host wait state control +#define MO_GPHST_XFR 0x380058 // Host transfer control +#define MO_GPHST_WDTH 0x38005C // Host interface width +#define MO_GPHST_HDSHK 0x380060 // Host peripheral handshake +#define MO_GPHST_MUX16 0x380064 // Host muxed 16-bit transfer parameters +#define MO_GPHST_MODE 0x380068 // Host mode select + + +/* ---------------------------------------------------------------------- */ +/* RISC instructions */ + +#define RISC_SYNC 0x80000000 +#define RISC_SYNC_ODD 0x80000000 +#define RISC_SYNC_EVEN 0x80000200 +#define RISC_RESYNC 0x80008000 +#define RISC_RESYNC_ODD 0x80008000 +#define RISC_RESYNC_EVEN 0x80008200 +#define RISC_WRITE 0x10000000 +#define RISC_WRITEC 0x50000000 +#define RISC_READ 0x90000000 +#define RISC_READC 0xA0000000 +#define RISC_JUMP 0x70000000 +#define RISC_SKIP 0x20000000 +#define RISC_WRITERM 0xB0000000 +#define RISC_WRITECM 0xC0000000 +#define RISC_WRITECR 0xD0000000 +#define RISC_IMM 0x00000001 + +#define RISC_SOL 0x08000000 +#define RISC_EOL 0x04000000 + +#define RISC_IRQ2 0x02000000 +#define RISC_IRQ1 0x01000000 + +#define RISC_CNT_NONE 0x00000000 +#define RISC_CNT_INC 0x00010000 +#define RISC_CNT_RSVR 0x00020000 +#define RISC_CNT_RESET 0x00030000 +#define RISC_JMP_SRP 0x01 + + +/* ---------------------------------------------------------------------- */ +/* various constants */ + +#define SEL_BTSC 0x01 +#define SEL_EIAJ 0x02 +#define SEL_A2 0x04 +#define SEL_SAP 0x08 +#define SEL_NICAM 0x10 +#define SEL_FMRADIO 0x20 + +// AUD_CTL +#define EN_BTSC_FORCE_MONO 0 +#define EN_BTSC_FORCE_STEREO 1 +#define EN_BTSC_FORCE_SAP 2 +#define EN_BTSC_AUTO_STEREO 3 +#define EN_BTSC_AUTO_SAP 4 + +#define EN_A2_FORCE_MONO1 8 +#define EN_A2_FORCE_MONO2 9 +#define EN_A2_FORCE_STEREO 10 +#define EN_A2_AUTO_MONO2 11 +#define EN_A2_AUTO_STEREO 12 + +#define EN_EIAJ_FORCE_MONO1 16 +#define EN_EIAJ_FORCE_MONO2 17 +#define EN_EIAJ_FORCE_STEREO 18 +#define EN_EIAJ_AUTO_MONO2 19 +#define EN_EIAJ_AUTO_STEREO 20 + +#define EN_NICAM_FORCE_MONO1 32 +#define EN_NICAM_FORCE_MONO2 33 +#define EN_NICAM_FORCE_STEREO 34 +#define EN_NICAM_AUTO_MONO2 35 +#define EN_NICAM_AUTO_STEREO 36 + +#define EN_FMRADIO_FORCE_MONO 24 +#define EN_FMRADIO_FORCE_STEREO 25 +#define EN_FMRADIO_AUTO_STEREO 26 + +#define EN_NICAM_AUTO_FALLBACK 0x00000040 +#define EN_FMRADIO_EN_RDS 0x00000200 +#define EN_NICAM_TRY_AGAIN_BIT 0x00000400 +#define EN_DAC_ENABLE 0x00001000 +#define EN_I2SOUT_ENABLE 0x00002000 +#define EN_I2SIN_STR2DAC 0x00004000 +#define EN_I2SIN_ENABLE 0x00008000 + +#define EN_DMTRX_SUMDIFF 0x00000800 +#define EN_DMTRX_SUMR 0x00000880 +#define EN_DMTRX_LR 0x00000900 +#define EN_DMTRX_MONO 0x00000980 + +// Video +#define VID_CAPTURE_CONTROL 0x310180 + +#define CX23880_CAP_CTL_CAPTURE_VBI_ODD (1<<3) +#define CX23880_CAP_CTL_CAPTURE_VBI_EVEN (1<<2) +#define CX23880_CAP_CTL_CAPTURE_ODD (1<<1) +#define CX23880_CAP_CTL_CAPTURE_EVEN (1<<0) + +#define VideoInputMux0 0x0 +#define VideoInputMux1 0x1 +#define VideoInputMux2 0x2 +#define VideoInputMux3 0x3 +#define VideoInputTuner 0x0 +#define VideoInputComposite 0x1 +#define VideoInputSVideo 0x2 +#define VideoInputOther 0x3 + +#define Xtal0 0x1 +#define Xtal1 0x2 +#define XtalAuto 0x3 + +#define VideoFormatAuto 0x0 +#define VideoFormatNTSC 0x1 +#define VideoFormatNTSCJapan 0x2 +#define VideoFormatNTSC443 0x3 +#define VideoFormatPAL 0x4 +#define VideoFormatPALB 0x4 +#define VideoFormatPALD 0x4 +#define VideoFormatPALG 0x4 +#define VideoFormatPALH 0x4 +#define VideoFormatPALI 0x4 +#define VideoFormatPALBDGHI 0x4 +#define VideoFormatPALM 0x5 +#define VideoFormatPALN 0x6 +#define VideoFormatPALNC 0x7 +#define VideoFormatPAL60 0x8 +#define VideoFormatSECAM 0x9 + +#define VideoFormatAuto27MHz 0x10 +#define VideoFormatNTSC27MHz 0x11 +#define VideoFormatNTSCJapan27MHz 0x12 +#define VideoFormatNTSC44327MHz 0x13 +#define VideoFormatPAL27MHz 0x14 +#define VideoFormatPALB27MHz 0x14 +#define VideoFormatPALD27MHz 0x14 +#define VideoFormatPALG27MHz 0x14 +#define VideoFormatPALH27MHz 0x14 +#define VideoFormatPALI27MHz 0x14 +#define VideoFormatPALBDGHI27MHz 0x14 +#define VideoFormatPALM27MHz 0x15 +#define VideoFormatPALN27MHz 0x16 +#define VideoFormatPALNC27MHz 0x17 +#define VideoFormatPAL6027MHz 0x18 +#define VideoFormatSECAM27MHz 0x19 + +#define NominalUSECAM 0x87 +#define NominalVSECAM 0x85 +#define NominalUNTSC 0xFE +#define NominalVNTSC 0xB4 + +#define NominalContrast 0xD8 + +#define HFilterAutoFormat 0x0 +#define HFilterCIF 0x1 +#define HFilterQCIF 0x2 +#define HFilterICON 0x3 + +#define VFilter2TapInterpolate 0 +#define VFilter3TapInterpolate 1 +#define VFilter4TapInterpolate 2 +#define VFilter5TapInterpolate 3 +#define VFilter2TapNoInterpolate 4 +#define VFilter3TapNoInterpolate 5 +#define VFilter4TapNoInterpolate 6 +#define VFilter5TapNoInterpolate 7 + +#define ColorFormatRGB32 0x0000 +#define ColorFormatRGB24 0x0011 +#define ColorFormatRGB16 0x0022 +#define ColorFormatRGB15 0x0033 +#define ColorFormatYUY2 0x0044 +#define ColorFormatBTYUV 0x0055 +#define ColorFormatY8 0x0066 +#define ColorFormatRGB8 0x0077 +#define ColorFormatPL422 0x0088 +#define ColorFormatPL411 0x0099 +#define ColorFormatYUV12 0x00AA +#define ColorFormatYUV9 0x00BB +#define ColorFormatRAW 0x00EE +#define ColorFormatBSWAP 0x0300 +#define ColorFormatWSWAP 0x0c00 +#define ColorFormatEvenMask 0x050f +#define ColorFormatOddMask 0x0af0 +#define ColorFormatGamma 0x1000 + +#define Interlaced 0x1 +#define NonInterlaced 0x0 + +#define FieldEven 0x1 +#define FieldOdd 0x0 + +#define TGReadWriteMode 0x0 +#define TGEnableMode 0x1 + +#define DV_CbAlign 0x0 +#define DV_Y0Align 0x1 +#define DV_CrAlign 0x2 +#define DV_Y1Align 0x3 + +#define DVF_Analog 0x0 +#define DVF_CCIR656 0x1 +#define DVF_ByteStream 0x2 +#define DVF_ExtVSYNC 0x4 +#define DVF_ExtField 0x5 + +#define CHANNEL_VID_Y 0x1 +#define CHANNEL_VID_U 0x2 +#define CHANNEL_VID_V 0x3 +#define CHANNEL_VID_VBI 0x4 +#define CHANNEL_AUD_DN 0x5 +#define CHANNEL_AUD_UP 0x6 +#define CHANNEL_AUD_RDS_DN 0x7 +#define CHANNEL_MPEG_DN 0x8 +#define CHANNEL_VIP_DN 0x9 +#define CHANNEL_VIP_UP 0xA +#define CHANNEL_HOST_DN 0xB +#define CHANNEL_HOST_UP 0xC +#define CHANNEL_FIRST 0x1 +#define CHANNEL_LAST 0xC + +#define GP_COUNT_CONTROL_NONE 0x0 +#define GP_COUNT_CONTROL_INC 0x1 +#define GP_COUNT_CONTROL_RESERVED 0x2 +#define GP_COUNT_CONTROL_RESET 0x3 + +#define PLL_PRESCALE_BY_2 2 +#define PLL_PRESCALE_BY_3 3 +#define PLL_PRESCALE_BY_4 4 +#define PLL_PRESCALE_BY_5 5 + +#define HLNotchFilter4xFsc 0 +#define HLNotchFilterSquare 1 +#define HLNotchFilter135NTSC 2 +#define HLNotchFilter135PAL 3 + +#define NTSC_8x_SUB_CARRIER 28.63636E6 +#define PAL_8x_SUB_CARRIER 35.46895E6 + +// Default analog settings +#define DEFAULT_HUE_NTSC 0x00 +#define DEFAULT_BRIGHTNESS_NTSC 0x00 +#define DEFAULT_CONTRAST_NTSC 0x39 +#define DEFAULT_SAT_U_NTSC 0x7F +#define DEFAULT_SAT_V_NTSC 0x5A + +typedef enum +{ + SOURCE_TUNER = 0, + SOURCE_COMPOSITE, + SOURCE_SVIDEO, + SOURCE_OTHER1, + SOURCE_OTHER2, + SOURCE_COMPVIASVIDEO, + SOURCE_CCIR656 +} VIDEOSOURCETYPE; + +#endif /* _CX88_REG_H_ */ diff --git a/linux/drivers/media/video/cx88/cx88-tvaudio.c b/linux/drivers/media/video/cx88/cx88-tvaudio.c new file mode 100644 index 000000000..e6b2f8f5f --- /dev/null +++ b/linux/drivers/media/video/cx88/cx88-tvaudio.c @@ -0,0 +1,472 @@ +/* + cx88x-audio.c - Conexant CX23880/23881 audio downstream driver driver + + (c) 2001 Michael Eskin, Tom Zakrajsek [Windows version] + (c) 2002 Yurij Sysoev <yurij@naturesoft.net> + (c) 2003 Gerd Knorr <kraxel@bytesex.org> + + ----------------------------------------------------------------------- + + Lot of voodoo here. Even the data sheet doesn't help to + understand what is going on here, the documentation for the audio + part of the cx2388x chip is *very* bad. + + Some of this comes from party done linux driver sources I got from + [undocumented]. + + Some comes from the dscaler sources, the dscaler driver guy works + for Conexant ... + + ----------------------------------------------------------------------- + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/poll.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/vmalloc.h> +#include <linux/init.h> + +#include "cx88.h" + +static unsigned int audio_debug = UNSET; +MODULE_PARM(audio_debug,"i"); +MODULE_PARM_DESC(audio_debug,"enable debug messages [audio]"); + +#define dprintk(fmt, arg...) if (audio_debug) \ + printk(KERN_DEBUG "%s: " fmt, dev->name , ## arg) + +/* ----------------------------------------------------------- */ + +struct rlist { + u32 reg; + u32 val; +}; + +static void set_audio_registers(struct cx8800_dev *dev, + const struct rlist *l) +{ + int i; + + for (i = 0; l[i].reg; i++) + cx_write(l[i].reg, l[i].val); +} + +static void set_audio_standard_BTSC(struct cx8800_dev *dev, unsigned int sap) +{ + dprintk("set_audio_standard_BTSC() [TODO]\n"); +} + +static void set_audio_standard_NICAM(struct cx8800_dev *dev) +{ + static const struct rlist nicam[] = { + // increase level of input by 12dB + { AUD_AFE_12DB_EN, 0x00000001 }, + + // initialize NICAM + { AUD_INIT, 0x00000010 }, + { AUD_INIT_LD, 0x00000001 }, + { AUD_SOFT_RESET, 0x00000001 }, + + // WARNING!!!! Stereo mode is FORCED!!!! + { AUD_CTL, EN_DAC_ENABLE | EN_DMTRX_LR | EN_NICAM_FORCE_STEREO }, + + { AUD_SOFT_RESET, 0x00000001 }, + { AUD_RATE_ADJ1, 0x00000010 }, + { AUD_RATE_ADJ2, 0x00000040 }, + { AUD_RATE_ADJ3, 0x00000100 }, + { AUD_RATE_ADJ4, 0x00000400 }, + { AUD_RATE_ADJ5, 0x00001000 }, + // { AUD_DMD_RA_DDS, 0x00c0d5ce }, + + { /* end of list */ }, + }; + + printk("set_audio_standard_NICAM()\n"); + set_audio_registers(dev, nicam); + + // setup QAM registers + cx_write(0x320d01, 0x06); + cx_write(0x320d02, 0x82); + cx_write(0x320d03, 0x16); + cx_write(0x320d04, 0x05); + cx_write(0x320d2a, 0x34); + cx_write(0x320d2b, 0x4c); + + // setup Audio PLL + //cx_write(AUD_PLL_PRESCALE, 0x0002); + //cx_write(AUD_PLL_INT, 0x001f); + + // de-assert Audio soft reset + cx_write(AUD_SOFT_RESET, 0x00000000); // Causes a pop every time +} + +static void set_audio_standard_A2(struct cx8800_dev *dev) +{ + static const struct rlist a2[] = { + // increase level of input by 12dB + { AUD_AFE_12DB_EN, 0x00000001 }, + + // initialize A2 + { AUD_INIT, 0x00000004 }, + { AUD_INIT_LD, 0x00000001 }, + { AUD_SOFT_RESET, 0x00000001 }, + + // ; WARNING!!! A2 STEREO DEMATRIX HAS TO BE + // ; SET MANUALLY!!! Value sould be 0x100c + { AUD_CTL, EN_DAC_ENABLE | EN_DMTRX_SUMR | EN_A2_AUTO_STEREO }, + + { AUD_DN0_FREQ, 0x0000312b }, + { AUD_POLY0_DDS_CONSTANT, 0x000a62b4 }, + { AUD_IIR1_0_SEL, 0x00000000 }, + { AUD_IIR1_1_SEL, 0x00000001 }, + { AUD_IIR1_2_SEL, 0x0000001f }, + { AUD_IIR1_3_SEL, 0x00000020 }, + { AUD_IIR1_4_SEL, 0x00000023 }, + { AUD_IIR1_5_SEL, 0x00000007 }, + { AUD_IIR1_0_SHIFT, 0x00000000 }, + { AUD_IIR1_1_SHIFT, 0x00000000 }, + { AUD_IIR1_2_SHIFT, 0x00000007 }, + { AUD_IIR1_3_SHIFT, 0x00000007 }, + { AUD_IIR1_4_SHIFT, 0x00000007 }, + { AUD_IIR1_5_SHIFT, 0x00000000 }, + { AUD_IIR2_0_SEL, 0x00000002 }, + { AUD_IIR2_1_SEL, 0x00000003 }, + { AUD_IIR2_2_SEL, 0x00000004 }, + { AUD_IIR2_3_SEL, 0x00000005 }, + { AUD_IIR3_0_SEL, 0x00000021 }, + { AUD_IIR3_1_SEL, 0x00000023 }, + { AUD_IIR3_2_SEL, 0x00000016 }, + { AUD_IIR3_0_SHIFT, 0x00000000 }, + { AUD_IIR3_1_SHIFT, 0x00000000 }, + { AUD_IIR3_2_SHIFT, 0x00000000 }, + { AUD_IIR4_0_SEL, 0x0000001d }, + { AUD_IIR4_1_SEL, 0x00000019 }, + { AUD_IIR4_2_SEL, 0x00000008 }, + { AUD_IIR4_0_SHIFT, 0x00000000 }, + { AUD_IIR4_1_SHIFT, 0x00000000 }, + { AUD_IIR4_2_SHIFT, 0x00000001 }, + { AUD_IIR4_0_CA0, 0x0003e57e }, + { AUD_IIR4_0_CA1, 0x00005e11 }, + { AUD_IIR4_0_CA2, 0x0003a7cf }, + { AUD_IIR4_0_CB0, 0x00002368 }, + { AUD_IIR4_0_CB1, 0x0003bf1b }, + { AUD_IIR4_1_CA0, 0x00006349 }, + { AUD_IIR4_1_CA1, 0x00006f27 }, + { AUD_IIR4_1_CA2, 0x0000e7a3 }, + { AUD_IIR4_1_CB0, 0x00005653 }, + { AUD_IIR4_1_CB1, 0x0000cf97 }, + { AUD_IIR4_2_CA0, 0x00006349 }, + { AUD_IIR4_2_CA1, 0x00006f27 }, + { AUD_IIR4_2_CA2, 0x0000e7a3 }, + { AUD_IIR4_2_CB0, 0x00005653 }, + { AUD_IIR4_2_CB1, 0x0000cf97 }, + { AUD_HP_MD_IIR4_1, 0x00000001 }, + { AUD_HP_PROG_IIR4_1, 0x00000017 }, + { AUD_DN1_FREQ, 0x00003618 }, + { AUD_DN1_SRC_SEL, 0x00000017 }, + { AUD_DN1_SHFT, 0x00000007 }, + { AUD_DN1_AFC, 0x00000000 }, + { AUD_DN1_FREQ_SHIFT, 0x00000000 }, + { AUD_DN2_SRC_SEL, 0x00000040 }, + { AUD_DN2_SHFT, 0x00000000 }, + { AUD_DN2_AFC, 0x00000002 }, + { AUD_DN2_FREQ, 0x0000caaf }, + { AUD_DN2_FREQ_SHIFT, 0x00000000 }, + { AUD_PDET_SRC, 0x00000014 }, + { AUD_PDET_SHIFT, 0x00000000 }, + { AUD_DEEMPH0_SRC_SEL, 0x00000011 }, + { AUD_DEEMPH1_SRC_SEL, 0x00000013 }, + { AUD_DEEMPH0_SHIFT, 0x00000000 }, + { AUD_DEEMPH1_SHIFT, 0x00000000 }, + { AUD_DEEMPH0_G0, 0x000004da }, + { AUD_DEEMPH0_A0, 0x0000777a }, + { AUD_DEEMPH0_B0, 0x00000000 }, + { AUD_DEEMPH0_A1, 0x0003f062 }, + { AUD_DEEMPH0_B1, 0x00000000 }, + { AUD_DEEMPH1_G0, 0x000004da }, + { AUD_DEEMPH1_A0, 0x0000777a }, + { AUD_DEEMPH1_B0, 0x00000000 }, + { AUD_DEEMPH1_A1, 0x0003f062 }, + { AUD_DEEMPH1_B1, 0x00000000 }, + { AUD_PLL_EN, 0x00000000 }, + { AUD_DMD_RA_DDS, 0x002a4efb }, + { AUD_RATE_ADJ1, 0x00001000 }, + { AUD_RATE_ADJ2, 0x00002000 }, + { AUD_RATE_ADJ3, 0x00003000 }, + { AUD_RATE_ADJ4, 0x00004000 }, + { AUD_RATE_ADJ5, 0x00005000 }, + { AUD_C2_UP_THR, 0x0000ffff }, + { AUD_C2_LO_THR, 0x0000e800 }, + { AUD_C1_UP_THR, 0x00008c00 }, + { AUD_C1_LO_THR, 0x00006c00 }, + + // ; Completely ditch AFC feedback + { AUD_DCOC_0_SRC, 0x00000021 }, + { AUD_DCOC_1_SRC, 0x0000001a }, + { AUD_DCOC1_SHIFT, 0x00000000 }, + { AUD_DCOC_1_SHIFT_IN0, 0x0000000a }, + { AUD_DCOC_1_SHIFT_IN1, 0x00000008 }, + { AUD_DCOC_PASS_IN, 0x00000000 }, + { AUD_IIR4_0_SEL, 0x00000023 }, + + // ; Completely ditc FM-2 AFC feedback + { AUD_DN1_AFC, 0x00000000 }, + { AUD_DCOC_2_SRC, 0x0000001b }, + { AUD_IIR4_1_SEL, 0x00000025 }, + + // ; WARNING!!! THIS CHANGE WAS NOT EXPECTED!!! + // ; Swap I & Q inputs into second rotator + // ; to reverse frequency and therefor invert + // ; phase from the cordic FM demodulator + // ; (frequency rotation must also be reversed + { AUD_DN2_SRC_SEL, 0x00000001 }, + { AUD_DN2_FREQ, 0x00003551 }, + + + // setup Audio PLL + { AUD_PLL_PRESCALE, 0x00000002 }, + { AUD_PLL_INT, 0x0000001f }, + + // de-assert Audio soft reset + { AUD_SOFT_RESET, 0x00000000 }, + + { /* end of list */ }, + }; + + dprintk("set_audio_standard_A2()\n"); + set_audio_registers(dev, a2); +} + +static void set_audio_standard_EIAJ(struct cx8800_dev *dev) +{ + dprintk("set_audio_standard_EIAJ() [TODO]\n"); +} + +static void set_audio_standard_FM(struct cx8800_dev *dev) +{ + dprintk("set_audio_standard_FM\n"); + + // initialize FM Radio + cx_write(AUD_INIT,0x0020); + cx_write(AUD_INIT_LD,0x0001); + cx_write(AUD_SOFT_RESET,0x0001); + +#if 0 /* FIXME */ + switch (dev->audio_properties.FM_deemphasis) + { + case WW_FM_DEEMPH_50: + //Set De-emphasis filter coefficients for 50 usec + cx_write(AUD_DEEMPH0_G0, 0x0C45); + cx_write(AUD_DEEMPH0_A0, 0x6262); + cx_write(AUD_DEEMPH0_B0, 0x1C29); + cx_write(AUD_DEEMPH0_A1, 0x3FC66); + cx_write(AUD_DEEMPH0_B1, 0x399A); + + cx_write(AUD_DEEMPH1_G0, 0x0D80); + cx_write(AUD_DEEMPH1_A0, 0x6262); + cx_write(AUD_DEEMPH1_B0, 0x1C29); + cx_write(AUD_DEEMPH1_A1, 0x3FC66); + cx_write(AUD_DEEMPH1_B1, 0x399A); + + break; + + case WW_FM_DEEMPH_75: + //Set De-emphasis filter coefficients for 75 usec + cx_write(AUD_DEEMPH0_G0, 0x91B ); + cx_write(AUD_DEEMPH0_A0, 0x6B68); + cx_write(AUD_DEEMPH0_B0, 0x11EC); + cx_write(AUD_DEEMPH0_A1, 0x3FC66); + cx_write(AUD_DEEMPH0_B1, 0x399A); + + cx_write(AUD_DEEMPH1_G0, 0xAA0 ); + cx_write(AUD_DEEMPH1_A0, 0x6B68); + cx_write(AUD_DEEMPH1_B0, 0x11EC); + cx_write(AUD_DEEMPH1_A1, 0x3FC66); + cx_write(AUD_DEEMPH1_B1, 0x399A); + + break; + } +#endif + + // de-assert Audio soft reset + cx_write(AUD_SOFT_RESET,0x0000); + + // AB: 10/2/01: this register is not being reset appropriately on occasion. + cx_write(AUD_POLYPH80SCALEFAC,3); +} + +/* ----------------------------------------------------------- */ + +void cx88_set_tvaudio(struct cx8800_dev *dev) +{ + cx_write(AUD_CTL, 0x00); + + switch (dev->tvaudio) { + case WW_BTSC: + set_audio_standard_BTSC(dev,0); + break; + case WW_NICAM_I: + case WW_NICAM_BGDKL: + set_audio_standard_NICAM(dev); + break; + case WW_A2_BG: + case WW_A2_DK: + case WW_A2_M: + set_audio_standard_A2(dev); + break; + case WW_EIAJ: + set_audio_standard_EIAJ(dev); + break; + case WW_FM: + set_audio_standard_FM(dev); + break; + case WW_NONE: + default: + printk("%s: unknown tv audio mode [%d]\n", + dev->name, dev->tvaudio); + break; + } + + // unmute + cx_set(AUD_CTL, EN_DAC_ENABLE); + cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, 0x00); + return; +} + +void cx88_get_stereo(struct cx8800_dev *dev, struct v4l2_tuner *t) +{ + static char *m[] = {"stereo", "dual mono", "mono", "sap"}; + static char *p[] = {"no pilot", "pilot c1", "pilot c2", "?"}; + u32 reg,mode,pilot; + + reg = cx_read(AUD_STATUS); + mode = reg & 0x03; + pilot = (reg >> 2) & 0x03; + dprintk("AUD_STATUS: %s / %s [status=0x%x,ctl=0x%x,vol=0x%x]\n", + m[mode], p[pilot], reg, + cx_read(AUD_CTL), cx_sread(SHADOW_AUD_VOL_CTL)); + + t->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_SAP | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; + t->rxsubchans = V4L2_TUNER_SUB_MONO; + t->audmode = V4L2_TUNER_MODE_MONO; + + switch (dev->tvaudio) { + case WW_A2_BG: + if (1 == pilot) { + /* stereo */ + t->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + if (0 == mode) + t->audmode = V4L2_TUNER_MODE_STEREO; + } + if (2 == pilot) { + /* dual language -- FIXME */ + t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + t->audmode = V4L2_TUNER_MODE_LANG1; + } + break; + case WW_NICAM_BGDKL: + if (0 == mode) + t->audmode = V4L2_TUNER_MODE_STEREO; + break; + default: + t->rxsubchans = V4L2_TUNER_SUB_MONO; + t->audmode = V4L2_TUNER_MODE_MONO; + break; + } + return; +} + +void cx88_set_stereo(struct cx8800_dev *dev, u32 mode) +{ + u32 ctl = UNSET; + u32 mask = UNSET; + + switch (dev->tvaudio) { + case WW_A2_BG: + switch (mode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: + ctl = EN_A2_FORCE_MONO1; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_LANG2: + ctl = EN_A2_AUTO_MONO2; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_STEREO: + ctl = EN_A2_AUTO_STEREO | EN_DMTRX_SUMR; + mask = 0x8bf; + break; + } + break; + case WW_NICAM_BGDKL: + switch (mode) { + case V4L2_TUNER_MODE_MONO: + ctl = EN_NICAM_FORCE_MONO1; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_LANG1: + ctl = EN_NICAM_AUTO_MONO2; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_STEREO: + ctl = EN_NICAM_FORCE_STEREO | EN_DMTRX_LR; + mask = 0x93f; + break; + } + break; + case WW_FM: + switch (mode) { + case V4L2_TUNER_MODE_MONO: + ctl = EN_FMRADIO_FORCE_MONO; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_STEREO: + ctl = EN_FMRADIO_AUTO_STEREO; + mask = 0x3f; + break; + } + break; + } + + if (UNSET != ctl) { + cx_write(AUD_SOFT_RESET, 0x0001); + cx_andor(AUD_CTL, mask, ctl); + cx_write(AUD_SOFT_RESET, 0x0000); + dprintk("cx88_set_stereo: mask 0x%x, ctl 0x%x " + "[status=0x%x,ctl=0x%x,vol=0x%x]\n", + mask, ctl, cx_read(AUD_STATUS), + cx_read(AUD_CTL), cx_sread(SHADOW_AUD_VOL_CTL)); + } + return; +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/cx88/cx88-vbi.c b/linux/drivers/media/video/cx88/cx88-vbi.c new file mode 100644 index 000000000..f6806b828 --- /dev/null +++ b/linux/drivers/media/video/cx88/cx88-vbi.c @@ -0,0 +1,227 @@ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include "cx88.h" + +static unsigned int vbibufs = 4; +MODULE_PARM(vbibufs,"i"); +MODULE_PARM_DESC(vbibufs,"number of vbi buffers, range 2-32"); + +static unsigned int vbi_debug = 0; +MODULE_PARM(vbi_debug,"i"); +MODULE_PARM_DESC(vbi_debug,"enable debug messages [video]"); + +#define dprintk(level,fmt, arg...) if (vbi_debug >= level) \ + printk(KERN_DEBUG "%s: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ + +void cx8800_vbi_fmt(struct cx8800_dev *dev, struct v4l2_format *f) +{ + memset(&f->fmt.vbi,0,sizeof(f->fmt.vbi)); + + f->fmt.vbi.samples_per_line = VBI_LINE_LENGTH; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 244; + f->fmt.vbi.count[0] = VBI_LINE_COUNT; + f->fmt.vbi.count[1] = VBI_LINE_COUNT; + + switch (dev->tvnorm->id) { + case V4L2_STD_NTSC_M: + case V4L2_STD_NTSC_M_JP: + f->fmt.vbi.sampling_rate = 28636363; + f->fmt.vbi.start[0] = 10; + f->fmt.vbi.start[1] = 273; + break; + case V4L2_STD_PAL_BG: + case V4L2_STD_PAL_DK: + case V4L2_STD_PAL_I: + case V4L2_STD_SECAM: + f->fmt.vbi.sampling_rate = 35468950; + f->fmt.vbi.start[0] = 7; + f->fmt.vbi.start[1] = 319; + } +} + +int cx8800_start_vbi_dma(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf) +{ + /* setup fifo + format */ + cx88_sram_channel_setup(dev, &cx88_sram_channels[SRAM_CH24], + buf->vb.width, buf->risc.dma); + + cx_write(MO_VBOS_CONTROL, ( (1 << 18) | // comb filter delay fixup + (1 << 15) | // enable vbi capture + (1 << 11) )); + + /* reset counter */ + cx_write(MO_VBI_GPCNTRL,0x3); + q->count = 1; + + /* enable irqs */ + cx_set(MO_PCI_INTMSK, 0x00fc01); + cx_set(MO_VID_INTMSK, 0x0f0088); + + /* enable capture */ + cx_set(VID_CAPTURE_CONTROL,0x18); + + /* start dma */ + cx_set(MO_DEV_CNTRL2, (1<<5)); + cx_set(MO_VID_DMACNTRL, 0x88); + + return 0; +} + +int cx8800_restart_vbi_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q) +{ + struct cx88_buffer *buf; + struct list_head *item; + + if (list_empty(&q->active)) + return 0; + + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + dprintk(2,"restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + cx8800_start_vbi_dma(dev, q, buf); + list_for_each(item,&q->active) { + buf = list_entry(item, struct cx88_buffer, vb.queue); + buf->count = q->count++; + } + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +void cx8800_vbi_timeout(unsigned long data) +{ + struct cx8800_dev *dev = (struct cx8800_dev*)data; + struct cx88_dmaqueue *q = &dev->vbiq; + struct cx88_buffer *buf; + unsigned long flags; + + cx88_sram_channel_dump(dev, &cx88_sram_channels[SRAM_CH24]); + + cx_clear(MO_VID_DMACNTRL, 0x88); + cx_clear(VID_CAPTURE_CONTROL, 0x18); + + spin_lock_irqsave(&dev->slock,flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = STATE_ERROR; + wake_up(&buf->vb.done); + printk("%s: [%p/%d] timeout - dma=0x%08lx\n", dev->name, + buf, buf->vb.i, (unsigned long)buf->risc.dma); + } + cx8800_restart_vbi_queue(dev,q); + spin_unlock_irqrestore(&dev->slock,flags); +} + +/* ------------------------------------------------------------------ */ + +static int +vbi_setup(struct file *file, unsigned int *count, unsigned int *size) +{ + *size = VBI_LINE_COUNT * VBI_LINE_LENGTH * 2; + if (0 == *count) + *count = vbibufs; + if (*count < 2) + *count = 2; + if (*count > 32) + *count = 32; + return 0; +} + +static int +vbi_prepare(struct file *file, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_buffer *buf = (struct cx88_buffer*)vb; + unsigned int size; + int rc; + + size = VBI_LINE_COUNT * VBI_LINE_LENGTH * 2; + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + if (STATE_NEEDS_INIT == buf->vb.state) { + buf->vb.width = VBI_LINE_LENGTH; + buf->vb.height = VBI_LINE_COUNT; + buf->vb.size = size; + buf->vb.field = V4L2_FIELD_SEQ_TB; + + if (0 != (rc = videobuf_iolock(dev->pci,&buf->vb,NULL))) + goto fail; + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, + 0, buf->vb.width * buf->vb.height, + buf->vb.width, 0, + buf->vb.height); + } + buf->vb.state = STATE_PREPARED; + return 0; + + fail: + cx88_free_buffer(dev->pci,buf); + return rc; +} + +static void +vbi_queue(struct file *file, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = (struct cx88_buffer*)vb; + struct cx88_buffer *prev; + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_dmaqueue *q = &dev->vbiq; + + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | 0x10000); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + + if (list_empty(&q->active)) { + list_add_tail(&buf->vb.queue,&q->active); + cx8800_start_vbi_dma(dev, q, buf); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] vbi_queue - first active\n", + buf, buf->vb.i); + + } else { + prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue); + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.i); + } +} + +static void vbi_release(struct file *file, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = (struct cx88_buffer*)vb; + struct cx8800_fh *fh = file->private_data; + + cx88_free_buffer(fh->dev->pci,buf); +} + +struct videobuf_queue_ops cx8800_vbi_qops = { + .buf_setup = vbi_setup, + .buf_prepare = vbi_prepare, + .buf_queue = vbi_queue, + .buf_release = vbi_release, +}; + +/* ------------------------------------------------------------------ */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/cx88/cx88-video.c b/linux/drivers/media/video/cx88/cx88-video.c new file mode 100644 index 000000000..e831b1c0d --- /dev/null +++ b/linux/drivers/media/video/cx88/cx88-video.c @@ -0,0 +1,2550 @@ +/* + * device driver for Conexant 2388x based TV cards + * video4linux video interface + * + * (c) 2003 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define __NO_VERSION__ 1 + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kmod.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <asm/div64.h> + +#include "cx88.h" + +MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------------ */ + +static unsigned int video_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(video_nr,"1-" __stringify(CX88_MAXBOARDS) "i"); +MODULE_PARM_DESC(video_nr,"video device numbers"); + +static unsigned int vbi_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(vbi_nr,"1-" __stringify(CX88_MAXBOARDS) "i"); +MODULE_PARM_DESC(vbi_nr,"vbi device numbers"); + +static unsigned int radio_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(radio_nr,"1-" __stringify(CX88_MAXBOARDS) "i"); +MODULE_PARM_DESC(radio_nr,"radio device numbers"); + +static unsigned int latency = UNSET; +MODULE_PARM(latency,"i"); +MODULE_PARM_DESC(latency,"pci latency timer"); + +static unsigned int video_debug = 0; +MODULE_PARM(video_debug,"i"); +MODULE_PARM_DESC(video_debug,"enable debug messages [video]"); + +static unsigned int irq_debug = 0; +MODULE_PARM(irq_debug,"i"); +MODULE_PARM_DESC(irq_debug,"enable debug messages [IRQ handler]"); + +static unsigned int vid_limit = 16; +MODULE_PARM(vid_limit,"i"); +MODULE_PARM_DESC(vid_limit,"capture memory limit in megabytes"); + +static unsigned int tuner[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(tuner,"1-" __stringify(CX88_MAXBOARDS) "i"); +MODULE_PARM_DESC(tuner,"tuner type"); + +static unsigned int card[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(card,"1-" __stringify(CX88_MAXBOARDS) "i"); +MODULE_PARM_DESC(card,"card type"); + +static unsigned int nicam = 0; +MODULE_PARM(nicam,"i"); +MODULE_PARM_DESC(nicam,"tv audio is nicam"); + +#define dprintk(level,fmt, arg...) if (video_debug >= level) \ + printk(KERN_DEBUG "%s: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ + +static struct list_head cx8800_devlist; +static unsigned int cx8800_devcount; + +/* ------------------------------------------------------------------- */ +/* static data */ + +static unsigned int inline norm_swidth(struct cx8800_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 922 : 754; +} + +static unsigned int inline norm_hdelay(struct cx8800_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 186 : 135; +} + +static unsigned int inline norm_vdelay(struct cx8800_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 0x24 : 0x18; +} + +static unsigned int inline norm_maxw(struct cx8800_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 768 : 640; +// return (norm->id & V4L2_STD_625_50) ? 720 : 640; +} + +static unsigned int inline norm_maxh(struct cx8800_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 576 : 480; +} + +static unsigned int inline norm_fsc8(struct cx8800_tvnorm *norm) +{ + static const unsigned int ntsc = 28636360; + static const unsigned int pal = 35468950; + + return (norm->id & V4L2_STD_625_50) ? pal : ntsc; +} + +static unsigned int inline norm_notchfilter(struct cx8800_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) + ? HLNotchFilter135PAL + : HLNotchFilter135NTSC; +} + +static unsigned int inline norm_htotal(struct cx8800_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 1135 : 910; +} + +static unsigned int inline norm_vbipack(struct cx8800_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 255 : 144; +} + +static struct cx8800_tvnorm tvnorms[] = { + { + .name = "NTSC-M", + .id = V4L2_STD_NTSC_M, + .cxiformat = VideoFormatNTSC, + .cxoformat = 0x181f0008, + },{ + .name = "NTSC-JP", + .id = V4L2_STD_NTSC_M_JP, + .cxiformat = VideoFormatNTSCJapan, + .cxoformat = 0x181f0008, +#if 0 + },{ + .name = "NTSC-4.43", + .id = FIXME, + .cxiformat = VideoFormatNTSC443, + .cxoformat = 0x181f0008, +#endif + },{ + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + .cxiformat = VideoFormatPAL, + .cxoformat = 0x181f0008, + },{ + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + .cxiformat = VideoFormatPAL, + .cxoformat = 0x181f0008, + },{ + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + .cxiformat = VideoFormatPAL, + .cxoformat = 0x181f0008, + },{ + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + .cxiformat = VideoFormatPALM, + .cxoformat = 0x1c1f0008, + },{ + .name = "PAL-N", + .id = V4L2_STD_PAL_N, + .cxiformat = VideoFormatPALN, + .cxoformat = 0x1c1f0008, + },{ + .name = "PAL-Nc", + .id = V4L2_STD_PAL_Nc, + .cxiformat = VideoFormatPALNC, + .cxoformat = 0x1c1f0008, + },{ + .name = "PAL-60", + .id = V4L2_STD_PAL_60, + .cxiformat = VideoFormatPAL60, + .cxoformat = 0x181f0008, + },{ + .name = "SECAM", + .id = V4L2_STD_SECAM, + .cxiformat = VideoFormatSECAM, + .cxoformat = 0x181f0008, + } +}; + +static struct cx8800_fmt formats[] = { + { + .name = "8 bpp, gray", + .fourcc = V4L2_PIX_FMT_GREY, + .cxformat = ColorFormatY8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "15 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB555, + .cxformat = ColorFormatRGB15, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "15 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB555X, + .cxformat = ColorFormatRGB15 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB565, + .cxformat = ColorFormatRGB16, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB565X, + .cxformat = ColorFormatRGB16 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "24 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR24, + .cxformat = ColorFormatRGB24, + .depth = 24, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .cxformat = ColorFormatRGB32, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB32, + .cxformat = ColorFormatRGB32 | ColorFormatBSWAP | ColorFormatWSWAP, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .cxformat = ColorFormatYUY2, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .cxformat = ColorFormatYUY2 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, +}; + +static struct cx8800_fmt* format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) + if (formats[i].fourcc == fourcc) + return formats+i; + return NULL; +} + +/* ------------------------------------------------------------------- */ + +static const struct v4l2_queryctrl no_ctl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; + +static struct cx88_ctrl cx8800_ctls[] = { + /* --- video --- */ + { + .v = { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = 0x00, + .maximum = 0xff, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .off = 128, + .reg = MO_CONTR_BRIGHT, + .mask = 0x00ff, + .shift = 0, + },{ + .v = { + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .reg = MO_CONTR_BRIGHT, + .mask = 0xff00, + .shift = 8, + },{ + .v = { + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .off = 0, + .reg = MO_HUE, + .mask = 0x00ff, + .shift = 0, + },{ + /* strictly, this only describes only U saturation. + * V saturation is handled specially through code. + */ + .v = { + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .off = 0, + .reg = MO_UV_SATURATION, + .mask = 0x00ff, + .shift = 0, + },{ + /* --- audio --- */ + .v = { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, + .reg = AUD_VOL_CTL, + .sreg = SHADOW_AUD_VOL_CTL, + .mask = (1 << 6), + .shift = 6, + },{ + .v = { + .id = V4L2_CID_AUDIO_VOLUME, + .name = "Volume", + .minimum = 0, + .maximum = 0x3f, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .reg = AUD_VOL_CTL, + .sreg = SHADOW_AUD_VOL_CTL, + .mask = 0x3f, + .shift = 0, + },{ + .v = { + .id = V4L2_CID_AUDIO_BALANCE, + .name = "Balance", + .minimum = 0, + .maximum = 0x7f, + .step = 1, + .default_value = 0x40, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .reg = AUD_BAL_CTL, + .sreg = SHADOW_AUD_BAL_CTL, + .mask = 0x7f, + .shift = 0, + } +}; +const int CX8800_CTLS = ARRAY_SIZE(cx8800_ctls); + +/* ------------------------------------------------------------------- */ +/* resource management */ + +static int res_get(struct cx8800_dev *dev, struct cx8800_fh *fh, unsigned int bit) +{ + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + down(&dev->lock); + if (dev->resources & bit) { + /* no, someone else uses it */ + up(&dev->lock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + dev->resources |= bit; + dprintk(1,"res: get %d\n",bit); + up(&dev->lock); + return 1; +} + +static +int res_check(struct cx8800_fh *fh, unsigned int bit) +{ + return (fh->resources & bit); +} + +static +int res_locked(struct cx8800_dev *dev, unsigned int bit) +{ + return (dev->resources & bit); +} + +static +void res_free(struct cx8800_dev *dev, struct cx8800_fh *fh, unsigned int bits) +{ + if ((fh->resources & bits) != bits) + BUG(); + + down(&dev->lock); + fh->resources &= ~bits; + dev->resources &= ~bits; + dprintk(1,"res: put %d\n",bits); + up(&dev->lock); +} + +/* ------------------------------------------------------------------ */ + +static const u32 xtal = 28636363; + +static int set_pll(struct cx8800_dev *dev, int prescale, u32 ofreq) +{ + static u32 pre[] = { 0, 0, 0, 3, 2, 1 }; + u64 pll; + u32 reg; + int i; + + if (prescale < 2) + prescale = 2; + if (prescale > 5) + prescale = 5; + + pll = ofreq * 8 * prescale * (u64)(1 << 20); + do_div(pll,xtal); + reg = (pll & 0x3ffffff) | (pre[prescale] << 26); + if (((reg >> 20) & 0x3f) < 14) { + printk("%s: pll out of range\n",dev->name); + return -1; + } + + dprintk(1,"set_pll: MO_PLL_REG 0x%08x [old=0x%08x,freq=%d]\n", + reg, cx_read(MO_PLL_REG), ofreq); + cx_write(MO_PLL_REG, reg); + for (i = 0; i < 10; i++) { + reg = cx_read(MO_DEVICE_STATUS); + if (reg & (1<<2)) { + dprintk(1,"pll locked [pre=%d,ofreq=%d]\n", + prescale,ofreq); + return 0; + } + dprintk(1,"pll not locked yet, waiting ...\n"); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + } + dprintk(1,"pll NOT locked [pre=%d,ofreq=%d]\n",prescale,ofreq); + return -1; +} + +static int set_tvaudio(struct cx8800_dev *dev) +{ + if (CX88_VMUX_TELEVISION != INPUT(dev->input)->type) + return 0; + + switch (dev->tvnorm->id) { + case V4L2_STD_PAL_BG: + dev->tvaudio = nicam ? WW_NICAM_BGDKL : WW_A2_BG; + break; + case V4L2_STD_PAL_DK: + dev->tvaudio = nicam ? WW_NICAM_BGDKL : WW_A2_DK; + break; + case V4L2_STD_PAL_I: + dev->tvaudio = WW_NICAM_I; + break; + case V4L2_STD_SECAM: + dev->tvaudio = WW_SYSTEM_L_AM; /* FIXME: fr != ru */ + break; + case V4L2_STD_NTSC_M: + dev->tvaudio = WW_BTSC; + break; + case V4L2_STD_NTSC_M_JP: + dev->tvaudio = WW_EIAJ; + break; + default: + dprintk(1,"tvaudio support needs work for this tv norm [%s], sorry\n", + dev->tvnorm->name); + dev->tvaudio = 0; + return 0; + } + + cx_andor(MO_AFECFG_IO, 0x1f, 0x0); + cx88_set_tvaudio(dev); + cx88_set_stereo(dev,V4L2_TUNER_MODE_STEREO); + + cx_write(MO_AUDD_LNGTH, 128/8); /* fifo size */ + cx_write(MO_AUDR_LNGTH, 128/8); /* fifo size */ + cx_write(MO_AUD_DMACNTRL, 0x03); /* need audio fifo */ + return 0; +} + +static int set_tvnorm(struct cx8800_dev *dev, struct cx8800_tvnorm *norm) +{ + u32 fsc8; + u32 adc_clock; + u32 vdec_clock; + u64 tmp64; + u32 bdelay,agcdelay,htotal; + struct video_channel c; + + dev->tvnorm = norm; + fsc8 = norm_fsc8(norm); + adc_clock = xtal; + vdec_clock = fsc8; + + dprintk(1,"set_tvnorm: \"%s\" fsc8=%d adc=%d vdec=%d\n", + norm->name, fsc8, adc_clock, vdec_clock); + set_pll(dev,2,vdec_clock); + + dprintk(1,"set_tvnorm: MO_INPUT_FORMAT 0x%08x [old=0x%08x]\n", + norm->cxiformat, cx_read(MO_INPUT_FORMAT) & 0x0f); + cx_andor(MO_INPUT_FORMAT, 0xf, norm->cxiformat); + +#if 1 + // FIXME: as-is from DScaler + dprintk(1,"set_tvnorm: MO_OUTPUT_FORMAT 0x%08x [old=0x%08x]\n", + norm->cxoformat, cx_read(MO_OUTPUT_FORMAT)); + cx_write(MO_OUTPUT_FORMAT, norm->cxoformat); +#endif + + // MO_SCONV_REG = adc clock / video dec clock * 2^17 + tmp64 = adc_clock * (u64)(1 << 17); + do_div(tmp64, vdec_clock); + dprintk(1,"set_tvnorm: MO_SCONV_REG 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SCONV_REG)); + cx_write(MO_SCONV_REG, (u32)tmp64); + + // MO_SUB_STEP = 8 * fsc / video dec clock * 2^22 + tmp64 = fsc8 * (u64)(1 << 22); + do_div(tmp64, vdec_clock); + dprintk(1,"set_tvnorm: MO_SUB_STEP 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SUB_STEP)); + cx_write(MO_SUB_STEP, (u32)tmp64); + + // MO_SUB_STEP_DR = 8 * 4406250 / video dec clock * 2^22 + tmp64 = 4406250 * 8 * (u64)(1 << 22); + do_div(tmp64, vdec_clock); + dprintk(1,"set_tvnorm: MO_SUB_STEP_DR 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SUB_STEP_DR)); + cx_write(MO_SUB_STEP_DR, (u32)tmp64); + + // bdelay + agcdelay + bdelay = vdec_clock * 65 / 20000000 + 21; + agcdelay = vdec_clock * 68 / 20000000 + 15; + dprintk(1,"set_tvnorm: MO_AGC_BURST 0x%08x [old=0x%08x,bdelay=%d,agcdelay=%d]\n", + (bdelay << 8) | agcdelay, cx_read(MO_AGC_BURST), bdelay, agcdelay); + cx_write(MO_AGC_BURST, (bdelay << 8) | agcdelay); + + // htotal + tmp64 = norm_htotal(norm) * (u64)vdec_clock; + do_div(tmp64, fsc8); + htotal = (u32)tmp64 | (norm_notchfilter(norm) << 11); + dprintk(1,"set_tvnorm: MO_HTOTAL 0x%08x [old=0x%08x,htotal=%d]\n", + htotal, cx_read(MO_HTOTAL), (u32)tmp64); + cx_write(MO_HTOTAL, htotal); + + // vbi stuff + cx_write(MO_VBI_PACKET, ( (1 << 11) | /* (norm_vdelay(norm) << 11) | */ + (norm_vbipack(norm) << 0) )); + + // audio + set_tvaudio(dev); + + // tell i2c chips + memset(&c,0,sizeof(c)); + c.channel = dev->input; + c.norm = VIDEO_MODE_PAL; + if ((norm->id & (V4L2_STD_NTSC_M|V4L2_STD_NTSC_M_JP))) + c.norm = VIDEO_MODE_NTSC; + if (norm->id & V4L2_STD_SECAM) + c.norm = VIDEO_MODE_SECAM; + cx8800_call_i2c_clients(dev,VIDIOCSCHAN,&c); + + // done + return 0; +} + +static int set_scale(struct cx8800_dev *dev, unsigned int width, unsigned int height, + int interlaced) +{ + unsigned int swidth = norm_swidth(dev->tvnorm); + unsigned int sheight = norm_maxh(dev->tvnorm); + u32 value; + + dprintk(1,"set_scale: %dx%d [%s]\n", width, height, dev->tvnorm->name); + + // recalc H delay and scale registers + value = (width * norm_hdelay(dev->tvnorm)) / swidth; + cx_write(MO_HDELAY_EVEN, value); + cx_write(MO_HDELAY_ODD, value); + dprintk(1,"set_scale: hdelay 0x%04x\n", value); + + value = (swidth * 4096 / width) - 4096; + cx_write(MO_HSCALE_EVEN, value); + cx_write(MO_HSCALE_ODD, value); + dprintk(1,"set_scale: hscale 0x%04x\n", value); + + cx_write(MO_HACTIVE_EVEN, width); + cx_write(MO_HACTIVE_ODD, width); + dprintk(1,"set_scale: hactive 0x%04x\n", width); + + // recalc V scale Register (delay is constant) + cx_write(MO_VDELAY_EVEN, norm_vdelay(dev->tvnorm)); + cx_write(MO_VDELAY_ODD, norm_vdelay(dev->tvnorm)); + dprintk(1,"set_scale: vdelay 0x%04x\n", norm_vdelay(dev->tvnorm)); + + value = (0x10000 - (sheight * 512 / height - 512)) & 0x1fff; + cx_write(MO_VSCALE_EVEN, value); + cx_write(MO_VSCALE_ODD, value); + dprintk(1,"set_scale: vscale 0x%04x\n", value); + + cx_write(MO_VACTIVE_EVEN, sheight); + cx_write(MO_VACTIVE_ODD, sheight); + dprintk(1,"set_scale: vactive 0x%04x\n", sheight); + + // setup filters + value = 0; + value |= (1 << 19); // CFILT (default) + if (interlaced) + value |= (1 << 3); // VINT (interlaced vertical scaling) + if (width < 385) + value |= (1 << 0); // 3-tap interpolation + if (width < 193) + value |= (1 << 1); // 5-tap interpolation + + cx_write(MO_FILTER_EVEN, value); + cx_write(MO_FILTER_ODD, value); + dprintk(1,"set_scale: filter 0x%04x\n", value); + + return 0; +} + +static int video_mux(struct cx8800_dev *dev, unsigned int input) +{ + dprintk(1,"video_mux: %d [vmux=%d,gpio=0x%x]\n", + input, INPUT(input)->vmux, INPUT(input)->gpio0); + dev->input = input; + cx_andor(MO_INPUT_FORMAT, 0x03 << 14, INPUT(input)->vmux << 14); + cx_write(MO_GP0_IO, INPUT(input)->gpio0); + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int start_video_dma(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf) +{ + /* setup fifo + format */ + cx88_sram_channel_setup(dev, &cx88_sram_channels[SRAM_CH21], + buf->bpl, buf->risc.dma); + set_scale(dev, buf->vb.width, buf->vb.height, 1); + cx_write(MO_COLOR_CTRL, buf->fmt->cxformat | ColorFormatGamma); + + /* reset counter */ + cx_write(MO_VIDY_GPCNTRL,0x3); + q->count = 1; + + /* enable irqs */ + cx_set(MO_PCI_INTMSK, 0x00fc01); + cx_set(MO_VID_INTMSK, 0x0f0011); + + /* enable capture */ + cx_set(VID_CAPTURE_CONTROL,0x06); + + /* start dma */ + cx_set(MO_DEV_CNTRL2, (1<<5)); + cx_set(MO_VID_DMACNTRL, 0x11); + + return 0; +} + +static int restart_video_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q) +{ + struct cx88_buffer *buf, *prev; + struct list_head *item; + + if (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + dprintk(2,"restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + start_video_dma(dev, q, buf); + list_for_each(item,&q->active) { + buf = list_entry(item, struct cx88_buffer, vb.queue); + buf->count = q->count++; + } + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + return 0; + } + + prev = NULL; + for (;;) { + if (list_empty(&q->queued)) + return 0; + buf = list_entry(q->queued.next, struct cx88_buffer, vb.queue); + if (NULL == prev) { + list_del(&buf->vb.queue); + list_add_tail(&buf->vb.queue,&q->active); + start_video_dma(dev, q, buf); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] restart_queue - first active\n", + buf,buf->vb.i); + + } else if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_del(&buf->vb.queue); + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] restart_queue - move to active\n", + buf,buf->vb.i); + } else { + return 0; + } + prev = buf; + } +} + +/* ------------------------------------------------------------------ */ + +static int +buffer_setup(struct file *file, unsigned int *count, unsigned int *size) +{ + struct cx8800_fh *fh = file->private_data; + + *size = fh->fmt->depth*fh->width*fh->height >> 3; + if (0 == *count) + *count = 32; + while (*size * *count > vid_limit * 1024 * 1024) + (*count)--; + return 0; +} + +static int +buffer_prepare(struct file *file, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_buffer *buf = (struct cx88_buffer*)vb; + int rc, init_buffer = 0; + + BUG_ON(NULL == fh->fmt); + if (fh->width < 48 || fh->width > norm_maxw(dev->tvnorm) || + fh->height < 32 || fh->height > norm_maxh(dev->tvnorm)) + return -EINVAL; + buf->vb.size = (fh->width * fh->height * fh->fmt->depth) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != fh->fmt || + buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.field != field) { + buf->fmt = fh->fmt; + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + init_buffer = 1; + } + + if (STATE_NEEDS_INIT == buf->vb.state) { + init_buffer = 1; + if (0 != (rc = videobuf_iolock(dev->pci,&buf->vb,NULL))) + goto fail; + } + + if (init_buffer) { + buf->bpl = buf->vb.width * buf->fmt->depth >> 3; + switch (buf->vb.field) { + case V4L2_FIELD_TOP: + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, 0, UNSET, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_BOTTOM: + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, UNSET, 0, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_INTERLACED: + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, 0, buf->bpl, + buf->bpl, buf->bpl, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, + 0, buf->bpl * (buf->vb.height >> 1), + buf->bpl, 0, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, + buf->bpl * (buf->vb.height >> 1), 0, + buf->bpl, 0, + buf->vb.height >> 1); + break; + default: + BUG(); + } + } + dprintk(2,"[%p/%d] buffer_prepare - %dx%d %dbpp \"%s\" - dma=0x%08lx\n", + buf, buf->vb.i, + fh->width, fh->height, fh->fmt->depth, fh->fmt->name, + (unsigned long)buf->risc.dma); + + buf->vb.state = STATE_PREPARED; + return 0; + + fail: + cx88_free_buffer(dev->pci,buf); + return rc; +} + +static void +buffer_queue(struct file *file, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = (struct cx88_buffer*)vb; + struct cx88_buffer *prev; + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_dmaqueue *q = &dev->vidq; + + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | 0x10000); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + + if (!list_empty(&q->queued)) { + list_add_tail(&buf->vb.queue,&q->queued); + buf->vb.state = STATE_QUEUED; + dprintk(2,"[%p/%d] buffer_queue - append to queued\n", + buf, buf->vb.i); + + } else if (list_empty(&q->active)) { + list_add_tail(&buf->vb.queue,&q->active); + start_video_dma(dev, q, buf); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] buffer_queue - first active\n", + buf, buf->vb.i); + + } else { + prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue); + if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.i); + + } else { + list_add_tail(&buf->vb.queue,&q->queued); + buf->vb.state = STATE_QUEUED; + dprintk(2,"[%p/%d] buffer_queue - first queued\n", + buf, buf->vb.i); + } + } +} + +static void buffer_release(struct file *file, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = (struct cx88_buffer*)vb; + struct cx8800_fh *fh = file->private_data; + + cx88_free_buffer(fh->dev->pci,buf); +} + +struct videobuf_queue_ops cx8800_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +#if 0 /* overlay support not finished yet */ +static u32* ov_risc_field(struct cx8800_dev *dev, struct cx8800_fh *fh, + u32 *rp, struct btcx_skiplist *skips, + u32 sync_line, int skip_even, int skip_odd) +{ + int line,maxy,start,end,skip,nskips; + u32 ri,ra; + u32 addr; + + /* sync instruction */ + *(rp++) = cpu_to_le32(RISC_RESYNC | sync_line); + + addr = (unsigned long)dev->fbuf.base; + addr += dev->fbuf.fmt.bytesperline * fh->win.w.top; + addr += (fh->fmt->depth >> 3) * fh->win.w.left; + + /* scan lines */ + for (maxy = -1, line = 0; line < fh->win.w.height; + line++, addr += dev->fbuf.fmt.bytesperline) { + if ((line%2) == 0 && skip_even) + continue; + if ((line%2) == 1 && skip_odd) + continue; + + /* calculate clipping */ + if (line > maxy) + btcx_calc_skips(line, fh->win.w.width, &maxy, + skips, &nskips, fh->clips, fh->nclips); + + /* write out risc code */ + for (start = 0, skip = 0; start < fh->win.w.width; start = end) { + if (skip >= nskips) { + ri = RISC_WRITE; + end = fh->win.w.width; + } else if (start < skips[skip].start) { + ri = RISC_WRITE; + end = skips[skip].start; + } else { + ri = RISC_SKIP; + end = skips[skip].end; + skip++; + } + if (RISC_WRITE == ri) + ra = addr + (fh->fmt->depth>>3)*start; + else + ra = 0; + + if (0 == start) + ri |= RISC_SOL; + if (fh->win.w.width == end) + ri |= RISC_EOL; + ri |= (fh->fmt->depth>>3) * (end-start); + + *(rp++)=cpu_to_le32(ri); + if (0 != ra) + *(rp++)=cpu_to_le32(ra); + } + } + kfree(skips); + return rp; +} + +static int ov_risc_frame(struct cx8800_dev *dev, struct cx8800_fh *fh, + struct cx88_buffer *buf) +{ + struct btcx_skiplist *skips; + u32 instructions,fields; + u32 *rp; + int rc; + + /* skip list for window clipping */ + if (NULL == (skips = kmalloc(sizeof(*skips) * fh->nclips,GFP_KERNEL))) + return -ENOMEM; + + fields = 0; + if (V4L2_FIELD_HAS_TOP(fh->win.field)) + fields++; + if (V4L2_FIELD_HAS_BOTTOM(fh->win.field)) + fields++; + + /* estimate risc mem: worst case is (clip+1) * lines instructions + + syncs + jump (all 2 dwords) */ + instructions = (fh->nclips+1) * fh->win.w.height; + instructions += 3 + 4; + if ((rc = btcx_riscmem_alloc(dev->pci,&buf->risc,instructions*8)) < 0) { + kfree(skips); + return rc; + } + + /* write risc instructions */ + rp = buf->risc.cpu; + switch (fh->win.field) { + case V4L2_FIELD_TOP: + rp = ov_risc_field(dev, fh, rp, skips, 0, 0, 0); + break; + case V4L2_FIELD_BOTTOM: + rp = ov_risc_field(dev, fh, rp, skips, 0x200, 0, 0); + break; + case V4L2_FIELD_INTERLACED: + rp = ov_risc_field(dev, fh, rp, skips, 0, 0, 1); + rp = ov_risc_field(dev, fh, rp, skips, 0x200, 1, 0); + break; + default: + BUG(); + } + + /* save pointer to jmp instruction address */ + buf->risc.jmp = rp; + kfree(skips); + return 0; +} + +static int verify_window(struct cx8800_dev *dev, struct v4l2_window *win) +{ + enum v4l2_field field; + int maxw, maxh; + + if (NULL == dev->fbuf.base) + return -EINVAL; + if (win->w.width < 48 || win->w.height < 32) + return -EINVAL; + if (win->clipcount > 2048) + return -EINVAL; + + field = win->field; + maxw = norm_maxw(dev->tvnorm); + maxh = norm_maxh(dev->tvnorm); + + if (V4L2_FIELD_ANY == field) { + field = (win->w.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + win->field = field; + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + return 0; +} + +static int setup_window(struct cx8800_dev *dev, struct cx8800_fh *fh, + struct v4l2_window *win) +{ + struct v4l2_clip *clips = NULL; + int n,size,retval = 0; + + if (NULL == fh->fmt) + return -EINVAL; + retval = verify_window(dev,win); + if (0 != retval) + return retval; + + /* copy clips -- luckily v4l1 + v4l2 are binary + compatible here ...*/ + n = win->clipcount; + size = sizeof(*clips)*(n+4); + clips = kmalloc(size,GFP_KERNEL); + if (NULL == clips) + return -ENOMEM; + if (n > 0) { + if (copy_from_user(clips,win->clips,sizeof(struct v4l2_clip)*n)) { + kfree(clips); + return -EFAULT; + } + } + + /* clip against screen */ + if (NULL != dev->fbuf.base) + n = btcx_screen_clips(dev->fbuf.fmt.width, dev->fbuf.fmt.height, + &win->w, clips, n); + btcx_sort_clips(clips,n); + + /* 4-byte alignments */ + switch (fh->fmt->depth) { + case 8: + case 24: + btcx_align(&win->w, clips, n, 3); + break; + case 16: + btcx_align(&win->w, clips, n, 1); + break; + case 32: + /* no alignment fixups needed */ + break; + default: + BUG(); + } + + down(&fh->vidq.lock); + if (fh->clips) + kfree(fh->clips); + fh->clips = clips; + fh->nclips = n; + fh->win = *win; +#if 0 + fh->ov.setup_ok = 1; +#endif + + /* update overlay if needed */ + retval = 0; +#if 0 + if (check_btres(fh, RESOURCE_OVERLAY)) { + struct bttv_buffer *new; + + new = videobuf_alloc(sizeof(*new)); + bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new); + retval = bttv_switch_overlay(btv,fh,new); + } +#endif + up(&fh->vidq.lock); + return retval; +} +#endif + +/* ------------------------------------------------------------------ */ + +static struct videobuf_queue* get_queue(struct cx8800_fh *fh) +{ + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &fh->vidq; + case V4L2_BUF_TYPE_VBI_CAPTURE: + return &fh->vbiq; + default: + BUG(); + return NULL; + } +} + +static int get_ressource(struct cx8800_fh *fh) +{ + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return RESOURCE_VIDEO; + case V4L2_BUF_TYPE_VBI_CAPTURE: + return RESOURCE_VBI; + default: + BUG(); + return 0; + } +} + +static int video_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct cx8800_dev *h,*dev = NULL; + struct cx8800_fh *fh; + struct list_head *list; + enum v4l2_buf_type type = 0; + int radio = 0; + + list_for_each(list,&cx8800_devlist) { + h = list_entry(list, struct cx8800_dev, devlist); + if (h->video_dev->minor == minor) { + dev = h; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + } + if (h->vbi_dev->minor == minor) { + dev = h; + type = V4L2_BUF_TYPE_VBI_CAPTURE; + } + if (h->radio_dev && + h->radio_dev->minor == minor) { + radio = 1; + dev = h; + } + } + if (NULL == dev) + return -ENODEV; + + dprintk(1,"open minor=%d radio=%d type=%s\n", + minor,radio,v4l2_type_names[type]); + + /* allocate + initialize per filehandle data */ + fh = kmalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + memset(fh,0,sizeof(*fh)); + file->private_data = fh; + fh->dev = dev; + fh->radio = radio; + fh->type = type; + fh->width = 320; + fh->height = 240; + fh->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + + videobuf_queue_init(&fh->vidq, &cx8800_video_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct cx88_buffer)); + videobuf_queue_init(&fh->vbiq, &cx8800_vbi_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct cx88_buffer)); + init_MUTEX(&fh->vidq.lock); + init_MUTEX(&fh->vbiq.lock); + + if (fh->radio) { + dprintk(1,"video_open: setting radio device\n"); + cx_write(MO_GP0_IO, cx88_boards[dev->board].radio.gpio0); + dev->tvaudio = WW_FM; + cx88_set_tvaudio(dev); + cx88_set_stereo(dev,V4L2_TUNER_MODE_STEREO); + cx8800_call_i2c_clients(dev,AUDC_SET_RADIO,NULL); + } + + return 0; +} + +static ssize_t +video_read(struct file *file, char *data, size_t count, loff_t *ppos) +{ + struct cx8800_fh *fh = file->private_data; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (res_locked(fh->dev,RESOURCE_VIDEO)) + return -EBUSY; + return videobuf_read_one(file, &fh->vidq, data, count, ppos); + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (!res_get(fh->dev,fh,RESOURCE_VBI)) + return -EBUSY; + return videobuf_read_stream(file, &fh->vbiq, data, count, ppos, 1); + default: + BUG(); + return 0; + } +} + +static unsigned int +video_poll(struct file *file, struct poll_table_struct *wait) +{ + struct cx8800_fh *fh = file->private_data; + + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) + return videobuf_poll_stream(file, &fh->vbiq, wait); + + /* FIXME */ + return POLLERR; +} + +static int video_release(struct inode *inode, struct file *file) +{ + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; + + /* turn off overlay */ + if (res_check(fh, RESOURCE_OVERLAY)) { + /* FIXME */ + res_free(dev,fh,RESOURCE_OVERLAY); + } + + /* stop video capture */ + if (res_check(fh, RESOURCE_VIDEO)) { + videobuf_queue_cancel(file,&fh->vidq); + res_free(dev,fh,RESOURCE_VIDEO); + } + if (fh->vidq.read_buf) { + buffer_release(file,fh->vidq.read_buf); + kfree(fh->vidq.read_buf); + } + + /* stop vbi capture */ + if (res_check(fh, RESOURCE_VBI)) { + if (fh->vbiq.streaming) + videobuf_streamoff(file,&fh->vbiq); + if (fh->vbiq.reading) + videobuf_read_stop(file,&fh->vbiq); + res_free(dev,fh,RESOURCE_VBI); + } + + file->private_data = NULL; + kfree(fh); + return 0; +} + +static int +video_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct cx8800_fh *fh = file->private_data; + + return videobuf_mmap_mapper(vma, get_queue(fh)); +} + +/* ------------------------------------------------------------------ */ + +static int get_control(struct cx8800_dev *dev, struct v4l2_control *ctl) +{ + struct cx88_ctrl *c = NULL; + u32 value; + int i; + + for (i = 0; i < CX8800_CTLS; i++) + if (cx8800_ctls[i].v.id == ctl->id) + c = &cx8800_ctls[i]; + if (NULL == c) + return -EINVAL; + + value = c->sreg ? cx_sread(c->sreg) : cx_read(c->reg); + switch (ctl->id) { + case V4L2_CID_AUDIO_BALANCE: + ctl->value = (value & 0x40) ? (value & 0x3f) : (0x40 - (value & 0x3f)); + break; + default: + ctl->value = ((value + (c->off << c->shift)) & c->mask) >> c->shift; + break; + } + return 0; +} + +static int set_control(struct cx8800_dev *dev, struct v4l2_control *ctl) +{ + struct cx88_ctrl *c = NULL; + u32 v_sat_value; + u32 value; + int i; + + for (i = 0; i < CX8800_CTLS; i++) + if (cx8800_ctls[i].v.id == ctl->id) + c = &cx8800_ctls[i]; + if (NULL == c) + return -EINVAL; + + if (ctl->value < c->v.minimum) + return -ERANGE; + if (ctl->value > c->v.maximum) + return -ERANGE; + switch (ctl->id) { + case V4L2_CID_AUDIO_BALANCE: + value = (ctl->value < 0x40) ? (0x40 - ctl->value) : ctl->value; + break; + case V4L2_CID_SATURATION: + /* special v_sat handling */ + v_sat_value = ctl->value - (0x7f - 0x5a); + if (v_sat_value > 0xff) + v_sat_value = 0xff; + if (v_sat_value < 0x00) + v_sat_value = 0x00; + cx_andor(MO_UV_SATURATION, 0xff00, v_sat_value << 8); + /* fall through to default route for u_sat */ + default: + value = ((ctl->value - c->off) << c->shift) & c->mask; + break; + } + dprintk(1,"set_control id=0x%X reg=0x%x val=0x%x%s\n", + ctl->id, c->reg, value, c->sreg ? " [shadowed]" : ""); + if (c->sreg) { + cx_sandor(c->sreg, c->reg, c->mask, value); + } else { + cx_andor(c->reg, c->mask, value); + } + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int cx8800_g_fmt(struct cx8800_dev *dev, struct cx8800_fh *fh, + struct v4l2_format *f) +{ + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memset(&f->fmt.pix,0,sizeof(f->fmt.pix)); + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->vidq.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + cx8800_vbi_fmt(dev, f); + return 0; + default: + return -EINVAL; + } +} + +static int cx8800_try_fmt(struct cx8800_dev *dev, struct cx8800_fh *fh, + struct v4l2_format *f) +{ + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + { + struct cx8800_fmt *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = norm_maxw(dev->tvnorm); + maxh = norm_maxh(dev->tvnorm); + +#if 0 + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } +#else + field = V4L2_FIELD_INTERLACED; +#endif + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + f->fmt.pix.field = field; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; + } + case V4L2_BUF_TYPE_VBI_CAPTURE: + cx8800_vbi_fmt(dev, f); + return 0; + default: + return -EINVAL; + } +} + +static int cx8800_s_fmt(struct cx8800_dev *dev, struct cx8800_fh *fh, + struct v4l2_format *f) +{ + int err; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + err = cx8800_try_fmt(dev,fh,f); + if (0 != err) + return err; + + fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->vidq.field = f->fmt.pix.field; + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + cx8800_vbi_fmt(dev, f); + return 0; + default: + return -EINVAL; + } +} + +/* + * This function is _not_ called directly, but from + * video_generic_ioctl (and maybe others). userspace + * copying is done already, arg is a kernel pointer. + */ +static int video_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; +#if 0 + unsigned long flags; +#endif + int err; + + if (video_debug > 1) + cx88_print_ioctl(dev->name,cmd); + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->driver, "cx8800"); + strlcpy(cap->card, cx88_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s",pci_name(dev->pci)); + cap->version = CX88_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING | +#if 0 + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_VBI_CAPTURE | +#endif + 0; + if (UNSET != dev->tuner_type) + cap->capabilities |= V4L2_CAP_TUNER; + + return 0; + } + + /* ---------- tv norms ---------- */ + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *e = arg; + unsigned int i; + + i = e->index; + if (i >= ARRAY_SIZE(tvnorms)) + return -EINVAL; + err = v4l2_video_std_construct(e, tvnorms[e->index].id, + tvnorms[e->index].name); + e->index = i; + if (err < 0) + return err; + return 0; + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + + *id = dev->tvnorm->id; + return 0; + } + case VIDIOC_S_STD: + { + v4l2_std_id *id = arg; + unsigned int i; + + for(i = 0; i < ARRAY_SIZE(tvnorms); i++) + if (*id & tvnorms[i].id) + break; + if (i == ARRAY_SIZE(tvnorms)) + return -EINVAL; + + down(&dev->lock); + set_tvnorm(dev,&tvnorms[i]); + up(&dev->lock); + return 0; + } + + /* ------ input switching ---------- */ + case VIDIOC_ENUMINPUT: + { + static const char *iname[] = { + [ CX88_VMUX_COMPOSITE1 ] = "Composite1", + [ CX88_VMUX_COMPOSITE2 ] = "Composite2", + [ CX88_VMUX_COMPOSITE3 ] = "Composite3", + [ CX88_VMUX_COMPOSITE4 ] = "Composite4", + [ CX88_VMUX_TELEVISION ] = "Television", + [ CX88_VMUX_SVIDEO ] = "S-Video", + [ CX88_VMUX_DEBUG ] = "for debug only", + }; + struct v4l2_input *i = arg; + unsigned int n; + + n = i->index; + if (n >= 4) + return -EINVAL; + if (0 == INPUT(n)->type) + return -EINVAL; + memset(i,0,sizeof(*i)); + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name,iname[INPUT(n)->type]); + if (CX88_VMUX_TELEVISION == INPUT(n)->type) + i->type = V4L2_INPUT_TYPE_TUNER; + for (n = 0; n < ARRAY_SIZE(tvnorms); n++) + i->std |= tvnorms[n].id; + return 0; + } + case VIDIOC_G_INPUT: + { + unsigned int *i = arg; + + *i = dev->input; + return 0; + } + case VIDIOC_S_INPUT: + { + unsigned int *i = arg; + + if (*i >= 4) + return -EINVAL; + down(&dev->lock); + video_mux(dev,*i); + up(&dev->lock); + return 0; + } + + /* --- capture ioctls ---------------------------------------- */ + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *f = arg; + enum v4l2_buf_type type; + unsigned int index; + + index = f->index; + type = f->type; + switch (type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (index >= ARRAY_SIZE(formats)) + return -EINVAL; + memset(f,0,sizeof(*f)); + f->index = index; + f->type = type; + strlcpy(f->description,formats[index].name,sizeof(f->description)); + f->pixelformat = formats[index].fourcc; + break; + default: + return -EINVAL; + } + return 0; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *f = arg; + return cx8800_g_fmt(dev,fh,f); + } + case VIDIOC_S_FMT: + { + struct v4l2_format *f = arg; + return cx8800_s_fmt(dev,fh,f); + } + case VIDIOC_TRY_FMT: + { + struct v4l2_format *f = arg; + return cx8800_try_fmt(dev,fh,f); + } + + /* --- controls ---------------------------------------------- */ + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *c = arg; + int i; + + if (c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) + return -EINVAL; + for (i = 0; i < CX8800_CTLS; i++) + if (cx8800_ctls[i].v.id == c->id) + break; + if (i == CX8800_CTLS) { + *c = no_ctl; + return 0; + } + *c = cx8800_ctls[i].v; + return 0; + } + case VIDIOC_G_CTRL: + return get_control(dev,arg); + case VIDIOC_S_CTRL: + return set_control(dev,arg); + + /* --- tuner ioctls ------------------------------------------ */ + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + u32 reg; + + if (UNSET == dev->tuner_type) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + memset(t,0,sizeof(*t)); + strcpy(t->name, "Television"); + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM; + t->rangehigh = 0xffffffffUL; + + cx88_get_stereo(dev ,t); + reg = cx_read(MO_DEVICE_STATUS); + t->signal = (reg & (1<<5)) ? 0xffff : 0x0000; + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *t = arg; + + if (UNSET == dev->tuner_type) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + cx88_set_stereo(dev,t->audmode); + return 0; + } + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if (UNSET == dev->tuner_type) + return -EINVAL; + if (f->tuner != 0) + return -EINVAL; + memset(f,0,sizeof(*f)); + f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + f->frequency = dev->freq; + return 0; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if (UNSET == dev->tuner_type) + return -EINVAL; + if (f->tuner != 0) + return -EINVAL; + if (0 == fh->radio && f->type != V4L2_TUNER_ANALOG_TV) + return -EINVAL; + if (1 == fh->radio && f->type != V4L2_TUNER_RADIO) + return -EINVAL; + down(&dev->lock); + dev->freq = f->frequency; + cx8800_call_i2c_clients(dev,VIDIOCSFREQ,&dev->freq); + up(&dev->lock); + return 0; + } + + /* --- streaming capture ------------------------------------- */ + case VIDIOC_REQBUFS: + return videobuf_reqbufs(file, get_queue(fh), arg); + + case VIDIOC_QUERYBUF: + return videobuf_querybuf(get_queue(fh), arg); + + case VIDIOC_QBUF: + return videobuf_qbuf(file, get_queue(fh), arg); + + case VIDIOC_DQBUF: + return videobuf_dqbuf(file, get_queue(fh), arg); + + case VIDIOC_STREAMON: + { + int res = get_ressource(fh); + + if (!res_get(dev,fh,res)) + return -EBUSY; + return videobuf_streamon(file, get_queue(fh)); + } + case VIDIOC_STREAMOFF: + { + int res = get_ressource(fh); + + err = videobuf_streamoff(file, get_queue(fh)); + if (err < 0) + return err; + res_free(dev,fh,res); + return 0; + } + + default: + return v4l_compat_translate_ioctl(inode,file,cmd,arg, + video_do_ioctl); + } + return 0; +} + +static int video_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, video_do_ioctl); +} + +/* ----------------------------------------------------------- */ + +static int radio_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; + + if (video_debug > 1) + cx88_print_ioctl(dev->name,cmd); + + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->driver, "cx8800"); + strlcpy(cap->card, cx88_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s", pci_name(dev->pci)); + cap->version = CX88_VERSION_CODE; + cap->capabilities = V4L2_CAP_TUNER; + return 0; + } + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + struct video_tuner vt; + + if (t->index > 0) + return -EINVAL; + + memset(t,0,sizeof(*t)); + strcpy(t->name, "Radio"); + t->rangelow = (int)(65*16); + t->rangehigh = (int)(108*16); + + memset(&vt,0,sizeof(vt)); + cx8800_call_i2c_clients(dev,VIDIOCGTUNER,&vt); + t->signal = vt.signal; + return 0; + } + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + + if (i->index != 0) + return -EINVAL; + strcpy(i->name,"Radio"); + i->type = V4L2_INPUT_TYPE_TUNER; + return 0; + } + case VIDIOC_G_INPUT: + { + int *i = arg; + *i = 0; + return 0; + } + case VIDIOC_G_AUDIO: + { + struct v4l2_audio *a = arg; + + memset(a,0,sizeof(*a)); + strcpy(a->name,"Radio"); + return 0; + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + *id = 0; + return 0; + } + case VIDIOC_S_AUDIO: + case VIDIOC_S_TUNER: + case VIDIOC_S_INPUT: + case VIDIOC_S_STD: + return 0; + + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *c = arg; + int i; + + if (c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) + return -EINVAL; + if (c->id == V4L2_CID_AUDIO_MUTE) { + for (i = 0; i < CX8800_CTLS; i++) + if (cx8800_ctls[i].v.id == c->id) + break; + *c = cx8800_ctls[i].v; + } else + *c = no_ctl; + return 0; + } + + + case VIDIOC_G_CTRL: + case VIDIOC_S_CTRL: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + return video_do_ioctl(inode,file,cmd,arg); + + default: + return v4l_compat_translate_ioctl(inode,file,cmd,arg, + radio_do_ioctl); + } + return 0; +}; + +static int radio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, radio_do_ioctl); +}; + +/* ----------------------------------------------------------- */ + +static void cx8800_vid_timeout(unsigned long data) +{ + struct cx8800_dev *dev = (struct cx8800_dev*)data; + struct cx88_dmaqueue *q = &dev->vidq; + struct cx88_buffer *buf; + unsigned long flags; + + cx88_sram_channel_dump(dev, &cx88_sram_channels[SRAM_CH21]); + //cx88_risc_disasm(dev,&dev->vidq.stopper); + + cx_clear(MO_VID_DMACNTRL, 0x11); + cx_clear(VID_CAPTURE_CONTROL, 0x06); + + spin_lock_irqsave(&dev->slock,flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = STATE_ERROR; + wake_up(&buf->vb.done); + printk("%s: [%p/%d] timeout - dma=0x%08lx\n", dev->name, + buf, buf->vb.i, (unsigned long)buf->risc.dma); + } + restart_video_queue(dev,q); + spin_unlock_irqrestore(&dev->slock,flags); +} + +static void cx8800_wakeup(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, u32 count) +{ + struct cx88_buffer *buf; + + for (;;) { + if (list_empty(&q->active)) + break; + buf = list_entry(q->active.next, + struct cx88_buffer, vb.queue); + if (buf->count > count) + break; + do_gettimeofday(&buf->vb.ts); + dprintk(2,"[%p/%d] wakeup reg=%d buf=%d\n",buf,buf->vb.i, + count, buf->count); + buf->vb.state = STATE_DONE; + list_del(&buf->vb.queue); + wake_up(&buf->vb.done); + } + if (list_empty(&q->active)) { + del_timer(&q->timeout); + } else { + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + } +} + +static void cx8800_vid_irq(struct cx8800_dev *dev) +{ + u32 status, mask, count; + + status = cx_read(MO_VID_INTSTAT); + mask = cx_read(MO_VID_INTMSK); + if (0 == (status & mask)) + return; + cx_write(MO_VID_INTSTAT, status); + if (irq_debug || (status & mask & ~0xff)) + cx88_print_irqbits(dev->name, "irq vid", + cx88_vid_irqs, status, mask); + + /* risc op code error */ + if (status & (1 << 16)) { + printk(KERN_WARNING "%s: video risc op code error\n",dev->name); + cx_clear(MO_VID_DMACNTRL, 0x11); + cx_clear(VID_CAPTURE_CONTROL, 0x06); + cx88_sram_channel_dump(dev, &cx88_sram_channels[SRAM_CH21]); + } + + /* risc1 y */ + if (status & 0x01) { + spin_lock(&dev->slock); + count = cx_read(MO_VIDY_GPCNT); + cx8800_wakeup(dev, &dev->vidq, count); + spin_unlock(&dev->slock); + } + + /* risc1 vbi */ + if (status & 0x04) { + spin_lock(&dev->slock); + count = cx_read(MO_VBI_GPCNT); + cx8800_wakeup(dev, &dev->vbiq, count); + spin_unlock(&dev->slock); + } + + /* risc2 y */ + if (status & 0x10) { + dprintk(2,"stopper video\n"); + spin_lock(&dev->slock); + restart_video_queue(dev,&dev->vidq); + spin_unlock(&dev->slock); + } + + /* risc2 vbi */ + if (status & 0x40) { + dprintk(2,"stopper vbi\n"); + spin_lock(&dev->slock); + cx8800_restart_vbi_queue(dev,&dev->vbiq); + spin_unlock(&dev->slock); + } +} + +static irqreturn_t cx8800_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct cx8800_dev *dev = dev_id; + u32 status, mask; + int loop, handled = 0; + + for (loop = 0; loop < 10; loop++) { + status = cx_read(MO_PCI_INTSTAT); + mask = cx_read(MO_PCI_INTMSK); + if (0 == (status & mask)) + goto out; + handled = 1; + cx_write(MO_PCI_INTSTAT, status); + if (irq_debug || (status & mask & ~0x1f)) + cx88_print_irqbits(dev->name, "irq pci", + cx88_pci_irqs, status, mask); + + if (status & 1) + cx8800_vid_irq(dev); + }; + if (10 == loop) { + printk(KERN_WARNING "%s: irq loop -- clearing mask\n", + dev->name); + cx_write(MO_PCI_INTMSK,0); + } + + out: + return IRQ_RETVAL(handled); +} + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +static struct file_operations video_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = video_mmap, + .ioctl = video_ioctl, + .llseek = no_llseek, +}; + +struct video_device cx8800_video_template = +{ + .name = "cx8800-video", + .type = VID_TYPE_CAPTURE|VID_TYPE_TUNER|VID_TYPE_SCALES, + .hardware = 0, + .fops = &video_fops, + .minor = -1, +}; + +struct video_device cx8800_vbi_template = +{ + .name = "cx8800-vbi", + .type = VID_TYPE_TELETEXT|VID_TYPE_TUNER, + .hardware = 0, + .fops = &video_fops, + .minor = -1, +}; + +static struct file_operations radio_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .ioctl = radio_ioctl, + .llseek = no_llseek, +}; + +struct video_device cx8800_radio_template = +{ + .name = "cx8800-radio", + .type = VID_TYPE_TUNER, + .hardware = 0, + .fops = &radio_fops, + .minor = -1, +}; + +/* ----------------------------------------------------------- */ + +static void cx8800_shutdown(struct cx8800_dev *dev) +{ + /* disable RISC controller + IRQs */ + cx_write(MO_DEV_CNTRL2, 0); + + /* stop dma transfers */ + cx_write(MO_VID_DMACNTRL, 0x0); + cx_write(MO_AUD_DMACNTRL, 0x0); + cx_write(MO_TS_DMACNTRL, 0x0); + cx_write(MO_VIP_DMACNTRL, 0x0); + cx_write(MO_GPHST_DMACNTRL, 0x0); + + /* stop interupts */ + cx_write(MO_PCI_INTMSK, 0x0); + cx_write(MO_VID_INTMSK, 0x0); + cx_write(MO_AUD_INTMSK, 0x0); + cx_write(MO_TS_INTMSK, 0x0); + cx_write(MO_VIP_INTMSK, 0x0); + cx_write(MO_GPHST_INTMSK, 0x0); + + /* stop capturing */ + cx_write(VID_CAPTURE_CONTROL, 0); +} + +static int cx8800_reset(struct cx8800_dev *dev) +{ + dprintk(1,"cx8800_reset\n"); + + cx8800_shutdown(dev); + + /* clear irq status */ + cx_write(MO_VID_INTSTAT, 0xFFFFFFFF); // Clear PIV int + cx_write(MO_PCI_INTSTAT, 0xFFFFFFFF); // Clear PCI int + cx_write(MO_INT1_STAT, 0xFFFFFFFF); // Clear RISC int + + /* wait a bit */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + + /* init sram */ + cx88_sram_channel_setup(dev, &cx88_sram_channels[SRAM_CH21], 720*4, 0); + cx88_sram_channel_setup(dev, &cx88_sram_channels[SRAM_CH22], 128, 0); + cx88_sram_channel_setup(dev, &cx88_sram_channels[SRAM_CH23], 128, 0); + cx88_sram_channel_setup(dev, &cx88_sram_channels[SRAM_CH24], 128, 0); + cx88_sram_channel_setup(dev, &cx88_sram_channels[SRAM_CH25], 128, 0); + cx88_sram_channel_setup(dev, &cx88_sram_channels[SRAM_CH26], 128, 0); + + /* misc init ... */ + cx_write(MO_INPUT_FORMAT, ((1 << 13) | // agc enable + (1 << 12) | // agc gain + (1 << 11) | // adaptibe agc + (0 << 10) | // chroma agc + (0 << 9) | // ckillen + (7))); + + /* setup image format */ + cx_andor(MO_COLOR_CTRL, 0x4000, 0x4000); + + /* setup FIFO Threshholds */ + cx_write(MO_PDMA_STHRSH, 0x0807); + cx_write(MO_PDMA_DTHRSH, 0x0807); + + /* fixes flashing of image */ + cx_write(MO_AGC_SYNC_TIP1, 0x0380000F); + cx_write(MO_AGC_BACK_VBI, 0x00E00555); + + cx_write(MO_VID_INTSTAT, 0xFFFFFFFF); // Clear PIV int + cx_write(MO_PCI_INTSTAT, 0xFFFFFFFF); // Clear PCI int + cx_write(MO_INT1_STAT, 0xFFFFFFFF); // Clear RISC int + + return 0; +} + +static struct video_device *vdev_init(struct cx8800_dev *dev, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->minor = -1; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + vfd->dev = &dev->pci->dev; + vfd->release = video_device_release; +#endif + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", + dev->name, type, cx88_boards[dev->board].name); + return vfd; +} + +static void cx8800_unregister_video(struct cx8800_dev *dev) +{ + if (dev->radio_dev) { + if (-1 != dev->radio_dev->minor) + video_unregister_device(dev->radio_dev); + else + video_device_release(dev->radio_dev); + dev->radio_dev = NULL; + } + if (dev->vbi_dev) { + if (-1 != dev->vbi_dev->minor) + video_unregister_device(dev->vbi_dev); + else + video_device_release(dev->vbi_dev); + dev->vbi_dev = NULL; + } + if (dev->video_dev) { + if (-1 != dev->video_dev->minor) + video_unregister_device(dev->video_dev); + else + video_device_release(dev->video_dev); + dev->video_dev = NULL; + } +} + +/* debug that damn oops ... */ +static unsigned int oops = 0; +MODULE_PARM(oops,"i"); +#define OOPS(msg) if (oops) printk("%s: %s\n",__FUNCTION__,msg); + +static int __devinit cx8800_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cx8800_dev *dev; + unsigned int i; + int err; + + dev = kmalloc(sizeof(*dev),GFP_KERNEL); + if (NULL == dev) + return -ENOMEM; + memset(dev,0,sizeof(*dev)); + + /* pci init */ + OOPS("pci init"); + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail1; + } + sprintf(dev->name,"cx%x[%d]",pci_dev->device,cx8800_devcount); + + /* pci quirks */ + OOPS("pci quirks"); + cx88_pci_quirks(dev->name, dev->pci, &latency); + if (UNSET != latency) { + printk(KERN_INFO "%s: setting pci latency timer to %d\n", + dev->name,latency); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency); + } + + /* print pci info */ + OOPS("pci info"); + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &dev->pci_rev); + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + printk(KERN_INFO "%s: found at %s, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%lx\n", dev->name, + pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat,pci_resource_start(pci_dev,0)); + + pci_set_master(pci_dev); + if (!pci_dma_supported(pci_dev,0xffffffff)) { + printk("%s: Oops: no 32bit PCI DMA ???\n",dev->name); + err = -EIO; + goto fail1; + } + + /* board config */ + OOPS("board config"); + dev->board = card[cx8800_devcount]; + for (i = 0; UNSET == dev->board && i < cx88_idcount; i++) + if (pci_dev->subsystem_vendor == cx88_subids[i].subvendor && + pci_dev->subsystem_device == cx88_subids[i].subdevice) + dev->board = cx88_subids[i].card; + if (UNSET == dev->board) + dev->board = CX88_BOARD_UNKNOWN; + printk(KERN_INFO "%s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n", + dev->name,pci_dev->subsystem_vendor, + pci_dev->subsystem_device,cx88_boards[dev->board].name, + dev->board, card[cx8800_devcount] == dev->board ? + "insmod option" : "autodetected"); + + dev->tuner_type = tuner[cx8800_devcount]; + if (UNSET == dev->tuner_type) + dev->tuner_type = cx88_boards[dev->board].tuner_type; + + /* get mmio */ + OOPS("get mmio"); + if (!request_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0), + dev->name)) { + err = -EBUSY; + printk(KERN_ERR "%s: can't get MMIO memory @ 0x%lx\n", + dev->name,pci_resource_start(pci_dev,0)); + goto fail1; + } + dev->lmmio = ioremap(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); + + /* initialize driver struct */ + OOPS("init structs"); + init_MUTEX(&dev->lock); + dev->slock = SPIN_LOCK_UNLOCKED; + dev->tvnorm = tvnorms; + + /* init video dma queues */ + INIT_LIST_HEAD(&dev->vidq.active); + INIT_LIST_HEAD(&dev->vidq.queued); + dev->vidq.timeout.function = cx8800_vid_timeout; + dev->vidq.timeout.data = (unsigned long)dev; + init_timer(&dev->vidq.timeout); + cx88_risc_stopper(dev->pci,&dev->vidq.stopper, + MO_VID_DMACNTRL,0x11,0x00); + + /* init vbi dma queues */ + INIT_LIST_HEAD(&dev->vbiq.active); + INIT_LIST_HEAD(&dev->vbiq.queued); + dev->vbiq.timeout.function = cx8800_vbi_timeout; + dev->vbiq.timeout.data = (unsigned long)dev; + init_timer(&dev->vbiq.timeout); + cx88_risc_stopper(dev->pci,&dev->vbiq.stopper, + MO_VID_DMACNTRL,0x88,0x00); + + /* initialize hardware */ + OOPS("reset hardware"); + cx8800_reset(dev); + + /* get irq */ + OOPS("install irq handler"); + err = request_irq(pci_dev->irq, cx8800_irq, + SA_SHIRQ | SA_INTERRUPT, dev->name, dev); + if (err < 0) { + printk(KERN_ERR "%s: can't get IRQ %d\n", + dev->name,pci_dev->irq); + goto fail2; + } + + /* register i2c bus + load i2c helpers */ + OOPS("i2c setup"); + cx8800_i2c_init(dev); + OOPS("card setup"); + cx88_card_setup(dev); + + /* load and configure helper modules */ + OOPS("configure i2c clients"); + if (TUNER_ABSENT != dev->tuner_type) + request_module("tuner"); + if (dev->tuner_type != UNSET) + cx8800_call_i2c_clients(dev,TUNER_SET_TYPE,&dev->tuner_type); + + /* register v4l devices */ + OOPS("register video"); + dev->video_dev = vdev_init(dev,&cx8800_video_template,"video"); + err = video_register_device(dev->video_dev,VFL_TYPE_GRABBER, + video_nr[cx8800_devcount]); + if (err < 0) { + printk(KERN_INFO "%s: can't register video device\n", + dev->name); + goto fail3; + } + printk(KERN_INFO "%s: registered device video%d [v4l2]\n", + dev->name,dev->video_dev->minor & 0x1f); + + OOPS("register vbi"); + dev->vbi_dev = vdev_init(dev,&cx8800_vbi_template,"vbi"); + err = video_register_device(dev->vbi_dev,VFL_TYPE_VBI, + vbi_nr[cx8800_devcount]); + if (err < 0) { + printk(KERN_INFO "%s: can't register vbi device\n", + dev->name); + goto fail3; + } + printk(KERN_INFO "%s: registered device vbi%d\n", + dev->name,dev->vbi_dev->minor & 0x1f); + + if (dev->has_radio) { + OOPS("register radio"); + dev->radio_dev = vdev_init(dev,&cx8800_radio_template,"radio"); + err = video_register_device(dev->radio_dev,VFL_TYPE_RADIO, + radio_nr[cx8800_devcount]); + if (err < 0) { + printk(KERN_INFO "%s: can't register radio device\n", + dev->name); + goto fail3; + } + printk(KERN_INFO "%s: registered device radio%d\n", + dev->name,dev->radio_dev->minor & 0x1f); + } + + /* everything worked */ + OOPS("finalize"); + list_add_tail(&dev->devlist,&cx8800_devlist); + pci_set_drvdata(pci_dev,dev); + cx8800_devcount++; + + /* initial device configuration */ + OOPS("init device"); + down(&dev->lock); + set_tvnorm(dev,tvnorms); + video_mux(dev,0); + up(&dev->lock); + return 0; + + fail3: + OOPS("fail3"); + cx8800_unregister_video(dev); + if (0 == dev->i2c_rc) + i2c_bit_del_bus(&dev->i2c_adap); + free_irq(pci_dev->irq, dev); + fail2: + OOPS("fail2"); + release_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); + fail1: + OOPS("fail1"); + kfree(dev); + return err; +} + +static void __devexit cx8800_finidev(struct pci_dev *pci_dev) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + + cx8800_shutdown(dev); + pci_disable_device(pci_dev); + + /* unregister stuff */ + if (0 == dev->i2c_rc) + i2c_bit_del_bus(&dev->i2c_adap); + + free_irq(pci_dev->irq, dev); + release_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); + + cx8800_unregister_video(dev); + pci_set_drvdata(pci_dev, NULL); + + /* free memory */ + btcx_riscmem_free(dev->pci,&dev->vidq.stopper); + list_del(&dev->devlist); + cx8800_devcount--; + kfree(dev); +} + +static int cx8800_suspend(struct pci_dev *pci_dev, u32 state) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + + printk("%s: suspend %d\n", dev->name, state); + + cx8800_shutdown(dev); + del_timer(&dev->vidq.timeout); + + pci_save_state(pci_dev, dev->state.pci_cfg); + if (0 != pci_set_power_state(pci_dev, state)) { + pci_disable_device(pci_dev); + dev->state.disabled = 1; + } + return 0; +} + +static int cx8800_resume(struct pci_dev *pci_dev) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + + printk("%s: resume\n", dev->name); + + if (dev->state.disabled) { + pci_enable_device(pci_dev); + dev->state.disabled = 0; + } + pci_set_power_state(pci_dev, 0); + pci_restore_state(pci_dev, dev->state.pci_cfg); + + /* re-initialize hardware */ + cx8800_reset(dev); + + /* restart video capture */ + spin_lock(&dev->slock); + restart_video_queue(dev,&dev->vidq); + spin_unlock(&dev->slock); + + return 0; +} + +/* ----------------------------------------------------------- */ + +struct pci_device_id cx8800_pci_tbl[] = { + { + .vendor = 0x14f1, + .device = 0x8800, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + },{ + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, cx8800_pci_tbl); + +static struct pci_driver cx8800_pci_driver = { + .name = "cx8800", + .id_table = cx8800_pci_tbl, + .probe = cx8800_initdev, + .remove = cx8800_finidev, + + .suspend = cx8800_suspend, + .resume = cx8800_resume, +}; + +static int cx8800_init(void) +{ + INIT_LIST_HEAD(&cx8800_devlist); + printk(KERN_INFO "cx2388x v4l2 driver version %d.%d.%d loaded\n", + (CX88_VERSION_CODE >> 16) & 0xff, + (CX88_VERSION_CODE >> 8) & 0xff, + CX88_VERSION_CODE & 0xff); +#ifdef SNAPSHOT + printk(KERN_INFO "cx2388x: snapshot date %04d-%02d-%02d\n", + SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100); +#endif + return pci_module_init(&cx8800_pci_driver); +} + +static void cx8800_fini(void) +{ + pci_unregister_driver(&cx8800_pci_driver); +} + +module_init(cx8800_init); +module_exit(cx8800_fini); + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/cx88/cx88.h b/linux/drivers/media/video/cx88/cx88.h new file mode 100644 index 000000000..55a2f1f7b --- /dev/null +++ b/linux/drivers/media/video/cx88/cx88.h @@ -0,0 +1,394 @@ +/* + * v4l2 device driver for philips saa7134 based TV cards + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/videodev.h> +#include <linux/kdev_t.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,64) +#include "video-buf.h" +#include "tuner.h" +#include "audiochip.h" +#include "i2c-compat.h" +#else +#include <media/video-buf.h> +#include <media/tuner.h> +#include <media/audiochip.h> +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,23) +# define irqreturn_t void +# define IRQ_RETVAL(foobar) +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,71) +# define strlcpy(dest,src,len) strncpy(dest,src,(len)-1) +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +# define iminor(inode) minor(inode->i_rdev) +#endif + +#include "btcx-risc.h" +#include "cx88-reg.h" + +#include <linux/version.h> +#define CX88_VERSION_CODE KERNEL_VERSION(0,0,1) + +#ifndef TRUE +# define TRUE (1==1) +#endif +#ifndef FALSE +# define FALSE (1==0) +#endif +#define UNSET (-1U) + +#define CX88_MAXBOARDS 8 + +/* ----------------------------------------------------------- */ +/* defines and enums */ + +#define FORMAT_FLAGS_PACKED 0x01 +#define FORMAT_FLAGS_PLANAR 0x02 + +#define VBI_LINE_COUNT 16 +#define VBI_LINE_LENGTH 2048 + +/* need "shadow" registers for some write-only ones ... */ +#define SHADOW_AUD_VOL_CTL 1 +#define SHADOW_AUD_BAL_CTL 2 +#define SHADOW_MAX 2 + +/* ----------------------------------------------------------- */ +/* static data */ + +struct cx8800_tvnorm { + char *name; + v4l2_std_id id; + u32 cxiformat; + u32 cxoformat; +}; + +struct cx8800_fmt { + char *name; + u32 fourcc; /* v4l2 format id */ + int depth; + int flags; + u32 cxformat; +}; + +struct cx88_ctrl { + struct v4l2_queryctrl v; + u32 off; + u32 reg; + u32 sreg; + u32 mask; + u32 shift; +}; + +/* ----------------------------------------------------------- */ +/* SRAM memory management data (see cx88-core.c) */ + +#define SRAM_CH21 0 /* video */ +#define SRAM_CH22 1 +#define SRAM_CH23 2 +#define SRAM_CH24 3 /* vbi */ +#define SRAM_CH25 4 /* audio */ +#define SRAM_CH26 5 +/* more */ + +struct sram_channel { + char *name; + u32 cmds_start; + u32 ctrl_start; + u32 cdt; + u32 fifo_start; + u32 fifo_size; + u32 ptr1_reg; + u32 ptr2_reg; + u32 cnt1_reg; + u32 cnt2_reg; +}; +extern struct sram_channel cx88_sram_channels[]; + +/* ----------------------------------------------------------- */ +/* card configuration */ + +#define CX88_BOARD_NOAUTO UNSET +#define CX88_BOARD_UNKNOWN 0 +#define CX88_BOARD_HAUPPAUGE 1 +#define CX88_BOARD_GDI 2 +#define CX88_BOARD_PIXELVIEW 3 +#define CX88_BOARD_ATI_WONDER_PRO 4 +#define CX88_BOARD_WINFAST2000XP 5 +#define CX88_BOARD_AVERTV_303 6 +#define CX88_BOARD_MSI_TVANYWHERE 7 +#define CX88_BOARD_WINFAST_DV2000 8 +#define CX88_BOARD_LEADTEK_PVR2000 9 + + +enum cx88_itype { + CX88_VMUX_COMPOSITE1 = 1, + CX88_VMUX_COMPOSITE2 = 2, + CX88_VMUX_COMPOSITE3 = 3, + CX88_VMUX_COMPOSITE4 = 4, + CX88_VMUX_TELEVISION = 5, + CX88_VMUX_SVIDEO = 6, + CX88_VMUX_DEBUG = 7, + CX88_RADIO = 8, +}; + +struct cx88_input { + enum cx88_itype type; + unsigned int vmux; + u32 gpio0; +}; + +struct cx88_board { + char *name; + unsigned int tuner_type; + struct cx88_input input[8]; + struct cx88_input radio; +}; + +struct cx88_subid { + u16 subvendor; + u16 subdevice; + u32 card; +}; + +#define INPUT(nr) (&cx88_boards[dev->board].input[nr]) + +/* ----------------------------------------------------------- */ +/* device / file handle status */ + +#define RESOURCE_OVERLAY 1 +#define RESOURCE_VIDEO 2 +#define RESOURCE_VBI 4 + +//#define BUFFER_TIMEOUT (HZ/2) /* 0.5 seconds */ +#define BUFFER_TIMEOUT (HZ*2) + +struct cx8800_dev; + +/* buffer for one video frame */ +struct cx88_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* cx88 specific */ + unsigned int bpl; + struct btcx_riscmem risc; + struct cx8800_fmt *fmt; + u32 count; +}; + +struct cx88_dmaqueue { + struct list_head active; + struct list_head queued; + struct timer_list timeout; + struct btcx_riscmem stopper; + u32 count; +}; + +/* video filehandle status */ +struct cx8800_fh { + struct cx8800_dev *dev; + enum v4l2_buf_type type; + int radio; + unsigned int resources; + + /* video overlay */ + struct v4l2_window win; + struct v4l2_clip *clips; + unsigned int nclips; + + /* video capture */ + struct cx8800_fmt *fmt; + unsigned int width,height; + struct videobuf_queue vidq; + + /* vbi capture */ + struct videobuf_queue vbiq; +}; + +struct cx8800_suspend_state { + u32 pci_cfg[64 / sizeof(u32)]; + int disabled; +}; + +/* global device status */ +struct cx8800_dev { + struct list_head devlist; + struct semaphore lock; + spinlock_t slock; + + /* various device info */ + unsigned int resources; + struct video_device *video_dev; + struct video_device *vbi_dev; + struct video_device *radio_dev; + + /* pci i/o */ + char name[32]; + struct pci_dev *pci; + unsigned char pci_rev,pci_lat; + u32 *lmmio; + + /* config info */ + unsigned int board; + unsigned int tuner_type; + unsigned int has_radio; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_algo_bit_data i2c_algo; + struct i2c_client i2c_client; + u32 i2c_state, i2c_rc; + + /* video overlay */ + struct v4l2_framebuffer fbuf; + struct cx88_buffer *screen; + + /* capture queues */ + struct cx88_dmaqueue vidq; + struct cx88_dmaqueue vbiq; + + /* various v4l controls */ + struct cx8800_tvnorm *tvnorm; + u32 tvaudio; + u32 input; + u32 freq; + + /* other global state info */ + u32 shadow[SHADOW_MAX]; + struct cx8800_suspend_state state; +}; + +/* ----------------------------------------------------------- */ + +#define cx_read(reg) readl(dev->lmmio + ((reg)>>2)) +#define cx_write(reg,value) writel((value), dev->lmmio + ((reg)>>2)); + +#define cx_andor(reg,mask,value) \ + writel((readl(dev->lmmio+((reg)>>2)) & ~(mask)) |\ + ((value) & (mask)), dev->lmmio+((reg)>>2)) +#define cx_set(reg,bit) cx_andor((reg),(bit),(bit)) +#define cx_clear(reg,bit) cx_andor((reg),(bit),0) + +#define cx_wait(d) { if (need_resched()) schedule(); else udelay(d); } + +/* shadow registers */ +#define cx_sread(sreg) (dev->shadow[sreg]) +#define cx_swrite(sreg,reg,value) \ + (dev->shadow[sreg] = value, \ + writel(dev->shadow[sreg], dev->lmmio + ((reg)>>2))) +#define cx_sandor(sreg,reg,mask,value) \ + (dev->shadow[sreg] = (dev->shadow[sreg] & ~(mask)) | ((value) & (mask)), \ + writel(dev->shadow[sreg], dev->lmmio + ((reg)>>2))) + +/* ----------------------------------------------------------- */ +/* cx88-core.c */ + +extern char *cx88_pci_irqs[32]; +extern char *cx88_vid_irqs[32]; +extern void cx88_print_irqbits(char *name, char *tag, char **strings, + u32 bits, u32 mask); +extern void cx88_print_ioctl(char *name, unsigned int cmd); + +extern int +cx88_risc_buffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines); +extern int +cx88_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc, + u32 reg, u32 mask, u32 value); +extern void +cx88_free_buffer(struct pci_dev *pci, struct cx88_buffer *buf); + +extern void cx88_risc_disasm(struct cx8800_dev *dev, + struct btcx_riscmem *risc); + +extern int cx88_sram_channel_setup(struct cx8800_dev *dev, + struct sram_channel *ch, + unsigned int bpl, u32 risc); +extern void cx88_sram_channel_dump(struct cx8800_dev *dev, + struct sram_channel *ch); + +extern int cx88_pci_quirks(char *name, struct pci_dev *pci, + unsigned int *latency); + +/* ----------------------------------------------------------- */ +/* cx88-vbi.c */ + +void cx8800_vbi_fmt(struct cx8800_dev *dev, struct v4l2_format *f); +int cx8800_start_vbi_dma(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf); +int cx8800_restart_vbi_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q); +void cx8800_vbi_timeout(unsigned long data); + +extern struct videobuf_queue_ops cx8800_vbi_qops; + +/* ----------------------------------------------------------- */ +/* cx88-i2c.c */ + +extern int cx8800_i2c_init(struct cx8800_dev *dev); +extern void cx8800_call_i2c_clients(struct cx8800_dev *dev, + unsigned int cmd, void *arg); + + +/* ----------------------------------------------------------- */ +/* cx88-cards.c */ + +extern struct cx88_board cx88_boards[]; +extern const unsigned int cx88_bcount; + +extern struct cx88_subid cx88_subids[]; +extern const unsigned int cx88_idcount; + +extern void cx88_card_setup(struct cx8800_dev *dev); + +/* ----------------------------------------------------------- */ +/* cx88-tvaudio.c */ + +#define WW_NONE 1 +#define WW_BTSC 2 +#define WW_NICAM_I 3 +#define WW_NICAM_BGDKL 4 +#define WW_A1 5 +#define WW_A2_BG 6 +#define WW_A2_DK 7 +#define WW_A2_M 8 +#define WW_EIAJ 9 +#define WW_SYSTEM_L_AM 10 +#define WW_I2SPT 11 +#define WW_FM 12 + +void cx88_set_tvaudio(struct cx8800_dev *dev); +void cx88_get_stereo(struct cx8800_dev *dev, struct v4l2_tuner *t); +void cx88_set_stereo(struct cx8800_dev *dev, u32 mode); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/ir-kbd-gpio.c b/linux/drivers/media/video/ir-kbd-gpio.c new file mode 100644 index 000000000..550632d29 --- /dev/null +++ b/linux/drivers/media/video/ir-kbd-gpio.c @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2003 Gerd Knorr + * Copyright (c) 2003 Pavel Machek + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/pci.h> + +#include <media/ir-common.h> + +#include "bttv.h" + +/* ---------------------------------------------------------------------- */ + +static IR_KEYTAB_TYPE ir_codes_avermedia[IR_KEYTAB_SIZE] = { + [ 17 ] = KEY_KP0, + [ 20 ] = KEY_KP1, + [ 12 ] = KEY_KP2, + [ 28 ] = KEY_KP3, + [ 18 ] = KEY_KP4, + [ 10 ] = KEY_KP5, + [ 26 ] = KEY_KP6, + [ 22 ] = KEY_KP7, + [ 14 ] = KEY_KP8, + [ 30 ] = KEY_KP9, + + [ 24 ] = KEY_EJECTCD, // Unmarked on my controller + [ 0 ] = KEY_POWER, + [ 9 ] = BTN_LEFT, // DISPLAY/L + [ 25 ] = BTN_RIGHT, // LOOP/R + [ 5 ] = KEY_MUTE, + [ 19 ] = KEY_RECORD, + [ 11 ] = KEY_PAUSE, + [ 27 ] = KEY_STOP, + [ 15 ] = KEY_VOLUMEDOWN, + [ 31 ] = KEY_VOLUMEUP, + + [ 16 ] = KEY_TUNER, // TV/FM + [ 8 ] = KEY_CD, + [ 4 ] = KEY_VIDEO, + [ 2 ] = KEY_AUDIO, + [ 6 ] = KEY_ZOOM, // full screen + [ 1 ] = KEY_INFO, // preview + [ 21 ] = KEY_SEARCH, // autoscan + [ 13 ] = KEY_STOP, // freeze + [ 29 ] = KEY_RECORD, // capture + [ 3 ] = KEY_PLAY, // unmarked + [ 24 ] = KEY_RED, // unmarked + [ 7 ] = KEY_GREEN, // unmarked + +#if 0 + [ 16 ] = KEY_YELLOW, // unmarked + [ 8 ] = KEY_CHANNELDOWN, + [ 24 ] = KEY_CHANNELUP, + [ 0 ] = KEY_BLUE, // unmarked +#endif +}; + +static IR_KEYTAB_TYPE winfast_codes[IR_KEYTAB_SIZE] = { + [ 5 ] = KEY_KP1, + [ 6 ] = KEY_KP2, + [ 7 ] = KEY_KP3, + [ 9 ] = KEY_KP4, + [ 10 ] = KEY_KP5, + [ 11 ] = KEY_KP6, + [ 13 ] = KEY_KP7, + [ 14 ] = KEY_KP8, + [ 15 ] = KEY_KP9, + [ 18 ] = KEY_KP0, + + [ 0 ] = KEY_POWER, +// [ 27 ] = MTS button + [ 2 ] = KEY_TUNER, // TV/FM + [ 30 ] = KEY_VIDEO, +// [ 22 ] = display button + [ 4 ] = KEY_VOLUMEUP, + [ 8 ] = KEY_VOLUMEDOWN, + [ 12 ] = KEY_CHANNELUP, + [ 16 ] = KEY_CHANNELDOWN, + [ 3 ] = KEY_ZOOM, // fullscreen + [ 31 ] = KEY_SUBTITLE, // closed caption/teletext + [ 32 ] = KEY_SLEEP, +// [ 41 ] = boss key + [ 20 ] = KEY_MUTE, + [ 43 ] = KEY_RED, + [ 44 ] = KEY_GREEN, + [ 45 ] = KEY_YELLOW, + [ 46 ] = KEY_BLUE, + [ 24 ] = KEY_KPPLUS, //fine tune + + [ 25 ] = KEY_KPMINUS, //fine tune - +// [ 42 ] = picture in picture + [ 33 ] = KEY_KPDOT, + [ 19 ] = KEY_KPENTER, +// [ 17 ] = recall + [ 34 ] = KEY_BACK, + [ 35 ] = KEY_PLAYPAUSE, + [ 36 ] = KEY_NEXT, +// [ 37 ] = time shifting + [ 38 ] = KEY_STOP, + [ 39 ] = KEY_RECORD +// [ 40 ] = snapshot +}; + +static IR_KEYTAB_TYPE ir_codes_pixelview[IR_KEYTAB_SIZE] = { + [ 2 ] = KEY_KP0, + [ 1 ] = KEY_KP1, + [ 11 ] = KEY_KP2, + [ 27 ] = KEY_KP3, + [ 5 ] = KEY_KP4, + [ 9 ] = KEY_KP5, + [ 21 ] = KEY_KP6, + [ 6 ] = KEY_KP7, + [ 10 ] = KEY_KP8, + [ 18 ] = KEY_KP9, + + [ 3 ] = KEY_TUNER, // TV/FM + [ 7 ] = KEY_SEARCH, // scan + [ 28 ] = KEY_ZOOM, // full screen + [ 30 ] = KEY_POWER, + [ 23 ] = KEY_VOLUMEDOWN, + [ 31 ] = KEY_VOLUMEUP, + [ 20 ] = KEY_CHANNELDOWN, + [ 22 ] = KEY_CHANNELUP, + [ 24 ] = KEY_MUTE, + + [ 0 ] = KEY_LIST, // source + [ 19 ] = KEY_INFO, // loop + [ 16 ] = KEY_LAST, // +100 + [ 13 ] = KEY_CLEAR, // reset + [ 12 ] = BTN_RIGHT, // fun++ + [ 4 ] = BTN_LEFT, // fun-- + [ 14 ] = KEY_GOTO, // function + [ 15 ] = KEY_STOP, // freeze +}; + +/* ---------------------------------------------------------------------- */ + +struct IR { + struct bttv_sub_device *sub; + struct input_dev input; + struct ir_input_state ir; + char name[32]; + char phys[32]; + u32 mask_keycode; + u32 mask_keydown; + u32 mask_keyup; + + int polling; + u32 last_gpio; + struct work_struct work; + struct timer_list timer; +}; + +static int debug; +module_param(debug, int, 0644); /* debug level (0,1,2) */ + +#define DEVNAME "ir-kbd-gpio" +#define dprintk(fmt, arg...) if (debug) \ + printk(KERN_DEBUG DEVNAME ": " fmt , ## arg) + +static void ir_irq(struct bttv_sub_device *sub); +static int ir_probe(struct device *dev); +static int ir_remove(struct device *dev); + +static struct bttv_sub_driver driver = { + .drv = { + .name = DEVNAME, + .probe = ir_probe, + .remove = ir_remove, + }, + .gpio_irq = ir_irq, +}; + +/* ---------------------------------------------------------------------- */ + +static void ir_handle_key(struct IR *ir) +{ + u32 gpio,data; + + /* read gpio value */ + gpio = bttv_gpio_read(ir->sub->core); + if (ir->polling) { + if (ir->last_gpio == gpio) + return; + ir->last_gpio = gpio; + } + + /* extract data */ + data = ir_extract_bits(gpio, ir->mask_keycode); + dprintk(DEVNAME ": irq gpio=0x%x code=%d | %s%s%s\n", + gpio, data, + ir->polling ? "poll" : "irq", + (gpio & ir->mask_keydown) ? " down" : "", + (gpio & ir->mask_keyup) ? " up" : ""); + + if (ir->mask_keydown) { + /* bit set on keydown */ + if (gpio & ir->mask_keydown) { + ir_input_keydown(&ir->input,&ir->ir,data,data); + } else { + ir_input_nokey(&ir->input,&ir->ir); + } + + } else if (ir->mask_keyup) { + /* bit cleared on keydown */ + if (0 == (gpio & ir->mask_keyup)) { + ir_input_keydown(&ir->input,&ir->ir,data,data); + } else { + ir_input_nokey(&ir->input,&ir->ir); + } + + } else { + /* can't disturgissh keydown/up :-/ */ + ir_input_keydown(&ir->input,&ir->ir,data,data); + ir_input_nokey(&ir->input,&ir->ir); + } +} + +static void ir_irq(struct bttv_sub_device *sub) +{ + struct IR *ir = dev_get_drvdata(&sub->dev); + + if (!ir->polling) + ir_handle_key(ir); +} + +static void ir_timer(unsigned long data) +{ + struct IR *ir = (struct IR*)data; + + schedule_work(&ir->work); +} + +static void ir_work(void *data) +{ + struct IR *ir = data; + unsigned long timeout; + + ir_handle_key(ir); + timeout = jiffies + (ir->polling * HZ / 1000); + mod_timer(&ir->timer, timeout); +} + +/* ---------------------------------------------------------------------- */ + +static int ir_probe(struct device *dev) +{ + struct bttv_sub_device *sub = to_bttv_sub_dev(dev); + struct IR *ir; + IR_KEYTAB_TYPE *ir_codes = NULL; + int ir_type = IR_TYPE_OTHER; + + ir = kmalloc(sizeof(*ir),GFP_KERNEL); + if (NULL == ir) + return -ENOMEM; + memset(ir,0,sizeof(*ir)); + + /* detect & configure */ + switch (sub->core->type) { + case BTTV_AVERMEDIA: + case BTTV_AVPHONE98: + ir_codes = ir_codes_avermedia; + ir->mask_keycode = 0xf80000; + ir->mask_keydown = 0x010000; + break; + case BTTV_WINFAST2000: + ir_codes = winfast_codes; + ir->mask_keycode = 0x8f8; + break; + case BTTV_PXELVWPLTVPAK: + ir_codes = ir_codes_pixelview; + ir->mask_keycode = 0x003e00; + ir->mask_keyup = 0x010000; + ir->polling = 50; // ms + break; + case BTTV_PV_BT878P_9B: + case BTTV_PV_BT878P_PLUS: + ir_codes = ir_codes_pixelview; + ir->mask_keycode = 0x001f00; + ir->mask_keyup = 0x008000; + ir->polling = 50; // ms + break; + case BTTV_MAGICTVIEW061: + case BTTV_MAGICTVIEW063: + ir_codes = ir_codes_empty; + ir->mask_keycode = 0x0008e000; + ir->mask_keydown = 0x00200000; + break; + } + if (NULL == ir_codes) { + kfree(ir); + return -ENODEV; + } + + /* init hardware-specific stuff */ + bttv_gpio_inout(sub->core, ir->mask_keycode | ir->mask_keydown, 0); + ir->sub = sub; + + /* init input device */ + snprintf(ir->name, sizeof(ir->name), "bttv IR (card=%d)", + sub->core->type); + snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", + pci_name(sub->core->pci)); + + ir_input_init(&ir->input, &ir->ir, ir_type, ir_codes); + ir->input.name = ir->name; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + ir->input.phys = ir->phys; + ir->input.id.bustype = BUS_PCI; + ir->input.id.version = 1; + if (sub->core->pci->subsystem_vendor) { + ir->input.id.vendor = sub->core->pci->subsystem_vendor; + ir->input.id.product = sub->core->pci->subsystem_device; + } else { + ir->input.id.vendor = sub->core->pci->vendor; + ir->input.id.product = sub->core->pci->device; + } +#endif + + if (ir->polling) { + INIT_WORK(&ir->work, ir_work, ir); + init_timer(&ir->timer); + ir->timer.function = ir_timer; + ir->timer.data = (unsigned long)ir; + schedule_work(&ir->work); + } + + /* all done */ + dev_set_drvdata(dev,ir); + input_register_device(&ir->input); + printk(DEVNAME ": %s detected at %s\n",ir->input.name,ir->input.phys); + + return 0; +} + +static int ir_remove(struct device *dev) +{ + struct IR *ir = dev_get_drvdata(dev); + + if (ir->polling) { + del_timer(&ir->timer); + flush_scheduled_work(); + } + + input_unregister_device(&ir->input); + kfree(ir); + return 0; +} + +/* ---------------------------------------------------------------------- */ + +MODULE_AUTHOR("Gerd Knorr, Pavel Machek"); +MODULE_DESCRIPTION("input driver for bt8x8 gpio IR remote controls"); +MODULE_LICENSE("GPL"); + +static int ir_init(void) +{ + return bttv_sub_register(&driver, "remote"); +} + +static void ir_fini(void) +{ + bttv_sub_unregister(&driver); +} + +module_init(ir_init); +module_exit(ir_fini); + + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/ir-kbd-i2c.c b/linux/drivers/media/video/ir-kbd-i2c.c new file mode 100644 index 000000000..6d40f3a96 --- /dev/null +++ b/linux/drivers/media/video/ir-kbd-i2c.c @@ -0,0 +1,373 @@ +/* + * keyboard input driver for i2c IR remote controls + * + * Copyright (c) 2000-2003 Gerd Knorr <kraxel@bytesex.org> + * modified for PixelView (BT878P+W/FM) by + * Michal Kochanowicz <mkochano@pld.org.pl> + * Christoph Bartelmus <lirc@bartelmus.de> + * modified for KNC ONE TV Station/Anubis Typhoon TView Tuner by + * Ulrich Mueller <ulrich.mueller42@web.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> + +#include <asm/semaphore.h> + +#include <media/ir-common.h> + +struct IR; +struct IR { + struct i2c_client c; + struct input_dev input; + struct ir_input_state ir; + + struct work_struct work; + struct timer_list timer; + char phys[32]; + int (*get_key)(struct IR*, u32*, u32*); +}; + +/* ----------------------------------------------------------------------- */ +/* insmod parameters */ + +static int debug; +module_param(debug, int, 0644); /* debug level (0,1,2) */ + +#define DEVNAME "ir-kbd-i2c" +#define dprintk(level, fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG DEVNAME ": " fmt , ## arg) + +/* ----------------------------------------------------------------------- */ + +static inline int reverse(int data, int bits) +{ + int i,c; + + for (c=0,i=0; i<bits; i++) { + c |= (((data & (1<<i)) ? 1:0)) << (bits-1-i); + } + return c; +} + +static int get_key_haup(struct IR *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char buf[3]; + int start, toggle, dev, code; + + /* poll IR chip */ + if (3 != i2c_master_recv(&ir->c,buf,3)) + return -EIO; + + /* split rc5 data block ... */ + start = (buf[0] >> 6) & 3; + toggle = (buf[0] >> 5) & 1; + dev = buf[0] & 0x1f; + code = (buf[1] >> 2) & 0x3f; + + if (3 != start) + /* no key pressed */ + return 0; + dprintk(1,"ir hauppauge (rc5): s%d t%d dev=%d code=%d\n", + start, toggle, dev, code); + + /* return key */ + *ir_key = code; + *ir_raw = (start << 12) | (toggle << 11) | (dev << 6) | code; + return 1; +} + +static int get_key_pixelview(struct IR *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char b; + + /* poll IR chip */ + if (1 != i2c_master_recv(&ir->c,&b,1)) { + dprintk(1,"read error\n"); + return -EIO; + } + *ir_key = b; + *ir_raw = b; + return 1; +} + +static int get_key_pv951(struct IR *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char b; + + /* poll IR chip */ + if (1 != i2c_master_recv(&ir->c,&b,1)) { + dprintk(1,"read error\n"); + return -EIO; + } + + /* ignore 0xaa */ + if (b==0xaa) + return 0; + dprintk(2,"key %02x\n", b); + + *ir_key = b; + *ir_raw = b; + return 1; +} + +static int get_key_knc1(struct IR *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char b; + + /* poll IR chip */ + if (1 != i2c_master_recv(&ir->c,&b,1)) { + dprintk(1,"read error\n"); + return -EIO; + } + + /* it seems that 0xFE indicates that a button is still hold + down, while 0xFF indicates that no button is hold + down. 0xFE sequences are sometimes interrupted by 0xFF */ + + dprintk(2,"key %02x\n", b); + + if (b == 0xFF) + return 0; + + if (b == 0xFE) + /* keep old data */ + return 1; + + *ir_key = b; + *ir_raw = b; + return 1; +} + +/* ----------------------------------------------------------------------- */ + +static void ir_key_poll(struct IR *ir) +{ + u32 ir_key, ir_raw; + int rc; + + dprintk(2,"ir_poll_key\n"); + rc = ir->get_key(ir, &ir_key, &ir_raw); + if (rc < 0) { + dprintk(2,"error\n"); + return; + } + + if (0 == rc) { + ir_input_nokey(&ir->input,&ir->ir); + } else { + ir_input_keydown(&ir->input,&ir->ir, ir_key, ir_raw); + } +} + +static void ir_timer(unsigned long data) +{ + struct IR *ir = (struct IR*)data; + schedule_work(&ir->work); +} + +static void ir_work(void *data) +{ + struct IR *ir = data; + ir_key_poll(ir); + mod_timer(&ir->timer, jiffies+HZ/10); +} + +/* ----------------------------------------------------------------------- */ + +static int ir_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind); +static int ir_detach(struct i2c_client *client); +static int ir_probe(struct i2c_adapter *adap); + +static struct i2c_driver driver = { + .name = "ir remote kbd driver", + .id = I2C_DRIVERID_EXP3, /* FIXME */ + .flags = I2C_DF_NOTIFY, + .attach_adapter = ir_probe, + .detach_client = ir_detach, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("unset"), + .driver = &driver +}; + +static int ir_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + IR_KEYTAB_TYPE *ir_codes = NULL; + char *name; + int ir_type; + struct IR *ir; + + if (NULL == (ir = kmalloc(sizeof(struct IR),GFP_KERNEL))) + return -ENOMEM; + memset(ir,0,sizeof(*ir)); + ir->c = client_template; + + i2c_set_clientdata(&ir->c, ir); + ir->c.adapter = adap; + ir->c.addr = addr; + + switch(addr) { + case 0x64: + name = "Pixelview"; + ir->get_key = get_key_pixelview; + ir_type = IR_TYPE_OTHER; + ir_codes = ir_codes_empty; + break; + case 0x4b: + name = "PV951"; + ir->get_key = get_key_pv951; + ir_type = IR_TYPE_OTHER; + ir_codes = ir_codes_empty; + break; + case 0x18: + case 0x1a: + name = "Hauppauge"; + ir->get_key = get_key_haup; + ir_type = IR_TYPE_RC5; + ir_codes = ir_codes_rc5_tv; + break; + case 0x30: + name = "KNC One"; + ir->get_key = get_key_knc1; + ir_type = IR_TYPE_OTHER; + ir_codes = ir_codes_empty; + break; + default: + /* shouldn't happen */ + printk(DEVNAME ": Huh? unknown i2c address (0x%02x)?\n",addr); + kfree(ir); + return -1; + } + + /* register i2c device */ + i2c_attach_client(&ir->c); + snprintf(ir->c.name, sizeof(ir->c.name), "i2c IR (%s)", name); + snprintf(ir->phys, sizeof(ir->phys), "%s/%s/ir0", + ir->c.adapter->dev.bus_id, + ir->c.dev.bus_id); + + /* init + register input device */ + ir_input_init(&ir->input,&ir->ir,ir_type,ir_codes); + ir->input.id.bustype = BUS_I2C; + ir->input.name = ir->c.name; + ir->input.phys = ir->phys; + input_register_device(&ir->input); + printk(DEVNAME ": %s detected at %s\n",ir->input.name,ir->input.phys); + + /* start polling via eventd */ + INIT_WORK(&ir->work, ir_work, ir); + init_timer(&ir->timer); + ir->timer.function = ir_timer; + ir->timer.data = (unsigned long)ir; + schedule_work(&ir->work); + + return 0; +} + +static int ir_detach(struct i2c_client *client) +{ + struct IR *ir = i2c_get_clientdata(client); + + /* kill outstanding polls */ + del_timer(&ir->timer); + flush_scheduled_work(); + + /* unregister devices */ + input_unregister_device(&ir->input); + i2c_detach_client(&ir->c); + + /* free memory */ + kfree(ir); + return 0; +} + +static int ir_probe(struct i2c_adapter *adap) +{ + + /* The external IR receiver is at i2c address 0x34 (0x35 for + reads). Future Hauppauge cards will have an internal + receiver at 0x30 (0x31 for reads). In theory, both can be + fitted, and Hauppauge suggest an external overrides an + internal. + + That's why we probe 0x1a (~0x34) first. CB + */ + + static const int probe[] = { 0x1a, 0x18, 0x4b, 0x64, 0x30, -1}; + struct i2c_client c; char buf; int i,rc; + + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) { + memset(&c,0,sizeof(c)); + c.adapter = adap; + for (i = 0; -1 != probe[i]; i++) { + c.addr = probe[i]; + rc = i2c_master_recv(&c,&buf,1); + dprintk(1,"probe 0x%02x @ %s: %s\n", + probe[i], adap->name, + (1 == rc) ? "yes" : "no"); + if (1 == rc) { + ir_attach(adap,probe[i],0,0); + break; + } + } + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, Ulrich Mueller"); +MODULE_DESCRIPTION("input driver for i2c IR remote controls"); +MODULE_LICENSE("GPL"); + +static int ir_init(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void ir_fini(void) +{ + i2c_del_driver(&driver); +} + +module_init(ir_init); +module_exit(ir_fini); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/msp3400-driver.c b/linux/drivers/media/video/msp3400-driver.c new file mode 100644 index 000000000..f7db546d2 --- /dev/null +++ b/linux/drivers/media/video/msp3400-driver.c @@ -0,0 +1,1618 @@ +/* + * programming the msp34* sound processor family + * + * (c) 1997-2001 Gerd Knorr <kraxel@bytesex.org> + * + * what works and what doesn't: + * + * AM-Mono + * Support for Hauppauge cards added (decoding handled by tuner) added by + * Frederic Crozat <fcrozat@mail.dotcom.fr> + * + * FM-Mono + * should work. The stereo modes are backward compatible to FM-mono, + * therefore FM-Mono should be allways available. + * + * FM-Stereo (B/G, used in germany) + * should work, with autodetect + * + * FM-Stereo (satellite) + * should work, no autodetect (i.e. default is mono, but you can + * switch to stereo -- untested) + * + * NICAM (B/G, L , used in UK, Scandinavia, Spain and France) + * should work, with autodetect. Support for NICAM was added by + * Pekka Pietikainen <pp@netppl.fi> + * + * + * TODO: + * - better SAT support + * + * + * 980623 Thomas Sailer (sailer@ife.ee.ethz.ch) + * using soundcore instead of OSS + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <asm/semaphore.h> +#include <asm/pgtable.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "audiochip.h" +#include "i2c-compat.h" +#else +#include <media/audiochip.h> +#endif +#include "msp3400.h" + +/* insmod parameters */ +static int debug = 0; /* debug output */ +static int once = 0; /* no continous stereo monitoring */ +static int amsound = 0; /* hard-wire AM sound at 6.5 Hz (france), + the autoscan seems work well only with FM... */ +static int simple = -1; /* use short programming (>= msp3410 only) */ +static int dolby = 0; + +#define DFP_COUNT 0x41 +static const int bl_dfp[] = { + 0x00, 0x01, 0x02, 0x03, 0x06, 0x08, 0x09, 0x0a, + 0x0b, 0x0d, 0x0e, 0x10 +}; + +struct msp3400c { + int rev1,rev2; + + int simple; + int mode; + int norm; + int stereo; + int nicam_on; + int acb; + int main, second; /* sound carrier */ + int input; + + int muted; + int left, right; /* volume */ + int bass, treble; + + /* shadow register set */ + int dfp_regs[DFP_COUNT]; + + /* thread */ + pid_t tpid; + struct completion texit; + wait_queue_head_t wq; + + int active:1; + int restart:1; + int rmmod:1; + + int watch_stereo; + struct timer_list wake_stereo; +}; + +#define HAVE_NICAM(msp) (((msp->rev2>>8) & 0xff) != 00) +#define HAVE_SIMPLE(msp) ((msp->rev1 & 0xff) >= 'D'-'@') +#define HAVE_RADIO(msp) ((msp->rev1 & 0xff) >= 'G'-'@') + +#define VIDEO_MODE_RADIO 16 /* norm magic for radio mode */ + +/* ---------------------------------------------------------------------- */ + +#define dprintk if (debug >= 1) printk +#define d2printk if (debug >= 2) printk + +MODULE_PARM(once,"i"); +MODULE_PARM(debug,"i"); +MODULE_PARM(simple,"i"); +MODULE_PARM(amsound,"i"); +MODULE_PARM(dolby,"i"); + +MODULE_DESCRIPTION("device driver for msp34xx TV sound processor"); +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("Dual BSD/GPL"); /* FreeBSD uses this too */ + +/* ---------------------------------------------------------------------- */ + +#define I2C_MSP3400C 0x80 +#define I2C_MSP3400C_ALT 0x88 + +#define I2C_MSP3400C_DEM 0x10 +#define I2C_MSP3400C_DFP 0x12 + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + I2C_MSP3400C >> 1, + I2C_MSP3400C_ALT >> 1, + I2C_CLIENT_END +}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END,I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +/* ----------------------------------------------------------------------- */ +/* functions for talking to the MSP3400C Sound processor */ + +#ifndef I2C_M_IGNORE_NAK +# define I2C_M_IGNORE_NAK 0x1000 +#endif + +static int msp3400c_reset(struct i2c_client *client) +{ + /* reset and read revision code */ + static char reset_off[3] = { 0x00, 0x80, 0x00 }; + static char reset_on[3] = { 0x00, 0x00, 0x00 }; + static char write[3] = { I2C_MSP3400C_DFP + 1, 0x00, 0x1e }; + char read[2]; + struct i2c_msg reset[2] = { + { client->addr, I2C_M_IGNORE_NAK, 3, reset_off }, + { client->addr, I2C_M_IGNORE_NAK, 3, reset_on }, + }; + struct i2c_msg test[2] = { + { client->addr, 0, 3, write }, + { client->addr, I2C_M_RD, 2, read }, + }; + + if ( (1 != i2c_transfer(client->adapter,&reset[0],1)) || + (1 != i2c_transfer(client->adapter,&reset[1],1)) || + (2 != i2c_transfer(client->adapter,test,2)) ) { + printk(KERN_ERR "msp3400: chip reset failed\n"); + return -1; + } + return 0; +} + +static int +msp3400c_read(struct i2c_client *client, int dev, int addr) +{ + int err; + + unsigned char write[3]; + unsigned char read[2]; + struct i2c_msg msgs[2] = { + { client->addr, 0, 3, write }, + { client->addr, I2C_M_RD, 2, read } + }; + write[0] = dev+1; + write[1] = addr >> 8; + write[2] = addr & 0xff; + + for (err = 0; err < 3;) { + if (2 == i2c_transfer(client->adapter,msgs,2)) + break; + err++; + printk(KERN_WARNING "msp34xx: I/O error #%d (read 0x%02x/0x%02x)\n", + err, dev, addr); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + } + if (3 == err) { + printk(KERN_WARNING "msp34xx: giving up, reseting chip. Sound will go off, sorry folks :-|\n"); + msp3400c_reset(client); + return -1; + } + return read[0] << 8 | read[1]; +} + +static int +msp3400c_write(struct i2c_client *client, int dev, int addr, int val) +{ + int err; + unsigned char buffer[5]; + + buffer[0] = dev; + buffer[1] = addr >> 8; + buffer[2] = addr & 0xff; + buffer[3] = val >> 8; + buffer[4] = val & 0xff; + + for (err = 0; err < 3;) { + if (5 == i2c_master_send(client, buffer, 5)) + break; + err++; + printk(KERN_WARNING "msp34xx: I/O error #%d (write 0x%02x/0x%02x)\n", + err, dev, addr); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + } + if (3 == err) { + printk(KERN_WARNING "msp34xx: giving up, reseting chip. Sound will go off, sorry folks :-|\n"); + msp3400c_reset(client); + return -1; + } + return 0; +} + +/* ------------------------------------------------------------------------ */ + +/* This macro is allowed for *constants* only, gcc must calculate it + at compile time. Remember -- no floats in kernel mode */ +#define MSP_CARRIER(freq) ((int)((float)(freq/18.432)*(1<<24))) + +#define MSP_MODE_AM_DETECT 0 +#define MSP_MODE_FM_RADIO 2 +#define MSP_MODE_FM_TERRA 3 +#define MSP_MODE_FM_SAT 4 +#define MSP_MODE_FM_NICAM1 5 +#define MSP_MODE_FM_NICAM2 6 +#define MSP_MODE_AM_NICAM 7 +#define MSP_MODE_BTSC 8 +#define MSP_MODE_EXTERN 9 + +static struct MSP_INIT_DATA_DEM { + int fir1[6]; + int fir2[6]; + int cdo1; + int cdo2; + int ad_cv; + int mode_reg; + int dfp_src; + int dfp_matrix; +} msp_init_data[] = { + /* AM (for carrier detect / msp3400) */ + { { 75, 19, 36, 35, 39, 40 }, { 75, 19, 36, 35, 39, 40 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0500, 0x0020, 0x3000}, + + /* AM (for carrier detect / msp3410) */ + { { -1, -1, -8, 2, 59, 126 }, { -1, -1, -8, 2, 59, 126 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0100, 0x0020, 0x3000}, + + /* FM Radio */ + { { -8, -8, 4, 6, 78, 107 }, { -8, -8, 4, 6, 78, 107 }, + MSP_CARRIER(10.7), MSP_CARRIER(10.7), + 0x00d0, 0x0480, 0x0020, 0x3000 }, + + /* Terrestial FM-mono + FM-stereo */ + { { 3, 18, 27, 48, 66, 72 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0480, 0x0030, 0x3000}, + + /* Sat FM-mono */ + { { 1, 9, 14, 24, 33, 37 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(6.5), MSP_CARRIER(6.5), + 0x00c6, 0x0480, 0x0000, 0x3000}, + + /* NICAM/FM -- B/G (5.5/5.85), D/K (6.5/5.85) */ + { { -2, -8, -10, 10, 50, 86 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0040, 0x0120, 0x3000}, + + /* NICAM/FM -- I (6.0/6.552) */ + { { 2, 4, -6, -4, 40, 94 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(6.0), MSP_CARRIER(6.0), + 0x00d0, 0x0040, 0x0120, 0x3000}, + + /* NICAM/AM -- L (6.5/5.85) */ + { { -2, -8, -10, 10, 50, 86 }, { -4, -12, -9, 23, 79, 126 }, + MSP_CARRIER(6.5), MSP_CARRIER(6.5), + 0x00c6, 0x0140, 0x0120, 0x7c03}, +}; + +struct CARRIER_DETECT { + int cdo; + char *name; +}; + +static struct CARRIER_DETECT carrier_detect_main[] = { + /* main carrier */ + { MSP_CARRIER(4.5), "4.5 NTSC" }, + { MSP_CARRIER(5.5), "5.5 PAL B/G" }, + { MSP_CARRIER(6.0), "6.0 PAL I" }, + { MSP_CARRIER(6.5), "6.5 PAL D/K + SAT + SECAM" } +}; + +static struct CARRIER_DETECT carrier_detect_55[] = { + /* PAL B/G */ + { MSP_CARRIER(5.7421875), "5.742 PAL B/G FM-stereo" }, + { MSP_CARRIER(5.85), "5.85 PAL B/G NICAM" } +}; + +static struct CARRIER_DETECT carrier_detect_65[] = { + /* PAL SAT / SECAM */ + { MSP_CARRIER(5.85), "5.85 PAL D/K + SECAM NICAM" }, + { MSP_CARRIER(6.2578125), "6.25 PAL D/K1 FM-stereo" }, + { MSP_CARRIER(6.7421875), "6.74 PAL D/K2 FM-stereo" }, + { MSP_CARRIER(7.02), "7.02 PAL SAT FM-stereo s/b" }, + { MSP_CARRIER(7.20), "7.20 PAL SAT FM-stereo s" }, + { MSP_CARRIER(7.38), "7.38 PAL SAT FM-stereo b" }, +}; + +#define CARRIER_COUNT(x) (sizeof(x)/sizeof(struct CARRIER_DETECT)) + +/* ----------------------------------------------------------------------- */ + +#define SCART_MASK 0 +#define SCART_IN1 1 +#define SCART_IN2 2 +#define SCART_IN1_DA 3 +#define SCART_IN2_DA 4 +#define SCART_IN3 5 +#define SCART_IN4 6 +#define SCART_MONO 7 +#define SCART_MUTE 8 + +static int scarts[3][9] = { + /* MASK IN1 IN2 IN1_DA IN2_DA IN3 IN4 MONO MUTE */ + { 0x0320, 0x0000, 0x0200, -1, -1, 0x0300, 0x0020, 0x0100, 0x0320 }, + { 0x0c40, 0x0440, 0x0400, 0x0c00, 0x0040, 0x0000, 0x0840, 0x0800, 0x0c40 }, + { 0x3080, 0x1000, 0x1080, 0x0000, 0x0080, 0x2080, 0x3080, 0x2000, 0x3000 }, +}; + +static char *scart_names[] = { + "mask", "in1", "in2", "in1 da", "in2 da", "in3", "in4", "mono", "mute" +}; + +static void +msp3400c_set_scart(struct i2c_client *client, int in, int out) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + if (-1 == scarts[out][in]) + return; + + dprintk(KERN_DEBUG + "msp34xx: scart switch: %s => %d\n",scart_names[in],out); + msp->acb &= ~scarts[out][SCART_MASK]; + msp->acb |= scarts[out][in]; + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0013, msp->acb); +} + +/* ------------------------------------------------------------------------ */ + +static void msp3400c_setcarrier(struct i2c_client *client, int cdo1, int cdo2) +{ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0093, cdo1 & 0xfff); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x009b, cdo1 >> 12); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00a3, cdo2 & 0xfff); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00ab, cdo2 >> 12); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/ +} + +static void msp3400c_setvolume(struct i2c_client *client, + int muted, int left, int right) +{ + int vol = 0,val = 0,balance = 0; + + if (!muted) { + vol = (left > right) ? left : right; + val = (vol * 0x73 / 65535) << 8; + } + if (vol > 0) { + balance = ((right-left) * 127) / vol; + } + + dprintk(KERN_DEBUG + "msp34xx: setvolume: mute=%s %d:%d v=0x%02x b=0x%02x\n", + muted ? "on" : "off", left, right, val>>8, balance); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0000, val); /* loudspeaker */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0006, val); /* headphones */ + /* scart - on/off only */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0007, val ? 0x4000 : 0); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0001, balance << 8); +} + +static void msp3400c_setbass(struct i2c_client *client, int bass) +{ + int val = ((bass-32768) * 0x60 / 65535) << 8; + + dprintk(KERN_DEBUG "msp34xx: setbass: %d 0x%02x\n",bass, val>>8); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0002, val); /* loudspeaker */ +} + +static void msp3400c_settreble(struct i2c_client *client, int treble) +{ + int val = ((treble-32768) * 0x60 / 65535) << 8; + + dprintk(KERN_DEBUG "msp34xx: settreble: %d 0x%02x\n",treble, val>>8); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0003, val); /* loudspeaker */ +} + +static void msp3400c_setmode(struct i2c_client *client, int type) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + int i; + + dprintk(KERN_DEBUG "msp3400: setmode: %d\n",type); + msp->mode = type; + msp->stereo = VIDEO_SOUND_MONO; + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00bb, /* ad_cv */ + msp_init_data[type].ad_cv); + + for (i = 5; i >= 0; i--) /* fir 1 */ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0001, + msp_init_data[type].fir1[i]); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0004); /* fir 2 */ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0040); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0000); + for (i = 5; i >= 0; i--) + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, + msp_init_data[type].fir2[i]); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0083, /* MODE_REG */ + msp_init_data[type].mode_reg); + + msp3400c_setcarrier(client, msp_init_data[type].cdo1, + msp_init_data[type].cdo2); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/ + + if (dolby) { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008, + 0x0520); /* I2S1 */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009, + 0x0620); /* I2S2 */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b, + msp_init_data[type].dfp_src); + } else { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008, + msp_init_data[type].dfp_src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009, + msp_init_data[type].dfp_src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b, + msp_init_data[type].dfp_src); + } + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a, + msp_init_data[type].dfp_src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, + msp_init_data[type].dfp_matrix); + + if (HAVE_NICAM(msp)) { + /* nicam prescale */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0010, 0x5a00); /* was: 0x3000 */ + } +} + +/* turn on/off nicam + stereo */ +static void msp3400c_setstereo(struct i2c_client *client, int mode) +{ + static char *strmode[16] = { +#if __GNUC__ >= 3 + [ 0 ... 15 ] = "invalid", +#endif + [ VIDEO_SOUND_MONO ] = "mono", + [ VIDEO_SOUND_STEREO ] = "stereo", + [ VIDEO_SOUND_LANG1 ] = "lang1", + [ VIDEO_SOUND_LANG2 ] = "lang2", + }; + struct msp3400c *msp = i2c_get_clientdata(client); + int nicam=0; /* channel source: FM/AM or nicam */ + int src=0; + + /* switch demodulator */ + switch (msp->mode) { + case MSP_MODE_FM_TERRA: + dprintk(KERN_DEBUG "msp3400: FM setstereo: %s\n",strmode[mode]); + msp3400c_setcarrier(client,msp->second,msp->main); + switch (mode) { + case VIDEO_SOUND_STEREO: + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3001); + break; + case VIDEO_SOUND_MONO: + case VIDEO_SOUND_LANG1: + case VIDEO_SOUND_LANG2: + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3000); + break; + } + break; + case MSP_MODE_FM_SAT: + dprintk(KERN_DEBUG "msp3400: SAT setstereo: %s\n",strmode[mode]); + switch (mode) { + case VIDEO_SOUND_MONO: + msp3400c_setcarrier(client, MSP_CARRIER(6.5), MSP_CARRIER(6.5)); + break; + case VIDEO_SOUND_STEREO: + msp3400c_setcarrier(client, MSP_CARRIER(7.2), MSP_CARRIER(7.02)); + break; + case VIDEO_SOUND_LANG1: + msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02)); + break; + case VIDEO_SOUND_LANG2: + msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02)); + break; + } + break; + case MSP_MODE_FM_NICAM1: + case MSP_MODE_FM_NICAM2: + case MSP_MODE_AM_NICAM: + dprintk(KERN_DEBUG "msp3400: NICAM setstereo: %s\n",strmode[mode]); + msp3400c_setcarrier(client,msp->second,msp->main); + if (msp->nicam_on) + nicam=0x0100; + break; + case MSP_MODE_BTSC: + dprintk(KERN_DEBUG "msp3400: BTSC setstereo: %s\n",strmode[mode]); + nicam=0x0300; + break; + case MSP_MODE_EXTERN: + dprintk(KERN_DEBUG "msp3400: extern setstereo: %s\n",strmode[mode]); + nicam = 0x0200; + break; + case MSP_MODE_FM_RADIO: + dprintk(KERN_DEBUG "msp3400: FM-Radio setstereo: %s\n",strmode[mode]); + break; + default: + dprintk(KERN_DEBUG "msp3400: mono setstereo\n"); + return; + } + + /* switch audio */ + switch (mode) { + case VIDEO_SOUND_STEREO: + src = 0x0020 | nicam; +#if 0 + /* spatial effect */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0005,0x4000); +#endif + break; + case VIDEO_SOUND_MONO: + if (msp->mode == MSP_MODE_AM_NICAM) { + dprintk("msp3400: switching to AM mono\n"); + /* AM mono decoding is handled by tuner, not MSP chip */ + /* SCART switching control register */ + msp3400c_set_scart(client,SCART_MONO,0); + src = 0x0200; + break; + } + case VIDEO_SOUND_LANG1: + src = 0x0000 | nicam; + break; + case VIDEO_SOUND_LANG2: + src = 0x0010 | nicam; + break; + } + dprintk(KERN_DEBUG + "msp3400: setstereo final source/matrix = 0x%x\n", src); + + if (dolby) { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,0x0520); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,0x0620); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,src); + } else { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,src); + } +} + +static void +msp3400c_print_mode(struct msp3400c *msp) +{ + if (msp->main == msp->second) { + printk(KERN_DEBUG "msp3400: mono sound carrier: %d.%03d MHz\n", + msp->main/910000,(msp->main/910)%1000); + } else { + printk(KERN_DEBUG "msp3400: main sound carrier: %d.%03d MHz\n", + msp->main/910000,(msp->main/910)%1000); + } + if (msp->mode == MSP_MODE_FM_NICAM1 || + msp->mode == MSP_MODE_FM_NICAM2) + printk(KERN_DEBUG "msp3400: NICAM/FM carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + if (msp->mode == MSP_MODE_AM_NICAM) + printk(KERN_DEBUG "msp3400: NICAM/AM carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + if (msp->mode == MSP_MODE_FM_TERRA && + msp->main != msp->second) { + printk(KERN_DEBUG "msp3400: FM-stereo carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + } +} + +static void +msp3400c_restore_dfp(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + int i; + + for (i = 0; i < DFP_COUNT; i++) { + if (-1 == msp->dfp_regs[i]) + continue; + msp3400c_write(client,I2C_MSP3400C_DFP, i, msp->dfp_regs[i]); + } +} + +/* ----------------------------------------------------------------------- */ + +struct REGISTER_DUMP { + int addr; + char *name; +}; + +struct REGISTER_DUMP d1[] = { + { 0x007e, "autodetect" }, + { 0x0023, "C_AD_BITS " }, + { 0x0038, "ADD_BITS " }, + { 0x003e, "CIB_BITS " }, + { 0x0057, "ERROR_RATE" }, +}; + +static int +autodetect_stereo(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + int val; + int newstereo = msp->stereo; + int newnicam = msp->nicam_on; + int update = 0; + + switch (msp->mode) { + case MSP_MODE_FM_TERRA: + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x18); + if (val > 32767) + val -= 65536; + dprintk(KERN_DEBUG + "msp34xx: stereo detect register: %d\n",val); + if (val > 4096) { + newstereo = VIDEO_SOUND_STEREO | VIDEO_SOUND_MONO; + } else if (val < -4096) { + newstereo = VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } else { + newstereo = VIDEO_SOUND_MONO; + } + newnicam = 0; + break; + case MSP_MODE_FM_NICAM1: + case MSP_MODE_FM_NICAM2: + case MSP_MODE_AM_NICAM: + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x23); + dprintk(KERN_DEBUG + "msp34xx: nicam sync=%d, mode=%d\n", + val & 1, (val & 0x1e) >> 1); + + if (val & 1) { + /* nicam synced */ + switch ((val & 0x1e) >> 1) { + case 0: + case 8: + newstereo = VIDEO_SOUND_STEREO; + break; + case 1: + case 9: + newstereo = VIDEO_SOUND_MONO + | VIDEO_SOUND_LANG1; + break; + case 2: + case 10: + newstereo = VIDEO_SOUND_MONO + | VIDEO_SOUND_LANG1 + | VIDEO_SOUND_LANG2; + break; + default: + newstereo = VIDEO_SOUND_MONO; + break; + } + newnicam=1; + } else { + newnicam = 0; + newstereo = VIDEO_SOUND_MONO; + } + break; + case MSP_MODE_BTSC: + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x200); + dprintk(KERN_DEBUG + "msp3410: status=0x%x (pri=%s, sec=%s, %s%s%s)\n", + val, + (val & 0x0002) ? "no" : "yes", + (val & 0x0004) ? "no" : "yes", + (val & 0x0040) ? "stereo" : "mono", + (val & 0x0080) ? ", nicam 2nd mono" : "", + (val & 0x0100) ? ", bilingual/SAP" : ""); + newstereo = VIDEO_SOUND_MONO; + if (val & 0x0040) newstereo |= VIDEO_SOUND_STEREO; + if (val & 0x0100) newstereo |= VIDEO_SOUND_LANG1; + break; + } + if (newstereo != msp->stereo) { + update = 1; + dprintk(KERN_DEBUG "msp34xx: watch: stereo %d => %d\n", + msp->stereo,newstereo); + msp->stereo = newstereo; + } + if (newnicam != msp->nicam_on) { + update = 1; + dprintk(KERN_DEBUG "msp34xx: watch: nicam %d => %d\n", + msp->nicam_on,newnicam); + msp->nicam_on = newnicam; + } + return update; +} + +/* + * A kernel thread for msp3400 control -- we don't want to block the + * in the ioctl while doing the sound carrier & stereo detect + */ + +static int msp34xx_sleep(struct msp3400c *msp, int timeout) +{ + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&msp->wq, &wait); + if (!msp->rmmod) { + set_current_state(TASK_INTERRUPTIBLE); + if (timeout < 0) + schedule(); + else + schedule_timeout(timeout); + } + remove_wait_queue(&msp->wq, &wait); + return msp->rmmod || signal_pending(current); +} + +static void msp3400c_stereo_wake(unsigned long data) +{ + struct msp3400c *msp = (struct msp3400c*)data; /* XXX alpha ??? */ + + wake_up_interruptible(&msp->wq); +} + +/* stereo/multilang monitoring */ +static void watch_stereo(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + if (autodetect_stereo(client)) { + if (msp->stereo & VIDEO_SOUND_STEREO) + msp3400c_setstereo(client,VIDEO_SOUND_STEREO); + else if (msp->stereo & VIDEO_SOUND_LANG1) + msp3400c_setstereo(client,VIDEO_SOUND_LANG1); + else if (msp->stereo & VIDEO_SOUND_LANG2) + msp3400c_setstereo(client,VIDEO_SOUND_LANG2); + else + msp3400c_setstereo(client,VIDEO_SOUND_MONO); + } + if (once) + msp->watch_stereo = 0; + if (msp->watch_stereo) + mod_timer(&msp->wake_stereo, jiffies+5*HZ); +} + +static int msp3400c_thread(void *data) +{ + struct i2c_client *client = data; + struct msp3400c *msp = i2c_get_clientdata(client); + + struct CARRIER_DETECT *cd; + int count, max1,max2,val1,val2, val,this; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,61) + lock_kernel(); + daemonize(); + sigfillset(¤t->blocked); + strcpy(current->comm,"msp3400"); + unlock_kernel(); +#else + daemonize("msp3400"); + allow_signal(SIGTERM); +#endif + printk("msp3400: daemon started\n"); + + for (;;) { + d2printk("msp3400: thread: sleep\n"); + if (msp34xx_sleep(msp,-1)) + goto done; + + d2printk("msp3400: thread: wakeup\n"); + msp->active = 1; + + if (msp->watch_stereo) { + watch_stereo(client); + msp->active = 0; + continue; + } + + /* some time for the tuner to sync */ + if (msp34xx_sleep(msp,HZ/5)) + goto done; + + restart: + if (VIDEO_MODE_RADIO == msp->norm || + MSP_MODE_EXTERN == msp->mode) { + /* no carrier scan, just unmute */ + printk("msp3400: thread: no carrier scan\n"); + msp3400c_setvolume(client, msp->muted, + msp->left, msp->right); + continue; + } + msp->restart = 0; + msp3400c_setvolume(client, msp->muted, 0, 0); + msp3400c_setmode(client, MSP_MODE_AM_DETECT /* +1 */ ); + val1 = val2 = 0; + max1 = max2 = -1; + del_timer(&msp->wake_stereo); + msp->watch_stereo = 0; + + /* carrier detect pass #1 -- main carrier */ + cd = carrier_detect_main; count = CARRIER_COUNT(carrier_detect_main); + + if (amsound && (msp->norm == VIDEO_MODE_SECAM)) { + /* autodetect doesn't work well with AM ... */ + max1 = 3; + count = 0; + dprintk("msp3400: AM sound override\n"); + } + + for (this = 0; this < count; this++) { + msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo); + + if (msp34xx_sleep(msp,HZ/10)) + goto done; + if (msp->restart) + msp->restart = 0; + + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b); + if (val > 32767) + val -= 65536; + if (val1 < val) + val1 = val, max1 = this; + dprintk("msp3400: carrier1 val: %5d / %s\n", val,cd[this].name); + } + + /* carrier detect pass #2 -- second (stereo) carrier */ + switch (max1) { + case 1: /* 5.5 */ + cd = carrier_detect_55; count = CARRIER_COUNT(carrier_detect_55); + break; + case 3: /* 6.5 */ + cd = carrier_detect_65; count = CARRIER_COUNT(carrier_detect_65); + break; + case 0: /* 4.5 */ + case 2: /* 6.0 */ + default: + cd = NULL; count = 0; + break; + } + + if (amsound && (msp->norm == VIDEO_MODE_SECAM)) { + /* autodetect doesn't work well with AM ... */ + cd = NULL; count = 0; max2 = 0; + } + for (this = 0; this < count; this++) { + msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo); + + if (msp34xx_sleep(msp,HZ/10)) + goto done; + if (msp->restart) + goto restart; + + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b); + if (val > 32767) + val -= 65536; + if (val2 < val) + val2 = val, max2 = this; + dprintk("msp3400: carrier2 val: %5d / %s\n", val,cd[this].name); + } + + /* programm the msp3400 according to the results */ + msp->main = carrier_detect_main[max1].cdo; + switch (max1) { + case 1: /* 5.5 */ + if (max2 == 0) { + /* B/G FM-stereo */ + msp->second = carrier_detect_55[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + msp->watch_stereo = 1; + } else if (max2 == 1 && HAVE_NICAM(msp)) { + /* B/G NICAM */ + msp->second = carrier_detect_55[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_NICAM1); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + } else { + goto no_second; + } + break; + case 2: /* 6.0 */ + /* PAL I NICAM */ + msp->second = MSP_CARRIER(6.552); + msp3400c_setmode(client, MSP_MODE_FM_NICAM2); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + break; + case 3: /* 6.5 */ + if (max2 == 1 || max2 == 2) { + /* D/K FM-stereo */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + msp->watch_stereo = 1; + } else if (max2 == 0 && + msp->norm == VIDEO_MODE_SECAM) { + /* L NICAM or AM-mono */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_AM_NICAM); + msp->nicam_on = 0; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + msp3400c_setcarrier(client, msp->second, msp->main); + /* volume prescale for SCART (AM mono input) */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000d, 0x1900); + msp->watch_stereo = 1; + } else if (max2 == 0 && HAVE_NICAM(msp)) { + /* D/K NICAM */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_NICAM1); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + } else { + goto no_second; + } + break; + case 0: /* 4.5 */ + default: + no_second: + msp->second = carrier_detect_main[max1].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->stereo = VIDEO_SOUND_MONO; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + break; + } + + /* unmute + restore dfp registers */ + msp3400c_setvolume(client, msp->muted, msp->left, msp->right); + msp3400c_restore_dfp(client); + + if (msp->watch_stereo) + mod_timer(&msp->wake_stereo, jiffies+5*HZ); + + if (debug) + msp3400c_print_mode(msp); + + msp->active = 0; + } + +done: + msp->active = 0; + dprintk(KERN_DEBUG "msp3400: thread: exit\n"); + complete_and_exit(&msp->texit, 0); +} + +/* ----------------------------------------------------------------------- */ +/* this one uses the automatic sound standard detection of newer */ +/* msp34xx chip versions */ + +static struct MODES { + int retval; + int main, second; + char *name; +} modelist[] = { + { 0x0000, 0, 0, "ERROR" }, + { 0x0001, 0, 0, "autodetect start" }, + { 0x0002, MSP_CARRIER(4.5), MSP_CARRIER(4.72), "4.5/4.72 M Dual FM-Stereo" }, + { 0x0003, MSP_CARRIER(5.5), MSP_CARRIER(5.7421875), "5.5/5.74 B/G Dual FM-Stereo" }, + { 0x0004, MSP_CARRIER(6.5), MSP_CARRIER(6.2578125), "6.5/6.25 D/K1 Dual FM-Stereo" }, + { 0x0005, MSP_CARRIER(6.5), MSP_CARRIER(6.7421875), "6.5/6.74 D/K2 Dual FM-Stereo" }, + { 0x0006, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5 D/K FM-Mono (HDEV3)" }, + { 0x0008, MSP_CARRIER(5.5), MSP_CARRIER(5.85), "5.5/5.85 B/G NICAM FM" }, + { 0x0009, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 L NICAM AM" }, + { 0x000a, MSP_CARRIER(6.0), MSP_CARRIER(6.55), "6.0/6.55 I NICAM FM" }, + { 0x000b, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 D/K NICAM FM" }, + { 0x000c, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 D/K NICAM FM (HDEV2)" }, + { 0x0020, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M BTSC-Stereo" }, + { 0x0021, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M BTSC-Mono + SAP" }, + { 0x0030, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M EIA-J Japan Stereo" }, + { 0x0040, MSP_CARRIER(10.7), MSP_CARRIER(10.7), "10.7 FM-Stereo Radio" }, + { 0x0050, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5 SAT-Mono" }, + { 0x0051, MSP_CARRIER(7.02), MSP_CARRIER(7.20), "7.02/7.20 SAT-Stereo" }, + { 0x0060, MSP_CARRIER(7.2), MSP_CARRIER(7.2), "7.2 SAT ADR" }, + { -1, 0, 0, NULL }, /* EOF */ +}; + +static int msp3410d_thread(void *data) +{ + struct i2c_client *client = data; + struct msp3400c *msp = i2c_get_clientdata(client); + int mode,val,i,std; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,61) + lock_kernel(); + daemonize(); + sigfillset(¤t->blocked); + strcpy(current->comm,"msp3410 [auto]"); + unlock_kernel(); +#else + daemonize("msp3410 [auto]"); + allow_signal(SIGTERM); +#endif + printk("msp3410: daemon started\n"); + + for (;;) { + d2printk(KERN_DEBUG "msp3410: thread: sleep\n"); + if (msp34xx_sleep(msp,-1)) + goto done; + + d2printk(KERN_DEBUG "msp3410: thread: wakeup\n"); + msp->active = 1; + + if (msp->watch_stereo) { + watch_stereo(client); + msp->active = 0; + continue; + } + + /* some time for the tuner to sync */ + if (msp34xx_sleep(msp,HZ/5)) + goto done; + + restart: + if (msp->mode == MSP_MODE_EXTERN) { + /* no carrier scan needed, just unmute */ + dprintk(KERN_DEBUG "msp3410: thread: no carrier scan\n"); + msp3400c_setvolume(client, msp->muted, + msp->left, msp->right); + continue; + } + msp->restart = 0; + del_timer(&msp->wake_stereo); + msp->watch_stereo = 0; + + /* put into sane state (and mute) */ + msp3400c_reset(client); + + /* start autodetect */ + switch (msp->norm) { + case VIDEO_MODE_PAL: + mode = 0x1003; + std = 1; + break; + case VIDEO_MODE_NTSC: /* BTSC */ + mode = 0x2003; + std = 0x0020; + break; + case VIDEO_MODE_SECAM: + mode = 0x0003; + std = 1; + break; + case VIDEO_MODE_RADIO: + mode = 0x0003; + std = 0x0040; + break; + default: + mode = 0x0003; + std = 1; + break; + } + msp3400c_write(client, I2C_MSP3400C_DEM, 0x30, mode); + msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, std); + + if (debug) { + int i; + for (i = 0; modelist[i].name != NULL; i++) + if (modelist[i].retval == std) + break; + printk(KERN_DEBUG "msp3410: setting mode: %s (0x%04x)\n", + modelist[i].name ? modelist[i].name : "unknown",std); + } + + if (std != 1) { + /* programmed some specific mode */ + val = std; + } else { + /* triggered autodetect */ + for (;;) { + if (msp34xx_sleep(msp,HZ/10)) + goto done; + if (msp->restart) + goto restart; + + /* check results */ + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x7e); + if (val < 0x07ff) + break; + dprintk(KERN_DEBUG "msp3410: detection still in progress\n"); + } + } + for (i = 0; modelist[i].name != NULL; i++) + if (modelist[i].retval == val) + break; + dprintk(KERN_DEBUG "msp3410: current mode: %s (0x%04x)\n", + modelist[i].name ? modelist[i].name : "unknown", + val); + msp->main = modelist[i].main; + msp->second = modelist[i].second; + + if (amsound && (msp->norm == VIDEO_MODE_SECAM) && (val != 0x0009)) { + /* autodetection has failed, let backup */ + dprintk(KERN_DEBUG "msp3410: autodetection failed," + " switching to backup mode: %s (0x%04x)\n", + modelist[8].name ? modelist[8].name : "unknown",val); + val = 0x0009; + msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, val); + } + + /* set various prescales */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x0d, 0x1900); /* scart */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403); /* FM */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x10, 0x5a00); /* nicam */ + + /* set stereo */ + switch (val) { + case 0x0008: /* B/G NICAM */ + case 0x000a: /* I NICAM */ + if (val == 0x0008) + msp->mode = MSP_MODE_FM_NICAM1; + else + msp->mode = MSP_MODE_FM_NICAM2; + /* just turn on stereo */ + msp->stereo = VIDEO_SOUND_STEREO; + msp->nicam_on = 1; + msp->watch_stereo = 1; + msp3400c_setstereo(client,VIDEO_SOUND_STEREO); + break; + case 0x0009: + msp->mode = MSP_MODE_AM_NICAM; + msp->stereo = VIDEO_SOUND_MONO; + msp->nicam_on = 1; + msp3400c_setstereo(client,VIDEO_SOUND_MONO); + msp->watch_stereo = 1; + break; + case 0x0020: /* BTSC */ + /* just turn on stereo */ + msp->mode = MSP_MODE_BTSC; + msp->stereo = VIDEO_SOUND_STEREO; + msp->nicam_on = 0; + msp->watch_stereo = 1; + msp3400c_setstereo(client,VIDEO_SOUND_STEREO); + break; + case 0x0040: /* FM radio */ + msp->mode = MSP_MODE_FM_RADIO; + msp->stereo = VIDEO_SOUND_STEREO; + msp->nicam_on = 0; + msp->watch_stereo = 0; + /* not needed in theory if HAVE_RADIO(), but + short programming enables carrier mute */ + msp3400c_setmode(client,MSP_MODE_FM_RADIO); + msp3400c_setcarrier(client, MSP_CARRIER(10.7), + MSP_CARRIER(10.7)); + /* scart routing */ + msp3400c_set_scart(client,SCART_IN2,0); +#if 0 + /* radio from SCART_IN2 */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x08, 0x0220); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x09, 0x0220); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0b, 0x0220); +#else + /* msp34xx does radio decoding */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x08, 0x0020); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x09, 0x0020); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0b, 0x0020); +#endif + break; + case 0x0003: + case 0x0004: + case 0x0005: + msp->mode = MSP_MODE_FM_TERRA; + msp->stereo = VIDEO_SOUND_MONO; + msp->nicam_on = 0; + msp->watch_stereo = 1; + break; + } + + /* unmute + restore dfp registers */ + msp3400c_setbass(client, msp->bass); + msp3400c_settreble(client, msp->treble); + msp3400c_setvolume(client, msp->muted, msp->left, msp->right); + msp3400c_restore_dfp(client); + + if (msp->watch_stereo) + mod_timer(&msp->wake_stereo, jiffies+HZ); + + msp->active = 0; + } + +done: + msp->active = 0; + dprintk(KERN_DEBUG "msp3410: thread: exit\n"); + complete_and_exit(&msp->texit, 0); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int msp_attach(struct i2c_adapter *adap, int addr, int kind); +#else +static int msp_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind); +#endif +static int msp_detach(struct i2c_client *client); +static int msp_probe(struct i2c_adapter *adap); +static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg); + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) + .owner = THIS_MODULE, +#endif + .name = "i2c msp3400 driver", + .id = I2C_DRIVERID_MSP3400, + .flags = I2C_DF_NOTIFY, + .attach_adapter = msp_probe, + .detach_client = msp_detach, + .command = msp_command, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("(unset)"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int msp_attach(struct i2c_adapter *adap, int addr, int kind) +#else +static int msp_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +#endif +{ + struct msp3400c *msp; + struct i2c_client *c; + int i; + + client_template.adapter = adap; + client_template.addr = addr; + + if (-1 == msp3400c_reset(&client_template)) { + dprintk("msp3400: no chip found\n"); + return -1; + } + + if (NULL == (c = kmalloc(sizeof(struct i2c_client),GFP_KERNEL))) + return -ENOMEM; + memcpy(c,&client_template,sizeof(struct i2c_client)); + if (NULL == (msp = kmalloc(sizeof(struct msp3400c),GFP_KERNEL))) { + kfree(c); + return -ENOMEM; + } + + memset(msp,0,sizeof(struct msp3400c)); + msp->left = 65535; + msp->right = 65535; + msp->bass = 32768; + msp->treble = 32768; + msp->input = -1; + msp->muted = 1; + for (i = 0; i < DFP_COUNT; i++) + msp->dfp_regs[i] = -1; + + i2c_set_clientdata(c, msp); + init_waitqueue_head(&msp->wq); + + if (-1 == msp3400c_reset(c)) { + kfree(msp); + kfree(c); + dprintk("msp3400: no chip found\n"); + return -1; + } + + msp->rev1 = msp3400c_read(c, I2C_MSP3400C_DFP, 0x1e); + if (-1 != msp->rev1) + msp->rev2 = msp3400c_read(c, I2C_MSP3400C_DFP, 0x1f); + if ((-1 == msp->rev1) || (0 == msp->rev1 && 0 == msp->rev2)) { + kfree(msp); + kfree(c); + printk("msp3400: error while reading chip version\n"); + return -1; + } + +#if 0 + /* this will turn on a 1kHz beep - might be useful for debugging... */ + msp3400c_write(c,I2C_MSP3400C_DFP, 0x0014, 0x1040); +#endif + msp3400c_setvolume(c,msp->muted,msp->left,msp->right); + + snprintf(c->name, sizeof(c->name), "MSP34%02d%c-%c%d", + (msp->rev2>>8)&0xff, (msp->rev1&0xff)+'@', + ((msp->rev1>>8)&0xff)+'@', msp->rev2&0x1f); + + if (simple == -1) { + /* default mode */ + msp->simple = HAVE_SIMPLE(msp); + } else { + /* use insmod option */ + msp->simple = simple; + } + + /* timer for stereo checking */ + init_timer(&msp->wake_stereo); + msp->wake_stereo.function = msp3400c_stereo_wake; + msp->wake_stereo.data = (unsigned long)msp; + + /* hello world :-) */ + printk(KERN_INFO "msp34xx: init: chip=%s",i2c_clientname(c)); + if (HAVE_NICAM(msp)) + printk(" +nicam"); + if (HAVE_SIMPLE(msp)) + printk(" +simple"); + if (HAVE_RADIO(msp)) + printk(" +radio"); + printk("\n"); + + /* startup control thread */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_INC_USE_COUNT; +#endif + init_completion(&msp->texit); + msp->tpid = kernel_thread(msp->simple ? msp3410d_thread : msp3400c_thread, + (void *)c, 0); + if (msp->tpid < 0) + printk(KERN_WARNING "msp34xx: kernel_thread() failed\n"); + wake_up_interruptible(&msp->wq); + + /* done */ + i2c_attach_client(c); + return 0; +} + +static int msp_detach(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + /* shutdown control thread */ + del_timer_sync(&msp->wake_stereo); + if (msp->tpid >= 0) { + msp->rmmod = 1; + wake_up_interruptible(&msp->wq); + wait_for_completion(&msp->texit); + } + msp3400c_reset(client); + + i2c_detach_client(client); + kfree(msp); + kfree(client); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_DEC_USE_COUNT; +#endif + return 0; +} + +static int msp_probe(struct i2c_adapter *adap) +{ +#ifdef I2C_ADAP_CLASS_TV_ANALOG + if (adap->class & I2C_ADAP_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, msp_attach); +#else + switch (adap->id) { + case I2C_ALGO_BIT | I2C_HW_SMBUS_VOODOO3: + case I2C_ALGO_BIT | I2C_HW_B_BT848: + //case I2C_ALGO_SAA7134: + return i2c_probe(adap, &addr_data, msp_attach); + break; + } +#endif + return 0; +} + +static void msp_wake_thread(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + msp3400c_setvolume(client,msp->muted,0,0); + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + if (msp->active) + msp->restart = 1; + wake_up_interruptible(&msp->wq); +} + +static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + __u16 *sarg = arg; + int scart = 0; + + switch (cmd) { + + case AUDC_SET_INPUT: + dprintk(KERN_DEBUG "msp34xx: AUDC_SET_INPUT(%d)\n",*sarg); + if (*sarg == msp->input) + break; + msp->input = *sarg; + switch (*sarg) { + case AUDIO_RADIO: + /* Hauppauge uses IN2 for the radio */ + msp->mode = MSP_MODE_FM_RADIO; + scart = SCART_IN2; + break; + case AUDIO_EXTERN_1: + /* IN1 is often used for external input ... */ + msp->mode = MSP_MODE_EXTERN; + scart = SCART_IN1; + break; + case AUDIO_EXTERN_2: + /* ... sometimes it is IN2 through ;) */ + msp->mode = MSP_MODE_EXTERN; + scart = SCART_IN2; + break; + case AUDIO_TUNER: + msp->mode = -1; + msp_wake_thread(client); + break; + default: + if (*sarg & AUDIO_MUTE) + msp3400c_set_scart(client,SCART_MUTE,0); + break; + } + if (scart) { + msp->stereo = VIDEO_SOUND_STEREO; + msp3400c_set_scart(client,scart,0); + msp3400c_write(client,I2C_MSP3400C_DFP,0x000d,0x1900); + msp3400c_setstereo(client,msp->stereo); + } + if (msp->active) + msp->restart = 1; + break; + + case AUDC_SET_RADIO: + dprintk(KERN_DEBUG "msp34xx: AUDC_SET_RADIO\n"); + msp->norm = VIDEO_MODE_RADIO; + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + dprintk(KERN_DEBUG "msp34xx: switching to radio mode\n"); + if (msp->simple) { + /* the thread will do for us */ + msp_wake_thread(client); + } else { + /* set msp3400 to FM radio mode */ + msp3400c_setmode(client,MSP_MODE_FM_RADIO); + msp3400c_setcarrier(client, MSP_CARRIER(10.7),MSP_CARRIER(10.7)); + msp3400c_setvolume(client,msp->muted,msp->left,msp->right); + } + if (msp->active) + msp->restart = 1; + break; + +#if 1 + /* work-in-progress: hook to control the DFP registers */ + case MSP_SET_DFPREG: + { + struct msp_dfpreg *r = arg; + unsigned int i; + + if (r->reg < 0 || r->reg >= DFP_COUNT) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(bl_dfp); i++) + if (r->reg == bl_dfp[i]) + return -EINVAL; + msp->dfp_regs[r->reg] = r->value; + msp3400c_write(client,I2C_MSP3400C_DFP,r->reg,r->value); + return 0; + } + case MSP_GET_DFPREG: + { + struct msp_dfpreg *r = arg; + + if (r->reg < 0 || r->reg >= DFP_COUNT) + return -EINVAL; + r->value = msp3400c_read(client,I2C_MSP3400C_DFP,r->reg); + return 0; + } +#endif + + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + + dprintk(KERN_DEBUG "msp34xx: VIDIOCGAUDIO\n"); + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE | + VIDEO_AUDIO_MUTABLE; + if (msp->muted) + va->flags |= VIDEO_AUDIO_MUTE; + va->volume=max(msp->left,msp->right); + + if (0 == va->volume) { + va->balance = 32768; + } else { + va->balance = (32768 * min(msp->left,msp->right)) + / va->volume; + va->balance = (msp->left<msp->right) ? + (65535 - va->balance) : va->balance; + } + va->bass = msp->bass; + va->treble = msp->treble; + + if (msp->norm != VIDEO_MODE_RADIO) { + autodetect_stereo(client); + va->mode = msp->stereo; + } + break; + } + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + + dprintk(KERN_DEBUG "msp34xx: VIDIOCSAUDIO\n"); + msp->muted = (va->flags & VIDEO_AUDIO_MUTE); + msp->left = (min(65536 - va->balance,32768) * + va->volume) / 32768; + msp->right = (min(va->balance,(__u16)32768) * + va->volume) / 32768; + msp->bass = va->bass; + msp->treble = va->treble; + msp3400c_setvolume(client,msp->muted,msp->left,msp->right); + msp3400c_setbass(client,msp->bass); + msp3400c_settreble(client,msp->treble); + + if (va->mode != 0 && msp->norm != VIDEO_MODE_RADIO) { + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + msp->stereo = va->mode & 0x0f; + msp3400c_setstereo(client,va->mode & 0x0f); + } + break; + } + case VIDIOCSCHAN: + { + struct video_channel *vc = arg; + + dprintk(KERN_DEBUG "msp34xx: VIDIOCSCHAN (norm=%d)\n",vc->norm); + msp->norm = vc->norm; + break; + } + case VIDIOCSFREQ: + { + /* new channel -- kick audio carrier scan */ + dprintk(KERN_DEBUG "msp34xx: VIDIOCSFREQ\n"); + msp_wake_thread(client); + break; + } + + default: + /* nothing */ + break; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int msp3400_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void msp3400_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(msp3400_init_module); +module_exit(msp3400_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/msp3400.c b/linux/drivers/media/video/msp3400.c new file mode 100644 index 000000000..f7db546d2 --- /dev/null +++ b/linux/drivers/media/video/msp3400.c @@ -0,0 +1,1618 @@ +/* + * programming the msp34* sound processor family + * + * (c) 1997-2001 Gerd Knorr <kraxel@bytesex.org> + * + * what works and what doesn't: + * + * AM-Mono + * Support for Hauppauge cards added (decoding handled by tuner) added by + * Frederic Crozat <fcrozat@mail.dotcom.fr> + * + * FM-Mono + * should work. The stereo modes are backward compatible to FM-mono, + * therefore FM-Mono should be allways available. + * + * FM-Stereo (B/G, used in germany) + * should work, with autodetect + * + * FM-Stereo (satellite) + * should work, no autodetect (i.e. default is mono, but you can + * switch to stereo -- untested) + * + * NICAM (B/G, L , used in UK, Scandinavia, Spain and France) + * should work, with autodetect. Support for NICAM was added by + * Pekka Pietikainen <pp@netppl.fi> + * + * + * TODO: + * - better SAT support + * + * + * 980623 Thomas Sailer (sailer@ife.ee.ethz.ch) + * using soundcore instead of OSS + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <asm/semaphore.h> +#include <asm/pgtable.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "audiochip.h" +#include "i2c-compat.h" +#else +#include <media/audiochip.h> +#endif +#include "msp3400.h" + +/* insmod parameters */ +static int debug = 0; /* debug output */ +static int once = 0; /* no continous stereo monitoring */ +static int amsound = 0; /* hard-wire AM sound at 6.5 Hz (france), + the autoscan seems work well only with FM... */ +static int simple = -1; /* use short programming (>= msp3410 only) */ +static int dolby = 0; + +#define DFP_COUNT 0x41 +static const int bl_dfp[] = { + 0x00, 0x01, 0x02, 0x03, 0x06, 0x08, 0x09, 0x0a, + 0x0b, 0x0d, 0x0e, 0x10 +}; + +struct msp3400c { + int rev1,rev2; + + int simple; + int mode; + int norm; + int stereo; + int nicam_on; + int acb; + int main, second; /* sound carrier */ + int input; + + int muted; + int left, right; /* volume */ + int bass, treble; + + /* shadow register set */ + int dfp_regs[DFP_COUNT]; + + /* thread */ + pid_t tpid; + struct completion texit; + wait_queue_head_t wq; + + int active:1; + int restart:1; + int rmmod:1; + + int watch_stereo; + struct timer_list wake_stereo; +}; + +#define HAVE_NICAM(msp) (((msp->rev2>>8) & 0xff) != 00) +#define HAVE_SIMPLE(msp) ((msp->rev1 & 0xff) >= 'D'-'@') +#define HAVE_RADIO(msp) ((msp->rev1 & 0xff) >= 'G'-'@') + +#define VIDEO_MODE_RADIO 16 /* norm magic for radio mode */ + +/* ---------------------------------------------------------------------- */ + +#define dprintk if (debug >= 1) printk +#define d2printk if (debug >= 2) printk + +MODULE_PARM(once,"i"); +MODULE_PARM(debug,"i"); +MODULE_PARM(simple,"i"); +MODULE_PARM(amsound,"i"); +MODULE_PARM(dolby,"i"); + +MODULE_DESCRIPTION("device driver for msp34xx TV sound processor"); +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("Dual BSD/GPL"); /* FreeBSD uses this too */ + +/* ---------------------------------------------------------------------- */ + +#define I2C_MSP3400C 0x80 +#define I2C_MSP3400C_ALT 0x88 + +#define I2C_MSP3400C_DEM 0x10 +#define I2C_MSP3400C_DFP 0x12 + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + I2C_MSP3400C >> 1, + I2C_MSP3400C_ALT >> 1, + I2C_CLIENT_END +}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END,I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +/* ----------------------------------------------------------------------- */ +/* functions for talking to the MSP3400C Sound processor */ + +#ifndef I2C_M_IGNORE_NAK +# define I2C_M_IGNORE_NAK 0x1000 +#endif + +static int msp3400c_reset(struct i2c_client *client) +{ + /* reset and read revision code */ + static char reset_off[3] = { 0x00, 0x80, 0x00 }; + static char reset_on[3] = { 0x00, 0x00, 0x00 }; + static char write[3] = { I2C_MSP3400C_DFP + 1, 0x00, 0x1e }; + char read[2]; + struct i2c_msg reset[2] = { + { client->addr, I2C_M_IGNORE_NAK, 3, reset_off }, + { client->addr, I2C_M_IGNORE_NAK, 3, reset_on }, + }; + struct i2c_msg test[2] = { + { client->addr, 0, 3, write }, + { client->addr, I2C_M_RD, 2, read }, + }; + + if ( (1 != i2c_transfer(client->adapter,&reset[0],1)) || + (1 != i2c_transfer(client->adapter,&reset[1],1)) || + (2 != i2c_transfer(client->adapter,test,2)) ) { + printk(KERN_ERR "msp3400: chip reset failed\n"); + return -1; + } + return 0; +} + +static int +msp3400c_read(struct i2c_client *client, int dev, int addr) +{ + int err; + + unsigned char write[3]; + unsigned char read[2]; + struct i2c_msg msgs[2] = { + { client->addr, 0, 3, write }, + { client->addr, I2C_M_RD, 2, read } + }; + write[0] = dev+1; + write[1] = addr >> 8; + write[2] = addr & 0xff; + + for (err = 0; err < 3;) { + if (2 == i2c_transfer(client->adapter,msgs,2)) + break; + err++; + printk(KERN_WARNING "msp34xx: I/O error #%d (read 0x%02x/0x%02x)\n", + err, dev, addr); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + } + if (3 == err) { + printk(KERN_WARNING "msp34xx: giving up, reseting chip. Sound will go off, sorry folks :-|\n"); + msp3400c_reset(client); + return -1; + } + return read[0] << 8 | read[1]; +} + +static int +msp3400c_write(struct i2c_client *client, int dev, int addr, int val) +{ + int err; + unsigned char buffer[5]; + + buffer[0] = dev; + buffer[1] = addr >> 8; + buffer[2] = addr & 0xff; + buffer[3] = val >> 8; + buffer[4] = val & 0xff; + + for (err = 0; err < 3;) { + if (5 == i2c_master_send(client, buffer, 5)) + break; + err++; + printk(KERN_WARNING "msp34xx: I/O error #%d (write 0x%02x/0x%02x)\n", + err, dev, addr); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + } + if (3 == err) { + printk(KERN_WARNING "msp34xx: giving up, reseting chip. Sound will go off, sorry folks :-|\n"); + msp3400c_reset(client); + return -1; + } + return 0; +} + +/* ------------------------------------------------------------------------ */ + +/* This macro is allowed for *constants* only, gcc must calculate it + at compile time. Remember -- no floats in kernel mode */ +#define MSP_CARRIER(freq) ((int)((float)(freq/18.432)*(1<<24))) + +#define MSP_MODE_AM_DETECT 0 +#define MSP_MODE_FM_RADIO 2 +#define MSP_MODE_FM_TERRA 3 +#define MSP_MODE_FM_SAT 4 +#define MSP_MODE_FM_NICAM1 5 +#define MSP_MODE_FM_NICAM2 6 +#define MSP_MODE_AM_NICAM 7 +#define MSP_MODE_BTSC 8 +#define MSP_MODE_EXTERN 9 + +static struct MSP_INIT_DATA_DEM { + int fir1[6]; + int fir2[6]; + int cdo1; + int cdo2; + int ad_cv; + int mode_reg; + int dfp_src; + int dfp_matrix; +} msp_init_data[] = { + /* AM (for carrier detect / msp3400) */ + { { 75, 19, 36, 35, 39, 40 }, { 75, 19, 36, 35, 39, 40 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0500, 0x0020, 0x3000}, + + /* AM (for carrier detect / msp3410) */ + { { -1, -1, -8, 2, 59, 126 }, { -1, -1, -8, 2, 59, 126 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0100, 0x0020, 0x3000}, + + /* FM Radio */ + { { -8, -8, 4, 6, 78, 107 }, { -8, -8, 4, 6, 78, 107 }, + MSP_CARRIER(10.7), MSP_CARRIER(10.7), + 0x00d0, 0x0480, 0x0020, 0x3000 }, + + /* Terrestial FM-mono + FM-stereo */ + { { 3, 18, 27, 48, 66, 72 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0480, 0x0030, 0x3000}, + + /* Sat FM-mono */ + { { 1, 9, 14, 24, 33, 37 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(6.5), MSP_CARRIER(6.5), + 0x00c6, 0x0480, 0x0000, 0x3000}, + + /* NICAM/FM -- B/G (5.5/5.85), D/K (6.5/5.85) */ + { { -2, -8, -10, 10, 50, 86 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0040, 0x0120, 0x3000}, + + /* NICAM/FM -- I (6.0/6.552) */ + { { 2, 4, -6, -4, 40, 94 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(6.0), MSP_CARRIER(6.0), + 0x00d0, 0x0040, 0x0120, 0x3000}, + + /* NICAM/AM -- L (6.5/5.85) */ + { { -2, -8, -10, 10, 50, 86 }, { -4, -12, -9, 23, 79, 126 }, + MSP_CARRIER(6.5), MSP_CARRIER(6.5), + 0x00c6, 0x0140, 0x0120, 0x7c03}, +}; + +struct CARRIER_DETECT { + int cdo; + char *name; +}; + +static struct CARRIER_DETECT carrier_detect_main[] = { + /* main carrier */ + { MSP_CARRIER(4.5), "4.5 NTSC" }, + { MSP_CARRIER(5.5), "5.5 PAL B/G" }, + { MSP_CARRIER(6.0), "6.0 PAL I" }, + { MSP_CARRIER(6.5), "6.5 PAL D/K + SAT + SECAM" } +}; + +static struct CARRIER_DETECT carrier_detect_55[] = { + /* PAL B/G */ + { MSP_CARRIER(5.7421875), "5.742 PAL B/G FM-stereo" }, + { MSP_CARRIER(5.85), "5.85 PAL B/G NICAM" } +}; + +static struct CARRIER_DETECT carrier_detect_65[] = { + /* PAL SAT / SECAM */ + { MSP_CARRIER(5.85), "5.85 PAL D/K + SECAM NICAM" }, + { MSP_CARRIER(6.2578125), "6.25 PAL D/K1 FM-stereo" }, + { MSP_CARRIER(6.7421875), "6.74 PAL D/K2 FM-stereo" }, + { MSP_CARRIER(7.02), "7.02 PAL SAT FM-stereo s/b" }, + { MSP_CARRIER(7.20), "7.20 PAL SAT FM-stereo s" }, + { MSP_CARRIER(7.38), "7.38 PAL SAT FM-stereo b" }, +}; + +#define CARRIER_COUNT(x) (sizeof(x)/sizeof(struct CARRIER_DETECT)) + +/* ----------------------------------------------------------------------- */ + +#define SCART_MASK 0 +#define SCART_IN1 1 +#define SCART_IN2 2 +#define SCART_IN1_DA 3 +#define SCART_IN2_DA 4 +#define SCART_IN3 5 +#define SCART_IN4 6 +#define SCART_MONO 7 +#define SCART_MUTE 8 + +static int scarts[3][9] = { + /* MASK IN1 IN2 IN1_DA IN2_DA IN3 IN4 MONO MUTE */ + { 0x0320, 0x0000, 0x0200, -1, -1, 0x0300, 0x0020, 0x0100, 0x0320 }, + { 0x0c40, 0x0440, 0x0400, 0x0c00, 0x0040, 0x0000, 0x0840, 0x0800, 0x0c40 }, + { 0x3080, 0x1000, 0x1080, 0x0000, 0x0080, 0x2080, 0x3080, 0x2000, 0x3000 }, +}; + +static char *scart_names[] = { + "mask", "in1", "in2", "in1 da", "in2 da", "in3", "in4", "mono", "mute" +}; + +static void +msp3400c_set_scart(struct i2c_client *client, int in, int out) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + if (-1 == scarts[out][in]) + return; + + dprintk(KERN_DEBUG + "msp34xx: scart switch: %s => %d\n",scart_names[in],out); + msp->acb &= ~scarts[out][SCART_MASK]; + msp->acb |= scarts[out][in]; + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0013, msp->acb); +} + +/* ------------------------------------------------------------------------ */ + +static void msp3400c_setcarrier(struct i2c_client *client, int cdo1, int cdo2) +{ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0093, cdo1 & 0xfff); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x009b, cdo1 >> 12); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00a3, cdo2 & 0xfff); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00ab, cdo2 >> 12); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/ +} + +static void msp3400c_setvolume(struct i2c_client *client, + int muted, int left, int right) +{ + int vol = 0,val = 0,balance = 0; + + if (!muted) { + vol = (left > right) ? left : right; + val = (vol * 0x73 / 65535) << 8; + } + if (vol > 0) { + balance = ((right-left) * 127) / vol; + } + + dprintk(KERN_DEBUG + "msp34xx: setvolume: mute=%s %d:%d v=0x%02x b=0x%02x\n", + muted ? "on" : "off", left, right, val>>8, balance); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0000, val); /* loudspeaker */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0006, val); /* headphones */ + /* scart - on/off only */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0007, val ? 0x4000 : 0); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0001, balance << 8); +} + +static void msp3400c_setbass(struct i2c_client *client, int bass) +{ + int val = ((bass-32768) * 0x60 / 65535) << 8; + + dprintk(KERN_DEBUG "msp34xx: setbass: %d 0x%02x\n",bass, val>>8); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0002, val); /* loudspeaker */ +} + +static void msp3400c_settreble(struct i2c_client *client, int treble) +{ + int val = ((treble-32768) * 0x60 / 65535) << 8; + + dprintk(KERN_DEBUG "msp34xx: settreble: %d 0x%02x\n",treble, val>>8); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0003, val); /* loudspeaker */ +} + +static void msp3400c_setmode(struct i2c_client *client, int type) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + int i; + + dprintk(KERN_DEBUG "msp3400: setmode: %d\n",type); + msp->mode = type; + msp->stereo = VIDEO_SOUND_MONO; + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00bb, /* ad_cv */ + msp_init_data[type].ad_cv); + + for (i = 5; i >= 0; i--) /* fir 1 */ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0001, + msp_init_data[type].fir1[i]); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0004); /* fir 2 */ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0040); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0000); + for (i = 5; i >= 0; i--) + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, + msp_init_data[type].fir2[i]); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0083, /* MODE_REG */ + msp_init_data[type].mode_reg); + + msp3400c_setcarrier(client, msp_init_data[type].cdo1, + msp_init_data[type].cdo2); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/ + + if (dolby) { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008, + 0x0520); /* I2S1 */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009, + 0x0620); /* I2S2 */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b, + msp_init_data[type].dfp_src); + } else { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008, + msp_init_data[type].dfp_src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009, + msp_init_data[type].dfp_src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b, + msp_init_data[type].dfp_src); + } + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a, + msp_init_data[type].dfp_src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, + msp_init_data[type].dfp_matrix); + + if (HAVE_NICAM(msp)) { + /* nicam prescale */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0010, 0x5a00); /* was: 0x3000 */ + } +} + +/* turn on/off nicam + stereo */ +static void msp3400c_setstereo(struct i2c_client *client, int mode) +{ + static char *strmode[16] = { +#if __GNUC__ >= 3 + [ 0 ... 15 ] = "invalid", +#endif + [ VIDEO_SOUND_MONO ] = "mono", + [ VIDEO_SOUND_STEREO ] = "stereo", + [ VIDEO_SOUND_LANG1 ] = "lang1", + [ VIDEO_SOUND_LANG2 ] = "lang2", + }; + struct msp3400c *msp = i2c_get_clientdata(client); + int nicam=0; /* channel source: FM/AM or nicam */ + int src=0; + + /* switch demodulator */ + switch (msp->mode) { + case MSP_MODE_FM_TERRA: + dprintk(KERN_DEBUG "msp3400: FM setstereo: %s\n",strmode[mode]); + msp3400c_setcarrier(client,msp->second,msp->main); + switch (mode) { + case VIDEO_SOUND_STEREO: + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3001); + break; + case VIDEO_SOUND_MONO: + case VIDEO_SOUND_LANG1: + case VIDEO_SOUND_LANG2: + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3000); + break; + } + break; + case MSP_MODE_FM_SAT: + dprintk(KERN_DEBUG "msp3400: SAT setstereo: %s\n",strmode[mode]); + switch (mode) { + case VIDEO_SOUND_MONO: + msp3400c_setcarrier(client, MSP_CARRIER(6.5), MSP_CARRIER(6.5)); + break; + case VIDEO_SOUND_STEREO: + msp3400c_setcarrier(client, MSP_CARRIER(7.2), MSP_CARRIER(7.02)); + break; + case VIDEO_SOUND_LANG1: + msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02)); + break; + case VIDEO_SOUND_LANG2: + msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02)); + break; + } + break; + case MSP_MODE_FM_NICAM1: + case MSP_MODE_FM_NICAM2: + case MSP_MODE_AM_NICAM: + dprintk(KERN_DEBUG "msp3400: NICAM setstereo: %s\n",strmode[mode]); + msp3400c_setcarrier(client,msp->second,msp->main); + if (msp->nicam_on) + nicam=0x0100; + break; + case MSP_MODE_BTSC: + dprintk(KERN_DEBUG "msp3400: BTSC setstereo: %s\n",strmode[mode]); + nicam=0x0300; + break; + case MSP_MODE_EXTERN: + dprintk(KERN_DEBUG "msp3400: extern setstereo: %s\n",strmode[mode]); + nicam = 0x0200; + break; + case MSP_MODE_FM_RADIO: + dprintk(KERN_DEBUG "msp3400: FM-Radio setstereo: %s\n",strmode[mode]); + break; + default: + dprintk(KERN_DEBUG "msp3400: mono setstereo\n"); + return; + } + + /* switch audio */ + switch (mode) { + case VIDEO_SOUND_STEREO: + src = 0x0020 | nicam; +#if 0 + /* spatial effect */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0005,0x4000); +#endif + break; + case VIDEO_SOUND_MONO: + if (msp->mode == MSP_MODE_AM_NICAM) { + dprintk("msp3400: switching to AM mono\n"); + /* AM mono decoding is handled by tuner, not MSP chip */ + /* SCART switching control register */ + msp3400c_set_scart(client,SCART_MONO,0); + src = 0x0200; + break; + } + case VIDEO_SOUND_LANG1: + src = 0x0000 | nicam; + break; + case VIDEO_SOUND_LANG2: + src = 0x0010 | nicam; + break; + } + dprintk(KERN_DEBUG + "msp3400: setstereo final source/matrix = 0x%x\n", src); + + if (dolby) { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,0x0520); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,0x0620); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,src); + } else { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,src); + } +} + +static void +msp3400c_print_mode(struct msp3400c *msp) +{ + if (msp->main == msp->second) { + printk(KERN_DEBUG "msp3400: mono sound carrier: %d.%03d MHz\n", + msp->main/910000,(msp->main/910)%1000); + } else { + printk(KERN_DEBUG "msp3400: main sound carrier: %d.%03d MHz\n", + msp->main/910000,(msp->main/910)%1000); + } + if (msp->mode == MSP_MODE_FM_NICAM1 || + msp->mode == MSP_MODE_FM_NICAM2) + printk(KERN_DEBUG "msp3400: NICAM/FM carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + if (msp->mode == MSP_MODE_AM_NICAM) + printk(KERN_DEBUG "msp3400: NICAM/AM carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + if (msp->mode == MSP_MODE_FM_TERRA && + msp->main != msp->second) { + printk(KERN_DEBUG "msp3400: FM-stereo carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + } +} + +static void +msp3400c_restore_dfp(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + int i; + + for (i = 0; i < DFP_COUNT; i++) { + if (-1 == msp->dfp_regs[i]) + continue; + msp3400c_write(client,I2C_MSP3400C_DFP, i, msp->dfp_regs[i]); + } +} + +/* ----------------------------------------------------------------------- */ + +struct REGISTER_DUMP { + int addr; + char *name; +}; + +struct REGISTER_DUMP d1[] = { + { 0x007e, "autodetect" }, + { 0x0023, "C_AD_BITS " }, + { 0x0038, "ADD_BITS " }, + { 0x003e, "CIB_BITS " }, + { 0x0057, "ERROR_RATE" }, +}; + +static int +autodetect_stereo(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + int val; + int newstereo = msp->stereo; + int newnicam = msp->nicam_on; + int update = 0; + + switch (msp->mode) { + case MSP_MODE_FM_TERRA: + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x18); + if (val > 32767) + val -= 65536; + dprintk(KERN_DEBUG + "msp34xx: stereo detect register: %d\n",val); + if (val > 4096) { + newstereo = VIDEO_SOUND_STEREO | VIDEO_SOUND_MONO; + } else if (val < -4096) { + newstereo = VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } else { + newstereo = VIDEO_SOUND_MONO; + } + newnicam = 0; + break; + case MSP_MODE_FM_NICAM1: + case MSP_MODE_FM_NICAM2: + case MSP_MODE_AM_NICAM: + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x23); + dprintk(KERN_DEBUG + "msp34xx: nicam sync=%d, mode=%d\n", + val & 1, (val & 0x1e) >> 1); + + if (val & 1) { + /* nicam synced */ + switch ((val & 0x1e) >> 1) { + case 0: + case 8: + newstereo = VIDEO_SOUND_STEREO; + break; + case 1: + case 9: + newstereo = VIDEO_SOUND_MONO + | VIDEO_SOUND_LANG1; + break; + case 2: + case 10: + newstereo = VIDEO_SOUND_MONO + | VIDEO_SOUND_LANG1 + | VIDEO_SOUND_LANG2; + break; + default: + newstereo = VIDEO_SOUND_MONO; + break; + } + newnicam=1; + } else { + newnicam = 0; + newstereo = VIDEO_SOUND_MONO; + } + break; + case MSP_MODE_BTSC: + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x200); + dprintk(KERN_DEBUG + "msp3410: status=0x%x (pri=%s, sec=%s, %s%s%s)\n", + val, + (val & 0x0002) ? "no" : "yes", + (val & 0x0004) ? "no" : "yes", + (val & 0x0040) ? "stereo" : "mono", + (val & 0x0080) ? ", nicam 2nd mono" : "", + (val & 0x0100) ? ", bilingual/SAP" : ""); + newstereo = VIDEO_SOUND_MONO; + if (val & 0x0040) newstereo |= VIDEO_SOUND_STEREO; + if (val & 0x0100) newstereo |= VIDEO_SOUND_LANG1; + break; + } + if (newstereo != msp->stereo) { + update = 1; + dprintk(KERN_DEBUG "msp34xx: watch: stereo %d => %d\n", + msp->stereo,newstereo); + msp->stereo = newstereo; + } + if (newnicam != msp->nicam_on) { + update = 1; + dprintk(KERN_DEBUG "msp34xx: watch: nicam %d => %d\n", + msp->nicam_on,newnicam); + msp->nicam_on = newnicam; + } + return update; +} + +/* + * A kernel thread for msp3400 control -- we don't want to block the + * in the ioctl while doing the sound carrier & stereo detect + */ + +static int msp34xx_sleep(struct msp3400c *msp, int timeout) +{ + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&msp->wq, &wait); + if (!msp->rmmod) { + set_current_state(TASK_INTERRUPTIBLE); + if (timeout < 0) + schedule(); + else + schedule_timeout(timeout); + } + remove_wait_queue(&msp->wq, &wait); + return msp->rmmod || signal_pending(current); +} + +static void msp3400c_stereo_wake(unsigned long data) +{ + struct msp3400c *msp = (struct msp3400c*)data; /* XXX alpha ??? */ + + wake_up_interruptible(&msp->wq); +} + +/* stereo/multilang monitoring */ +static void watch_stereo(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + if (autodetect_stereo(client)) { + if (msp->stereo & VIDEO_SOUND_STEREO) + msp3400c_setstereo(client,VIDEO_SOUND_STEREO); + else if (msp->stereo & VIDEO_SOUND_LANG1) + msp3400c_setstereo(client,VIDEO_SOUND_LANG1); + else if (msp->stereo & VIDEO_SOUND_LANG2) + msp3400c_setstereo(client,VIDEO_SOUND_LANG2); + else + msp3400c_setstereo(client,VIDEO_SOUND_MONO); + } + if (once) + msp->watch_stereo = 0; + if (msp->watch_stereo) + mod_timer(&msp->wake_stereo, jiffies+5*HZ); +} + +static int msp3400c_thread(void *data) +{ + struct i2c_client *client = data; + struct msp3400c *msp = i2c_get_clientdata(client); + + struct CARRIER_DETECT *cd; + int count, max1,max2,val1,val2, val,this; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,61) + lock_kernel(); + daemonize(); + sigfillset(¤t->blocked); + strcpy(current->comm,"msp3400"); + unlock_kernel(); +#else + daemonize("msp3400"); + allow_signal(SIGTERM); +#endif + printk("msp3400: daemon started\n"); + + for (;;) { + d2printk("msp3400: thread: sleep\n"); + if (msp34xx_sleep(msp,-1)) + goto done; + + d2printk("msp3400: thread: wakeup\n"); + msp->active = 1; + + if (msp->watch_stereo) { + watch_stereo(client); + msp->active = 0; + continue; + } + + /* some time for the tuner to sync */ + if (msp34xx_sleep(msp,HZ/5)) + goto done; + + restart: + if (VIDEO_MODE_RADIO == msp->norm || + MSP_MODE_EXTERN == msp->mode) { + /* no carrier scan, just unmute */ + printk("msp3400: thread: no carrier scan\n"); + msp3400c_setvolume(client, msp->muted, + msp->left, msp->right); + continue; + } + msp->restart = 0; + msp3400c_setvolume(client, msp->muted, 0, 0); + msp3400c_setmode(client, MSP_MODE_AM_DETECT /* +1 */ ); + val1 = val2 = 0; + max1 = max2 = -1; + del_timer(&msp->wake_stereo); + msp->watch_stereo = 0; + + /* carrier detect pass #1 -- main carrier */ + cd = carrier_detect_main; count = CARRIER_COUNT(carrier_detect_main); + + if (amsound && (msp->norm == VIDEO_MODE_SECAM)) { + /* autodetect doesn't work well with AM ... */ + max1 = 3; + count = 0; + dprintk("msp3400: AM sound override\n"); + } + + for (this = 0; this < count; this++) { + msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo); + + if (msp34xx_sleep(msp,HZ/10)) + goto done; + if (msp->restart) + msp->restart = 0; + + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b); + if (val > 32767) + val -= 65536; + if (val1 < val) + val1 = val, max1 = this; + dprintk("msp3400: carrier1 val: %5d / %s\n", val,cd[this].name); + } + + /* carrier detect pass #2 -- second (stereo) carrier */ + switch (max1) { + case 1: /* 5.5 */ + cd = carrier_detect_55; count = CARRIER_COUNT(carrier_detect_55); + break; + case 3: /* 6.5 */ + cd = carrier_detect_65; count = CARRIER_COUNT(carrier_detect_65); + break; + case 0: /* 4.5 */ + case 2: /* 6.0 */ + default: + cd = NULL; count = 0; + break; + } + + if (amsound && (msp->norm == VIDEO_MODE_SECAM)) { + /* autodetect doesn't work well with AM ... */ + cd = NULL; count = 0; max2 = 0; + } + for (this = 0; this < count; this++) { + msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo); + + if (msp34xx_sleep(msp,HZ/10)) + goto done; + if (msp->restart) + goto restart; + + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b); + if (val > 32767) + val -= 65536; + if (val2 < val) + val2 = val, max2 = this; + dprintk("msp3400: carrier2 val: %5d / %s\n", val,cd[this].name); + } + + /* programm the msp3400 according to the results */ + msp->main = carrier_detect_main[max1].cdo; + switch (max1) { + case 1: /* 5.5 */ + if (max2 == 0) { + /* B/G FM-stereo */ + msp->second = carrier_detect_55[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + msp->watch_stereo = 1; + } else if (max2 == 1 && HAVE_NICAM(msp)) { + /* B/G NICAM */ + msp->second = carrier_detect_55[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_NICAM1); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + } else { + goto no_second; + } + break; + case 2: /* 6.0 */ + /* PAL I NICAM */ + msp->second = MSP_CARRIER(6.552); + msp3400c_setmode(client, MSP_MODE_FM_NICAM2); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + break; + case 3: /* 6.5 */ + if (max2 == 1 || max2 == 2) { + /* D/K FM-stereo */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + msp->watch_stereo = 1; + } else if (max2 == 0 && + msp->norm == VIDEO_MODE_SECAM) { + /* L NICAM or AM-mono */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_AM_NICAM); + msp->nicam_on = 0; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + msp3400c_setcarrier(client, msp->second, msp->main); + /* volume prescale for SCART (AM mono input) */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000d, 0x1900); + msp->watch_stereo = 1; + } else if (max2 == 0 && HAVE_NICAM(msp)) { + /* D/K NICAM */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_NICAM1); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + } else { + goto no_second; + } + break; + case 0: /* 4.5 */ + default: + no_second: + msp->second = carrier_detect_main[max1].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->stereo = VIDEO_SOUND_MONO; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + break; + } + + /* unmute + restore dfp registers */ + msp3400c_setvolume(client, msp->muted, msp->left, msp->right); + msp3400c_restore_dfp(client); + + if (msp->watch_stereo) + mod_timer(&msp->wake_stereo, jiffies+5*HZ); + + if (debug) + msp3400c_print_mode(msp); + + msp->active = 0; + } + +done: + msp->active = 0; + dprintk(KERN_DEBUG "msp3400: thread: exit\n"); + complete_and_exit(&msp->texit, 0); +} + +/* ----------------------------------------------------------------------- */ +/* this one uses the automatic sound standard detection of newer */ +/* msp34xx chip versions */ + +static struct MODES { + int retval; + int main, second; + char *name; +} modelist[] = { + { 0x0000, 0, 0, "ERROR" }, + { 0x0001, 0, 0, "autodetect start" }, + { 0x0002, MSP_CARRIER(4.5), MSP_CARRIER(4.72), "4.5/4.72 M Dual FM-Stereo" }, + { 0x0003, MSP_CARRIER(5.5), MSP_CARRIER(5.7421875), "5.5/5.74 B/G Dual FM-Stereo" }, + { 0x0004, MSP_CARRIER(6.5), MSP_CARRIER(6.2578125), "6.5/6.25 D/K1 Dual FM-Stereo" }, + { 0x0005, MSP_CARRIER(6.5), MSP_CARRIER(6.7421875), "6.5/6.74 D/K2 Dual FM-Stereo" }, + { 0x0006, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5 D/K FM-Mono (HDEV3)" }, + { 0x0008, MSP_CARRIER(5.5), MSP_CARRIER(5.85), "5.5/5.85 B/G NICAM FM" }, + { 0x0009, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 L NICAM AM" }, + { 0x000a, MSP_CARRIER(6.0), MSP_CARRIER(6.55), "6.0/6.55 I NICAM FM" }, + { 0x000b, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 D/K NICAM FM" }, + { 0x000c, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 D/K NICAM FM (HDEV2)" }, + { 0x0020, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M BTSC-Stereo" }, + { 0x0021, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M BTSC-Mono + SAP" }, + { 0x0030, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M EIA-J Japan Stereo" }, + { 0x0040, MSP_CARRIER(10.7), MSP_CARRIER(10.7), "10.7 FM-Stereo Radio" }, + { 0x0050, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5 SAT-Mono" }, + { 0x0051, MSP_CARRIER(7.02), MSP_CARRIER(7.20), "7.02/7.20 SAT-Stereo" }, + { 0x0060, MSP_CARRIER(7.2), MSP_CARRIER(7.2), "7.2 SAT ADR" }, + { -1, 0, 0, NULL }, /* EOF */ +}; + +static int msp3410d_thread(void *data) +{ + struct i2c_client *client = data; + struct msp3400c *msp = i2c_get_clientdata(client); + int mode,val,i,std; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,61) + lock_kernel(); + daemonize(); + sigfillset(¤t->blocked); + strcpy(current->comm,"msp3410 [auto]"); + unlock_kernel(); +#else + daemonize("msp3410 [auto]"); + allow_signal(SIGTERM); +#endif + printk("msp3410: daemon started\n"); + + for (;;) { + d2printk(KERN_DEBUG "msp3410: thread: sleep\n"); + if (msp34xx_sleep(msp,-1)) + goto done; + + d2printk(KERN_DEBUG "msp3410: thread: wakeup\n"); + msp->active = 1; + + if (msp->watch_stereo) { + watch_stereo(client); + msp->active = 0; + continue; + } + + /* some time for the tuner to sync */ + if (msp34xx_sleep(msp,HZ/5)) + goto done; + + restart: + if (msp->mode == MSP_MODE_EXTERN) { + /* no carrier scan needed, just unmute */ + dprintk(KERN_DEBUG "msp3410: thread: no carrier scan\n"); + msp3400c_setvolume(client, msp->muted, + msp->left, msp->right); + continue; + } + msp->restart = 0; + del_timer(&msp->wake_stereo); + msp->watch_stereo = 0; + + /* put into sane state (and mute) */ + msp3400c_reset(client); + + /* start autodetect */ + switch (msp->norm) { + case VIDEO_MODE_PAL: + mode = 0x1003; + std = 1; + break; + case VIDEO_MODE_NTSC: /* BTSC */ + mode = 0x2003; + std = 0x0020; + break; + case VIDEO_MODE_SECAM: + mode = 0x0003; + std = 1; + break; + case VIDEO_MODE_RADIO: + mode = 0x0003; + std = 0x0040; + break; + default: + mode = 0x0003; + std = 1; + break; + } + msp3400c_write(client, I2C_MSP3400C_DEM, 0x30, mode); + msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, std); + + if (debug) { + int i; + for (i = 0; modelist[i].name != NULL; i++) + if (modelist[i].retval == std) + break; + printk(KERN_DEBUG "msp3410: setting mode: %s (0x%04x)\n", + modelist[i].name ? modelist[i].name : "unknown",std); + } + + if (std != 1) { + /* programmed some specific mode */ + val = std; + } else { + /* triggered autodetect */ + for (;;) { + if (msp34xx_sleep(msp,HZ/10)) + goto done; + if (msp->restart) + goto restart; + + /* check results */ + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x7e); + if (val < 0x07ff) + break; + dprintk(KERN_DEBUG "msp3410: detection still in progress\n"); + } + } + for (i = 0; modelist[i].name != NULL; i++) + if (modelist[i].retval == val) + break; + dprintk(KERN_DEBUG "msp3410: current mode: %s (0x%04x)\n", + modelist[i].name ? modelist[i].name : "unknown", + val); + msp->main = modelist[i].main; + msp->second = modelist[i].second; + + if (amsound && (msp->norm == VIDEO_MODE_SECAM) && (val != 0x0009)) { + /* autodetection has failed, let backup */ + dprintk(KERN_DEBUG "msp3410: autodetection failed," + " switching to backup mode: %s (0x%04x)\n", + modelist[8].name ? modelist[8].name : "unknown",val); + val = 0x0009; + msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, val); + } + + /* set various prescales */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x0d, 0x1900); /* scart */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403); /* FM */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x10, 0x5a00); /* nicam */ + + /* set stereo */ + switch (val) { + case 0x0008: /* B/G NICAM */ + case 0x000a: /* I NICAM */ + if (val == 0x0008) + msp->mode = MSP_MODE_FM_NICAM1; + else + msp->mode = MSP_MODE_FM_NICAM2; + /* just turn on stereo */ + msp->stereo = VIDEO_SOUND_STEREO; + msp->nicam_on = 1; + msp->watch_stereo = 1; + msp3400c_setstereo(client,VIDEO_SOUND_STEREO); + break; + case 0x0009: + msp->mode = MSP_MODE_AM_NICAM; + msp->stereo = VIDEO_SOUND_MONO; + msp->nicam_on = 1; + msp3400c_setstereo(client,VIDEO_SOUND_MONO); + msp->watch_stereo = 1; + break; + case 0x0020: /* BTSC */ + /* just turn on stereo */ + msp->mode = MSP_MODE_BTSC; + msp->stereo = VIDEO_SOUND_STEREO; + msp->nicam_on = 0; + msp->watch_stereo = 1; + msp3400c_setstereo(client,VIDEO_SOUND_STEREO); + break; + case 0x0040: /* FM radio */ + msp->mode = MSP_MODE_FM_RADIO; + msp->stereo = VIDEO_SOUND_STEREO; + msp->nicam_on = 0; + msp->watch_stereo = 0; + /* not needed in theory if HAVE_RADIO(), but + short programming enables carrier mute */ + msp3400c_setmode(client,MSP_MODE_FM_RADIO); + msp3400c_setcarrier(client, MSP_CARRIER(10.7), + MSP_CARRIER(10.7)); + /* scart routing */ + msp3400c_set_scart(client,SCART_IN2,0); +#if 0 + /* radio from SCART_IN2 */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x08, 0x0220); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x09, 0x0220); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0b, 0x0220); +#else + /* msp34xx does radio decoding */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x08, 0x0020); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x09, 0x0020); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0b, 0x0020); +#endif + break; + case 0x0003: + case 0x0004: + case 0x0005: + msp->mode = MSP_MODE_FM_TERRA; + msp->stereo = VIDEO_SOUND_MONO; + msp->nicam_on = 0; + msp->watch_stereo = 1; + break; + } + + /* unmute + restore dfp registers */ + msp3400c_setbass(client, msp->bass); + msp3400c_settreble(client, msp->treble); + msp3400c_setvolume(client, msp->muted, msp->left, msp->right); + msp3400c_restore_dfp(client); + + if (msp->watch_stereo) + mod_timer(&msp->wake_stereo, jiffies+HZ); + + msp->active = 0; + } + +done: + msp->active = 0; + dprintk(KERN_DEBUG "msp3410: thread: exit\n"); + complete_and_exit(&msp->texit, 0); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int msp_attach(struct i2c_adapter *adap, int addr, int kind); +#else +static int msp_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind); +#endif +static int msp_detach(struct i2c_client *client); +static int msp_probe(struct i2c_adapter *adap); +static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg); + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) + .owner = THIS_MODULE, +#endif + .name = "i2c msp3400 driver", + .id = I2C_DRIVERID_MSP3400, + .flags = I2C_DF_NOTIFY, + .attach_adapter = msp_probe, + .detach_client = msp_detach, + .command = msp_command, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("(unset)"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int msp_attach(struct i2c_adapter *adap, int addr, int kind) +#else +static int msp_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +#endif +{ + struct msp3400c *msp; + struct i2c_client *c; + int i; + + client_template.adapter = adap; + client_template.addr = addr; + + if (-1 == msp3400c_reset(&client_template)) { + dprintk("msp3400: no chip found\n"); + return -1; + } + + if (NULL == (c = kmalloc(sizeof(struct i2c_client),GFP_KERNEL))) + return -ENOMEM; + memcpy(c,&client_template,sizeof(struct i2c_client)); + if (NULL == (msp = kmalloc(sizeof(struct msp3400c),GFP_KERNEL))) { + kfree(c); + return -ENOMEM; + } + + memset(msp,0,sizeof(struct msp3400c)); + msp->left = 65535; + msp->right = 65535; + msp->bass = 32768; + msp->treble = 32768; + msp->input = -1; + msp->muted = 1; + for (i = 0; i < DFP_COUNT; i++) + msp->dfp_regs[i] = -1; + + i2c_set_clientdata(c, msp); + init_waitqueue_head(&msp->wq); + + if (-1 == msp3400c_reset(c)) { + kfree(msp); + kfree(c); + dprintk("msp3400: no chip found\n"); + return -1; + } + + msp->rev1 = msp3400c_read(c, I2C_MSP3400C_DFP, 0x1e); + if (-1 != msp->rev1) + msp->rev2 = msp3400c_read(c, I2C_MSP3400C_DFP, 0x1f); + if ((-1 == msp->rev1) || (0 == msp->rev1 && 0 == msp->rev2)) { + kfree(msp); + kfree(c); + printk("msp3400: error while reading chip version\n"); + return -1; + } + +#if 0 + /* this will turn on a 1kHz beep - might be useful for debugging... */ + msp3400c_write(c,I2C_MSP3400C_DFP, 0x0014, 0x1040); +#endif + msp3400c_setvolume(c,msp->muted,msp->left,msp->right); + + snprintf(c->name, sizeof(c->name), "MSP34%02d%c-%c%d", + (msp->rev2>>8)&0xff, (msp->rev1&0xff)+'@', + ((msp->rev1>>8)&0xff)+'@', msp->rev2&0x1f); + + if (simple == -1) { + /* default mode */ + msp->simple = HAVE_SIMPLE(msp); + } else { + /* use insmod option */ + msp->simple = simple; + } + + /* timer for stereo checking */ + init_timer(&msp->wake_stereo); + msp->wake_stereo.function = msp3400c_stereo_wake; + msp->wake_stereo.data = (unsigned long)msp; + + /* hello world :-) */ + printk(KERN_INFO "msp34xx: init: chip=%s",i2c_clientname(c)); + if (HAVE_NICAM(msp)) + printk(" +nicam"); + if (HAVE_SIMPLE(msp)) + printk(" +simple"); + if (HAVE_RADIO(msp)) + printk(" +radio"); + printk("\n"); + + /* startup control thread */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_INC_USE_COUNT; +#endif + init_completion(&msp->texit); + msp->tpid = kernel_thread(msp->simple ? msp3410d_thread : msp3400c_thread, + (void *)c, 0); + if (msp->tpid < 0) + printk(KERN_WARNING "msp34xx: kernel_thread() failed\n"); + wake_up_interruptible(&msp->wq); + + /* done */ + i2c_attach_client(c); + return 0; +} + +static int msp_detach(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + /* shutdown control thread */ + del_timer_sync(&msp->wake_stereo); + if (msp->tpid >= 0) { + msp->rmmod = 1; + wake_up_interruptible(&msp->wq); + wait_for_completion(&msp->texit); + } + msp3400c_reset(client); + + i2c_detach_client(client); + kfree(msp); + kfree(client); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_DEC_USE_COUNT; +#endif + return 0; +} + +static int msp_probe(struct i2c_adapter *adap) +{ +#ifdef I2C_ADAP_CLASS_TV_ANALOG + if (adap->class & I2C_ADAP_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, msp_attach); +#else + switch (adap->id) { + case I2C_ALGO_BIT | I2C_HW_SMBUS_VOODOO3: + case I2C_ALGO_BIT | I2C_HW_B_BT848: + //case I2C_ALGO_SAA7134: + return i2c_probe(adap, &addr_data, msp_attach); + break; + } +#endif + return 0; +} + +static void msp_wake_thread(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + msp3400c_setvolume(client,msp->muted,0,0); + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + if (msp->active) + msp->restart = 1; + wake_up_interruptible(&msp->wq); +} + +static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + __u16 *sarg = arg; + int scart = 0; + + switch (cmd) { + + case AUDC_SET_INPUT: + dprintk(KERN_DEBUG "msp34xx: AUDC_SET_INPUT(%d)\n",*sarg); + if (*sarg == msp->input) + break; + msp->input = *sarg; + switch (*sarg) { + case AUDIO_RADIO: + /* Hauppauge uses IN2 for the radio */ + msp->mode = MSP_MODE_FM_RADIO; + scart = SCART_IN2; + break; + case AUDIO_EXTERN_1: + /* IN1 is often used for external input ... */ + msp->mode = MSP_MODE_EXTERN; + scart = SCART_IN1; + break; + case AUDIO_EXTERN_2: + /* ... sometimes it is IN2 through ;) */ + msp->mode = MSP_MODE_EXTERN; + scart = SCART_IN2; + break; + case AUDIO_TUNER: + msp->mode = -1; + msp_wake_thread(client); + break; + default: + if (*sarg & AUDIO_MUTE) + msp3400c_set_scart(client,SCART_MUTE,0); + break; + } + if (scart) { + msp->stereo = VIDEO_SOUND_STEREO; + msp3400c_set_scart(client,scart,0); + msp3400c_write(client,I2C_MSP3400C_DFP,0x000d,0x1900); + msp3400c_setstereo(client,msp->stereo); + } + if (msp->active) + msp->restart = 1; + break; + + case AUDC_SET_RADIO: + dprintk(KERN_DEBUG "msp34xx: AUDC_SET_RADIO\n"); + msp->norm = VIDEO_MODE_RADIO; + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + dprintk(KERN_DEBUG "msp34xx: switching to radio mode\n"); + if (msp->simple) { + /* the thread will do for us */ + msp_wake_thread(client); + } else { + /* set msp3400 to FM radio mode */ + msp3400c_setmode(client,MSP_MODE_FM_RADIO); + msp3400c_setcarrier(client, MSP_CARRIER(10.7),MSP_CARRIER(10.7)); + msp3400c_setvolume(client,msp->muted,msp->left,msp->right); + } + if (msp->active) + msp->restart = 1; + break; + +#if 1 + /* work-in-progress: hook to control the DFP registers */ + case MSP_SET_DFPREG: + { + struct msp_dfpreg *r = arg; + unsigned int i; + + if (r->reg < 0 || r->reg >= DFP_COUNT) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(bl_dfp); i++) + if (r->reg == bl_dfp[i]) + return -EINVAL; + msp->dfp_regs[r->reg] = r->value; + msp3400c_write(client,I2C_MSP3400C_DFP,r->reg,r->value); + return 0; + } + case MSP_GET_DFPREG: + { + struct msp_dfpreg *r = arg; + + if (r->reg < 0 || r->reg >= DFP_COUNT) + return -EINVAL; + r->value = msp3400c_read(client,I2C_MSP3400C_DFP,r->reg); + return 0; + } +#endif + + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + + dprintk(KERN_DEBUG "msp34xx: VIDIOCGAUDIO\n"); + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE | + VIDEO_AUDIO_MUTABLE; + if (msp->muted) + va->flags |= VIDEO_AUDIO_MUTE; + va->volume=max(msp->left,msp->right); + + if (0 == va->volume) { + va->balance = 32768; + } else { + va->balance = (32768 * min(msp->left,msp->right)) + / va->volume; + va->balance = (msp->left<msp->right) ? + (65535 - va->balance) : va->balance; + } + va->bass = msp->bass; + va->treble = msp->treble; + + if (msp->norm != VIDEO_MODE_RADIO) { + autodetect_stereo(client); + va->mode = msp->stereo; + } + break; + } + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + + dprintk(KERN_DEBUG "msp34xx: VIDIOCSAUDIO\n"); + msp->muted = (va->flags & VIDEO_AUDIO_MUTE); + msp->left = (min(65536 - va->balance,32768) * + va->volume) / 32768; + msp->right = (min(va->balance,(__u16)32768) * + va->volume) / 32768; + msp->bass = va->bass; + msp->treble = va->treble; + msp3400c_setvolume(client,msp->muted,msp->left,msp->right); + msp3400c_setbass(client,msp->bass); + msp3400c_settreble(client,msp->treble); + + if (va->mode != 0 && msp->norm != VIDEO_MODE_RADIO) { + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + msp->stereo = va->mode & 0x0f; + msp3400c_setstereo(client,va->mode & 0x0f); + } + break; + } + case VIDIOCSCHAN: + { + struct video_channel *vc = arg; + + dprintk(KERN_DEBUG "msp34xx: VIDIOCSCHAN (norm=%d)\n",vc->norm); + msp->norm = vc->norm; + break; + } + case VIDIOCSFREQ: + { + /* new channel -- kick audio carrier scan */ + dprintk(KERN_DEBUG "msp34xx: VIDIOCSFREQ\n"); + msp_wake_thread(client); + break; + } + + default: + /* nothing */ + break; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int msp3400_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void msp3400_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(msp3400_init_module); +module_exit(msp3400_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/msp3400.h b/linux/drivers/media/video/msp3400.h new file mode 100644 index 000000000..9673133f2 --- /dev/null +++ b/linux/drivers/media/video/msp3400.h @@ -0,0 +1,14 @@ +#ifndef MSP3400_H +#define MSP3400_H + +/* ---------------------------------------------------------------------- */ + +struct msp_dfpreg { + int reg; + int value; +}; + +#define MSP_SET_DFPREG _IOW('m',15,struct msp_dfpreg) +#define MSP_GET_DFPREG _IOW('m',16,struct msp_dfpreg) + +#endif /* MSP3400_H */ diff --git a/linux/drivers/media/video/saa6752hs.c b/linux/drivers/media/video/saa6752hs.c new file mode 100644 index 000000000..78273dbc3 --- /dev/null +++ b/linux/drivers/media/video/saa6752hs.c @@ -0,0 +1,441 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev.h> +#include <linux/init.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "i2c-compat.h" +#include "id.h" +#include "saa6752hs.h" +#else +#include <media/id.h> +#include <media/saa6752hs.h> +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,71) +# define strlcpy(dest,src,len) strncpy(dest,src,(len)-1) +#endif + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {0x20, I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +MODULE_DESCRIPTION("device driver for saa6752hs MPEG2 encoder"); +MODULE_AUTHOR("Andrew de Quincey"); +MODULE_LICENSE("GPL"); + +static struct i2c_driver driver; +static struct i2c_client client_template; + + +enum saa6752hs_command { + SAA6752HS_COMMAND_RESET = 0, + SAA6752HS_COMMAND_STOP = 1, + SAA6752HS_COMMAND_START = 2, + SAA6752HS_COMMAND_PAUSE = 3, + SAA6752HS_COMMAND_RECONFIGURE = 4, + SAA6752HS_COMMAND_SLEEP = 5, + SAA6752HS_COMMAND_RECONFIGURE_FORCE = 6, + + SAA6752HS_COMMAND_MAX +}; + + +/* ---------------------------------------------------------------------- */ + +static u8 PAT[] = { + 0xc2, // i2c register + 0x00, // table number for encoder + + 0x47, // sync + 0x40, 0x00, // transport_error_indicator(0), payload_unit_start(1), transport_priority(0), pid(0) + 0x10, // transport_scrambling_control(00), adaptation_field_control(01), continuity_counter(0) + + 0x00, // PSI pointer to start of table + + 0x00, // tid(0) + 0xb0, 0x0d, // section_syntax_indicator(1), section_length(13) + + 0x00, 0x01, // transport_stream_id(1) + + 0xc1, // version_number(0), current_next_indicator(1) + + 0x00, 0x00, // section_number(0), last_section_number(0) + + 0x00, 0x01, // program_number(1) + + 0xe0, 0x10, // PMT PID(0x10) + + 0x76, 0xf1, 0x44, 0xd1 // CRC32 +}; + +static u8 PMT[] = { + 0xc2, // i2c register + 0x01, // table number for encoder + + 0x47, // sync + 0x40, 0x10, // transport_error_indicator(0), payload_unit_start(1), transport_priority(0), pid(0x10) + 0x10, // transport_scrambling_control(00), adaptation_field_control(01), continuity_counter(0) + + 0x00, // PSI pointer to start of table + + 0x02, // tid(2) + 0xb0, 0x17, // section_syntax_indicator(1), section_length(23) + + 0x00, 0x01, // program_number(1) + + 0xc1, // version_number(0), current_next_indicator(1) + + 0x00, 0x00, // section_number(0), last_section_number(0) + + 0xe1, 0x04, // PCR_PID (0x104) + + 0xf0, 0x00, // program_info_length(0) + + 0x02, 0xe1, 0x00, 0xf0, 0x00, // video stream type(2), pid(0x100) + 0x04, 0xe1, 0x03, 0xf0, 0x00, // audio stream type(4), pid(0x103) + + 0xa1, 0xca, 0x0f, 0x82 // CRC32 +}; + +static struct mpeg_params mpeg_params_template = +{ + .bitrate_mode = MPEG_BITRATE_MODE_CBR, + .video_target_bitrate = 5000, + .audio_bitrate = MPEG_AUDIO_BITRATE_256, + .total_bitrate = 6000, +}; + + +/* ---------------------------------------------------------------------- */ + + +static int saa6752hs_chip_command(struct i2c_client* client, + enum saa6752hs_command command) +{ + unsigned char buf[3]; + unsigned long timeout; + int status = 0; + + // execute the command + switch(command) { + case SAA6752HS_COMMAND_RESET: + buf[0] = 0x00; + break; + + case SAA6752HS_COMMAND_STOP: + buf[0] = 0x03; + break; + + case SAA6752HS_COMMAND_START: + buf[0] = 0x02; + break; + + case SAA6752HS_COMMAND_PAUSE: + buf[0] = 0x04; + break; + + case SAA6752HS_COMMAND_RECONFIGURE: + buf[0] = 0x05; + break; + + case SAA6752HS_COMMAND_SLEEP: + buf[0] = 0x06; + break; + + case SAA6752HS_COMMAND_RECONFIGURE_FORCE: + buf[0] = 0x07; + break; + + default: + return -EINVAL; + } + + // set it and wait for it to be so + i2c_master_send(client, buf, 1); + timeout = jiffies + HZ * 3; + for (;;) { + // get the current status + buf[0] = 0x10; + i2c_master_send(client, buf, 1); + i2c_master_recv(client, buf, 1); + + if (!(buf[0] & 0x20)) + break; + if (time_after(jiffies,timeout)) { + status = -ETIMEDOUT; + break; + } + + // wait a bit + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/100); + } + + // delay a bit to let encoder settle + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/20); + + // done + return status; +} + + +static int saa6752hs_set_bitrate(struct i2c_client* client, + struct mpeg_params* params) +{ + u8 buf[3]; + + // set the bitrate mode + buf[0] = 0x71; + buf[1] = params->bitrate_mode; + i2c_master_send(client, buf, 2); + + // set the video bitrate + if (params->bitrate_mode == MPEG_BITRATE_MODE_VBR) { + // set the target bitrate + buf[0] = 0x80; + buf[1] = params->video_target_bitrate >> 8; + buf[2] = params->video_target_bitrate & 0xff; + i2c_master_send(client, buf, 3); + + // set the max bitrate + buf[0] = 0x81; + buf[1] = params->video_max_bitrate >> 8; + buf[2] = params->video_max_bitrate & 0xff; + i2c_master_send(client, buf, 3); + } else { + // set the target bitrate (no max bitrate for CBR) + buf[0] = 0x81; + buf[1] = params->video_target_bitrate >> 8; + buf[2] = params->video_target_bitrate & 0xff; + i2c_master_send(client, buf, 3); + } + + // set the audio bitrate + buf[0] = 0x94; + buf[1] = params->audio_bitrate; + i2c_master_send(client, buf, 2); + + // set the total bitrate + buf[0] = 0xb1; + buf[1] = params->total_bitrate >> 8; + buf[2] = params->total_bitrate & 0xff; + i2c_master_send(client, buf, 3); + + return 0; +} + + +static int saa6752hs_init(struct i2c_client* client, struct mpeg_params* params) +{ + unsigned char buf[3]; + void *data; + + // check the bitrate parameters first + if (params != NULL) { + if (params->bitrate_mode >= MPEG_BITRATE_MODE_MAX) + return -EINVAL; + if (params->video_target_bitrate >= MPEG_VIDEO_TARGET_BITRATE_MAX) + return -EINVAL; + if (params->video_max_bitrate >= MPEG_VIDEO_MAX_BITRATE_MAX) + return -EINVAL; + if (params->audio_bitrate >= MPEG_AUDIO_BITRATE_MAX) + return -EINVAL; + if (params->total_bitrate >= MPEG_TOTAL_BITRATE_MAX) + return -EINVAL; + if (params->bitrate_mode == MPEG_BITRATE_MODE_MAX && + params->video_target_bitrate <= params->video_max_bitrate) + return -EINVAL; + } + + // Set GOP structure {3, 13} + buf[0] = 0x72; + buf[1] = 0x03; + buf[2] = 0x0D; + i2c_master_send(client,buf,3); + + // Set minimum Q-scale {4} + buf[0] = 0x82; + buf[1] = 0x04; + i2c_master_send(client,buf,2); + + // Set maximum Q-scale {12} + buf[0] = 0x83; + buf[1] = 0x0C; + i2c_master_send(client,buf,2); + + // Set Output Protocol + buf[0] = 0xD0; + buf[1] = 0x01; + i2c_master_send(client,buf,2); + + // Set video output stream format {TS} + buf[0] = 0xB0; + buf[1] = 0x05; + i2c_master_send(client,buf,2); + + // Set Audio PID {0x103} + buf[0] = 0xC1; + buf[1] = 0x01; + buf[2] = 0x03; + i2c_master_send(client,buf,3); + + // setup bitrate settings + data = i2c_get_clientdata(client); + if (params) { + saa6752hs_set_bitrate(client, params); + memcpy(data, params, sizeof(struct mpeg_params)); + } else { + // parameters were not supplied. use the previous set + saa6752hs_set_bitrate(client, (struct mpeg_params*) data); + } + + // Send SI tables + i2c_master_send(client,PAT,sizeof(PAT)); + i2c_master_send(client,PMT,sizeof(PMT)); + + // mute then unmute audio. This removes buzzing artefacts + buf[0] = 0xa4; + buf[1] = 1; + i2c_master_send(client, buf, 2); + buf[1] = 0; + i2c_master_send(client, buf, 2); + + // start it going + saa6752hs_chip_command(client, SAA6752HS_COMMAND_START); + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int saa6752hs_attach(struct i2c_adapter *adap, int addr, int kind) +#else +static int saa6752hs_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +#endif +{ + struct i2c_client *client; + struct mpeg_params* params; + + client_template.adapter = adap; + client_template.addr = addr; + + printk("saa6752hs: chip found @ 0x%x\n", addr<<1); + + if (NULL == (client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL))) + return -ENOMEM; + memcpy(client,&client_template,sizeof(struct i2c_client)); + strlcpy(client->name, "saa6752hs", sizeof(client->name)); + + if (NULL == (params = kmalloc(sizeof(struct mpeg_params), GFP_KERNEL))) + return -ENOMEM; + memcpy(params,&mpeg_params_template,sizeof(struct mpeg_params)); + i2c_set_clientdata(client, params); + + i2c_attach_client(client); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_INC_USE_COUNT; +#endif + return 0; +} + +static int saa6752hs_probe(struct i2c_adapter *adap) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) + if (adap->class & I2C_ADAP_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, saa6752hs_attach); +#else + switch (adap->id) { + case I2C_ALGO_SAA7134: + return i2c_probe(adap, &addr_data, saa6752hs_attach); + break; + } +#endif + + return 0; +} + +static int saa6752hs_detach(struct i2c_client *client) +{ + void *data; + + data = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(data); + kfree(client); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_DEC_USE_COUNT; +#endif + return 0; +} + +static int +saa6752hs_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct mpeg_params* init_arg = arg; + + switch (cmd) { + case MPEG_SETPARAMS: + return saa6752hs_init(client, init_arg); + + default: + /* nothing */ + break; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif + .name = "i2c saa6752hs MPEG encoder", + .id = I2C_DRIVERID_SAA6752HS, + .flags = I2C_DF_NOTIFY, + .attach_adapter = saa6752hs_probe, + .detach_client = saa6752hs_detach, + .command = saa6752hs_command, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("(saa6752hs unset)"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + +static int saa6752hs_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void saa6752hs_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(saa6752hs_init_module); +module_exit(saa6752hs_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/saa7134/saa6752hs.c b/linux/drivers/media/video/saa7134/saa6752hs.c new file mode 100644 index 000000000..78273dbc3 --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa6752hs.c @@ -0,0 +1,441 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev.h> +#include <linux/init.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "i2c-compat.h" +#include "id.h" +#include "saa6752hs.h" +#else +#include <media/id.h> +#include <media/saa6752hs.h> +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,71) +# define strlcpy(dest,src,len) strncpy(dest,src,(len)-1) +#endif + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {0x20, I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +MODULE_DESCRIPTION("device driver for saa6752hs MPEG2 encoder"); +MODULE_AUTHOR("Andrew de Quincey"); +MODULE_LICENSE("GPL"); + +static struct i2c_driver driver; +static struct i2c_client client_template; + + +enum saa6752hs_command { + SAA6752HS_COMMAND_RESET = 0, + SAA6752HS_COMMAND_STOP = 1, + SAA6752HS_COMMAND_START = 2, + SAA6752HS_COMMAND_PAUSE = 3, + SAA6752HS_COMMAND_RECONFIGURE = 4, + SAA6752HS_COMMAND_SLEEP = 5, + SAA6752HS_COMMAND_RECONFIGURE_FORCE = 6, + + SAA6752HS_COMMAND_MAX +}; + + +/* ---------------------------------------------------------------------- */ + +static u8 PAT[] = { + 0xc2, // i2c register + 0x00, // table number for encoder + + 0x47, // sync + 0x40, 0x00, // transport_error_indicator(0), payload_unit_start(1), transport_priority(0), pid(0) + 0x10, // transport_scrambling_control(00), adaptation_field_control(01), continuity_counter(0) + + 0x00, // PSI pointer to start of table + + 0x00, // tid(0) + 0xb0, 0x0d, // section_syntax_indicator(1), section_length(13) + + 0x00, 0x01, // transport_stream_id(1) + + 0xc1, // version_number(0), current_next_indicator(1) + + 0x00, 0x00, // section_number(0), last_section_number(0) + + 0x00, 0x01, // program_number(1) + + 0xe0, 0x10, // PMT PID(0x10) + + 0x76, 0xf1, 0x44, 0xd1 // CRC32 +}; + +static u8 PMT[] = { + 0xc2, // i2c register + 0x01, // table number for encoder + + 0x47, // sync + 0x40, 0x10, // transport_error_indicator(0), payload_unit_start(1), transport_priority(0), pid(0x10) + 0x10, // transport_scrambling_control(00), adaptation_field_control(01), continuity_counter(0) + + 0x00, // PSI pointer to start of table + + 0x02, // tid(2) + 0xb0, 0x17, // section_syntax_indicator(1), section_length(23) + + 0x00, 0x01, // program_number(1) + + 0xc1, // version_number(0), current_next_indicator(1) + + 0x00, 0x00, // section_number(0), last_section_number(0) + + 0xe1, 0x04, // PCR_PID (0x104) + + 0xf0, 0x00, // program_info_length(0) + + 0x02, 0xe1, 0x00, 0xf0, 0x00, // video stream type(2), pid(0x100) + 0x04, 0xe1, 0x03, 0xf0, 0x00, // audio stream type(4), pid(0x103) + + 0xa1, 0xca, 0x0f, 0x82 // CRC32 +}; + +static struct mpeg_params mpeg_params_template = +{ + .bitrate_mode = MPEG_BITRATE_MODE_CBR, + .video_target_bitrate = 5000, + .audio_bitrate = MPEG_AUDIO_BITRATE_256, + .total_bitrate = 6000, +}; + + +/* ---------------------------------------------------------------------- */ + + +static int saa6752hs_chip_command(struct i2c_client* client, + enum saa6752hs_command command) +{ + unsigned char buf[3]; + unsigned long timeout; + int status = 0; + + // execute the command + switch(command) { + case SAA6752HS_COMMAND_RESET: + buf[0] = 0x00; + break; + + case SAA6752HS_COMMAND_STOP: + buf[0] = 0x03; + break; + + case SAA6752HS_COMMAND_START: + buf[0] = 0x02; + break; + + case SAA6752HS_COMMAND_PAUSE: + buf[0] = 0x04; + break; + + case SAA6752HS_COMMAND_RECONFIGURE: + buf[0] = 0x05; + break; + + case SAA6752HS_COMMAND_SLEEP: + buf[0] = 0x06; + break; + + case SAA6752HS_COMMAND_RECONFIGURE_FORCE: + buf[0] = 0x07; + break; + + default: + return -EINVAL; + } + + // set it and wait for it to be so + i2c_master_send(client, buf, 1); + timeout = jiffies + HZ * 3; + for (;;) { + // get the current status + buf[0] = 0x10; + i2c_master_send(client, buf, 1); + i2c_master_recv(client, buf, 1); + + if (!(buf[0] & 0x20)) + break; + if (time_after(jiffies,timeout)) { + status = -ETIMEDOUT; + break; + } + + // wait a bit + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/100); + } + + // delay a bit to let encoder settle + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/20); + + // done + return status; +} + + +static int saa6752hs_set_bitrate(struct i2c_client* client, + struct mpeg_params* params) +{ + u8 buf[3]; + + // set the bitrate mode + buf[0] = 0x71; + buf[1] = params->bitrate_mode; + i2c_master_send(client, buf, 2); + + // set the video bitrate + if (params->bitrate_mode == MPEG_BITRATE_MODE_VBR) { + // set the target bitrate + buf[0] = 0x80; + buf[1] = params->video_target_bitrate >> 8; + buf[2] = params->video_target_bitrate & 0xff; + i2c_master_send(client, buf, 3); + + // set the max bitrate + buf[0] = 0x81; + buf[1] = params->video_max_bitrate >> 8; + buf[2] = params->video_max_bitrate & 0xff; + i2c_master_send(client, buf, 3); + } else { + // set the target bitrate (no max bitrate for CBR) + buf[0] = 0x81; + buf[1] = params->video_target_bitrate >> 8; + buf[2] = params->video_target_bitrate & 0xff; + i2c_master_send(client, buf, 3); + } + + // set the audio bitrate + buf[0] = 0x94; + buf[1] = params->audio_bitrate; + i2c_master_send(client, buf, 2); + + // set the total bitrate + buf[0] = 0xb1; + buf[1] = params->total_bitrate >> 8; + buf[2] = params->total_bitrate & 0xff; + i2c_master_send(client, buf, 3); + + return 0; +} + + +static int saa6752hs_init(struct i2c_client* client, struct mpeg_params* params) +{ + unsigned char buf[3]; + void *data; + + // check the bitrate parameters first + if (params != NULL) { + if (params->bitrate_mode >= MPEG_BITRATE_MODE_MAX) + return -EINVAL; + if (params->video_target_bitrate >= MPEG_VIDEO_TARGET_BITRATE_MAX) + return -EINVAL; + if (params->video_max_bitrate >= MPEG_VIDEO_MAX_BITRATE_MAX) + return -EINVAL; + if (params->audio_bitrate >= MPEG_AUDIO_BITRATE_MAX) + return -EINVAL; + if (params->total_bitrate >= MPEG_TOTAL_BITRATE_MAX) + return -EINVAL; + if (params->bitrate_mode == MPEG_BITRATE_MODE_MAX && + params->video_target_bitrate <= params->video_max_bitrate) + return -EINVAL; + } + + // Set GOP structure {3, 13} + buf[0] = 0x72; + buf[1] = 0x03; + buf[2] = 0x0D; + i2c_master_send(client,buf,3); + + // Set minimum Q-scale {4} + buf[0] = 0x82; + buf[1] = 0x04; + i2c_master_send(client,buf,2); + + // Set maximum Q-scale {12} + buf[0] = 0x83; + buf[1] = 0x0C; + i2c_master_send(client,buf,2); + + // Set Output Protocol + buf[0] = 0xD0; + buf[1] = 0x01; + i2c_master_send(client,buf,2); + + // Set video output stream format {TS} + buf[0] = 0xB0; + buf[1] = 0x05; + i2c_master_send(client,buf,2); + + // Set Audio PID {0x103} + buf[0] = 0xC1; + buf[1] = 0x01; + buf[2] = 0x03; + i2c_master_send(client,buf,3); + + // setup bitrate settings + data = i2c_get_clientdata(client); + if (params) { + saa6752hs_set_bitrate(client, params); + memcpy(data, params, sizeof(struct mpeg_params)); + } else { + // parameters were not supplied. use the previous set + saa6752hs_set_bitrate(client, (struct mpeg_params*) data); + } + + // Send SI tables + i2c_master_send(client,PAT,sizeof(PAT)); + i2c_master_send(client,PMT,sizeof(PMT)); + + // mute then unmute audio. This removes buzzing artefacts + buf[0] = 0xa4; + buf[1] = 1; + i2c_master_send(client, buf, 2); + buf[1] = 0; + i2c_master_send(client, buf, 2); + + // start it going + saa6752hs_chip_command(client, SAA6752HS_COMMAND_START); + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int saa6752hs_attach(struct i2c_adapter *adap, int addr, int kind) +#else +static int saa6752hs_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +#endif +{ + struct i2c_client *client; + struct mpeg_params* params; + + client_template.adapter = adap; + client_template.addr = addr; + + printk("saa6752hs: chip found @ 0x%x\n", addr<<1); + + if (NULL == (client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL))) + return -ENOMEM; + memcpy(client,&client_template,sizeof(struct i2c_client)); + strlcpy(client->name, "saa6752hs", sizeof(client->name)); + + if (NULL == (params = kmalloc(sizeof(struct mpeg_params), GFP_KERNEL))) + return -ENOMEM; + memcpy(params,&mpeg_params_template,sizeof(struct mpeg_params)); + i2c_set_clientdata(client, params); + + i2c_attach_client(client); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_INC_USE_COUNT; +#endif + return 0; +} + +static int saa6752hs_probe(struct i2c_adapter *adap) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) + if (adap->class & I2C_ADAP_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, saa6752hs_attach); +#else + switch (adap->id) { + case I2C_ALGO_SAA7134: + return i2c_probe(adap, &addr_data, saa6752hs_attach); + break; + } +#endif + + return 0; +} + +static int saa6752hs_detach(struct i2c_client *client) +{ + void *data; + + data = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(data); + kfree(client); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_DEC_USE_COUNT; +#endif + return 0; +} + +static int +saa6752hs_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct mpeg_params* init_arg = arg; + + switch (cmd) { + case MPEG_SETPARAMS: + return saa6752hs_init(client, init_arg); + + default: + /* nothing */ + break; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif + .name = "i2c saa6752hs MPEG encoder", + .id = I2C_DRIVERID_SAA6752HS, + .flags = I2C_DF_NOTIFY, + .attach_adapter = saa6752hs_probe, + .detach_client = saa6752hs_detach, + .command = saa6752hs_command, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("(saa6752hs unset)"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + +static int saa6752hs_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void saa6752hs_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(saa6752hs_init_module); +module_exit(saa6752hs_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/saa7134/saa7134-cards.c b/linux/drivers/media/video/saa7134/saa7134-cards.c new file mode 100644 index 000000000..f60777a6e --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa7134-cards.c @@ -0,0 +1,1253 @@ +/* + * device driver for philips saa7134 based TV cards + * card-specific stuff. + * + * (c) 2001-03 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * 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/init.h> +#include <linux/module.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* commly used strings */ +static char name_mute[] = "mute"; +static char name_radio[] = "Radio"; +static char name_tv[] = "Television"; +static char name_tv_mono[] = "TV (mono only)"; +static char name_comp1[] = "Composite1"; +static char name_comp2[] = "Composite2"; +static char name_comp3[] = "Composite3"; +static char name_comp4[] = "Composite4"; +static char name_svideo[] = "S-Video"; + +/* ------------------------------------------------------------------ */ +/* board config info */ + +struct saa7134_board saa7134_boards[] = { + [SAA7134_BOARD_UNKNOWN] = { + .name = "UNKNOWN/GENERIC", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_ABSENT, + .inputs = {{ + .name = "default", + .vmux = 0, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_PROTEUS_PRO] = { + /* /me */ + .name = "Proteus Pro [philips reference design]", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_comp1, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_FLYVIDEO3000] = { + /* "Marco d'Itri" <md@Linux.IT> */ + .name = "LifeView FlyVIDEO3000", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .gpiomask = 0xe000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .gpio = 0x8000, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + .gpio = 0x4000, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x2000, + }, + }, + [SAA7134_BOARD_FLYVIDEO2000] = { + /* "TC Wan" <tcwan@cs.usm.my> */ + .name = "LifeView FlyVIDEO2000", + .audio_clock = 0x00200000, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .gpiomask = 0xe000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + .gpio = 0x4000, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x2000, + }, + .mute = { + .name = name_mute, + .amux = LINE2, + .gpio = 0x8000, + }, + }, + [SAA7134_BOARD_EMPRESS] = { + /* "Gert Vervoort" <gert.vervoort@philips.com> */ + .name = "EMPRESS", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_comp1, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + .i2s_rate = 48000, + .has_ts = 1, + .video_out = CCIR656, + }, + [SAA7134_BOARD_MONSTERTV] = { + /* "K.Ohta" <alpha292@bremen.or.jp> */ + .name = "SKNet Monster TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_MD9717] = { + .name = "Tevion MD 9717", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + /* workaround for problems with normal TV sound */ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 2, + .amux = LINE1, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_TVSTATION_RDS] = { + /* Typhoon TV Tuner RDS: Art.Nr. 50694 */ + .name = "KNC One TV-Station RDS / Typhoon TV Tuner RDS", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .need_tda9887 = 1, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + + .name = "CVid over SVid", + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_TVSTATION_DVR] = { + .name = "KNC One TV-Station DVR", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .need_tda9887 = 1, + .gpiomask = 0x820000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + .gpio = 0x20000, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + .gpio = 0x20000, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x20000, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x20000, + }, + .i2s_rate = 48000, + .has_ts = 1, + .video_out = CCIR656, + }, + [SAA7134_BOARD_CINERGY400] = { + .name = "Terratec Cinergy 400 TV", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 4, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp2, // CVideo over SVideo Connector + .vmux = 0, + .amux = LINE1, + }} + }, + [SAA7134_BOARD_MD5044] = { + .name = "Medion 5044", + .audio_clock = 0x00187de7, // was: 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .need_tda9887 = 1, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + /* workaround for problems with normal TV sound */ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_KWORLD] = { + .name = "Kworld/KuroutoShikou SAA7130-TVPCI", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_CINERGY600] = { + .name = "Terratec Cinergy 600 TV", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 4, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp2, // CVideo over SVideo Connector + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_MD7134] = { + .name = "Medion 7134", + //.audio_clock = 0x00200000, + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .need_tda9887 = 1, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, +#if 0 + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, +#endif + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_TYPHOON_90031] = { + /* aka Typhoon "TV+Radio", Art.Nr 90031 */ + /* Tom Zoerner <tomzo at users sourceforge net> */ + .name = "Typhoon TV+Radio 90031", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .need_tda9887 = 1, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_ELSA] = { + .name = "ELSA EX-VISION 300TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_HITACHI_NTSC, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 4, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_ELSA_500TV] = { + .name = "ELSA EX-VISION 500TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_HITACHI_NTSC, + .inputs = {{ + .name = name_svideo, + .vmux = 7, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 8, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 8, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_ASUSTeK_TVFM7134] = { + .name = "ASUS TV-FM 7134", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .need_tda9887 = 1, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, +#if 0 /* untested */ + },{ + .name = name_comp1, + .vmux = 4, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 2, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 6, + .amux = LINE2, + },{ + .name = "S-Video2", + .vmux = 7, + .amux = LINE2, +#endif + }}, + .radio = { + .name = name_radio, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_VA1000POWER] = { + .name = "AOPEN VA1000 POWER", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_10MOONSTVMASTER] = { + /* "lilicheng" <llc@linuxfans.org> */ + .name = "10MOONS PCI TV CAPTURE CARD", + .audio_clock = 0x00200000, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .gpiomask = 0xe000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + .gpio = 0x4000, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x2000, + }, + .mute = { + .name = name_mute, + .amux = LINE2, + .gpio = 0x8000, + }, + }, + [SAA7134_BOARD_BMK_MPEX_NOTUNER] = { + /* "Andrew de Quincey" <adq@lidskialf.net> */ + .name = "BMK MPEX No Tuner", + .audio_clock = 0x200000, + .tuner_type = TUNER_ABSENT, + .inputs = {{ + .name = name_comp1, + .vmux = 4, + .amux = LINE1, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_comp3, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_comp4, + .vmux = 1, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + }}, + .i2s_rate = 48000, + .has_ts = 1, + .video_out = CCIR656, + }, + [SAA7134_BOARD_VIDEOMATE_TV] = { + .name = "Compro VideoMate TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_CRONOS_PLUS] = { + /* gpio pins: + 0 .. 3 BASE_ID + 4 .. 7 PROTECT_ID + 8 .. 11 USER_OUT + 12 .. 13 USER_IN + 14 .. 15 VIDIN_SEL */ + .name = "Matrox CronosPlus", + .tuner_type = TUNER_ABSENT, + .gpiomask = 0xcf00, + .inputs = {{ + .name = name_comp1, + .vmux = 0, + .gpio = 2 << 14, + },{ + .name = name_comp2, + .vmux = 0, + .gpio = 1 << 14, + },{ + .name = name_comp3, + .vmux = 0, + .gpio = 0 << 14, + },{ + .name = name_comp4, + .vmux = 0, + .gpio = 3 << 14, + },{ + .name = name_svideo, + .vmux = 8, + .gpio = 2 << 14, + }}, + }, + [SAA7134_BOARD_MD2819] = { + .name = "Medion 2819/ AverMedia M156", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .need_tda9887 = 1, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BMK_MPEX_TUNER] = { + /* "Greg Wickham <greg.wickham@grangenet.net> */ + .name = "BMK MPEX Tuner", + .audio_clock = 0x200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_comp1, + .vmux = 1, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 3, + .amux = LINE2, + .tv = 1, + }}, + .i2s_rate = 48000, + .has_ts = 1, + .video_out = CCIR656, + }, + [SAA7134_BOARD_ASUSTEK_TVFM7133] = { + .name = "ASUS TV-FM 7133", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1236_MK3, + .need_tda9887 = 1, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + }}, + .radio = { + .name = name_radio, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_PINNACLE_PCTV_STEREO] = { + .name = "Pinnacle PCTV Stereo (saa7134)", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_MT2032, + .need_tda9887 = 1, + .inputs = {{ + .name = name_tv, + .vmux = 3, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 1, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_MANLI_MTV002] = { + /* Ognjen Nastic <ognjen@logosoft.ba> */ + .name = "Manli MuchTV M-TV002", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 1, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 3, + .amux = LINE2, + .tv = 1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_MANLI_MTV001] = { + /* Ognjen Nastic <ognjen@logosoft.ba> UNTESTED */ + .name = "Manli MuchTV M-TV001", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 1, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 3, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_TG3000TV] = { + /* TransGear 3000TV */ + .name = "Nagase Sangyo TransGear 3000TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_ECS_TVP3XP] = { + .name = "Elitegroup ECS TVP3XP FM1216 Tuner Card(PAL-BG,FM) ", + .audio_clock = 0x187de7, // xtal 32.1 MHz + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = "CVid over SVid", + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_ECS_TVP3XP_4CB5] = { + .name = "Elitegroup ECS TVP3XP FM1236 Tuner Card (NTSC,FM)", + .audio_clock = 0x187de7, + .tuner_type = TUNER_PHILIPS_NTSC, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = "CVid over SVid", + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_AVACSSMARTTV] = { + /* Roman Pszonczenko <romka@kolos.math.uni.lodz.pl> */ + .name = "AVACS SmartTV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x200000, + }, + }, + [SAA7134_BOARD_AVERMEDIA_DVD_EZMAKER] = { + /* Michael Smith <msmith@cbnco.com> */ + .name = "AVerMedia DVD EZMaker", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_ABSENT, + .inputs = {{ + .name = name_comp1, + .vmux = 3, + }}, + }, +}; +const unsigned int saa7134_bcount = ARRAY_SIZE(saa7134_boards); + +/* ------------------------------------------------------------------ */ +/* PCI ids + subsystem IDs */ + +struct pci_device_id saa7134_pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0x2001, + .driver_data = SAA7134_BOARD_PROTEUS_PRO, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0x2001, + .driver_data = SAA7134_BOARD_PROTEUS_PRO, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0x6752, + .driver_data = SAA7134_BOARD_EMPRESS, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1131, + .subdevice = 0x4e85, + .driver_data = SAA7134_BOARD_MONSTERTV, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x153B, + .subdevice = 0x1142, + .driver_data = SAA7134_BOARD_CINERGY400, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x153B, + .subdevice = 0x1143, + .driver_data = SAA7134_BOARD_CINERGY600, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x5168, + .subdevice = 0x0138, + .driver_data = SAA7134_BOARD_FLYVIDEO3000, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x4e42, //"Typhoon PCI Capture TV Card" Art.No. 50673 + .subdevice = 0x0138, + .driver_data = SAA7134_BOARD_FLYVIDEO3000, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x5168, + .subdevice = 0x0138, + .driver_data = SAA7134_BOARD_FLYVIDEO2000, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x16be, + .subdevice = 0x0003, + .driver_data = SAA7134_BOARD_MD7134, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x1048, + .subdevice = 0x226b, + .driver_data = SAA7134_BOARD_ELSA, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x1048, + .subdevice = 0x226b, + .driver_data = SAA7134_BOARD_ELSA_500TV, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_ASUSTEK, + .subdevice = 0x4842, + .driver_data = SAA7134_BOARD_ASUSTeK_TVFM7134, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_ASUSTEK, + .subdevice = 0x4830, + .driver_data = SAA7134_BOARD_ASUSTeK_TVFM7134, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = PCI_VENDOR_ID_ASUSTEK, + .subdevice = 0x4843, + .driver_data = SAA7134_BOARD_ASUSTEK_TVFM7133, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_ASUSTEK, + .subdevice = 0x4840, + .driver_data = SAA7134_BOARD_ASUSTeK_TVFM7134, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0xfe01, + .driver_data = SAA7134_BOARD_TVSTATION_RDS, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1894, + .subdevice = 0xfe01, + .driver_data = SAA7134_BOARD_TVSTATION_RDS, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1894, + .subdevice = 0xa006, + .driver_data = SAA7134_BOARD_TVSTATION_DVR, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1131, + .subdevice = 0x7133, + .driver_data = SAA7134_BOARD_VA1000POWER, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0x2001, + .driver_data = SAA7134_BOARD_10MOONSTVMASTER, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x185b, + .subdevice = 0xc100, + .driver_data = SAA7134_BOARD_VIDEOMATE_TV, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = PCI_VENDOR_ID_MATROX, + .subdevice = 0x48d0, + .driver_data = SAA7134_BOARD_CRONOS_PLUS, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0xa70b, + .driver_data = SAA7134_BOARD_MD2819, + },{ + /* AverMedia Studio 305, using AverMedia M156 entry for now */ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0x2115, + .driver_data = SAA7134_BOARD_MD2819, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0x10ff, + .driver_data = SAA7134_BOARD_AVERMEDIA_DVD_EZMAKER, + },{ + /* TransGear 3000TV */ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0x050c, + .driver_data = SAA7134_BOARD_TG3000TV, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x11bd, + .subdevice = 0x002b, + .driver_data = SAA7134_BOARD_PINNACLE_PCTV_STEREO, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1019, + .subdevice = 0x4cb4, + .driver_data = SAA7134_BOARD_ECS_TVP3XP, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x1019, + .subdevice = 0x4cb5, + .driver_data = SAA7134_BOARD_ECS_TVP3XP_4CB5, + },{ + + /* --- boards without eeprom + subsystem ID --- */ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0, + .driver_data = SAA7134_BOARD_NOAUTO, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0, + .driver_data = SAA7134_BOARD_NOAUTO, + },{ + + /* --- default catch --- */ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = SAA7134_BOARD_UNKNOWN, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = SAA7134_BOARD_UNKNOWN, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = SAA7134_BOARD_UNKNOWN, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7135, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = SAA7134_BOARD_UNKNOWN, + },{ + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, saa7134_pci_tbl); + +/* ----------------------------------------------------------- */ +/* flyvideo tweaks */ + +#if 0 +static struct { + char *model; + int tuner_type; +} fly_list[0x20] = { + /* default catch ... */ + [ 0 ... 0x1f ] = { + .model = "UNKNOWN", + .tuner_type = TUNER_ABSENT, + }, + /* ... the ones known so far */ + [ 0x05 ] = { + .model = "PAL-BG", + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + }, + [ 0x10 ] = { + .model = "PAL-BG / PAL-DK", + .tuner_type = TUNER_PHILIPS_PAL, + }, + [ 0x15 ] = { + .model = "NTSC", + .tuner_type = TUNER_ABSENT /* FIXME */, + }, +}; +#endif + +static void board_flyvideo(struct saa7134_dev *dev) +{ +#if 0 + u32 value; + int index; + + value = dev->gpio_value; + index = (value & 0x1f00) >> 8; + printk(KERN_INFO "%s: flyvideo: gpio is 0x%x [model=%s,tuner=%d]\n", + dev->name, value, fly_list[index].model, + fly_list[index].tuner_type); + dev->tuner_type = fly_list[index].tuner_type; +#endif +} + +/* ----------------------------------------------------------- */ + +int saa7134_board_init(struct saa7134_dev *dev) +{ + // Always print gpio, often manufacturers encode tuner type and other info. + saa_writel(SAA7134_GPIO_GPMODE0 >> 2, 0); + dev->gpio_value = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + printk(KERN_INFO "%s: board init: gpio is %x\n", dev->name, dev->gpio_value); + + switch (dev->board) { + case SAA7134_BOARD_FLYVIDEO2000: + case SAA7134_BOARD_FLYVIDEO3000: + board_flyvideo(dev); + dev->has_remote = 1; + break; + case SAA7134_BOARD_CINERGY400: + case SAA7134_BOARD_CINERGY600: + case SAA7134_BOARD_ECS_TVP3XP: + case SAA7134_BOARD_ECS_TVP3XP_4CB5: + dev->has_remote = 1; + break; + } + return 0; +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/saa7134/saa7134-core.c b/linux/drivers/media/video/saa7134/saa7134-core.c new file mode 100644 index 000000000..a35839c5b --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa7134-core.c @@ -0,0 +1,1111 @@ +/* + * device driver for philips saa7134 based TV cards + * driver core + * + * (c) 2001-03 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * 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/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/kmod.h> +#include <linux/sound.h> +#include <linux/interrupt.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +MODULE_DESCRIPTION("v4l2 driver module for saa7130/34 based TV cards"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +#define SAA7134_MAXBOARDS 8 + +/* ------------------------------------------------------------------ */ + +static unsigned int irq_debug = 0; +MODULE_PARM(irq_debug,"i"); +MODULE_PARM_DESC(irq_debug,"enable debug messages [IRQ handler]"); + +static unsigned int core_debug = 0; +MODULE_PARM(core_debug,"i"); +MODULE_PARM_DESC(core_debug,"enable debug messages [core]"); + +static unsigned int gpio_tracking = 0; +MODULE_PARM(gpio_tracking,"i"); +MODULE_PARM_DESC(gpio_tracking,"enable debug messages [gpio]"); + +static unsigned int video_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(video_nr,"1-" __stringify(SAA7134_MAXBOARDS) "i"); +MODULE_PARM_DESC(video_nr,"video device number"); + +static unsigned int ts_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(ts_nr,"1-" __stringify(SAA7134_MAXBOARDS) "i"); +MODULE_PARM_DESC(ts_nr,"ts device number"); + +static unsigned int vbi_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(vbi_nr,"1-" __stringify(SAA7134_MAXBOARDS) "i"); +MODULE_PARM_DESC(vbi_nr,"vbi device number"); + +static unsigned int radio_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(radio_nr,"1-" __stringify(SAA7134_MAXBOARDS) "i"); +MODULE_PARM_DESC(radio_nr,"radio device number"); + +static unsigned int oss = 0; +MODULE_PARM(oss,"i"); +MODULE_PARM_DESC(oss,"register oss devices (default: no)"); + +static unsigned int dsp_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(dsp_nr,"1-" __stringify(SAA7134_MAXBOARDS) "i"); +MODULE_PARM_DESC(dsp_nr,"oss dsp device number"); + +static unsigned int mixer_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(mixer_nr,"1-" __stringify(SAA7134_MAXBOARDS) "i"); +MODULE_PARM_DESC(mixer_nr,"oss mixer device number"); + +static unsigned int tuner[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(tuner,"1-" __stringify(SAA7134_MAXBOARDS) "i"); +MODULE_PARM_DESC(tuner,"tuner type"); + +static unsigned int card[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM(card,"1-" __stringify(SAA7134_MAXBOARDS) "i"); +MODULE_PARM_DESC(card,"card type"); + +static unsigned int latency = UNSET; +MODULE_PARM(latency,"i"); +MODULE_PARM_DESC(latency,"pci latency timer"); + +struct list_head saa7134_devlist; +unsigned int saa7134_devcount; + +#define dprintk(fmt, arg...) if (core_debug) \ + printk(KERN_DEBUG "%s/core: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ +/* debug help functions */ + +static const char *v4l1_ioctls[] = { + "0", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", "GPICT", "SPICT", + "CCAPTURE", "GWIN", "SWIN", "GFBUF", "SFBUF", "KEY", "GFREQ", + "SFREQ", "GAUDIO", "SAUDIO", "SYNC", "MCAPTURE", "GMBUF", "GUNIT", + "GCAPTURE", "SCAPTURE", "SPLAYMODE", "SWRITEMODE", "GPLAYINFO", + "SMICROCODE", "GVBIFMT", "SVBIFMT" }; +#define V4L1_IOCTLS ARRAY_SIZE(v4l1_ioctls) + +static const char *v4l2_ioctls[] = { + "QUERYCAP", "1", "ENUM_PIXFMT", "ENUM_FBUFFMT", "G_FMT", "S_FMT", + "G_COMP", "S_COMP", "REQBUFS", "QUERYBUF", "G_FBUF", "S_FBUF", + "G_WIN", "S_WIN", "PREVIEW", "QBUF", "16", "DQBUF", "STREAMON", + "STREAMOFF", "G_PERF", "G_PARM", "S_PARM", "G_STD", "S_STD", + "ENUMSTD", "ENUMINPUT", "G_CTRL", "S_CTRL", "G_TUNER", "S_TUNER", + "G_FREQ", "S_FREQ", "G_AUDIO", "S_AUDIO", "35", "QUERYCTRL", + "QUERYMENU", "G_INPUT", "S_INPUT", "ENUMCVT", "41", "42", "43", + "44", "45", "G_OUTPUT", "S_OUTPUT", "ENUMOUTPUT", "G_AUDOUT", + "S_AUDOUT", "ENUMFX", "G_EFFECT", "S_EFFECT", "G_MODULATOR", + "S_MODULATOR" +}; +#define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls) + +static const char *osspcm_ioctls[] = { + "RESET", "SYNC", "SPEED", "STEREO", "GETBLKSIZE", "SETFMT", + "CHANNELS", "?", "POST", "SUBDIVIDE", "SETFRAGMENT", "GETFMTS", + "GETOSPACE", "GETISPACE", "NONBLOCK", "GETCAPS", "GET/SETTRIGGER", + "GETIPTR", "GETOPTR", "MAPINBUF", "MAPOUTBUF", "SETSYNCRO", + "SETDUPLEX", "GETODELAY" +}; +#define OSSPCM_IOCTLS ARRAY_SIZE(v4l2_ioctls) + +void saa7134_print_ioctl(char *name, unsigned int cmd) +{ + char *dir; + + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: dir = "--"; break; + case _IOC_READ: dir = "r-"; break; + case _IOC_WRITE: dir = "-w"; break; + case _IOC_READ | _IOC_WRITE: dir = "rw"; break; + default: dir = "??"; break; + } + switch (_IOC_TYPE(cmd)) { + case 'v': + printk(KERN_DEBUG "%s: ioctl 0x%08x (v4l1, %s, VIDIOC%s)\n", + name, cmd, dir, (_IOC_NR(cmd) < V4L1_IOCTLS) ? + v4l1_ioctls[_IOC_NR(cmd)] : "???"); + break; + case 'V': + printk(KERN_DEBUG "%s: ioctl 0x%08x (v4l2, %s, VIDIOC_%s)\n", + name, cmd, dir, (_IOC_NR(cmd) < V4L2_IOCTLS) ? + v4l2_ioctls[_IOC_NR(cmd)] : "???"); + break; + case 'P': + printk(KERN_DEBUG "%s: ioctl 0x%08x (oss dsp, %s, SNDCTL_DSP_%s)\n", + name, cmd, dir, (_IOC_NR(cmd) < OSSPCM_IOCTLS) ? + osspcm_ioctls[_IOC_NR(cmd)] : "???"); + break; + case 'M': + printk(KERN_DEBUG "%s: ioctl 0x%08x (oss mixer, %s, #%d)\n", + name, cmd, dir, _IOC_NR(cmd)); + break; + default: + printk(KERN_DEBUG "%s: ioctl 0x%08x (???, %s, #%d)\n", + name, cmd, dir, _IOC_NR(cmd)); + } +} + +void saa7134_track_gpio(struct saa7134_dev *dev, char *msg) +{ + unsigned long mode,status; + + if (!gpio_tracking) + return; + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + saa_andorb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN,0); + saa_andorb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN,SAA7134_GPIO_GPRESCAN); + mode = saa_readl(SAA7134_GPIO_GPMODE0 >> 2) & 0xfffffff; + status = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2) & 0xfffffff; + printk(KERN_DEBUG + "%s: gpio: mode=0x%07lx in=0x%07lx out=0x%07lx [%s]\n", + dev->name, mode, (~mode) & status, mode & status, msg); +} + +/* ------------------------------------------------------------------ */ + +#if 0 +static char *dec1_bits[8] = { + "DCSTD0", "DCSCT1", "WIPA", "GLIMB", + "GLIMT", "SLTCA", "HLCK" +}; +static char *dec2_bits[8] = { + "RDCAP", "COPRO", "COLSTR", "TYPE3", + NULL, "FIDT", "HLVLN", "INTL" +}; +static char *scale1_bits[8] = { + "VID_A", "VBI_A", NULL, NULL, "VID_B", "VBI_B" +}; +static char *scale2_bits[8] = { + "TRERR", "CFERR", "LDERR", "WASRST", + "FIDSCI", "FIDSCO", "D6^D5", "TASK" +}; + +static void dump_statusreg(struct saa7134_dev *dev, int reg, + char *regname, char **bits) +{ + int value,i; + + value = saa_readb(reg); + printk(KERN_DEBUG "%s: %s:", dev->name, regname); + for (i = 7; i >= 0; i--) { + if (NULL == bits[i]) + continue; + printk(" %s=%d", bits[i], (value & (1 << i)) ? 1 : 0); + } + printk("\n"); +} + +static void dump_statusregs(struct saa7134_dev *dev) +{ + dump_statusreg(dev,SAA7134_STATUS_VIDEO1,"dec1",dec1_bits); + dump_statusreg(dev,SAA7134_STATUS_VIDEO2,"dec2",dec2_bits); + dump_statusreg(dev,SAA7134_SCALER_STATUS0,"scale0",scale1_bits); + dump_statusreg(dev,SAA7134_SCALER_STATUS1,"scale1",scale2_bits); +} +#endif + +/* ------------------------------------------------------------------ */ + +/* nr of (saa7134-)pages for the given buffer size */ +int saa7134_buffer_pages(int size) +{ + size = PAGE_ALIGN(size); + size += PAGE_SIZE; /* for non-page-aligned buffers */ + size /= 4096; + return size; +} + +/* calc max # of buffers from size (must not exceed the 4MB virtual + * address space per DMA channel) */ +int saa7134_buffer_count(unsigned int size, unsigned int count) +{ + unsigned int maxcount; + + maxcount = 1024 / saa7134_buffer_pages(size); + if (count > maxcount) + count = maxcount; + return count; +} + +int saa7134_buffer_startpage(struct saa7134_buf *buf) +{ + return saa7134_buffer_pages(buf->vb.bsize) * buf->vb.i; +} + +unsigned long saa7134_buffer_base(struct saa7134_buf *buf) +{ + unsigned long base; + + base = saa7134_buffer_startpage(buf) * 4096; + base += buf->vb.dma.sglist[0].offset; + return base; +} + +/* ------------------------------------------------------------------ */ + +int saa7134_pgtable_alloc(struct pci_dev *pci, struct saa7134_pgtable *pt) +{ + u32 *cpu; + dma_addr_t dma_addr; + + cpu = pci_alloc_consistent(pci, SAA7134_PGTABLE_SIZE, &dma_addr); + if (NULL == cpu) + return -ENOMEM; + pt->size = SAA7134_PGTABLE_SIZE; + pt->cpu = cpu; + pt->dma = dma_addr; + return 0; +} + +int saa7134_pgtable_build(struct pci_dev *pci, struct saa7134_pgtable *pt, + struct scatterlist *list, unsigned int length, + unsigned int startpage) +{ + u32 *ptr; + unsigned int i,p; + + BUG_ON(NULL == pt || NULL == pt->cpu); + + ptr = pt->cpu + startpage; + for (i = 0; i < length; i++, list++) + for (p = 0; p * 4096 < list->length; p++, ptr++) + *ptr = sg_dma_address(list) - list->offset; + return 0; +} + +void saa7134_pgtable_free(struct pci_dev *pci, struct saa7134_pgtable *pt) +{ + if (NULL == pt->cpu) + return; + pci_free_consistent(pci, pt->size, pt->cpu, pt->dma); + pt->cpu = NULL; +} + +/* ------------------------------------------------------------------ */ + +void saa7134_dma_free(struct saa7134_dev *dev,struct saa7134_buf *buf) +{ + if (in_interrupt()) + BUG(); + + videobuf_waiton(&buf->vb,0,0); + videobuf_dma_pci_unmap(dev->pci, &buf->vb.dma); + videobuf_dma_free(&buf->vb.dma); + buf->vb.state = STATE_NEEDS_INIT; +} + +/* ------------------------------------------------------------------ */ + +int saa7134_buffer_queue(struct saa7134_dev *dev, + struct saa7134_dmaqueue *q, + struct saa7134_buf *buf) +{ + struct saa7134_buf *next = NULL; +#if DEBUG_SPINLOCKS + BUG_ON(!spin_is_locked(&dev->slock)); +#endif + + dprintk("buffer_queue %p\n",buf); + if (NULL == q->curr) { + if (!q->need_two) { + q->curr = buf; + buf->activate(dev,buf,NULL); + } else if (list_empty(&q->queue)) { + list_add_tail(&buf->vb.queue,&q->queue); + buf->vb.state = STATE_QUEUED; + } else { + next = list_entry(q->queue.next,struct saa7134_buf, + vb.queue); + q->curr = buf; + buf->activate(dev,buf,next); + } + } else { + list_add_tail(&buf->vb.queue,&q->queue); + buf->vb.state = STATE_QUEUED; + } + return 0; +} + +void saa7134_buffer_finish(struct saa7134_dev *dev, + struct saa7134_dmaqueue *q, + unsigned int state) +{ +#if DEBUG_SPINLOCKS + BUG_ON(!spin_is_locked(&dev->slock)); +#endif + dprintk("buffer_finish %p\n",q->curr); + + /* finish current buffer */ + q->curr->vb.state = state; + do_gettimeofday(&q->curr->vb.ts); + wake_up(&q->curr->vb.done); + q->curr = NULL; +} + +void saa7134_buffer_next(struct saa7134_dev *dev, + struct saa7134_dmaqueue *q) +{ + struct saa7134_buf *buf,*next = NULL; + +#if DEBUG_SPINLOCKS + BUG_ON(!spin_is_locked(&dev->slock)); +#endif + BUG_ON(NULL != q->curr); + + if (!list_empty(&q->queue)) { + /* activate next one from queue */ + buf = list_entry(q->queue.next,struct saa7134_buf,vb.queue); + dprintk("buffer_next %p [prev=%p/next=%p]\n", + buf,q->queue.prev,q->queue.next); + list_del(&buf->vb.queue); + if (!list_empty(&q->queue)) + next = list_entry(q->queue.next,struct saa7134_buf, + vb.queue); + q->curr = buf; + buf->activate(dev,buf,next); + dprintk("buffer_next #2 prev=%p/next=%p\n", + q->queue.prev,q->queue.next); + } else { + /* nothing to do -- just stop DMA */ + dprintk("buffer_next %p\n",NULL); + saa7134_set_dmabits(dev); + del_timer(&q->timeout); + } +} + +void saa7134_buffer_timeout(unsigned long data) +{ + struct saa7134_dmaqueue *q = (struct saa7134_dmaqueue*)data; + struct saa7134_dev *dev = q->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->slock,flags); + + /* try to reset the hardware (SWRST) */ + saa_writeb(SAA7134_REGION_ENABLE, 0x00); + saa_writeb(SAA7134_REGION_ENABLE, 0x80); + saa_writeb(SAA7134_REGION_ENABLE, 0x00); + + /* flag current buffer as failed, + try to start over with the next one. */ + if (q->curr) { + dprintk("timeout on %p\n",q->curr); + saa7134_buffer_finish(dev,q,STATE_ERROR); + } + saa7134_buffer_next(dev,q); + spin_unlock_irqrestore(&dev->slock,flags); +} + +/* ------------------------------------------------------------------ */ + +int saa7134_set_dmabits(struct saa7134_dev *dev) +{ + u32 split, task=0, ctrl=0, irq=0; + enum v4l2_field cap = V4L2_FIELD_ANY; + enum v4l2_field ov = V4L2_FIELD_ANY; + +#if DEBUG_SPINLOCKS + BUG_ON(!spin_is_locked(&dev->slock)); +#endif + + /* video capture -- dma 0 + video task A */ + if (dev->video_q.curr) { + task |= 0x01; + ctrl |= SAA7134_MAIN_CTRL_TE0; + irq |= SAA7134_IRQ1_INTE_RA0_1 | + SAA7134_IRQ1_INTE_RA0_0; + cap = dev->video_q.curr->vb.field; + } + + /* video capture -- dma 1+2 (planar modes) */ + if (dev->video_q.curr && + dev->video_q.curr->fmt->planar) { + ctrl |= SAA7134_MAIN_CTRL_TE4 | + SAA7134_MAIN_CTRL_TE5; + } + + /* screen overlay -- dma 0 + video task B */ + if (dev->ovenable) { + task |= 0x10; + ctrl |= SAA7134_MAIN_CTRL_TE1; + ov = dev->ovfield; + } + + /* vbi capture -- dma 0 + vbi task A+B */ + if (dev->vbi_q.curr) { + task |= 0x22; + ctrl |= SAA7134_MAIN_CTRL_TE2 | + SAA7134_MAIN_CTRL_TE3; + irq |= SAA7134_IRQ1_INTE_RA0_7 | + SAA7134_IRQ1_INTE_RA0_6 | + SAA7134_IRQ1_INTE_RA0_5 | + SAA7134_IRQ1_INTE_RA0_4; + } + + /* audio capture -- dma 3 */ + if (dev->oss.recording) { + ctrl |= SAA7134_MAIN_CTRL_TE6; + irq |= SAA7134_IRQ1_INTE_RA3_1 | + SAA7134_IRQ1_INTE_RA3_0; + } + + /* TS capture -- dma 5 */ + if (dev->ts_q.curr) { + ctrl |= SAA7134_MAIN_CTRL_TE5; + irq |= SAA7134_IRQ1_INTE_RA2_3 | + SAA7134_IRQ1_INTE_RA2_2 | + SAA7134_IRQ1_INTE_RA2_1 | + SAA7134_IRQ1_INTE_RA2_0; + } + + /* set task conditions + field handling */ + if (V4L2_FIELD_HAS_BOTH(cap) || V4L2_FIELD_HAS_BOTH(ov) || cap == ov) { + /* default config -- use full frames */ + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0d); + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0d); + saa_writeb(SAA7134_FIELD_HANDLING(TASK_A), 0x02); + saa_writeb(SAA7134_FIELD_HANDLING(TASK_B), 0x02); + split = 0; + } else { + /* split fields between tasks */ + if (V4L2_FIELD_TOP == cap) { + /* odd A, even B, repeat */ + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0d); + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0e); + } else { + /* odd B, even A, repeat */ + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0e); + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0d); + } + saa_writeb(SAA7134_FIELD_HANDLING(TASK_A), 0x01); + saa_writeb(SAA7134_FIELD_HANDLING(TASK_B), 0x01); + split = 1; + } + + /* irqs */ + saa_writeb(SAA7134_REGION_ENABLE, task); + saa_writel(SAA7134_IRQ1, irq); + saa_andorl(SAA7134_MAIN_CTRL, + SAA7134_MAIN_CTRL_TE0 | + SAA7134_MAIN_CTRL_TE1 | + SAA7134_MAIN_CTRL_TE2 | + SAA7134_MAIN_CTRL_TE3 | + SAA7134_MAIN_CTRL_TE4 | + SAA7134_MAIN_CTRL_TE5 | + SAA7134_MAIN_CTRL_TE6, + ctrl); + dprintk("dmabits: task=0x%02x ctrl=0x%02x irq=0x%x split=%s\n", + task, ctrl, irq, split ? "no" : "yes"); + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* IRQ handler + helpers */ + +static char *irqbits[] = { + "DONE_RA0", "DONE_RA1", "DONE_RA2", "DONE_RA3", + "AR", "PE", "PWR_ON", "RDCAP", "INTL", "FIDT", "MMC", + "TRIG_ERR", "CONF_ERR", "LOAD_ERR", + "GPIO16?", "GPIO18", "GPIO22", "GPIO23" +}; +#define IRQBITS ARRAY_SIZE(irqbits) + +static void print_irqstatus(struct saa7134_dev *dev, int loop, + unsigned long report, unsigned long status) +{ + unsigned int i; + + printk(KERN_DEBUG "%s/irq[%d,%ld]: r=0x%lx s=0x%02lx", + dev->name,loop,jiffies,report,status); + for (i = 0; i < IRQBITS; i++) { + if (!(report & (1 << i))) + continue; + printk(" %s",irqbits[i]); + } + if (report & SAA7134_IRQ_REPORT_DONE_RA0) { + printk(" | RA0=%s,%s,%s,%ld", + (status & 0x40) ? "vbi" : "video", + (status & 0x20) ? "b" : "a", + (status & 0x10) ? "odd" : "even", + (status & 0x0f)); + } + printk("\n"); +} + +static irqreturn_t saa7134_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct saa7134_dev *dev = (struct saa7134_dev*) dev_id; + 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 (0 == report) { + if (irq_debug > 1) + printk(KERN_DEBUG "%s/irq: no (more) work\n", + dev->name); + goto out; + } + handled = 1; + saa_writel(SAA7134_IRQ_REPORT,report); + if (irq_debug) + print_irqstatus(dev,loop,report,status); + +#if 0 + if (report & SAA7134_IRQ_REPORT_CONF_ERR) + dump_statusregs(dev); +#endif + + if (report & SAA7134_IRQ_REPORT_INTL) + saa7134_irq_video_intl(dev); + + if ((report & SAA7134_IRQ_REPORT_DONE_RA0) && + (status & 0x60) == 0) + saa7134_irq_video_done(dev,status); + + if ((report & SAA7134_IRQ_REPORT_DONE_RA0) && + (status & 0x40) == 0x40) + saa7134_irq_vbi_done(dev,status); + + if ((report & SAA7134_IRQ_REPORT_DONE_RA2) && + card_has_ts(dev)) + saa7134_irq_ts_done(dev,status); + + if ((report & SAA7134_IRQ_REPORT_DONE_RA3)) + saa7134_irq_oss_done(dev,status); + + if ((report & (SAA7134_IRQ_REPORT_GPIO16 | + SAA7134_IRQ_REPORT_GPIO18)) && + dev->remote) + saa7134_input_irq(dev); + + }; + if (10 == loop) { + print_irqstatus(dev,loop,report,status); + printk(KERN_WARNING "%s/irq: looping -- clearing enable bits\n",dev->name); + /* disable all irqs */ + saa_writel(SAA7134_IRQ1,0); + saa_writel(SAA7134_IRQ2,0); + } + + out: + return IRQ_RETVAL(handled); +} + +/* ------------------------------------------------------------------ */ + +/* early init (no i2c, no irq) */ +static int saa7134_hwinit1(struct saa7134_dev *dev) +{ + dprintk("hwinit1\n"); + + saa_writel(SAA7134_IRQ1, 0); + saa_writel(SAA7134_IRQ2, 0); + init_MUTEX(&dev->lock); + dev->slock = SPIN_LOCK_UNLOCKED; + + saa7134_track_gpio(dev,"pre-init"); + saa7134_video_init1(dev); + saa7134_vbi_init1(dev); + if (card_has_ts(dev)) + saa7134_ts_init1(dev); + saa7134_input_init1(dev); + + 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; + } + + /* RAM FIFO config */ + saa_writel(SAA7134_FIFO_SIZE, 0x08070503); + saa_writel(SAA7134_THRESHOULD,0x02020202); + + /* enable audio + video processing */ + saa_writel(SAA7134_MAIN_CTRL, + SAA7134_MAIN_CTRL_VPLLE | + SAA7134_MAIN_CTRL_APLLE | + SAA7134_MAIN_CTRL_EXOSC | + SAA7134_MAIN_CTRL_EVFE1 | + SAA7134_MAIN_CTRL_EVFE2 | + SAA7134_MAIN_CTRL_ESFE | + SAA7134_MAIN_CTRL_EBADC | + SAA7134_MAIN_CTRL_EBDAC); + + /* enable peripheral devices */ + saa_writeb(SAA7134_SPECIAL_MODE, 0x01); + + /* set vertical line numbering start (vbi needs this) */ + saa_writeb(SAA7134_SOURCE_TIMING2, 0x20); + + return 0; +} + +/* late init (with i2c + irq) */ +static int saa7134_hwinit2(struct saa7134_dev *dev) +{ + dprintk("hwinit2\n"); + + saa7134_video_init2(dev); + saa7134_tvaudio_init2(dev); + + /* enable IRQ's */ + saa_writel(SAA7134_IRQ1, 0); + saa_writel(SAA7134_IRQ2, + SAA7134_IRQ2_INTE_GPIO18 | + SAA7134_IRQ2_INTE_GPIO18A | + SAA7134_IRQ2_INTE_GPIO16 | + SAA7134_IRQ2_INTE_SC2 | + SAA7134_IRQ2_INTE_SC1 | + SAA7134_IRQ2_INTE_SC0 | + /* SAA7134_IRQ2_INTE_DEC5 | FIXME: TRIG_ERR ??? */ + SAA7134_IRQ2_INTE_DEC3 | + SAA7134_IRQ2_INTE_DEC2 | + /* SAA7134_IRQ2_INTE_DEC1 | */ + SAA7134_IRQ2_INTE_DEC0 | + SAA7134_IRQ2_INTE_PE | + SAA7134_IRQ2_INTE_AR); + + return 0; +} + +/* shutdown */ +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_ts(dev)) + saa7134_ts_fini(dev); + saa7134_input_fini(dev); + saa7134_vbi_fini(dev); + saa7134_video_fini(dev); + saa7134_tvaudio_fini(dev); + return 0; +} + +static void __devinit must_configure_manually(void) +{ + unsigned int i,p; + + printk(KERN_WARNING + "saa7134: <rant>\n" + "saa7134: Congratulations! Your TV card vendor saved a few\n" + "saa7134: cents for a eeprom, thus your pci board has no\n" + "saa7134: subsystem ID and I can't identify it automatically\n" + "saa7134: </rant>\n" + "saa7134: I feel better now. Ok, here are the good news:\n" + "saa7134: You can use the card=<nr> insmod option to specify\n" + "saa7134: which board do you have. The list:\n"); + for (i = 0; i < saa7134_bcount; i++) { + printk(KERN_WARNING "saa7134: card=%d -> %-40.40s", + i,saa7134_boards[i].name); + for (p = 0; saa7134_pci_tbl[p].driver_data; p++) { + if (saa7134_pci_tbl[p].driver_data != i) + continue; + printk(" %04x:%04x", + saa7134_pci_tbl[p].subvendor, + saa7134_pci_tbl[p].subdevice); + } + printk("\n"); + } +} + +static struct video_device *vdev_init(struct saa7134_dev *dev, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->minor = -1; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + vfd->dev = &dev->pci->dev; + vfd->release = video_device_release; +#endif + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", + dev->name, type, saa7134_boards[dev->board].name); + return vfd; +} + +static void saa7134_unregister_video(struct saa7134_dev *dev) +{ + if (dev->video_dev) { + if (-1 != dev->video_dev->minor) + video_unregister_device(dev->video_dev); + else + video_device_release(dev->video_dev); + dev->video_dev = NULL; + } + if (dev->ts_dev) { + if (-1 != dev->ts_dev->minor) + video_unregister_device(dev->ts_dev); + else + video_device_release(dev->ts_dev); + dev->ts_dev = NULL; + } + if (dev->vbi_dev) { + if (-1 != dev->vbi_dev->minor) + video_unregister_device(dev->vbi_dev); + else + video_device_release(dev->vbi_dev); + dev->vbi_dev = NULL; + } + if (dev->radio_dev) { + if (-1 != dev->radio_dev->minor) + video_unregister_device(dev->radio_dev); + else + video_device_release(dev->radio_dev); + dev->radio_dev = NULL; + } +} + +static int __devinit saa7134_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct saa7134_dev *dev; + int err; + + dev = kmalloc(sizeof(*dev),GFP_KERNEL); + if (NULL == dev) + return -ENOMEM; + memset(dev,0,sizeof(*dev)); + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail1; + } + sprintf(dev->name,"saa%x[%d]",pci_dev->device,saa7134_devcount); + + /* pci quirks */ + if (pci_pci_problems) { + if (pci_pci_problems & PCIPCI_TRITON) + printk(KERN_INFO "%s: quirk: PCIPCI_TRITON\n", dev->name); + if (pci_pci_problems & PCIPCI_NATOMA) + printk(KERN_INFO "%s: quirk: PCIPCI_NATOMA\n", dev->name); + if (pci_pci_problems & PCIPCI_VIAETBF) + printk(KERN_INFO "%s: quirk: PCIPCI_VIAETBF\n", dev->name); + if (pci_pci_problems & PCIPCI_VSFX) + printk(KERN_INFO "%s: quirk: PCIPCI_VSFX\n",dev->name); +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) { + printk(KERN_INFO "%s: quirk: PCIPCI_ALIMAGIK -- latency fixup\n", + dev->name); + latency = 0x0A; + } +#endif + } + if (UNSET != latency) { + printk(KERN_INFO "%s: setting pci latency timer to %d\n", + dev->name,latency); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency); + } + + /* print pci info */ + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &dev->pci_rev); + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + printk(KERN_INFO "%s: found at %s, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%lx\n", dev->name, + pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat,pci_resource_start(pci_dev,0)); + pci_set_master(pci_dev); + if (!pci_dma_supported(pci_dev,0xffffffff)) { + printk("%s: Oops: no 32bit PCI DMA ???\n",dev->name); + err = -EIO; + goto fail1; + } + + /* board config */ + dev->board = pci_id->driver_data; + if (card[saa7134_devcount] >= 0 && + card[saa7134_devcount] < saa7134_bcount) + dev->board = card[saa7134_devcount]; + if (SAA7134_BOARD_NOAUTO == dev->board) { + must_configure_manually(); + dev->board = SAA7134_BOARD_UNKNOWN; + } + dev->tuner_type = saa7134_boards[dev->board].tuner_type; + if (UNSET != tuner[saa7134_devcount]) + dev->tuner_type = tuner[saa7134_devcount]; + printk(KERN_INFO "%s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n", + dev->name,pci_dev->subsystem_vendor, + pci_dev->subsystem_device,saa7134_boards[dev->board].name, + dev->board, card[saa7134_devcount] == dev->board ? + "insmod option" : "autodetected"); + + /* get mmio */ + if (!request_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0), + dev->name)) { + err = -EBUSY; + printk(KERN_ERR "%s: can't get MMIO memory @ 0x%lx\n", + dev->name,pci_resource_start(pci_dev,0)); + goto fail1; + } + dev->lmmio = ioremap(pci_resource_start(pci_dev,0), 0x1000); + dev->bmmio = (__u8*)dev->lmmio; + if (NULL == dev->lmmio) { + err = -EIO; + printk(KERN_ERR "%s: can't ioremap() MMIO memory\n", + dev->name); + goto fail2; + } + + /* initialize hardware #1 */ + saa7134_board_init(dev); + saa7134_hwinit1(dev); + + /* get irq */ + err = request_irq(pci_dev->irq, saa7134_irq, + SA_SHIRQ | SA_INTERRUPT, dev->name, dev); + if (err < 0) { + printk(KERN_ERR "%s: can't get IRQ %d\n", + dev->name,pci_dev->irq); + goto fail3; + } + + /* wait a bit, register i2c bus */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + saa7134_i2c_register(dev); + + /* initialize hardware #2 */ + saa7134_hwinit2(dev); + + /* load i2c helpers */ + if (TUNER_ABSENT != dev->tuner_type) + request_module("tuner"); + if (saa7134_boards[dev->board].need_tda9887) + request_module("tda9887"); + if (card_has_ts(dev)) + request_module("saa6752hs"); + +#ifdef VIDIOC_G_PRIORITY + v4l2_prio_init(&dev->prio); +#endif + + /* register v4l devices */ + dev->video_dev = vdev_init(dev,&saa7134_video_template,"video"); + err = video_register_device(dev->video_dev,VFL_TYPE_GRABBER, + video_nr[saa7134_devcount]); + if (err < 0) { + printk(KERN_INFO "%s: can't register video device\n", + dev->name); + goto fail4; + } + printk(KERN_INFO "%s: registered device video%d [v4l2]\n", + dev->name,dev->video_dev->minor & 0x1f); + + if (card_has_ts(dev)) { + dev->ts_dev = vdev_init(dev,&saa7134_ts_template,"ts"); + err = video_register_device(dev->ts_dev,VFL_TYPE_GRABBER, + ts_nr[saa7134_devcount]); + if (err < 0) { + printk(KERN_INFO "%s: can't register video device\n", + dev->name); + goto fail4; + } + printk(KERN_INFO "%s: registered device video%d [ts]\n", + dev->name,dev->ts_dev->minor & 0x1f); + } + + dev->vbi_dev = vdev_init(dev,&saa7134_vbi_template,"vbi"); + err = video_register_device(dev->vbi_dev,VFL_TYPE_VBI, + vbi_nr[saa7134_devcount]); + if (err < 0) + goto fail4; + printk(KERN_INFO "%s: registered device vbi%d\n", + dev->name,dev->vbi_dev->minor & 0x1f); + + if (card_has_radio(dev)) { + dev->radio_dev = vdev_init(dev,&saa7134_radio_template,"radio"); + err = video_register_device(dev->radio_dev,VFL_TYPE_RADIO, + radio_nr[saa7134_devcount]); + if (err < 0) + goto fail4; + printk(KERN_INFO "%s: registered device radio%d\n", + 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->oss.minor_dsp = + register_sound_dsp(&saa7134_dsp_fops, + dsp_nr[saa7134_devcount]); + if (err < 0) { + goto fail4; + } + printk(KERN_INFO "%s: registered device dsp%d\n", + dev->name,dev->oss.minor_dsp >> 4); + + err = dev->oss.minor_mixer = + register_sound_mixer(&saa7134_mixer_fops, + mixer_nr[saa7134_devcount]); + if (err < 0) + goto fail5; + printk(KERN_INFO "%s: registered device mixer%d\n", + dev->name,dev->oss.minor_mixer >> 4); + } + break; + } + + /* everything worked */ + list_add_tail(&dev->devlist,&saa7134_devlist); + pci_set_drvdata(pci_dev,dev); + saa7134_devcount++; + 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->oss.minor_dsp); + break; + } + fail4: + saa7134_unregister_video(dev); + saa7134_i2c_unregister(dev); + free_irq(pci_dev->irq, dev); + fail3: + saa7134_hwfini(dev); + iounmap(dev->lmmio); + fail2: + release_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); + fail1: + kfree(dev); + return err; +} + +static void __devexit saa7134_finidev(struct pci_dev *pci_dev) +{ + struct saa7134_dev *dev = pci_get_drvdata(pci_dev); + + /* debugging ... */ + if (irq_debug) { + u32 report = saa_readl(SAA7134_IRQ_REPORT); + u32 status = saa_readl(SAA7134_IRQ_STATUS); + print_irqstatus(dev,42,report,status); + } + + /* disable peripheral devices */ + saa_writeb(SAA7134_SPECIAL_MODE,0); + + /* shutdown hardware */ + saa_writel(SAA7134_IRQ1,0); + saa_writel(SAA7134_IRQ2,0); + saa_writel(SAA7134_MAIN_CTRL,0); + + /* shutdown subsystems */ + saa7134_hwfini(dev); + + /* unregister */ + 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->oss.minor_mixer); + unregister_sound_dsp(dev->oss.minor_dsp); + } + break; + } + saa7134_unregister_video(dev); + + /* release ressources */ + free_irq(pci_dev->irq, dev); + iounmap(dev->lmmio); + release_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); + +#if 0 /* causes some trouble when reinserting the driver ... */ + pci_disable_device(pci_dev); +#endif + pci_set_drvdata(pci_dev, NULL); + + /* free memory */ + list_del(&dev->devlist); + saa7134_devcount--; + kfree(dev); +} + +static struct pci_driver saa7134_pci_driver = { + .name = "saa7134", + .id_table = saa7134_pci_tbl, + .probe = saa7134_initdev, + .remove = saa7134_finidev, +}; + +static int saa7134_init(void) +{ + INIT_LIST_HEAD(&saa7134_devlist); + printk(KERN_INFO "saa7130/34: v4l2 driver version %d.%d.%d loaded\n", + (SAA7134_VERSION_CODE >> 16) & 0xff, + (SAA7134_VERSION_CODE >> 8) & 0xff, + SAA7134_VERSION_CODE & 0xff); +#ifdef SNAPSHOT + printk(KERN_INFO "saa7130/34: snapshot date %04d-%02d-%02d\n", + SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100); +#endif + return pci_module_init(&saa7134_pci_driver); +} + +static void saa7134_fini(void) +{ + pci_unregister_driver(&saa7134_pci_driver); +} + +module_init(saa7134_init); +module_exit(saa7134_fini); + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/saa7134/saa7134-i2c.c b/linux/drivers/media/video/saa7134/saa7134-i2c.c new file mode 100644 index 000000000..a1b8df331 --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa7134-i2c.c @@ -0,0 +1,448 @@ +/* + * device driver for philips saa7134 based TV cards + * i2c interface support + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * 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/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* ----------------------------------------------------------- */ + +static unsigned int i2c_debug = 0; +MODULE_PARM(i2c_debug,"i"); +MODULE_PARM_DESC(i2c_debug,"enable debug messages [i2c]"); + +static unsigned int i2c_scan = 0; +MODULE_PARM(i2c_scan,"i"); +MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time"); + +#define d1printk if (1 == i2c_debug) printk +#define d2printk if (2 == i2c_debug) printk + +#define I2C_WAIT_DELAY 32 +#define I2C_WAIT_RETRY 16 + +/* ----------------------------------------------------------- */ + +static char *str_i2c_status[] = { + "IDLE", "DONE_STOP", "BUSY", "TO_SCL", "TO_ARB", "DONE_WRITE", + "DONE_READ", "DONE_WRITE_TO", "DONE_READ_TO", "NO_DEVICE", + "NO_ACKN", "BUS_ERR", "ARB_LOST", "SEQ_ERR", "ST_ERR", "SW_ERR" +}; + +enum i2c_status { + IDLE = 0, // no I2C command pending + DONE_STOP = 1, // I2C command done and STOP executed + BUSY = 2, // executing I2C command + TO_SCL = 3, // executing I2C command, time out on clock stretching + TO_ARB = 4, // time out on arbitration trial, still trying + DONE_WRITE = 5, // I2C command done and awaiting next write command + DONE_READ = 6, // I2C command done and awaiting next read command + DONE_WRITE_TO = 7, // see 5, and time out on status echo + DONE_READ_TO = 8, // see 6, and time out on status echo + NO_DEVICE = 9, // no acknowledge on device slave address + NO_ACKN = 10, // no acknowledge after data byte transfer + BUS_ERR = 11, // bus error + ARB_LOST = 12, // arbitration lost during transfer + SEQ_ERR = 13, // erroneous programming sequence + ST_ERR = 14, // wrong status echoing + SW_ERR = 15 // software error +}; + +static char *str_i2c_attr[] = { + "NOP", "STOP", "CONTINUE", "START" +}; + +enum i2c_attr { + NOP = 0, // no operation on I2C bus + STOP = 1, // stop condition, no associated byte transfer + CONTINUE = 2, // continue with byte transfer + START = 3 // start condition with byte transfer +}; + +static inline enum i2c_status i2c_get_status(struct saa7134_dev *dev) +{ + enum i2c_status status; + + status = saa_readb(SAA7134_I2C_ATTR_STATUS) & 0x0f; + d2printk(KERN_DEBUG "%s: i2c stat <= %s\n",dev->name, + str_i2c_status[status]); + return status; +} + +static inline void i2c_set_status(struct saa7134_dev *dev, + enum i2c_status status) +{ + d2printk(KERN_DEBUG "%s: i2c stat => %s\n",dev->name, + str_i2c_status[status]); + saa_andorb(SAA7134_I2C_ATTR_STATUS,0x0f,status); +} + +static inline void i2c_set_attr(struct saa7134_dev *dev, enum i2c_attr attr) +{ + d2printk(KERN_DEBUG "%s: i2c attr => %s\n",dev->name, + str_i2c_attr[attr]); + saa_andorb(SAA7134_I2C_ATTR_STATUS,0xc0,attr << 6); +} + +static inline int i2c_is_error(enum i2c_status status) +{ + switch (status) { + case NO_DEVICE: + case NO_ACKN: + case BUS_ERR: + case ARB_LOST: + case SEQ_ERR: + case ST_ERR: + return TRUE; + default: + return FALSE; + } +} + +static inline int i2c_is_idle(enum i2c_status status) +{ + switch (status) { + case IDLE: + case DONE_STOP: + return TRUE; + default: + return FALSE; + } +} + +static inline int i2c_is_busy(enum i2c_status status) +{ + switch (status) { + case BUSY: + return TRUE; + default: + return FALSE; + } +} + +static int i2c_is_busy_wait(struct saa7134_dev *dev) +{ + enum i2c_status status; + int count; + + for (count = 0; count < I2C_WAIT_RETRY; count++) { + status = i2c_get_status(dev); + if (!i2c_is_busy(status)) + break; + saa_wait(I2C_WAIT_DELAY); + } + if (I2C_WAIT_RETRY == count) + return FALSE; + return TRUE; +} + +static int i2c_reset(struct saa7134_dev *dev) +{ + enum i2c_status status; + int count; + + d2printk(KERN_DEBUG "%s: i2c reset\n",dev->name); + status = i2c_get_status(dev); + if (!i2c_is_error(status)) + return TRUE; + i2c_set_status(dev,status); + + for (count = 0; count < I2C_WAIT_RETRY; count++) { + status = i2c_get_status(dev); + if (!i2c_is_error(status)) + break; + udelay(I2C_WAIT_DELAY); + } + if (I2C_WAIT_RETRY == count) + return FALSE; + + if (!i2c_is_idle(status)) + return FALSE; + + i2c_set_attr(dev,NOP); + return TRUE; +} + +static inline int i2c_send_byte(struct saa7134_dev *dev, + enum i2c_attr attr, + unsigned char data) +{ + enum i2c_status status; + __u32 dword; + +#if 0 + i2c_set_attr(dev,attr); + saa_writeb(SAA7134_I2C_DATA, data); +#else + /* have to write both attr + data in one 32bit word */ + dword = saa_readl(SAA7134_I2C_ATTR_STATUS >> 2); + dword &= 0x0f; + dword |= (attr << 6); + dword |= ((__u32)data << 8); + dword |= 0x00 << 16; + dword |= 0xf0 << 24; + saa_writel(SAA7134_I2C_ATTR_STATUS >> 2, dword); +#endif + d2printk(KERN_DEBUG "%s: i2c data => 0x%x\n",dev->name,data); + + if (!i2c_is_busy_wait(dev)) + return -EIO; + status = i2c_get_status(dev); + if (i2c_is_error(status)) + return -EIO; + return 0; +} + +static inline int i2c_recv_byte(struct saa7134_dev *dev) +{ + enum i2c_status status; + unsigned char data; + + i2c_set_attr(dev,CONTINUE); + if (!i2c_is_busy_wait(dev)) + return -EIO; + status = i2c_get_status(dev); + if (i2c_is_error(status)) + return -EIO; + data = saa_readb(SAA7134_I2C_DATA); + d2printk(KERN_DEBUG "%s: i2c data <= 0x%x\n",dev->name,data); + return data; +} + +static int saa7134_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msgs[], int num) +{ + struct saa7134_dev *dev = i2c_adap->algo_data; + enum i2c_status status; + unsigned char data; + int addr,rc,i,byte; + + status = i2c_get_status(dev); + if (!i2c_is_idle(status)) + if (!i2c_reset(dev)) + return -EIO; + + d1printk(KERN_DEBUG "%s: i2c xfer:",dev->name); + for (i = 0; i < num; i++) { + if (!(msgs[i].flags & I2C_M_NOSTART) || 0 == i) { + /* send address */ + addr = msgs[i].addr << 1; + if (msgs[i].flags & I2C_M_RD) + addr |= 1; + d1printk(" < %02x", addr); + rc = i2c_send_byte(dev,START,addr); + if (rc < 0) + goto err; + } + if (msgs[i].flags & I2C_M_RD) { + /* read bytes */ + for (byte = 0; byte < msgs[i].len; byte++) { + d1printk(" ="); + rc = i2c_recv_byte(dev); + if (rc < 0) + goto err; + d1printk("%02x", rc); + msgs[i].buf[byte] = rc; + } + } else { + /* write bytes */ + for (byte = 0; byte < msgs[i].len; byte++) { + data = msgs[i].buf[byte]; + d1printk(" %02x", data); + rc = i2c_send_byte(dev,CONTINUE,data); + if (rc < 0) + goto err; + } + } + } + d1printk(" >"); + i2c_set_attr(dev,STOP); + rc = -EIO; + if (!i2c_is_busy_wait(dev)) + goto err; + status = i2c_get_status(dev); + if (i2c_is_error(status)) + goto err; + + d1printk("\n"); + return num; + err: + if (1 == i2c_debug) { + status = i2c_get_status(dev); + printk(" ERROR: %s\n",str_i2c_status[status]); + } + return rc; +} + +/* ----------------------------------------------------------- */ + +static int algo_control(struct i2c_adapter *adapter, + unsigned int cmd, unsigned long arg) +{ + return 0; +} + +static u32 functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +#ifndef I2C_PEC +static void inc_use(struct i2c_adapter *adap) +{ + MOD_INC_USE_COUNT; +} + +static void dec_use(struct i2c_adapter *adap) +{ + MOD_DEC_USE_COUNT; +} +#endif + +static int attach_inform(struct i2c_client *client) +{ + struct saa7134_dev *dev = client->adapter->algo_data; + int tuner = dev->tuner_type; + + saa7134_i2c_call_clients(dev,TUNER_SET_TYPE,&tuner); + return 0; +} + +static struct i2c_algorithm saa7134_algo = { + .name = "saa7134", + .id = I2C_ALGO_SAA7134, + .master_xfer = saa7134_i2c_xfer, + .algo_control = algo_control, + .functionality = functionality, +}; + +static struct i2c_adapter saa7134_adap_template = { +#ifdef I2C_PEC + .owner = THIS_MODULE, +#else + .inc_use = inc_use, + .dec_use = dec_use, +#endif +#ifdef I2C_ADAP_CLASS_TV_ANALOG + .class = I2C_ADAP_CLASS_TV_ANALOG, +#endif + I2C_DEVNAME("saa7134"), + .id = I2C_ALGO_SAA7134, + .algo = &saa7134_algo, + .client_register = attach_inform, +}; + +static struct i2c_client saa7134_client_template = { + I2C_DEVNAME("saa7134 internal"), + .id = -1, +}; + +/* ----------------------------------------------------------- */ + +static int +saa7134_i2c_eeprom(struct saa7134_dev *dev, unsigned char *eedata, int len) +{ + unsigned char buf; + int i,err; + + dev->i2c_client.addr = 0xa0 >> 1; + buf = 0; + if (1 != (err = i2c_master_send(&dev->i2c_client,&buf,1))) { + printk(KERN_INFO "%s: Huh, no eeprom present (err=%d)?\n", + dev->name,err); + return -1; + } + if (len != (err = i2c_master_recv(&dev->i2c_client,eedata,len))) { + printk(KERN_WARNING "%s: i2c eeprom read error (err=%d)\n", + dev->name,err); + return -1; + } + for (i = 0; i < len; i++) { + if (0 == (i % 16)) + printk(KERN_INFO "%s: i2c eeprom %02x:",dev->name,i); + printk(" %02x",eedata[i]); + if (15 == (i % 16)) + printk("\n"); + } + return 0; +} + +static int +saa7134_i2c_scan(struct saa7134_dev *dev) +{ + unsigned char buf; + int i,rc; + + for (i = 0; i < 256; i+= 2) { + dev->i2c_client.addr = i >> 1; + rc = i2c_master_recv(&dev->i2c_client,&buf,0); + if (rc < 0) + continue; + printk("%s: i2c scan: found device @ %x%s\n", + dev->name, i, (i == 0xa0) ? " [eeprom]" : ""); + } + return 0; +} + +void saa7134_i2c_call_clients(struct saa7134_dev *dev, + unsigned int cmd, void *arg) +{ + BUG_ON(NULL == dev->i2c_adap.algo_data); + i2c_clients_command(&dev->i2c_adap, cmd, arg); +} + +int saa7134_i2c_register(struct saa7134_dev *dev) +{ + dev->i2c_adap = saa7134_adap_template; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,66) + dev->i2c_adap.dev.parent = &dev->pci->dev; +#endif + strcpy(dev->i2c_adap.name,dev->name); + dev->i2c_adap.algo_data = dev; + i2c_add_adapter(&dev->i2c_adap); + + dev->i2c_client = saa7134_client_template; + dev->i2c_client.adapter = &dev->i2c_adap; + + saa7134_i2c_eeprom(dev,dev->eedata,sizeof(dev->eedata)); + if (i2c_scan) + saa7134_i2c_scan(dev); + return 0; +} + +int saa7134_i2c_unregister(struct saa7134_dev *dev) +{ + i2c_del_adapter(&dev->i2c_adap); + return 0; +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/saa7134/saa7134-input.c b/linux/drivers/media/video/saa7134/saa7134-input.c new file mode 100644 index 000000000..2aa1f6914 --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa7134-input.c @@ -0,0 +1,315 @@ +/* + * handle saa7134 IR remotes via linux kernel input layer. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/input.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* ---------------------------------------------------------------------- */ + +static IR_KEYTAB_TYPE flyvideo_codes[IR_KEYTAB_SIZE] = { + [ 15 ] = KEY_KP0, + [ 3 ] = KEY_KP1, + [ 4 ] = KEY_KP2, + [ 5 ] = KEY_KP3, + [ 7 ] = KEY_KP4, + [ 8 ] = KEY_KP5, + [ 9 ] = KEY_KP6, + [ 11 ] = KEY_KP7, + [ 12 ] = KEY_KP8, + [ 13 ] = KEY_KP9, + + [ 14 ] = KEY_TUNER, // Air/Cable + [ 17 ] = KEY_VIDEO, // Video + [ 21 ] = KEY_AUDIO, // Audio + [ 0 ] = KEY_POWER, // Pover + [ 2 ] = KEY_ZOOM, // Fullscreen + [ 27 ] = KEY_MUTE, // Mute + [ 20 ] = KEY_VOLUMEUP, + [ 23 ] = KEY_VOLUMEDOWN, + [ 18 ] = KEY_CHANNELUP, // Channel + + [ 19 ] = KEY_CHANNELDOWN, // Channel - + [ 6 ] = KEY_AGAIN, // Recal + [ 16 ] = KEY_KPENTER, // Enter + +#if 1 /* FIXME */ + [ 26 ] = KEY_F22, // Stereo + [ 24 ] = KEY_EDIT, // AV Source +#endif +}; + +static IR_KEYTAB_TYPE cinergy_codes[IR_KEYTAB_SIZE] = { + [ 0 ] = KEY_KP0, + [ 1 ] = KEY_KP1, + [ 2 ] = KEY_KP2, + [ 3 ] = KEY_KP3, + [ 4 ] = KEY_KP4, + [ 5 ] = KEY_KP5, + [ 6 ] = KEY_KP6, + [ 7 ] = KEY_KP7, + [ 8 ] = KEY_KP8, + [ 9 ] = KEY_KP9, + + [ 0x0a ] = KEY_POWER, + [ 0x0b ] = KEY_PROG1, // app + [ 0x0c ] = KEY_ZOOM, // zoom/fullscreen + [ 0x0d ] = KEY_CHANNELUP, // channel + [ 0x0e ] = KEY_CHANNELDOWN, // channel- + [ 0x0f ] = KEY_VOLUMEUP, + [ 0x10 ] = KEY_VOLUMEDOWN, + [ 0x11 ] = KEY_TUNER, // AV + [ 0x12 ] = KEY_NUMLOCK, // -/-- + [ 0x13 ] = KEY_AUDIO, // audio + [ 0x14 ] = KEY_MUTE, + [ 0x15 ] = KEY_UP, + [ 0x16 ] = KEY_DOWN, + [ 0x17 ] = KEY_LEFT, + [ 0x18 ] = KEY_RIGHT, + [ 0x19 ] = BTN_LEFT, + [ 0x1a ] = BTN_RIGHT, + [ 0x1b ] = KEY_WWW, // text + [ 0x1c ] = KEY_REWIND, + [ 0x1d ] = KEY_FORWARD, + [ 0x1e ] = KEY_RECORD, + [ 0x1f ] = KEY_PLAY, + [ 0x20 ] = KEY_PREVIOUSSONG, + [ 0x21 ] = KEY_NEXTSONG, + [ 0x22 ] = KEY_PAUSE, + [ 0x23 ] = KEY_STOP, +}; + +/* Alfons Geser <a.geser@cox.net> */ +static IR_KEYTAB_TYPE eztv_codes[IR_KEYTAB_SIZE] = { + [ 18 ] = KEY_POWER, + [ 1 ] = KEY_TV, // DVR + [ 21 ] = KEY_VIDEO, // DVD + [ 23 ] = KEY_AUDIO, // music + + // DVR mode / DVD mode / music mode + + [ 27 ] = KEY_MUTE, // mute + [ 2 ] = KEY_RESERVED, // MTS/SAP / audio /autoseek + [ 30 ] = KEY_RESERVED, // closed captioning / subtitle / seek + [ 22 ] = KEY_ZOOM, // full screen + [ 28 ] = KEY_RESERVED, // video source / eject /delall + [ 29 ] = KEY_RESERVED, // playback / angle /del + [ 47 ] = KEY_SEARCH, // scan / menu / playlist + [ 48 ] = KEY_RESERVED, // CH surfing / bookmark / memo + + [ 49 ] = KEY_HELP, // help + [ 50 ] = KEY_RESERVED, // num/memo + [ 51 ] = KEY_ESC, // cancel + + [ 12 ] = KEY_UP, // up + [ 16 ] = KEY_DOWN, // down + [ 8 ] = KEY_LEFT, // left + [ 4 ] = KEY_RIGHT, // right + [ 3 ] = KEY_ENTER, // select + + [ 31 ] = KEY_REWIND, // rewind + [ 32 ] = KEY_PLAYPAUSE, // play/pause + [ 41 ] = KEY_FORWARD, // forward + [ 20 ] = KEY_RESERVED, // repeat + [ 43 ] = KEY_RECORD, // recording + [ 44 ] = KEY_STOP, // stop + [ 45 ] = KEY_PLAY, // play + [ 46 ] = KEY_RESERVED, // snapshot + + [ 0 ] = KEY_KP0, + [ 5 ] = KEY_KP1, + [ 6 ] = KEY_KP2, + [ 7 ] = KEY_KP3, + [ 9 ] = KEY_KP4, + [ 10 ] = KEY_KP5, + [ 11 ] = KEY_KP6, + [ 13 ] = KEY_KP7, + [ 14 ] = KEY_KP8, + [ 15 ] = KEY_KP9, + + [ 42 ] = KEY_VOLUMEUP, + [ 17 ] = KEY_VOLUMEDOWN, + [ 24 ] = KEY_CHANNELUP, // CH.tracking up + [ 25 ] = KEY_CHANNELDOWN, // CH.tracking down + + [ 19 ] = KEY_KPENTER, // enter + [ 33 ] = KEY_KPDOT, // . (decimal dot) +}; + +/* ---------------------------------------------------------------------- */ + +static int build_key(struct saa7134_dev *dev) +{ + struct saa7134_ir *ir = dev->remote; + u32 gpio, data; + + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + saa_clearb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + if (ir->polling) { + if (ir->last_gpio == gpio) + return 0; + ir->last_gpio = gpio; + } + + data = ir_extract_bits(gpio, ir->mask_keycode); + printk("%s: build_key gpio=0x%x mask=0x%x data=%d\n", + dev->name, gpio, ir->mask_keycode, data); + + if ((ir->mask_keydown && (0 != (gpio & ir->mask_keydown))) || + (ir->mask_keyup && (0 == (gpio & ir->mask_keyup)))) { + ir_input_keydown(&ir->dev,&ir->ir,data,data); + } else { + ir_input_nokey(&ir->dev,&ir->ir); + } + return 0; +} + +/* ---------------------------------------------------------------------- */ + +void saa7134_input_irq(struct saa7134_dev *dev) +{ + struct saa7134_ir *ir = dev->remote; + + if (!ir->polling) + build_key(dev); +} + +static void saa7134_input_timer(unsigned long data) +{ + struct saa7134_dev *dev = (struct saa7134_dev*)data; + struct saa7134_ir *ir = dev->remote; + unsigned long timeout; + + build_key(dev); + timeout = jiffies + (ir->polling * HZ / 1000); + mod_timer(&ir->timer, timeout); +} + +int saa7134_input_init1(struct saa7134_dev *dev) +{ + struct saa7134_ir *ir; + IR_KEYTAB_TYPE *ir_codes = NULL; + u32 mask_keycode = 0; + u32 mask_keydown = 0; + u32 mask_keyup = 0; + int polling = 0; + int ir_type = IR_TYPE_OTHER; + + /* detect & configure */ + if (!dev->has_remote) + return -ENODEV; + switch (dev->board) { + case SAA7134_BOARD_FLYVIDEO2000: + case SAA7134_BOARD_FLYVIDEO3000: + ir_codes = flyvideo_codes; + mask_keycode = 0xEC00000; + mask_keydown = 0x0040000; + break; + case SAA7134_BOARD_CINERGY400: + case SAA7134_BOARD_CINERGY600: + ir_codes = cinergy_codes; + mask_keycode = 0x00003f; + mask_keyup = 0x040000; + break; + case SAA7134_BOARD_ECS_TVP3XP: + case SAA7134_BOARD_ECS_TVP3XP_4CB5: + ir_codes = eztv_codes; + mask_keycode = 0x00017c; + mask_keyup = 0x000002; + polling = 50; // ms + break; + } + if (NULL == ir_codes) { + printk("%s: Oops: IR config error [card=%d]\n", + dev->name, dev->board); + return -ENODEV; + } + + ir = kmalloc(sizeof(*ir),GFP_KERNEL); + if (NULL == ir) + return -ENOMEM; + memset(ir,0,sizeof(*ir)); + + /* init hardware-specific stuff */ + ir->mask_keycode = mask_keycode; + ir->mask_keydown = mask_keydown; + ir->mask_keyup = mask_keyup; + ir->polling = polling; + + /* init input device */ + snprintf(ir->name, sizeof(ir->name), "saa7134 IR (%s)", + saa7134_boards[dev->board].name); + snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", + pci_name(dev->pci)); + + ir_input_init(&ir->dev, &ir->ir, ir_type, ir_codes); + ir->dev.name = ir->name; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + ir->dev.phys = ir->phys; + ir->dev.id.bustype = BUS_PCI; + ir->dev.id.version = 1; + if (dev->pci->subsystem_vendor) { + ir->dev.id.vendor = dev->pci->subsystem_vendor; + ir->dev.id.product = dev->pci->subsystem_device; + } else { + ir->dev.id.vendor = dev->pci->vendor; + ir->dev.id.product = dev->pci->device; + } +#endif + + /* all done */ + dev->remote = ir; + if (ir->polling) { + init_timer(&ir->timer); + ir->timer.function = saa7134_input_timer; + ir->timer.data = (unsigned long)dev; + ir->timer.expires = jiffies + HZ; + add_timer(&ir->timer); + } + + input_register_device(&dev->remote->dev); + printk("%s: registered input device for IR\n",dev->name); + return 0; +} + +void saa7134_input_fini(struct saa7134_dev *dev) +{ + if (NULL == dev->remote) + return; + + input_unregister_device(&dev->remote->dev); + if (dev->remote->polling) + del_timer_sync(&dev->remote->timer); + kfree(dev->remote); + dev->remote = NULL; +} + +/* ---------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/saa7134/saa7134-oss.c b/linux/drivers/media/video/saa7134/saa7134-oss.c new file mode 100644 index 000000000..398e62438 --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa7134-oss.c @@ -0,0 +1,829 @@ +/* + * device driver for philips saa7134 based TV cards + * oss dsp interface + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * 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/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/soundcard.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* ------------------------------------------------------------------ */ + +static unsigned int oss_debug = 0; +MODULE_PARM(oss_debug,"i"); +MODULE_PARM_DESC(oss_debug,"enable debug messages [oss]"); + +static unsigned int oss_rate = 0; +MODULE_PARM(oss_rate,"i"); +MODULE_PARM_DESC(oss_rate,"sample rate (valid are: 32000,48000)"); + +#define dprintk(fmt, arg...) if (oss_debug) \ + printk(KERN_DEBUG "%s/oss: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ + +static int dsp_buffer_conf(struct saa7134_dev *dev, int blksize, int blocks) +{ + blksize &= ~0xff; + if (blksize < 0x100) + blksize = 0x100; + if (blksize > 0x10000) + blksize = 0x100; + + if (blocks < 2) + blocks = 2; + while ((blksize * blocks) & ~PAGE_MASK) + blocks++; + if ((blksize * blocks) > 1024*1024) + blocks = 1024*1024 / blksize; + + dev->oss.blocks = blocks; + dev->oss.blksize = blksize; + dev->oss.bufsize = blksize * blocks; + + dprintk("buffer config: %d blocks / %d bytes, %d kB total\n", + blocks,blksize,blksize * blocks / 1024); + return 0; +} + +static int dsp_buffer_init(struct saa7134_dev *dev) +{ + int err; + + if (!dev->oss.bufsize) + BUG(); + err = videobuf_dma_init_kernel(&dev->oss.dma, PCI_DMA_FROMDEVICE, + dev->oss.bufsize >> PAGE_SHIFT); + if (0 != err) + return err; + return 0; +} + +static int dsp_buffer_free(struct saa7134_dev *dev) +{ + if (!dev->oss.blksize) + BUG(); + videobuf_dma_free(&dev->oss.dma); + dev->oss.blocks = 0; + dev->oss.blksize = 0; + dev->oss.bufsize = 0; + return 0; +} + +static int dsp_rec_start(struct saa7134_dev *dev) +{ + int err, bswap, sign; + u32 fmt, control; + unsigned long flags; + + /* prepare buffer */ + if (0 != (err = videobuf_dma_pci_map(dev->pci,&dev->oss.dma))) + return err; + if (0 != (err = saa7134_pgtable_alloc(dev->pci,&dev->oss.pt))) + goto fail1; + if (0 != (err = saa7134_pgtable_build(dev->pci,&dev->oss.pt, + dev->oss.dma.sglist, + dev->oss.dma.sglen, + 0))) + goto fail2; + + /* sample format */ + switch (dev->oss.afmt) { + case AFMT_U8: + case AFMT_S8: fmt = 0x00; break; + case AFMT_U16_LE: + case AFMT_U16_BE: + case AFMT_S16_LE: + case AFMT_S16_BE: fmt = 0x01; break; + default: + err = -EINVAL; + goto fail2; + } + + switch (dev->oss.afmt) { + case AFMT_S8: + case AFMT_S16_LE: + case AFMT_S16_BE: sign = 1; break; + default: sign = 0; break; + } + + switch (dev->oss.afmt) { + case AFMT_U16_BE: + case AFMT_S16_BE: bswap = 1; break; + default: bswap = 0; break; + } + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + if (1 == dev->oss.channels) + fmt |= (1 << 3); + if (2 == dev->oss.channels) + fmt |= (3 << 3); + if (sign) + fmt |= 0x04; + fmt |= (TV == dev->oss.input) ? 0xc0 : 0x80; + + saa_writeb(SAA7134_NUM_SAMPLES0, (dev->oss.blksize & 0x0000ff)); + saa_writeb(SAA7134_NUM_SAMPLES1, (dev->oss.blksize & 0x00ff00) >> 8); + saa_writeb(SAA7134_NUM_SAMPLES2, (dev->oss.blksize & 0xff0000) >> 16); + saa_writeb(SAA7134_AUDIO_FORMAT_CTRL, fmt); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + if (1 == dev->oss.channels) + fmt |= (1 << 4); + if (2 == dev->oss.channels) + fmt |= (2 << 4); + if (!sign) + fmt |= 0x04; + saa_writel(0x588 >> 2, dev->oss.blksize); + saa_writel(0x58c >> 2, 0x543210 | (fmt << 24)); + break; + } + dprintk("rec_start: afmt=%d ch=%d => fmt=0x%x swap=%c\n", + dev->oss.afmt, dev->oss.channels, fmt, + bswap ? 'b' : '-'); + + /* dma: setup channel 6 (= AUDIO) */ + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (dev->oss.pt.dma >> 12); + if (bswap) + control |= SAA7134_RS_CONTROL_BSWAP; + saa_writel(SAA7134_RS_BA1(6),0); + saa_writel(SAA7134_RS_BA2(6),dev->oss.blksize); + saa_writel(SAA7134_RS_PITCH(6),0); + saa_writel(SAA7134_RS_CONTROL(6),control); + + /* start dma */ + spin_lock_irqsave(&dev->slock,flags); + dev->oss.recording = 1; + dev->oss.dma_blk = 0; + saa7134_set_dmabits(dev); + spin_unlock_irqrestore(&dev->slock,flags); + return 0; + + fail2: + saa7134_pgtable_free(dev->pci,&dev->oss.pt); + fail1: + videobuf_dma_pci_unmap(dev->pci,&dev->oss.dma); + return err; +} + +static int dsp_rec_stop(struct saa7134_dev *dev) +{ + unsigned long flags; + + dprintk("rec_stop dma_blk=%d\n",dev->oss.dma_blk); + + /* stop dma */ + spin_lock_irqsave(&dev->slock,flags); + dev->oss.dma_blk = -1; + dev->oss.recording = 0; + saa7134_set_dmabits(dev); + spin_unlock_irqrestore(&dev->slock,flags); + + /* unlock buffer */ + saa7134_pgtable_free(dev->pci,&dev->oss.pt); + videobuf_dma_pci_unmap(dev->pci,&dev->oss.dma); + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int dsp_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct saa7134_dev *h,*dev = NULL; + struct list_head *list; + int err; + + list_for_each(list,&saa7134_devlist) { + h = list_entry(list, struct saa7134_dev, devlist); + if (h->oss.minor_dsp == minor) + dev = h; + } + if (NULL == dev) + return -ENODEV; + + down(&dev->oss.lock); + err = -EBUSY; + if (dev->oss.users_dsp) + goto fail1; + dev->oss.users_dsp++; + file->private_data = dev; + + dev->oss.afmt = AFMT_U8; + dev->oss.channels = 1; + dev->oss.read_count = 0; + dev->oss.read_offset = 0; + dsp_buffer_conf(dev,PAGE_SIZE,64); + err = dsp_buffer_init(dev); + if (0 != err) + goto fail2; + + up(&dev->oss.lock); + return 0; + + fail2: + dev->oss.users_dsp--; + fail1: + up(&dev->oss.lock); + return err; +} + +static int dsp_release(struct inode *inode, struct file *file) +{ + struct saa7134_dev *dev = file->private_data; + + down(&dev->oss.lock); + if (dev->oss.recording) + dsp_rec_stop(dev); + dsp_buffer_free(dev); + dev->oss.users_dsp--; + file->private_data = NULL; + up(&dev->oss.lock); + return 0; +} + +static ssize_t dsp_read(struct file *file, char *buffer, + size_t count, loff_t *ppos) +{ + struct saa7134_dev *dev = file->private_data; + DECLARE_WAITQUEUE(wait, current); + unsigned int bytes; + int err,ret = 0; + + add_wait_queue(&dev->oss.wq, &wait); + down(&dev->oss.lock); + while (count > 0) { + /* wait for data if needed */ + if (0 == dev->oss.read_count) { + if (!dev->oss.recording) { + err = dsp_rec_start(dev); + if (err < 0) { + if (0 == ret) + ret = err; + break; + } + } + if (file->f_flags & O_NONBLOCK) { + if (0 == ret) + ret = -EAGAIN; + break; + } + up(&dev->oss.lock); + set_current_state(TASK_INTERRUPTIBLE); + if (0 == dev->oss.read_count) + schedule(); + set_current_state(TASK_RUNNING); + down(&dev->oss.lock); + if (signal_pending(current)) { + if (0 == ret) + ret = -EINTR; + break; + } + } + + /* copy data to userspace */ + bytes = count; + if (bytes > dev->oss.read_count) + bytes = dev->oss.read_count; + if (bytes > dev->oss.bufsize - dev->oss.read_offset) + bytes = dev->oss.bufsize - dev->oss.read_offset; + if (copy_to_user(buffer + ret, + dev->oss.dma.vmalloc + dev->oss.read_offset, + bytes)) { + if (0 == ret) + ret = -EFAULT; + break; + } + + ret += bytes; + count -= bytes; + dev->oss.read_count -= bytes; + dev->oss.read_offset += bytes; + if (dev->oss.read_offset == dev->oss.bufsize) + dev->oss.read_offset = 0; + } + up(&dev->oss.lock); + remove_wait_queue(&dev->oss.wq, &wait); + return ret; +} + +static ssize_t dsp_write(struct file *file, const char *buffer, + size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +static int dsp_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct saa7134_dev *dev = file->private_data; + int val = 0; + + if (oss_debug > 1) + saa7134_print_ioctl(dev->name,cmd); + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, (int *)arg); + case SNDCTL_DSP_GETCAPS: + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, (int*)arg)) + return -EFAULT; + /* fall through */ + case SOUND_PCM_READ_RATE: + return put_user(dev->oss.rate, (int*)arg); + + case SNDCTL_DSP_STEREO: + if (get_user(val, (int*)arg)) + return -EFAULT; + down(&dev->oss.lock); + dev->oss.channels = val ? 2 : 1; + if (dev->oss.recording) { + dsp_rec_stop(dev); + dsp_rec_start(dev); + } + up(&dev->oss.lock); + return put_user(dev->oss.channels-1, (int *)arg); + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, (int*)arg)) + return -EFAULT; + if (val != 1 && val != 2) + return -EINVAL; + down(&dev->oss.lock); + dev->oss.channels = val; + if (dev->oss.recording) { + dsp_rec_stop(dev); + dsp_rec_start(dev); + } + up(&dev->oss.lock); + /* fall through */ + case SOUND_PCM_READ_CHANNELS: + return put_user(dev->oss.channels, (int *)arg); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_U8 | AFMT_S8 | + AFMT_U16_LE | AFMT_U16_BE | + AFMT_S16_LE | AFMT_S16_BE, (int*)arg); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt */ + if (get_user(val, (int*)arg)) + return -EFAULT; + switch (val) { + case AFMT_QUERY: + /* nothing to do */ + break; + case AFMT_U8: + case AFMT_S8: + case AFMT_U16_LE: + case AFMT_U16_BE: + case AFMT_S16_LE: + case AFMT_S16_BE: + down(&dev->oss.lock); + dev->oss.afmt = val; + if (dev->oss.recording) { + dsp_rec_stop(dev); + dsp_rec_start(dev); + } + up(&dev->oss.lock); + return put_user(dev->oss.afmt,(int*)arg); + default: + return -EINVAL; + } + + case SOUND_PCM_READ_BITS: + switch (dev->oss.afmt) { + case AFMT_U8: + case AFMT_S8: + return put_user(8, (int*)arg); + case AFMT_U16_LE: + case AFMT_U16_BE: + case AFMT_S16_LE: + case AFMT_S16_BE: + return put_user(16, (int*)arg); + default: + return -EINVAL; + } + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_RESET: + down(&dev->oss.lock); + if (dev->oss.recording) + dsp_rec_stop(dev); + up(&dev->oss.lock); + return 0; + case SNDCTL_DSP_GETBLKSIZE: + return put_user(dev->oss.blksize,(int*)arg); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, (int*)arg)) + return -EFAULT; + if (dev->oss.recording) + return -EBUSY; + dsp_buffer_free(dev); + dsp_buffer_conf(dev,1 << (val & 0xffff), (arg >> 16) & 0xffff); + dsp_buffer_init(dev); + return 0; + + case SNDCTL_DSP_SYNC: + /* NOP */ + return 0; + + case SNDCTL_DSP_GETISPACE: + { + audio_buf_info info; + info.fragsize = dev->oss.blksize; + info.fragstotal = dev->oss.blocks; + info.bytes = dev->oss.read_count; + info.fragments = info.bytes / info.fragsize; + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + default: + return -EINVAL; + } +} + +static unsigned int dsp_poll(struct file *file, struct poll_table_struct *wait) +{ + struct saa7134_dev *dev = file->private_data; + unsigned int mask = 0; + + poll_wait(file, &dev->oss.wq, wait); + + if (0 == dev->oss.read_count) { + down(&dev->oss.lock); + if (!dev->oss.recording) + dsp_rec_start(dev); + up(&dev->oss.lock); + } else + mask |= (POLLIN | POLLRDNORM); + return mask; +} + +struct file_operations saa7134_dsp_fops = { + .owner = THIS_MODULE, + .open = dsp_open, + .release = dsp_release, + .read = dsp_read, + .write = dsp_write, + .ioctl = dsp_ioctl, + .poll = dsp_poll, + .llseek = no_llseek, +}; + +/* ------------------------------------------------------------------ */ + +static int +mixer_recsrc_7134(struct saa7134_dev *dev) +{ + int analog_io,rate; + + switch (dev->oss.input) { + case TV: + saa_andorb(SAA7134_AUDIO_FORMAT_CTRL, 0xc0, 0xc0); + saa_andorb(SAA7134_SIF_SAMPLE_FREQ, 0x03, 0x00); + break; + case LINE1: + case LINE2: + analog_io = (LINE1 == dev->oss.input) ? 0x00 : 0x08; + rate = (32000 == dev->oss.rate) ? 0x01 : 0x03; + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x08, analog_io); + saa_andorb(SAA7134_AUDIO_FORMAT_CTRL, 0xc0, 0x80); + saa_andorb(SAA7134_SIF_SAMPLE_FREQ, 0x03, rate); + break; + } + return 0; +} + +static int +mixer_recsrc_7133(struct saa7134_dev *dev) +{ + u32 value = 0xbbbbbb; + + switch (dev->oss.input) { + case TV: + value = 0xbbbb10; /* MAIN */ + break; + case LINE1: + value = 0xbbbb32; /* AUX1 */ + break; + case LINE2: + value = 0xbbbb54; /* AUX2 */ + break; + } + saa_dsp_writel(dev, 0x46c >> 2, value); + return 0; +} + +static int +mixer_recsrc(struct saa7134_dev *dev, enum saa7134_audio_in src) +{ + static const char *iname[] = { "Oops", "TV", "LINE1", "LINE2" }; + + dev->oss.count++; + dev->oss.input = src; + dprintk("mixer input = %s\n",iname[dev->oss.input]); + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + mixer_recsrc_7134(dev); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + mixer_recsrc_7133(dev); + break; + } + return 0; +} + +static int +mixer_level(struct saa7134_dev *dev, enum saa7134_audio_in src, int level) +{ + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + switch (src) { + case TV: + /* nothing */ + break; + case LINE1: + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x10, + (100 == level) ? 0x00 : 0x10); + break; + case LINE2: + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x20, + (100 == level) ? 0x00 : 0x20); + break; + } + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + /* nothing */ + break; + } + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int mixer_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct saa7134_dev *h,*dev = NULL; + struct list_head *list; + + list_for_each(list,&saa7134_devlist) { + h = list_entry(list, struct saa7134_dev, devlist); + if (h->oss.minor_mixer == minor) + dev = h; + } + if (NULL == dev) + return -ENODEV; + + file->private_data = dev; + return 0; +} + +static int mixer_release(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + return 0; +} + +static int mixer_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct saa7134_dev *dev = file->private_data; + enum saa7134_audio_in input; + int val,ret; + + if (oss_debug > 1) + saa7134_print_ioctl(dev->name,cmd); + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, (int *)arg); + case SOUND_MIXER_INFO: + { + mixer_info info; + memset(&info,0,sizeof(info)); + strlcpy(info.id, "TV audio", sizeof(info.id)); + strlcpy(info.name, dev->name, sizeof(info.name)); + info.modify_counter = dev->oss.count; + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + case SOUND_OLD_MIXER_INFO: + { + _old_mixer_info info; + memset(&info,0,sizeof(info)); + strlcpy(info.id, "TV audio", sizeof(info.id)); + strlcpy(info.name, dev->name, sizeof(info.name)); + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + case MIXER_READ(SOUND_MIXER_CAPS): + return put_user(SOUND_CAP_EXCL_INPUT,(int*)arg); + case MIXER_READ(SOUND_MIXER_STEREODEVS): + return put_user(0,(int*)arg); + case MIXER_READ(SOUND_MIXER_RECMASK): + case MIXER_READ(SOUND_MIXER_DEVMASK): + val = SOUND_MASK_LINE1 | SOUND_MASK_LINE2; + if (32000 == dev->oss.rate) + val |= SOUND_MASK_VIDEO; + return put_user(val,(int*)arg); + + case MIXER_WRITE(SOUND_MIXER_RECSRC): + if (get_user(val, (int *)arg)) + return -EFAULT; + input = dev->oss.input; + if (32000 == dev->oss.rate && + val & SOUND_MASK_VIDEO && dev->oss.input != TV) + input = TV; + if (val & SOUND_MASK_LINE1 && dev->oss.input != LINE1) + input = LINE1; + if (val & SOUND_MASK_LINE2 && dev->oss.input != LINE2) + input = LINE2; + if (input != dev->oss.input) + mixer_recsrc(dev,input); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_RECSRC): + switch (dev->oss.input) { + case TV: ret = SOUND_MASK_VIDEO; break; + case LINE1: ret = SOUND_MASK_LINE1; break; + case LINE2: ret = SOUND_MASK_LINE2; break; + default: ret = 0; + } + return put_user(ret,(int*)arg); + + case MIXER_WRITE(SOUND_MIXER_VIDEO): + case MIXER_READ(SOUND_MIXER_VIDEO): + if (32000 != dev->oss.rate) + return -EINVAL; + return put_user(100 | 100 << 8,(int*)arg); + + case MIXER_WRITE(SOUND_MIXER_LINE1): + if (get_user(val, (int *)arg)) + return -EFAULT; + val &= 0xff; + val = (val <= 50) ? 50 : 100; + dev->oss.line1 = val; + mixer_level(dev,LINE1,dev->oss.line1); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_LINE1): + return put_user(dev->oss.line1 | dev->oss.line1 << 8, + (int*)arg); + + case MIXER_WRITE(SOUND_MIXER_LINE2): + if (get_user(val, (int *)arg)) + return -EFAULT; + val &= 0xff; + val = (val <= 50) ? 50 : 100; + dev->oss.line2 = val; + mixer_level(dev,LINE2,dev->oss.line2); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_LINE2): + return put_user(dev->oss.line2 | dev->oss.line2 << 8, + (int*)arg); + + default: + return -EINVAL; + } +} + +struct file_operations saa7134_mixer_fops = { + .owner = THIS_MODULE, + .open = mixer_open, + .release = mixer_release, + .ioctl = mixer_ioctl, + .llseek = no_llseek, +}; + +/* ------------------------------------------------------------------ */ + +int saa7134_oss_init1(struct saa7134_dev *dev) +{ + /* general */ + init_MUTEX(&dev->oss.lock); + init_waitqueue_head(&dev->oss.wq); + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + saa_writel(0x588 >> 2, 0x00000fff); + saa_writel(0x58c >> 2, 0x00543210); + saa_dsp_writel(dev, 0x46c >> 2, 0xbbbbbb); + break; + } + + /* dsp */ + dev->oss.rate = 32000; + if (oss_rate) + dev->oss.rate = oss_rate; + if (saa7134_boards[dev->board].i2s_rate) + dev->oss.rate = saa7134_boards[dev->board].i2s_rate; + dev->oss.rate = (dev->oss.rate > 40000) ? 48000 : 32000; + + /* mixer */ + dev->oss.line1 = 50; + dev->oss.line2 = 50; + mixer_level(dev,LINE1,dev->oss.line1); + mixer_level(dev,LINE2,dev->oss.line2); + mixer_recsrc(dev, (dev->oss.rate == 32000) ? TV : LINE2); + + return 0; +} + +int saa7134_oss_fini(struct saa7134_dev *dev) +{ + /* nothing */ + return 0; +} + +void saa7134_irq_oss_done(struct saa7134_dev *dev, unsigned long status) +{ + int next_blk, reg = 0; + + spin_lock(&dev->slock); + if (UNSET == dev->oss.dma_blk) { + dprintk("irq: recording stopped\n"); + goto done; + } + if (0 != (status & 0x0f000000)) + dprintk("irq: lost %ld\n", (status >> 24) & 0x0f); + if (0 == (status & 0x10000000)) { + /* odd */ + if (0 == (dev->oss.dma_blk & 0x01)) + reg = SAA7134_RS_BA1(6); + } else { + /* even */ + if (0 == (dev->oss.dma_blk & 0x00)) + reg = SAA7134_RS_BA2(6); + } + if (0 == reg) { + dprintk("irq: field oops [%s]\n", + (status & 0x10000000) ? "even" : "odd"); + goto done; + } + if (dev->oss.read_count >= dev->oss.blksize * (dev->oss.blocks-2)) { + dprintk("irq: overrun [full=%d/%d]\n",dev->oss.read_count, + dev->oss.bufsize); + dsp_rec_stop(dev); + goto done; + } + + /* next block addr */ + next_blk = (dev->oss.dma_blk + 2) % dev->oss.blocks; + saa_writel(reg,next_blk * dev->oss.blksize); + if (oss_debug > 2) + dprintk("irq: ok, %s, next_blk=%d, addr=%x\n", + (status & 0x10000000) ? "even" : "odd ", next_blk, + next_blk * dev->oss.blksize); + + /* update status & wake waiting readers */ + dev->oss.dma_blk = (dev->oss.dma_blk + 1) % dev->oss.blocks; + dev->oss.read_count += dev->oss.blksize; + wake_up(&dev->oss.wq); + + done: + spin_unlock(&dev->slock); +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/saa7134/saa7134-reg.h b/linux/drivers/media/video/saa7134/saa7134-reg.h new file mode 100644 index 000000000..c39ea875a --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa7134-reg.h @@ -0,0 +1,364 @@ +/* + * philips saa7134 registers + */ + +/* ------------------------------------------------------------------ */ +/* + * PCI ID's + */ +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7130 +# define PCI_DEVICE_ID_PHILIPS_SAA7130 0x7130 +#endif +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7133 +# define PCI_DEVICE_ID_PHILIPS_SAA7133 0x7133 +#endif +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7134 +# define PCI_DEVICE_ID_PHILIPS_SAA7134 0x7134 +#endif +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7135 +# define PCI_DEVICE_ID_PHILIPS_SAA7135 0x7135 +#endif + +/* ------------------------------------------------------------------ */ +/* + * registers -- 32 bit + */ + +/* DMA channels, n = 0 ... 6 */ +#define SAA7134_RS_BA1(n) ((0x200 >> 2) + 4*n) +#define SAA7134_RS_BA2(n) ((0x204 >> 2) + 4*n) +#define SAA7134_RS_PITCH(n) ((0x208 >> 2) + 4*n) +#define SAA7134_RS_CONTROL(n) ((0x20c >> 2) + 4*n) +#define SAA7134_RS_CONTROL_WSWAP (0x01 << 25) +#define SAA7134_RS_CONTROL_BSWAP (0x01 << 24) +#define SAA7134_RS_CONTROL_BURST_2 (0x01 << 21) +#define SAA7134_RS_CONTROL_BURST_4 (0x02 << 21) +#define SAA7134_RS_CONTROL_BURST_8 (0x03 << 21) +#define SAA7134_RS_CONTROL_BURST_16 (0x04 << 21) +#define SAA7134_RS_CONTROL_BURST_32 (0x05 << 21) +#define SAA7134_RS_CONTROL_BURST_64 (0x06 << 21) +#define SAA7134_RS_CONTROL_BURST_MAX (0x07 << 21) +#define SAA7134_RS_CONTROL_ME (0x01 << 20) +#define SAA7134_FIFO_SIZE (0x2a0 >> 2) +#define SAA7134_THRESHOULD (0x2a4 >> 2) + +/* main control */ +#define SAA7134_MAIN_CTRL (0x2a8 >> 2) +#define SAA7134_MAIN_CTRL_VPLLE (1 << 15) +#define SAA7134_MAIN_CTRL_APLLE (1 << 14) +#define SAA7134_MAIN_CTRL_EXOSC (1 << 13) +#define SAA7134_MAIN_CTRL_EVFE1 (1 << 12) +#define SAA7134_MAIN_CTRL_EVFE2 (1 << 11) +#define SAA7134_MAIN_CTRL_ESFE (1 << 10) +#define SAA7134_MAIN_CTRL_EBADC (1 << 9) +#define SAA7134_MAIN_CTRL_EBDAC (1 << 8) +#define SAA7134_MAIN_CTRL_TE6 (1 << 6) +#define SAA7134_MAIN_CTRL_TE5 (1 << 5) +#define SAA7134_MAIN_CTRL_TE4 (1 << 4) +#define SAA7134_MAIN_CTRL_TE3 (1 << 3) +#define SAA7134_MAIN_CTRL_TE2 (1 << 2) +#define SAA7134_MAIN_CTRL_TE1 (1 << 1) +#define SAA7134_MAIN_CTRL_TE0 (1 << 0) + +/* DMA status */ +#define SAA7134_DMA_STATUS (0x2ac >> 2) + +/* audio / video status */ +#define SAA7134_AV_STATUS (0x2c0 >> 2) +#define SAA7134_AV_STATUS_STEREO (1 << 17) +#define SAA7134_AV_STATUS_DUAL (1 << 16) +#define SAA7134_AV_STATUS_PILOT (1 << 15) +#define SAA7134_AV_STATUS_SMB (1 << 14) +#define SAA7134_AV_STATUS_DMB (1 << 13) +#define SAA7134_AV_STATUS_VDSP (1 << 12) +#define SAA7134_AV_STATUS_IIC_STATUS (3 << 10) +#define SAA7134_AV_STATUS_MVM (7 << 7) +#define SAA7134_AV_STATUS_FIDT (1 << 6) +#define SAA7134_AV_STATUS_INTL (1 << 5) +#define SAA7134_AV_STATUS_RDCAP (1 << 4) +#define SAA7134_AV_STATUS_PWR_ON (1 << 3) +#define SAA7134_AV_STATUS_LOAD_ERR (1 << 2) +#define SAA7134_AV_STATUS_TRIG_ERR (1 << 1) +#define SAA7134_AV_STATUS_CONF_ERR (1 << 0) + +/* interrupt */ +#define SAA7134_IRQ1 (0x2c4 >> 2) +#define SAA7134_IRQ1_INTE_RA3_1 (1 << 25) +#define SAA7134_IRQ1_INTE_RA3_0 (1 << 24) +#define SAA7134_IRQ1_INTE_RA2_3 (1 << 19) +#define SAA7134_IRQ1_INTE_RA2_2 (1 << 18) +#define SAA7134_IRQ1_INTE_RA2_1 (1 << 17) +#define SAA7134_IRQ1_INTE_RA2_0 (1 << 16) +#define SAA7134_IRQ1_INTE_RA1_3 (1 << 11) +#define SAA7134_IRQ1_INTE_RA1_2 (1 << 10) +#define SAA7134_IRQ1_INTE_RA1_1 (1 << 9) +#define SAA7134_IRQ1_INTE_RA1_0 (1 << 8) +#define SAA7134_IRQ1_INTE_RA0_7 (1 << 7) +#define SAA7134_IRQ1_INTE_RA0_6 (1 << 6) +#define SAA7134_IRQ1_INTE_RA0_5 (1 << 5) +#define SAA7134_IRQ1_INTE_RA0_4 (1 << 4) +#define SAA7134_IRQ1_INTE_RA0_3 (1 << 3) +#define SAA7134_IRQ1_INTE_RA0_2 (1 << 2) +#define SAA7134_IRQ1_INTE_RA0_1 (1 << 1) +#define SAA7134_IRQ1_INTE_RA0_0 (1 << 0) + +#define SAA7134_IRQ2 (0x2c8 >> 2) +#define SAA7134_IRQ2_INTE_GPIO23A (1 << 17) +#define SAA7134_IRQ2_INTE_GPIO23 (1 << 16) +#define SAA7134_IRQ2_INTE_GPIO22A (1 << 15) +#define SAA7134_IRQ2_INTE_GPIO22 (1 << 14) +#define SAA7134_IRQ2_INTE_GPIO18A (1 << 13) +#define SAA7134_IRQ2_INTE_GPIO18 (1 << 12) +#define SAA7134_IRQ2_INTE_GPIO16 (1 << 11) /* not certain */ +#define SAA7134_IRQ2_INTE_SC2 (1 << 10) +#define SAA7134_IRQ2_INTE_SC1 (1 << 9) +#define SAA7134_IRQ2_INTE_SC0 (1 << 8) +#define SAA7134_IRQ2_INTE_DEC5 (1 << 7) +#define SAA7134_IRQ2_INTE_DEC4 (1 << 6) +#define SAA7134_IRQ2_INTE_DEC3 (1 << 5) +#define SAA7134_IRQ2_INTE_DEC2 (1 << 4) +#define SAA7134_IRQ2_INTE_DEC1 (1 << 3) +#define SAA7134_IRQ2_INTE_DEC0 (1 << 2) +#define SAA7134_IRQ2_INTE_PE (1 << 1) +#define SAA7134_IRQ2_INTE_AR (1 << 0) + +#define SAA7134_IRQ_REPORT (0x2cc >> 2) +#define SAA7134_IRQ_REPORT_GPIO23 (1 << 17) +#define SAA7134_IRQ_REPORT_GPIO22 (1 << 16) +#define SAA7134_IRQ_REPORT_GPIO18 (1 << 15) +#define SAA7134_IRQ_REPORT_GPIO16 (1 << 14) /* not certain */ +#define SAA7134_IRQ_REPORT_LOAD_ERR (1 << 13) +#define SAA7134_IRQ_REPORT_CONF_ERR (1 << 12) +#define SAA7134_IRQ_REPORT_TRIG_ERR (1 << 11) +#define SAA7134_IRQ_REPORT_MMC (1 << 10) +#define SAA7134_IRQ_REPORT_FIDT (1 << 9) +#define SAA7134_IRQ_REPORT_INTL (1 << 8) +#define SAA7134_IRQ_REPORT_RDCAP (1 << 7) +#define SAA7134_IRQ_REPORT_PWR_ON (1 << 6) +#define SAA7134_IRQ_REPORT_PE (1 << 5) +#define SAA7134_IRQ_REPORT_AR (1 << 4) +#define SAA7134_IRQ_REPORT_DONE_RA3 (1 << 3) +#define SAA7134_IRQ_REPORT_DONE_RA2 (1 << 2) +#define SAA7134_IRQ_REPORT_DONE_RA1 (1 << 1) +#define SAA7134_IRQ_REPORT_DONE_RA0 (1 << 0) +#define SAA7134_IRQ_STATUS (0x2d0 >> 2) + + +/* ------------------------------------------------------------------ */ +/* + * registers -- 8 bit + */ + +/* video decoder */ +#define SAA7134_INCR_DELAY 0x101 +#define SAA7134_ANALOG_IN_CTRL1 0x102 +#define SAA7134_ANALOG_IN_CTRL2 0x103 +#define SAA7134_ANALOG_IN_CTRL3 0x104 +#define SAA7134_ANALOG_IN_CTRL4 0x105 +#define SAA7134_HSYNC_START 0x106 +#define SAA7134_HSYNC_STOP 0x107 +#define SAA7134_SYNC_CTRL 0x108 +#define SAA7134_LUMA_CTRL 0x109 +#define SAA7134_DEC_LUMA_BRIGHT 0x10a +#define SAA7134_DEC_LUMA_CONTRAST 0x10b +#define SAA7134_DEC_CHROMA_SATURATION 0x10c +#define SAA7134_DEC_CHROMA_HUE 0x10d +#define SAA7134_CHROMA_CTRL1 0x10e +#define SAA7134_CHROMA_GAIN 0x10f +#define SAA7134_CHROMA_CTRL2 0x110 +#define SAA7134_MODE_DELAY_CTRL 0x111 + +#define SAA7134_ANALOG_ADC 0x114 +#define SAA7134_VGATE_START 0x115 +#define SAA7134_VGATE_STOP 0x116 +#define SAA7134_MISC_VGATE_MSB 0x117 +#define SAA7134_RAW_DATA_GAIN 0x118 +#define SAA7134_RAW_DATA_OFFSET 0x119 +#define SAA7134_STATUS_VIDEO1 0x11e +#define SAA7134_STATUS_VIDEO2 0x11f + +/* video scaler */ +#define SAA7134_SOURCE_TIMING1 0x000 +#define SAA7134_SOURCE_TIMING2 0x001 +#define SAA7134_REGION_ENABLE 0x004 +#define SAA7134_SCALER_STATUS0 0x006 +#define SAA7134_SCALER_STATUS1 0x007 +#define SAA7134_START_GREEN 0x00c +#define SAA7134_START_BLUE 0x00d +#define SAA7134_START_RED 0x00e +#define SAA7134_GREEN_PATH(x) (0x010 +x) +#define SAA7134_BLUE_PATH(x) (0x020 +x) +#define SAA7134_RED_PATH(x) (0x030 +x) + +#define TASK_A 0x040 +#define TASK_B 0x080 +#define SAA7134_TASK_CONDITIONS(t) (0x000 +t) +#define SAA7134_FIELD_HANDLING(t) (0x001 +t) +#define SAA7134_DATA_PATH(t) (0x002 +t) +#define SAA7134_VBI_H_START1(t) (0x004 +t) +#define SAA7134_VBI_H_START2(t) (0x005 +t) +#define SAA7134_VBI_H_STOP1(t) (0x006 +t) +#define SAA7134_VBI_H_STOP2(t) (0x007 +t) +#define SAA7134_VBI_V_START1(t) (0x008 +t) +#define SAA7134_VBI_V_START2(t) (0x009 +t) +#define SAA7134_VBI_V_STOP1(t) (0x00a +t) +#define SAA7134_VBI_V_STOP2(t) (0x00b +t) +#define SAA7134_VBI_H_LEN1(t) (0x00c +t) +#define SAA7134_VBI_H_LEN2(t) (0x00d +t) +#define SAA7134_VBI_V_LEN1(t) (0x00e +t) +#define SAA7134_VBI_V_LEN2(t) (0x00f +t) + +#define SAA7134_VIDEO_H_START1(t) (0x014 +t) +#define SAA7134_VIDEO_H_START2(t) (0x015 +t) +#define SAA7134_VIDEO_H_STOP1(t) (0x016 +t) +#define SAA7134_VIDEO_H_STOP2(t) (0x017 +t) +#define SAA7134_VIDEO_V_START1(t) (0x018 +t) +#define SAA7134_VIDEO_V_START2(t) (0x019 +t) +#define SAA7134_VIDEO_V_STOP1(t) (0x01a +t) +#define SAA7134_VIDEO_V_STOP2(t) (0x01b +t) +#define SAA7134_VIDEO_PIXELS1(t) (0x01c +t) +#define SAA7134_VIDEO_PIXELS2(t) (0x01d +t) +#define SAA7134_VIDEO_LINES1(t) (0x01e +t) +#define SAA7134_VIDEO_LINES2(t) (0x01f +t) + +#define SAA7134_H_PRESCALE(t) (0x020 +t) +#define SAA7134_ACC_LENGTH(t) (0x021 +t) +#define SAA7134_LEVEL_CTRL(t) (0x022 +t) +#define SAA7134_FIR_PREFILTER_CTRL(t) (0x023 +t) +#define SAA7134_LUMA_BRIGHT(t) (0x024 +t) +#define SAA7134_LUMA_CONTRAST(t) (0x025 +t) +#define SAA7134_CHROMA_SATURATION(t) (0x026 +t) +#define SAA7134_VBI_H_SCALE_INC1(t) (0x028 +t) +#define SAA7134_VBI_H_SCALE_INC2(t) (0x029 +t) +#define SAA7134_VBI_PHASE_OFFSET_LUMA(t) (0x02a +t) +#define SAA7134_VBI_PHASE_OFFSET_CHROMA(t) (0x02b +t) +#define SAA7134_H_SCALE_INC1(t) (0x02c +t) +#define SAA7134_H_SCALE_INC2(t) (0x02d +t) +#define SAA7134_H_PHASE_OFF_LUMA(t) (0x02e +t) +#define SAA7134_H_PHASE_OFF_CHROMA(t) (0x02f +t) +#define SAA7134_V_SCALE_RATIO1(t) (0x030 +t) +#define SAA7134_V_SCALE_RATIO2(t) (0x031 +t) +#define SAA7134_V_FILTER(t) (0x032 +t) +#define SAA7134_V_PHASE_OFFSET0(t) (0x034 +t) +#define SAA7134_V_PHASE_OFFSET1(t) (0x035 +t) +#define SAA7134_V_PHASE_OFFSET2(t) (0x036 +t) +#define SAA7134_V_PHASE_OFFSET3(t) (0x037 +t) + +/* clipping & dma */ +#define SAA7134_OFMT_VIDEO_A 0x300 +#define SAA7134_OFMT_DATA_A 0x301 +#define SAA7134_OFMT_VIDEO_B 0x302 +#define SAA7134_OFMT_DATA_B 0x303 +#define SAA7134_ALPHA_NOCLIP 0x304 +#define SAA7134_ALPHA_CLIP 0x305 +#define SAA7134_UV_PIXEL 0x308 +#define SAA7134_CLIP_RED 0x309 +#define SAA7134_CLIP_GREEN 0x30a +#define SAA7134_CLIP_BLUE 0x30b + +/* i2c bus */ +#define SAA7134_I2C_ATTR_STATUS 0x180 +#define SAA7134_I2C_DATA 0x181 +#define SAA7134_I2C_CLOCK_SELECT 0x182 +#define SAA7134_I2C_TIMER 0x183 + +/* audio */ +#define SAA7134_NICAM_ADD_DATA1 0x140 +#define SAA7134_NICAM_ADD_DATA2 0x141 +#define SAA7134_NICAM_STATUS 0x142 +#define SAA7134_AUDIO_STATUS 0x143 +#define SAA7134_NICAM_ERROR_COUNT 0x144 +#define SAA7134_IDENT_SIF 0x145 +#define SAA7134_LEVEL_READOUT1 0x146 +#define SAA7134_LEVEL_READOUT2 0x147 +#define SAA7134_NICAM_ERROR_LOW 0x148 +#define SAA7134_NICAM_ERROR_HIGH 0x149 +#define SAA7134_DCXO_IDENT_CTRL 0x14a +#define SAA7134_DEMODULATOR 0x14b +#define SAA7134_AGC_GAIN_SELECT 0x14c +#define SAA7134_CARRIER1_FREQ0 0x150 +#define SAA7134_CARRIER1_FREQ1 0x151 +#define SAA7134_CARRIER1_FREQ2 0x152 +#define SAA7134_CARRIER2_FREQ0 0x154 +#define SAA7134_CARRIER2_FREQ1 0x155 +#define SAA7134_CARRIER2_FREQ2 0x156 +#define SAA7134_NUM_SAMPLES0 0x158 +#define SAA7134_NUM_SAMPLES1 0x159 +#define SAA7134_NUM_SAMPLES2 0x15a +#define SAA7134_AUDIO_FORMAT_CTRL 0x15b +#define SAA7134_MONITOR_SELECT 0x160 +#define SAA7134_FM_DEEMPHASIS 0x161 +#define SAA7134_FM_DEMATRIX 0x162 +#define SAA7134_CHANNEL1_LEVEL 0x163 +#define SAA7134_CHANNEL2_LEVEL 0x164 +#define SAA7134_NICAM_CONFIG 0x165 +#define SAA7134_NICAM_LEVEL_ADJUST 0x166 +#define SAA7134_STEREO_DAC_OUTPUT_SELECT 0x167 +#define SAA7134_I2S_OUTPUT_FORMAT 0x168 +#define SAA7134_I2S_OUTPUT_SELECT 0x169 +#define SAA7134_I2S_OUTPUT_LEVEL 0x16a +#define SAA7134_DSP_OUTPUT_SELECT 0x16b +#define SAA7134_AUDIO_MUTE_CTRL 0x16c +#define SAA7134_SIF_SAMPLE_FREQ 0x16d +#define SAA7134_ANALOG_IO_SELECT 0x16e +#define SAA7134_AUDIO_CLOCK0 0x170 +#define SAA7134_AUDIO_CLOCK1 0x171 +#define SAA7134_AUDIO_CLOCK2 0x172 +#define SAA7134_AUDIO_PLL_CTRL 0x173 +#define SAA7134_AUDIO_CLOCKS_PER_FIELD0 0x174 +#define SAA7134_AUDIO_CLOCKS_PER_FIELD1 0x175 +#define SAA7134_AUDIO_CLOCKS_PER_FIELD2 0x176 + +/* video port output */ +#define SAA7134_VIDEO_PORT_CTRL0 0x190 +#define SAA7134_VIDEO_PORT_CTRL1 0x191 +#define SAA7134_VIDEO_PORT_CTRL2 0x192 +#define SAA7134_VIDEO_PORT_CTRL3 0x193 +#define SAA7134_VIDEO_PORT_CTRL4 0x194 +#define SAA7134_VIDEO_PORT_CTRL5 0x195 +#define SAA7134_VIDEO_PORT_CTRL6 0x196 +#define SAA7134_VIDEO_PORT_CTRL7 0x197 +#define SAA7134_VIDEO_PORT_CTRL8 0x198 + +/* transport stream interface */ +#define SAA7134_TS_PARALLEL 0x1a0 +#define SAA7134_TS_PARALLEL_SERIAL 0x1a1 +#define SAA7134_TS_SERIAL0 0x1a2 +#define SAA7134_TS_SERIAL1 0x1a3 +#define SAA7134_TS_DMA0 0x1a4 +#define SAA7134_TS_DMA1 0x1a5 +#define SAA7134_TS_DMA2 0x1a6 + +/* GPIO Controls */ +#define SAA7134_GPIO_GPRESCAN 0x80 +#define SAA7134_GPIO_27_25 0x0E + +#define SAA7134_GPIO_GPMODE0 0x1B0 +#define SAA7134_GPIO_GPMODE1 0x1B1 +#define SAA7134_GPIO_GPMODE2 0x1B2 +#define SAA7134_GPIO_GPMODE3 0x1B3 +#define SAA7134_GPIO_GPSTATUS0 0x1B4 +#define SAA7134_GPIO_GPSTATUS1 0x1B5 +#define SAA7134_GPIO_GPSTATUS2 0x1B6 +#define SAA7134_GPIO_GPSTATUS3 0x1B7 + +/* I2S output */ +#define SAA7134_I2S_AUDIO_OUTPUT 0x1c0 + +/* test modes */ +#define SAA7134_SPECIAL_MODE 0x1d0 + +/* audio -- saa7133 + saa7135 only */ +#define SAA7135_DSP_RWSTATE 0x580 +#define SAA7135_DSP_RWSTATE_ERR (1 << 3) +#define SAA7135_DSP_RWSTATE_IDA (1 << 2) +#define SAA7135_DSP_RWSTATE_RDB (1 << 1) +#define SAA7135_DSP_RWSTATE_WRR (1 << 0) + +/* ------------------------------------------------------------------ */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ + diff --git a/linux/drivers/media/video/saa7134/saa7134-ts.c b/linux/drivers/media/video/saa7134/saa7134-ts.c new file mode 100644 index 000000000..a18478bfe --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa7134-ts.c @@ -0,0 +1,521 @@ +/* + * device driver for philips saa7134 based TV cards + * video4linux video interface + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * 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/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,64) +#include "saa6752hs.h" +#else +#include <media/saa6752hs.h> +#endif + +/* ------------------------------------------------------------------ */ + +static unsigned int ts_debug = 0; +MODULE_PARM(ts_debug,"i"); +MODULE_PARM_DESC(ts_debug,"enable debug messages [ts]"); + +static unsigned int tsbufs = 4; +MODULE_PARM(tsbufs,"i"); +MODULE_PARM_DESC(tsbufs,"number of ts buffers, range 2-32"); + +#define TS_PACKET_SIZE 188 /* TS packets 188 bytes */ +#define TS_NR_PACKETS 312 + +#define dprintk(fmt, arg...) if (ts_debug) \ + printk(KERN_DEBUG "%s/ts: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ + +static int buffer_activate(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next) +{ + u32 control; + + dprintk("buffer_activate [%p]",buf); + buf->vb.state = STATE_ACTIVE; + buf->top_seen = 0; + + /* dma: setup channel 5 (= TS) */ + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (buf->pt->dma >> 12); + + if (NULL == next) + next = buf; + if (V4L2_FIELD_TOP == buf->vb.field) { + dprintk("- [top] buf=%p next=%p\n",buf,next); + saa_writel(SAA7134_RS_BA1(5),saa7134_buffer_base(buf)); + saa_writel(SAA7134_RS_BA2(5),saa7134_buffer_base(next)); + } else { + dprintk("- [bottom] buf=%p next=%p\n",buf,next); + saa_writel(SAA7134_RS_BA1(5),saa7134_buffer_base(next)); + saa_writel(SAA7134_RS_BA2(5),saa7134_buffer_base(buf)); + } + saa_writel(SAA7134_RS_PITCH(5),TS_PACKET_SIZE); + saa_writel(SAA7134_RS_CONTROL(5),control); + + /* start DMA */ + saa7134_set_dmabits(dev); + + mod_timer(&dev->ts_q.timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +static int buffer_prepare(struct file *file, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct saa7134_dev *dev = file->private_data; + struct saa7134_buf *buf = (struct saa7134_buf *)vb; + unsigned int lines, llength, size; + int err; + + dprintk("buffer_prepare [%p,%s]\n",buf,v4l2_field_names[field]); + + llength = TS_PACKET_SIZE; + lines = TS_NR_PACKETS; + + size = lines * llength; + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + if (buf->vb.size != size) { + saa7134_dma_free(dev,buf); + } + + if (STATE_NEEDS_INIT == buf->vb.state) { + buf->vb.width = llength; + buf->vb.height = lines; + buf->vb.size = size; + buf->pt = &dev->ts.pt_ts; + + err = videobuf_iolock(dev->pci,&buf->vb,NULL); + if (err) + goto oops; + err = saa7134_pgtable_build(dev->pci,buf->pt, + buf->vb.dma.sglist, + buf->vb.dma.sglen, + saa7134_buffer_startpage(buf)); + if (err) + goto oops; + } + buf->vb.state = STATE_PREPARED; + buf->activate = buffer_activate; + buf->vb.field = field; + return 0; + + oops: + saa7134_dma_free(dev,buf); + return err; +} + +static int +buffer_setup(struct file *file, unsigned int *count, unsigned int *size) +{ + *size = TS_PACKET_SIZE * TS_NR_PACKETS; + if (0 == *count) + *count = tsbufs; + *count = saa7134_buffer_count(*size,*count); + return 0; +} + +static void buffer_queue(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7134_dev *dev = file->private_data; + struct saa7134_buf *buf = (struct saa7134_buf *)vb; + + saa7134_buffer_queue(dev,&dev->ts_q,buf); +} + +static void buffer_release(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7134_dev *dev = file->private_data; + struct saa7134_buf *buf = (struct saa7134_buf *)vb; + + saa7134_dma_free(dev,buf); +} + +static struct videobuf_queue_ops ts_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + + +/* ------------------------------------------------------------------ */ + +static void ts_reset_encoder(struct saa7134_dev* dev) +{ + saa_writeb(SAA7134_SPECIAL_MODE, 0x00); + mdelay(10); + saa_writeb(SAA7134_SPECIAL_MODE, 0x01); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); +} + +static int ts_init_encoder(struct saa7134_dev* dev, void* arg) +{ + ts_reset_encoder(dev); + saa7134_i2c_call_clients(dev, MPEG_SETPARAMS, arg); + return 0; +} + + +/* ------------------------------------------------------------------ */ + +static int ts_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct saa7134_dev *h,*dev = NULL; + struct list_head *list; + int err; + + list_for_each(list,&saa7134_devlist) { + h = list_entry(list, struct saa7134_dev, devlist); + if (h->ts_dev && h->ts_dev->minor == minor) + dev = h; + } + if (NULL == dev) + return -ENODEV; + + dprintk("open minor=%d\n",minor); + down(&dev->ts.ts.lock); + err = -EBUSY; + if (dev->ts.users) + goto done; + + dev->ts.started = 0; + dev->ts.users++; + file->private_data = dev; + err = 0; + + done: + up(&dev->ts.ts.lock); + return err; +} + +static int ts_release(struct inode *inode, struct file *file) +{ + struct saa7134_dev *dev = file->private_data; + + if (dev->ts.ts.streaming) + videobuf_streamoff(file,&dev->ts.ts); + down(&dev->ts.ts.lock); + if (dev->ts.ts.reading) + videobuf_read_stop(file,&dev->ts.ts); + dev->ts.users--; + + /* stop the encoder */ + if (dev->ts.started) + ts_reset_encoder(dev); + + up(&dev->ts.ts.lock); + return 0; +} + +static ssize_t +ts_read(struct file *file, char *data, size_t count, loff_t *ppos) +{ + struct saa7134_dev *dev = file->private_data; + + if (!dev->ts.started) { + ts_init_encoder(dev, NULL); + dev->ts.started = 1; + } + + return videobuf_read_stream(file, &dev->ts.ts, data, count, ppos, 0); +} + +static unsigned int +ts_poll(struct file *file, struct poll_table_struct *wait) +{ + struct saa7134_dev *dev = file->private_data; + + return videobuf_poll_stream(file, &dev->ts.ts, wait); +} + + +static int +ts_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct saa7134_dev *dev = file->private_data; + + return videobuf_mmap_mapper(vma, &dev->ts.ts); +} + +/* + * This function is _not_ called directly, but from + * video_generic_ioctl (and maybe others). userspace + * copying is done already, arg is a kernel pointer. + */ +static int ts_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct saa7134_dev *dev = file->private_data; + + if (ts_debug > 1) + saa7134_print_ioctl(dev->name,cmd); + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->driver, "saa7134"); + strlcpy(cap->card, saa7134_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s",pci_name(dev->pci)); + cap->version = SAA7134_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + return 0; + } + + /* --- input switching --------------------------------------- */ + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + + if (i->index != 0) + return -EINVAL; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name,"CCIR656"); + return 0; + } + case VIDIOC_G_INPUT: + { + int *i = arg; + *i = 0; + return 0; + } + case VIDIOC_S_INPUT: + { + int *i = arg; + + if (*i != 0) + return -EINVAL; + return 0; + } + /* --- capture ioctls ---------------------------------------- */ + + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *f = arg; + int index; + + index = f->index; + if (index != 0) + return -EINVAL; + + memset(f,0,sizeof(*f)); + f->index = index; + strlcpy(f->description, "MPEG TS", sizeof(f->description)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->pixelformat = V4L2_PIX_FMT_MPEG; + return 0; + } + + case VIDIOC_G_FMT: + { + struct v4l2_format *f = arg; + + memset(f,0,sizeof(*f)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* FIXME: translate subsampling type EMPRESS into + * width/height: */ + f->fmt.pix.width = 720; /* D1 */ + f->fmt.pix.height = 576; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.sizeimage = TS_PACKET_SIZE*TS_NR_PACKETS; + return 0; + } + + case VIDIOC_S_FMT: + { + struct v4l2_format *f = arg; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* + FIXME: translate and round width/height into EMPRESS + subsample type: + + type | PAL | NTSC + --------------------------- + SIF | 352x288 | 352x240 + 1/2 D1 | 352x576 | 352x480 + 2/3 D1 | 480x576 | 480x480 + D1 | 720x576 | 720x480 + */ + + f->fmt.pix.width = 720; /* D1 */ + f->fmt.pix.height = 576; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.sizeimage = TS_PACKET_SIZE*TS_NR_PACKETS; + return 0; + } + + case VIDIOC_REQBUFS: + return videobuf_reqbufs(file,&dev->ts.ts,arg); + + case VIDIOC_QUERYBUF: + return videobuf_querybuf(&dev->ts.ts,arg); + + case VIDIOC_QBUF: + return videobuf_qbuf(file,&dev->ts.ts,arg); + + case VIDIOC_DQBUF: + return videobuf_dqbuf(file,&dev->ts.ts,arg); + + case VIDIOC_STREAMON: + return videobuf_streamon(file,&dev->ts.ts); + + case VIDIOC_STREAMOFF: + return videobuf_streamoff(file,&dev->ts.ts); + + case VIDIOC_QUERYCTRL: + case VIDIOC_G_CTRL: + case VIDIOC_S_CTRL: + return saa7134_common_ioctl(dev, cmd, arg); + + case MPEG_SETPARAMS: + return ts_init_encoder(dev, arg); + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int ts_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, ts_do_ioctl); +} + + +static struct file_operations ts_fops = +{ + .owner = THIS_MODULE, + .open = ts_open, + .release = ts_release, + .read = ts_read, + .poll = ts_poll, + .mmap = ts_mmap, + .ioctl = ts_ioctl, + .llseek = no_llseek, +}; + + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +struct video_device saa7134_ts_template = +{ + .name = "saa7134-ts", + .type = 0 /* FIXME */, + .type2 = 0 /* FIXME */, + .hardware = 0, + .fops = &ts_fops, + .minor = -1, +}; + +int saa7134_ts_init1(struct saa7134_dev *dev) +{ + /* sanitycheck insmod options */ + if (tsbufs < 2) + tsbufs = 2; + if (tsbufs > VIDEO_MAX_FRAME) + tsbufs = VIDEO_MAX_FRAME; + + INIT_LIST_HEAD(&dev->ts_q.queue); + init_timer(&dev->ts_q.timeout); + dev->ts_q.timeout.function = saa7134_buffer_timeout; + dev->ts_q.timeout.data = (unsigned long)(&dev->ts_q); + dev->ts_q.dev = dev; + dev->ts_q.need_two = 1; + videobuf_queue_init(&dev->ts.ts, &ts_qops, dev->pci, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_ALTERNATE, + sizeof(struct saa7134_buf)); + saa7134_pgtable_alloc(dev->pci,&dev->ts.pt_ts); + + /* init TS hw */ + saa_writeb(SAA7134_TS_SERIAL1, 0x00); /* deactivate TS softreset */ + saa_writeb(SAA7134_TS_PARALLEL, 0xec); /* TSSOP high active, TSVAL high active, TSLOCK ignored */ + saa_writeb(SAA7134_TS_PARALLEL_SERIAL, (TS_PACKET_SIZE-1)); + saa_writeb(SAA7134_TS_DMA0, ((TS_NR_PACKETS-1)&0xff)); + saa_writeb(SAA7134_TS_DMA1, (((TS_NR_PACKETS-1)>>8)&0xff)); + saa_writeb(SAA7134_TS_DMA2, ((((TS_NR_PACKETS-1)>>16)&0x3f) | 0x00)); /* TSNOPIT=0, TSCOLAP=0 */ + + return 0; +} + +int saa7134_ts_fini(struct saa7134_dev *dev) +{ + /* nothing */ + saa7134_pgtable_free(dev->pci,&dev->ts.pt_ts); + return 0; +} + + +void saa7134_irq_ts_done(struct saa7134_dev *dev, unsigned long status) +{ + enum v4l2_field field; + + spin_lock(&dev->slock); + if (dev->ts_q.curr) { + field = dev->ts_q.curr->vb.field; + if (field == V4L2_FIELD_TOP) { + if ((status & 0x100000) != 0x100000) + goto done; + } else { + if ((status & 0x100000) != 0x000000) + goto done; + } + saa7134_buffer_finish(dev,&dev->ts_q,STATE_DONE); + } + saa7134_buffer_next(dev,&dev->ts_q); + + done: + spin_unlock(&dev->slock); +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/saa7134/saa7134-tvaudio.c b/linux/drivers/media/video/saa7134/saa7134-tvaudio.c new file mode 100644 index 000000000..a6caeae21 --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa7134-tvaudio.c @@ -0,0 +1,973 @@ +/* + * device driver for philips saa7134 based TV cards + * tv audio decoder (fm stereo, nicam, ...) + * + * (c) 2001-03 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * 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/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/smp_lock.h> +#include <asm/div64.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* ------------------------------------------------------------------ */ + +static unsigned int audio_debug = 0; +MODULE_PARM(audio_debug,"i"); +MODULE_PARM_DESC(audio_debug,"enable debug messages [tv audio]"); + +static unsigned int audio_carrier = 0; +MODULE_PARM(audio_carrier,"i"); +MODULE_PARM_DESC(audio_carrier,"audio carrier location"); + +static unsigned int audio_ddep = 0; +MODULE_PARM(audio_ddep,"i"); +MODULE_PARM_DESC(audio_ddep,"audio ddep overwrite"); + +static int audio_clock_override = UNSET; +MODULE_PARM(audio_clock_override, "i"); + +static int audio_clock_tweak = 0; +MODULE_PARM(audio_clock_tweak, "i"); +MODULE_PARM_DESC(audio_clock_tweak, "Audio clock tick fine tuning for cards with audio crystal that's slightly off (range [-1024 .. 1024])"); + +#define dprintk(fmt, arg...) if (audio_debug) \ + printk(KERN_DEBUG "%s/audio: " fmt, dev->name , ## arg) +#define d2printk(fmt, arg...) if (audio_debug > 1) \ + printk(KERN_DEBUG "%s/audio: " fmt, dev->name, ## arg) + +#define print_regb(reg) printk("%s: reg 0x%03x [%-16s]: 0x%02x\n", \ + dev->name,(SAA7134_##reg),(#reg),saa_readb((SAA7134_##reg))) + +#define SCAN_INITIAL_DELAY (HZ) +#define SCAN_SAMPLE_DELAY (HZ/5) + +/* ------------------------------------------------------------------ */ +/* saa7134 code */ + +static struct saa7134_tvaudio tvaudio[] = { + { + .name = "PAL-B/G FM-stereo", + .std = V4L2_STD_PAL, + .mode = TVAUDIO_FM_BG_STEREO, + .carr1 = 5500, + .carr2 = 5742, + },{ + .name = "PAL-D/K1 FM-stereo", + .std = V4L2_STD_PAL, + .carr1 = 6500, + .carr2 = 6258, + .mode = TVAUDIO_FM_BG_STEREO, + },{ + .name = "PAL-D/K2 FM-stereo", + .std = V4L2_STD_PAL, + .carr1 = 6500, + .carr2 = 6742, + .mode = TVAUDIO_FM_BG_STEREO, + },{ + .name = "PAL-D/K3 FM-stereo", + .std = V4L2_STD_PAL, + .carr1 = 6500, + .carr2 = 5742, + .mode = TVAUDIO_FM_BG_STEREO, + },{ + .name = "PAL-B/G NICAM", + .std = V4L2_STD_PAL, + .carr1 = 5500, + .carr2 = 5850, + .mode = TVAUDIO_NICAM_FM, + },{ + .name = "PAL-I NICAM", + .std = V4L2_STD_PAL, + .carr1 = 6000, + .carr2 = 6552, + .mode = TVAUDIO_NICAM_FM, + },{ + .name = "PAL-D/K NICAM", + .std = V4L2_STD_PAL, + .carr1 = 6500, + .carr2 = 5850, + .mode = TVAUDIO_NICAM_FM, + },{ + .name = "SECAM-L NICAM", + .std = V4L2_STD_SECAM, + .carr1 = 6500, + .carr2 = 5850, + .mode = TVAUDIO_NICAM_AM, + },{ + .name = "SECAM-D/K", + .std = V4L2_STD_SECAM, + .carr1 = 6500, + .carr2 = -1, + .mode = TVAUDIO_FM_MONO, + },{ + .name = "NTSC-M", + .std = V4L2_STD_NTSC, + .carr1 = 4500, + .carr2 = -1, + .mode = TVAUDIO_FM_MONO, + },{ + .name = "NTSC-A2 FM-stereo", + .std = V4L2_STD_NTSC, + .carr1 = 4500, + .carr2 = 4724, + .mode = TVAUDIO_FM_K_STEREO, + } +}; +#define TVAUDIO (sizeof(tvaudio)/sizeof(struct saa7134_tvaudio)) + +/* ------------------------------------------------------------------ */ + +static void tvaudio_init(struct saa7134_dev *dev) +{ + int clock = saa7134_boards[dev->board].audio_clock; + + if (UNSET != audio_clock_override) + clock = audio_clock_override; + + /* init all audio registers */ + saa_writeb(SAA7134_AUDIO_PLL_CTRL, 0x00); + if (need_resched()) + schedule(); + else + udelay(10); + + saa_writeb(SAA7134_AUDIO_CLOCK0, clock & 0xff); + saa_writeb(SAA7134_AUDIO_CLOCK1, (clock >> 8) & 0xff); + saa_writeb(SAA7134_AUDIO_CLOCK2, (clock >> 16) & 0xff); + saa_writeb(SAA7134_AUDIO_PLL_CTRL, 0x01); + + saa_writeb(SAA7134_NICAM_ERROR_LOW, 0x14); + saa_writeb(SAA7134_NICAM_ERROR_HIGH, 0x50); + saa_writeb(SAA7134_MONITOR_SELECT, 0xa0); + saa_writeb(SAA7134_FM_DEMATRIX, 0x80); +} + +static u32 tvaudio_carr2reg(u32 carrier) +{ + u64 a = carrier; + + a <<= 24; + do_div(a,12288); + return a; +} + +static void tvaudio_setcarrier(struct saa7134_dev *dev, + int primary, int secondary) +{ + if (-1 == secondary) + secondary = primary; + saa_writel(SAA7134_CARRIER1_FREQ0 >> 2, tvaudio_carr2reg(primary)); + saa_writel(SAA7134_CARRIER2_FREQ0 >> 2, tvaudio_carr2reg(secondary)); +} + +static void mute_input_7134(struct saa7134_dev *dev) +{ + unsigned int mute; + struct saa7134_input *in; + int ausel=0, ics=0, ocs=0; + int mask; + + /* look what is to do ... */ + in = dev->input; + mute = (dev->ctl_mute || + (dev->automute && (&card(dev).radio) != in)); + if (PCI_DEVICE_ID_PHILIPS_SAA7130 == dev->pci->device && + card(dev).mute.name) { + /* 7130 - we'll mute using some unconnected audio input */ + if (mute) + in = &card(dev).mute; + } + if (dev->hw_mute == mute && + dev->hw_input == in) { + dprintk("mute/input: nothing to do [mute=%d,input=%s]\n", + mute,in->name); + return; + } + + dprintk("ctl_mute=%d automute=%d input=%s => mute=%d input=%s\n", + dev->ctl_mute,dev->automute,dev->input->name,mute,in->name); + dev->hw_mute = mute; + dev->hw_input = in; + + if (PCI_DEVICE_ID_PHILIPS_SAA7134 == dev->pci->device) + /* 7134 mute */ + saa_writeb(SAA7134_AUDIO_MUTE_CTRL, mute ? 0xff : 0xbb); + + /* switch internal audio mux */ + switch (in->amux) { + case TV: ausel=0xc0; ics=0x00; ocs=0x02; break; + case LINE1: ausel=0x80; ics=0x00; ocs=0x00; break; + case LINE2: ausel=0x80; ics=0x08; ocs=0x01; break; + } + saa_andorb(SAA7134_AUDIO_FORMAT_CTRL, 0xc0, ausel); + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x08, ics); + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x07, ocs); + + /* switch gpio-connected external audio mux */ + if (0 == card(dev).gpiomask) + return; + mask = card(dev).gpiomask; + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, mask, mask); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, mask, in->gpio); + saa7134_track_gpio(dev,in->name); +} + +static void tvaudio_setmode(struct saa7134_dev *dev, + struct saa7134_tvaudio *audio, + char *note) +{ + int acpf, tweak = 0; + + if (dev->tvnorm->id == V4L2_STD_NTSC) { + acpf = 0x19066; + } else { + acpf = 0x1e000; + } + if (audio_clock_tweak > -1024 && audio_clock_tweak < 1024) + tweak = audio_clock_tweak; + + if (note) + dprintk("tvaudio_setmode: %s %s [%d.%03d/%d.%03d MHz] acpf=%d%+d\n", + note,audio->name, + audio->carr1 / 1000, audio->carr1 % 1000, + audio->carr2 / 1000, audio->carr2 % 1000, + acpf, tweak); + + acpf += tweak; + saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD0, (acpf & 0x0000ff) >> 0); + saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD1, (acpf & 0x00ff00) >> 8); + saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD2, (acpf & 0x030000) >> 16); + tvaudio_setcarrier(dev,audio->carr1,audio->carr2); + + switch (audio->mode) { + case TVAUDIO_FM_MONO: + case TVAUDIO_FM_BG_STEREO: + saa_writeb(SAA7134_DEMODULATOR, 0x00); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x00); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x22); + saa_writeb(SAA7134_FM_DEMATRIX, 0x80); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa0); + break; + case TVAUDIO_FM_K_STEREO: + saa_writeb(SAA7134_DEMODULATOR, 0x00); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x01); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x22); + saa_writeb(SAA7134_FM_DEMATRIX, 0x80); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa0); + break; + case TVAUDIO_NICAM_FM: + saa_writeb(SAA7134_DEMODULATOR, 0x10); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x00); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x44); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa1); + saa_writeb(SAA7134_NICAM_CONFIG, 0x00); + break; + case TVAUDIO_NICAM_AM: + saa_writeb(SAA7134_DEMODULATOR, 0x12); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x00); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x44); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa1); + saa_writeb(SAA7134_NICAM_CONFIG, 0x00); + break; + case TVAUDIO_FM_SAT_STEREO: + /* not implemented (yet) */ + break; + } +} + +static int tvaudio_sleep(struct saa7134_dev *dev, int timeout) +{ + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&dev->thread.wq, &wait); + if (dev->thread.scan1 == dev->thread.scan2 && !dev->thread.shutdown) { + set_current_state(TASK_INTERRUPTIBLE); + if (timeout < 0) + schedule(); + else + schedule_timeout(timeout); + } + remove_wait_queue(&dev->thread.wq, &wait); + return dev->thread.scan1 != dev->thread.scan2; +} + +static int tvaudio_checkcarrier(struct saa7134_dev *dev, int carrier) +{ + __s32 left,right,value; + + if (audio_debug > 1) { + int i; + dprintk("debug %d:",carrier); + for (i = -150; i <= 150; i += 30) { + tvaudio_setcarrier(dev,carrier+i,carrier+i); + saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (tvaudio_sleep(dev,SCAN_SAMPLE_DELAY)) + return -1; + value = saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (0 == i) + printk(" # %6d # ",value >> 16); + else + printk(" %6d",value >> 16); + } + printk("\n"); + } + + tvaudio_setcarrier(dev,carrier-90,carrier-90); + saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (tvaudio_sleep(dev,SCAN_SAMPLE_DELAY)) + return -1; + left = saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + + tvaudio_setcarrier(dev,carrier+90,carrier+90); + saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (tvaudio_sleep(dev,SCAN_SAMPLE_DELAY)) + return -1; + right = saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + + left >>= 16; + right >>= 16; + value = left > right ? left - right : right - left; + dprintk("scanning %d.%03d MHz => dc is %5d [%d/%d]\n", + carrier/1000,carrier%1000,value,left,right); + return value; +} + +#if 0 +static void sifdebug_dump_regs(struct saa7134_dev *dev) +{ + print_regb(AUDIO_STATUS); + print_regb(IDENT_SIF); + print_regb(LEVEL_READOUT1); + print_regb(LEVEL_READOUT2); + print_regb(DCXO_IDENT_CTRL); + print_regb(DEMODULATOR); + print_regb(AGC_GAIN_SELECT); + print_regb(MONITOR_SELECT); + print_regb(FM_DEEMPHASIS); + print_regb(FM_DEMATRIX); + print_regb(SIF_SAMPLE_FREQ); + print_regb(ANALOG_IO_SELECT); +} +#endif + +static int tvaudio_getstereo(struct saa7134_dev *dev, struct saa7134_tvaudio *audio) +{ + __u32 idp,nicam; + int retval = -1; + + switch (audio->mode) { + case TVAUDIO_FM_MONO: + return V4L2_TUNER_SUB_MONO; + case TVAUDIO_FM_K_STEREO: + case TVAUDIO_FM_BG_STEREO: + idp = (saa_readb(SAA7134_IDENT_SIF) & 0xe0) >> 5; + if (0x03 == (idp & 0x03)) + retval = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + else if (0x05 == (idp & 0x05)) + retval = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + else if (0x01 == (idp & 0x01)) + retval = V4L2_TUNER_SUB_MONO; + break; + case TVAUDIO_FM_SAT_STEREO: + /* not implemented (yet) */ + break; + case TVAUDIO_NICAM_FM: + case TVAUDIO_NICAM_AM: + nicam = saa_readb(SAA7134_NICAM_STATUS); + switch (nicam & 0x0b) { + case 0x08: + retval = V4L2_TUNER_SUB_MONO; + break; + case 0x09: + retval = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + break; + case 0x0a: + retval = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + break; + } + break; + } + if (retval != -1) + dprintk("found audio subchannels:%s%s%s%s\n", + (retval & V4L2_TUNER_SUB_MONO) ? " mono" : "", + (retval & V4L2_TUNER_SUB_STEREO) ? " stereo" : "", + (retval & V4L2_TUNER_SUB_LANG1) ? " lang1" : "", + (retval & V4L2_TUNER_SUB_LANG2) ? " lang2" : ""); + return retval; +} + +static int tvaudio_setstereo(struct saa7134_dev *dev, struct saa7134_tvaudio *audio, + u32 mode) +{ + static char *name[] = { + [ V4L2_TUNER_MODE_MONO ] = "mono", + [ V4L2_TUNER_MODE_STEREO ] = "stereo", + [ V4L2_TUNER_MODE_LANG1 ] = "lang1", + [ V4L2_TUNER_MODE_LANG2 ] = "lang2", + }; + static u32 fm[] = { + [ V4L2_TUNER_MODE_MONO ] = 0x00, /* ch1 */ + [ V4L2_TUNER_MODE_STEREO ] = 0x80, /* auto */ + [ V4L2_TUNER_MODE_LANG1 ] = 0x00, /* ch1 */ + [ V4L2_TUNER_MODE_LANG2 ] = 0x01, /* ch2 */ + }; + u32 reg; + + switch (audio->mode) { + case TVAUDIO_FM_MONO: + /* nothing to do ... */ + break; + case TVAUDIO_FM_K_STEREO: + case TVAUDIO_FM_BG_STEREO: + dprintk("setstereo [fm] => %s\n", + name[ mode % ARRAY_SIZE(name) ]); + reg = fm[ mode % ARRAY_SIZE(fm) ]; + saa_writeb(SAA7134_FM_DEMATRIX, reg); + break; + case TVAUDIO_FM_SAT_STEREO: + case TVAUDIO_NICAM_AM: + case TVAUDIO_NICAM_FM: + /* FIXME */ + break; + } + return 0; +} + +static int tvaudio_thread(void *data) +{ +#define MAX_SCAN 4 + static const int carr_pal[MAX_SCAN] = { 5500, 6000, 6500 }; + static const int carr_ntsc[MAX_SCAN] = { 4500 }; + static const int carr_secam[MAX_SCAN] = { 6500 }; + static const int carr_default[MAX_SCAN] = { 4500, 5500, 6000, 6500 }; + struct saa7134_dev *dev = data; + const int *carr_scan; + int carr_vals[4]; + unsigned int i, audio; + int max1,max2,carrier,rx,mode,lastmode; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,61) + lock_kernel(); + daemonize(); + sigfillset(¤t->blocked); + sprintf(current->comm, "%s", dev->name); + unlock_kernel(); +#else + daemonize("%s", dev->name); + allow_signal(SIGTERM); +#endif + for (;;) { + tvaudio_sleep(dev,-1); + if (dev->thread.shutdown || signal_pending(current)) + goto done; + + restart: + dev->thread.scan1 = dev->thread.scan2; + dprintk("tvaudio thread scan start [%d]\n",dev->thread.scan1); + dev->tvaudio = NULL; + tvaudio_init(dev); + dev->automute = 1; + mute_input_7134(dev); + + /* give the tuner some time */ + if (tvaudio_sleep(dev,SCAN_INITIAL_DELAY)) + goto restart; + + /* find the main carrier */ + carr_scan = carr_default; + if (dev->tvnorm->id & V4L2_STD_PAL) + carr_scan = carr_pal; + if (dev->tvnorm->id & V4L2_STD_NTSC) + carr_scan = carr_ntsc; + if (dev->tvnorm->id & V4L2_STD_SECAM) + carr_scan = carr_secam; + saa_writeb(SAA7134_MONITOR_SELECT,0x00); + tvaudio_setmode(dev,&tvaudio[0],NULL); + for (i = 0; i < MAX_SCAN; i++) { + if (!carr_scan[i]) + continue; + carr_vals[i] = tvaudio_checkcarrier(dev,carr_scan[i]); + if (dev->thread.scan1 != dev->thread.scan2) + goto restart; + } + for (carrier = 0, max1 = 0, max2 = 0, i = 0; i < MAX_SCAN; i++) { + if (!carr_scan[i]) + continue; + if (max1 < carr_vals[i]) { + max2 = max1; + max1 = carr_vals[i]; + carrier = carr_scan[i]; + } else if (max2 < carr_vals[i]) { + max2 = carr_vals[i]; + } + } + + if (0 != carrier && max1 > 2000 && max1 > max2*3) { + /* found good carrier */ + dprintk("found %s main sound carrier @ %d.%03d MHz [%d/%d]\n", + dev->tvnorm->name, carrier/1000, carrier%1000, + max1, max2); + dev->last_carrier = carrier; + } else if (0 != audio_carrier) { + /* no carrier -- try insmod option as fallback */ + carrier = audio_carrier; + printk(KERN_WARNING "%s/audio: audio carrier scan failed, " + "using %d.%03d MHz [insmod option]\n", + dev->name, carrier/1000, carrier%1000); + } else if (0 != dev->last_carrier) { + /* no carrier -- try last detected one as fallback */ + carrier = dev->last_carrier; + printk(KERN_WARNING "%s/audio: audio carrier scan failed, " + "using %d.%03d MHz [last detected]\n", + dev->name, carrier/1000, carrier%1000); + } else { + /* no carrier + no fallback -- try first in list */ + carrier = carr_scan[0]; + printk(KERN_WARNING "%s/audio: audio carrier scan failed, " + "using %d.%03d MHz [default]\n", + dev->name, carrier/1000, carrier%1000); + } + tvaudio_setcarrier(dev,carrier,carrier); + dev->automute = 0; + saa_andorb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0x30, 0x00); + saa7134_tvaudio_setmute(dev); + + /* find the exact tv audio norm */ + for (audio = UNSET, i = 0; i < TVAUDIO; i++) { + if (dev->tvnorm->id != UNSET && + dev->tvnorm->id != tvaudio[i].std) + continue; + if (tvaudio[i].carr1 != carrier) + continue; + + if (UNSET == audio) + audio = i; + tvaudio_setmode(dev,&tvaudio[i],"trying"); + if (tvaudio_sleep(dev,HZ*2)) + goto restart; + if (-1 != tvaudio_getstereo(dev,&tvaudio[i])) { + audio = i; + break; + } + } + saa_andorb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0x30, 0x30); + if (UNSET == audio) + continue; + tvaudio_setmode(dev,&tvaudio[audio],"using"); + tvaudio_setstereo(dev,&tvaudio[audio],V4L2_TUNER_MODE_MONO); + dev->tvaudio = &tvaudio[audio]; + + lastmode = 42; + for (;;) { + if (tvaudio_sleep(dev,5*HZ)) + goto restart; + if (dev->thread.shutdown || signal_pending(current)) + break; + if (UNSET == dev->thread.mode) { + rx = tvaudio_getstereo(dev,&tvaudio[i]); + mode = saa7134_tvaudio_rx2mode(rx); + } else { + mode = dev->thread.mode; + } + if (lastmode != mode) { + tvaudio_setstereo(dev,&tvaudio[audio],mode); + lastmode = mode; + } + } + } + + done: + complete_and_exit(&dev->thread.exit, 0); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* saa7133 / saa7135 code */ + +static char *stdres[0x20] = { + [0x00] = "no standard detected", + [0x01] = "B/G (in progress)", + [0x02] = "D/K (in progress)", + [0x03] = "M (in progress)", + + [0x04] = "B/G A2", + [0x05] = "B/G NICAM", + [0x06] = "D/K A2 (1)", + [0x07] = "D/K A2 (2)", + [0x08] = "D/K A2 (3)", + [0x09] = "D/K NICAM", + [0x0a] = "L NICAM", + [0x0b] = "I NICAM", + + [0x0c] = "M Korea", + [0x0d] = "M BTSC ", + [0x0e] = "M EIAJ", + + [0x0f] = "FM radio / IF 10.7 / 50 deemp", + [0x10] = "FM radio / IF 10.7 / 75 deemp", + [0x11] = "FM radio / IF sel / 50 deemp", + [0x12] = "FM radio / IF sel / 75 deemp", + + [0x13 ... 0x1e ] = "unknown", + [0x1f] = "??? [in progress]", +}; + +#define DSP_RETRY 32 +#define DSP_DELAY 16 + +static inline int saa_dsp_wait_bit(struct saa7134_dev *dev, int bit) +{ + int state, count = DSP_RETRY; + + state = saa_readb(SAA7135_DSP_RWSTATE); + if (unlikely(state & SAA7135_DSP_RWSTATE_ERR)) { + printk("%s: dsp access error\n",dev->name); + /* FIXME: send ack ... */ + return -EIO; + } + while (0 == (state & bit)) { + if (unlikely(0 == count)) { + printk("%s: dsp access wait timeout [bit=%s]\n", + dev->name, + (bit & SAA7135_DSP_RWSTATE_WRR) ? "WRR" : + (bit & SAA7135_DSP_RWSTATE_RDB) ? "RDB" : + (bit & SAA7135_DSP_RWSTATE_IDA) ? "IDA" : + "???"); + return -EIO; + } + saa_wait(DSP_DELAY); + state = saa_readb(SAA7135_DSP_RWSTATE); + count--; + } + return 0; +} + +#if 0 +static int saa_dsp_readl(struct saa7134_dev *dev, int reg, u32 *value) +{ + int err; + + d2printk("dsp read reg 0x%x\n", reg<<2); + saa_readl(reg); + err = saa_dsp_wait_bit(dev,SAA7135_DSP_RWSTATE_RDB); + if (err < 0) + return err; + *value = saa_readl(reg); + d2printk("dsp read => 0x%06x\n", *value & 0xffffff); + err = saa_dsp_wait_bit(dev,SAA7135_DSP_RWSTATE_IDA); + if (err < 0) + return err; + return 0; +} +#endif + +int saa_dsp_writel(struct saa7134_dev *dev, int reg, u32 value) +{ + int err; + + d2printk("dsp write reg 0x%x = 0x%06x\n",reg<<2,value); + err = saa_dsp_wait_bit(dev,SAA7135_DSP_RWSTATE_WRR); + if (err < 0) + return err; + saa_writel(reg,value); + err = saa_dsp_wait_bit(dev,SAA7135_DSP_RWSTATE_WRR); + if (err < 0) + return err; + return 0; +} + +static int getstereo_7133(struct saa7134_dev *dev) +{ + int retval = V4L2_TUNER_SUB_MONO; + u32 value; + + value = saa_readl(0x528 >> 2); + if (value & 0x20) + retval = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + if (value & 0x40) + retval = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + return retval; +} + +static int mute_input_7133(struct saa7134_dev *dev) +{ + u32 reg = 0; + + switch (dev->input->amux) { + case TV: reg = 0x02; break; + case LINE1: reg = 0x00; break; + case LINE2: reg = 0x01; break; + } + if (dev->ctl_mute) + reg = 0x07; + saa_writel(0x594 >> 2, reg); + return 0; +} + +static int tvaudio_thread_ddep(void *data) +{ + struct saa7134_dev *dev = data; + u32 value, norms; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,61) + lock_kernel(); + daemonize(); + sigfillset(¤t->blocked); + sprintf(current->comm, "%s", dev->name); + unlock_kernel(); +#else + daemonize("%s", dev->name); + allow_signal(SIGTERM); +#endif + + /* unmute */ + saa_dsp_writel(dev, 0x474 >> 2, 0x00); + saa_dsp_writel(dev, 0x450 >> 2, 0x00); + + for (;;) { + tvaudio_sleep(dev,-1); + if (dev->thread.shutdown || signal_pending(current)) + goto done; + + restart: + dev->thread.scan1 = dev->thread.scan2; + dprintk("tvaudio thread scan start [%d]\n",dev->thread.scan1); + + if (audio_ddep >= 0x04 && audio_ddep <= 0x0e) { + /* insmod option override */ + norms = (audio_ddep << 2) | 0x01; + dprintk("ddep override: %s\n",stdres[audio_ddep]); + } else{ + /* (let chip) scan for sound carrier */ + norms = 0; + if (dev->tvnorm->id & V4L2_STD_PAL) { + dprintk("PAL scan\n"); + norms |= 0x2c; /* B/G + D/K + I */ + } + if (dev->tvnorm->id & V4L2_STD_NTSC) { + dprintk("NTSC scan\n"); + norms |= 0x40; /* M */ + } + if (dev->tvnorm->id & V4L2_STD_SECAM) { + dprintk("SECAM scan\n"); + norms |= 0x18; /* L + D/K */ + } + if (0 == norms) + norms = 0x7c; /* all */ + dprintk("scanning:%s%s%s%s%s\n", + (norms & 0x04) ? " B/G" : "", + (norms & 0x08) ? " D/K" : "", + (norms & 0x10) ? " L/L'" : "", + (norms & 0x20) ? " I" : "", + (norms & 0x40) ? " M" : ""); + } + + /* quick & dirty -- to be fixed up later ... */ + saa_dsp_writel(dev, 0x454 >> 2, 0); + saa_dsp_writel(dev, 0x454 >> 2, norms | 0x80); + saa_dsp_writel(dev, 0x464 >> 2, 0x000000); + saa_dsp_writel(dev, 0x470 >> 2, 0x101010); + + if (tvaudio_sleep(dev,3*HZ)) + goto restart; + value = saa_readl(0x528 >> 2) & 0xffffff; + + dprintk("tvaudio thread status: 0x%x [%s%s%s]\n", + value, stdres[value & 0x1f], + (value & 0x000020) ? ",stereo" : "", + (value & 0x000040) ? ",dual" : ""); + dprintk("detailed status: " + "%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s\n", + (value & 0x000080) ? " A2/EIAJ pilot tone " : "", + (value & 0x000100) ? " A2/EIAJ dual " : "", + (value & 0x000200) ? " A2/EIAJ stereo " : "", + (value & 0x000400) ? " A2/EIAJ noise mute " : "", + + (value & 0x000800) ? " BTSC/FM radio pilot " : "", + (value & 0x001000) ? " SAP carrier " : "", + (value & 0x002000) ? " BTSC stereo noise mute " : "", + (value & 0x004000) ? " SAP noise mute " : "", + (value & 0x008000) ? " VDSP " : "", + + (value & 0x010000) ? " NICST " : "", + (value & 0x020000) ? " NICDU " : "", + (value & 0x040000) ? " NICAM muted " : "", + (value & 0x080000) ? " NICAM reserve sound " : "", + + (value & 0x100000) ? " init done " : ""); + } + + done: + complete_and_exit(&dev->thread.exit, 0); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* common stuff + external entry points */ + +int saa7134_tvaudio_rx2mode(u32 rx) +{ + u32 mode; + + mode = V4L2_TUNER_MODE_MONO; + if (rx & V4L2_TUNER_SUB_STEREO) + mode = V4L2_TUNER_MODE_STEREO; + else if (rx & V4L2_TUNER_SUB_LANG1) + mode = V4L2_TUNER_MODE_LANG1; + else if (rx & V4L2_TUNER_SUB_LANG2) + mode = V4L2_TUNER_MODE_LANG2; + return mode; +} + +void saa7134_tvaudio_setmute(struct saa7134_dev *dev) +{ + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7130: + case PCI_DEVICE_ID_PHILIPS_SAA7134: + mute_input_7134(dev); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + mute_input_7133(dev); + break; + } +} + +void saa7134_tvaudio_setinput(struct saa7134_dev *dev, + struct saa7134_input *in) +{ + dev->input = in; + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7130: + case PCI_DEVICE_ID_PHILIPS_SAA7134: + mute_input_7134(dev); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + mute_input_7133(dev); + break; + } +} + +void saa7134_tvaudio_setvolume(struct saa7134_dev *dev, int level) +{ + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + saa_writeb(SAA7134_CHANNEL1_LEVEL, level & 0x1f); + saa_writeb(SAA7134_CHANNEL2_LEVEL, level & 0x1f); + saa_writeb(SAA7134_NICAM_LEVEL_ADJUST, level & 0x1f); + break; + } +} + +int saa7134_tvaudio_getstereo(struct saa7134_dev *dev) +{ + int retval = V4L2_TUNER_SUB_MONO; + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + if (dev->tvaudio) + retval = tvaudio_getstereo(dev,dev->tvaudio); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + retval = getstereo_7133(dev); + break; + } + return retval; +} + +int saa7134_tvaudio_init2(struct saa7134_dev *dev) +{ + DECLARE_MUTEX_LOCKED(sem); + int (*my_thread)(void *data) = NULL; + + /* enable I2S audio output */ + if (saa7134_boards[dev->board].i2s_rate) { + int i2sform = (32000 == saa7134_boards[dev->board].i2s_rate) ? 0x00 : 0x01; + + /* enable I2S output */ + saa_writeb(SAA7134_I2S_OUTPUT_SELECT, 0x80); + saa_writeb(SAA7134_I2S_OUTPUT_FORMAT, i2sform); + saa_writeb(SAA7134_I2S_OUTPUT_LEVEL, 0x0F); + saa_writeb(SAA7134_I2S_AUDIO_OUTPUT, 0x01); + } + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + my_thread = tvaudio_thread; + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + my_thread = tvaudio_thread_ddep; + break; + } + + dev->thread.pid = -1; + if (my_thread) { + /* start tvaudio thread */ + init_waitqueue_head(&dev->thread.wq); + init_completion(&dev->thread.exit); + dev->thread.pid = kernel_thread(my_thread,dev,0); + if (dev->thread.pid < 0) + printk(KERN_WARNING "%s: kernel_thread() failed\n", + dev->name); + wake_up_interruptible(&dev->thread.wq); + } + + return 0; +} + +int saa7134_tvaudio_fini(struct saa7134_dev *dev) +{ + /* shutdown tvaudio thread */ + if (dev->thread.pid >= 0) { + dev->thread.shutdown = 1; + wake_up_interruptible(&dev->thread.wq); + wait_for_completion(&dev->thread.exit); + } + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x07, 0x00); /* LINE1 */ + return 0; +} + +int saa7134_tvaudio_do_scan(struct saa7134_dev *dev) +{ + if (dev->thread.pid >= 0) { + dev->thread.mode = UNSET; + dev->thread.scan2++; + wake_up_interruptible(&dev->thread.wq); + } else { + dev->automute = 0; + saa7134_tvaudio_setmute(dev); + } + return 0; +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/saa7134/saa7134-vbi.c b/linux/drivers/media/video/saa7134/saa7134-vbi.c new file mode 100644 index 000000000..01451b37a --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa7134-vbi.c @@ -0,0 +1,266 @@ +/* + * device driver for philips saa7134 based TV cards + * video4linux video interface + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * 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/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* ------------------------------------------------------------------ */ + +static unsigned int vbi_debug = 0; +MODULE_PARM(vbi_debug,"i"); +MODULE_PARM_DESC(vbi_debug,"enable debug messages [vbi]"); + +static unsigned int vbibufs = 4; +MODULE_PARM(vbibufs,"i"); +MODULE_PARM_DESC(vbibufs,"number of vbi buffers, range 2-32"); + +#define dprintk(fmt, arg...) if (vbi_debug) \ + printk(KERN_DEBUG "%s/vbi: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ + +#define VBI_LINE_COUNT 16 +#define VBI_LINE_LENGTH 2048 +#define VBI_SCALE 0x200 + +static void task_init(struct saa7134_dev *dev, struct saa7134_buf *buf, + int task) +{ + struct saa7134_tvnorm *norm = dev->tvnorm; + + /* setup video scaler */ + saa_writeb(SAA7134_VBI_H_START1(task), norm->h_start & 0xff); + saa_writeb(SAA7134_VBI_H_START2(task), norm->h_start >> 8); + saa_writeb(SAA7134_VBI_H_STOP1(task), norm->h_stop & 0xff); + saa_writeb(SAA7134_VBI_H_STOP2(task), norm->h_stop >> 8); + saa_writeb(SAA7134_VBI_V_START1(task), norm->vbi_v_start & 0xff); + saa_writeb(SAA7134_VBI_V_START2(task), norm->vbi_v_start >> 8); + saa_writeb(SAA7134_VBI_V_STOP1(task), norm->vbi_v_stop & 0xff); + saa_writeb(SAA7134_VBI_V_STOP2(task), norm->vbi_v_stop >> 8); + + saa_writeb(SAA7134_VBI_H_SCALE_INC1(task), VBI_SCALE & 0xff); + saa_writeb(SAA7134_VBI_H_SCALE_INC2(task), VBI_SCALE >> 8); + saa_writeb(SAA7134_VBI_PHASE_OFFSET_LUMA(task), 0x00); + saa_writeb(SAA7134_VBI_PHASE_OFFSET_CHROMA(task), 0x00); + + saa_writeb(SAA7134_VBI_H_LEN1(task), buf->vb.width & 0xff); + saa_writeb(SAA7134_VBI_H_LEN2(task), buf->vb.width >> 8); + saa_writeb(SAA7134_VBI_V_LEN1(task), buf->vb.height & 0xff); + saa_writeb(SAA7134_VBI_V_LEN2(task), buf->vb.height >> 8); + + saa_andorb(SAA7134_DATA_PATH(task), 0xc0, 0x00); +} + +/* ------------------------------------------------------------------ */ + +static int buffer_activate(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next) +{ + unsigned long control,base; + + dprintk("buffer_activate [%p]\n",buf); + buf->vb.state = STATE_ACTIVE; + buf->top_seen = 0; + + task_init(dev,buf,TASK_A); + task_init(dev,buf,TASK_B); + saa_writeb(SAA7134_OFMT_DATA_A, 0x06); + saa_writeb(SAA7134_OFMT_DATA_B, 0x06); + + /* DMA: setup channel 2+3 (= VBI Task A+B) */ + base = saa7134_buffer_base(buf); + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (buf->pt->dma >> 12); + saa_writel(SAA7134_RS_BA1(2),base); + saa_writel(SAA7134_RS_BA2(2),base + buf->vb.size/2); + saa_writel(SAA7134_RS_PITCH(2),buf->vb.width); + saa_writel(SAA7134_RS_CONTROL(2),control); + saa_writel(SAA7134_RS_BA1(3),base); + saa_writel(SAA7134_RS_BA2(3),base + buf->vb.size/2); + saa_writel(SAA7134_RS_PITCH(3),buf->vb.width); + saa_writel(SAA7134_RS_CONTROL(3),control); + + /* start DMA */ + saa7134_set_dmabits(dev); + mod_timer(&dev->vbi_q.timeout, jiffies+BUFFER_TIMEOUT); + + return 0; +} + +static int buffer_prepare(struct file *file, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + struct saa7134_buf *buf = (struct saa7134_buf *)vb; + struct saa7134_tvnorm *norm = dev->tvnorm; + unsigned int lines, llength, size; + int err; + + lines = norm->vbi_v_stop - norm->vbi_v_start +1; + if (lines > VBI_LINE_COUNT) + lines = VBI_LINE_COUNT; +#if 1 + llength = VBI_LINE_LENGTH; +#else + llength = (norm->h_stop - norm->h_start +1) * 2; + if (llength > VBI_LINE_LENGTH) + llength = VBI_LINE_LENGTH; +#endif + size = lines * llength * 2; + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + if (buf->vb.size != size) + saa7134_dma_free(dev,buf); + + if (STATE_NEEDS_INIT == buf->vb.state) { + buf->vb.width = llength; + buf->vb.height = lines; + buf->vb.size = size; + buf->pt = &fh->pt_vbi; + + err = videobuf_iolock(dev->pci,&buf->vb,NULL); + if (err) + goto oops; + err = saa7134_pgtable_build(dev->pci,buf->pt, + buf->vb.dma.sglist, + buf->vb.dma.sglen, + saa7134_buffer_startpage(buf)); + if (err) + goto oops; + } + buf->vb.state = STATE_PREPARED; + buf->activate = buffer_activate; + buf->vb.field = field; + return 0; + + oops: + saa7134_dma_free(dev,buf); + return err; +} + +static int +buffer_setup(struct file *file, unsigned int *count, unsigned int *size) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + int llength,lines; + + lines = dev->tvnorm->vbi_v_stop - dev->tvnorm->vbi_v_start +1; +#if 1 + llength = VBI_LINE_LENGTH; +#else + llength = (norm->h_stop - norm->h_start +1) * 2; + if (llength > VBI_LINE_LENGTH) + llength = VBI_LINE_LENGTH; +#endif + *size = lines * llength * 2; + if (0 == *count) + *count = vbibufs; + *count = saa7134_buffer_count(*size,*count); + return 0; +} + +static void buffer_queue(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + struct saa7134_buf *buf = (struct saa7134_buf *)vb; + + saa7134_buffer_queue(dev,&dev->vbi_q,buf); +} + +static void buffer_release(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + struct saa7134_buf *buf = (struct saa7134_buf *)vb; + + saa7134_dma_free(dev,buf); +} + +struct videobuf_queue_ops saa7134_vbi_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +int saa7134_vbi_init1(struct saa7134_dev *dev) +{ + INIT_LIST_HEAD(&dev->vbi_q.queue); + init_timer(&dev->vbi_q.timeout); + dev->vbi_q.timeout.function = saa7134_buffer_timeout; + dev->vbi_q.timeout.data = (unsigned long)(&dev->vbi_q); + dev->vbi_q.dev = dev; + + if (vbibufs < 2) + vbibufs = 2; + if (vbibufs > VIDEO_MAX_FRAME) + vbibufs = VIDEO_MAX_FRAME; + return 0; +} + +int saa7134_vbi_fini(struct saa7134_dev *dev) +{ + /* nothing */ + return 0; +} + +void saa7134_irq_vbi_done(struct saa7134_dev *dev, unsigned long status) +{ + spin_lock(&dev->slock); + if (dev->vbi_q.curr) { + dev->vbi_fieldcount++; + /* make sure we have seen both fields */ + if ((status & 0x10) == 0x00) { + dev->vbi_q.curr->top_seen = 1; + goto done; + } + if (!dev->vbi_q.curr->top_seen) + goto done; + + dev->vbi_q.curr->vb.field_count = dev->vbi_fieldcount; + saa7134_buffer_finish(dev,&dev->vbi_q,STATE_DONE); + } + saa7134_buffer_next(dev,&dev->vbi_q); + + done: + spin_unlock(&dev->slock); +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/saa7134/saa7134-video.c b/linux/drivers/media/video/saa7134/saa7134-video.c new file mode 100644 index 000000000..170956f92 --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa7134-video.c @@ -0,0 +1,2243 @@ +/* + * device driver for philips saa7134 based TV cards + * video4linux video interface + * + * (c) 2001-03 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * 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/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* ------------------------------------------------------------------ */ + +static unsigned int video_debug = 0; +static unsigned int gbuffers = 8; +static unsigned int noninterlaced = 0; +static unsigned int gbufsize = 768*576*4; +static unsigned int gbufsize_max = 768*576*4; +MODULE_PARM(video_debug,"i"); +MODULE_PARM_DESC(video_debug,"enable debug messages [video]"); +MODULE_PARM(gbuffers,"i"); +MODULE_PARM_DESC(gbuffers,"number of capture buffers, range 2-32"); +MODULE_PARM(noninterlaced,"i"); +MODULE_PARM_DESC(noninterlaced,"video input is noninterlaced"); + +#define dprintk(fmt, arg...) if (video_debug) \ + printk(KERN_DEBUG "%s/video: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ +/* data structs for video */ + +static int video_out[][9] = { + [CCIR656] = { 0x00, 0xb1, 0x00, 0xa1, 0x00, 0x04, 0x06, 0x00, 0x00 }, +}; + +static struct saa7134_format formats[] = { + { + .name = "8 bpp gray", + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .pm = 0x06, + },{ + .name = "15 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .pm = 0x13 | 0x80, + },{ + .name = "15 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB555X, + .depth = 16, + .pm = 0x13 | 0x80, + .bswap = 1, + },{ + .name = "16 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .pm = 0x10 | 0x80, + },{ + .name = "16 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .pm = 0x10 | 0x80, + .bswap = 1, + },{ + .name = "24 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .pm = 0x11, + },{ + .name = "24 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + .pm = 0x11, + .bswap = 1, + },{ + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + .pm = 0x12, + },{ + .name = "32 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .pm = 0x12, + .bswap = 1, + .wswap = 1, + },{ + .name = "4:2:2 packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .pm = 0x00, + .bswap = 1, + .yuv = 1, + },{ + .name = "4:2:2 packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .pm = 0x00, + .yuv = 1, + },{ + .name = "4:2:2 planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 16, + .pm = 0x09, + .yuv = 1, + .planar = 1, + .hshift = 1, + .vshift = 0, + },{ + .name = "4:2:0 planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .pm = 0x0a, + .yuv = 1, + .planar = 1, + .hshift = 1, + .vshift = 1, + },{ + .name = "4:2:0 planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YVU420, + .depth = 12, + .pm = 0x0a, + .yuv = 1, + .planar = 1, + .uvswap = 1, + .hshift = 1, + .vshift = 1, + } +}; +#define FORMATS ARRAY_SIZE(formats) + +static struct saa7134_tvnorm tvnorms[] = { + { + .name = "PAL", + .id = V4L2_STD_PAL, + .width = 720, + .height = 576, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + .h_start = 0, + .h_stop = 719, + .video_v_start = 24, + .video_v_stop = 311, + .vbi_v_start = 7, + .vbi_v_stop = 22, + .src_timing = 4, + },{ + .name = "NTSC", + .id = V4L2_STD_NTSC, + .width = 720, + .height = 480, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0x89, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + + .h_start = 0, + .h_stop = 719, + .video_v_start = 22, + .video_v_stop = 22+240, + .vbi_v_start = 10, /* FIXME */ + .vbi_v_stop = 21, /* FIXME */ + .src_timing = 1, + },{ + .name = "SECAM", + .id = V4L2_STD_SECAM, + .width = 720, + .height = 576, + + .sync_control = 0x18, /* old: 0x58, */ + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + + .h_start = 0, + .h_stop = 719, + .video_v_start = 24, + .video_v_stop = 311, + .vbi_v_start = 7, + .vbi_v_stop = 22, + .src_timing = 4, + },{ + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + .width = 720, + .height = 480, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0xb9, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + + .h_start = 0, + .h_stop = 719, + .video_v_start = 22, + .video_v_stop = 22+240, + .vbi_v_start = 10, /* FIXME */ + .vbi_v_stop = 21, /* FIXME */ + .src_timing = 1, + },{ + .name = "PAL-Nc", + .id = V4L2_STD_PAL_Nc, + .width = 720, + .height = 576, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0xa1, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + .h_start = 0, + .h_stop = 719, + .video_v_start = 24, + .video_v_stop = 311, + .vbi_v_start = 7, + .vbi_v_stop = 22, + .src_timing = 4, +#if 0 + },{ + .name = "AUTO", + .id = V4L2_STD_PAL|V4L2_STD_NTSC|V4L2_STD_SECAM, + .width = 768, + .height = 576, + + .sync_control = 0x98, + .luma_control = 0x40, + .chroma_ctrl1 = 0x8b, + .chroma_gain = 0x00, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x18, + + .h_start = 0, + .h_stop = 719, + .video_v_start = 24, + .video_v_stop = 311, + .vbi_v_start = 7, + .vbi_v_stop = 22, + .src_timing = 4, +#endif + } +}; +#define TVNORMS ARRAY_SIZE(tvnorms) + +#define V4L2_CID_PRIVATE_INVERT (V4L2_CID_PRIVATE_BASE + 0) +#define V4L2_CID_PRIVATE_Y_ODD (V4L2_CID_PRIVATE_BASE + 1) +#define V4L2_CID_PRIVATE_Y_EVEN (V4L2_CID_PRIVATE_BASE + 2) +#define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 3) + +static const struct v4l2_queryctrl no_ctrl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; +static const struct v4l2_queryctrl video_ctrls[] = { + /* --- video --- */ + { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 128, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 68, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 64, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = -128, + .maximum = 127, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_VFLIP, + .name = "vertical flip", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, + /* --- audio --- */ + { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_AUDIO_VOLUME, + .name = "Volume", + .minimum = -15, + .maximum = 15, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + /* --- private --- */ + { + .id = V4L2_CID_PRIVATE_INVERT, + .name = "Invert", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_Y_ODD, + .name = "y offset odd field", + .minimum = 0, + .maximum = 128, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_PRIVATE_Y_EVEN, + .name = "y offset even field", + .minimum = 0, + .maximum = 128, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + } +}; +static const unsigned int CTRLS = ARRAY_SIZE(video_ctrls); + +static const struct v4l2_queryctrl* ctrl_by_id(unsigned int id) +{ + unsigned int i; + + for (i = 0; i < CTRLS; i++) + if (video_ctrls[i].id == id) + return video_ctrls+i; + return NULL; +} + +static struct saa7134_format* format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < FORMATS; i++) + if (formats[i].fourcc == fourcc) + return formats+i; + return NULL; +} + +/* ----------------------------------------------------------------------- */ +/* resource management */ + +static int res_get(struct saa7134_dev *dev, struct saa7134_fh *fh, unsigned int bit) +{ + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + down(&dev->lock); + if (dev->resources & bit) { + /* no, someone else uses it */ + up(&dev->lock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + dev->resources |= bit; + dprintk("res: get %d\n",bit); + up(&dev->lock); + return 1; +} + +static +int res_check(struct saa7134_fh *fh, unsigned int bit) +{ + return (fh->resources & bit); +} + +static +int res_locked(struct saa7134_dev *dev, unsigned int bit) +{ + return (dev->resources & bit); +} + +static +void res_free(struct saa7134_dev *dev, struct saa7134_fh *fh, unsigned int bits) +{ + if ((fh->resources & bits) != bits) + BUG(); + + down(&dev->lock); + fh->resources &= ~bits; + dev->resources &= ~bits; + dprintk("res: put %d\n",bits); + up(&dev->lock); +} + +/* ------------------------------------------------------------------ */ + +static void set_tvnorm(struct saa7134_dev *dev, struct saa7134_tvnorm *norm) +{ + struct video_channel c; + int luma_control,sync_control,mux; + + dprintk("set tv norm = %s\n",norm->name); + dev->tvnorm = norm; + + mux = card_in(dev,dev->ctl_input).vmux; + luma_control = norm->luma_control; + sync_control = norm->sync_control; + + if (mux > 5) + luma_control |= 0x80; /* svideo */ + if (noninterlaced) + sync_control |= 0x20; + + /* setup video decoder */ + saa_writeb(SAA7134_INCR_DELAY, 0x08); + saa_writeb(SAA7134_ANALOG_IN_CTRL1, 0xc0 | mux); + saa_writeb(SAA7134_ANALOG_IN_CTRL2, 0x00); + + saa_writeb(SAA7134_ANALOG_IN_CTRL3, 0x90); + saa_writeb(SAA7134_ANALOG_IN_CTRL4, 0x90); + saa_writeb(SAA7134_HSYNC_START, 0xeb); + saa_writeb(SAA7134_HSYNC_STOP, 0xe0); + saa_writeb(SAA7134_SOURCE_TIMING1, norm->src_timing); + + saa_writeb(SAA7134_SYNC_CTRL, sync_control); + saa_writeb(SAA7134_LUMA_CTRL, luma_control); + saa_writeb(SAA7134_DEC_LUMA_BRIGHT, dev->ctl_bright); + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, dev->ctl_contrast); + + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, dev->ctl_saturation); + saa_writeb(SAA7134_DEC_CHROMA_HUE, dev->ctl_hue); + saa_writeb(SAA7134_CHROMA_CTRL1, norm->chroma_ctrl1); + saa_writeb(SAA7134_CHROMA_GAIN, norm->chroma_gain); + + saa_writeb(SAA7134_CHROMA_CTRL2, norm->chroma_ctrl2); + saa_writeb(SAA7134_MODE_DELAY_CTRL, 0x00); + + saa_writeb(SAA7134_ANALOG_ADC, 0x01); + saa_writeb(SAA7134_VGATE_START, 0x11); + saa_writeb(SAA7134_VGATE_STOP, 0xfe); + saa_writeb(SAA7134_MISC_VGATE_MSB, norm->vgate_misc); + saa_writeb(SAA7134_RAW_DATA_GAIN, 0x40); + saa_writeb(SAA7134_RAW_DATA_OFFSET, 0x80); + + /* pass down info to the i2c chips (v4l1) */ + memset(&c,0,sizeof(c)); + c.channel = dev->ctl_input; + c.norm = VIDEO_MODE_PAL; + if (norm->id & V4L2_STD_NTSC) + c.norm = VIDEO_MODE_NTSC; + if (norm->id & V4L2_STD_SECAM) + c.norm = VIDEO_MODE_SECAM; + saa7134_i2c_call_clients(dev,VIDIOCSCHAN,&c); +} + +static void video_mux(struct saa7134_dev *dev, int input) +{ + dprintk("video input = %d [%s]\n",input,card_in(dev,input).name); + dev->ctl_input = input; + set_tvnorm(dev,dev->tvnorm); + saa7134_tvaudio_setinput(dev,&card_in(dev,input)); +} + +static void set_h_prescale(struct saa7134_dev *dev, int task, int prescale) +{ + static const struct { + int xpsc; + int xacl; + int xc2_1; + int xdcg; + int vpfy; + } vals[] = { + /* XPSC XACL XC2_1 XDCG VPFY */ + { 1, 0, 0, 0, 0 }, + { 2, 2, 1, 2, 2 }, + { 3, 4, 1, 3, 2 }, + { 4, 8, 1, 4, 2 }, + { 5, 8, 1, 4, 2 }, + { 6, 8, 1, 4, 3 }, + { 7, 8, 1, 4, 3 }, + { 8, 15, 0, 4, 3 }, + { 9, 15, 0, 4, 3 }, + { 10, 16, 1, 5, 3 }, + }; + static const int count = ARRAY_SIZE(vals); + int i; + + for (i = 0; i < count; i++) + if (vals[i].xpsc == prescale) + break; + if (i == count) + return; + + saa_writeb(SAA7134_H_PRESCALE(task), vals[i].xpsc); + saa_writeb(SAA7134_ACC_LENGTH(task), vals[i].xacl); + saa_writeb(SAA7134_LEVEL_CTRL(task), + (vals[i].xc2_1 << 3) | (vals[i].xdcg)); + saa_andorb(SAA7134_FIR_PREFILTER_CTRL(task), 0x0f, + (vals[i].vpfy << 2) | vals[i].vpfy); +} + +static void set_v_scale(struct saa7134_dev *dev, int task, int yscale) +{ + int val,mirror; + + saa_writeb(SAA7134_V_SCALE_RATIO1(task), yscale & 0xff); + saa_writeb(SAA7134_V_SCALE_RATIO2(task), yscale >> 8); + + mirror = (dev->ctl_mirror) ? 0x02 : 0x00; + if (yscale < 2048) { + /* LPI */ + dprintk("yscale LPI yscale=%d\n",yscale); + saa_writeb(SAA7134_V_FILTER(task), 0x00 | mirror); + saa_writeb(SAA7134_LUMA_CONTRAST(task), 0x40); + saa_writeb(SAA7134_CHROMA_SATURATION(task), 0x40); + } else { + /* ACM */ + val = 0x40 * 1024 / yscale; + dprintk("yscale ACM yscale=%d val=0x%x\n",yscale,val); + saa_writeb(SAA7134_V_FILTER(task), 0x01 | mirror); + saa_writeb(SAA7134_LUMA_CONTRAST(task), val); + saa_writeb(SAA7134_CHROMA_SATURATION(task), val); + } + saa_writeb(SAA7134_LUMA_BRIGHT(task), 0x80); +} + +static void set_size(struct saa7134_dev *dev, int task, + int width, int height, int interlace) +{ + struct saa7134_tvnorm *norm = dev->tvnorm; + int prescale,xscale,yscale,y_even,y_odd; + int div = interlace ? 2 : 1; + + /* setup video scaler */ + saa_writeb(SAA7134_VIDEO_H_START1(task), norm->h_start & 0xff); + saa_writeb(SAA7134_VIDEO_H_START2(task), norm->h_start >> 8); + saa_writeb(SAA7134_VIDEO_H_STOP1(task), norm->h_stop & 0xff); + saa_writeb(SAA7134_VIDEO_H_STOP2(task), norm->h_stop >> 8); + saa_writeb(SAA7134_VIDEO_V_START1(task), norm->video_v_start & 0xff); + saa_writeb(SAA7134_VIDEO_V_START2(task), norm->video_v_start >> 8); + saa_writeb(SAA7134_VIDEO_V_STOP1(task), norm->video_v_stop & 0xff); + saa_writeb(SAA7134_VIDEO_V_STOP2(task), norm->video_v_stop >> 8); + + prescale = norm->width / width; + if (0 == prescale) + prescale = 1; + xscale = 1024 * norm->width / prescale / width; + yscale = 512 * div * norm->height / height; + dprintk("prescale=%d xscale=%d yscale=%d\n",prescale,xscale,yscale); + set_h_prescale(dev,task,prescale); + saa_writeb(SAA7134_H_SCALE_INC1(task), xscale & 0xff); + saa_writeb(SAA7134_H_SCALE_INC2(task), xscale >> 8); + set_v_scale(dev,task,yscale); + + saa_writeb(SAA7134_VIDEO_PIXELS1(task), width & 0xff); + saa_writeb(SAA7134_VIDEO_PIXELS2(task), width >> 8); + saa_writeb(SAA7134_VIDEO_LINES1(task), height/div & 0xff); + saa_writeb(SAA7134_VIDEO_LINES2(task), height/div >> 8); + + /* deinterlace y offsets */ + y_odd = dev->ctl_y_odd; + y_even = dev->ctl_y_even; + saa_writeb(SAA7134_V_PHASE_OFFSET0(task), y_odd); + saa_writeb(SAA7134_V_PHASE_OFFSET1(task), y_even); + saa_writeb(SAA7134_V_PHASE_OFFSET2(task), y_odd); + saa_writeb(SAA7134_V_PHASE_OFFSET3(task), y_even); +} + +/* ------------------------------------------------------------------ */ + +struct cliplist { + __u16 position; + __u8 enable; + __u8 disable; +}; + +static void sort_cliplist(struct cliplist *cl, int entries) +{ + struct cliplist swap; + int i,j,n; + + for (i = entries-2; i >= 0; i--) { + for (n = 0, j = 0; j <= i; j++) { + if (cl[j].position > cl[j+1].position) { + swap = cl[j]; + cl[j] = cl[j+1]; + cl[j+1] = swap; + n++; + } + } + if (0 == n) + break; + } +} + +static void set_cliplist(struct saa7134_dev *dev, int reg, + struct cliplist *cl, int entries, char *name) +{ + __u8 winbits = 0; + int i; + + for (i = 0; i < entries; i++) { + winbits |= cl[i].enable; + winbits &= ~cl[i].disable; + if (i < 15 && cl[i].position == cl[i+1].position) + continue; + saa_writeb(reg + 0, winbits); + saa_writeb(reg + 2, cl[i].position & 0xff); + saa_writeb(reg + 3, cl[i].position >> 8); + dprintk("clip: %s winbits=%02x pos=%d\n", + name,winbits,cl[i].position); + reg += 8; + } + for (; reg < 0x400; reg += 8) { + saa_writeb(reg+ 0, 0); + saa_writeb(reg + 1, 0); + saa_writeb(reg + 2, 0); + saa_writeb(reg + 3, 0); + } +} + +static int clip_range(int val) +{ + if (val < 0) + val = 0; + return val; +} + +static int setup_clipping(struct saa7134_dev *dev, struct v4l2_clip *clips, + int nclips, int interlace) +{ + struct cliplist col[16], row[16]; + int cols, rows, i; + int div = interlace ? 2 : 1; + + memset(col,0,sizeof(col)); cols = 0; + memset(row,0,sizeof(row)); rows = 0; + for (i = 0; i < nclips && i < 8; i++) { + col[cols].position = clip_range(clips[i].c.left); + col[cols].enable = (1 << i); + cols++; + col[cols].position = clip_range(clips[i].c.left+clips[i].c.width); + col[cols].disable = (1 << i); + cols++; + row[rows].position = clip_range(clips[i].c.top / div); + row[rows].enable = (1 << i); + rows++; + row[rows].position = clip_range((clips[i].c.top + clips[i].c.height) + / div); + row[rows].disable = (1 << i); + rows++; + } + sort_cliplist(col,cols); + sort_cliplist(row,rows); + set_cliplist(dev,0x380,col,cols,"cols"); + set_cliplist(dev,0x384,row,rows,"rows"); + return 0; +} + +static int verify_preview(struct saa7134_dev *dev, struct v4l2_window *win) +{ + enum v4l2_field field; + int maxw, maxh; + + if (NULL == dev->ovbuf.base) + return -EINVAL; + if (NULL == dev->ovfmt) + return -EINVAL; + if (win->w.width < 48 || win->w.height < 32) + return -EINVAL; + if (win->clipcount > 2048) + return -EINVAL; + + field = win->field; + maxw = dev->tvnorm->width; + maxh = dev->tvnorm->height; + + if (V4L2_FIELD_ANY == field) { + field = (win->w.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + win->field = field; + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + return 0; +} + +static int start_preview(struct saa7134_dev *dev, struct saa7134_fh *fh) +{ + unsigned long base,control,bpl; + int err; + + err = verify_preview(dev,&fh->win); + if (0 != err) + return err; + + dev->ovfield = fh->win.field; + dprintk("start_preview %dx%d+%d+%d %s field=%s\n", + fh->win.w.width,fh->win.w.height, + fh->win.w.left,fh->win.w.top, + dev->ovfmt->name,v4l2_field_names[dev->ovfield]); + + /* setup window + clipping */ + set_size(dev,TASK_B,fh->win.w.width,fh->win.w.height, + V4L2_FIELD_HAS_BOTH(dev->ovfield)); + setup_clipping(dev,fh->clips,fh->nclips, + V4L2_FIELD_HAS_BOTH(dev->ovfield)); + if (dev->ovfmt->yuv) + saa_andorb(SAA7134_DATA_PATH(TASK_B), 0x3f, 0x03); + else + saa_andorb(SAA7134_DATA_PATH(TASK_B), 0x3f, 0x01); + saa_writeb(SAA7134_OFMT_VIDEO_B, dev->ovfmt->pm | 0x20); + + /* dma: setup channel 1 (= Video Task B) */ + base = (unsigned long)dev->ovbuf.base; + base += dev->ovbuf.fmt.bytesperline * fh->win.w.top; + base += dev->ovfmt->depth/8 * fh->win.w.left; + bpl = dev->ovbuf.fmt.bytesperline; + control = SAA7134_RS_CONTROL_BURST_16; + if (dev->ovfmt->bswap) + control |= SAA7134_RS_CONTROL_BSWAP; + if (dev->ovfmt->wswap) + control |= SAA7134_RS_CONTROL_WSWAP; + if (V4L2_FIELD_HAS_BOTH(dev->ovfield)) { + saa_writel(SAA7134_RS_BA1(1),base); + saa_writel(SAA7134_RS_BA2(1),base+bpl); + saa_writel(SAA7134_RS_PITCH(1),bpl*2); + saa_writel(SAA7134_RS_CONTROL(1),control); + } else { + saa_writel(SAA7134_RS_BA1(1),base); + saa_writel(SAA7134_RS_BA2(1),base); + saa_writel(SAA7134_RS_PITCH(1),bpl); + saa_writel(SAA7134_RS_CONTROL(1),control); + } + + /* start dma */ + dev->ovenable = 1; + saa7134_set_dmabits(dev); + + return 0; +} + +static int stop_preview(struct saa7134_dev *dev, struct saa7134_fh *fh) +{ + dev->ovenable = 0; + saa7134_set_dmabits(dev); + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int buffer_activate(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next) +{ + unsigned long base,control,bpl; + unsigned long bpl_uv,lines_uv,base2,base3,tmp; /* planar */ + + dprintk("buffer_activate buf=%p\n",buf); + buf->vb.state = STATE_ACTIVE; + buf->top_seen = 0; + + set_size(dev,TASK_A,buf->vb.width,buf->vb.height, + V4L2_FIELD_HAS_BOTH(buf->vb.field)); + if (buf->fmt->yuv) + saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x03); + else + saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x01); + saa_writeb(SAA7134_OFMT_VIDEO_A, buf->fmt->pm); + + /* DMA: setup channel 0 (= Video Task A0) */ + base = saa7134_buffer_base(buf); + if (buf->fmt->planar) + bpl = buf->vb.width; + else + bpl = (buf->vb.width * buf->fmt->depth) / 8; + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (buf->pt->dma >> 12); + if (buf->fmt->bswap) + control |= SAA7134_RS_CONTROL_BSWAP; + if (buf->fmt->wswap) + control |= SAA7134_RS_CONTROL_WSWAP; + if (V4L2_FIELD_HAS_BOTH(buf->vb.field)) { + /* interlaced */ + saa_writel(SAA7134_RS_BA1(0),base); + saa_writel(SAA7134_RS_BA2(0),base+bpl); + saa_writel(SAA7134_RS_PITCH(0),bpl*2); + } else { + /* non-interlaced */ + saa_writel(SAA7134_RS_BA1(0),base); + saa_writel(SAA7134_RS_BA2(0),base); + saa_writel(SAA7134_RS_PITCH(0),bpl); + } + saa_writel(SAA7134_RS_CONTROL(0),control); + + if (buf->fmt->planar) { + /* DMA: setup channel 4+5 (= planar task A) */ + bpl_uv = bpl >> buf->fmt->hshift; + lines_uv = buf->vb.height >> buf->fmt->vshift; + base2 = base + bpl * buf->vb.height; + base3 = base2 + bpl_uv * lines_uv; + if (buf->fmt->uvswap) + tmp = base2, base2 = base3, base3 = tmp; + dprintk("uv: bpl=%ld lines=%ld base2/3=%ld/%ld\n", + bpl_uv,lines_uv,base2,base3); + if (V4L2_FIELD_HAS_BOTH(buf->vb.field)) { + /* interlaced */ + saa_writel(SAA7134_RS_BA1(4),base2); + saa_writel(SAA7134_RS_BA2(4),base2+bpl_uv); + saa_writel(SAA7134_RS_PITCH(4),bpl_uv*2); + saa_writel(SAA7134_RS_BA1(5),base3); + saa_writel(SAA7134_RS_BA2(5),base3+bpl_uv); + saa_writel(SAA7134_RS_PITCH(5),bpl_uv*2); + } else { + /* non-interlaced */ + saa_writel(SAA7134_RS_BA1(4),base2); + saa_writel(SAA7134_RS_BA2(4),base2); + saa_writel(SAA7134_RS_PITCH(4),bpl_uv); + saa_writel(SAA7134_RS_BA1(5),base3); + saa_writel(SAA7134_RS_BA2(5),base3); + saa_writel(SAA7134_RS_PITCH(5),bpl_uv); + } + saa_writel(SAA7134_RS_CONTROL(4),control); + saa_writel(SAA7134_RS_CONTROL(5),control); + } + + /* start DMA */ + saa7134_set_dmabits(dev); + mod_timer(&dev->video_q.timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +static int buffer_prepare(struct file *file, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + struct saa7134_buf *buf = (struct saa7134_buf *)vb; + unsigned int size; + int err; + + /* sanity checks */ + if (NULL == fh->fmt) + return -EINVAL; + if (fh->width < 48 || + fh->height < 32 || + fh->width > dev->tvnorm->width || + fh->height > dev->tvnorm->height) + return -EINVAL; + size = (fh->width * fh->height * fh->fmt->depth) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + dprintk("buffer_prepare [%d,size=%dx%d,bytes=%d,fields=%s,%s]\n", + vb->i,fh->width,fh->height,size,v4l2_field_names[field], + fh->fmt->name); + if (buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.size != size || + buf->vb.field != field || + buf->fmt != fh->fmt) { + saa7134_dma_free(dev,buf); + } + + if (STATE_NEEDS_INIT == buf->vb.state) { + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.size = size; + buf->vb.field = field; + buf->fmt = fh->fmt; + buf->pt = &fh->pt_cap; + + err = videobuf_iolock(dev->pci,&buf->vb,&dev->ovbuf); + if (err) + goto oops; + err = saa7134_pgtable_build(dev->pci,buf->pt, + buf->vb.dma.sglist, + buf->vb.dma.sglen, + saa7134_buffer_startpage(buf)); + if (err) + goto oops; + } + buf->vb.state = STATE_PREPARED; + buf->activate = buffer_activate; + return 0; + + oops: + saa7134_dma_free(dev,buf); + return err; +} + +static int +buffer_setup(struct file *file, unsigned int *count, unsigned int *size) +{ + struct saa7134_fh *fh = file->private_data; + + *size = fh->fmt->depth * fh->width * fh->height >> 3; + if (0 == *count) + *count = gbuffers; + *count = saa7134_buffer_count(*size,*count); + return 0; +} + +static void buffer_queue(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_buf *buf = (struct saa7134_buf *)vb; + + saa7134_buffer_queue(fh->dev,&fh->dev->video_q,buf); +} + +static void buffer_release(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_buf *buf = (struct saa7134_buf *)vb; + + saa7134_dma_free(fh->dev,buf); +} + +static struct videobuf_queue_ops video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +static int get_control(struct saa7134_dev *dev, struct v4l2_control *c) +{ + const struct v4l2_queryctrl* ctrl; + + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) + return -EINVAL; + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->value = dev->ctl_bright; + break; + case V4L2_CID_HUE: + c->value = dev->ctl_hue; + break; + case V4L2_CID_CONTRAST: + c->value = dev->ctl_contrast; + break; + case V4L2_CID_SATURATION: + c->value = dev->ctl_saturation; + break; + case V4L2_CID_AUDIO_MUTE: + c->value = dev->ctl_mute; + break; + case V4L2_CID_AUDIO_VOLUME: + c->value = dev->ctl_volume; + break; + case V4L2_CID_PRIVATE_INVERT: + c->value = dev->ctl_invert; + break; + case V4L2_CID_VFLIP: + c->value = dev->ctl_mirror; + break; + case V4L2_CID_PRIVATE_Y_EVEN: + c->value = dev->ctl_y_even; + break; + case V4L2_CID_PRIVATE_Y_ODD: + c->value = dev->ctl_y_odd; + break; + default: + return -EINVAL; + } + return 0; +} + +static int set_control(struct saa7134_dev *dev, struct saa7134_fh *fh, + struct v4l2_control *c) +{ + const struct v4l2_queryctrl* ctrl; + unsigned long flags; + int restart_overlay = 0; + + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) + return -EINVAL; + dprintk("set_control name=%s val=%d\n",ctrl->name,c->value); + switch (ctrl->type) { + case V4L2_CTRL_TYPE_BOOLEAN: + case V4L2_CTRL_TYPE_MENU: + case V4L2_CTRL_TYPE_INTEGER: + if (c->value < ctrl->minimum) + c->value = ctrl->minimum; + if (c->value > ctrl->maximum) + c->value = ctrl->maximum; + break; + default: + /* nothing */; + }; + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + dev->ctl_bright = c->value; + saa_writeb(SAA7134_DEC_LUMA_BRIGHT, dev->ctl_bright); + break; + case V4L2_CID_HUE: + dev->ctl_hue = c->value; + saa_writeb(SAA7134_DEC_CHROMA_HUE, dev->ctl_hue); + break; + case V4L2_CID_CONTRAST: + dev->ctl_contrast = c->value; + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, + dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); + break; + case V4L2_CID_SATURATION: + dev->ctl_saturation = c->value; + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, + dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); + break; + case V4L2_CID_AUDIO_MUTE: + dev->ctl_mute = c->value; + saa7134_tvaudio_setmute(dev); + break; + case V4L2_CID_AUDIO_VOLUME: + dev->ctl_volume = c->value; + saa7134_tvaudio_setvolume(dev,dev->ctl_volume); + break; + case V4L2_CID_PRIVATE_INVERT: + dev->ctl_invert = c->value; + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, + dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, + dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); + break; + case V4L2_CID_VFLIP: + dev->ctl_mirror = c->value; + restart_overlay = 1; + break; + case V4L2_CID_PRIVATE_Y_EVEN: + dev->ctl_y_even = c->value; + restart_overlay = 1; + break; + case V4L2_CID_PRIVATE_Y_ODD: + dev->ctl_y_odd = c->value; + restart_overlay = 1; + break; + default: + return -EINVAL; + } + if (restart_overlay && fh && res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + start_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + } + return 0; +} + +/* ------------------------------------------------------------------ */ + +static struct videobuf_queue* saa7134_queue(struct saa7134_fh *fh) +{ + struct videobuf_queue* q = NULL; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + q = &fh->cap; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + q = &fh->vbi; + break; + default: + BUG(); + } + return q; +} + +static int saa7134_resource(struct saa7134_fh *fh) +{ + int res = 0; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + res = RESOURCE_VIDEO; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + res = RESOURCE_VBI; + break; + default: + BUG(); + } + return res; +} + +static int video_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct saa7134_dev *h,*dev = NULL; + struct saa7134_fh *fh; + struct list_head *list; + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + int radio = 0; + + list_for_each(list,&saa7134_devlist) { + h = list_entry(list, struct saa7134_dev, devlist); + if (h->video_dev && (h->video_dev->minor == minor)) + dev = h; + if (h->radio_dev && (h->radio_dev->minor == minor)) { + radio = 1; + dev = h; + } + if (h->vbi_dev && (h->vbi_dev->minor == minor)) { + type = V4L2_BUF_TYPE_VBI_CAPTURE; + dev = h; + } + } + if (NULL == dev) + return -ENODEV; + + dprintk("open minor=%d radio=%d type=%s\n",minor,radio, + v4l2_type_names[type]); + + /* allocate + initialize per filehandle data */ + fh = kmalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + memset(fh,0,sizeof(*fh)); + file->private_data = fh; + fh->dev = dev; + fh->radio = radio; + fh->type = type; + fh->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + fh->width = 768; + fh->height = 576; +#ifdef VIDIOC_G_PRIORITY + v4l2_prio_open(&dev->prio,&fh->prio); +#endif + + videobuf_queue_init(&fh->cap, &video_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct saa7134_buf)); + init_MUTEX(&fh->cap.lock); + saa7134_pgtable_alloc(dev->pci,&fh->pt_cap); + + videobuf_queue_init(&fh->vbi, &saa7134_vbi_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct saa7134_buf)); + init_MUTEX(&fh->vbi.lock); + saa7134_pgtable_alloc(dev->pci,&fh->pt_vbi); + + if (fh->radio) { + /* switch to radio mode */ + saa7134_tvaudio_setinput(dev,&card(dev).radio); + saa7134_i2c_call_clients(dev,AUDC_SET_RADIO,NULL); + } else { + /* switch to video/vbi mode */ + video_mux(dev,dev->ctl_input); + } + return 0; +} + +static ssize_t +video_read(struct file *file, char *data, size_t count, loff_t *ppos) +{ + struct saa7134_fh *fh = file->private_data; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (res_locked(fh->dev,RESOURCE_VIDEO)) + return -EBUSY; + return videobuf_read_one(file, saa7134_queue(fh), + data, count, ppos); + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (!res_get(fh->dev,fh,RESOURCE_VBI)) + return -EBUSY; + return videobuf_read_stream(file, saa7134_queue(fh), + data, count, ppos, 1); + break; + default: + BUG(); + return 0; + } +} + +static unsigned int +video_poll(struct file *file, struct poll_table_struct *wait) +{ + struct saa7134_fh *fh = file->private_data; + struct videobuf_buffer *buf = NULL; + + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) + return videobuf_poll_stream(file, &fh->vbi, wait); + + if (res_check(fh,RESOURCE_VIDEO)) { + if (!list_empty(&fh->cap.stream)) + buf = list_entry(fh->cap.stream.next, struct videobuf_buffer, stream); + } else { + down(&fh->cap.lock); + if (UNSET == fh->cap.read_off) { + /* need to capture a new frame */ + if (res_locked(fh->dev,RESOURCE_VIDEO)) { + up(&fh->cap.lock); + return POLLERR; + } + if (0 != fh->cap.ops->buf_prepare(file,fh->cap.read_buf,fh->cap.field)) { + up(&fh->cap.lock); + return POLLERR; + } + fh->cap.ops->buf_queue(file,fh->cap.read_buf); + fh->cap.read_off = 0; + } + up(&fh->cap.lock); + buf = fh->cap.read_buf; + } + + if (!buf) + return POLLERR; + + poll_wait(file, &buf->done, wait); + if (buf->state == STATE_DONE || + buf->state == STATE_ERROR) + return POLLIN|POLLRDNORM; + return 0; +} + +static int video_release(struct inode *inode, struct file *file) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + unsigned long flags; + + /* turn off overlay */ + if (res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + res_free(dev,fh,RESOURCE_OVERLAY); + } + + /* stop video capture */ + if (res_check(fh, RESOURCE_VIDEO)) { + videobuf_streamoff(file,&fh->cap); + res_free(dev,fh,RESOURCE_VIDEO); + } + if (fh->cap.read_buf) { + buffer_release(file,fh->cap.read_buf); + kfree(fh->cap.read_buf); + } + + /* stop vbi capture */ + if (res_check(fh, RESOURCE_VBI)) { + if (fh->vbi.streaming) + videobuf_streamoff(file,&fh->vbi); + if (fh->vbi.reading) + videobuf_read_stop(file,&fh->vbi); + res_free(dev,fh,RESOURCE_VBI); + } + + saa7134_pgtable_free(dev->pci,&fh->pt_cap); + saa7134_pgtable_free(dev->pci,&fh->pt_vbi); + +#ifdef VIDIOC_G_PRIORITY + v4l2_prio_close(&dev->prio,&fh->prio); +#endif + file->private_data = NULL; + kfree(fh); + return 0; +} + +static int +video_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct saa7134_fh *fh = file->private_data; + + return videobuf_mmap_mapper(vma,saa7134_queue(fh)); +} + +/* ------------------------------------------------------------------ */ + +void saa7134_vbi_fmt(struct saa7134_dev *dev, struct v4l2_format *f) +{ + struct saa7134_tvnorm *norm = dev->tvnorm; + + f->fmt.vbi.sampling_rate = 6750000 * 4; + f->fmt.vbi.samples_per_line = 2048 /* VBI_LINE_LENGTH */; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 64 * 4; + f->fmt.vbi.start[0] = norm->vbi_v_start; + f->fmt.vbi.count[0] = norm->vbi_v_stop - norm->vbi_v_start +1; + f->fmt.vbi.start[1] = norm->video_v_stop + norm->vbi_v_start +1; + f->fmt.vbi.count[1] = f->fmt.vbi.count[0]; + f->fmt.vbi.flags = 0; /* VBI_UNSYNC VBI_INTERLACED */ + +#if 0 + if (V4L2_STD_PAL == norm->id) { + /* FIXME */ + f->fmt.vbi.start[0] += 3; + f->fmt.vbi.start[1] += 3*2; + } +#endif +} + +int saa7134_g_fmt(struct saa7134_dev *dev, struct saa7134_fh *fh, + struct v4l2_format *f) +{ + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memset(&f->fmt.pix,0,sizeof(f->fmt.pix)); + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->cap.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + f->fmt.win = fh->win; + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + saa7134_vbi_fmt(dev,f); + return 0; + default: + return -EINVAL; + } +} + +int saa7134_try_fmt(struct saa7134_dev *dev, struct saa7134_fh *fh, + struct v4l2_format *f) +{ + int err; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + { + struct saa7134_format *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = dev->tvnorm->width; + maxh = dev->tvnorm->height; + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + f->fmt.pix.field = field; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + err = verify_preview(dev,&f->fmt.win); + if (0 != err) + return err; + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + saa7134_vbi_fmt(dev,f); + return 0; + default: + return -EINVAL; + } +} + +int saa7134_s_fmt(struct saa7134_dev *dev, struct saa7134_fh *fh, + struct v4l2_format *f) +{ + unsigned long flags; + int err; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + err = saa7134_try_fmt(dev,fh,f); + if (0 != err) + return err; + + fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->cap.field = f->fmt.pix.field; + return 0; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + err = verify_preview(dev,&f->fmt.win); + if (0 != err) + return err; + + down(&dev->lock); + fh->win = f->fmt.win; + fh->nclips = f->fmt.win.clipcount; + if (fh->nclips > 8) + fh->nclips = 8; + if (copy_from_user(fh->clips,f->fmt.win.clips, + sizeof(struct v4l2_clip)*fh->nclips)) { + up(&dev->lock); + return -EFAULT; + } + + if (res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + start_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + } + up(&dev->lock); + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + saa7134_vbi_fmt(dev,f); + return 0; + default: + return -EINVAL; + } +} + +int saa7134_common_ioctl(struct saa7134_dev *dev, + unsigned int cmd, void *arg) +{ + int err; + + switch (cmd) { + case VIDIOC_QUERYCTRL: + { + const struct v4l2_queryctrl *ctrl; + struct v4l2_queryctrl *c = arg; + + if ((c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) && + (c->id < V4L2_CID_PRIVATE_BASE || + c->id >= V4L2_CID_PRIVATE_LASTP1)) + return -EINVAL; + ctrl = ctrl_by_id(c->id); + *c = (NULL != ctrl) ? *ctrl : no_ctrl; + return 0; + } + case VIDIOC_G_CTRL: + return get_control(dev,arg); + case VIDIOC_S_CTRL: + { + down(&dev->lock); + err = set_control(dev,NULL,arg); + up(&dev->lock); + return err; + } + /* --- input switching --------------------------------------- */ + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + unsigned int n; + + n = i->index; + if (n >= SAA7134_INPUT_MAX) + return -EINVAL; + if (NULL == card_in(dev,i->index).name) + return -EINVAL; + memset(i,0,sizeof(*i)); + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name,card_in(dev,n).name); + if (card_in(dev,n).tv) + i->type = V4L2_INPUT_TYPE_TUNER; + i->audioset = 1; + if (n == dev->ctl_input) { + int v1 = saa_readb(SAA7134_STATUS_VIDEO1); + int v2 = saa_readb(SAA7134_STATUS_VIDEO2); + + if (0 != (v1 & 0x40)) + i->status |= V4L2_IN_ST_NO_H_LOCK; + if (0 != (v2 & 0x40)) + i->status |= V4L2_IN_ST_NO_SYNC; + if (0 != (v2 & 0x0e)) + i->status |= V4L2_IN_ST_MACROVISION; + } + for (n = 0; n < TVNORMS; n++) + i->std |= tvnorms[n].id; + return 0; + } + case VIDIOC_G_INPUT: + { + int *i = arg; + *i = dev->ctl_input; + return 0; + } + case VIDIOC_S_INPUT: + { + int *i = arg; + + if (*i < 0 || *i >= SAA7134_INPUT_MAX) + return -EINVAL; + if (NULL == card_in(dev,*i).name) + return -EINVAL; + down(&dev->lock); + video_mux(dev,*i); + up(&dev->lock); + return 0; + } + + } + return 0; +} + +/* + * This function is _not_ called directly, but from + * video_generic_ioctl (and maybe others). userspace + * copying is done already, arg is a kernel pointer. + */ +static int video_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + unsigned long flags; + int err; + + if (video_debug > 1) + saa7134_print_ioctl(dev->name,cmd); + +#ifdef VIDIOC_G_PRIORITY + switch (cmd) { + case VIDIOC_S_CTRL: + case VIDIOC_S_STD: + case VIDIOC_S_INPUT: + case VIDIOC_S_TUNER: + case VIDIOC_S_FREQUENCY: + err = v4l2_prio_check(&dev->prio,&fh->prio); + if (0 != err) + return err; + } +#endif + + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->driver, "saa7134"); + strlcpy(cap->card, saa7134_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s",pci_name(dev->pci)); + cap->version = SAA7134_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_TUNER | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + return 0; + } + + /* --- tv standards ------------------------------------------ */ + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *e = arg; + unsigned int i; + + i = e->index; + if (i >= TVNORMS) + return -EINVAL; + err = v4l2_video_std_construct(e, tvnorms[e->index].id, + tvnorms[e->index].name); + e->index = i; + if (err < 0) + return err; + return 0; + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + + *id = dev->tvnorm->id; + return 0; + } + case VIDIOC_S_STD: + { + v4l2_std_id *id = arg; + unsigned int i; + + for(i = 0; i < TVNORMS; i++) + if (*id & tvnorms[i].id) + break; + if (i == TVNORMS) + return -EINVAL; + + down(&dev->lock); + if (res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + set_tvnorm(dev,&tvnorms[i]); + start_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + } else + set_tvnorm(dev,&tvnorms[i]); + up(&dev->lock); + return 0; + } + + /* --- tuner ioctls ------------------------------------------ */ + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + int n; + + if (0 != t->index) + return -EINVAL; + memset(t,0,sizeof(*t)); + for (n = 0; n < SAA7134_INPUT_MAX; n++) + if (card_in(dev,n).tv) + break; + if (NULL != card_in(dev,n).name) { + strcpy(t->name, "Television"); + t->capability = V4L2_TUNER_CAP_NORM | + V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2; + t->rangehigh = 0xffffffffUL; + t->rxsubchans = saa7134_tvaudio_getstereo(dev); + t->audmode = saa7134_tvaudio_rx2mode(t->rxsubchans); + } + if (0 != (saa_readb(SAA7134_STATUS_VIDEO1) & 0x03)) + t->signal = 0xffff; + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *t = arg; + int rx,mode; + + mode = dev->thread.mode; + if (UNSET == mode) { + rx = saa7134_tvaudio_getstereo(dev); + mode = saa7134_tvaudio_rx2mode(t->rxsubchans); + } + if (mode != t->audmode) { + dev->thread.mode = t->audmode; + } + return 0; + } + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + memset(f,0,sizeof(*f)); + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = dev->ctl_freq; + return 0; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if (0 != f->tuner) + return -EINVAL; + if (V4L2_TUNER_ANALOG_TV != f->type) + return -EINVAL; + down(&dev->lock); + dev->ctl_freq = f->frequency; + saa7134_i2c_call_clients(dev,VIDIOCSFREQ,&dev->ctl_freq); + saa7134_tvaudio_do_scan(dev); + up(&dev->lock); + return 0; + } + + /* --- control ioctls ---------------------------------------- */ + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_CTRL: + case VIDIOC_S_CTRL: + return saa7134_common_ioctl(dev, cmd, arg); + + case VIDIOC_G_AUDIO: + { + struct v4l2_audio *a = arg; + + memset(a,0,sizeof(*a)); + strcpy(a->name,"audio"); + return 0; + } + case VIDIOC_S_AUDIO: + return 0; + case VIDIOC_G_PARM: + { + struct v4l2_captureparm *parm = arg; + memset(parm,0,sizeof(*parm)); + return 0; + } + +#ifdef VIDIOC_G_PRIORITY + case VIDIOC_G_PRIORITY: + { + enum v4l2_priority *p = arg; + + *p = v4l2_prio_max(&dev->prio); + return 0; + } + case VIDIOC_S_PRIORITY: + { + enum v4l2_priority *prio = arg; + + return v4l2_prio_change(&dev->prio, &fh->prio, *prio); + } +#endif + + /* --- preview ioctls ---------------------------------------- */ + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *f = arg; + enum v4l2_buf_type type; + unsigned int index; + + index = f->index; + type = f->type; + switch (type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + if (index >= FORMATS) + return -EINVAL; + if (f->type == V4L2_BUF_TYPE_VIDEO_OVERLAY && + formats[index].planar) + return -EINVAL; + memset(f,0,sizeof(*f)); + f->index = index; + f->type = type; + strlcpy(f->description,formats[index].name,sizeof(f->description)); + f->pixelformat = formats[index].fourcc; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (0 != index) + return -EINVAL; + memset(f,0,sizeof(*f)); + f->index = index; + f->type = type; + f->pixelformat = V4L2_PIX_FMT_GREY; + strcpy(f->description,"vbi data"); + break; + default: + return -EINVAL; + } + return 0; + } + case VIDIOC_G_FBUF: + { + struct v4l2_framebuffer *fb = arg; + + *fb = dev->ovbuf; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + return 0; + } + case VIDIOC_S_FBUF: + { + struct v4l2_framebuffer *fb = arg; + struct saa7134_format *fmt; + + if(!capable(CAP_SYS_ADMIN) && + !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* check args */ + fmt = format_by_fourcc(fb->fmt.pixelformat); + if (NULL == fmt) + return -EINVAL; + + /* ok, accept it */ + dev->ovbuf = *fb; + dev->ovfmt = fmt; + if (0 == dev->ovbuf.fmt.bytesperline) + dev->ovbuf.fmt.bytesperline = + dev->ovbuf.fmt.width*fmt->depth/8; + return 0; + } + case VIDIOC_OVERLAY: + { + int *on = arg; + + if (*on) { + if (!res_get(dev,fh,RESOURCE_OVERLAY)) + return -EBUSY; + spin_lock_irqsave(&dev->slock,flags); + start_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + } + if (!*on) { + if (!res_check(fh, RESOURCE_OVERLAY)) + return -EINVAL; + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + res_free(dev,fh,RESOURCE_OVERLAY); + } + return 0; + } + + /* --- capture ioctls ---------------------------------------- */ + case VIDIOC_G_FMT: + { + struct v4l2_format *f = arg; + return saa7134_g_fmt(dev,fh,f); + } + case VIDIOC_S_FMT: + { + struct v4l2_format *f = arg; + return saa7134_s_fmt(dev,fh,f); + } + case VIDIOC_TRY_FMT: + { + struct v4l2_format *f = arg; + return saa7134_try_fmt(dev,fh,f); + } + + case VIDIOCGMBUF: + { + struct video_mbuf *mbuf = arg; + struct videobuf_queue *q; + struct v4l2_requestbuffers req; + unsigned int i; + + q = saa7134_queue(fh); + memset(&req,0,sizeof(req)); + req.type = q->type; + req.count = gbuffers; + req.memory = V4L2_MEMORY_MMAP; + err = videobuf_reqbufs(file,q,&req); + if (err < 0) + return err; + memset(mbuf,0,sizeof(*mbuf)); + mbuf->frames = req.count; + mbuf->size = 0; + for (i = 0; i < mbuf->frames; i++) { + mbuf->offsets[i] = q->bufs[i]->boff; + mbuf->size += q->bufs[i]->bsize; + } + return 0; + } + case VIDIOC_REQBUFS: + return videobuf_reqbufs(file,saa7134_queue(fh),arg); + + case VIDIOC_QUERYBUF: + return videobuf_querybuf(saa7134_queue(fh),arg); + + case VIDIOC_QBUF: + return videobuf_qbuf(file,saa7134_queue(fh),arg); + + case VIDIOC_DQBUF: + return videobuf_dqbuf(file,saa7134_queue(fh),arg); + + case VIDIOC_STREAMON: + { + int res = saa7134_resource(fh); + + if (!res_get(dev,fh,res)) + return -EBUSY; + return videobuf_streamon(file,saa7134_queue(fh)); + } + case VIDIOC_STREAMOFF: + { + int res = saa7134_resource(fh); + + err = videobuf_streamoff(file,saa7134_queue(fh)); + if (err < 0) + return err; + res_free(dev,fh,res); + return 0; + } + + default: + return v4l_compat_translate_ioctl(inode,file,cmd,arg, + video_do_ioctl); + } + return 0; +} + +static int video_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, video_do_ioctl); +} + +static int radio_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + + if (video_debug > 1) + saa7134_print_ioctl(dev->name,cmd); + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->driver, "saa7134"); + strlcpy(cap->card, saa7134_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s",pci_name(dev->pci)); + cap->version = SAA7134_VERSION_CODE; + cap->capabilities = V4L2_CAP_TUNER; + return 0; + } + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + struct video_tuner vt; + + if (0 != t->index) + return -EINVAL; + + memset(t,0,sizeof(*t)); + strcpy(t->name, "Radio"); + t->rangelow = (int)(65*16); + t->rangehigh = (int)(108*16); + + memset(&vt,0,sizeof(vt)); + saa7134_i2c_call_clients(dev,VIDIOCGTUNER,&vt); + t->signal = vt.signal; + return 0; + } + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + + if (i->index != 0) + return -EINVAL; + strcpy(i->name,"Radio"); + i->type = V4L2_INPUT_TYPE_TUNER; + return 0; + } + case VIDIOC_G_INPUT: + { + int *i = arg; + *i = 0; + return 0; + } + case VIDIOC_G_AUDIO: + { + struct v4l2_audio *a = arg; + + memset(a,0,sizeof(*a)); + strcpy(a->name,"Radio"); + return 0; + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + *id = 0; + return 0; + } + case VIDIOC_S_AUDIO: + case VIDIOC_S_TUNER: + case VIDIOC_S_INPUT: + case VIDIOC_S_STD: + return 0; + + case VIDIOC_QUERYCTRL: + { + const struct v4l2_queryctrl *ctrl; + struct v4l2_queryctrl *c = arg; + + if (c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) + return -EINVAL; + if (c->id == V4L2_CID_AUDIO_MUTE) { + ctrl = ctrl_by_id(c->id); + *c = *ctrl; + } else + *c = no_ctrl; + return 0; + } + + case VIDIOC_G_CTRL: + case VIDIOC_S_CTRL: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + return video_do_ioctl(inode,file,cmd,arg); + + default: + return v4l_compat_translate_ioctl(inode,file,cmd,arg, + radio_do_ioctl); + } + return 0; +} + +static int radio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, radio_do_ioctl); +} + +static struct file_operations video_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = video_mmap, + .ioctl = video_ioctl, + .llseek = no_llseek, +}; + +static struct file_operations radio_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .ioctl = radio_ioctl, + .llseek = no_llseek, +}; + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +struct video_device saa7134_video_template = +{ + .name = "saa7134-video", + .type = VID_TYPE_CAPTURE|VID_TYPE_TUNER|VID_TYPE_OVERLAY| + VID_TYPE_CLIPPING|VID_TYPE_SCALES, + .hardware = 0, + .fops = &video_fops, + .minor = -1, +}; + +struct video_device saa7134_vbi_template = +{ + .name = "saa7134-vbi", + .type = VID_TYPE_TUNER|VID_TYPE_TELETEXT, + .hardware = 0, + .fops = &video_fops, + .minor = -1, +}; + +struct video_device saa7134_radio_template = +{ + .name = "saa7134-radio", + .type = VID_TYPE_TUNER, + .hardware = 0, + .fops = &radio_fops, + .minor = -1, +}; + +int saa7134_video_init1(struct saa7134_dev *dev) +{ + /* sanitycheck insmod options */ + if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) + gbuffers = 2; + if (gbufsize < 0 || gbufsize > gbufsize_max) + gbufsize = gbufsize_max; + gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK; + + /* put some sensible defaults into the data structures ... */ + dev->ctl_bright = ctrl_by_id(V4L2_CID_BRIGHTNESS)->default_value; + dev->ctl_contrast = ctrl_by_id(V4L2_CID_CONTRAST)->default_value; + dev->ctl_hue = ctrl_by_id(V4L2_CID_HUE)->default_value; + dev->ctl_saturation = ctrl_by_id(V4L2_CID_SATURATION)->default_value; + dev->ctl_volume = ctrl_by_id(V4L2_CID_AUDIO_VOLUME)->default_value; + + dev->ctl_invert = 0; + dev->ctl_mute = 1; + dev->automute = 0; + + INIT_LIST_HEAD(&dev->video_q.queue); + init_timer(&dev->video_q.timeout); + dev->video_q.timeout.function = saa7134_buffer_timeout; + dev->video_q.timeout.data = (unsigned long)(&dev->video_q); + dev->video_q.dev = dev; + + if (saa7134_boards[dev->board].video_out) { + /* enable video output */ + int vo = saa7134_boards[dev->board].video_out; + saa_writeb(SAA7134_VIDEO_PORT_CTRL0, video_out[vo][0]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL1, video_out[vo][1]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL2, video_out[vo][2]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL3, video_out[vo][3]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL4, video_out[vo][4]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL5, video_out[vo][5]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL6, video_out[vo][6]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL7, video_out[vo][7]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL8, video_out[vo][8]); + } + + return 0; +} + +int saa7134_video_init2(struct saa7134_dev *dev) +{ + /* init video hw */ + set_tvnorm(dev,&tvnorms[0]); + video_mux(dev,0); + saa7134_tvaudio_setmute(dev); + saa7134_tvaudio_setvolume(dev,dev->ctl_volume); + return 0; +} + +int saa7134_video_fini(struct saa7134_dev *dev) +{ + /* nothing */ + return 0; +} + +void saa7134_irq_video_intl(struct saa7134_dev *dev) +{ + static const char *st[] = { + "no signal", "found NTSC", "found PAL", "found SECAM" }; + int norm; + + norm = saa_readb(SAA7134_STATUS_VIDEO1) & 0x03; + dprintk("DCSDT: %s\n",st[norm]); + + if (0 != norm) { + /* wake up tvaudio audio carrier scan thread */ + saa7134_tvaudio_do_scan(dev); + } else { + /* no video signal -> mute audio */ + dev->automute = 1; + saa7134_tvaudio_setmute(dev); + } +} + +void saa7134_irq_video_done(struct saa7134_dev *dev, unsigned long status) +{ + enum v4l2_field field; + + spin_lock(&dev->slock); + if (dev->video_q.curr) { + dev->video_fieldcount++; + field = dev->video_q.curr->vb.field; + if (V4L2_FIELD_HAS_BOTH(field)) { + /* make sure we have seen both fields */ + if ((status & 0x10) == 0x00) { + dev->video_q.curr->top_seen = 1; + goto done; + } + if (!dev->video_q.curr->top_seen) + goto done; + } else if (field == V4L2_FIELD_TOP) { + if ((status & 0x10) != 0x10) + goto done; + } else if (field == V4L2_FIELD_BOTTOM) { + if ((status & 0x10) != 0x00) + goto done; + } + dev->video_q.curr->vb.field_count = dev->video_fieldcount; + saa7134_buffer_finish(dev,&dev->video_q,STATE_DONE); + } + saa7134_buffer_next(dev,&dev->video_q); + + done: + spin_unlock(&dev->slock); +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/saa7134/saa7134.h b/linux/drivers/media/video/saa7134/saa7134.h new file mode 100644 index 000000000..f76625f66 --- /dev/null +++ b/linux/drivers/media/video/saa7134/saa7134.h @@ -0,0 +1,579 @@ +/* + * v4l2 device driver for philips saa7134 based TV cards + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/version.h> +#define SAA7134_VERSION_CODE KERNEL_VERSION(0,2,9) + +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <linux/kdev_t.h> +#include <linux/input.h> + +#include <asm/io.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,64) +#include "video-buf.h" +#include "tuner.h" +#include "audiochip.h" +#include "id.h" +#include "i2c-compat.h" +#include "ir-common.h" +#else +#include <media/video-buf.h> +#include <media/tuner.h> +#include <media/audiochip.h> +#include <media/id.h> +#include <media/ir-common.h> +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,23) +# define irqreturn_t void +# define IRQ_RETVAL(foobar) +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,71) +# define strlcpy(dest,src,len) strncpy(dest,src,(len)-1) +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +# define iminor(inode) minor(inode->i_rdev) +#endif + +#ifndef TRUE +# define TRUE (1==1) +#endif +#ifndef FALSE +# define FALSE (1==0) +#endif +#define UNSET (-1U) + +/* 2.4 / 2.5 driver compatibility stuff */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,18) +# define minor(x) MINOR(x) +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +# define need_resched() (current->need_resched) +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,19) +# define BUG_ON(condition) do { if ((condition)!=0) BUG(); } while(0) +#endif + +/* ----------------------------------------------------------- */ +/* enums */ + +enum saa7134_tvaudio_mode { + TVAUDIO_FM_MONO = 1, + TVAUDIO_FM_BG_STEREO = 2, + TVAUDIO_FM_SAT_STEREO = 3, + TVAUDIO_FM_K_STEREO = 4, + TVAUDIO_NICAM_AM = 5, + TVAUDIO_NICAM_FM = 6, +}; + +enum saa7134_audio_in { + TV = 1, + LINE1 = 2, + LINE2 = 3, +}; + +enum saa7134_video_out { + CCIR656 = 1, +}; + +/* ----------------------------------------------------------- */ +/* static data */ + +struct saa7134_tvnorm { + char *name; + v4l2_std_id id; + unsigned int width; + unsigned int height; + + /* video decoder */ + unsigned int sync_control; + unsigned int luma_control; + unsigned int chroma_ctrl1; + unsigned int chroma_gain; + unsigned int chroma_ctrl2; + unsigned int vgate_misc; + + /* video scaler */ + unsigned int h_start; + unsigned int h_stop; + unsigned int video_v_start; + unsigned int video_v_stop; + unsigned int vbi_v_start; + unsigned int vbi_v_stop; + unsigned int src_timing; +}; + +struct saa7134_tvaudio { + char *name; + v4l2_std_id std; + enum saa7134_tvaudio_mode mode; + int carr1; + int carr2; +}; + +struct saa7134_format { + char *name; + unsigned int fourcc; + unsigned int depth; + unsigned int pm; + unsigned int vshift; /* vertical downsampling (for planar yuv) */ + unsigned int hshift; /* horizontal downsampling (for planar yuv) */ + unsigned int bswap:1; + unsigned int wswap:1; + unsigned int yuv:1; + unsigned int planar:1; + unsigned int uvswap:1; +}; + +/* ----------------------------------------------------------- */ +/* card configuration */ + +#define SAA7134_BOARD_NOAUTO UNSET +#define SAA7134_BOARD_UNKNOWN 0 +#define SAA7134_BOARD_PROTEUS_PRO 1 +#define SAA7134_BOARD_FLYVIDEO3000 2 +#define SAA7134_BOARD_FLYVIDEO2000 3 +#define SAA7134_BOARD_EMPRESS 4 +#define SAA7134_BOARD_MONSTERTV 5 +#define SAA7134_BOARD_MD9717 6 +#define SAA7134_BOARD_TVSTATION_RDS 7 +#define SAA7134_BOARD_CINERGY400 8 +#define SAA7134_BOARD_MD5044 9 +#define SAA7134_BOARD_KWORLD 10 +#define SAA7134_BOARD_CINERGY600 11 +#define SAA7134_BOARD_MD7134 12 +#define SAA7134_BOARD_TYPHOON_90031 13 +#define SAA7134_BOARD_ELSA 14 +#define SAA7134_BOARD_ELSA_500TV 15 +#define SAA7134_BOARD_ASUSTeK_TVFM7134 16 +#define SAA7134_BOARD_VA1000POWER 17 +#define SAA7134_BOARD_BMK_MPEX_NOTUNER 18 +#define SAA7134_BOARD_VIDEOMATE_TV 19 +#define SAA7134_BOARD_CRONOS_PLUS 20 +#define SAA7134_BOARD_10MOONSTVMASTER 21 +#define SAA7134_BOARD_MD2819 22 +#define SAA7134_BOARD_BMK_MPEX_TUNER 23 +#define SAA7134_BOARD_TVSTATION_DVR 24 +#define SAA7134_BOARD_ASUSTEK_TVFM7133 25 +#define SAA7134_BOARD_PINNACLE_PCTV_STEREO 26 +#define SAA7134_BOARD_MANLI_MTV002 27 +#define SAA7134_BOARD_MANLI_MTV001 28 +#define SAA7134_BOARD_TG3000TV 29 +#define SAA7134_BOARD_ECS_TVP3XP 30 +#define SAA7134_BOARD_ECS_TVP3XP_4CB5 31 +#define SAA7134_BOARD_AVACSSMARTTV 32 +#define SAA7134_BOARD_AVERMEDIA_DVD_EZMAKER 33 + +#define SAA7134_INPUT_MAX 8 + +struct saa7134_input { + char *name; + unsigned int vmux; + enum saa7134_audio_in amux; + unsigned int gpio; + unsigned int tv:1; +}; + +struct saa7134_board { + char *name; + unsigned int audio_clock; + + /* input switching */ + unsigned int gpiomask; + struct saa7134_input inputs[SAA7134_INPUT_MAX]; + struct saa7134_input radio; + struct saa7134_input mute; + + /* peripheral I/O */ + unsigned int i2s_rate; + unsigned int has_ts; + enum saa7134_video_out video_out; + + /* i2c chip info */ + unsigned int tuner_type; + unsigned int need_tda9887:1; +}; + +#define card_has_radio(dev) (NULL != saa7134_boards[dev->board].radio.name) +#define card_has_ts(dev) (saa7134_boards[dev->board].has_ts) +#define card(dev) (saa7134_boards[dev->board]) +#define card_in(dev,n) (saa7134_boards[dev->board].inputs[n]) + +/* ----------------------------------------------------------- */ +/* device / file handle status */ + +#define RESOURCE_OVERLAY 1 +#define RESOURCE_VIDEO 2 +#define RESOURCE_VBI 4 + +#define INTERLACE_AUTO 0 +#define INTERLACE_ON 1 +#define INTERLACE_OFF 2 + +#define BUFFER_TIMEOUT (HZ/2) /* 0.5 seconds */ + +struct saa7134_dev; +struct saa7134_dma; + +/* saa7134 page table */ +struct saa7134_pgtable { + unsigned int size; + u32 *cpu; + dma_addr_t dma; +}; + +/* tvaudio thread status */ +struct saa7134_thread { + pid_t pid; + struct completion exit; + wait_queue_head_t wq; + unsigned int shutdown; + unsigned int scan1; + unsigned int scan2; + unsigned int mode; +}; + +/* buffer for one video/vbi/ts frame */ +struct saa7134_buf { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* saa7134 specific */ + struct saa7134_format *fmt; + unsigned int top_seen; + int (*activate)(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next); + + /* page tables */ + struct saa7134_pgtable *pt; +}; + +struct saa7134_dmaqueue { + struct saa7134_dev *dev; + struct saa7134_buf *curr; + struct list_head queue; + struct timer_list timeout; + unsigned int need_two; +}; + +/* video filehandle status */ +struct saa7134_fh { + struct saa7134_dev *dev; + unsigned int radio; + enum v4l2_buf_type type; + unsigned int resources; +#ifdef VIDIOC_G_PRIORITY + enum v4l2_priority prio; +#endif + + /* video overlay */ + struct v4l2_window win; + struct v4l2_clip clips[8]; + unsigned int nclips; + + /* video capture */ + struct saa7134_format *fmt; + unsigned int width,height; + struct videobuf_queue cap; + struct saa7134_pgtable pt_cap; + + /* vbi capture */ + struct videobuf_queue vbi; + struct saa7134_pgtable pt_vbi; +}; + +/* TS status */ +struct saa7134_ts { + unsigned int users; + + /* TS capture */ + struct videobuf_queue ts; + struct saa7134_pgtable pt_ts; + int started; +}; + +/* oss dsp status */ +struct saa7134_oss { + struct semaphore lock; + int minor_mixer; + int minor_dsp; + unsigned int users_dsp; + + /* mixer */ + enum saa7134_audio_in input; + unsigned int count; + unsigned int line1; + unsigned int line2; + + /* dsp */ + unsigned int afmt; + unsigned int rate; + unsigned int channels; + unsigned int recording; + unsigned int blocks; + unsigned int blksize; + unsigned int bufsize; + struct saa7134_pgtable pt; + struct videobuf_dmabuf dma; + wait_queue_head_t wq; + unsigned int dma_blk; + unsigned int read_offset; + unsigned int read_count; +}; + +/* IR input */ +struct saa7134_ir { + struct input_dev dev; + struct ir_input_state ir; + char name[32]; + char phys[32]; + u32 mask_keycode; + u32 mask_keydown; + u32 mask_keyup; + int polling; + u32 last_gpio; + struct timer_list timer; +}; + +/* global device status */ +struct saa7134_dev { + struct list_head devlist; + struct semaphore lock; + spinlock_t slock; +#ifdef VIDIOC_G_PRIORITY + struct v4l2_prio_state prio; +#endif + + /* various device info */ + unsigned int resources; + struct video_device *video_dev; + struct video_device *ts_dev; + struct video_device *radio_dev; + struct video_device *vbi_dev; + struct saa7134_oss oss; + struct saa7134_ts ts; + + /* infrared remote */ + int has_remote; + struct saa7134_ir *remote; + + /* pci i/o */ + char name[32]; + struct pci_dev *pci; + unsigned char pci_rev,pci_lat; + __u32 *lmmio; + __u8 *bmmio; + + /* config info */ + unsigned int board; + unsigned int tuner_type; + unsigned int gpio_value; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + unsigned char eedata[64]; + + /* video overlay */ + struct v4l2_framebuffer ovbuf; + struct saa7134_format *ovfmt; + unsigned int ovenable; + enum v4l2_field ovfield; + + /* video+ts+vbi capture */ + struct saa7134_dmaqueue video_q; + struct saa7134_dmaqueue ts_q; + struct saa7134_dmaqueue vbi_q; + unsigned int video_fieldcount; + unsigned int vbi_fieldcount; + + /* various v4l controls */ + struct saa7134_tvnorm *tvnorm; /* video */ + struct saa7134_tvaudio *tvaudio; + unsigned int ctl_input; + int ctl_bright; + int ctl_contrast; + int ctl_hue; + int ctl_saturation; + int ctl_freq; + int ctl_mute; /* audio */ + int ctl_volume; + int ctl_invert; /* private */ + int ctl_mirror; + int ctl_y_odd; + int ctl_y_even; + + /* other global state info */ + unsigned int automute; + struct saa7134_thread thread; + struct saa7134_input *input; + struct saa7134_input *hw_input; + unsigned int hw_mute; + int last_carrier; +}; + +/* ----------------------------------------------------------- */ + +#define saa_readl(reg) readl(dev->lmmio + (reg)) +#define saa_writel(reg,value) writel((value), dev->lmmio + (reg)); +#define saa_andorl(reg,mask,value) \ + writel((readl(dev->lmmio+(reg)) & ~(mask)) |\ + ((value) & (mask)), dev->lmmio+(reg)) +#define saa_setl(reg,bit) saa_andorl((reg),(bit),(bit)) +#define saa_clearl(reg,bit) saa_andorl((reg),(bit),0) + +#define saa_readb(reg) readb(dev->bmmio + (reg)) +#define saa_writeb(reg,value) writeb((value), dev->bmmio + (reg)); +#define saa_andorb(reg,mask,value) \ + writeb((readb(dev->bmmio+(reg)) & ~(mask)) |\ + ((value) & (mask)), dev->bmmio+(reg)) +#define saa_setb(reg,bit) saa_andorb((reg),(bit),(bit)) +#define saa_clearb(reg,bit) saa_andorb((reg),(bit),0) + +#define saa_wait(d) { udelay(d); } + +/* ----------------------------------------------------------- */ +/* saa7134-core.c */ + +extern struct list_head saa7134_devlist; +extern unsigned int saa7134_devcount; + +void saa7134_print_ioctl(char *name, unsigned int cmd); +void saa7134_track_gpio(struct saa7134_dev *dev, char *msg); + +#define SAA7134_PGTABLE_SIZE 4096 + +int saa7134_pgtable_alloc(struct pci_dev *pci, struct saa7134_pgtable *pt); +int saa7134_pgtable_build(struct pci_dev *pci, struct saa7134_pgtable *pt, + struct scatterlist *list, unsigned int length, + unsigned int startpage); +void saa7134_pgtable_free(struct pci_dev *pci, struct saa7134_pgtable *pt); + +int saa7134_buffer_count(unsigned int size, unsigned int count); +int saa7134_buffer_startpage(struct saa7134_buf *buf); +unsigned long saa7134_buffer_base(struct saa7134_buf *buf); + +int saa7134_buffer_queue(struct saa7134_dev *dev, struct saa7134_dmaqueue *q, + struct saa7134_buf *buf); +void saa7134_buffer_finish(struct saa7134_dev *dev, struct saa7134_dmaqueue *q, + unsigned int state); +void saa7134_buffer_next(struct saa7134_dev *dev, struct saa7134_dmaqueue *q); +void saa7134_buffer_timeout(unsigned long data); +void saa7134_dma_free(struct saa7134_dev *dev,struct saa7134_buf *buf); + +int saa7134_set_dmabits(struct saa7134_dev *dev); + +/* ----------------------------------------------------------- */ +/* saa7134-cards.c */ + +extern struct saa7134_board saa7134_boards[]; +extern const unsigned int saa7134_bcount; +extern struct pci_device_id __devinitdata saa7134_pci_tbl[]; + +extern int saa7134_board_init(struct saa7134_dev *dev); + + +/* ----------------------------------------------------------- */ +/* saa7134-i2c.c */ + +int saa7134_i2c_register(struct saa7134_dev *dev); +int saa7134_i2c_unregister(struct saa7134_dev *dev); +void saa7134_i2c_call_clients(struct saa7134_dev *dev, + unsigned int cmd, void *arg); + + +/* ----------------------------------------------------------- */ +/* saa7134-video.c */ + +extern struct video_device saa7134_video_template; +extern struct video_device saa7134_radio_template; + +int saa7134_common_ioctl(struct saa7134_dev *dev, + unsigned int cmd, void *arg); + +int saa7134_video_init1(struct saa7134_dev *dev); +int saa7134_video_init2(struct saa7134_dev *dev); +int saa7134_video_fini(struct saa7134_dev *dev); +void saa7134_irq_video_intl(struct saa7134_dev *dev); +void saa7134_irq_video_done(struct saa7134_dev *dev, unsigned long status); + + +/* ----------------------------------------------------------- */ +/* saa7134-ts.c */ + +extern struct video_device saa7134_ts_template; +int saa7134_ts_init1(struct saa7134_dev *dev); +int saa7134_ts_fini(struct saa7134_dev *dev); +void saa7134_irq_ts_done(struct saa7134_dev *dev, unsigned long status); + + +/* ----------------------------------------------------------- */ +/* saa7134-vbi.c */ + +extern struct videobuf_queue_ops saa7134_vbi_qops; +extern struct video_device saa7134_vbi_template; + +int saa7134_vbi_init1(struct saa7134_dev *dev); +int saa7134_vbi_fini(struct saa7134_dev *dev); +void saa7134_irq_vbi_done(struct saa7134_dev *dev, unsigned long status); + + +/* ----------------------------------------------------------- */ +/* saa7134-tvaudio.c */ + +int saa7134_tvaudio_rx2mode(u32 rx); + +void saa7134_tvaudio_setmute(struct saa7134_dev *dev); +void saa7134_tvaudio_setinput(struct saa7134_dev *dev, + struct saa7134_input *in); +void saa7134_tvaudio_setvolume(struct saa7134_dev *dev, int level); +int saa7134_tvaudio_getstereo(struct saa7134_dev *dev); + +int saa7134_tvaudio_init2(struct saa7134_dev *dev); +int saa7134_tvaudio_fini(struct saa7134_dev *dev); +int saa7134_tvaudio_do_scan(struct saa7134_dev *dev); + +int saa_dsp_writel(struct saa7134_dev *dev, int reg, u32 value); + +/* ----------------------------------------------------------- */ +/* saa7134-oss.c */ + +extern struct file_operations saa7134_dsp_fops; +extern struct file_operations saa7134_mixer_fops; + +int saa7134_oss_init1(struct saa7134_dev *dev); +int saa7134_oss_fini(struct saa7134_dev *dev); +void saa7134_irq_oss_done(struct saa7134_dev *dev, unsigned long status); + +/* ----------------------------------------------------------- */ +/* saa7134-input.c */ + +int saa7134_input_init1(struct saa7134_dev *dev); +void saa7134_input_fini(struct saa7134_dev *dev); +void saa7134_input_irq(struct saa7134_dev *dev); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/tda7432.c b/linux/drivers/media/video/tda7432.c new file mode 100644 index 000000000..7d2a6dd14 --- /dev/null +++ b/linux/drivers/media/video/tda7432.c @@ -0,0 +1,576 @@ +/* + * For the STS-Thompson TDA7432 audio processor chip + * + * Handles audio functions: volume, balance, tone, loudness + * This driver will not complain if used with any + * other i2c device with the same address. + * + * Muting and tone control by Jonathan Isom <jisom@ematic.com> + * + * Copyright (c) 2000 Eric Sandeen <eric_sandeen@bigfoot.com> + * This code is placed under the terms of the GNU General Public License + * Based on tda9855.c by Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Which was based on tda8425.c by Greg Alexander (c) 1998 + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * set to 2 if you'd like to be inundated with debug messages + * + * loudness - set between 0 and 15 for varying degrees of loudness effect + * + * maxvol - set maximium volume to +20db (1), default is 0db(0) + * + * + * Revision: 0.7 - maxvol module parm to set maximium volume 0db or +20db + * store if muted so we can return it + * change balance only if flaged to + * Revision: 0.6 - added tone controls + * Revision: 0.5 - Fixed odd balance problem + * Revision: 0.4 - added muting + * Revision: 0.3 - Fixed silly reversed volume controls. :) + * Revision: 0.2 - Cleaned up #defines + * fixed volume control + * Added I2C_DRIVERID_TDA7432 + * added loudness insmod control + * Revision: 0.1 - initial version + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include "bttv.h" +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "audiochip.h" +#include "id.h" +#include "i2c-compat.h" +#else +#include <media/audiochip.h> +#include <media/id.h> +#endif + +#ifndef VIDEO_AUDIO_BALANCE +# define VIDEO_AUDIO_BALANCE 32 +#endif + +MODULE_AUTHOR("Eric Sandeen <eric_sandeen@bigfoot.com>"); +MODULE_DESCRIPTION("bttv driver for the tda7432 audio processor chip"); +MODULE_LICENSE("GPL"); + +MODULE_PARM(debug,"i"); +MODULE_PARM(loudness,"i"); +MODULE_PARM_DESC(maxvol,"Set maximium volume to +20db (0), default is 0db(1)"); +MODULE_PARM(maxvol,"i"); +static int maxvol = 0; +static int loudness = 0; /* disable loudness by default */ +static int debug = 0; /* insmod parameter */ + + +/* Address to scan (I2C address of this chip) */ +static unsigned short normal_i2c[] = { + I2C_TDA7432 >> 1, + I2C_CLIENT_END, +}; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END, I2C_CLIENT_END }; +I2C_CLIENT_INSMOD; + +/* Structure of address and subaddresses for the tda7432 */ + +struct tda7432 { + int addr; + int input; + int volume; + int muted; + int bass, treble; + int lf, lr, rf, rr; + int loud; + struct i2c_client c; +}; +static struct i2c_driver driver; +static struct i2c_client client_template; + +#define dprintk if (debug) printk +#define d2printk if (debug > 1) printk + +/* The TDA7432 is made by STS-Thompson + * http://www.st.com + * http://us.st.com/stonline/books/pdf/docs/4056.pdf + * + * TDA7432: I2C-bus controlled basic audio processor + * + * The TDA7432 controls basic audio functions like volume, balance, + * and tone control (including loudness). It also has four channel + * output (for front and rear). Since most vidcap cards probably + * don't have 4 channel output, this driver will set front & rear + * together (no independent control). + */ + + /* Subaddresses for TDA7432 */ + +#define TDA7432_IN 0x00 /* Input select */ +#define TDA7432_VL 0x01 /* Volume */ +#define TDA7432_TN 0x02 /* Bass, Treble (Tone) */ +#define TDA7432_LF 0x03 /* Attenuation LF (Left Front) */ +#define TDA7432_LR 0x04 /* Attenuation LR (Left Rear) */ +#define TDA7432_RF 0x05 /* Attenuation RF (Right Front) */ +#define TDA7432_RR 0x06 /* Attenuation RR (Right Rear) */ +#define TDA7432_LD 0x07 /* Loudness */ + + + /* Masks for bits in TDA7432 subaddresses */ + +/* Many of these not used - just for documentation */ + +/* Subaddress 0x00 - Input selection and bass control */ + +/* Bits 0,1,2 control input: + * 0x00 - Stereo input + * 0x02 - Mono input + * 0x03 - Mute (Using Attenuators Plays better with modules) + * Mono probably isn't used - I'm guessing only the stereo + * input is connected on most cards, so we'll set it to stereo. + * + * Bit 3 controls bass cut: 0/1 is non-symmetric/symmetric bass cut + * Bit 4 controls bass range: 0/1 is extended/standard bass range + * + * Highest 3 bits not used + */ + +#define TDA7432_STEREO_IN 0 +#define TDA7432_MONO_IN 2 /* Probably won't be used */ +#define TDA7432_BASS_SYM 1 << 3 +#define TDA7432_BASS_NORM 1 << 4 + +/* Subaddress 0x01 - Volume */ + +/* Lower 7 bits control volume from -79dB to +32dB in 1dB steps + * Recommended maximum is +20 dB + * + * +32dB: 0x00 + * +20dB: 0x0c + * 0dB: 0x20 + * -79dB: 0x6f + * + * MSB (bit 7) controls loudness: 1/0 is loudness on/off + */ + +#define TDA7432_VOL_0DB 0x20 +#define TDA7432_LD_ON 1 << 7 + + +/* Subaddress 0x02 - Tone control */ + +/* Bits 0,1,2 control absolute treble gain from 0dB to 14dB + * 0x0 is 14dB, 0x7 is 0dB + * + * Bit 3 controls treble attenuation/gain (sign) + * 1 = gain (+) + * 0 = attenuation (-) + * + * Bits 4,5,6 control absolute bass gain from 0dB to 14dB + * (This is only true for normal base range, set in 0x00) + * 0x0 << 4 is 14dB, 0x7 is 0dB + * + * Bit 7 controls bass attenuation/gain (sign) + * 1 << 7 = gain (+) + * 0 << 7 = attenuation (-) + * + * Example: + * 1 1 0 1 0 1 0 1 is +4dB bass, -4dB treble + */ + +#define TDA7432_TREBLE_0DB 0xf +#define TDA7432_TREBLE 7 +#define TDA7432_TREBLE_GAIN 1 << 3 +#define TDA7432_BASS_0DB 0xf +#define TDA7432_BASS 7 << 4 +#define TDA7432_BASS_GAIN 1 << 7 + + +/* Subaddress 0x03 - Left Front attenuation */ +/* Subaddress 0x04 - Left Rear attenuation */ +/* Subaddress 0x05 - Right Front attenuation */ +/* Subaddress 0x06 - Right Rear attenuation */ + +/* Bits 0,1,2,3,4 control attenuation from 0dB to -37.5dB + * in 1.5dB steps. + * + * 0x00 is 0dB + * 0x1f is -37.5dB + * + * Bit 5 mutes that channel when set (1 = mute, 0 = unmute) + * We'll use the mute on the input, though (above) + * Bits 6,7 unused + */ + +#define TDA7432_ATTEN_0DB 0x00 +#define TDA7432_MUTE 0x1 << 5 + + +/* Subaddress 0x07 - Loudness Control */ + +/* Bits 0,1,2,3 control loudness from 0dB to -15dB in 1dB steps + * when bit 4 is NOT set + * + * 0x0 is 0dB + * 0xf is -15dB + * + * If bit 4 is set, then there is a flat attenuation according to + * the lower 4 bits, as above. + * + * Bits 5,6,7 unused + */ + + + +/* Begin code */ + +static int tda7432_write(struct i2c_client *client, int subaddr, int val) +{ + unsigned char buffer[2]; + d2printk("tda7432: In tda7432_write\n"); + dprintk("tda7432: Writing %d 0x%x\n", subaddr, val); + buffer[0] = subaddr; + buffer[1] = val; + if (2 != i2c_master_send(client,buffer,2)) { + printk(KERN_WARNING "tda7432: I/O error, trying (write %d 0x%x)\n", + subaddr, val); + return -1; + } + return 0; +} + +/* I don't think we ever actually _read_ the chip... */ +#if 0 +static int tda7432_read(struct i2c_client *client) +{ + unsigned char buffer; + d2printk("tda7432: In tda7432_read\n"); + if (1 != i2c_master_recv(client,&buffer,1)) { + printk(KERN_WARNING "tda7432: I/O error, trying (read)\n"); + return -1; + } + dprintk("tda7432: Read 0x%02x\n", buffer); + return buffer; +} +#endif + +static int tda7432_set(struct i2c_client *client) +{ + struct tda7432 *t = i2c_get_clientdata(client); + unsigned char buf[16]; + d2printk("tda7432: In tda7432_set\n"); + + dprintk(KERN_INFO + "tda7432: 7432_set(0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x)\n", + t->input,t->volume,t->bass,t->treble,t->lf,t->lr,t->rf,t->rr,t->loud); + buf[0] = TDA7432_IN; + buf[1] = t->input; + buf[2] = t->volume; + buf[3] = t->bass; + buf[4] = t->treble; + buf[5] = t->lf; + buf[6] = t->lr; + buf[7] = t->rf; + buf[8] = t->rr; + buf[9] = t->loud; + if (10 != i2c_master_send(client,buf,10)) { + printk(KERN_WARNING "tda7432: I/O error, trying tda7432_set\n"); + return -1; + } + + return 0; +} + +static void do_tda7432_init(struct i2c_client *client) +{ + struct tda7432 *t = i2c_get_clientdata(client); + d2printk("tda7432: In tda7432_init\n"); + + t->input = TDA7432_STEREO_IN | /* Main (stereo) input */ + TDA7432_BASS_SYM | /* Symmetric bass cut */ + TDA7432_BASS_NORM; /* Normal bass range */ + t->volume = 0x3b ; /* -27dB Volume */ + if (loudness) /* Turn loudness on? */ + t->volume |= TDA7432_LD_ON; + t->muted = VIDEO_AUDIO_MUTE; + t->treble = TDA7432_TREBLE_0DB; /* 0dB Treble */ + t->bass = TDA7432_BASS_0DB; /* 0dB Bass */ + t->lf = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->lr = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->rf = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->rr = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->loud = loudness; /* insmod parameter */ + + tda7432_set(client); +} + +/* *********************** * + * i2c interface functions * + * *********************** */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int tda7432_attach(struct i2c_adapter *adap, int addr, int kind) +#else +static int tda7432_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +#endif +{ + struct tda7432 *t; + struct i2c_client *client; + d2printk("tda7432: In tda7432_attach\n"); + + t = kmalloc(sizeof *t,GFP_KERNEL); + if (!t) + return -ENOMEM; + memset(t,0,sizeof *t); + + client = &t->c; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->adapter = adap; + client->addr = addr; + i2c_set_clientdata(client, t); + + do_tda7432_init(client); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_INC_USE_COUNT; +#endif + printk(KERN_INFO "tda7432: init\n"); + + i2c_attach_client(client); + return 0; +} + +static int tda7432_probe(struct i2c_adapter *adap) +{ +#ifdef I2C_ADAP_CLASS_TV_ANALOG + if (adap->class & I2C_ADAP_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, tda7432_attach); +#else + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, tda7432_attach); +#endif + return 0; +} + +static int tda7432_detach(struct i2c_client *client) +{ + struct tda7432 *t = i2c_get_clientdata(client); + + do_tda7432_init(client); + i2c_detach_client(client); + + kfree(t); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_DEC_USE_COUNT; +#endif + return 0; +} + +static int tda7432_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct tda7432 *t = i2c_get_clientdata(client); + d2printk("tda7432: In tda7432_command\n"); + + switch (cmd) { + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + + /* Query card - scale from TDA7432 settings to V4L settings */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + dprintk("tda7432: VIDIOCGAUDIO\n"); + + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE | + VIDEO_AUDIO_MUTABLE; + if (t->muted) + va->flags |= VIDEO_AUDIO_MUTE; + va->mode |= VIDEO_SOUND_STEREO; + /* Master volume control + * V4L volume is min 0, max 65535 + * TDA7432 Volume: + * Min (-79dB) is 0x6f + * Max (+20dB) is 0x07 (630) + * Max (0dB) is 0x20 (829) + * (Mask out bit 7 of vol - it's for the loudness setting) + */ + if (!maxvol){ /* max +20db */ + va->volume = ( 0x6f - (t->volume & 0x7F) ) * 630; + } else { /* max 0db */ + va->volume = ( 0x6f - (t->volume & 0x7F) ) * 829; + } + + /* Balance depends on L,R attenuation + * V4L balance is 0 to 65535, middle is 32768 + * TDA7432 attenuation: min (0dB) is 0, max (-37.5dB) is 0x1f + * to scale up to V4L numbers, mult by 1057 + * attenuation exists for lf, lr, rf, rr + * we use only lf and rf (front channels) + */ + + if ( (t->lf) < (t->rf) ) + /* right is attenuated, balance shifted left */ + va->balance = (32768 - 1057*(t->rf)); + else + /* left is attenuated, balance shifted right */ + va->balance = (32768 + 1057*(t->lf)); + + /* Bass/treble 4 bits each */ + va->bass=t->bass; + if(va->bass >= 0x8) + va->bass = ~(va->bass - 0x8) & 0xf; + va->bass = (va->bass << 12)+(va->bass << 8)+(va->bass << 4)+(va->bass); + va->treble=t->treble; + if(va->treble >= 0x8) + va->treble = ~(va->treble - 0x8) & 0xf; + va->treble = (va->treble << 12)+(va->treble << 8)+(va->treble << 4)+(va->treble); + + break; /* VIDIOCGAUDIO case */ + } + + /* Set card - scale from V4L settings to TDA7432 settings */ + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + dprintk("tda7432: VIDEOCSAUDIO\n"); + + if(va->flags & VIDEO_AUDIO_VOLUME){ + if(!maxvol){ /* max +20db */ + t->volume = 0x6f - ((va->volume)/630); + } else { /* max 0db */ + t->volume = 0x6f - ((va->volume)/829); + } + + if (loudness) /* Turn on the loudness bit */ + t->volume |= TDA7432_LD_ON; + + tda7432_write(client,TDA7432_VL, t->volume); + } + + if(va->flags & VIDEO_AUDIO_BASS) + { + t->bass = va->bass >> 12; + if(t->bass>= 0x8) + t->bass = (~t->bass & 0xf) + 0x8 ; + } + if(va->flags & VIDEO_AUDIO_TREBLE) + { + t->treble= va->treble >> 12; + if(t->treble>= 0x8) + t->treble = (~t->treble & 0xf) + 0x8 ; + } + if(va->flags & (VIDEO_AUDIO_TREBLE| VIDEO_AUDIO_BASS)) + tda7432_write(client,TDA7432_TN, 0x10 | (t->bass << 4) | t->treble ); + + if(va->flags & VIDEO_AUDIO_BALANCE) { + if (va->balance < 32768) + { + /* shifted to left, attenuate right */ + t->rr = (32768 - va->balance)/1057; + t->rf = t->rr; + t->lr = TDA7432_ATTEN_0DB; + t->lf = TDA7432_ATTEN_0DB; + } + else if(va->balance > 32769) + { + /* shifted to right, attenuate left */ + t->lf = (va->balance - 32768)/1057; + t->lr = t->lf; + t->rr = TDA7432_ATTEN_0DB; + t->rf = TDA7432_ATTEN_0DB; + } + else + { + /* centered */ + t->rr = TDA7432_ATTEN_0DB; + t->rf = TDA7432_ATTEN_0DB; + t->lf = TDA7432_ATTEN_0DB; + t->lr = TDA7432_ATTEN_0DB; + } + } + + t->muted=(va->flags & VIDEO_AUDIO_MUTE); + if (t->muted) + { + /* Mute & update balance*/ + tda7432_write(client,TDA7432_LF, t->lf | TDA7432_MUTE); + tda7432_write(client,TDA7432_LR, t->lr | TDA7432_MUTE); + tda7432_write(client,TDA7432_RF, t->rf | TDA7432_MUTE); + tda7432_write(client,TDA7432_RR, t->rr | TDA7432_MUTE); + } else { + tda7432_write(client,TDA7432_LF, t->lf); + tda7432_write(client,TDA7432_LR, t->lr); + tda7432_write(client,TDA7432_RF, t->rf); + tda7432_write(client,TDA7432_RR, t->rr); + } + + break; + + } /* end of VIDEOCSAUDIO case */ + + default: /* Not VIDEOCGAUDIO or VIDEOCSAUDIO */ + + /* nothing */ + d2printk("tda7432: Default\n"); + + } /* end of (cmd) switch */ + + return 0; +} + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif + .name = "i2c tda7432 driver", + .id = I2C_DRIVERID_TDA7432, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tda7432_probe, + .detach_client = tda7432_detach, + .command = tda7432_command, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("tda7432"), + .id = -1, + .driver = &driver, +}; + +static int tda7432_init(void) +{ + if ( (loudness < 0) || (loudness > 15) ) { + printk(KERN_ERR "tda7432: loudness parameter must be between 0 and 15\n"); + return -EINVAL; + } + i2c_add_driver(&driver); + return 0; +} + +static void tda7432_fini(void) +{ + i2c_del_driver(&driver); +} + +module_init(tda7432_init); +module_exit(tda7432_fini); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/tda9875.c b/linux/drivers/media/video/tda9875.c new file mode 100644 index 000000000..88563a6e7 --- /dev/null +++ b/linux/drivers/media/video/tda9875.c @@ -0,0 +1,444 @@ +/* + * For the TDA9875 chip + * (The TDA9875 is used on the Diamond DTV2000 french version + * Other cards probably use these chips as well.) + * This driver will not complain if used with any + * other i2c device with the same address. + * + * Copyright (c) 2000 Guillaume Delvit based on Gerd Knorr source and + * Eric Sandeen + * This code is placed under the terms of the GNU General Public License + * Based on tda9855.c by Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Which was based on tda8425.c by Greg Alexander (c) 1998 + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * + * Revision: 0.1 - original version + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/init.h> + +#include "bttv.h" +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "audiochip.h" +#include "id.h" +#include "i2c-compat.h" +#else +#include <media/audiochip.h> +#include <media/id.h> +#endif + +MODULE_PARM(debug,"i"); +MODULE_LICENSE("GPL"); + +static int debug = 0; /* insmod parameter */ + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + I2C_TDA9875 >> 1, + I2C_CLIENT_END +}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +/* This is a superset of the TDA9875 */ +struct tda9875 { + int mode; + int rvol, lvol; + int bass, treble; + struct i2c_client c; +}; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +#define dprintk if (debug) printk + +/* The TDA9875 is made by Philips Semiconductor + * http://www.semiconductors.philips.com + * TDA9875: I2C-bus controlled DSP audio processor, FM demodulator + * + */ + + /* subaddresses for TDA9875 */ +#define TDA9875_MUT 0x12 /*General mute (value --> 0b11001100*/ +#define TDA9875_CFG 0x01 /* Config register (value --> 0b00000000 */ +#define TDA9875_DACOS 0x13 /*DAC i/o select (ADC) 0b0000100*/ +#define TDA9875_LOSR 0x16 /*Line output select regirter 0b0100 0001*/ + +#define TDA9875_CH1V 0x0c /*Channel 1 volume (mute)*/ +#define TDA9875_CH2V 0x0d /*Channel 2 volume (mute)*/ +#define TDA9875_SC1 0x14 /*SCART 1 in (mono)*/ +#define TDA9875_SC2 0x15 /*SCART 2 in (mono)*/ + +#define TDA9875_ADCIS 0x17 /*ADC input select (mono) 0b0110 000*/ +#define TDA9875_AER 0x19 /*Audio effect (AVL+Pseudo) 0b0000 0110*/ +#define TDA9875_MCS 0x18 /*Main channel select (DAC) 0b0000100*/ +#define TDA9875_MVL 0x1a /* Main volume gauche */ +#define TDA9875_MVR 0x1b /* Main volume droite */ +#define TDA9875_MBA 0x1d /* Main Basse */ +#define TDA9875_MTR 0x1e /* Main treble */ +#define TDA9875_ACS 0x1f /* Auxilary channel select (FM) 0b0000000*/ +#define TDA9875_AVL 0x20 /* Auxilary volume gauche */ +#define TDA9875_AVR 0x21 /* Auxilary volume droite */ +#define TDA9875_ABA 0x22 /* Auxilary Basse */ +#define TDA9875_ATR 0x23 /* Auxilary treble */ + +#define TDA9875_MSR 0x02 /* Monitor select register */ +#define TDA9875_C1MSB 0x03 /* Carrier 1 (FM) frequency register MSB */ +#define TDA9875_C1MIB 0x04 /* Carrier 1 (FM) frequency register (16-8]b */ +#define TDA9875_C1LSB 0x05 /* Carrier 1 (FM) frequency register LSB */ +#define TDA9875_C2MSB 0x06 /* Carrier 2 (nicam) frequency register MSB */ +#define TDA9875_C2MIB 0x07 /* Carrier 2 (nicam) frequency register (16-8]b */ +#define TDA9875_C2LSB 0x08 /* Carrier 2 (nicam) frequency register LSB */ +#define TDA9875_DCR 0x09 /* Demodulateur configuration regirter*/ +#define TDA9875_DEEM 0x0a /* FM de-emphasis regirter*/ +#define TDA9875_FMAT 0x0b /* FM Matrix regirter*/ + +/* values */ +#define TDA9875_MUTE_ON 0xff /* general mute */ +#define TDA9875_MUTE_OFF 0xcc /* general no mute */ + + + +/* Begin code */ + +static int tda9875_write(struct i2c_client *client, int subaddr, unsigned char val) +{ + unsigned char buffer[2]; + dprintk("In tda9875_write\n"); + dprintk("Writing %d 0x%x\n", subaddr, val); + buffer[0] = subaddr; + buffer[1] = val; + if (2 != i2c_master_send(client,buffer,2)) { + printk(KERN_WARNING "tda9875: I/O error, trying (write %d 0x%x)\n", + subaddr, val); + return -1; + } + return 0; +} + +#if 0 +static int tda9875_read(struct i2c_client *client) +{ + unsigned char buffer; + dprintk("In tda9875_read\n"); + if (1 != i2c_master_recv(client,&buffer,1)) { + printk(KERN_WARNING "tda9875: I/O error, trying (read)\n"); + return -1; + } + dprintk("Read 0x%02x\n", buffer); + return buffer; +} +#endif + +static int i2c_read_register(struct i2c_adapter *adap, int addr, int reg) +{ + unsigned char write[1]; + unsigned char read[1]; + struct i2c_msg msgs[2] = { + { addr, 0, 1, write }, + { addr, I2C_M_RD, 1, read } + }; + write[0] = reg; + + if (2 != i2c_transfer(adap,msgs,2)) { + printk(KERN_WARNING "tda9875: I/O error (read2)\n"); + return -1; + } + dprintk("tda9875: chip_read2: reg%d=0x%x\n",reg,read[0]); + return read[0]; +} + +static void tda9875_set(struct i2c_client *client) +{ + struct tda9875 *tda = i2c_get_clientdata(client); + unsigned char a; + + dprintk(KERN_DEBUG "tda9875_set(%04x,%04x,%04x,%04x)\n", + tda->lvol,tda->rvol,tda->bass,tda->treble); + + + a = tda->lvol & 0xff; + tda9875_write(client, TDA9875_MVL, a); + a =tda->rvol & 0xff; + tda9875_write(client, TDA9875_MVR, a); + a =tda->bass & 0xff; + tda9875_write(client, TDA9875_MBA, a); + a =tda->treble & 0xff; + tda9875_write(client, TDA9875_MTR, a); +} + +static void do_tda9875_init(struct i2c_client *client) +{ + struct tda9875 *t = i2c_get_clientdata(client); + dprintk("In tda9875_init\n"); + tda9875_write(client, TDA9875_CFG, 0xd0 ); /*reg de config 0 (reset)*/ + tda9875_write(client, TDA9875_MSR, 0x03 ); /* Monitor 0b00000XXX*/ + tda9875_write(client, TDA9875_C1MSB, 0x00 ); /*Car1(FM) MSB XMHz*/ + tda9875_write(client, TDA9875_C1MIB, 0x00 ); /*Car1(FM) MIB XMHz*/ + tda9875_write(client, TDA9875_C1LSB, 0x00 ); /*Car1(FM) LSB XMHz*/ + tda9875_write(client, TDA9875_C2MSB, 0x00 ); /*Car2(NICAM) MSB XMHz*/ + tda9875_write(client, TDA9875_C2MIB, 0x00 ); /*Car2(NICAM) MIB XMHz*/ + tda9875_write(client, TDA9875_C2LSB, 0x00 ); /*Car2(NICAM) LSB XMHz*/ + tda9875_write(client, TDA9875_DCR, 0x00 ); /*Demod config 0x00*/ + tda9875_write(client, TDA9875_DEEM, 0x44 ); /*DE-Emph 0b0100 0100*/ + tda9875_write(client, TDA9875_FMAT, 0x00 ); /*FM Matrix reg 0x00*/ + tda9875_write(client, TDA9875_SC1, 0x00 ); /* SCART 1 (SC1)*/ + tda9875_write(client, TDA9875_SC2, 0x01 ); /* SCART 2 (sc2)*/ + + tda9875_write(client, TDA9875_CH1V, 0x10 ); /* Channel volume 1 mute*/ + tda9875_write(client, TDA9875_CH2V, 0x10 ); /* Channel volume 2 mute */ + tda9875_write(client, TDA9875_DACOS, 0x02 ); /* sig DAC i/o(in:nicam)*/ + tda9875_write(client, TDA9875_ADCIS, 0x6f ); /* sig ADC input(in:mono)*/ + tda9875_write(client, TDA9875_LOSR, 0x00 ); /* line out (in:mono)*/ + tda9875_write(client, TDA9875_AER, 0x00 ); /*06 Effect (AVL+PSEUDO) */ + tda9875_write(client, TDA9875_MCS, 0x44 ); /* Main ch select (DAC) */ + tda9875_write(client, TDA9875_MVL, 0x03 ); /* Vol Main left 10dB */ + tda9875_write(client, TDA9875_MVR, 0x03 ); /* Vol Main right 10dB*/ + tda9875_write(client, TDA9875_MBA, 0x00 ); /* Main Bass Main 0dB*/ + tda9875_write(client, TDA9875_MTR, 0x00 ); /* Main Treble Main 0dB*/ + tda9875_write(client, TDA9875_ACS, 0x44 ); /* Aux chan select (dac)*/ + tda9875_write(client, TDA9875_AVL, 0x00 ); /* Vol Aux left 0dB*/ + tda9875_write(client, TDA9875_AVR, 0x00 ); /* Vol Aux right 0dB*/ + tda9875_write(client, TDA9875_ABA, 0x00 ); /* Aux Bass Main 0dB*/ + tda9875_write(client, TDA9875_ATR, 0x00 ); /* Aux Aigus Main 0dB*/ + + tda9875_write(client, TDA9875_MUT, 0xcc ); /* General mute */ + + t->mode=AUDIO_UNMUTE; + t->lvol=t->rvol =0; /* 0dB */ + t->bass=0; /* 0dB */ + t->treble=0; /* 0dB */ + tda9875_set(client); + +} + + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tda9875_checkit(struct i2c_adapter *adap, int addr) +{ + int dic,rev; + + dic=i2c_read_register(adap,addr,254); + rev=i2c_read_register(adap,addr,255); + + if(dic==0 || dic==2) { // tda9875 and tda9875A + printk("tda9875: TDA9875%s Rev.%d detected at 0x%x\n", + dic==0?"":"A", rev,addr<<1); + return 1; + } + printk("tda9875: no such chip at 0x%x (dic=0x%x rev=0x%x)\n",addr<<1,dic,rev); + return(0); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int tda9875_attach(struct i2c_adapter *adap, int addr, int kind) +#else +static int tda9875_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +#endif +{ + struct tda9875 *t; + struct i2c_client *client; + dprintk("In tda9875_attach\n"); + + t = kmalloc(sizeof *t,GFP_KERNEL); + if (!t) + return -ENOMEM; + memset(t,0,sizeof *t); + + client = &t->c; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->adapter = adap; + client->addr = addr; + i2c_set_clientdata(client, t); + + if(!tda9875_checkit(adap,addr)) { + kfree(t); + return 1; + } + + do_tda9875_init(client); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_INC_USE_COUNT; +#endif + printk(KERN_INFO "tda9875: init\n"); + + i2c_attach_client(client); + return 0; +} + +static int tda9875_probe(struct i2c_adapter *adap) +{ +#ifdef I2C_ADAP_CLASS_TV_ANALOG + if (adap->class & I2C_ADAP_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, tda9875_attach); +#else + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, tda9875_attach); +#endif + return 0; +} + +static int tda9875_detach(struct i2c_client *client) +{ + struct tda9875 *t = i2c_get_clientdata(client); + + do_tda9875_init(client); + i2c_detach_client(client); + + kfree(t); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_DEC_USE_COUNT; +#endif + return 0; +} + +static int tda9875_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct tda9875 *t = i2c_get_clientdata(client); + + dprintk("In tda9875_command...\n"); + + switch (cmd) { + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + int left,right; + + dprintk("VIDIOCGAUDIO\n"); + + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE; + + /* min is -84 max is 24 */ + left = (t->lvol+84)*606; + right = (t->rvol+84)*606; + va->volume=max(left,right); + va->balance=(32768*min(left,right))/ + (va->volume ? va->volume : 1); + va->balance=(left<right)? + (65535-va->balance) : va->balance; + va->bass = (t->bass+12)*2427; /* min -12 max +15 */ + va->treble = (t->treble+12)*2730;/* min -12 max +12 */ + va->mode |= VIDEO_SOUND_MONO; + + break; /* VIDIOCGAUDIO case */ + } + + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + int left,right; + + dprintk("VIDEOCSAUDIO...\n"); + left = (min(65536 - va->balance,32768) * + va->volume) / 32768; + right = (min(va->balance,(__u16)32768) * + va->volume) / 32768; + t->lvol = ((left/606)-84) & 0xff; + if (t->lvol > 24) + t->lvol = 24; + if (t->lvol < -84) + t->lvol = -84 & 0xff; + + t->rvol = ((right/606)-84) & 0xff; + if (t->rvol > 24) + t->rvol = 24; + if (t->rvol < -84) + t->rvol = -84 & 0xff; + + t->bass = ((va->bass/2400)-12) & 0xff; + if (t->bass > 15) + t->bass = 15; + if (t->bass < -12) + t->bass = -12 & 0xff; + + t->treble = ((va->treble/2700)-12) & 0xff; + if (t->treble > 12) + t->treble = 12; + if (t->treble < -12) + t->treble = -12 & 0xff; + + + +//printk("tda9875 bal:%04x vol:%04x bass:%04x treble:%04x\n",va->balance,va->volume,va->bass,va->treble); + + + tda9875_set(client); + + break; + + } /* end of VIDEOCSAUDIO case */ + + default: /* Not VIDEOCGAUDIO or VIDEOCSAUDIO */ + + /* nothing */ + dprintk("Default\n"); + + } /* end of (cmd) switch */ + + return 0; +} + + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif + .name = "i2c tda9875 driver", + .id = I2C_DRIVERID_TDA9875, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tda9875_probe, + .detach_client = tda9875_detach, + .command = tda9875_command, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("tda9875"), + .id = -1, + .driver = &driver, +}; + +static int tda9875_init(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void tda9875_fini(void) +{ + i2c_del_driver(&driver); +} + +module_init(tda9875_init); +module_exit(tda9875_fini); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ + diff --git a/linux/drivers/media/video/tda9887.c b/linux/drivers/media/video/tda9887.c new file mode 100644 index 000000000..448e8942c --- /dev/null +++ b/linux/drivers/media/video/tda9887.c @@ -0,0 +1,495 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/slab.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "audiochip.h" +#include "id.h" +#include "i2c-compat.h" +#else +#include <media/audiochip.h> +#include <media/id.h> +#endif + +/* Chips: + TDA9885 (PAL, NTSC) + TDA9886 (PAL, SECAM, NTSC) + TDA9887 (PAL, SECAM, NTSC, FM Radio) + + found on: + - Pinnacle PCTV (Jul.2002 Version with MT2032, bttv) + TDA9887 (world), TDA9885 (USA) + Note: OP2 of tda988x must be set to 1, else MT2032 is disabled! + - KNC One TV-Station RDS (saa7134) +*/ + + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + 0x86 >>1, + 0x96 >>1, + I2C_CLIENT_END, +}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END,I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +/* insmod options */ +static int debug = 0; +static char *pal = "b"; +static char *secam = "l"; +MODULE_PARM(debug,"i"); +MODULE_PARM(pal,"s"); +MODULE_PARM(secam,"s"); +MODULE_LICENSE("GPL"); + +/* ---------------------------------------------------------------------- */ + +#define dprintk if (debug) printk + +struct tda9887 { + struct i2c_client client; + int radio,tvnorm; + int pinnacle_id; +}; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +/* ---------------------------------------------------------------------- */ + +// +// TDA defines +// + +//// first reg +#define cVideoTrapBypassOFF 0x00 // bit b0 +#define cVideoTrapBypassON 0x01 // bit b0 + +#define cAutoMuteFmInactive 0x00 // bit b1 +#define cAutoMuteFmActive 0x02 // bit b1 + +#define cIntercarrier 0x00 // bit b2 +#define cQSS 0x04 // bit b2 + +#define cPositiveAmTV 0x00 // bit b3:4 +#define cFmRadio 0x08 // bit b3:4 +#define cNegativeFmTV 0x10 // bit b3:4 + + +#define cForcedMuteAudioON 0x20 // bit b5 +#define cForcedMuteAudioOFF 0x00 // bit b5 + +#define cOutputPort1Active 0x00 // bit b6 +#define cOutputPort1Inactive 0x40 // bit b6 + +#define cOutputPort2Active 0x00 // bit b7 +#define cOutputPort2Inactive 0x80 // bit b7 + + +//// second reg +#define cDeemphasisOFF 0x00 // bit c5 +#define cDeemphasisON 0x20 // bit c5 + +#define cDeemphasis75 0x00 // bit c6 +#define cDeemphasis50 0x40 // bit c6 + +#define cAudioGain0 0x00 // bit c7 +#define cAudioGain6 0x80 // bit c7 + + +//// third reg +#define cAudioIF_4_5 0x00 // bit e0:1 +#define cAudioIF_5_5 0x01 // bit e0:1 +#define cAudioIF_6_0 0x02 // bit e0:1 +#define cAudioIF_6_5 0x03 // bit e0:1 + + +#define cVideoIF_58_75 0x00 // bit e2:4 +#define cVideoIF_45_75 0x04 // bit e2:4 +#define cVideoIF_38_90 0x08 // bit e2:4 +#define cVideoIF_38_00 0x0C // bit e2:4 +#define cVideoIF_33_90 0x10 // bit e2:4 +#define cVideoIF_33_40 0x14 // bit e2:4 +#define cRadioIF_45_75 0x18 // bit e2:4 +#define cRadioIF_38_90 0x1C // bit e2:4 + + +#define cTunerGainNormal 0x00 // bit e5 +#define cTunerGainLow 0x20 // bit e5 + +#define cGating_18 0x00 // bit e6 +#define cGating_36 0x40 // bit e6 + +#define cAgcOutON 0x80 // bit e7 +#define cAgcOutOFF 0x00 // bit e7 + +static int tda9887_miro(struct tda9887 *t) +{ + int rc; + u8 bData[4] = { 0 }; + u8 bVideoIF = 0; + u8 bAudioIF = 0; + u8 bDeEmphasis = 0; + u8 bDeEmphVal = 0; + u8 bModulation = 0; + u8 bCarrierMode = 0; + u8 bOutPort1 = cOutputPort1Inactive; +#if 0 + u8 bOutPort2 = cOutputPort2Inactive & mbTADState; // store i2c tuner state +#else + u8 bOutPort2 = cOutputPort2Inactive; +#endif + u8 bVideoTrap = cVideoTrapBypassOFF; +#if 1 + u8 bTopAdjust = 0x0e /* -2dB */; +#else + u8 bTopAdjust = 0; +#endif + +#if 0 + if (mParams.fVideoTrap) + bVideoTrap = cVideoTrapBypassON; +#endif + + if (t->radio) { + bVideoTrap = cVideoTrapBypassOFF; + bCarrierMode = cQSS; + bModulation = cFmRadio; + bOutPort1 = cOutputPort1Inactive; + bDeEmphasis = cDeemphasisON; + if (3 == t->pinnacle_id) { + /* ntsc */ + bDeEmphVal = cDeemphasis75; + bAudioIF = cAudioIF_4_5; + bVideoIF = cRadioIF_45_75; + } else { + /* pal */ + bAudioIF = cAudioIF_5_5; + bVideoIF = cRadioIF_38_90; + bDeEmphVal = cDeemphasis50; + } + + } else if (t->tvnorm == VIDEO_MODE_PAL) { + bDeEmphasis = cDeemphasisON; + bDeEmphVal = cDeemphasis50; + bModulation = cNegativeFmTV; + bOutPort1 = cOutputPort1Inactive; + if ((1 == t->pinnacle_id) || (7 == t->pinnacle_id)) { + bCarrierMode = cIntercarrier; + } else { + // stereo boards + bCarrierMode = cQSS; + } + switch (pal[0]) { + case 'b': + case 'g': + case 'h': + bVideoIF = cVideoIF_38_90; + bAudioIF = cAudioIF_5_5; + break; + case 'd': + bVideoIF = cVideoIF_38_00; + bAudioIF = cAudioIF_6_5; + break; + case 'i': + bVideoIF = cVideoIF_38_90; + bAudioIF = cAudioIF_6_0; + break; + case 'm': + case 'n': + bVideoIF = cVideoIF_45_75; + bAudioIF = cAudioIF_4_5; + bDeEmphVal = cDeemphasis75; + if ((5 == t->pinnacle_id) || (6 == t->pinnacle_id)) { + bCarrierMode = cIntercarrier; + } else { + bCarrierMode = cQSS; + } + break; + } + + } else if (t->tvnorm == VIDEO_MODE_SECAM) { + bAudioIF = cAudioIF_6_5; + bDeEmphasis = cDeemphasisON; + bDeEmphVal = cDeemphasis50; + bModulation = cNegativeFmTV; + bCarrierMode = cQSS; + bOutPort1 = cOutputPort1Inactive; + switch (secam[0]) { + case 'd': + bVideoIF = cVideoIF_38_00; + break; + case 'k': + bVideoIF = cVideoIF_38_90; + break; + case 'l': + bVideoIF = cVideoIF_38_90; + bDeEmphasis = cDeemphasisOFF; + bDeEmphVal = cDeemphasis75; + bModulation = cPositiveAmTV; + break; + case 'L' /* L1 */: + bVideoIF = cVideoIF_33_90; + bDeEmphasis = cDeemphasisOFF; + bDeEmphVal = cDeemphasis75; + bModulation = cPositiveAmTV; + break; + } + + } else if (t->tvnorm == VIDEO_MODE_NTSC) { + bVideoIF = cVideoIF_45_75; + bAudioIF = cAudioIF_4_5; + bDeEmphasis = cDeemphasisON; + bDeEmphVal = cDeemphasis75; + bModulation = cNegativeFmTV; + bOutPort1 = cOutputPort1Inactive; + if ((5 == t->pinnacle_id) || (6 == t->pinnacle_id)) { + bCarrierMode = cIntercarrier; + } else { + bCarrierMode = cQSS; + } + } + + bData[1] = bVideoTrap | // B0: video trap bypass + cAutoMuteFmInactive | // B1: auto mute + bCarrierMode | // B2: InterCarrier for PAL else QSS + bModulation | // B3 - B4: positive AM TV for SECAM only + cForcedMuteAudioOFF | // B5: forced Audio Mute (off) + bOutPort1 | // B6: Out Port 1 + bOutPort2; // B7: Out Port 2 + bData[2] = bTopAdjust | // C0 - C4: Top Adjust 0 == -16dB 31 == 15dB + bDeEmphasis | // C5: De-emphasis on/off + bDeEmphVal | // C6: De-emphasis 50/75 microsec + cAudioGain0; // C7: normal audio gain + bData[3] = bAudioIF | // E0 - E1: Sound IF + bVideoIF | // E2 - E4: Video IF + cTunerGainNormal | // E5: Tuner gain (normal) + cGating_18 | // E6: Gating (18%) + cAgcOutOFF; // E7: VAGC (off) + + dprintk("tda9885/6/7: 0x%02x 0x%02x 0x%02x [pinnacle_id=%d]\n", + bData[1],bData[2],bData[3],t->pinnacle_id); + if (4 != (rc = i2c_master_send(&t->client,bData,4))) + printk("tda9885/6/7: i2c i/o error: rc == %d (should be 4)\n",rc); + return 0; +} + +/* ---------------------------------------------------------------------- */ + +static unsigned char buf_pal_bg[] = { 0x00, 0xd4, 0x70, 0x09 }; +static unsigned char buf_pal_i[] = { 0x00, 0xd4, 0x70, 0x0a }; +static unsigned char buf_pal_dk[] = { 0x00, 0xd4, 0x70, 0x0b }; +static unsigned char buf_pal_l[] = { 0x00, 0xc4, 0x10, 0x0b }; +//static unsigned char buf_pal_l2[] = { 0x00, 0x84, 0x10, 0x13 }; + +static unsigned char buf_ntsc[] = { 0x00, 0x96, 0x70, 0x44 }; +static unsigned char buf_ntsc_jp[] = { 0x00, 0x96, 0x70, 0x40 }; + +//static unsigned char buf_fm_mono[] = { 0x00, 0xdc, 0x70, 0x1d }; +static unsigned char buf_fm_stereo[] = { 0x00, 0xdc, 0x10, 0x1d }; + +static int tda9887_configure(struct tda9887 *t) +{ + unsigned char *buf = NULL; + int rc; + + if (t->radio) { + dprintk("tda9885/6/7: FM Radio mode\n"); + buf = buf_fm_stereo; + + } else if (t->tvnorm == VIDEO_MODE_PAL) { + dprintk("tda9885/6/7: PAL-%c mode\n",pal[0]); + switch (pal[0]) { + case 'b': + case 'g': + buf = buf_pal_bg; + break; + case 'i': + buf = buf_pal_i; + break; + case 'd': + case 'k': + buf = buf_pal_dk; + break; + case 'l': + buf = buf_pal_l; + break; + } + + } else if (t->tvnorm == VIDEO_MODE_NTSC) { + dprintk("tda9885/6/7: NTSC mode\n"); + buf = buf_ntsc; + + } else if (t->tvnorm == VIDEO_MODE_SECAM) { + dprintk("tda9885/6/7: SECAM mode\n"); + buf = buf_pal_l; + + } else if (t->tvnorm == 6 /* BTTV hack */) { + dprintk("tda9885/6/7: NTSC-Japan mode\n"); + buf = buf_ntsc_jp; + } + + if (NULL == buf) { + printk("tda9885/6/7 unknown norm=%d\n",t->tvnorm); + return 0; + } + + dprintk("tda9885/6/7: 0x%02x 0x%02x 0x%02x\n", + buf[1],buf[2],buf[3]); + if (4 != (rc = i2c_master_send(&t->client,buf,4))) + printk("tda9885/6/7: i2c i/o error: rc == %d (should be 4)\n",rc); + return 0; +} + +/* ---------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int tda9887_attach(struct i2c_adapter *adap, int addr, int kind) +#else +static int tda9887_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +#endif +{ + struct tda9887 *t; + + client_template.adapter = adap; + client_template.addr = addr; + + printk("tda9887: chip found @ 0x%x\n", addr<<1); + + if (NULL == (t = kmalloc(sizeof(*t), GFP_KERNEL))) + return -ENOMEM; + memset(t,0,sizeof(*t)); + t->client = client_template; + t->pinnacle_id = -1; + t->tvnorm=VIDEO_MODE_PAL; + i2c_set_clientdata(&t->client, t); + i2c_attach_client(&t->client); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_INC_USE_COUNT; +#endif + return 0; +} + +static int tda9887_probe(struct i2c_adapter *adap) +{ +#ifdef I2C_ADAP_CLASS_TV_ANALOG + if (adap->class & I2C_ADAP_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, tda9887_attach); +#else + switch (adap->id) { + case I2C_ALGO_BIT | I2C_HW_B_BT848: + case I2C_ALGO_BIT | I2C_HW_B_RIVA: + case I2C_ALGO_SAA7134: + return i2c_probe(adap, &addr_data, tda9887_attach); + break; + } +#endif + return 0; +} + +static int tda9887_detach(struct i2c_client *client) +{ + struct tda9887 *t = i2c_get_clientdata(client); + + i2c_detach_client(client); + kfree(t); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_DEC_USE_COUNT; +#endif + return 0; +} + +static int +tda9887_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct tda9887 *t = i2c_get_clientdata(client); + + switch (cmd) { + + /* --- configuration --- */ + case AUDC_SET_RADIO: + t->radio = 1; + if (-1 != t->pinnacle_id) + tda9887_miro(t); + else + tda9887_configure(t); + break; + + case AUDC_CONFIG_PINNACLE: + { + int *i = arg; + + t->pinnacle_id = *i; + tda9887_miro(t); + break; + } + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCSCHAN: + { + struct video_channel *vc = arg; + + t->radio = 0; + t->tvnorm = vc->norm; + if (-1 != t->pinnacle_id) + tda9887_miro(t); + else + tda9887_configure(t); + break; + } + default: + /* nothing */ + break; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif + .name = "i2c tda9887 driver", + .id = -1, /* FIXME */ + .flags = I2C_DF_NOTIFY, + .attach_adapter = tda9887_probe, + .detach_client = tda9887_detach, + .command = tda9887_command, +}; +static struct i2c_client client_template = +{ + I2C_DEVNAME("tda9887"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + +static int tda9887_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void tda9887_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(tda9887_init_module); +module_exit(tda9887_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/tvaudio.c b/linux/drivers/media/video/tvaudio.c new file mode 100644 index 000000000..402b7b9e2 --- /dev/null +++ b/linux/drivers/media/video/tvaudio.c @@ -0,0 +1,1705 @@ +/* + * experimental driver for simple i2c audio chips. + * + * Copyright (c) 2000 Gerd Knorr + * based on code by: + * Eric Sandeen (eric_sandeen@bigfoot.com) + * Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Greg Alexander (galexand@acm.org) + * + * This code is placed under the terms of the GNU General Public License + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/init.h> +#include <linux/smp_lock.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "audiochip.h" +#include "id.h" +#include "i2c-compat.h" +#else +#include <media/audiochip.h> +#include <media/id.h> +#endif + +#include "tvaudio.h" + +/* ---------------------------------------------------------------------- */ +/* insmod args */ + +MODULE_PARM(debug,"i"); +static int debug = 0; /* insmod parameter */ + +MODULE_DESCRIPTION("device driver for various i2c TV sound decoder / audiomux chips"); +MODULE_AUTHOR("Eric Sandeen, Steve VanDeBogart, Greg Alexander, Gerd Knorr"); +MODULE_LICENSE("GPL"); + +#define UNSET (-1U) +#define dprintk if (debug) printk + +/* ---------------------------------------------------------------------- */ +/* our structs */ + +#define MAXREGS 64 + +struct CHIPSTATE; +typedef int (*getvalue)(int); +typedef int (*checkit)(struct CHIPSTATE*); +typedef int (*initialize)(struct CHIPSTATE*); +typedef int (*getmode)(struct CHIPSTATE*); +typedef void (*setmode)(struct CHIPSTATE*, int mode); +typedef void (*checkmode)(struct CHIPSTATE*); + +/* i2c command */ +typedef struct AUDIOCMD { + int count; /* # of bytes to send */ + unsigned char bytes[MAXREGS+1]; /* addr, data, data, ... */ +} audiocmd; + +/* chip description */ +struct CHIPDESC { + char *name; /* chip name */ + int id; /* ID */ + int addr_lo, addr_hi; /* i2c address range */ + int registers; /* # of registers */ + + int *insmodopt; + checkit checkit; + initialize initialize; + int flags; +#define CHIP_HAS_VOLUME 1 +#define CHIP_HAS_BASSTREBLE 2 +#define CHIP_HAS_INPUTSEL 4 + + /* various i2c command sequences */ + audiocmd init; + + /* which register has which value */ + int leftreg,rightreg,treblereg,bassreg; + + /* initialize with (defaults to 65535/65535/32768/32768 */ + int leftinit,rightinit,trebleinit,bassinit; + + /* functions to convert the values (v4l -> chip) */ + getvalue volfunc,treblefunc,bassfunc; + + /* get/set mode */ + getmode getmode; + setmode setmode; + + /* check / autoswitch audio after channel switches */ + checkmode checkmode; + + /* input switch register + values for v4l inputs */ + int inputreg; + int inputmap[8]; + int inputmute; + int inputmask; +}; +static struct CHIPDESC chiplist[]; + +/* current state of the chip */ +struct CHIPSTATE { + struct i2c_client c; + + /* index into CHIPDESC array */ + int type; + + /* shadow register set */ + audiocmd shadow; + + /* current settings */ + __u16 left,right,treble,bass,mode; + int prevmode; + int norm; + + /* thread */ + pid_t tpid; + struct completion texit; + wait_queue_head_t wq; + struct timer_list wt; + int done; + int watch_stereo; +}; + +#define VIDEO_MODE_RADIO 16 /* norm magic for radio mode */ + +/* ---------------------------------------------------------------------- */ +/* i2c addresses */ + +static unsigned short normal_i2c[] = { + I2C_TDA8425 >> 1, + I2C_TEA6300 >> 1, + I2C_TEA6420 >> 1, + I2C_TDA9840 >> 1, + I2C_TDA985x_L >> 1, + I2C_TDA985x_H >> 1, + I2C_TDA9874 >> 1, + I2C_PIC16C54 >> 1, + I2C_CLIENT_END }; +static unsigned short normal_i2c_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +I2C_CLIENT_INSMOD; + +static struct i2c_driver driver; +static struct i2c_client client_template; + + +/* ---------------------------------------------------------------------- */ +/* i2c I/O functions */ + +static int chip_write(struct CHIPSTATE *chip, int subaddr, int val) +{ + unsigned char buffer[2]; + + if (-1 == subaddr) { + dprintk("%s: chip_write: 0x%x\n", + i2c_clientname(&chip->c), val); + chip->shadow.bytes[1] = val; + buffer[0] = val; + if (1 != i2c_master_send(&chip->c,buffer,1)) { + printk(KERN_WARNING "%s: I/O error (write 0x%x)\n", + i2c_clientname(&chip->c), val); + return -1; + } + } else { + dprintk("%s: chip_write: reg%d=0x%x\n", + i2c_clientname(&chip->c), subaddr, val); + chip->shadow.bytes[subaddr+1] = val; + buffer[0] = subaddr; + buffer[1] = val; + if (2 != i2c_master_send(&chip->c,buffer,2)) { + printk(KERN_WARNING "%s: I/O error (write reg%d=0x%x)\n", + i2c_clientname(&chip->c), subaddr, val); + return -1; + } + } + return 0; +} + +static int chip_write_masked(struct CHIPSTATE *chip, int subaddr, int val, int mask) +{ + if (mask != 0) { + if (-1 == subaddr) { + val = (chip->shadow.bytes[1] & ~mask) | (val & mask); + } else { + val = (chip->shadow.bytes[subaddr+1] & ~mask) | (val & mask); + } + } + return chip_write(chip, subaddr, val); +} + +static int chip_read(struct CHIPSTATE *chip) +{ + unsigned char buffer; + + if (1 != i2c_master_recv(&chip->c,&buffer,1)) { + printk(KERN_WARNING "%s: I/O error (read)\n", + i2c_clientname(&chip->c)); + return -1; + } + dprintk("%s: chip_read: 0x%x\n",i2c_clientname(&chip->c),buffer); + return buffer; +} + +static int chip_read2(struct CHIPSTATE *chip, int subaddr) +{ + unsigned char write[1]; + unsigned char read[1]; + struct i2c_msg msgs[2] = { + { chip->c.addr, 0, 1, write }, + { chip->c.addr, I2C_M_RD, 1, read } + }; + write[0] = subaddr; + + if (2 != i2c_transfer(chip->c.adapter,msgs,2)) { + printk(KERN_WARNING "%s: I/O error (read2)\n", + i2c_clientname(&chip->c)); + return -1; + } + dprintk("%s: chip_read2: reg%d=0x%x\n", + i2c_clientname(&chip->c),subaddr,read[0]); + return read[0]; +} + +static int chip_cmd(struct CHIPSTATE *chip, char *name, audiocmd *cmd) +{ + int i; + + if (0 == cmd->count) + return 0; + + /* update our shadow register set; print bytes if (debug > 0) */ + dprintk("%s: chip_cmd(%s): reg=%d, data:", + i2c_clientname(&chip->c),name,cmd->bytes[0]); + for (i = 1; i < cmd->count; i++) { + dprintk(" 0x%x",cmd->bytes[i]); + chip->shadow.bytes[i+cmd->bytes[0]] = cmd->bytes[i]; + } + dprintk("\n"); + + /* send data to the chip */ + if (cmd->count != i2c_master_send(&chip->c,cmd->bytes,cmd->count)) { + printk(KERN_WARNING "%s: I/O error (%s)\n", i2c_clientname(&chip->c), name); + return -1; + } + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* kernel thread for doing i2c stuff asyncronly + * right now it is used only to check the audio mode (mono/stereo/whatever) + * some time after switching to another TV channel, then turn on stereo + * if available, ... + */ + +static void chip_thread_wake(unsigned long data) +{ + struct CHIPSTATE *chip = (struct CHIPSTATE*)data; + wake_up_interruptible(&chip->wq); +} + +static int chip_thread(void *data) +{ + DECLARE_WAITQUEUE(wait, current); + struct CHIPSTATE *chip = data; + struct CHIPDESC *desc = chiplist + chip->type; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,61) + lock_kernel(); + daemonize(); + sigfillset(¤t->blocked); + strcpy(current->comm,i2c_clientname(&chip->c)); + unlock_kernel(); +#else + daemonize("%s",i2c_clientname(&chip->c)); + allow_signal(SIGTERM); +#endif + dprintk("%s: thread started\n", i2c_clientname(&chip->c)); + + for (;;) { + add_wait_queue(&chip->wq, &wait); + if (!chip->done) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } + remove_wait_queue(&chip->wq, &wait); + if (chip->done || signal_pending(current)) + break; + dprintk("%s: thread wakeup\n", i2c_clientname(&chip->c)); + + /* don't do anything for radio or if mode != auto */ + if (chip->norm == VIDEO_MODE_RADIO || chip->mode != 0) + continue; + + /* have a look what's going on */ + desc->checkmode(chip); + + /* schedule next check */ + mod_timer(&chip->wt, jiffies+2*HZ); + } + + dprintk("%s: thread exiting\n", i2c_clientname(&chip->c)); + complete_and_exit(&chip->texit, 0); + return 0; +} + +static void generic_checkmode(struct CHIPSTATE *chip) +{ + struct CHIPDESC *desc = chiplist + chip->type; + int mode = desc->getmode(chip); + + if (mode == chip->prevmode) + return; + + dprintk("%s: thread checkmode\n", i2c_clientname(&chip->c)); + chip->prevmode = mode; + + if (mode & VIDEO_SOUND_STEREO) + desc->setmode(chip,VIDEO_SOUND_STEREO); + else if (mode & VIDEO_SOUND_LANG1) + desc->setmode(chip,VIDEO_SOUND_LANG1); + else if (mode & VIDEO_SOUND_LANG2) + desc->setmode(chip,VIDEO_SOUND_LANG2); + else + desc->setmode(chip,VIDEO_SOUND_MONO); +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda9840 */ + +#define TDA9840_SW 0x00 +#define TDA9840_LVADJ 0x02 +#define TDA9840_STADJ 0x03 +#define TDA9840_TEST 0x04 + +#define TDA9840_MONO 0x10 +#define TDA9840_STEREO 0x2a +#define TDA9840_DUALA 0x12 +#define TDA9840_DUALB 0x1e +#define TDA9840_DUALAB 0x1a +#define TDA9840_DUALBA 0x16 +#define TDA9840_EXTERNAL 0x7a + +#define TDA9840_DS_DUAL 0x20 /* Dual sound identified */ +#define TDA9840_ST_STEREO 0x40 /* Stereo sound identified */ +#define TDA9840_PONRES 0x80 /* Power-on reset detected if = 1 */ + +#define TDA9840_TEST_INT1SN 0x1 /* Integration time 0.5s when set */ +#define TDA9840_TEST_INTFU 0x02 /* Disables integrator function */ + +static int tda9840_getmode(struct CHIPSTATE *chip) +{ + int val, mode; + + val = chip_read(chip); + mode = VIDEO_SOUND_MONO; + if (val & TDA9840_DS_DUAL) + mode |= VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + if (val & TDA9840_ST_STEREO) + mode |= VIDEO_SOUND_STEREO; + + dprintk ("tda9840_getmode(): raw chip read: %d, return: %d\n", + val, mode); + return mode; +} + +static void tda9840_setmode(struct CHIPSTATE *chip, int mode) +{ + int update = 1; + int t = chip->shadow.bytes[TDA9840_SW + 1] & ~0x7e; + + switch (mode) { + case VIDEO_SOUND_MONO: + t |= TDA9840_MONO; + break; + case VIDEO_SOUND_STEREO: + t |= TDA9840_STEREO; + break; + case VIDEO_SOUND_LANG1: + t |= TDA9840_DUALA; + break; + case VIDEO_SOUND_LANG2: + t |= TDA9840_DUALB; + break; + default: + update = 0; + } + + if (update) + chip_write(chip, TDA9840_SW, t); +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda985x */ + +/* subaddresses for TDA9855 */ +#define TDA9855_VR 0x00 /* Volume, right */ +#define TDA9855_VL 0x01 /* Volume, left */ +#define TDA9855_BA 0x02 /* Bass */ +#define TDA9855_TR 0x03 /* Treble */ +#define TDA9855_SW 0x04 /* Subwoofer - not connected on DTV2000 */ + +/* subaddresses for TDA9850 */ +#define TDA9850_C4 0x04 /* Control 1 for TDA9850 */ + +/* subaddesses for both chips */ +#define TDA985x_C5 0x05 /* Control 2 for TDA9850, Control 1 for TDA9855 */ +#define TDA985x_C6 0x06 /* Control 3 for TDA9850, Control 2 for TDA9855 */ +#define TDA985x_C7 0x07 /* Control 4 for TDA9850, Control 3 for TDA9855 */ +#define TDA985x_A1 0x08 /* Alignment 1 for both chips */ +#define TDA985x_A2 0x09 /* Alignment 2 for both chips */ +#define TDA985x_A3 0x0a /* Alignment 3 for both chips */ + +/* Masks for bits in TDA9855 subaddresses */ +/* 0x00 - VR in TDA9855 */ +/* 0x01 - VL in TDA9855 */ +/* lower 7 bits control gain from -71dB (0x28) to 16dB (0x7f) + * in 1dB steps - mute is 0x27 */ + + +/* 0x02 - BA in TDA9855 */ +/* lower 5 bits control bass gain from -12dB (0x06) to 16.5dB (0x19) + * in .5dB steps - 0 is 0x0E */ + + +/* 0x03 - TR in TDA9855 */ +/* 4 bits << 1 control treble gain from -12dB (0x3) to 12dB (0xb) + * in 3dB steps - 0 is 0x7 */ + +/* Masks for bits in both chips' subaddresses */ +/* 0x04 - SW in TDA9855, C4/Control 1 in TDA9850 */ +/* Unique to TDA9855: */ +/* 4 bits << 2 control subwoofer/surround gain from -14db (0x1) to 14db (0xf) + * in 3dB steps - mute is 0x0 */ + +/* Unique to TDA9850: */ +/* lower 4 bits control stereo noise threshold, over which stereo turns off + * set to values of 0x00 through 0x0f for Ster1 through Ster16 */ + + +/* 0x05 - C5 - Control 1 in TDA9855 , Control 2 in TDA9850*/ +/* Unique to TDA9855: */ +#define TDA9855_MUTE 1<<7 /* GMU, Mute at outputs */ +#define TDA9855_AVL 1<<6 /* AVL, Automatic Volume Level */ +#define TDA9855_LOUD 1<<5 /* Loudness, 1==off */ +#define TDA9855_SUR 1<<3 /* Surround / Subwoofer 1==.5(L-R) 0==.5(L+R) */ + /* Bits 0 to 3 select various combinations + * of line in and line out, only the + * interesting ones are defined */ +#define TDA9855_EXT 1<<2 /* Selects inputs LIR and LIL. Pins 41 & 12 */ +#define TDA9855_INT 0 /* Selects inputs LOR and LOL. (internal) */ + +/* Unique to TDA9850: */ +/* lower 4 bits contol SAP noise threshold, over which SAP turns off + * set to values of 0x00 through 0x0f for SAP1 through SAP16 */ + + +/* 0x06 - C6 - Control 2 in TDA9855, Control 3 in TDA9850 */ +/* Common to TDA9855 and TDA9850: */ +#define TDA985x_SAP 3<<6 /* Selects SAP output, mute if not received */ +#define TDA985x_STEREO 1<<6 /* Selects Stereo ouput, mono if not received */ +#define TDA985x_MONO 0 /* Forces Mono output */ +#define TDA985x_LMU 1<<3 /* Mute (LOR/LOL for 9855, OUTL/OUTR for 9850) */ + +/* Unique to TDA9855: */ +#define TDA9855_TZCM 1<<5 /* If set, don't mute till zero crossing */ +#define TDA9855_VZCM 1<<4 /* If set, don't change volume till zero crossing*/ +#define TDA9855_LINEAR 0 /* Linear Stereo */ +#define TDA9855_PSEUDO 1 /* Pseudo Stereo */ +#define TDA9855_SPAT_30 2 /* Spatial Stereo, 30% anti-phase crosstalk */ +#define TDA9855_SPAT_50 3 /* Spatial Stereo, 52% anti-phase crosstalk */ +#define TDA9855_E_MONO 7 /* Forced mono - mono select elseware, so useless*/ + +/* 0x07 - C7 - Control 3 in TDA9855, Control 4 in TDA9850 */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 4 bits control input gain from -3.5dB (0x0) to 4dB (0xF) + * in .5dB steps - 0dB is 0x7 */ + +/* 0x08, 0x09 - A1 and A2 (read/write) */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 5 bites are wideband and spectral expander alignment + * from 0x00 to 0x1f - nominal at 0x0f and 0x10 (read/write) */ +#define TDA985x_STP 1<<5 /* Stereo Pilot/detect (read-only) */ +#define TDA985x_SAPP 1<<6 /* SAP Pilot/detect (read-only) */ +#define TDA985x_STS 1<<7 /* Stereo trigger 1= <35mV 0= <30mV (write-only)*/ + +/* 0x0a - A3 */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 3 bits control timing current for alignment: -30% (0x0), -20% (0x1), + * -10% (0x2), nominal (0x3), +10% (0x6), +20% (0x5), +30% (0x4) */ +#define TDA985x_ADJ 1<<7 /* Stereo adjust on/off (wideband and spectral */ + +static int tda9855_volume(int val) { return val/0x2e8+0x27; } +static int tda9855_bass(int val) { return val/0xccc+0x06; } +static int tda9855_treble(int val) { return (val/0x1c71+0x3)<<1; } + +static int tda985x_getmode(struct CHIPSTATE *chip) +{ + int mode; + + mode = ((TDA985x_STP | TDA985x_SAPP) & + chip_read(chip)) >> 4; + /* Add mono mode regardless of SAP and stereo */ + /* Allows forced mono */ + return mode | VIDEO_SOUND_MONO; +} + +static void tda985x_setmode(struct CHIPSTATE *chip, int mode) +{ + int update = 1; + int c6 = chip->shadow.bytes[TDA985x_C6+1] & 0x3f; + + switch (mode) { + case VIDEO_SOUND_MONO: + c6 |= TDA985x_MONO; + break; + case VIDEO_SOUND_STEREO: + c6 |= TDA985x_STEREO; + break; + case VIDEO_SOUND_LANG1: + c6 |= TDA985x_SAP; + break; + default: + update = 0; + } + if (update) + chip_write(chip,TDA985x_C6,c6); +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda9873h */ + +/* Subaddresses for TDA9873H */ + +#define TDA9873_SW 0x00 /* Switching */ +#define TDA9873_AD 0x01 /* Adjust */ +#define TDA9873_PT 0x02 /* Port */ + +/* Subaddress 0x00: Switching Data + * B7..B0: + * + * B1, B0: Input source selection + * 0, 0 internal + * 1, 0 external stereo + * 0, 1 external mono + */ +#define TDA9873_INP_MASK 3 +#define TDA9873_INTERNAL 0 +#define TDA9873_EXT_STEREO 2 +#define TDA9873_EXT_MONO 1 + +/* B3, B2: output signal select + * B4 : transmission mode + * 0, 0, 1 Mono + * 1, 0, 0 Stereo + * 1, 1, 1 Stereo (reversed channel) + * 0, 0, 0 Dual AB + * 0, 0, 1 Dual AA + * 0, 1, 0 Dual BB + * 0, 1, 1 Dual BA + */ + +#define TDA9873_TR_MASK (7 << 2) +#define TDA9873_TR_MONO 4 +#define TDA9873_TR_STEREO 1 << 4 +#define TDA9873_TR_REVERSE (1 << 3) & (1 << 2) +#define TDA9873_TR_DUALA 1 << 2 +#define TDA9873_TR_DUALB 1 << 3 + +/* output level controls + * B5: output level switch (0 = reduced gain, 1 = normal gain) + * B6: mute (1 = muted) + * B7: auto-mute (1 = auto-mute enabled) + */ + +#define TDA9873_GAIN_NORMAL 1 << 5 +#define TDA9873_MUTE 1 << 6 +#define TDA9873_AUTOMUTE 1 << 7 + +/* Subaddress 0x01: Adjust/standard */ + +/* Lower 4 bits (C3..C0) control stereo adjustment on R channel (-0.6 - +0.7 dB) + * Recommended value is +0 dB + */ + +#define TDA9873_STEREO_ADJ 0x06 /* 0dB gain */ + +/* Bits C6..C4 control FM stantard + * C6, C5, C4 + * 0, 0, 0 B/G (PAL FM) + * 0, 0, 1 M + * 0, 1, 0 D/K(1) + * 0, 1, 1 D/K(2) + * 1, 0, 0 D/K(3) + * 1, 0, 1 I + */ +#define TDA9873_BG 0 +#define TDA9873_M 1 +#define TDA9873_DK1 2 +#define TDA9873_DK2 3 +#define TDA9873_DK3 4 +#define TDA9873_I 5 + +/* C7 controls identification response time (1=fast/0=normal) + */ +#define TDA9873_IDR_NORM 0 +#define TDA9873_IDR_FAST 1 << 7 + + +/* Subaddress 0x02: Port data */ + +/* E1, E0 free programmable ports P1/P2 + 0, 0 both ports low + 0, 1 P1 high + 1, 0 P2 high + 1, 1 both ports high +*/ + +#define TDA9873_PORTS 3 + +/* E2: test port */ +#define TDA9873_TST_PORT 1 << 2 + +/* E5..E3 control mono output channel (together with transmission mode bit B4) + * + * E5 E4 E3 B4 OUTM + * 0 0 0 0 mono + * 0 0 1 0 DUAL B + * 0 1 0 1 mono (from stereo decoder) + */ +#define TDA9873_MOUT_MONO 0 +#define TDA9873_MOUT_FMONO 0 +#define TDA9873_MOUT_DUALA 0 +#define TDA9873_MOUT_DUALB 1 << 3 +#define TDA9873_MOUT_ST 1 << 4 +#define TDA9873_MOUT_EXTM (1 << 4 ) & (1 << 3) +#define TDA9873_MOUT_EXTL 1 << 5 +#define TDA9873_MOUT_EXTR (1 << 5 ) & (1 << 3) +#define TDA9873_MOUT_EXTLR (1 << 5 ) & (1 << 4) +#define TDA9873_MOUT_MUTE (1 << 5 ) & (1 << 4) & (1 << 3) + +/* Status bits: (chip read) */ +#define TDA9873_PONR 0 /* Power-on reset detected if = 1 */ +#define TDA9873_STEREO 2 /* Stereo sound is identified */ +#define TDA9873_DUAL 4 /* Dual sound is identified */ + +static int tda9873_getmode(struct CHIPSTATE *chip) +{ + int val,mode; + + val = chip_read(chip); + mode = VIDEO_SOUND_MONO; + if (val & TDA9873_STEREO) + mode |= VIDEO_SOUND_STEREO; + if (val & TDA9873_DUAL) + mode |= VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + dprintk ("tda9873_getmode(): raw chip read: %d, return: %d\n", + val, mode); + return mode; +} + +static void tda9873_setmode(struct CHIPSTATE *chip, int mode) +{ + int sw_data = chip->shadow.bytes[TDA9873_SW+1] & ~ TDA9873_TR_MASK; + /* int adj_data = chip->shadow.bytes[TDA9873_AD+1] ; */ + + if ((sw_data & TDA9873_INP_MASK) != TDA9873_INTERNAL) { + dprintk("tda9873_setmode(): external input\n"); + return; + } + + dprintk("tda9873_setmode(): chip->shadow.bytes[%d] = %d\n", TDA9873_SW+1, chip->shadow.bytes[TDA9873_SW+1]); + dprintk("tda9873_setmode(): sw_data = %d\n", sw_data); + + switch (mode) { + case VIDEO_SOUND_MONO: + sw_data |= TDA9873_TR_MONO; + break; + case VIDEO_SOUND_STEREO: + sw_data |= TDA9873_TR_STEREO; + break; + case VIDEO_SOUND_LANG1: + sw_data |= TDA9873_TR_DUALA; + break; + case VIDEO_SOUND_LANG2: + sw_data |= TDA9873_TR_DUALB; + break; + default: + chip->mode = 0; + return; + } + + chip_write(chip, TDA9873_SW, sw_data); + dprintk("tda9873_setmode(): req. mode %d; chip_write: %d\n", + mode, sw_data); +} + +static int tda9873_checkit(struct CHIPSTATE *chip) +{ + int rc; + + if (-1 == (rc = chip_read2(chip,254))) + return 0; + return (rc & ~0x1f) == 0x80; +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip description - defines+functions for tda9874h and tda9874a */ +/* Dariusz Kowalewski <darekk@automex.pl> */ + +/* Subaddresses for TDA9874H and TDA9874A (slave rx) */ +#define TDA9874A_AGCGR 0x00 /* AGC gain */ +#define TDA9874A_GCONR 0x01 /* general config */ +#define TDA9874A_MSR 0x02 /* monitor select */ +#define TDA9874A_C1FRA 0x03 /* carrier 1 freq. */ +#define TDA9874A_C1FRB 0x04 /* carrier 1 freq. */ +#define TDA9874A_C1FRC 0x05 /* carrier 1 freq. */ +#define TDA9874A_C2FRA 0x06 /* carrier 2 freq. */ +#define TDA9874A_C2FRB 0x07 /* carrier 2 freq. */ +#define TDA9874A_C2FRC 0x08 /* carrier 2 freq. */ +#define TDA9874A_DCR 0x09 /* demodulator config */ +#define TDA9874A_FMER 0x0a /* FM de-emphasis */ +#define TDA9874A_FMMR 0x0b /* FM dematrix */ +#define TDA9874A_C1OLAR 0x0c /* ch.1 output level adj. */ +#define TDA9874A_C2OLAR 0x0d /* ch.2 output level adj. */ +#define TDA9874A_NCONR 0x0e /* NICAM config */ +#define TDA9874A_NOLAR 0x0f /* NICAM output level adj. */ +#define TDA9874A_NLELR 0x10 /* NICAM lower error limit */ +#define TDA9874A_NUELR 0x11 /* NICAM upper error limit */ +#define TDA9874A_AMCONR 0x12 /* audio mute control */ +#define TDA9874A_SDACOSR 0x13 /* stereo DAC output select */ +#define TDA9874A_AOSR 0x14 /* analog output select */ +#define TDA9874A_DAICONR 0x15 /* digital audio interface config */ +#define TDA9874A_I2SOSR 0x16 /* I2S-bus output select */ +#define TDA9874A_I2SOLAR 0x17 /* I2S-bus output level adj. */ +#define TDA9874A_MDACOSR 0x18 /* mono DAC output select (tda9874a) */ +#define TDA9874A_ESP 0xFF /* easy standard progr. (tda9874a) */ + +/* Subaddresses for TDA9874H and TDA9874A (slave tx) */ +#define TDA9874A_DSR 0x00 /* device status */ +#define TDA9874A_NSR 0x01 /* NICAM status */ +#define TDA9874A_NECR 0x02 /* NICAM error count */ +#define TDA9874A_DR1 0x03 /* add. data LSB */ +#define TDA9874A_DR2 0x04 /* add. data MSB */ +#define TDA9874A_LLRA 0x05 /* monitor level read-out LSB */ +#define TDA9874A_LLRB 0x06 /* monitor level read-out MSB */ +#define TDA9874A_SIFLR 0x07 /* SIF level */ +#define TDA9874A_TR2 252 /* test reg. 2 */ +#define TDA9874A_TR1 253 /* test reg. 1 */ +#define TDA9874A_DIC 254 /* device id. code */ +#define TDA9874A_SIC 255 /* software id. code */ + + +static int tda9874a_mode = 1; /* 0: A2, 1: NICAM */ +static int tda9874a_GCONR = 0xc0; /* default config. input pin: SIFSEL=0 */ +static int tda9874a_NCONR = 0x01; /* default NICAM config.: AMSEL=0,AMUTE=1 */ +static int tda9874a_ESP = 0x07; /* default standard: NICAM D/K */ +static int tda9874a_dic = -1; /* device id. code */ + +/* insmod options for tda9874a */ +static unsigned int tda9874a_SIF = UNSET; +static unsigned int tda9874a_AMSEL = UNSET; +static unsigned int tda9874a_STD = UNSET; +MODULE_PARM(tda9874a_SIF,"i"); +MODULE_PARM(tda9874a_AMSEL,"i"); +MODULE_PARM(tda9874a_STD,"i"); + +/* + * initialization table for tda9874 decoder: + * - carrier 1 freq. registers (3 bytes) + * - carrier 2 freq. registers (3 bytes) + * - demudulator config register + * - FM de-emphasis register (slow identification mode) + * Note: frequency registers must be written in single i2c transfer. + */ +static struct tda9874a_MODES { + char *name; + audiocmd cmd; +} tda9874a_modelist[9] = { + { "A2, B/G", + { 9, { TDA9874A_C1FRA, 0x72,0x95,0x55, 0x77,0xA0,0x00, 0x00,0x00 }} }, + { "A2, M (Korea)", + { 9, { TDA9874A_C1FRA, 0x5D,0xC0,0x00, 0x62,0x6A,0xAA, 0x20,0x22 }} }, + { "A2, D/K (1)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x82,0x60,0x00, 0x00,0x00 }} }, + { "A2, D/K (2)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x8C,0x75,0x55, 0x00,0x00 }} }, + { "A2, D/K (3)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x77,0xA0,0x00, 0x00,0x00 }} }, + { "NICAM, I", + { 9, { TDA9874A_C1FRA, 0x7D,0x00,0x00, 0x88,0x8A,0xAA, 0x08,0x33 }} }, + { "NICAM, B/G", + { 9, { TDA9874A_C1FRA, 0x72,0x95,0x55, 0x79,0xEA,0xAA, 0x08,0x33 }} }, + { "NICAM, D/K", /* default */ + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x79,0xEA,0xAA, 0x08,0x33 }} }, + { "NICAM, L", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x79,0xEA,0xAA, 0x09,0x33 }} } +}; + +static int tda9874a_setup(struct CHIPSTATE *chip) +{ + chip_write(chip, TDA9874A_AGCGR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_GCONR, tda9874a_GCONR); + chip_write(chip, TDA9874A_MSR, (tda9874a_mode) ? 0x03:0x02); + if(tda9874a_dic == 0x11) { + chip_write(chip, TDA9874A_FMMR, 0x80); + } else { /* dic == 0x07 */ + chip_cmd(chip,"tda9874_modelist",&tda9874a_modelist[tda9874a_STD].cmd); + chip_write(chip, TDA9874A_FMMR, 0x00); + } + chip_write(chip, TDA9874A_C1OLAR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_C2OLAR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_NCONR, tda9874a_NCONR); + chip_write(chip, TDA9874A_NOLAR, 0x00); /* 0 dB */ + /* Note: If signal quality is poor you may want to change NICAM */ + /* error limit registers (NLELR and NUELR) to some greater values. */ + /* Then the sound would remain stereo, but won't be so clear. */ + chip_write(chip, TDA9874A_NLELR, 0x14); /* default */ + chip_write(chip, TDA9874A_NUELR, 0x50); /* default */ + + if(tda9874a_dic == 0x11) { + chip_write(chip, TDA9874A_AMCONR, 0xf9); + chip_write(chip, TDA9874A_SDACOSR, (tda9874a_mode) ? 0x81:0x80); + chip_write(chip, TDA9874A_AOSR, 0x80); + chip_write(chip, TDA9874A_MDACOSR, (tda9874a_mode) ? 0x82:0x80); + chip_write(chip, TDA9874A_ESP, tda9874a_ESP); + } else { /* dic == 0x07 */ + chip_write(chip, TDA9874A_AMCONR, 0xfb); + chip_write(chip, TDA9874A_SDACOSR, (tda9874a_mode) ? 0x81:0x80); + chip_write(chip, TDA9874A_AOSR, 0x00); // or 0x10 + } + dprintk("tda9874a_setup(): %s [0x%02X].\n", + tda9874a_modelist[tda9874a_STD].name,tda9874a_STD); + return 1; +} + +static int tda9874a_getmode(struct CHIPSTATE *chip) +{ + int dsr,nsr,mode; + int necr; /* just for debugging */ + + mode = VIDEO_SOUND_MONO; + + if(-1 == (dsr = chip_read2(chip,TDA9874A_DSR))) + return mode; + if(-1 == (nsr = chip_read2(chip,TDA9874A_NSR))) + return mode; + if(-1 == (necr = chip_read2(chip,TDA9874A_NECR))) + return mode; + + /* need to store dsr/nsr somewhere */ + chip->shadow.bytes[MAXREGS-2] = dsr; + chip->shadow.bytes[MAXREGS-1] = nsr; + + if(tda9874a_mode) { + /* Note: DSR.RSSF and DSR.AMSTAT bits are also checked. + * If NICAM auto-muting is enabled, DSR.AMSTAT=1 indicates + * that sound has (temporarily) switched from NICAM to + * mono FM (or AM) on 1st sound carrier due to high NICAM bit + * error count. So in fact there is no stereo in this case :-( + * But changing the mode to VIDEO_SOUND_MONO would switch + * external 4052 multiplexer in audio_hook(). + */ +#if 0 + if((nsr & 0x02) && !(dsr & 0x10)) /* NSR.S/MB=1 and DSR.AMSTAT=0 */ + mode |= VIDEO_SOUND_STEREO; +#else + if(nsr & 0x02) /* NSR.S/MB=1 */ + mode |= VIDEO_SOUND_STEREO; +#endif + if(nsr & 0x01) /* NSR.D/SB=1 */ + mode |= VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } else { + if(dsr & 0x02) /* DSR.IDSTE=1 */ + mode |= VIDEO_SOUND_STEREO; + if(dsr & 0x04) /* DSR.IDDUA=1 */ + mode |= VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } + + dprintk("tda9874a_getmode(): DSR=0x%X, NSR=0x%X, NECR=0x%X, return: %d.\n", + dsr, nsr, necr, mode); + return mode; +} + +static void tda9874a_setmode(struct CHIPSTATE *chip, int mode) +{ + /* Disable/enable NICAM auto-muting (based on DSR.RSSF status bit). */ + /* If auto-muting is disabled, we can hear a signal of degrading quality. */ + if(tda9874a_mode) { + if(chip->shadow.bytes[MAXREGS-2] & 0x20) /* DSR.RSSF=1 */ + tda9874a_NCONR &= 0xfe; /* enable */ + else + tda9874a_NCONR |= 0x01; /* disable */ + chip_write(chip, TDA9874A_NCONR, tda9874a_NCONR); + } + + /* Note: TDA9874A supports automatic FM dematrixing (FMMR register) + * and has auto-select function for audio output (AOSR register). + * Old TDA9874H doesn't support these features. + * TDA9874A also has additional mono output pin (OUTM), which + * on same (all?) tv-cards is not used, anyway (as well as MONOIN). + */ + if(tda9874a_dic == 0x11) { + int aosr = 0x80; + int mdacosr = (tda9874a_mode) ? 0x82:0x80; + + switch(mode) { + case VIDEO_SOUND_MONO: + case VIDEO_SOUND_STEREO: + break; + case VIDEO_SOUND_LANG1: + aosr = 0x80; /* auto-select, dual A/A */ + mdacosr = (tda9874a_mode) ? 0x82:0x80; + break; + case VIDEO_SOUND_LANG2: + aosr = 0xa0; /* auto-select, dual B/B */ + mdacosr = (tda9874a_mode) ? 0x83:0x81; + break; + default: + chip->mode = 0; + return; + } + chip_write(chip, TDA9874A_AOSR, aosr); + chip_write(chip, TDA9874A_MDACOSR, mdacosr); + + dprintk("tda9874a_setmode(): req. mode %d; AOSR=0x%X, MDACOSR=0x%X.\n", + mode, aosr, mdacosr); + + } else { /* dic == 0x07 */ + int fmmr,aosr; + + switch(mode) { + case VIDEO_SOUND_MONO: + fmmr = 0x00; /* mono */ + aosr = 0x10; /* A/A */ + break; + case VIDEO_SOUND_STEREO: + if(tda9874a_mode) { + fmmr = 0x00; + aosr = 0x00; /* handled by NICAM auto-mute */ + } else { + fmmr = (tda9874a_ESP == 1) ? 0x05 : 0x04; /* stereo */ + aosr = 0x00; + } + break; + case VIDEO_SOUND_LANG1: + fmmr = 0x02; /* dual */ + aosr = 0x10; /* dual A/A */ + break; + case VIDEO_SOUND_LANG2: + fmmr = 0x02; /* dual */ + aosr = 0x20; /* dual B/B */ + break; + default: + chip->mode = 0; + return; + } + chip_write(chip, TDA9874A_FMMR, fmmr); + chip_write(chip, TDA9874A_AOSR, aosr); + + dprintk("tda9874a_setmode(): req. mode %d; FMMR=0x%X, AOSR=0x%X.\n", + mode, fmmr, aosr); + } +} + +static int tda9874a_checkit(struct CHIPSTATE *chip) +{ + int dic,sic; /* device id. and software id. codes */ + + if(-1 == (dic = chip_read2(chip,TDA9874A_DIC))) + return 0; + if(-1 == (sic = chip_read2(chip,TDA9874A_SIC))) + return 0; + + dprintk("tda9874a_checkit(): DIC=0x%X, SIC=0x%X.\n", dic, sic); + + if((dic == 0x11)||(dic == 0x07)) { + printk("tvaudio: found tda9874%s.\n", (dic == 0x11) ? "a":"h"); + tda9874a_dic = dic; /* remember device id. */ + return 1; + } + return 0; /* not found */ +} + +static int tda9874a_initialize(struct CHIPSTATE *chip) +{ + if (tda9874a_SIF > 2) + tda9874a_SIF = 1; + if (tda9874a_STD >= 8) + tda9874a_STD = 0; + if(tda9874a_AMSEL > 1) + tda9874a_AMSEL = 0; + + if(tda9874a_SIF == 1) + tda9874a_GCONR = 0xc0; /* sound IF input 1 */ + else + tda9874a_GCONR = 0xc1; /* sound IF input 2 */ + + tda9874a_ESP = tda9874a_STD; + tda9874a_mode = (tda9874a_STD < 5) ? 0 : 1; + + if(tda9874a_AMSEL == 0) + tda9874a_NCONR = 0x01; /* auto-mute: analog mono input */ + else + tda9874a_NCONR = 0x05; /* auto-mute: 1st carrier FM or AM */ + + tda9874a_setup(chip); + return 0; +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tea6420 */ + +#define TEA6300_VL 0x00 /* volume left */ +#define TEA6300_VR 0x01 /* volume right */ +#define TEA6300_BA 0x02 /* bass */ +#define TEA6300_TR 0x03 /* treble */ +#define TEA6300_FA 0x04 /* fader control */ +#define TEA6300_S 0x05 /* switch register */ + /* values for those registers: */ +#define TEA6300_S_SA 0x01 /* stereo A input */ +#define TEA6300_S_SB 0x02 /* stereo B */ +#define TEA6300_S_SC 0x04 /* stereo C */ +#define TEA6300_S_GMU 0x80 /* general mute */ + +#define TEA6420_S_SA 0x00 /* stereo A input */ +#define TEA6420_S_SB 0x01 /* stereo B */ +#define TEA6420_S_SC 0x02 /* stereo C */ +#define TEA6420_S_SD 0x03 /* stereo D */ +#define TEA6420_S_SE 0x04 /* stereo E */ +#define TEA6420_S_GMU 0x05 /* general mute */ + +static int tea6300_shift10(int val) { return val >> 10; } +static int tea6300_shift12(int val) { return val >> 12; } + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda8425 */ + +#define TDA8425_VL 0x00 /* volume left */ +#define TDA8425_VR 0x01 /* volume right */ +#define TDA8425_BA 0x02 /* bass */ +#define TDA8425_TR 0x03 /* treble */ +#define TDA8425_S1 0x08 /* switch functions */ + /* values for those registers: */ +#define TDA8425_S1_OFF 0xEE /* audio off (mute on) */ +#define TDA8425_S1_CH1 0xCE /* audio channel 1 (mute off) - "linear stereo" mode */ +#define TDA8425_S1_CH2 0xCF /* audio channel 2 (mute off) - "linear stereo" mode */ +#define TDA8425_S1_MU 0x20 /* mute bit */ +#define TDA8425_S1_STEREO 0x18 /* stereo bits */ +#define TDA8425_S1_STEREO_SPATIAL 0x18 /* spatial stereo */ +#define TDA8425_S1_STEREO_LINEAR 0x08 /* linear stereo */ +#define TDA8425_S1_STEREO_PSEUDO 0x10 /* pseudo stereo */ +#define TDA8425_S1_STEREO_MONO 0x00 /* forced mono */ +#define TDA8425_S1_ML 0x06 /* language selector */ +#define TDA8425_S1_ML_SOUND_A 0x02 /* sound a */ +#define TDA8425_S1_ML_SOUND_B 0x04 /* sound b */ +#define TDA8425_S1_ML_STEREO 0x06 /* stereo */ +#define TDA8425_S1_IS 0x01 /* channel selector */ + + +static int tda8425_shift10(int val) { return (val >> 10) | 0xc0; } +static int tda8425_shift12(int val) { return (val >> 12) | 0xf0; } + +static int tda8425_initialize(struct CHIPSTATE *chip) +{ + struct CHIPDESC *desc = chiplist + chip->type; + int inputmap[8] = { /* tuner */ TDA8425_S1_CH2, /* radio */ TDA8425_S1_CH1, + /* extern */ TDA8425_S1_CH1, /* intern */ TDA8425_S1_OFF, + /* off */ TDA8425_S1_OFF, /* on */ TDA8425_S1_CH2}; + + if (chip->c.adapter->id == (I2C_ALGO_BIT | I2C_HW_B_RIVA)) { + memcpy (desc->inputmap, inputmap, sizeof (inputmap)); + } + return 0; +} + +static void tda8425_setmode(struct CHIPSTATE *chip, int mode) +{ + int s1 = chip->shadow.bytes[TDA8425_S1+1] & 0xe1; + + if (mode & VIDEO_SOUND_LANG1) { + s1 |= TDA8425_S1_ML_SOUND_A; + s1 |= TDA8425_S1_STEREO_PSEUDO; + + } else if (mode & VIDEO_SOUND_LANG2) { + s1 |= TDA8425_S1_ML_SOUND_B; + s1 |= TDA8425_S1_STEREO_PSEUDO; + + } else { + s1 |= TDA8425_S1_ML_STEREO; + + if (mode & VIDEO_SOUND_MONO) + s1 |= TDA8425_S1_STEREO_MONO; + if (mode & VIDEO_SOUND_STEREO) + s1 |= TDA8425_S1_STEREO_SPATIAL; + } + chip_write(chip,TDA8425_S1,s1); +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for pic16c54 (PV951) */ + +/* the registers of 16C54, I2C sub address. */ +#define PIC16C54_REG_KEY_CODE 0x01 /* Not use. */ +#define PIC16C54_REG_MISC 0x02 + +/* bit definition of the RESET register, I2C data. */ +#define PIC16C54_MISC_RESET_REMOTE_CTL 0x01 /* bit 0, Reset to receive the key */ + /* code of remote controller */ +#define PIC16C54_MISC_MTS_MAIN 0x02 /* bit 1 */ +#define PIC16C54_MISC_MTS_SAP 0x04 /* bit 2 */ +#define PIC16C54_MISC_MTS_BOTH 0x08 /* bit 3 */ +#define PIC16C54_MISC_SND_MUTE 0x10 /* bit 4, Mute Audio(Line-in and Tuner) */ +#define PIC16C54_MISC_SND_NOTMUTE 0x20 /* bit 5 */ +#define PIC16C54_MISC_SWITCH_TUNER 0x40 /* bit 6 , Switch to Line-in */ +#define PIC16C54_MISC_SWITCH_LINE 0x80 /* bit 7 , Switch to Tuner */ + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for TA8874Z */ + +// write 1st byte +#define TA8874Z_LED_STE 0x80 +#define TA8874Z_LED_BIL 0x40 +#define TA8874Z_LED_EXT 0x20 +#define TA8874Z_MONO_SET 0x10 +#define TA8874Z_MUTE 0x08 +#define TA8874Z_F_MONO 0x04 +#define TA8874Z_MODE_SUB 0x02 +#define TA8874Z_MODE_MAIN 0x01 + +// write 2nd byte +//#define TA8874Z_TI 0x80 // test mode +#define TA8874Z_SEPARATION 0x3f +#define TA8874Z_SEPARATION_DEFAULT 0x10 + +// read +#define TA8874Z_B1 0x80 +#define TA8874Z_B0 0x40 +#define TA8874Z_CHAG_FLAG 0x20 + +// B1 B0 +// mono L H +// stereo L L +// BIL H L + +static int ta8874z_getmode(struct CHIPSTATE *chip) +{ + int val, mode; + + val = chip_read(chip); + mode = VIDEO_SOUND_MONO; + if (val & TA8874Z_B1){ + mode |= VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + }else if (!(val & TA8874Z_B0)){ + mode |= VIDEO_SOUND_STEREO; + } + //dprintk ("ta8874z_getmode(): raw chip read: 0x%02x, return: 0x%02x\n", val, mode); + return mode; +} + +static audiocmd ta8874z_stereo = { 2, {0, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_mono = {2, { TA8874Z_MONO_SET, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_main = {2, { 0, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_sub = {2, { TA8874Z_MODE_SUB, TA8874Z_SEPARATION_DEFAULT}}; + +static void ta8874z_setmode(struct CHIPSTATE *chip, int mode) +{ + int update = 1; + audiocmd *t = NULL; + dprintk("ta8874z_setmode(): mode: 0x%02x\n", mode); + + switch(mode){ + case VIDEO_SOUND_MONO: + t = &ta8874z_mono; + break; + case VIDEO_SOUND_STEREO: + t = &ta8874z_stereo; + break; + case VIDEO_SOUND_LANG1: + t = &ta8874z_main; + break; + case VIDEO_SOUND_LANG2: + t = &ta8874z_sub; + break; + default: + update = 0; + } + + if(update) + chip_cmd(chip, "TA8874Z", t); +} + +static int ta8874z_checkit(struct CHIPSTATE *chip) +{ + int rc; + rc = chip_read(chip); + return ((rc & 0x1f) == 0x1f) ? 1 : 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - struct CHIPDESC */ + +/* insmod options to enable/disable individual audio chips */ +int tda8425 = 1; +int tda9840 = 1; +int tda9850 = 1; +int tda9855 = 1; +int tda9873 = 1; +int tda9874a = 1; +int tea6300 = 0; // address clash with msp34xx +int tea6420 = 1; +int pic16c54 = 1; +int ta8874z = 0; // address clash with tda9840 + +MODULE_PARM(tda8425,"i"); +MODULE_PARM(tda9840,"i"); +MODULE_PARM(tda9850,"i"); +MODULE_PARM(tda9855,"i"); +MODULE_PARM(tda9873,"i"); +MODULE_PARM(tda9874a,"i"); +MODULE_PARM(tea6300,"i"); +MODULE_PARM(tea6420,"i"); +MODULE_PARM(pic16c54,"i"); +MODULE_PARM(ta8874z,"i"); + +static struct CHIPDESC chiplist[] = { + { + .name = "tda9840", + .id = I2C_DRIVERID_TDA9840, + .insmodopt = &tda9840, + .addr_lo = I2C_TDA9840 >> 1, + .addr_hi = I2C_TDA9840 >> 1, + .registers = 5, + + .getmode = tda9840_getmode, + .setmode = tda9840_setmode, + .checkmode = generic_checkmode, + + .init = { 2, { TDA9840_TEST, TDA9840_TEST_INT1SN + /* ,TDA9840_SW, TDA9840_MONO */} } + }, + { + .name = "tda9873h", + .id = I2C_DRIVERID_TDA9873, + .checkit = tda9873_checkit, + .insmodopt = &tda9873, + .addr_lo = I2C_TDA985x_L >> 1, + .addr_hi = I2C_TDA985x_H >> 1, + .registers = 3, + .flags = CHIP_HAS_INPUTSEL, + + .getmode = tda9873_getmode, + .setmode = tda9873_setmode, + .checkmode = generic_checkmode, + + .init = { 4, { TDA9873_SW, 0xa4, 0x06, 0x03 } }, + .inputreg = TDA9873_SW, + .inputmute = TDA9873_MUTE | TDA9873_AUTOMUTE, + .inputmap = {0xa0, 0xa2, 0xa0, 0xa0, 0xc0}, + .inputmask = TDA9873_INP_MASK|TDA9873_MUTE|TDA9873_AUTOMUTE, + + }, + { + .name = "tda9874h/a", + .id = I2C_DRIVERID_TDA9874, + .checkit = tda9874a_checkit, + .initialize = tda9874a_initialize, + .insmodopt = &tda9874a, + .addr_lo = I2C_TDA9874 >> 1, + .addr_hi = I2C_TDA9874 >> 1, + + .getmode = tda9874a_getmode, + .setmode = tda9874a_setmode, + .checkmode = generic_checkmode, + }, + { + .name = "tda9850", + .id = I2C_DRIVERID_TDA9850, + .insmodopt = &tda9850, + .addr_lo = I2C_TDA985x_L >> 1, + .addr_hi = I2C_TDA985x_H >> 1, + .registers = 11, + + .getmode = tda985x_getmode, + .setmode = tda985x_setmode, + + .init = { 8, { TDA9850_C4, 0x08, 0x08, TDA985x_STEREO, 0x07, 0x10, 0x10, 0x03 } } + }, + { + .name = "tda9855", + .id = I2C_DRIVERID_TDA9855, + .insmodopt = &tda9855, + .addr_lo = I2C_TDA985x_L >> 1, + .addr_hi = I2C_TDA985x_H >> 1, + .registers = 11, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE, + + .leftreg = TDA9855_VL, + .rightreg = TDA9855_VR, + .bassreg = TDA9855_BA, + .treblereg = TDA9855_TR, + .volfunc = tda9855_volume, + .bassfunc = tda9855_bass, + .treblefunc = tda9855_treble, + + .getmode = tda985x_getmode, + .setmode = tda985x_setmode, + + .init = { 12, { 0, 0x6f, 0x6f, 0x0e, 0x07<<1, 0x8<<2, + TDA9855_MUTE | TDA9855_AVL | TDA9855_LOUD | TDA9855_INT, + TDA985x_STEREO | TDA9855_LINEAR | TDA9855_TZCM | TDA9855_VZCM, + 0x07, 0x10, 0x10, 0x03 }} + }, + { + .name = "tea6300", + .id = I2C_DRIVERID_TEA6300, + .insmodopt = &tea6300, + .addr_lo = I2C_TEA6300 >> 1, + .addr_hi = I2C_TEA6300 >> 1, + .registers = 6, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE | CHIP_HAS_INPUTSEL, + + .leftreg = TEA6300_VR, + .rightreg = TEA6300_VL, + .bassreg = TEA6300_BA, + .treblereg = TEA6300_TR, + .volfunc = tea6300_shift10, + .bassfunc = tea6300_shift12, + .treblefunc = tea6300_shift12, + + .inputreg = TEA6300_S, + .inputmap = { TEA6300_S_SA, TEA6300_S_SB, TEA6300_S_SC }, + .inputmute = TEA6300_S_GMU, + }, + { + .name = "tea6420", + .id = I2C_DRIVERID_TEA6420, + .insmodopt = &tea6420, + .addr_lo = I2C_TEA6420 >> 1, + .addr_hi = I2C_TEA6420 >> 1, + .registers = 1, + .flags = CHIP_HAS_INPUTSEL, + + .inputreg = -1, + .inputmap = { TEA6420_S_SA, TEA6420_S_SB, TEA6420_S_SC }, + .inputmute = TEA6300_S_GMU, + }, + { + .name = "tda8425", + .id = I2C_DRIVERID_TDA8425, + .insmodopt = &tda8425, + .addr_lo = I2C_TDA8425 >> 1, + .addr_hi = I2C_TDA8425 >> 1, + .registers = 9, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE | CHIP_HAS_INPUTSEL, + + .leftreg = TDA8425_VL, + .rightreg = TDA8425_VR, + .bassreg = TDA8425_BA, + .treblereg = TDA8425_TR, + .volfunc = tda8425_shift10, + .bassfunc = tda8425_shift12, + .treblefunc = tda8425_shift12, + + .inputreg = TDA8425_S1, + .inputmap = { TDA8425_S1_CH1, TDA8425_S1_CH1, TDA8425_S1_CH1 }, + .inputmute = TDA8425_S1_OFF, + + .setmode = tda8425_setmode, + .initialize = tda8425_initialize, + }, + { + .name = "pic16c54 (PV951)", + .id = I2C_DRIVERID_PIC16C54_PV951, + .insmodopt = &pic16c54, + .addr_lo = I2C_PIC16C54 >> 1, + .addr_hi = I2C_PIC16C54>> 1, + .registers = 2, + .flags = CHIP_HAS_INPUTSEL, + + .inputreg = PIC16C54_REG_MISC, + .inputmap = {PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_TUNER, + PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_LINE, + PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_LINE, + PIC16C54_MISC_SND_MUTE,PIC16C54_MISC_SND_MUTE, + PIC16C54_MISC_SND_NOTMUTE}, + .inputmute = PIC16C54_MISC_SND_MUTE, + }, + { + .name = "ta8874z", + .id = -1, + //.id = I2C_DRIVERID_TA8874Z, + .checkit = ta8874z_checkit, + .insmodopt = &ta8874z, + .addr_lo = I2C_TDA9840 >> 1, + .addr_hi = I2C_TDA9840 >> 1, + .registers = 2, + + .getmode = ta8874z_getmode, + .setmode = ta8874z_setmode, + .checkmode = generic_checkmode, + + .init = {2, { TA8874Z_MONO_SET, TA8874Z_SEPARATION_DEFAULT}}, + }, + { .name = NULL } /* EOF */ +}; + + +/* ---------------------------------------------------------------------- */ +/* i2c registration */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int chip_attach(struct i2c_adapter *adap, int addr, int kind) +#else +static int chip_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +#endif +{ + struct CHIPSTATE *chip; + struct CHIPDESC *desc; + + chip = kmalloc(sizeof(*chip),GFP_KERNEL); + if (!chip) + return -ENOMEM; + memset(chip,0,sizeof(*chip)); + memcpy(&chip->c,&client_template,sizeof(struct i2c_client)); + chip->c.adapter = adap; + chip->c.addr = addr; + i2c_set_clientdata(&chip->c, chip); + + /* find description for the chip */ + dprintk("tvaudio: chip found @ i2c-addr=0x%x\n", addr<<1); + for (desc = chiplist; desc->name != NULL; desc++) { + if (0 == *(desc->insmodopt)) + continue; + if (addr < desc->addr_lo || + addr > desc->addr_hi) + continue; + if (desc->checkit && !desc->checkit(chip)) + continue; + break; + } + if (desc->name == NULL) { + dprintk("tvaudio: no matching chip description found\n"); + return -EIO; + } + printk("tvaudio: found %s @ 0x%x\n", desc->name, addr<<1); + dprintk("tvaudio: matches:%s%s%s.\n", + (desc->flags & CHIP_HAS_VOLUME) ? " volume" : "", + (desc->flags & CHIP_HAS_BASSTREBLE) ? " bass/treble" : "", + (desc->flags & CHIP_HAS_INPUTSEL) ? " audiomux" : ""); + + /* fill required data structures */ + strcpy(i2c_clientname(&chip->c),desc->name); + chip->type = desc-chiplist; + chip->shadow.count = desc->registers+1; + chip->prevmode = -1; + /* register */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_INC_USE_COUNT; +#endif + i2c_attach_client(&chip->c); + + /* initialization */ + if (desc->initialize != NULL) + desc->initialize(chip); + else + chip_cmd(chip,"init",&desc->init); + + if (desc->flags & CHIP_HAS_VOLUME) { + chip->left = desc->leftinit ? desc->leftinit : 65535; + chip->right = desc->rightinit ? desc->rightinit : 65535; + chip_write(chip,desc->leftreg,desc->volfunc(chip->left)); + chip_write(chip,desc->rightreg,desc->volfunc(chip->right)); + } + if (desc->flags & CHIP_HAS_BASSTREBLE) { + chip->treble = desc->trebleinit ? desc->trebleinit : 32768; + chip->bass = desc->bassinit ? desc->bassinit : 32768; + chip_write(chip,desc->bassreg,desc->bassfunc(chip->bass)); + chip_write(chip,desc->treblereg,desc->treblefunc(chip->treble)); + } + + chip->tpid = -1; + if (desc->checkmode) { + /* start async thread */ + init_timer(&chip->wt); + chip->wt.function = chip_thread_wake; + chip->wt.data = (unsigned long)chip; + init_waitqueue_head(&chip->wq); + init_completion(&chip->texit); + chip->tpid = kernel_thread(chip_thread,(void *)chip,0); + if (chip->tpid < 0) + printk(KERN_WARNING "%s: kernel_thread() failed\n", + i2c_clientname(&chip->c)); + wake_up_interruptible(&chip->wq); + } + return 0; +} + +static int chip_probe(struct i2c_adapter *adap) +{ +#ifdef I2C_ADAP_CLASS_TV_ANALOG + if (adap->class & I2C_ADAP_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, chip_attach); +#else + switch (adap->id) { + case I2C_ALGO_BIT | I2C_HW_B_BT848: + case I2C_ALGO_BIT | I2C_HW_B_RIVA: + case I2C_ALGO_SAA7134: + return i2c_probe(adap, &addr_data, chip_attach); + } +#endif + return 0; +} + +static int chip_detach(struct i2c_client *client) +{ + struct CHIPSTATE *chip = i2c_get_clientdata(client); + + del_timer_sync(&chip->wt); + if (chip->tpid >= 0) { + /* shutdown async thread */ + chip->done = 1; + wake_up_interruptible(&chip->wq); + wait_for_completion(&chip->texit); + } + + i2c_detach_client(&chip->c); + kfree(chip); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + MOD_DEC_USE_COUNT; +#endif + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* video4linux interface */ + +static int chip_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + __u16 *sarg = arg; + struct CHIPSTATE *chip = i2c_get_clientdata(client); + struct CHIPDESC *desc = chiplist + chip->type; + + dprintk("%s: chip_command 0x%x\n",i2c_clientname(&chip->c),cmd); + + switch (cmd) { + case AUDC_SET_INPUT: + if (desc->flags & CHIP_HAS_INPUTSEL) { + if (*sarg & 0x80) + chip_write_masked(chip,desc->inputreg,desc->inputmute,desc->inputmask); + else + chip_write_masked(chip,desc->inputreg,desc->inputmap[*sarg],desc->inputmask); + } + break; + + case AUDC_SET_RADIO: + dprintk(KERN_DEBUG "tvaudio: AUDC_SET_RADIO\n"); + chip->norm = VIDEO_MODE_RADIO; + chip->watch_stereo = 0; + /* del_timer(&chip->wt); */ + break; + + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + + if (desc->flags & CHIP_HAS_VOLUME) { + va->flags |= VIDEO_AUDIO_VOLUME; + va->volume = max(chip->left,chip->right); + va->balance = (32768*min(chip->left,chip->right))/ + (va->volume ? va->volume : 1); + } + if (desc->flags & CHIP_HAS_BASSTREBLE) { + va->flags |= VIDEO_AUDIO_BASS | VIDEO_AUDIO_TREBLE; + va->bass = chip->bass; + va->treble = chip->treble; + } + if (chip->norm != VIDEO_MODE_RADIO) { + if (desc->getmode) + va->mode = desc->getmode(chip); + else + va->mode = VIDEO_SOUND_MONO; + } + break; + } + + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + + if (desc->flags & CHIP_HAS_VOLUME) { + chip->left = (min(65536 - va->balance,32768) * + va->volume) / 32768; + chip->right = (min(va->balance,(__u16)32768) * + va->volume) / 32768; + chip_write(chip,desc->leftreg,desc->volfunc(chip->left)); + chip_write(chip,desc->rightreg,desc->volfunc(chip->right)); + } + if (desc->flags & CHIP_HAS_BASSTREBLE) { + chip->bass = va->bass; + chip->treble = va->treble; + chip_write(chip,desc->bassreg,desc->bassfunc(chip->bass)); + chip_write(chip,desc->treblereg,desc->treblefunc(chip->treble)); + } + if (desc->setmode && va->mode) { + chip->watch_stereo = 0; + /* del_timer(&chip->wt); */ + chip->mode = va->mode; + desc->setmode(chip,va->mode); + } + break; + } + case VIDIOCSCHAN: + { + struct video_channel *vc = arg; + + dprintk(KERN_DEBUG "tvaudio: VIDIOCSCHAN\n"); + chip->norm = vc->norm; + break; + } + case VIDIOCSFREQ: + { + chip->mode = 0; /* automatic */ + if (desc->checkmode) { + desc->setmode(chip,VIDEO_SOUND_MONO); + if (chip->prevmode != VIDEO_SOUND_MONO) + chip->prevmode = -1; /* reset previous mode */ + mod_timer(&chip->wt, jiffies+2*HZ); + /* the thread will call checkmode() later */ + } + } + } + return 0; +} + + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif + .name = "generic i2c audio driver", + .id = I2C_DRIVERID_TVAUDIO, + .flags = I2C_DF_NOTIFY, + .attach_adapter = chip_probe, + .detach_client = chip_detach, + .command = chip_command, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("(unset)"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + +static int audiochip_init_module(void) +{ + struct CHIPDESC *desc; + printk(KERN_INFO "tvaudio: TV audio decoder + audio/video mux driver\n"); + printk(KERN_INFO "tvaudio: known chips: "); + for (desc = chiplist; desc->name != NULL; desc++) + printk("%s%s", (desc == chiplist) ? "" : ",",desc->name); + printk("\n"); + i2c_add_driver(&driver); + return 0; +} + +static void audiochip_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(audiochip_init_module); +module_exit(audiochip_cleanup_module); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/tvaudio.h b/linux/drivers/media/video/tvaudio.h new file mode 100644 index 000000000..ba29c35f6 --- /dev/null +++ b/linux/drivers/media/video/tvaudio.h @@ -0,0 +1,14 @@ +/* + * i2c bus addresses for the chips supported by tvaudio.c + */ + +#define I2C_TDA8425 0x82 +#define I2C_TDA9840 0x84 /* also used by TA8874Z */ +#define I2C_TDA985x_L 0xb4 /* also used by 9873 */ +#define I2C_TDA985x_H 0xb6 +#define I2C_TDA9874 0xb0 /* also used by 9875 */ + +#define I2C_TEA6300 0x80 +#define I2C_TEA6420 0x98 + +#define I2C_PIC16C54 0x96 /* PV951 */ diff --git a/linux/drivers/media/video/tvmixer.c b/linux/drivers/media/video/tvmixer.c new file mode 100644 index 000000000..7468aaf5c --- /dev/null +++ b/linux/drivers/media/video/tvmixer.c @@ -0,0 +1,387 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <linux/init.h> +#include <linux/kdev_t.h> +#include <linux/sound.h> +#include <linux/soundcard.h> + +#include <asm/semaphore.h> +#include <asm/uaccess.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +# include "i2c-compat.h" +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,71) +# define strlcpy(dest,src,len) strncpy(dest,src,(len)-1) +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +# define iminor(inode) minor(inode->i_rdev) +#endif + +#define DEV_MAX 4 + +static int devnr = -1; +MODULE_PARM(devnr,"i"); + +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("GPL"); + +/* ----------------------------------------------------------------------- */ + +struct TVMIXER { + struct i2c_client *dev; + int minor; + int count; +}; + +static struct TVMIXER devices[DEV_MAX]; + +static int tvmixer_adapters(struct i2c_adapter *adap); +static int tvmixer_clients(struct i2c_client *client); + +/* ----------------------------------------------------------------------- */ + +static int mix_to_v4l(int i) +{ + int r; + + r = ((i & 0xff) * 65536 + 50) / 100; + if (r > 65535) r = 65535; + if (r < 0) r = 0; + return r; +} + +static int v4l_to_mix(int i) +{ + int r; + + r = (i * 100 + 32768) / 65536; + if (r > 100) r = 100; + if (r < 0) r = 0; + return r | (r << 8); +} + +static int v4l_to_mix2(int l, int r) +{ + r = (r * 100 + 32768) / 65536; + if (r > 100) r = 100; + if (r < 0) r = 0; + l = (l * 100 + 32768) / 65536; + if (l > 100) l = 100; + if (l < 0) l = 0; + return (r << 8) | l; +} + +static int tvmixer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct video_audio va; + int left,right,ret,val = 0; + struct TVMIXER *mix = file->private_data; + struct i2c_client *client = mix->dev; + + if (NULL == client) + return -ENODEV; + + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + strlcpy(info.id, "tv card", sizeof(info.id)); + strlcpy(info.name, i2c_clientname(client), sizeof(info.name)); + info.modify_counter = 42 /* FIXME */; + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == SOUND_OLD_MIXER_INFO) { + _old_mixer_info info; + strlcpy(info.id, "tv card", sizeof(info.id)); + strlcpy(info.name, i2c_clientname(client), sizeof(info.name)); + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, (int *)arg); + + if (_SIOC_DIR(cmd) & _SIOC_WRITE) + if (get_user(val, (int *)arg)) + return -EFAULT; + + /* read state */ + memset(&va,0,sizeof(va)); + client->driver->command(client,VIDIOCGAUDIO,&va); + + switch (cmd) { + case MIXER_READ(SOUND_MIXER_RECMASK): + case MIXER_READ(SOUND_MIXER_CAPS): + case MIXER_READ(SOUND_MIXER_RECSRC): + case MIXER_WRITE(SOUND_MIXER_RECSRC): + ret = 0; + break; + + case MIXER_READ(SOUND_MIXER_STEREODEVS): + ret = SOUND_MASK_VOLUME; + break; + case MIXER_READ(SOUND_MIXER_DEVMASK): + ret = SOUND_MASK_VOLUME; + if (va.flags & VIDEO_AUDIO_BASS) + ret |= SOUND_MASK_BASS; + if (va.flags & VIDEO_AUDIO_TREBLE) + ret |= SOUND_MASK_TREBLE; + break; + + case MIXER_WRITE(SOUND_MIXER_VOLUME): + left = mix_to_v4l(val); + right = mix_to_v4l(val >> 8); + va.volume = max(left,right); + va.balance = (32768*min(left,right)) / (va.volume ? va.volume : 1); + va.balance = (left<right) ? (65535-va.balance) : va.balance; + if (va.volume) + va.flags &= ~VIDEO_AUDIO_MUTE; + client->driver->command(client,VIDIOCSAUDIO,&va); + client->driver->command(client,VIDIOCGAUDIO,&va); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_VOLUME): + left = (min(65536 - va.balance,32768) * + va.volume) / 32768; + right = (min(va.balance,(u16)32768) * + va.volume) / 32768; + ret = v4l_to_mix2(left,right); + break; + + case MIXER_WRITE(SOUND_MIXER_BASS): + va.bass = mix_to_v4l(val); + client->driver->command(client,VIDIOCSAUDIO,&va); + client->driver->command(client,VIDIOCGAUDIO,&va); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_BASS): + ret = v4l_to_mix(va.bass); + break; + + case MIXER_WRITE(SOUND_MIXER_TREBLE): + va.treble = mix_to_v4l(val); + client->driver->command(client,VIDIOCSAUDIO,&va); + client->driver->command(client,VIDIOCGAUDIO,&va); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_TREBLE): + ret = v4l_to_mix(va.treble); + break; + + default: + return -EINVAL; + } + if (put_user(ret, (int *)arg)) + return -EFAULT; + return 0; +} + +static int tvmixer_open(struct inode *inode, struct file *file) +{ + int i, minor = iminor(inode); + struct TVMIXER *mix = NULL; + struct i2c_client *client = NULL; + + for (i = 0; i < DEV_MAX; i++) { + if (devices[i].minor == minor) { + mix = devices+i; + client = mix->dev; + break; + } + } + + if (NULL == client) + return -ENODEV; + + /* lock bttv in memory while the mixer is in use */ + file->private_data = mix; +#ifndef I2C_PEC + if (client->adapter->inc_use) + client->adapter->inc_use(client->adapter); +#endif +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,54) + if (client->adapter->owner) + try_module_get(client->adapter->owner); +#endif + return 0; +} + +static int tvmixer_release(struct inode *inode, struct file *file) +{ + struct TVMIXER *mix = file->private_data; + struct i2c_client *client; + + client = mix->dev; + if (NULL == client) { + return -ENODEV; + } + +#ifndef I2C_PEC + if (client->adapter->dec_use) + client->adapter->dec_use(client->adapter); +#endif +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,54) + if (client->adapter->owner) + module_put(client->adapter->owner); +#endif + return 0; +} + +static struct i2c_driver driver = { +#ifdef I2C_PEC + .owner = THIS_MODULE, +#endif + .name = "tv card mixer driver", + .id = I2C_DRIVERID_TVMIXER, +#ifdef I2C_DF_DUMMY + .flags = I2C_DF_DUMMY, +#else + .flags = I2C_DF_NOTIFY, + .detach_adapter = tvmixer_adapters, +#endif + .attach_adapter = tvmixer_adapters, + .detach_client = tvmixer_clients, +}; + +static struct file_operations tvmixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = tvmixer_ioctl, + .open = tvmixer_open, + .release = tvmixer_release, +}; + +/* ----------------------------------------------------------------------- */ + +static int tvmixer_adapters(struct i2c_adapter *adap) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,54) + struct list_head *item; + struct i2c_client *client; + + list_for_each(item,&adap->clients) { + client = list_entry(item, struct i2c_client, list); + tvmixer_clients(client); + } +#else + int i; + + for (i=0; i<I2C_CLIENT_MAX; i++) { + if (!adap->clients[i]) + continue; + tvmixer_clients(adap->clients[i]); + } +#endif + return 0; +} + +static int tvmixer_clients(struct i2c_client *client) +{ + struct video_audio va; + int i,minor; + +#ifdef I2C_ADAP_CLASS_TV_ANALOG + if (!(client->adapter->class & I2C_ADAP_CLASS_TV_ANALOG)) + return -1; +#else + /* TV card ??? */ + switch (client->adapter->id) { + case I2C_ALGO_BIT | I2C_HW_SMBUS_VOODOO3: + case I2C_ALGO_BIT | I2C_HW_B_BT848: + case I2C_ALGO_BIT | I2C_HW_B_RIVA: + /* ok, have a look ... */ + break; + default: + /* ignore that one */ + return -1; + } +#endif + + /* unregister ?? */ + for (i = 0; i < DEV_MAX; i++) { + if (devices[i].dev == client) { + /* unregister */ + unregister_sound_mixer(devices[i].minor); + devices[i].dev = NULL; + devices[i].minor = -1; + printk("tvmixer: %s unregistered (#1)\n", + i2c_clientname(client)); + return 0; + } + } + + /* look for a free slot */ + for (i = 0; i < DEV_MAX; i++) + if (NULL == devices[i].dev) + break; + if (i == DEV_MAX) { + printk(KERN_WARNING "tvmixer: DEV_MAX too small\n"); + return -1; + } + + /* audio chip with mixer ??? */ + if (NULL == client->driver->command) + return -1; + memset(&va,0,sizeof(va)); + if (0 != client->driver->command(client,VIDIOCGAUDIO,&va)) + return -1; + if (0 == (va.flags & VIDEO_AUDIO_VOLUME)) + return -1; + + /* everything is fine, register */ + if ((minor = register_sound_mixer(&tvmixer_fops,devnr)) < 0) { + printk(KERN_ERR "tvmixer: cannot allocate mixer device\n"); + return -1; + } + + devices[i].minor = minor; + devices[i].count = 0; + devices[i].dev = client; + printk("tvmixer: %s (%s) registered with minor %d\n", + client->name,client->adapter->name,minor); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int tvmixer_init_module(void) +{ + int i; + + for (i = 0; i < DEV_MAX; i++) + devices[i].minor = -1; + i2c_add_driver(&driver); + return 0; +} + +static void tvmixer_cleanup_module(void) +{ + int i; + + i2c_del_driver(&driver); + for (i = 0; i < DEV_MAX; i++) { + if (devices[i].minor != -1) { + unregister_sound_mixer(devices[i].minor); + printk("tvmixer: %s unregistered (#2)\n", + i2c_clientname(devices[i].dev)); + } + } +} + +module_init(tvmixer_init_module); +module_exit(tvmixer_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/v4l1-compat.c b/linux/drivers/media/video/v4l1-compat.c new file mode 100644 index 000000000..c2c2e91a5 --- /dev/null +++ b/linux/drivers/media/video/v4l1-compat.c @@ -0,0 +1,1041 @@ +/* + * Video for Linux Two + * Backward Compatibility Layer + * + * Support subroutines for providing V4L2 drivers with backward + * compatibility with applications using the old API. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Author: Bill Dirks <bdirks@pacbell.net> + * et al. + * + */ + +#ifndef __KERNEL__ +#define __KERNEL__ +#endif + +#include <linux/config.h> + +#include <linux/version.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/videodev.h> + +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/pgtable.h> + +#ifdef CONFIG_KMOD +#include <linux/kmod.h> +#endif + +static unsigned int debug = 0; +MODULE_PARM(debug,"i"); +MODULE_PARM_DESC(debug,"enable debug messages"); +MODULE_AUTHOR("Bill Dirks"); +MODULE_DESCRIPTION("v4l(1) compatibility layer for v4l2 drivers."); +MODULE_LICENSE("GPL"); + +#define dprintk(fmt, arg...) if (debug) \ + printk(KERN_DEBUG "v4l1-compat: " fmt , ## arg) + +/* + * I O C T L T R A N S L A T I O N + * + * From here on down is the code for translating the numerous + * ioctl commands from the old API to the new API. + */ + +static int +get_v4l_control(struct inode *inode, + struct file *file, + int cid, + v4l2_kioctl drv) +{ + struct v4l2_queryctrl qctrl2; + struct v4l2_control ctrl2; + int err; + + qctrl2.id = cid; + err = drv(inode, file, VIDIOC_QUERYCTRL, &qctrl2); + if (err < 0) + dprintk("VIDIOC_QUERYCTRL: %d\n",err); + if (err == 0 && + !(qctrl2.flags & V4L2_CTRL_FLAG_DISABLED)) + { + ctrl2.id = qctrl2.id; + err = drv(inode, file, VIDIOC_G_CTRL, &ctrl2); + if (err < 0) { + dprintk("VIDIOC_G_CTRL: %d\n",err); + return 0; + } + return ((ctrl2.value - qctrl2.minimum) * 65535 + + (qctrl2.maximum - qctrl2.minimum) / 2) + / (qctrl2.maximum - qctrl2.minimum); + } + return 0; +} + +static int +set_v4l_control(struct inode *inode, + struct file *file, + int cid, + int value, + v4l2_kioctl drv) +{ + struct v4l2_queryctrl qctrl2; + struct v4l2_control ctrl2; + int err; + + qctrl2.id = cid; + err = drv(inode, file, VIDIOC_QUERYCTRL, &qctrl2); + if (err < 0) + dprintk("VIDIOC_QUERYCTRL: %d\n",err); + if (err == 0 && + !(qctrl2.flags & V4L2_CTRL_FLAG_DISABLED) && + !(qctrl2.flags & V4L2_CTRL_FLAG_GRABBED)) + { + if (value < 0) + value = 0; + if (value > 65535) + value = 65535; + if (value && qctrl2.type == V4L2_CTRL_TYPE_BOOLEAN) + value = 65535; + ctrl2.id = qctrl2.id; + ctrl2.value = + (value * (qctrl2.maximum - qctrl2.minimum) + + 32767) + / 65535; + ctrl2.value += qctrl2.minimum; + err = drv(inode, file, VIDIOC_S_CTRL, &ctrl2); + if (err < 0) + dprintk("VIDIOC_S_CTRL: %d\n",err); + } + return 0; +} + +/* ----------------------------------------------------------------- */ + +static int palette2pixelformat[] = { + [VIDEO_PALETTE_GREY] = V4L2_PIX_FMT_GREY, + [VIDEO_PALETTE_RGB555] = V4L2_PIX_FMT_RGB555, + [VIDEO_PALETTE_RGB565] = V4L2_PIX_FMT_RGB565, + [VIDEO_PALETTE_RGB24] = V4L2_PIX_FMT_BGR24, + [VIDEO_PALETTE_RGB32] = V4L2_PIX_FMT_BGR32, + /* yuv packed pixel */ + [VIDEO_PALETTE_YUYV] = V4L2_PIX_FMT_YUYV, + [VIDEO_PALETTE_YUV422] = V4L2_PIX_FMT_YUYV, + [VIDEO_PALETTE_UYVY] = V4L2_PIX_FMT_UYVY, + /* yuv planar */ + [VIDEO_PALETTE_YUV410P] = V4L2_PIX_FMT_YUV410, + [VIDEO_PALETTE_YUV420] = V4L2_PIX_FMT_YUV420, + [VIDEO_PALETTE_YUV420P] = V4L2_PIX_FMT_YUV420, + [VIDEO_PALETTE_YUV411P] = V4L2_PIX_FMT_YUV411P, + [VIDEO_PALETTE_YUV422P] = V4L2_PIX_FMT_YUV422P, +}; + +static unsigned int +palette_to_pixelformat(unsigned int palette) +{ + if (palette < ARRAY_SIZE(palette2pixelformat)) + return palette2pixelformat[palette]; + else + return 0; +} + +static unsigned int +pixelformat_to_palette(int pixelformat) +{ + int palette = 0; + switch (pixelformat) + { + case V4L2_PIX_FMT_GREY: + palette = VIDEO_PALETTE_GREY; + break; + case V4L2_PIX_FMT_RGB555: + palette = VIDEO_PALETTE_RGB555; + break; + case V4L2_PIX_FMT_RGB565: + palette = VIDEO_PALETTE_RGB565; + break; + case V4L2_PIX_FMT_BGR24: + palette = VIDEO_PALETTE_RGB24; + break; + case V4L2_PIX_FMT_BGR32: + palette = VIDEO_PALETTE_RGB32; + break; + /* yuv packed pixel */ + case V4L2_PIX_FMT_YUYV: + palette = VIDEO_PALETTE_YUYV; + break; + case V4L2_PIX_FMT_UYVY: + palette = VIDEO_PALETTE_UYVY; + break; + /* yuv planar */ + case V4L2_PIX_FMT_YUV410: + palette = VIDEO_PALETTE_YUV420; + break; + case V4L2_PIX_FMT_YUV420: + palette = VIDEO_PALETTE_YUV420; + break; + case V4L2_PIX_FMT_YUV411P: + palette = VIDEO_PALETTE_YUV411P; + break; + case V4L2_PIX_FMT_YUV422P: + palette = VIDEO_PALETTE_YUV422P; + break; + } + return palette; +} + +/* ----------------------------------------------------------------- */ + +static int poll_one(struct file *file) +{ + int retval = 1; + poll_table *table; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,48) + poll_table wait_table; + + poll_initwait(&wait_table); + table = &wait_table; +#else + struct poll_wqueues pwq; + + poll_initwait(&pwq); + table = &pwq.pt; +#endif + for (;;) { + int mask; + set_current_state(TASK_INTERRUPTIBLE); + mask = file->f_op->poll(file, table); + if (mask & POLLIN) + break; + table = NULL; + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + schedule(); + } + set_current_state(TASK_RUNNING); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,48) + poll_freewait(&wait_table); +#else + poll_freewait(&pwq); +#endif + return retval; +} + +static int count_inputs(struct inode *inode, + struct file *file, + v4l2_kioctl drv) +{ + struct v4l2_input input2; + int i; + + for (i = 0;; i++) { + memset(&input2,0,sizeof(input2)); + input2.index = i; + if (0 != drv(inode,file,VIDIOC_ENUMINPUT, &input2)) + break; + } + return i; +} + +static int check_size(struct inode *inode, + struct file *file, + v4l2_kioctl drv, + int *maxw, int *maxh) +{ + struct v4l2_fmtdesc desc2; + struct v4l2_format fmt2; + + memset(&desc2,0,sizeof(desc2)); + memset(&fmt2,0,sizeof(fmt2)); + + desc2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (0 != drv(inode,file,VIDIOC_ENUM_FMT, &desc2)) + goto done; + + fmt2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt2.fmt.pix.width = 10000; + fmt2.fmt.pix.height = 10000; + fmt2.fmt.pix.pixelformat = desc2.pixelformat; + if (0 != drv(inode,file,VIDIOC_TRY_FMT, &fmt2)) + goto done; + + *maxw = fmt2.fmt.pix.width; + *maxh = fmt2.fmt.pix.height; + + done: + return 0; +} + +/* ----------------------------------------------------------------- */ + +/* + * This function is exported. + */ +int +v4l_compat_translate_ioctl(struct inode *inode, + struct file *file, + int cmd, + void *arg, + v4l2_kioctl drv) +{ + struct v4l2_capability *cap2 = NULL; + struct v4l2_format *fmt2 = NULL; + + struct v4l2_framebuffer fbuf2; + struct v4l2_input input2; + struct v4l2_tuner tun2; + struct v4l2_standard std2; + struct v4l2_frequency freq2; + struct v4l2_audio aud2; + struct v4l2_queryctrl qctrl2; + struct v4l2_buffer buf2; + v4l2_std_id sid; + int i, err = 0; + + switch (cmd) { + case VIDIOCGCAP: /* capability */ + { + struct video_capability *cap = arg; + + cap2 = kmalloc(sizeof(*cap2),GFP_KERNEL); + memset(cap, 0, sizeof(*cap)); + memset(cap2, 0, sizeof(*cap2)); + memset(&fbuf2, 0, sizeof(fbuf2)); + + err = drv(inode, file, VIDIOC_QUERYCAP, cap2); + if (err < 0) { + dprintk("VIDIOCGCAP / VIDIOC_QUERYCAP: %d\n",err); + break; + } + if (cap2->capabilities & V4L2_CAP_VIDEO_OVERLAY) { + err = drv(inode, file, VIDIOC_G_FBUF, &fbuf2); + if (err < 0) { + dprintk("VIDIOCGCAP / VIDIOC_G_FBUF: %d\n",err); + memset(&fbuf2, 0, sizeof(fbuf2)); + } + err = 0; + } + + memcpy(cap->name, cap2->card, + min(sizeof(cap->name), sizeof(cap2->card))); + cap->name[sizeof(cap->name) - 1] = 0; + if (cap2->capabilities & V4L2_CAP_VIDEO_CAPTURE) + cap->type |= VID_TYPE_CAPTURE; + if (cap2->capabilities & V4L2_CAP_TUNER) + cap->type |= VID_TYPE_TUNER; + if (cap2->capabilities & V4L2_CAP_VBI_CAPTURE) + cap->type |= VID_TYPE_TELETEXT; + if (cap2->capabilities & V4L2_CAP_VIDEO_OVERLAY) + cap->type |= VID_TYPE_OVERLAY; + if (fbuf2.capability & V4L2_FBUF_CAP_LIST_CLIPPING) + cap->type |= VID_TYPE_CLIPPING; + + cap->channels = count_inputs(inode,file,drv); + check_size(inode,file,drv, + &cap->maxwidth,&cap->maxheight); + cap->audios = 0; /* FIXME */ + cap->minwidth = 48; /* FIXME */ + cap->minheight = 32; /* FIXME */ + break; + } + case VIDIOCGFBUF: /* get frame buffer */ + { + struct video_buffer *buffer = arg; + + err = drv(inode, file, VIDIOC_G_FBUF, &fbuf2); + if (err < 0) { + dprintk("VIDIOCGFBUF / VIDIOC_G_FBUF: %d\n",err); + break; + } + buffer->base = fbuf2.base; + buffer->height = fbuf2.fmt.height; + buffer->width = fbuf2.fmt.width; + + switch (fbuf2.fmt.pixelformat) { + case V4L2_PIX_FMT_RGB332: + buffer->depth = 8; + break; + case V4L2_PIX_FMT_RGB555: + buffer->depth = 15; + break; + case V4L2_PIX_FMT_RGB565: + buffer->depth = 16; + break; + case V4L2_PIX_FMT_BGR24: + buffer->depth = 24; + break; + case V4L2_PIX_FMT_BGR32: + buffer->depth = 32; + break; + default: + buffer->depth = 0; + } + if (0 != fbuf2.fmt.bytesperline) + buffer->bytesperline = fbuf2.fmt.bytesperline; + else { + buffer->bytesperline = + (buffer->width * buffer->depth + 7) & 7; + buffer->bytesperline >>= 3; + } + break; + } + case VIDIOCSFBUF: /* set frame buffer */ + { + struct video_buffer *buffer = arg; + + memset(&fbuf2, 0, sizeof(fbuf2)); + fbuf2.base = buffer->base; + fbuf2.fmt.height = buffer->height; + fbuf2.fmt.width = buffer->width; + switch (buffer->depth) { + case 8: + fbuf2.fmt.pixelformat = V4L2_PIX_FMT_RGB332; + break; + case 15: + fbuf2.fmt.pixelformat = V4L2_PIX_FMT_RGB555; + break; + case 16: + fbuf2.fmt.pixelformat = V4L2_PIX_FMT_RGB565; + break; + case 24: + fbuf2.fmt.pixelformat = V4L2_PIX_FMT_BGR24; + break; + case 32: + fbuf2.fmt.pixelformat = V4L2_PIX_FMT_BGR32; + break; + } + fbuf2.fmt.bytesperline = buffer->bytesperline; + err = drv(inode, file, VIDIOC_S_FBUF, &fbuf2); + if (err < 0) + dprintk("VIDIOCSFBUF / VIDIOC_S_FBUF: %d\n",err); + break; + } + case VIDIOCGWIN: /* get window or capture dimensions */ + { + struct video_window *win = arg; + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(win,0,sizeof(*win)); + memset(fmt2,0,sizeof(*fmt2)); + + fmt2->type = V4L2_BUF_TYPE_VIDEO_OVERLAY; + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) + dprintk("VIDIOCGWIN / VIDIOC_G_WIN: %d\n",err); + if (err == 0) { + win->x = fmt2->fmt.win.w.left; + win->y = fmt2->fmt.win.w.top; + win->width = fmt2->fmt.win.w.width; + win->height = fmt2->fmt.win.w.height; + win->chromakey = fmt2->fmt.win.chromakey; + win->clips = NULL; + win->clipcount = 0; + break; + } + + fmt2->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCGWIN / VIDIOC_G_FMT: %d\n",err); + break; + } + win->x = 0; + win->y = 0; + win->width = fmt2->fmt.pix.width; + win->height = fmt2->fmt.pix.height; + win->chromakey = 0; + win->clips = NULL; + win->clipcount = 0; + break; + } + case VIDIOCSWIN: /* set window and/or capture dimensions */ + { + struct video_window *win = arg; + int err1,err2; + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(fmt2,0,sizeof(*fmt2)); + fmt2->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err1 = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err1 < 0) + dprintk("VIDIOCSWIN / VIDIOC_G_FMT: %d\n",err); + if (err1 == 0) { + fmt2->fmt.pix.width = win->width; + fmt2->fmt.pix.height = win->height; + fmt2->fmt.pix.field = V4L2_FIELD_ANY; + fmt2->fmt.pix.bytesperline = 0; + err = drv(inode, file, VIDIOC_S_FMT, fmt2); + if (err < 0) + dprintk("VIDIOCSWIN / VIDIOC_S_FMT #1: %d\n", + err); + win->width = fmt2->fmt.pix.width; + win->height = fmt2->fmt.pix.height; + } + + memset(fmt2,0,sizeof(*fmt2)); + fmt2->type = V4L2_BUF_TYPE_VIDEO_OVERLAY; + fmt2->fmt.win.w.left = win->x; + fmt2->fmt.win.w.top = win->y; + fmt2->fmt.win.w.width = win->width; + fmt2->fmt.win.w.height = win->height; + fmt2->fmt.win.chromakey = win->chromakey; + fmt2->fmt.win.clips = (void *)win->clips; + fmt2->fmt.win.clipcount = win->clipcount; + err2 = drv(inode, file, VIDIOC_S_FMT, fmt2); + if (err2 < 0) + dprintk("VIDIOCSWIN / VIDIOC_S_FMT #2: %d\n",err); + + if (err1 != 0 && err2 != 0) + err = err1; + break; + } + case VIDIOCCAPTURE: /* turn on/off preview */ + { + int *on = arg; + + if (0 == *on) { + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + /* dirty hack time. But v4l1 has no STREAMOFF + * equivalent in the API, and this one at + * least comes close ... */ + drv(inode, file, VIDIOC_STREAMOFF, &type); + } + err = drv(inode, file, VIDIOC_OVERLAY, arg); + if (err < 0) + dprintk("VIDIOCCAPTURE / VIDIOC_PREVIEW: %d\n",err); + break; + } + case VIDIOCGCHAN: /* get input information */ + { + struct video_channel *chan = arg; + + memset(&input2,0,sizeof(input2)); + input2.index = chan->channel; + err = drv(inode, file, VIDIOC_ENUMINPUT, &input2); + if (err < 0) { + dprintk("VIDIOCGCHAN / VIDIOC_ENUMINPUT: " + "channel=%d err=%d\n",chan->channel,err); + break; + } + chan->channel = input2.index; + memcpy(chan->name, input2.name, + min(sizeof(chan->name), sizeof(input2.name))); + chan->name[sizeof(chan->name) - 1] = 0; + chan->tuners = (input2.type == V4L2_INPUT_TYPE_TUNER) ? 1 : 0; + chan->flags = (chan->tuners) ? VIDEO_VC_TUNER : 0; + switch (input2.type) { + case V4L2_INPUT_TYPE_TUNER: + chan->type = VIDEO_TYPE_TV; + break; + default: + case V4L2_INPUT_TYPE_CAMERA: + chan->type = VIDEO_TYPE_CAMERA; + break; + } + chan->norm = 0; + err = drv(inode, file, VIDIOC_G_STD, &sid); + if (err < 0) + dprintk("VIDIOCGCHAN / VIDIOC_G_STD: %d\n",err); + if (err == 0) { + if (sid & V4L2_STD_PAL) + chan->norm = VIDEO_MODE_PAL; + if (sid & V4L2_STD_NTSC) + chan->norm = VIDEO_MODE_NTSC; + if (sid & V4L2_STD_SECAM) + chan->norm = VIDEO_MODE_SECAM; + } + break; + } + case VIDIOCSCHAN: /* set input */ + { + struct video_channel *chan = arg; + + sid = 0; + err = drv(inode, file, VIDIOC_S_INPUT, &chan->channel); + if (err < 0) + dprintk("VIDIOCSCHAN / VIDIOC_S_INPUT: %d\n",err); + switch (chan->norm) { + case VIDEO_MODE_PAL: + sid = V4L2_STD_PAL; + break; + case VIDEO_MODE_NTSC: + sid = V4L2_STD_NTSC; + break; + case VIDEO_MODE_SECAM: + sid = V4L2_STD_SECAM; + break; + } + if (0 != sid) { + err = drv(inode, file, VIDIOC_S_STD, &sid); + if (err < 0) + dprintk("VIDIOCSCHAN / VIDIOC_S_STD: %d\n",err); + } + break; + } + case VIDIOCGPICT: /* get tone controls & partial capture format */ + { + struct video_picture *pict = arg; + + pict->brightness = get_v4l_control(inode, file, + V4L2_CID_BRIGHTNESS,drv); + pict->hue = get_v4l_control(inode, file, + V4L2_CID_HUE, drv); + pict->contrast = get_v4l_control(inode, file, + V4L2_CID_CONTRAST, drv); + pict->colour = get_v4l_control(inode, file, + V4L2_CID_SATURATION, drv); + pict->whiteness = get_v4l_control(inode, file, + V4L2_CID_WHITENESS, drv); + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(fmt2,0,sizeof(*fmt2)); + fmt2->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCGPICT / VIDIOC_G_FMT: %d\n",err); + break; + } +#if 0 /* FIXME */ + pict->depth = fmt2->fmt.pix.depth; +#endif + pict->palette = pixelformat_to_palette( + fmt2->fmt.pix.pixelformat); + break; + } + case VIDIOCSPICT: /* set tone controls & partial capture format */ + { + struct video_picture *pict = arg; + + set_v4l_control(inode, file, + V4L2_CID_BRIGHTNESS, pict->brightness, drv); + set_v4l_control(inode, file, + V4L2_CID_HUE, pict->hue, drv); + set_v4l_control(inode, file, + V4L2_CID_CONTRAST, pict->contrast, drv); + set_v4l_control(inode, file, + V4L2_CID_SATURATION, pict->colour, drv); + set_v4l_control(inode, file, + V4L2_CID_WHITENESS, pict->whiteness, drv); + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(fmt2,0,sizeof(*fmt2)); + fmt2->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) + dprintk("VIDIOCSPICT / VIDIOC_G_FMT: %d\n",err); + if (fmt2->fmt.pix.pixelformat != + palette_to_pixelformat(pict->palette)) { + fmt2->fmt.pix.pixelformat = palette_to_pixelformat( + pict->palette); + err = drv(inode, file, VIDIOC_S_FMT, fmt2); + if (err < 0) + dprintk("VIDIOCSPICT / VIDIOC_S_FMT: %d\n",err); + } + + err = drv(inode, file, VIDIOC_G_FBUF, &fbuf2); + if (err < 0) + dprintk("VIDIOCSPICT / VIDIOC_G_FBUF: %d\n",err); + if (fbuf2.fmt.pixelformat != + palette_to_pixelformat(pict->palette)) { + fbuf2.fmt.pixelformat = palette_to_pixelformat( + pict->palette); + err = drv(inode, file, VIDIOC_S_FBUF, &fbuf2); + if (err < 0) + dprintk("VIDIOCSPICT / VIDIOC_S_FBUF: %d\n",err); + err = 0; /* likely fails for non-root */ + } + break; + } + case VIDIOCGTUNER: /* get tuner information */ + { + struct video_tuner *tun = arg; + + memset(&tun2,0,sizeof(tun2)); + err = drv(inode, file, VIDIOC_G_TUNER, &tun2); + if (err < 0) { + dprintk("VIDIOCGTUNER / VIDIOC_G_TUNER: %d\n",err); + break; + } + memcpy(tun->name, tun2.name, + min(sizeof(tun->name), sizeof(tun2.name))); + tun->name[sizeof(tun->name) - 1] = 0; + tun->rangelow = tun2.rangelow; + tun->rangehigh = tun2.rangehigh; + tun->flags = 0; + tun->mode = VIDEO_MODE_AUTO; + + for (i = 0; i < 64; i++) { + memset(&std2,0,sizeof(std2)); + std2.index = i; + if (0 != drv(inode, file, VIDIOC_ENUMSTD, &std2)) + break; + if (std2.id & V4L2_STD_PAL) + tun->flags |= VIDEO_TUNER_PAL; + if (std2.id & V4L2_STD_NTSC) + tun->flags |= VIDEO_TUNER_NTSC; + if (std2.id & V4L2_STD_SECAM) + tun->flags |= VIDEO_TUNER_SECAM; + } + + err = drv(inode, file, VIDIOC_G_STD, &sid); + if (err < 0) + dprintk("VIDIOCGTUNER / VIDIOC_G_STD: %d\n",err); + if (err == 0) { + if (sid & V4L2_STD_PAL) + tun->mode = VIDEO_MODE_PAL; + if (sid & V4L2_STD_NTSC) + tun->mode = VIDEO_MODE_NTSC; + if (sid & V4L2_STD_SECAM) + tun->mode = VIDEO_MODE_SECAM; + } + + if (tun2.capability & V4L2_TUNER_CAP_LOW) + tun->flags |= VIDEO_TUNER_LOW; + if (tun2.rxsubchans & V4L2_TUNER_SUB_STEREO) + tun->flags |= VIDEO_TUNER_STEREO_ON; + tun->signal = tun2.signal; + break; + } + case VIDIOCSTUNER: /* select a tuner input */ + { +#if 0 /* FIXME */ + err = drv(inode, file, VIDIOC_S_INPUT, &i); + if (err < 0) + dprintk("VIDIOCSTUNER / VIDIOC_S_INPUT: %d\n",err); +#else + err = 0; +#endif + break; + } + case VIDIOCGFREQ: /* get frequency */ + { + int *freq = arg; + + freq2.tuner = 0; + err = drv(inode, file, VIDIOC_G_FREQUENCY, &freq2); + if (err < 0) + dprintk("VIDIOCGFREQ / VIDIOC_G_FREQUENCY: %d\n",err); + if (0 == err) + *freq = freq2.frequency; + break; + } + case VIDIOCSFREQ: /* set frequency */ + { + int *freq = arg; + + freq2.tuner = 0; + drv(inode, file, VIDIOC_G_FREQUENCY, &freq2); + freq2.frequency = *freq; + err = drv(inode, file, VIDIOC_S_FREQUENCY, &freq2); + if (err < 0) + dprintk("VIDIOCSFREQ / VIDIOC_S_FREQUENCY: %d\n",err); + break; + } + case VIDIOCGAUDIO: /* get audio properties/controls */ + { + struct video_audio *aud = arg; + + err = drv(inode, file, VIDIOC_G_AUDIO, &aud2); + if (err < 0) { + dprintk("VIDIOCGAUDIO / VIDIOC_G_AUDIO: %d\n",err); + break; + } + memcpy(aud->name, aud2.name, + min(sizeof(aud->name), sizeof(aud2.name))); + aud->name[sizeof(aud->name) - 1] = 0; + aud->audio = aud2.index; + aud->flags = 0; + i = get_v4l_control(inode, file, V4L2_CID_AUDIO_VOLUME, drv); + if (i >= 0) { + aud->volume = i; + aud->flags |= VIDEO_AUDIO_VOLUME; + } + i = get_v4l_control(inode, file, V4L2_CID_AUDIO_BASS, drv); + if (i >= 0) { + aud->bass = i; + aud->flags |= VIDEO_AUDIO_BASS; + } + i = get_v4l_control(inode, file, V4L2_CID_AUDIO_TREBLE, drv); + if (i >= 0) { + aud->treble = i; + aud->flags |= VIDEO_AUDIO_TREBLE; + } + i = get_v4l_control(inode, file, V4L2_CID_AUDIO_BALANCE, drv); + if (i >= 0) { + aud->balance = i; + aud->flags |= VIDEO_AUDIO_BALANCE; + } + i = get_v4l_control(inode, file, V4L2_CID_AUDIO_MUTE, drv); + if (i >= 0) { + if (i) + aud->flags |= VIDEO_AUDIO_MUTE; + aud->flags |= VIDEO_AUDIO_MUTABLE; + } + aud->step = 1; + qctrl2.id = V4L2_CID_AUDIO_VOLUME; + if (drv(inode, file, VIDIOC_QUERYCTRL, &qctrl2) == 0 && + !(qctrl2.flags & V4L2_CTRL_FLAG_DISABLED)) + aud->step = qctrl2.step; + aud->mode = 0; + err = drv(inode, file, VIDIOC_G_TUNER, &tun2); + if (err < 0) { + dprintk("VIDIOCGAUDIO / VIDIOC_G_TUNER: %d\n",err); + err = 0; + break; + } + if (tun2.rxsubchans & V4L2_TUNER_SUB_LANG2) + aud->mode = VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + else if (tun2.rxsubchans & V4L2_TUNER_SUB_STEREO) + aud->mode = VIDEO_SOUND_STEREO; + else if (tun2.rxsubchans & V4L2_TUNER_SUB_MONO) + aud->mode = VIDEO_SOUND_MONO; + break; + } + case VIDIOCSAUDIO: /* set audio controls */ + { + struct video_audio *aud = arg; + + memset(&aud2,0,sizeof(aud2)); + memset(&tun2,0,sizeof(tun2)); + + aud2.index = aud->audio; + err = drv(inode, file, VIDIOC_S_AUDIO, &aud2); + if (err < 0) { + dprintk("VIDIOCSAUDIO / VIDIOC_S_AUDIO: %d\n",err); + break; + } + + set_v4l_control(inode, file, V4L2_CID_AUDIO_VOLUME, + aud->volume, drv); + set_v4l_control(inode, file, V4L2_CID_AUDIO_BASS, + aud->bass, drv); + set_v4l_control(inode, file, V4L2_CID_AUDIO_TREBLE, + aud->treble, drv); + set_v4l_control(inode, file, V4L2_CID_AUDIO_BALANCE, + aud->balance, drv); + set_v4l_control(inode, file, V4L2_CID_AUDIO_MUTE, + !!(aud->flags & VIDEO_AUDIO_MUTE), drv); + + err = drv(inode, file, VIDIOC_G_TUNER, &tun2); + if (err < 0) + dprintk("VIDIOCSAUDIO / VIDIOC_G_TUNER: %d\n",err); + if (err == 0) { + switch (aud->mode) { + default: + case VIDEO_SOUND_MONO: + case VIDEO_SOUND_LANG1: + tun2.audmode = V4L2_TUNER_MODE_MONO; + break; + case VIDEO_SOUND_STEREO: + tun2.audmode = V4L2_TUNER_MODE_STEREO; + break; + case VIDEO_SOUND_LANG2: + tun2.audmode = V4L2_TUNER_MODE_LANG2; + break; + } + err = drv(inode, file, VIDIOC_S_TUNER, &tun2); + if (err < 0) + dprintk("VIDIOCSAUDIO / VIDIOC_S_TUNER: %d\n",err); + } + err = 0; + break; + } +#if 0 + case VIDIOCGMBUF: + /* v4l2 drivers must implement that themself. The + mmap() differences can't be translated fully + transparent, thus there is no point to try that */ +#endif + case VIDIOCMCAPTURE: /* capture a frame */ + { + struct video_mmap *mm = arg; + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(&buf2,0,sizeof(buf2)); + memset(fmt2,0,sizeof(*fmt2)); + + fmt2->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCMCAPTURE / VIDIOC_G_FMT: %d\n",err); + break; + } + if (mm->width != fmt2->fmt.pix.width || + mm->height != fmt2->fmt.pix.height || + palette_to_pixelformat(mm->format) != + fmt2->fmt.pix.pixelformat) + {/* New capture format... */ + fmt2->fmt.pix.width = mm->width; + fmt2->fmt.pix.height = mm->height; + fmt2->fmt.pix.pixelformat = + palette_to_pixelformat(mm->format); + fmt2->fmt.pix.field = V4L2_FIELD_ANY; + fmt2->fmt.pix.bytesperline = 0; + err = drv(inode, file, VIDIOC_S_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCMCAPTURE / VIDIOC_S_FMT: %d\n",err); + break; + } + } + buf2.index = mm->frame; + buf2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_QUERYBUF, &buf2); + if (err < 0) { + dprintk("VIDIOCMCAPTURE / VIDIOC_QUERYBUF: %d\n",err); + break; + } + err = drv(inode, file, VIDIOC_QBUF, &buf2); + if (err < 0) { + dprintk("VIDIOCMCAPTURE / VIDIOC_QBUF: %d\n",err); + break; + } + err = drv(inode, file, VIDIOC_STREAMON, &type); + if (err < 0) + dprintk("VIDIOCMCAPTURE / VIDIOC_STREAMON: %d\n",err); + break; + } + case VIDIOCSYNC: /* wait for a frame */ + { + int *i = arg; + + buf2.index = *i; + buf2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_QUERYBUF, &buf2); + if (err < 0) { + /* No such buffer */ + dprintk("VIDIOCSYNC / VIDIOC_QUERYBUF: %d\n",err); + break; + } + if (!(buf2.flags & V4L2_BUF_FLAG_MAPPED)) { + /* Buffer is not mapped */ + err = -EINVAL; + break; + } + + /* Loop as long as the buffer is queued, but not done */ + while ((buf2.flags & + (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE)) + == V4L2_BUF_FLAG_QUEUED) + { + err = poll_one(file); + if (err < 0 || /* error or sleep was interrupted */ + err == 0) /* timeout? Shouldn't occur. */ + break; + err = drv(inode, file, VIDIOC_QUERYBUF, &buf2); + if (err < 0) + dprintk("VIDIOCSYNC / VIDIOC_QUERYBUF: %d\n",err); + } + if (!(buf2.flags & V4L2_BUF_FLAG_DONE)) /* not done */ + break; + do { + err = drv(inode, file, VIDIOC_DQBUF, &buf2); + if (err < 0) + dprintk("VIDIOCSYNC / VIDIOC_DQBUF: %d\n",err); + } while (err == 0 && buf2.index != *i); + break; + } + + case VIDIOCGVBIFMT: /* query VBI data capture format */ + { + struct vbi_format *fmt = arg; + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(fmt2, 0, sizeof(*fmt2)); + fmt2->type = V4L2_BUF_TYPE_VBI_CAPTURE; + + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCGVBIFMT / VIDIOC_G_FMT: %d\n", err); + break; + } + memset(fmt, 0, sizeof(*fmt)); + fmt->samples_per_line = fmt2->fmt.vbi.samples_per_line; + fmt->sampling_rate = fmt2->fmt.vbi.sampling_rate; + fmt->sample_format = VIDEO_PALETTE_RAW; + fmt->start[0] = fmt2->fmt.vbi.start[0]; + fmt->count[0] = fmt2->fmt.vbi.count[0]; + fmt->start[1] = fmt2->fmt.vbi.start[1]; + fmt->count[1] = fmt2->fmt.vbi.count[1]; + fmt->flags = fmt2->fmt.vbi.flags & 0x03; + break; + } + case VIDIOCSVBIFMT: + { + struct vbi_format *fmt = arg; + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(fmt2, 0, sizeof(*fmt2)); + + fmt2->type = V4L2_BUF_TYPE_VBI_CAPTURE; + fmt2->fmt.vbi.samples_per_line = fmt->samples_per_line; + fmt2->fmt.vbi.sampling_rate = fmt->sampling_rate; + fmt2->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + fmt2->fmt.vbi.start[0] = fmt->start[0]; + fmt2->fmt.vbi.count[0] = fmt->count[0]; + fmt2->fmt.vbi.start[1] = fmt->start[1]; + fmt2->fmt.vbi.count[1] = fmt->count[1]; + fmt2->fmt.vbi.flags = fmt->flags; + err = drv(inode, file, VIDIOC_TRY_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCSVBIFMT / VIDIOC_TRY_FMT: %d\n", err); + break; + } + + if (fmt2->fmt.vbi.samples_per_line != fmt->samples_per_line || + fmt2->fmt.vbi.sampling_rate != fmt->sampling_rate || + VIDEO_PALETTE_RAW != fmt->sample_format || + fmt2->fmt.vbi.start[0] != fmt->start[0] || + fmt2->fmt.vbi.count[0] != fmt->count[0] || + fmt2->fmt.vbi.start[1] != fmt->start[1] || + fmt2->fmt.vbi.count[1] != fmt->count[1] || + fmt2->fmt.vbi.flags != fmt->flags) { + err = -EINVAL; + break; + } + err = drv(inode, file, VIDIOC_S_FMT, fmt2); + if (err < 0) + dprintk("VIDIOCSVBIFMT / VIDIOC_S_FMT: %d\n", err); + break; + } + + default: + err = -ENOIOCTLCMD; + break; + } + + if (cap2) + kfree(cap2); + if (fmt2) + kfree(fmt2); + return err; +} + +EXPORT_SYMBOL(v4l_compat_translate_ioctl); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/v4l2-common.c b/linux/drivers/media/video/v4l2-common.c new file mode 100644 index 000000000..1b900c33b --- /dev/null +++ b/linux/drivers/media/video/v4l2-common.c @@ -0,0 +1,284 @@ +/* + * Video for Linux Two + * + * A generic video device interface for the LINUX operating system + * using a set of device structures/vectors for low level operations. + * + * This file replaces the videodev.c file that comes with the + * regular kernel distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Author: Bill Dirks <bdirks@pacbell.net> + * based on code by Alan Cox, <alan@cymru.net> + * + */ + +/* + * Video capture interface for Linux + * + * A generic video device interface for the LINUX operating system + * using a set of device structures/vectors for low level operations. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Author: Alan Cox, <alan@redhat.com> + * + * Fixes: + */ + +/* + * Video4linux 1/2 integration by Justin Schoeman + * <justin@suntiger.ee.up.ac.za> + * 2.4 PROCFS support ported from 2.4 kernels by + * Iñaki García Etxebarria <garetxe@euskalnet.net> + * Makefile fix by "W. Michael Petullo" <mike@flyn.org> + * 2.4 devfs support ported from 2.4 kernels by + * Dan Merillat <dan@merillat.org> + * Added Gerd Knorrs v4l1 enhancements (Justin Schoeman) + */ + +#include <linux/config.h> +#include <linux/version.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/pgtable.h> +#include <asm/io.h> +#include <asm/div64.h> + +#ifdef CONFIG_KMOD +#include <linux/kmod.h> +#endif + +#if defined(CONFIG_UST) || defined(CONFIG_UST_MODULE) +#include <linux/ust.h> +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,71) +# define strlcpy(dest,src,len) strncpy(dest,src,(len)-1) +#endif + +#include <linux/videodev.h> + +MODULE_AUTHOR("Bill Dirks, Justin Schoeman, Gerd Knorr"); +MODULE_DESCRIPTION("misc helper functions for v4l2 device drivers"); +MODULE_LICENSE("GPL"); + +/* + * + * V 4 L 2 D R I V E R H E L P E R A P I + * + */ + +/* + * Video Standard Operations (contributed by Michael Schimek) + */ + +/* This is the recommended method to deal with the framerate fields. More + sophisticated drivers will access the fields directly. */ +unsigned int +v4l2_video_std_fps(struct v4l2_standard *vs) +{ + if (vs->frameperiod.numerator > 0) + return (((vs->frameperiod.denominator << 8) / + vs->frameperiod.numerator) + + (1 << 7)) / (1 << 8); + return 0; +} + +/* Fill in the fields of a v4l2_standard structure according to the + 'id' and 'transmission' parameters. Returns negative on error. */ +int v4l2_video_std_construct(struct v4l2_standard *vs, + int id, char *name) +{ + u32 index = vs->index; + + memset(vs, 0, sizeof(struct v4l2_standard)); + vs->index = index; + vs->id = id; + if (id & (V4L2_STD_NTSC | V4L2_STD_PAL_M)) { + vs->frameperiod.numerator = 1001; + vs->frameperiod.denominator = 30000; + vs->framelines = 525; + } else { + vs->frameperiod.numerator = 1; + vs->frameperiod.denominator = 25; + vs->framelines = 625; + } + strlcpy(vs->name,name,sizeof(vs->name)); + return 0; +} + + +/* ----------------------------------------------------------------- */ +/* priority handling */ + +#define V4L2_PRIO_VALID(val) (val == V4L2_PRIORITY_BACKGROUND || \ + val == V4L2_PRIORITY_INTERACTIVE || \ + val == V4L2_PRIORITY_RECORD) + +int v4l2_prio_init(struct v4l2_prio_state *global) +{ + memset(global,0,sizeof(*global)); + return 0; +} + +int v4l2_prio_change(struct v4l2_prio_state *global, enum v4l2_priority *local, + enum v4l2_priority new) +{ + if (!V4L2_PRIO_VALID(new)) + return -EINVAL; + if (*local == new) + return 0; + + atomic_inc(&global->prios[new]); + if (V4L2_PRIO_VALID(*local)) + atomic_dec(&global->prios[*local]); + *local = new; + return 0; +} + +int v4l2_prio_open(struct v4l2_prio_state *global, enum v4l2_priority *local) +{ + return v4l2_prio_change(global,local,V4L2_PRIORITY_DEFAULT); +} + +int v4l2_prio_close(struct v4l2_prio_state *global, enum v4l2_priority *local) +{ + if (V4L2_PRIO_VALID(*local)) + atomic_dec(&global->prios[*local]); + return 0; +} + +enum v4l2_priority v4l2_prio_max(struct v4l2_prio_state *global) +{ + if (atomic_read(&global->prios[V4L2_PRIORITY_RECORD]) > 0) + return V4L2_PRIORITY_RECORD; + if (atomic_read(&global->prios[V4L2_PRIORITY_INTERACTIVE]) > 0) + return V4L2_PRIORITY_INTERACTIVE; + if (atomic_read(&global->prios[V4L2_PRIORITY_BACKGROUND]) > 0) + return V4L2_PRIORITY_BACKGROUND; + return V4L2_PRIORITY_UNSET; +} + +int v4l2_prio_check(struct v4l2_prio_state *global, enum v4l2_priority *local) +{ + if (*local < v4l2_prio_max(global)) + return -EBUSY; + return 0; +} + + +/* ----------------------------------------------------------------- */ +/* some arrays for pretty-printing debug messages */ + +char *v4l2_field_names[] = { + [V4L2_FIELD_ANY] = "any", + [V4L2_FIELD_NONE] = "none", + [V4L2_FIELD_TOP] = "top", + [V4L2_FIELD_BOTTOM] = "bottom", + [V4L2_FIELD_INTERLACED] = "interlaced", + [V4L2_FIELD_SEQ_TB] = "seq-tb", + [V4L2_FIELD_SEQ_BT] = "seq-bt", + [V4L2_FIELD_ALTERNATE] = "alternate", +}; + +char *v4l2_type_names[] = { + [V4L2_BUF_TYPE_VIDEO_CAPTURE] = "video-cap", + [V4L2_BUF_TYPE_VIDEO_OVERLAY] = "video-over", + [V4L2_BUF_TYPE_VIDEO_OUTPUT] = "video-out", + [V4L2_BUF_TYPE_VBI_CAPTURE] = "vbi-cap", + [V4L2_BUF_TYPE_VBI_OUTPUT] = "vbi-out", +}; + +char *v4l2_ioctl_names[256] = { +#if __GNUC__ >= 3 + [0 ... 255] = "UNKNOWN", +#endif + [_IOC_NR(VIDIOC_QUERYCAP)] = "VIDIOC_QUERYCAP", + [_IOC_NR(VIDIOC_RESERVED)] = "VIDIOC_RESERVED", + [_IOC_NR(VIDIOC_ENUM_FMT)] = "VIDIOC_ENUM_FMT", + [_IOC_NR(VIDIOC_G_FMT)] = "VIDIOC_G_FMT", + [_IOC_NR(VIDIOC_S_FMT)] = "VIDIOC_S_FMT", +#if 0 + [_IOC_NR(VIDIOC_G_COMP)] = "VIDIOC_G_COMP", + [_IOC_NR(VIDIOC_S_COMP)] = "VIDIOC_S_COMP", +#endif + [_IOC_NR(VIDIOC_REQBUFS)] = "VIDIOC_REQBUFS", + [_IOC_NR(VIDIOC_QUERYBUF)] = "VIDIOC_QUERYBUF", + [_IOC_NR(VIDIOC_G_FBUF)] = "VIDIOC_G_FBUF", + [_IOC_NR(VIDIOC_S_FBUF)] = "VIDIOC_S_FBUF", + [_IOC_NR(VIDIOC_OVERLAY)] = "VIDIOC_OVERLAY", + [_IOC_NR(VIDIOC_QBUF)] = "VIDIOC_QBUF", + [_IOC_NR(VIDIOC_DQBUF)] = "VIDIOC_DQBUF", + [_IOC_NR(VIDIOC_STREAMON)] = "VIDIOC_STREAMON", + [_IOC_NR(VIDIOC_STREAMOFF)] = "VIDIOC_STREAMOFF", + [_IOC_NR(VIDIOC_G_PARM)] = "VIDIOC_G_PARM", + [_IOC_NR(VIDIOC_S_PARM)] = "VIDIOC_S_PARM", + [_IOC_NR(VIDIOC_G_STD)] = "VIDIOC_G_STD", + [_IOC_NR(VIDIOC_S_STD)] = "VIDIOC_S_STD", + [_IOC_NR(VIDIOC_ENUMSTD)] = "VIDIOC_ENUMSTD", + [_IOC_NR(VIDIOC_ENUMINPUT)] = "VIDIOC_ENUMINPUT", + [_IOC_NR(VIDIOC_G_CTRL)] = "VIDIOC_G_CTRL", + [_IOC_NR(VIDIOC_S_CTRL)] = "VIDIOC_S_CTRL", + [_IOC_NR(VIDIOC_G_TUNER)] = "VIDIOC_G_TUNER", + [_IOC_NR(VIDIOC_S_TUNER)] = "VIDIOC_S_TUNER", + [_IOC_NR(VIDIOC_G_AUDIO)] = "VIDIOC_G_AUDIO", + [_IOC_NR(VIDIOC_S_AUDIO)] = "VIDIOC_S_AUDIO", + [_IOC_NR(VIDIOC_QUERYCTRL)] = "VIDIOC_QUERYCTRL", + [_IOC_NR(VIDIOC_QUERYMENU)] = "VIDIOC_QUERYMENU", + [_IOC_NR(VIDIOC_G_INPUT)] = "VIDIOC_G_INPUT", + [_IOC_NR(VIDIOC_S_INPUT)] = "VIDIOC_S_INPUT", + [_IOC_NR(VIDIOC_G_OUTPUT)] = "VIDIOC_G_OUTPUT", + [_IOC_NR(VIDIOC_S_OUTPUT)] = "VIDIOC_S_OUTPUT", + [_IOC_NR(VIDIOC_ENUMOUTPUT)] = "VIDIOC_ENUMOUTPUT", + [_IOC_NR(VIDIOC_G_AUDOUT)] = "VIDIOC_G_AUDOUT", + [_IOC_NR(VIDIOC_S_AUDOUT)] = "VIDIOC_S_AUDOUT", + [_IOC_NR(VIDIOC_G_MODULATOR)] = "VIDIOC_G_MODULATOR", + [_IOC_NR(VIDIOC_S_MODULATOR)] = "VIDIOC_S_MODULATOR", + [_IOC_NR(VIDIOC_G_FREQUENCY)] = "VIDIOC_G_FREQUENCY", + [_IOC_NR(VIDIOC_S_FREQUENCY)] = "VIDIOC_S_FREQUENCY", + [_IOC_NR(VIDIOC_CROPCAP)] = "VIDIOC_CROPCAP", + [_IOC_NR(VIDIOC_G_CROP)] = "VIDIOC_G_CROP", + [_IOC_NR(VIDIOC_S_CROP)] = "VIDIOC_S_CROP", + [_IOC_NR(VIDIOC_G_JPEGCOMP)] = "VIDIOC_G_JPEGCOMP", + [_IOC_NR(VIDIOC_S_JPEGCOMP)] = "VIDIOC_S_JPEGCOMP", + [_IOC_NR(VIDIOC_QUERYSTD)] = "VIDIOC_QUERYSTD", + [_IOC_NR(VIDIOC_TRY_FMT)] = "VIDIOC_TRY_FMT", +}; + +/* ----------------------------------------------------------------- */ + +EXPORT_SYMBOL(v4l2_video_std_fps); +EXPORT_SYMBOL(v4l2_video_std_construct); + +EXPORT_SYMBOL(v4l2_prio_init); +EXPORT_SYMBOL(v4l2_prio_change); +EXPORT_SYMBOL(v4l2_prio_open); +EXPORT_SYMBOL(v4l2_prio_close); +EXPORT_SYMBOL(v4l2_prio_max); +EXPORT_SYMBOL(v4l2_prio_check); + +EXPORT_SYMBOL(v4l2_field_names); +EXPORT_SYMBOL(v4l2_type_names); +EXPORT_SYMBOL(v4l2_ioctl_names); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/video/video-buf.c b/linux/drivers/media/video/video-buf.c new file mode 100644 index 000000000..433255bbc --- /dev/null +++ b/linux/drivers/media/video/video-buf.c @@ -0,0 +1,1264 @@ +/* + * generic helper functions for video4linux capture buffers, to handle + * memory management and PCI DMA. Right now bttv + saa7134 use it. + * + * The functions expect the hardware being able to scatter gatter + * (i.e. the buffers are not linear in physical memory, but fragmented + * into PAGE_SIZE chunks). They also assume the driver does not need + * to touch the video data (thus it is probably not useful for USB as + * data often must be uncompressed by the drivers). + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/version.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/vmalloc.h> +#include <linux/pagemap.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <asm/page.h> +#include <asm/pgtable.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,64) +#include "video-buf.h" +#else +#include <media/video-buf.h> +#endif + +static int debug = 0; + +MODULE_DESCRIPTION("helper module to manage video4linux pci dma buffers"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); +MODULE_PARM(debug,"i"); + +#define dprintk(level, fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG "vbuf: " fmt , ## arg) + +struct scatterlist* +videobuf_vmalloc_to_sg(unsigned char *virt, int nr_pages) +{ + struct scatterlist *sglist; + struct page *pg; + int i; + + sglist = kmalloc(sizeof(struct scatterlist)*nr_pages, GFP_KERNEL); + if (NULL == sglist) + return NULL; + memset(sglist,0,sizeof(struct scatterlist)*nr_pages); + for (i = 0; i < nr_pages; i++, virt += PAGE_SIZE) { + pg = vmalloc_to_page(virt); + if (NULL == pg) + goto err; + if (PageHighMem(pg)) + BUG(); + sglist[i].page = pg; + sglist[i].length = PAGE_SIZE; + } + return sglist; + + err: + kfree(sglist); + return NULL; +} + +struct scatterlist* +videobuf_pages_to_sg(struct page **pages, int nr_pages, int offset) +{ + struct scatterlist *sglist; + int i = 0; + + if (NULL == pages[0]) + return NULL; + sglist = kmalloc(sizeof(*sglist) * nr_pages, GFP_KERNEL); + if (NULL == sglist) + return NULL; + memset(sglist, 0, sizeof(*sglist) * nr_pages); + + if (NULL == pages[0]) + goto nopage; + if (PageHighMem(pages[0])) + /* DMA to highmem pages might not work */ + goto highmem; + sglist[0].page = pages[0]; + sglist[0].offset = offset; + sglist[0].length = PAGE_SIZE - offset; + for (i = 1; i < nr_pages; i++) { + if (NULL == pages[i]) + goto nopage; + if (PageHighMem(pages[i])) + goto highmem; + sglist[i].page = pages[i]; + sglist[i].length = PAGE_SIZE; + } + return sglist; + + nopage: + dprintk(2,"sgl: oops - no page\n"); + kfree(sglist); + return NULL; + + highmem: + dprintk(2,"sgl: oops - highmem page\n"); + kfree(sglist); + return NULL; +} + +/* --------------------------------------------------------------------- */ + +int videobuf_dma_init_user(struct videobuf_dmabuf *dma, int direction, + unsigned long data, unsigned long size) +{ + unsigned long first,last; + int err, rw = 0; + + dma->direction = direction; + switch (dma->direction) { + case PCI_DMA_FROMDEVICE: rw = READ; break; + case PCI_DMA_TODEVICE: rw = WRITE; break; + default: BUG(); + } + + first = (data & PAGE_MASK) >> PAGE_SHIFT; + last = ((data+size-1) & PAGE_MASK) >> PAGE_SHIFT; + dma->offset = data & ~PAGE_MASK; + dma->nr_pages = last-first+1; + dma->pages = kmalloc(dma->nr_pages * sizeof(struct page*), + GFP_KERNEL); + if (NULL == dma->pages) + return -ENOMEM; + dprintk(1,"init user [0x%lx+0x%lx => %d pages]\n", + data,size,dma->nr_pages); + + down_read(¤t->mm->mmap_sem); + err = get_user_pages(current,current->mm, + data & PAGE_MASK, dma->nr_pages, + rw == READ, 1, /* force */ + dma->pages, NULL); + up_read(¤t->mm->mmap_sem); + if (err != dma->nr_pages) { + dma->nr_pages = (err >= 0) ? err : 0; + dprintk(1,"get_user_pages: err=%d [%d]\n",err,dma->nr_pages); + return err < 0 ? err : -EINVAL; + } + return 0; +} + +int videobuf_dma_init_kernel(struct videobuf_dmabuf *dma, int direction, + int nr_pages) +{ + dprintk(1,"init kernel [%d pages]\n",nr_pages); + dma->direction = direction; + dma->vmalloc = vmalloc_32(nr_pages << PAGE_SHIFT); + if (NULL == dma->vmalloc) { + dprintk(1,"vmalloc_32(%d pages) failed\n",nr_pages); + return -ENOMEM; + } + memset(dma->vmalloc,0,nr_pages << PAGE_SHIFT); + dma->nr_pages = nr_pages; + return 0; +} + +int videobuf_dma_init_overlay(struct videobuf_dmabuf *dma, int direction, + dma_addr_t addr, int nr_pages) +{ + dprintk(1,"init overlay [%d pages @ bus 0x%lx]\n", + nr_pages,(unsigned long)addr); + dma->direction = direction; + if (0 == addr) + return -EINVAL; + + dma->bus_addr = addr; + dma->nr_pages = nr_pages; + return 0; +} + +int videobuf_dma_pci_map(struct pci_dev *dev, struct videobuf_dmabuf *dma) +{ + if (0 == dma->nr_pages) + BUG(); + + if (dma->pages) { + dma->sglist = videobuf_pages_to_sg(dma->pages, dma->nr_pages, + dma->offset); + } + if (dma->vmalloc) { + dma->sglist = videobuf_vmalloc_to_sg + (dma->vmalloc,dma->nr_pages); + } + if (dma->bus_addr) { + dma->sglist = kmalloc(sizeof(struct scatterlist), GFP_KERNEL); + if (NULL != dma->sglist) { + dma->sglen = 1; + sg_dma_address(&dma->sglist[0]) = dma->bus_addr & PAGE_MASK; + dma->sglist[0].offset = dma->bus_addr & ~PAGE_MASK; + sg_dma_len(&dma->sglist[0]) = dma->nr_pages * PAGE_SIZE; + } + } + if (NULL == dma->sglist) { + dprintk(1,"scatterlist is NULL\n"); + return -ENOMEM; + } + + if (!dma->bus_addr) + dma->sglen = pci_map_sg(dev,dma->sglist,dma->nr_pages, + dma->direction); + return 0; +} + +int videobuf_dma_pci_sync(struct pci_dev *dev, struct videobuf_dmabuf *dma) +{ + if (!dma->sglen) + BUG(); + + if (!dma->bus_addr) + pci_dma_sync_sg(dev,dma->sglist,dma->nr_pages,dma->direction); + return 0; +} + +int videobuf_dma_pci_unmap(struct pci_dev *dev, struct videobuf_dmabuf *dma) +{ + if (!dma->sglen) + return 0; + + if (!dma->bus_addr) + pci_unmap_sg(dev,dma->sglist,dma->nr_pages,dma->direction); + kfree(dma->sglist); + dma->sglist = NULL; + dma->sglen = 0; + return 0; +} + +int videobuf_dma_free(struct videobuf_dmabuf *dma) +{ + if (dma->sglen) + BUG(); + + if (dma->pages) { + int i; + for (i=0; i < dma->nr_pages; i++) + page_cache_release(dma->pages[i]); + kfree(dma->pages); + dma->pages = NULL; + } + if (dma->vmalloc) { + vfree(dma->vmalloc); + dma->vmalloc = NULL; + } + if (dma->bus_addr) { + dma->bus_addr = 0; + } + dma->direction = PCI_DMA_NONE; + return 0; +} + +/* --------------------------------------------------------------------- */ + +void* videobuf_alloc(unsigned int size) +{ + struct videobuf_buffer *vb; + + vb = kmalloc(size,GFP_KERNEL); + if (NULL != vb) { + memset(vb,0,size); + init_waitqueue_head(&vb->done); + } + return vb; +} + +int videobuf_waiton(struct videobuf_buffer *vb, int non_blocking, int intr) +{ + int retval = 0; + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&vb->done, &wait); + while (vb->state == STATE_ACTIVE || vb->state == STATE_QUEUED) { + if (non_blocking) { + retval = -EAGAIN; + break; + } + set_current_state(intr ? TASK_INTERRUPTIBLE + : TASK_UNINTERRUPTIBLE); + if (vb->state == STATE_ACTIVE || vb->state == STATE_QUEUED) + schedule(); + set_current_state(TASK_RUNNING); + if (intr && signal_pending(current)) { + dprintk(1,"buffer waiton: -EINTR\n"); + retval = -EINTR; + break; + } + } + remove_wait_queue(&vb->done, &wait); + return retval; +} + +int +videobuf_iolock(struct pci_dev *pci, struct videobuf_buffer *vb, + struct v4l2_framebuffer *fbuf) +{ + int err,pages; + dma_addr_t bus; + + switch (vb->memory) { + case V4L2_MEMORY_MMAP: + case V4L2_MEMORY_USERPTR: + if (0 == vb->baddr) { + /* no userspace addr -- kernel bounce buffer */ + pages = PAGE_ALIGN(vb->size) >> PAGE_SHIFT; + err = videobuf_dma_init_kernel(&vb->dma,PCI_DMA_FROMDEVICE, + pages); + if (0 != err) + return err; + } else { + /* dma directly to userspace */ + err = videobuf_dma_init_user(&vb->dma,PCI_DMA_FROMDEVICE, + vb->baddr,vb->bsize); + if (0 != err) + return err; + } + break; + case V4L2_MEMORY_OVERLAY: + if (NULL == fbuf) + return -EINVAL; + /* FIXME: need sanity checks for vb->boff */ + bus = (dma_addr_t)fbuf->base + vb->boff; + pages = PAGE_ALIGN(vb->size) >> PAGE_SHIFT; + err = videobuf_dma_init_overlay(&vb->dma,PCI_DMA_FROMDEVICE, + bus, pages); + if (0 != err) + return err; + break; + default: + BUG(); + } + err = videobuf_dma_pci_map(pci,&vb->dma); + if (0 != err) + return err; + + return 0; +} + +/* --------------------------------------------------------------------- */ + +void +videobuf_queue_init(struct videobuf_queue *q, + struct videobuf_queue_ops *ops, + struct pci_dev *pci, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize) +{ + memset(q,0,sizeof(*q)); + + q->irqlock = irqlock; + q->pci = pci; + q->type = type; + q->field = field; + q->msize = msize; + q->ops = ops; + + init_MUTEX(&q->lock); + INIT_LIST_HEAD(&q->stream); +} + +int +videobuf_queue_is_busy(struct videobuf_queue *q) +{ + int i; + + if (q->streaming) { + dprintk(1,"busy: streaming active\n"); + return 1; + } + if (q->reading) { + dprintk(1,"busy: pending read #1\n"); + return 1; + } + if (q->read_buf) { + dprintk(1,"busy: pending read #2\n"); + return 1; + } + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + if (q->bufs[i]->map) { + dprintk(1,"busy: buffer #%d mapped\n",i); + return 1; + } + if (q->bufs[i]->state == STATE_QUEUED) { + dprintk(1,"busy: buffer #%d queued\n",i); + return 1; + } + if (q->bufs[i]->state == STATE_ACTIVE) { + dprintk(1,"busy: buffer #%d avtive\n",i); + return 1; + } + } + return 0; +} + +void +videobuf_queue_cancel(struct file *file, struct videobuf_queue *q) +{ + unsigned long flags; + int i; + + /* remove queued buffers from list */ + spin_lock_irqsave(q->irqlock,flags); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + if (q->bufs[i]->state == STATE_QUEUED) { + list_del(&q->bufs[i]->queue); + q->bufs[i]->state = STATE_ERROR; + } + } + spin_unlock_irqrestore(q->irqlock,flags); + + /* free all buffers + clear queue */ + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + q->ops->buf_release(file,q->bufs[i]); + } + INIT_LIST_HEAD(&q->stream); +} + +/* --------------------------------------------------------------------- */ + +enum v4l2_field +videobuf_next_field(struct videobuf_queue *q) +{ + enum v4l2_field field = q->field; + + BUG_ON(V4L2_FIELD_ANY == field); + + if (V4L2_FIELD_ALTERNATE == field) { + if (V4L2_FIELD_TOP == q->last) { + field = V4L2_FIELD_BOTTOM; + q->last = V4L2_FIELD_BOTTOM; + } else { + field = V4L2_FIELD_TOP; + q->last = V4L2_FIELD_TOP; + } + } + return field; +} + +void +videobuf_status(struct v4l2_buffer *b, struct videobuf_buffer *vb, + enum v4l2_buf_type type) +{ + b->index = vb->i; + b->type = type; + + b->memory = vb->memory; + switch (b->memory) { + case V4L2_MEMORY_MMAP: + b->m.offset = vb->boff; + b->length = vb->bsize; + break; + case V4L2_MEMORY_USERPTR: + b->m.userptr = vb->baddr; + b->length = vb->bsize; + break; + case V4L2_MEMORY_OVERLAY: + b->m.offset = vb->boff; + break; + } + + b->flags = 0; + if (vb->map) + b->flags |= V4L2_BUF_FLAG_MAPPED; + + switch (vb->state) { + case STATE_PREPARED: + case STATE_QUEUED: + case STATE_ACTIVE: + b->flags |= V4L2_BUF_FLAG_QUEUED; + break; + case STATE_DONE: + case STATE_ERROR: + b->flags |= V4L2_BUF_FLAG_DONE; + break; + case STATE_NEEDS_INIT: + case STATE_IDLE: + /* nothing */ + break; + } + + if (vb->input != UNSET) { + b->flags |= V4L2_BUF_FLAG_INPUT; + b->input = vb->input; + } + + b->field = vb->field; + b->timestamp = vb->ts; + b->bytesused = vb->size; + b->sequence = vb->field_count >> 1; +} + +int +videobuf_reqbufs(struct file *file, struct videobuf_queue *q, + struct v4l2_requestbuffers *req) +{ + unsigned int size,count; + int retval; + + if (req->type != q->type) + return -EINVAL; + if (req->count < 1) + return -EINVAL; + if (req->memory != V4L2_MEMORY_MMAP && + req->memory != V4L2_MEMORY_USERPTR && + req->memory != V4L2_MEMORY_OVERLAY) + return -EINVAL; + + down(&q->lock); + count = req->count; + if (count > VIDEO_MAX_FRAME) + count = VIDEO_MAX_FRAME; + size = 0; + q->ops->buf_setup(file,&count,&size); + size = PAGE_ALIGN(size); + dprintk(1,"reqbufs: bufs=%d, size=0x%x [%d pages total]\n", + count, size, (count*size)>>PAGE_SHIFT); + + retval = videobuf_mmap_setup(file,q,count,size,req->memory); + if (retval < 0) + goto done; + + req->count = count; + + done: + up(&q->lock); + return retval; +} + +int +videobuf_querybuf(struct videobuf_queue *q, struct v4l2_buffer *b) +{ + if (unlikely(b->type != q->type)) + return -EINVAL; + if (unlikely(b->index < 0 || b->index >= VIDEO_MAX_FRAME)) + return -EINVAL; + if (unlikely(NULL == q->bufs[b->index])) + return -EINVAL; + videobuf_status(b,q->bufs[b->index],q->type); + return 0; +} + +int +videobuf_qbuf(struct file *file, struct videobuf_queue *q, + struct v4l2_buffer *b) +{ + struct videobuf_buffer *buf; + enum v4l2_field field; + unsigned long flags; + int retval; + + down(&q->lock); + retval = -EBUSY; + if (q->reading) + goto done; + retval = -EINVAL; + if (b->type != q->type) + goto done; + if (b->index < 0 || b->index >= VIDEO_MAX_FRAME) + goto done; + buf = q->bufs[b->index]; + if (NULL == buf) + goto done; + if (buf->memory != b->memory) + goto done; + if (buf->state == STATE_QUEUED || + buf->state == STATE_ACTIVE) + goto done; + + if (b->flags & V4L2_BUF_FLAG_INPUT) { + if (b->input >= q->inputs) + goto done; + buf->input = b->input; + } else { + buf->input = UNSET; + } + + switch (b->memory) { + case V4L2_MEMORY_MMAP: + if (0 == buf->baddr) + goto done; + break; + case V4L2_MEMORY_USERPTR: + if (b->length < buf->bsize) + goto done; + buf->baddr = b->m.userptr; + break; + case V4L2_MEMORY_OVERLAY: + buf->boff = b->m.offset; + break; + default: + goto done; + } + + field = videobuf_next_field(q); + retval = q->ops->buf_prepare(file,buf,field); + if (0 != retval) + goto done; + + list_add_tail(&buf->stream,&q->stream); + if (q->streaming) { + spin_lock_irqsave(q->irqlock,flags); + q->ops->buf_queue(file,buf); + spin_unlock_irqrestore(q->irqlock,flags); + } + retval = 0; + + done: + up(&q->lock); + return retval; +} + +int +videobuf_dqbuf(struct file *file, struct videobuf_queue *q, + struct v4l2_buffer *b) +{ + struct videobuf_buffer *buf; + int retval; + + down(&q->lock); + retval = -EBUSY; + if (q->reading) + goto done; + retval = -EINVAL; + if (b->type != q->type) + goto done; + if (list_empty(&q->stream)) + goto done; + buf = list_entry(q->stream.next, struct videobuf_buffer, stream); + retval = videobuf_waiton(buf, file->f_flags & O_NONBLOCK, 1); + if (retval < 0) + goto done; + switch (buf->state) { + case STATE_ERROR: + retval = -EIO; + /* fall through */ + case STATE_DONE: + videobuf_dma_pci_sync(q->pci,&buf->dma); + buf->state = STATE_IDLE; + break; + default: + retval = -EINVAL; + goto done; + } + list_del(&buf->stream); + memset(b,0,sizeof(*b)); + videobuf_status(b,buf,q->type); + + done: + up(&q->lock); + return retval; +} + +int videobuf_streamon(struct file *file, struct videobuf_queue *q) +{ + struct videobuf_buffer *buf; + struct list_head *list; + unsigned long flags; + int retval; + + down(&q->lock); + retval = -EBUSY; + if (q->reading) + goto done; + retval = 0; + if (q->streaming) + goto done; + q->streaming = 1; + spin_lock_irqsave(q->irqlock,flags); + list_for_each(list,&q->stream) { + buf = list_entry(list, struct videobuf_buffer, stream); + if (buf->state == STATE_PREPARED) + q->ops->buf_queue(file,buf); + } + spin_unlock_irqrestore(q->irqlock,flags); + + done: + up(&q->lock); + return retval; +} + +int videobuf_streamoff(struct file *file, struct videobuf_queue *q) +{ + int retval = -EINVAL; + + down(&q->lock); + if (!q->streaming) + goto done; + videobuf_queue_cancel(file,q); + q->streaming = 0; + retval = 0; + + done: + up(&q->lock); + return retval; +} + +static ssize_t +videobuf_read_zerocopy(struct file *file, struct videobuf_queue *q, + char *data, size_t count, loff_t *ppos) +{ + enum v4l2_field field; + unsigned long flags; + int retval; + + /* setup stuff */ + retval = -ENOMEM; + q->read_buf = videobuf_alloc(q->msize); + if (NULL == q->read_buf) + goto done; + + q->read_buf->memory = V4L2_MEMORY_USERPTR; + q->read_buf->baddr = (unsigned long)data; + q->read_buf->bsize = count; + field = videobuf_next_field(q); + retval = q->ops->buf_prepare(file,q->read_buf,field); + if (0 != retval) + goto done; + + /* start capture & wait */ + spin_lock_irqsave(q->irqlock,flags); + q->ops->buf_queue(file,q->read_buf); + spin_unlock_irqrestore(q->irqlock,flags); + retval = videobuf_waiton(q->read_buf,0,0); + if (0 == retval) { + videobuf_dma_pci_sync(q->pci,&q->read_buf->dma); + if (STATE_ERROR == q->read_buf->state) + retval = -EIO; + else + retval = q->read_buf->size; + } + + done: + /* cleanup */ + q->ops->buf_release(file,q->read_buf); + kfree(q->read_buf); + q->read_buf = NULL; + return retval; +} + +ssize_t videobuf_read_one(struct file *file, struct videobuf_queue *q, + char *data, size_t count, loff_t *ppos) +{ + enum v4l2_field field; + unsigned long flags; + unsigned size, nbufs, bytes; + int retval; + + down(&q->lock); + + nbufs = 1; size = 0; + q->ops->buf_setup(file,&nbufs,&size); + if (NULL == q->read_buf && + count >= size && + !(file->f_flags & O_NONBLOCK)) { + retval = videobuf_read_zerocopy(file,q,data,count,ppos); + if (retval >= 0 || retval == -EIO) + /* ok, all done */ + goto done; + /* fallback to kernel bounce buffer on failures */ + } + + if (NULL == q->read_buf) { + /* need to capture a new frame */ + retval = -ENOMEM; + q->read_buf = videobuf_alloc(q->msize); + if (NULL == q->read_buf) + goto done; + q->read_buf->memory = V4L2_MEMORY_USERPTR; + field = videobuf_next_field(q); + retval = q->ops->buf_prepare(file,q->read_buf,field); + if (0 != retval) + goto done; + spin_lock_irqsave(q->irqlock,flags); + q->ops->buf_queue(file,q->read_buf); + spin_unlock_irqrestore(q->irqlock,flags); + q->read_off = 0; + } + + /* wait until capture is done */ + retval = videobuf_waiton(q->read_buf, file->f_flags & O_NONBLOCK, 1); + if (0 != retval) + goto done; + videobuf_dma_pci_sync(q->pci,&q->read_buf->dma); + + if (STATE_ERROR == q->read_buf->state) { + /* catch I/O errors */ + q->ops->buf_release(file,q->read_buf); + kfree(q->read_buf); + q->read_buf = NULL; + retval = -EIO; + goto done; + } + + /* copy to userspace */ + bytes = count; + if (bytes > q->read_buf->size - q->read_off) + bytes = q->read_buf->size - q->read_off; + retval = -EFAULT; + if (copy_to_user(data, q->read_buf->dma.vmalloc+q->read_off, bytes)) + goto done; + + retval = bytes; + q->read_off += bytes; + if (q->read_off == q->read_buf->size) { + /* all data copied, cleanup */ + q->ops->buf_release(file,q->read_buf); + kfree(q->read_buf); + q->read_buf = NULL; + } + + done: + up(&q->lock); + return retval; +} + +int videobuf_read_start(struct file *file, struct videobuf_queue *q) +{ + enum v4l2_field field; + unsigned long flags; + int count = 0, size = 0; + int err, i; + + q->ops->buf_setup(file,&count,&size); + if (count < 2) + count = 2; + if (count > VIDEO_MAX_FRAME) + count = VIDEO_MAX_FRAME; + size = PAGE_ALIGN(size); + + err = videobuf_mmap_setup(file, q, count, size, V4L2_MEMORY_USERPTR); + if (err) + return err; + for (i = 0; i < count; i++) { + field = videobuf_next_field(q); + err = q->ops->buf_prepare(file,q->bufs[i],field); + if (err) + return err; + list_add_tail(&q->bufs[i]->stream, &q->stream); + } + spin_lock_irqsave(q->irqlock,flags); + for (i = 0; i < count; i++) + q->ops->buf_queue(file,q->bufs[i]); + spin_unlock_irqrestore(q->irqlock,flags); + q->reading = 1; + return 0; +} + +void videobuf_read_stop(struct file *file, struct videobuf_queue *q) +{ + int i; + + videobuf_queue_cancel(file,q); + INIT_LIST_HEAD(&q->stream); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + kfree(q->bufs[i]); + q->bufs[i] = NULL; + } + q->read_buf = NULL; + q->reading = 0; +} + +ssize_t videobuf_read_stream(struct file *file, struct videobuf_queue *q, + char *data, size_t count, loff_t *ppos, + int vbihack) +{ + unsigned int *fc, bytes; + int err, retval; + unsigned long flags; + + down(&q->lock); + retval = -EBUSY; + if (q->streaming) + goto done; + if (!q->reading) { + retval = videobuf_read_start(file,q); + if (retval < 0) + goto done; + } + + retval = 0; + while (count > 0) { + /* get / wait for data */ + if (NULL == q->read_buf) { + q->read_buf = list_entry(q->stream.next, + struct videobuf_buffer, + stream); + list_del(&q->read_buf->stream); + q->read_off = 0; + } + err = videobuf_waiton(q->read_buf, + file->f_flags & O_NONBLOCK,1); + if (err < 0) { + if (0 == retval) + retval = err; + break; + } + + if (q->read_buf->state == STATE_DONE) { + if (vbihack) { + /* dirty, undocumented hack -- pass the frame counter + * within the last four bytes of each vbi data block. + * We need that one to maintain backward compatibility + * to all vbi decoding software out there ... */ + fc = (unsigned int*)q->read_buf->dma.vmalloc; + fc += (q->read_buf->size>>2) -1; + *fc = q->read_buf->field_count >> 1; + dprintk(1,"vbihack: %d\n",*fc); + } + + /* copy stuff */ + bytes = count; + if (bytes > q->read_buf->size - q->read_off) + bytes = q->read_buf->size - q->read_off; + if (copy_to_user(data + retval, + q->read_buf->dma.vmalloc + q->read_off, + bytes)) { + if (0 == retval) + retval = -EFAULT; + break; + } + count -= bytes; + retval += bytes; + q->read_off += bytes; + } else { + /* some error */ + q->read_off = q->read_buf->size; + if (0 == retval) + retval = -EIO; + } + + /* requeue buffer when done with copying */ + if (q->read_off == q->read_buf->size) { + list_add_tail(&q->read_buf->stream, + &q->stream); + spin_lock_irqsave(q->irqlock,flags); + q->ops->buf_queue(file,q->read_buf); + spin_unlock_irqrestore(q->irqlock,flags); + q->read_buf = NULL; + } + if (retval < 0) + break; + } + + done: + up(&q->lock); + return retval; +} + +unsigned int videobuf_poll_stream(struct file *file, + struct videobuf_queue *q, + poll_table *wait) +{ + struct videobuf_buffer *buf = NULL; + unsigned int rc = 0; + + down(&q->lock); + if (q->streaming) { + if (!list_empty(&q->stream)) + buf = list_entry(q->stream.next, + struct videobuf_buffer, stream); + } else { + if (!q->reading) + videobuf_read_start(file,q); + if (!q->reading) { + rc = POLLERR; + } else if (NULL == q->read_buf) { + q->read_buf = list_entry(q->stream.next, + struct videobuf_buffer, + stream); + list_del(&q->read_buf->stream); + q->read_off = 0; + } + buf = q->read_buf; + } + if (!buf) + rc = POLLERR; + + if (0 == rc) { + poll_wait(file, &buf->done, wait); + if (buf->state == STATE_DONE || + buf->state == STATE_ERROR) + rc = POLLIN|POLLRDNORM; + } + up(&q->lock); + return rc; +} + +/* --------------------------------------------------------------------- */ + +static void +videobuf_vm_open(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + + dprintk(2,"vm_open %p [count=%d,vma=%08lx-%08lx]\n",map, + map->count,vma->vm_start,vma->vm_end); + map->count++; +} + +static void +videobuf_vm_close(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + int i; + + dprintk(2,"vm_close %p [count=%d,vma=%08lx-%08lx]\n",map, + map->count,vma->vm_start,vma->vm_end); + + /* down(&fh->lock); FIXME */ + map->count--; + if (0 == map->count) { + dprintk(1,"munmap %p\n",map); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == map->q->bufs[i]) + continue; + if (map->q->bufs[i]) + ; + if (map->q->bufs[i]->map != map) + continue; + map->q->bufs[i]->map = NULL; + map->q->bufs[i]->baddr = 0; + map->q->ops->buf_release(vma->vm_file,map->q->bufs[i]); + } + kfree(map); + } + /* up(&fh->lock); FIXME */ + return; +} + +/* + * Get a anonymous page for the mapping. Make sure we can DMA to that + * memory location with 32bit PCI devices (i.e. don't use highmem for + * now ...). Bounce buffers don't work very well for the data rates + * video capture has. + */ +static struct page* +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,1) +videobuf_vm_nopage(struct vm_area_struct *vma, unsigned long vaddr, + int write_access) +#else +videobuf_vm_nopage(struct vm_area_struct *vma, unsigned long vaddr, + int *type) +#endif +{ + struct page *page; + + dprintk(3,"nopage: fault @ %08lx [vma %08lx-%08lx]\n", + vaddr,vma->vm_start,vma->vm_end); + if (vaddr > vma->vm_end) + return NOPAGE_SIGBUS; + page = alloc_page(GFP_USER); + if (!page) + return NOPAGE_OOM; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,20) + clear_user_page(page_address(page), vaddr); +#else + clear_user_page(page_address(page), vaddr, page); +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,1) + if (type) + *type = VM_FAULT_MINOR; +#endif + return page; +} + +static struct vm_operations_struct videobuf_vm_ops = +{ + .open = videobuf_vm_open, + .close = videobuf_vm_close, + .nopage = videobuf_vm_nopage, +}; + +int videobuf_mmap_setup(struct file *file, struct videobuf_queue *q, + unsigned int bcount, unsigned int bsize, + enum v4l2_memory memory) +{ + unsigned int i; + int err; + + err = videobuf_mmap_free(file,q); + if (0 != err) + return err; + + for (i = 0; i < bcount; i++) { + q->bufs[i] = videobuf_alloc(q->msize); + q->bufs[i]->i = i; + q->bufs[i]->input = UNSET; + q->bufs[i]->memory = memory; + q->bufs[i]->bsize = bsize; + switch (memory) { + case V4L2_MEMORY_MMAP: + q->bufs[i]->boff = bsize * i; + break; + case V4L2_MEMORY_USERPTR: + case V4L2_MEMORY_OVERLAY: + /* nothing */ + break; + } + } + dprintk(1,"mmap setup: %d buffers, %d bytes each\n", + bcount,bsize); + return 0; +} + +int videobuf_mmap_free(struct file *file, struct videobuf_queue *q) +{ + int i; + + for (i = 0; i < VIDEO_MAX_FRAME; i++) + if (q->bufs[i] && q->bufs[i]->map) + return -EBUSY; + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + q->ops->buf_release(file,q->bufs[i]); + kfree(q->bufs[i]); + q->bufs[i] = NULL; + } + return 0; +} + +int videobuf_mmap_mapper(struct vm_area_struct *vma, + struct videobuf_queue *q) +{ + struct videobuf_mapping *map; + unsigned int first,last,size,i; + int retval; + + down(&q->lock); + retval = -EINVAL; + if (!(vma->vm_flags & VM_WRITE)) { + dprintk(1,"mmap app bug: PROT_WRITE please\n"); + goto done; + } + if (!(vma->vm_flags & VM_SHARED)) { + dprintk(1,"mmap app bug: MAP_SHARED please\n"); + goto done; + } + + /* look for first buffer to map */ + for (first = 0; first < VIDEO_MAX_FRAME; first++) { + if (NULL == q->bufs[first]) + continue; + if (V4L2_MEMORY_MMAP != q->bufs[first]->memory) + continue; + if (q->bufs[first]->boff == (vma->vm_pgoff << PAGE_SHIFT)) + break; + } + if (VIDEO_MAX_FRAME == first) { + dprintk(1,"mmap app bug: offset invalid [offset=0x%lx]\n", + (vma->vm_pgoff << PAGE_SHIFT)); + goto done; + } + + /* look for last buffer to map */ + for (size = 0, last = first; last < VIDEO_MAX_FRAME; last++) { + if (NULL == q->bufs[last]) + continue; + if (V4L2_MEMORY_MMAP != q->bufs[last]->memory) + continue; + if (q->bufs[last]->map) { + retval = -EBUSY; + goto done; + } + size += q->bufs[last]->bsize; + if (size == (vma->vm_end - vma->vm_start)) + break; + } + if (VIDEO_MAX_FRAME == last) { + dprintk(1,"mmap app bug: size invalid [size=0x%lx]\n", + (vma->vm_end - vma->vm_start)); + goto done; + } + + /* create mapping + update buffer list */ + retval = -ENOMEM; + map = kmalloc(sizeof(struct videobuf_mapping),GFP_KERNEL); + if (NULL == map) + goto done; + for (size = 0, i = first; i <= last; size += q->bufs[i++]->bsize) { + q->bufs[i]->map = map; + q->bufs[i]->baddr = vma->vm_start + size; + } + map->count = 1; + map->start = vma->vm_start; + map->end = vma->vm_end; + map->q = q; + vma->vm_ops = &videobuf_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + vma->vm_private_data = map; + dprintk(1,"mmap %p: %08lx-%08lx pgoff %08lx bufs %d-%d\n", + map,vma->vm_start,vma->vm_end,vma->vm_pgoff,first,last); + retval = 0; + + done: + up(&q->lock); + return retval; +} + +/* --------------------------------------------------------------------- */ + +EXPORT_SYMBOL_GPL(videobuf_vmalloc_to_sg); + +EXPORT_SYMBOL_GPL(videobuf_dma_init_user); +EXPORT_SYMBOL_GPL(videobuf_dma_init_kernel); +EXPORT_SYMBOL_GPL(videobuf_dma_init_overlay); +EXPORT_SYMBOL_GPL(videobuf_dma_pci_map); +EXPORT_SYMBOL_GPL(videobuf_dma_pci_sync); +EXPORT_SYMBOL_GPL(videobuf_dma_pci_unmap); +EXPORT_SYMBOL_GPL(videobuf_dma_free); + +EXPORT_SYMBOL_GPL(videobuf_alloc); +EXPORT_SYMBOL_GPL(videobuf_waiton); +EXPORT_SYMBOL_GPL(videobuf_iolock); + +EXPORT_SYMBOL_GPL(videobuf_queue_init); +EXPORT_SYMBOL_GPL(videobuf_queue_cancel); +EXPORT_SYMBOL_GPL(videobuf_queue_is_busy); + +EXPORT_SYMBOL_GPL(videobuf_next_field); +EXPORT_SYMBOL_GPL(videobuf_status); +EXPORT_SYMBOL_GPL(videobuf_reqbufs); +EXPORT_SYMBOL_GPL(videobuf_querybuf); +EXPORT_SYMBOL_GPL(videobuf_qbuf); +EXPORT_SYMBOL_GPL(videobuf_dqbuf); +EXPORT_SYMBOL_GPL(videobuf_streamon); +EXPORT_SYMBOL_GPL(videobuf_streamoff); + +EXPORT_SYMBOL_GPL(videobuf_read_start); +EXPORT_SYMBOL_GPL(videobuf_read_stop); +EXPORT_SYMBOL_GPL(videobuf_read_stream); +EXPORT_SYMBOL_GPL(videobuf_read_one); +EXPORT_SYMBOL_GPL(videobuf_poll_stream); + +EXPORT_SYMBOL_GPL(videobuf_mmap_setup); +EXPORT_SYMBOL_GPL(videobuf_mmap_free); +EXPORT_SYMBOL_GPL(videobuf_mmap_mapper); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/include/linux/videodev.h b/linux/include/linux/videodev.h new file mode 100644 index 000000000..28be56ad5 --- /dev/null +++ b/linux/include/linux/videodev.h @@ -0,0 +1,466 @@ +#ifndef __LINUX_VIDEODEV_H +#define __LINUX_VIDEODEV_H + +#include <linux/types.h> +#include <linux/version.h> + +#define HAVE_V4L2 1 +#include <linux/videodev2.h> + +#ifdef __KERNEL__ + +#include <linux/poll.h> +#include <linux/mm.h> +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,69) +#include <linux/devfs_fs_kernel.h> +#endif +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) +#include <linux/device.h> +#endif + +struct video_device +{ + /* device info */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + struct device *dev; +#endif + char name[32]; + int type; /* v4l1 */ + int type2; /* v4l2 */ + int hardware; + int minor; + + /* device ops + callbacks */ + struct file_operations *fops; + void (*release)(struct video_device *vfd); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + /* old, obsolete interface -- dropped in 2.5.x, don't use it */ + int (*open)(struct video_device *, int mode); + void (*close)(struct video_device *); + long (*read)(struct video_device *, char *, unsigned long, int noblock); + long (*write)(struct video_device *, const char *, unsigned long, int noblock); + unsigned int (*poll)(struct video_device *, struct file *, poll_table *); + int (*ioctl)(struct video_device *, unsigned int , void *); + int (*mmap)(struct video_device *, const char *, unsigned long); + int (*initialize)(struct video_device *); +#endif + +#if 1 /* to be removed in 2.7.x */ + /* obsolete -- fops->owner is used instead */ + struct module *owner; + /* dev->driver_data will be used instead some day. + * Use the video_{get|set}_drvdata() helper functions, + * so the switch over will be transparent for you. + * Or use {pci|usb}_{get|set}_drvdata() directly. */ + void *priv; +#endif + + /* for videodev.c intenal usage -- please don't touch */ + int users; /* video_exclusive_{open|close} ... */ + struct semaphore lock; /* ... helper function uses these */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,69) + devfs_handle_t devfs_handle; /* devfs */ +#else + char devfs_name[64]; /* devfs */ +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + struct class_device class_dev; /* sysfs */ +#endif +}; + +#define VIDEO_MAJOR 81 + +#define VFL_TYPE_GRABBER 0 +#define VFL_TYPE_VBI 1 +#define VFL_TYPE_RADIO 2 +#define VFL_TYPE_VTX 3 + +extern int video_register_device(struct video_device *, int type, int nr); +extern void video_unregister_device(struct video_device *); +extern struct video_device* video_devdata(struct file*); + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) +#define to_video_device(cd) container_of(cd, struct video_device, class_dev) +static inline void +video_device_create_file(struct video_device *vfd, + struct class_device_attribute *attr) +{ + class_device_create_file(&vfd->class_dev, attr); +} +static inline void +video_device_remove_file(struct video_device *vfd, + struct class_device_attribute *attr) +{ + class_device_remove_file(&vfd->class_dev, attr); +} +#endif + +/* helper functions to alloc / release struct video_device, the + later can be used for video_device->release() */ +struct video_device *video_device_alloc(void); +void video_device_release(struct video_device *vfd); + +/* helper functions to access driver private data. */ +static inline void *video_get_drvdata(struct video_device *dev) +{ + return dev->priv; +} + +static inline void video_set_drvdata(struct video_device *dev, void *data) +{ + dev->priv = data; +} + +extern int video_exclusive_open(struct inode *inode, struct file *file); +extern int video_exclusive_release(struct inode *inode, struct file *file); +extern int video_usercopy(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg, + int (*func)(struct inode *inode, struct file *file, + unsigned int cmd, void *arg)); +#endif /* __KERNEL__ */ + +#define VID_TYPE_CAPTURE 1 /* Can capture */ +#define VID_TYPE_TUNER 2 /* Can tune */ +#define VID_TYPE_TELETEXT 4 /* Does teletext */ +#define VID_TYPE_OVERLAY 8 /* Overlay onto frame buffer */ +#define VID_TYPE_CHROMAKEY 16 /* Overlay by chromakey */ +#define VID_TYPE_CLIPPING 32 /* Can clip */ +#define VID_TYPE_FRAMERAM 64 /* Uses the frame buffer memory */ +#define VID_TYPE_SCALES 128 /* Scalable */ +#define VID_TYPE_MONOCHROME 256 /* Monochrome only */ +#define VID_TYPE_SUBCAPTURE 512 /* Can capture subareas of the image */ +#define VID_TYPE_MPEG_DECODER 1024 /* Can decode MPEG streams */ +#define VID_TYPE_MPEG_ENCODER 2048 /* Can encode MPEG streams */ +#define VID_TYPE_MJPEG_DECODER 4096 /* Can decode MJPEG streams */ +#define VID_TYPE_MJPEG_ENCODER 8192 /* Can encode MJPEG streams */ + +struct video_capability +{ + char name[32]; + int type; + int channels; /* Num channels */ + int audios; /* Num audio devices */ + int maxwidth; /* Supported width */ + int maxheight; /* And height */ + int minwidth; /* Supported width */ + int minheight; /* And height */ +}; + + +struct video_channel +{ + int channel; + char name[32]; + int tuners; + __u32 flags; +#define VIDEO_VC_TUNER 1 /* Channel has a tuner */ +#define VIDEO_VC_AUDIO 2 /* Channel has audio */ + __u16 type; +#define VIDEO_TYPE_TV 1 +#define VIDEO_TYPE_CAMERA 2 + __u16 norm; /* Norm set by channel */ +}; + +struct video_tuner +{ + int tuner; + char name[32]; + unsigned long rangelow, rangehigh; /* Tuner range */ + __u32 flags; +#define VIDEO_TUNER_PAL 1 +#define VIDEO_TUNER_NTSC 2 +#define VIDEO_TUNER_SECAM 4 +#define VIDEO_TUNER_LOW 8 /* Uses KHz not MHz */ +#define VIDEO_TUNER_NORM 16 /* Tuner can set norm */ +#define VIDEO_TUNER_STEREO_ON 128 /* Tuner is seeing stereo */ +#define VIDEO_TUNER_RDS_ON 256 /* Tuner is seeing an RDS datastream */ +#define VIDEO_TUNER_MBS_ON 512 /* Tuner is seeing an MBS datastream */ + __u16 mode; /* PAL/NTSC/SECAM/OTHER */ +#define VIDEO_MODE_PAL 0 +#define VIDEO_MODE_NTSC 1 +#define VIDEO_MODE_SECAM 2 +#define VIDEO_MODE_AUTO 3 + __u16 signal; /* Signal strength 16bit scale */ +}; + +struct video_picture +{ + __u16 brightness; + __u16 hue; + __u16 colour; + __u16 contrast; + __u16 whiteness; /* Black and white only */ + __u16 depth; /* Capture depth */ + __u16 palette; /* Palette in use */ +#define VIDEO_PALETTE_GREY 1 /* Linear greyscale */ +#define VIDEO_PALETTE_HI240 2 /* High 240 cube (BT848) */ +#define VIDEO_PALETTE_RGB565 3 /* 565 16 bit RGB */ +#define VIDEO_PALETTE_RGB24 4 /* 24bit RGB */ +#define VIDEO_PALETTE_RGB32 5 /* 32bit RGB */ +#define VIDEO_PALETTE_RGB555 6 /* 555 15bit RGB */ +#define VIDEO_PALETTE_YUV422 7 /* YUV422 capture */ +#define VIDEO_PALETTE_YUYV 8 +#define VIDEO_PALETTE_UYVY 9 /* The great thing about standards is ... */ +#define VIDEO_PALETTE_YUV420 10 +#define VIDEO_PALETTE_YUV411 11 /* YUV411 capture */ +#define VIDEO_PALETTE_RAW 12 /* RAW capture (BT848) */ +#define VIDEO_PALETTE_YUV422P 13 /* YUV 4:2:2 Planar */ +#define VIDEO_PALETTE_YUV411P 14 /* YUV 4:1:1 Planar */ +#define VIDEO_PALETTE_YUV420P 15 /* YUV 4:2:0 Planar */ +#define VIDEO_PALETTE_YUV410P 16 /* YUV 4:1:0 Planar */ +#define VIDEO_PALETTE_PLANAR 13 /* start of planar entries */ +#define VIDEO_PALETTE_COMPONENT 7 /* start of component entries */ +}; + +struct video_audio +{ + int audio; /* Audio channel */ + __u16 volume; /* If settable */ + __u16 bass, treble; + __u32 flags; +#define VIDEO_AUDIO_MUTE 1 +#define VIDEO_AUDIO_MUTABLE 2 +#define VIDEO_AUDIO_VOLUME 4 +#define VIDEO_AUDIO_BASS 8 +#define VIDEO_AUDIO_TREBLE 16 +#define VIDEO_AUDIO_BALANCE 32 + char name[16]; +#define VIDEO_SOUND_MONO 1 +#define VIDEO_SOUND_STEREO 2 +#define VIDEO_SOUND_LANG1 4 +#define VIDEO_SOUND_LANG2 8 + __u16 mode; + __u16 balance; /* Stereo balance */ + __u16 step; /* Step actual volume uses */ +}; + +struct video_clip +{ + __s32 x,y; + __s32 width, height; + struct video_clip *next; /* For user use/driver use only */ +}; + +struct video_window +{ + __u32 x,y; /* Position of window */ + __u32 width,height; /* Its size */ + __u32 chromakey; + __u32 flags; + struct video_clip *clips; /* Set only */ + int clipcount; +#define VIDEO_WINDOW_INTERLACE 1 +#define VIDEO_WINDOW_CHROMAKEY 16 /* Overlay by chromakey */ +#define VIDEO_CLIP_BITMAP -1 +/* bitmap is 1024x625, a '1' bit represents a clipped pixel */ +#define VIDEO_CLIPMAP_SIZE (128 * 625) +}; + +struct video_capture +{ + __u32 x,y; /* Offsets into image */ + __u32 width, height; /* Area to capture */ + __u16 decimation; /* Decimation divider */ + __u16 flags; /* Flags for capture */ +#define VIDEO_CAPTURE_ODD 0 /* Temporal */ +#define VIDEO_CAPTURE_EVEN 1 +}; + +struct video_buffer +{ + void *base; + int height,width; + int depth; + int bytesperline; +}; + +struct video_mmap +{ + unsigned int frame; /* Frame (0 - n) for double buffer */ + int height,width; + unsigned int format; /* should be VIDEO_PALETTE_* */ +}; + +struct video_key +{ + __u8 key[8]; + __u32 flags; +}; + + +#define VIDEO_MAX_FRAME 32 + +struct video_mbuf +{ + int size; /* Total memory to map */ + int frames; /* Frames */ + int offsets[VIDEO_MAX_FRAME]; +}; + + +#define VIDEO_NO_UNIT (-1) + + +struct video_unit +{ + int video; /* Video minor */ + int vbi; /* VBI minor */ + int radio; /* Radio minor */ + int audio; /* Audio minor */ + int teletext; /* Teletext minor */ +}; + +struct vbi_format { + __u32 sampling_rate; /* in Hz */ + __u32 samples_per_line; + __u32 sample_format; /* VIDEO_PALETTE_RAW only (1 byte) */ + __s32 start[2]; /* starting line for each frame */ + __u32 count[2]; /* count of lines for each frame */ + __u32 flags; +#define VBI_UNSYNC 1 /* can distingues between top/bottom field */ +#define VBI_INTERLACED 2 /* lines are interlaced */ +}; + +/* video_info is biased towards hardware mpeg encode/decode */ +/* but it could apply generically to any hardware compressor/decompressor */ +struct video_info +{ + __u32 frame_count; /* frames output since decode/encode began */ + __u32 h_size; /* current unscaled horizontal size */ + __u32 v_size; /* current unscaled veritcal size */ + __u32 smpte_timecode; /* current SMPTE timecode (for current GOP) */ + __u32 picture_type; /* current picture type */ + __u32 temporal_reference; /* current temporal reference */ + __u8 user_data[256]; /* user data last found in compressed stream */ + /* user_data[0] contains user data flags, user_data[1] has count */ +}; + +/* generic structure for setting playback modes */ +struct video_play_mode +{ + int mode; + int p1; + int p2; +}; + +/* for loading microcode / fpga programming */ +struct video_code +{ + char loadwhat[16]; /* name or tag of file being passed */ + int datasize; + __u8 *data; +}; + +#define VIDIOCGCAP _IOR('v',1,struct video_capability) /* Get capabilities */ +#define VIDIOCGCHAN _IOWR('v',2,struct video_channel) /* Get channel info (sources) */ +#define VIDIOCSCHAN _IOW('v',3,struct video_channel) /* Set channel */ +#define VIDIOCGTUNER _IOWR('v',4,struct video_tuner) /* Get tuner abilities */ +#define VIDIOCSTUNER _IOW('v',5,struct video_tuner) /* Tune the tuner for the current channel */ +#define VIDIOCGPICT _IOR('v',6,struct video_picture) /* Get picture properties */ +#define VIDIOCSPICT _IOW('v',7,struct video_picture) /* Set picture properties */ +#define VIDIOCCAPTURE _IOW('v',8,int) /* Start, end capture */ +#define VIDIOCGWIN _IOR('v',9, struct video_window) /* Get the video overlay window */ +#define VIDIOCSWIN _IOW('v',10, struct video_window) /* Set the video overlay window - passes clip list for hardware smarts , chromakey etc */ +#define VIDIOCGFBUF _IOR('v',11, struct video_buffer) /* Get frame buffer */ +#define VIDIOCSFBUF _IOW('v',12, struct video_buffer) /* Set frame buffer - root only */ +#define VIDIOCKEY _IOR('v',13, struct video_key) /* Video key event - to dev 255 is to all - cuts capture on all DMA windows with this key (0xFFFFFFFF == all) */ +#define VIDIOCGFREQ _IOR('v',14, unsigned long) /* Set tuner */ +#define VIDIOCSFREQ _IOW('v',15, unsigned long) /* Set tuner */ +#define VIDIOCGAUDIO _IOR('v',16, struct video_audio) /* Get audio info */ +#define VIDIOCSAUDIO _IOW('v',17, struct video_audio) /* Audio source, mute etc */ +#define VIDIOCSYNC _IOW('v',18, int) /* Sync with mmap grabbing */ +#define VIDIOCMCAPTURE _IOW('v',19, struct video_mmap) /* Grab frames */ +#define VIDIOCGMBUF _IOR('v',20, struct video_mbuf) /* Memory map buffer info */ +#define VIDIOCGUNIT _IOR('v',21, struct video_unit) /* Get attached units */ +#define VIDIOCGCAPTURE _IOR('v',22, struct video_capture) /* Get subcapture */ +#define VIDIOCSCAPTURE _IOW('v',23, struct video_capture) /* Set subcapture */ +#define VIDIOCSPLAYMODE _IOW('v',24, struct video_play_mode) /* Set output video mode/feature */ +#define VIDIOCSWRITEMODE _IOW('v',25, int) /* Set write mode */ +#define VIDIOCGPLAYINFO _IOR('v',26, struct video_info) /* Get current playback info from hardware */ +#define VIDIOCSMICROCODE _IOW('v',27, struct video_code) /* Load microcode into hardware */ +#define VIDIOCGVBIFMT _IOR('v',28, struct vbi_format) /* Get VBI information */ +#define VIDIOCSVBIFMT _IOW('v',29, struct vbi_format) /* Set VBI information */ + + +#define BASE_VIDIOCPRIVATE 192 /* 192-255 are private */ + +/* VIDIOCSWRITEMODE */ +#define VID_WRITE_MPEG_AUD 0 +#define VID_WRITE_MPEG_VID 1 +#define VID_WRITE_OSD 2 +#define VID_WRITE_TTX 3 +#define VID_WRITE_CC 4 +#define VID_WRITE_MJPEG 5 + +/* VIDIOCSPLAYMODE */ +#define VID_PLAY_VID_OUT_MODE 0 + /* p1: = VIDEO_MODE_PAL, VIDEO_MODE_NTSC, etc ... */ +#define VID_PLAY_GENLOCK 1 + /* p1: 0 = OFF, 1 = ON */ + /* p2: GENLOCK FINE DELAY value */ +#define VID_PLAY_NORMAL 2 +#define VID_PLAY_PAUSE 3 +#define VID_PLAY_SINGLE_FRAME 4 +#define VID_PLAY_FAST_FORWARD 5 +#define VID_PLAY_SLOW_MOTION 6 +#define VID_PLAY_IMMEDIATE_NORMAL 7 +#define VID_PLAY_SWITCH_CHANNELS 8 +#define VID_PLAY_FREEZE_FRAME 9 +#define VID_PLAY_STILL_MODE 10 +#define VID_PLAY_MASTER_MODE 11 + /* p1: see below */ +#define VID_PLAY_MASTER_NONE 1 +#define VID_PLAY_MASTER_VIDEO 2 +#define VID_PLAY_MASTER_AUDIO 3 +#define VID_PLAY_ACTIVE_SCANLINES 12 + /* p1 = first active; p2 = last active */ +#define VID_PLAY_RESET 13 +#define VID_PLAY_END_MARK 14 + + + +#define VID_HARDWARE_BT848 1 +#define VID_HARDWARE_QCAM_BW 2 +#define VID_HARDWARE_PMS 3 +#define VID_HARDWARE_QCAM_C 4 +#define VID_HARDWARE_PSEUDO 5 +#define VID_HARDWARE_SAA5249 6 +#define VID_HARDWARE_AZTECH 7 +#define VID_HARDWARE_SF16MI 8 +#define VID_HARDWARE_RTRACK 9 +#define VID_HARDWARE_ZOLTRIX 10 +#define VID_HARDWARE_SAA7146 11 +#define VID_HARDWARE_VIDEUM 12 /* Reserved for Winnov videum */ +#define VID_HARDWARE_RTRACK2 13 +#define VID_HARDWARE_PERMEDIA2 14 /* Reserved for Permedia2 */ +#define VID_HARDWARE_RIVA128 15 /* Reserved for RIVA 128 */ +#define VID_HARDWARE_PLANB 16 /* PowerMac motherboard video-in */ +#define VID_HARDWARE_BROADWAY 17 /* Broadway project */ +#define VID_HARDWARE_GEMTEK 18 +#define VID_HARDWARE_TYPHOON 19 +#define VID_HARDWARE_VINO 20 /* SGI Indy Vino */ +#define VID_HARDWARE_CADET 21 /* Cadet radio */ +#define VID_HARDWARE_TRUST 22 /* Trust FM Radio */ +#define VID_HARDWARE_TERRATEC 23 /* TerraTec ActiveRadio */ +#define VID_HARDWARE_CPIA 24 +#define VID_HARDWARE_ZR36120 25 /* Zoran ZR36120/ZR36125 */ +#define VID_HARDWARE_ZR36067 26 /* Zoran ZR36067/36060 */ +#define VID_HARDWARE_OV511 27 +#define VID_HARDWARE_ZR356700 28 /* Zoran 36700 series */ +#define VID_HARDWARE_W9966 29 +#define VID_HARDWARE_SE401 30 /* SE401 USB webcams */ +#define VID_HARDWARE_PWC 31 /* Philips webcams */ +#define VID_HARDWARE_MEYE 32 /* Sony Vaio MotionEye cameras */ +#define VID_HARDWARE_CPIA2 33 +#define VID_HARDWARE_VICAM 34 +#define VID_HARDWARE_SF16FMR2 35 +#define VID_HARDWARE_W9968CF 36 +#define VID_HARDWARE_SAA7114H 37 +#endif /* __LINUX_VIDEODEV_H */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/include/linux/videodev2.h b/linux/include/linux/videodev2.h new file mode 100644 index 000000000..30c776df2 --- /dev/null +++ b/linux/include/linux/videodev2.h @@ -0,0 +1,939 @@ +#ifndef __LINUX_VIDEODEV2_H +#define __LINUX_VIDEODEV2_H +/* + * Video for Linux Two + * + * Header file for v4l or V4L2 drivers and applications, for + * Linux kernels 2.2.x or 2.4.x. + * + * See http://bytesex.org/v4l/ for API specs and other + * v4l2 documentation. + * + * Author: Bill Dirks <bdirks@pacbell.net> + * Justin Schoeman + * et al. + */ +#ifdef __KERNEL__ +#include <linux/time.h> /* need struct timeval */ +#endif + +/* + * M I S C E L L A N E O U S + */ + +/* Four-character-code (FOURCC) */ +#define v4l2_fourcc(a,b,c,d)\ + (((__u32)(a)<<0)|((__u32)(b)<<8)|((__u32)(c)<<16)|((__u32)(d)<<24)) + +/* + * E N U M S + */ +enum v4l2_field { + V4L2_FIELD_ANY = 0, /* driver can choose from none, + top, bottom, interlaced + depending on whatever it thinks + is approximate ... */ + V4L2_FIELD_NONE = 1, /* this device has no fields ... */ + V4L2_FIELD_TOP = 2, /* top field only */ + V4L2_FIELD_BOTTOM = 3, /* bottom field only */ + V4L2_FIELD_INTERLACED = 4, /* both fields interlaced */ + V4L2_FIELD_SEQ_TB = 5, /* both fields sequential into one + buffer, top-bottom order */ + V4L2_FIELD_SEQ_BT = 6, /* same as above + bottom-top order */ + V4L2_FIELD_ALTERNATE = 7, /* both fields alternating into + separate buffers */ +}; +#define V4L2_FIELD_HAS_TOP(field) \ + ((field) == V4L2_FIELD_TOP ||\ + (field) == V4L2_FIELD_INTERLACED ||\ + (field) == V4L2_FIELD_SEQ_TB ||\ + (field) == V4L2_FIELD_SEQ_BT) +#define V4L2_FIELD_HAS_BOTTOM(field) \ + ((field) == V4L2_FIELD_BOTTOM ||\ + (field) == V4L2_FIELD_INTERLACED ||\ + (field) == V4L2_FIELD_SEQ_TB ||\ + (field) == V4L2_FIELD_SEQ_BT) +#define V4L2_FIELD_HAS_BOTH(field) \ + ((field) == V4L2_FIELD_INTERLACED ||\ + (field) == V4L2_FIELD_SEQ_TB ||\ + (field) == V4L2_FIELD_SEQ_BT) + +enum v4l2_buf_type { + V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, + V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, + V4L2_BUF_TYPE_VIDEO_OVERLAY = 3, + V4L2_BUF_TYPE_VBI_CAPTURE = 4, + V4L2_BUF_TYPE_VBI_OUTPUT = 5, + V4L2_BUF_TYPE_PRIVATE = 0x80, +}; + +enum v4l2_ctrl_type { + V4L2_CTRL_TYPE_INTEGER = 1, + V4L2_CTRL_TYPE_BOOLEAN = 2, + V4L2_CTRL_TYPE_MENU = 3, + V4L2_CTRL_TYPE_BUTTON = 4, +}; + +enum v4l2_tuner_type { + V4L2_TUNER_RADIO = 1, + V4L2_TUNER_ANALOG_TV = 2, +}; + +enum v4l2_memory { + V4L2_MEMORY_MMAP = 1, + V4L2_MEMORY_USERPTR = 2, + V4L2_MEMORY_OVERLAY = 3, +}; + +/* see also http://vektor.theorem.ca/graphics/ycbcr/ */ +enum v4l2_colorspace { + /* ITU-R 601 -- broadcast NTSC/PAL */ + V4L2_COLORSPACE_SMPTE170M = 1, + + /* 1125-Line (US) HDTV */ + V4L2_COLORSPACE_SMPTE240M = 2, + + /* HD and modern captures. */ + V4L2_COLORSPACE_REC709 = 3, + + /* broken BT878 extents (601, luma range 16-253 instead of 16-235) */ + V4L2_COLORSPACE_BT878 = 4, + + /* These should be useful. Assume 601 extents. */ + V4L2_COLORSPACE_470_SYSTEM_M = 5, + V4L2_COLORSPACE_470_SYSTEM_BG = 6, + + /* I know there will be cameras that send this. So, this is + * unspecified chromaticities and full 0-255 on each of the + * Y'CbCr components + */ + V4L2_COLORSPACE_JPEG = 7, + + /* For RGB colourspaces, this is probably a good start. */ + V4L2_COLORSPACE_SRGB = 8, +}; + +enum v4l2_priority { + V4L2_PRIORITY_UNSET = 0, /* not initialized */ + V4L2_PRIORITY_BACKGROUND = 1, + V4L2_PRIORITY_INTERACTIVE = 2, + V4L2_PRIORITY_RECORD = 3, + V4L2_PRIORITY_DEFAULT = V4L2_PRIORITY_INTERACTIVE, +}; + +struct v4l2_rect { + __s32 left; + __s32 top; + __s32 width; + __s32 height; +}; + +struct v4l2_fract { + __u32 numerator; + __u32 denominator; +}; + +/* + * D R I V E R C A P A B I L I T I E S + */ +struct v4l2_capability +{ + __u8 driver[16]; /* i.e. "bttv" */ + __u8 card[32]; /* i.e. "Hauppauge WinTV" */ + __u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */ + __u32 version; /* should use KERNEL_VERSION() */ + __u32 capabilities; /* Device capabilities */ + __u32 reserved[4]; +}; + +/* Values for 'capabilities' field */ +#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* Is a video capture device */ +#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 /* Is a video output device */ +#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 /* Can do video overlay */ +#define V4L2_CAP_VBI_CAPTURE 0x00000010 /* Is a VBI capture device */ +#define V4L2_CAP_VBI_OUTPUT 0x00000020 /* Is a VBI output device */ +#define V4L2_CAP_RDS_CAPTURE 0x00000100 /* RDS data capture */ + +#define V4L2_CAP_TUNER 0x00010000 /* has a tuner */ +#define V4L2_CAP_AUDIO 0x00020000 /* has audio support */ +#define V4L2_CAP_RADIO 0x00040000 /* is a radio device */ + +#define V4L2_CAP_READWRITE 0x01000000 /* read/write systemcalls */ +#define V4L2_CAP_ASYNCIO 0x02000000 /* async I/O */ +#define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */ + +/* + * V I D E O I M A G E F O R M A T + */ + +struct v4l2_pix_format +{ + __u32 width; + __u32 height; + __u32 pixelformat; + enum v4l2_field field; + __u32 bytesperline; /* for padding, zero if unused */ + __u32 sizeimage; + enum v4l2_colorspace colorspace; + __u32 priv; /* private data, depends on pixelformat */ +}; + +/* Pixel format FOURCC depth Description */ +#define V4L2_PIX_FMT_RGB332 v4l2_fourcc('R','G','B','1') /* 8 RGB-3-3-2 */ +#define V4L2_PIX_FMT_RGB555 v4l2_fourcc('R','G','B','O') /* 16 RGB-5-5-5 */ +#define V4L2_PIX_FMT_RGB565 v4l2_fourcc('R','G','B','P') /* 16 RGB-5-6-5 */ +#define V4L2_PIX_FMT_RGB555X v4l2_fourcc('R','G','B','Q') /* 16 RGB-5-5-5 BE */ +#define V4L2_PIX_FMT_RGB565X v4l2_fourcc('R','G','B','R') /* 16 RGB-5-6-5 BE */ +#define V4L2_PIX_FMT_BGR24 v4l2_fourcc('B','G','R','3') /* 24 BGR-8-8-8 */ +#define V4L2_PIX_FMT_RGB24 v4l2_fourcc('R','G','B','3') /* 24 RGB-8-8-8 */ +#define V4L2_PIX_FMT_BGR32 v4l2_fourcc('B','G','R','4') /* 32 BGR-8-8-8-8 */ +#define V4L2_PIX_FMT_RGB32 v4l2_fourcc('R','G','B','4') /* 32 RGB-8-8-8-8 */ +#define V4L2_PIX_FMT_GREY v4l2_fourcc('G','R','E','Y') /* 8 Greyscale */ +#define V4L2_PIX_FMT_YVU410 v4l2_fourcc('Y','V','U','9') /* 9 YVU 4:1:0 */ +#define V4L2_PIX_FMT_YVU420 v4l2_fourcc('Y','V','1','2') /* 12 YVU 4:2:0 */ +#define V4L2_PIX_FMT_YUYV v4l2_fourcc('Y','U','Y','V') /* 16 YUV 4:2:2 */ +#define V4L2_PIX_FMT_UYVY v4l2_fourcc('U','Y','V','Y') /* 16 YUV 4:2:2 */ +#define V4L2_PIX_FMT_YUV422P v4l2_fourcc('4','2','2','P') /* 16 YVU422 planar */ +#define V4L2_PIX_FMT_YUV411P v4l2_fourcc('4','1','1','P') /* 16 YVU411 planar */ +#define V4L2_PIX_FMT_Y41P v4l2_fourcc('Y','4','1','P') /* 12 YUV 4:1:1 */ + +/* two planes -- one Y, one Cr + Cb interleaved */ +#define V4L2_PIX_FMT_NV12 v4l2_fourcc('N','V','1','2') /* 12 Y/CbCr 4:2:0 */ +#define V4L2_PIX_FMT_NV21 v4l2_fourcc('N','V','2','1') /* 12 Y/CrCb 4:2:0 */ + +/* The following formats are not defined in the V4L2 specification */ +#define V4L2_PIX_FMT_YUV410 v4l2_fourcc('Y','U','V','9') /* 9 YUV 4:1:0 */ +#define V4L2_PIX_FMT_YUV420 v4l2_fourcc('Y','U','1','2') /* 12 YUV 4:2:0 */ +#define V4L2_PIX_FMT_YYUV v4l2_fourcc('Y','Y','U','V') /* 16 YUV 4:2:2 */ +#define V4L2_PIX_FMT_HI240 v4l2_fourcc('H','I','2','4') /* 8 8-bit color */ + +/* compressed formats */ +#define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M','J','P','G') /* Motion-JPEG */ +#define V4L2_PIX_FMT_JPEG v4l2_fourcc('J','P','E','G') /* JFIF JPEG */ +#define V4L2_PIX_FMT_DV v4l2_fourcc('d','v','s','d') /* 1394 */ +#define V4L2_PIX_FMT_MPEG v4l2_fourcc('M','P','E','G') /* MPEG */ + +/* Vendor-specific formats */ +#define V4L2_PIX_FMT_WNVA v4l2_fourcc('W','N','V','A') /* Winnov hw compress */ + +/* + * F O R M A T E N U M E R A T I O N + */ +struct v4l2_fmtdesc +{ + __u32 index; /* Format number */ + enum v4l2_buf_type type; /* buffer type */ + __u32 flags; + __u8 description[32]; /* Description string */ + __u32 pixelformat; /* Format fourcc */ + __u32 reserved[4]; +}; + +#define V4L2_FMT_FLAG_COMPRESSED 0x0001 + + +/* + * T I M E C O D E + */ +struct v4l2_timecode +{ + __u32 type; + __u32 flags; + __u8 frames; + __u8 seconds; + __u8 minutes; + __u8 hours; + __u8 userbits[4]; +}; + +/* Type */ +#define V4L2_TC_TYPE_24FPS 1 +#define V4L2_TC_TYPE_25FPS 2 +#define V4L2_TC_TYPE_30FPS 3 +#define V4L2_TC_TYPE_50FPS 4 +#define V4L2_TC_TYPE_60FPS 5 + +/* Flags */ +#define V4L2_TC_FLAG_DROPFRAME 0x0001 /* "drop-frame" mode */ +#define V4L2_TC_FLAG_COLORFRAME 0x0002 +#define V4L2_TC_USERBITS_field 0x000C +#define V4L2_TC_USERBITS_USERDEFINED 0x0000 +#define V4L2_TC_USERBITS_8BITCHARS 0x0008 +/* The above is based on SMPTE timecodes */ + + +/* + * C O M P R E S S I O N P A R A M E T E R S + */ +#if 0 +/* ### generic compression settings don't work, there is too much + * ### codec-specific stuff. Maybe reuse that for MPEG codec settings + * ### later ... */ +struct v4l2_compression +{ + __u32 quality; + __u32 keyframerate; + __u32 pframerate; + __u32 reserved[5]; + +/* what we'll need for MPEG, extracted from some postings on + the v4l list (Gert Vervoort, PlasmaJohn). + +system stream: + - type: elementary stream(ES), packatised elementary stream(s) (PES) + program stream(PS), transport stream(TS) + - system bitrate + - PS packet size (DVD: 2048 bytes, VCD: 2324 bytes) + - TS video PID + - TS audio PID + - TS PCR PID + - TS system information tables (PAT, PMT, CAT, NIT and SIT) + - (MPEG-1 systems stream vs. MPEG-2 program stream (TS not supported + by MPEG-1 systems) + +audio: + - type: MPEG (+Layer I,II,III), AC-3, LPCM + - bitrate + - sampling frequency (DVD: 48 Khz, VCD: 44.1 KHz, 32 kHz) + - Trick Modes? (ff, rew) + - Copyright + - Inverse Telecine + +video: + - picturesize (SIF, 1/2 D1, 2/3 D1, D1) and PAL/NTSC norm can be set + through excisting V4L2 controls + - noise reduction, parameters encoder specific? + - MPEG video version: MPEG-1, MPEG-2 + - GOP (Group Of Pictures) definition: + - N: number of frames per GOP + - M: distance between reference (I,P) frames + - open/closed GOP + - quantiser matrix: inter Q matrix (64 bytes) and intra Q matrix (64 bytes) + - quantiser scale: linear or logarithmic + - scanning: alternate or zigzag + - bitrate mode: CBR (constant bitrate) or VBR (variable bitrate). + - target video bitrate for CBR + - target video bitrate for VBR + - maximum video bitrate for VBR - min. quantiser value for VBR + - max. quantiser value for VBR + - adaptive quantisation value + - return the number of bytes per GOP or bitrate for bitrate monitoring + +*/ +}; +#endif + +struct v4l2_jpegcompression +{ + int quality; + + int APPn; /* Number of APP segment to be written, + * must be 0..15 */ + int APP_len; /* Length of data in JPEG APPn segment */ + char APP_data[60]; /* Data in the JPEG APPn segment. */ + + int COM_len; /* Length of data in JPEG COM segment */ + char COM_data[60]; /* Data in JPEG COM segment */ + + __u32 jpeg_markers; /* Which markers should go into the JPEG + * output. Unless you exactly know what + * you do, leave them untouched. + * Inluding less markers will make the + * resulting code smaller, but there will + * be fewer aplications which can read it. + * The presence of the APP and COM marker + * is influenced by APP_len and COM_len + * ONLY, not by this property! */ + +#define V4L2_JPEG_MARKER_DHT (1<<3) /* Define Huffman Tables */ +#define V4L2_JPEG_MARKER_DQT (1<<4) /* Define Quantization Tables */ +#define V4L2_JPEG_MARKER_DRI (1<<5) /* Define Restart Interval */ +#define V4L2_JPEG_MARKER_COM (1<<6) /* Comment segment */ +#define V4L2_JPEG_MARKER_APP (1<<7) /* App segment, driver will + * allways use APP0 */ +}; + + +/* + * M E M O R Y - M A P P I N G B U F F E R S + */ +struct v4l2_requestbuffers +{ + __u32 count; + enum v4l2_buf_type type; + enum v4l2_memory memory; + __u32 reserved[2]; +}; + +struct v4l2_buffer +{ + __u32 index; + enum v4l2_buf_type type; + __u32 bytesused; + __u32 flags; + enum v4l2_field field; + struct timeval timestamp; + struct v4l2_timecode timecode; + __u32 sequence; + + /* memory location */ + enum v4l2_memory memory; + union { + __u32 offset; + unsigned long userptr; + } m; + __u32 length; + __u32 input; + __u32 reserved; +}; + +/* Flags for 'flags' field */ +#define V4L2_BUF_FLAG_MAPPED 0x0001 /* Buffer is mapped (flag) */ +#define V4L2_BUF_FLAG_QUEUED 0x0002 /* Buffer is queued for processing */ +#define V4L2_BUF_FLAG_DONE 0x0004 /* Buffer is ready */ +#define V4L2_BUF_FLAG_KEYFRAME 0x0008 /* Image is a keyframe (I-frame) */ +#define V4L2_BUF_FLAG_PFRAME 0x0010 /* Image is a P-frame */ +#define V4L2_BUF_FLAG_BFRAME 0x0020 /* Image is a B-frame */ +#define V4L2_BUF_FLAG_TIMECODE 0x0100 /* timecode field is valid */ +#define V4L2_BUF_FLAG_INPUT 0x0200 /* input field is valid */ + +/* + * O V E R L A Y P R E V I E W + */ +struct v4l2_framebuffer +{ + __u32 capability; + __u32 flags; +/* FIXME: in theory we should pass something like PCI device + memory + * region + offset instead of some physical address */ + void* base; + struct v4l2_pix_format fmt; +}; +/* Flags for the 'capability' field. Read only */ +#define V4L2_FBUF_CAP_EXTERNOVERLAY 0x0001 +#define V4L2_FBUF_CAP_CHROMAKEY 0x0002 +#define V4L2_FBUF_CAP_LIST_CLIPPING 0x0004 +#define V4L2_FBUF_CAP_BITMAP_CLIPPING 0x0008 +/* Flags for the 'flags' field. */ +#define V4L2_FBUF_FLAG_PRIMARY 0x0001 +#define V4L2_FBUF_FLAG_OVERLAY 0x0002 +#define V4L2_FBUF_FLAG_CHROMAKEY 0x0004 + +struct v4l2_clip +{ + struct v4l2_rect c; + struct v4l2_clip *next; +}; + +struct v4l2_window +{ + struct v4l2_rect w; + enum v4l2_field field; + __u32 chromakey; + struct v4l2_clip *clips; + __u32 clipcount; + void *bitmap; +}; + + +/* + * C A P T U R E P A R A M E T E R S + */ +struct v4l2_captureparm +{ + __u32 capability; /* Supported modes */ + __u32 capturemode; /* Current mode */ + struct v4l2_fract timeperframe; /* Time per frame in .1us units */ + __u32 extendedmode; /* Driver-specific extensions */ + __u32 readbuffers; /* # of buffers for read */ + __u32 reserved[4]; +}; +/* Flags for 'capability' and 'capturemode' fields */ +#define V4L2_MODE_HIGHQUALITY 0x0001 /* High quality imaging mode */ +#define V4L2_CAP_TIMEPERFRAME 0x1000 /* timeperframe field is supported */ + +struct v4l2_outputparm +{ + __u32 capability; /* Supported modes */ + __u32 outputmode; /* Current mode */ + struct v4l2_fract timeperframe; /* Time per frame in seconds */ + __u32 extendedmode; /* Driver-specific extensions */ + __u32 writebuffers; /* # of buffers for write */ + __u32 reserved[4]; +}; + +/* + * I N P U T I M A G E C R O P P I N G + */ + +struct v4l2_cropcap { + enum v4l2_buf_type type; + struct v4l2_rect bounds; + struct v4l2_rect defrect; + struct v4l2_fract pixelaspect; +}; + +struct v4l2_crop { + enum v4l2_buf_type type; + struct v4l2_rect c; +}; + +/* + * A N A L O G V I D E O S T A N D A R D + */ + +typedef __u64 v4l2_std_id; + +/* one bit for each */ +#define V4L2_STD_PAL_B ((v4l2_std_id)0x00000001) +#define V4L2_STD_PAL_B1 ((v4l2_std_id)0x00000002) +#define V4L2_STD_PAL_G ((v4l2_std_id)0x00000004) +#define V4L2_STD_PAL_H ((v4l2_std_id)0x00000008) +#define V4L2_STD_PAL_I ((v4l2_std_id)0x00000010) +#define V4L2_STD_PAL_D ((v4l2_std_id)0x00000020) +#define V4L2_STD_PAL_D1 ((v4l2_std_id)0x00000040) +#define V4L2_STD_PAL_K ((v4l2_std_id)0x00000080) + +#define V4L2_STD_PAL_M ((v4l2_std_id)0x00000100) +#define V4L2_STD_PAL_N ((v4l2_std_id)0x00000200) +#define V4L2_STD_PAL_Nc ((v4l2_std_id)0x00000400) +#define V4L2_STD_PAL_60 ((v4l2_std_id)0x00000800) + +#define V4L2_STD_NTSC_M ((v4l2_std_id)0x00001000) +#define V4L2_STD_NTSC_M_JP ((v4l2_std_id)0x00002000) + +#define V4L2_STD_SECAM_B ((v4l2_std_id)0x00010000) +#define V4L2_STD_SECAM_D ((v4l2_std_id)0x00020000) +#define V4L2_STD_SECAM_G ((v4l2_std_id)0x00040000) +#define V4L2_STD_SECAM_H ((v4l2_std_id)0x00080000) +#define V4L2_STD_SECAM_K ((v4l2_std_id)0x00100000) +#define V4L2_STD_SECAM_K1 ((v4l2_std_id)0x00200000) +#define V4L2_STD_SECAM_L ((v4l2_std_id)0x00400000) + +/* ATSC/HDTV */ +#define V4L2_STD_ATSC_8_VSB ((v4l2_std_id)0x01000000) +#define V4L2_STD_ATSC_16_VSB ((v4l2_std_id)0x02000000) + +/* some common needed stuff */ +#define V4L2_STD_PAL_BG (V4L2_STD_PAL_B |\ + V4L2_STD_PAL_B1 |\ + V4L2_STD_PAL_G) +#define V4L2_STD_PAL_DK (V4L2_STD_PAL_D |\ + V4L2_STD_PAL_D1 |\ + V4L2_STD_PAL_K) +#define V4L2_STD_PAL (V4L2_STD_PAL_BG |\ + V4L2_STD_PAL_DK |\ + V4L2_STD_PAL_H |\ + V4L2_STD_PAL_I) +#define V4L2_STD_NTSC (V4L2_STD_NTSC_M |\ + V4L2_STD_NTSC_M_JP) +#define V4L2_STD_SECAM (V4L2_STD_SECAM_B |\ + V4L2_STD_SECAM_D |\ + V4L2_STD_SECAM_G |\ + V4L2_STD_SECAM_H |\ + V4L2_STD_SECAM_K |\ + V4L2_STD_SECAM_K1 |\ + V4L2_STD_SECAM_L) + +#define V4L2_STD_525_60 (V4L2_STD_PAL_M |\ + V4L2_STD_PAL_60 |\ + V4L2_STD_NTSC) +#define V4L2_STD_625_50 (V4L2_STD_PAL |\ + V4L2_STD_PAL_N |\ + V4L2_STD_PAL_Nc |\ + V4L2_STD_SECAM) + +#define V4L2_STD_UNKNOWN 0 +#define V4L2_STD_ALL (V4L2_STD_525_60 |\ + V4L2_STD_625_50) + +struct v4l2_standard +{ + __u32 index; + v4l2_std_id id; + __u8 name[24]; + struct v4l2_fract frameperiod; /* Frames, not fields */ + __u32 framelines; + __u32 reserved[4]; +}; + + +/* + * V I D E O I N P U T S + */ +struct v4l2_input +{ + __u32 index; /* Which input */ + __u8 name[32]; /* Label */ + __u32 type; /* Type of input */ + __u32 audioset; /* Associated audios (bitfield) */ + __u32 tuner; /* Associated tuner */ + v4l2_std_id std; + __u32 status; + __u32 reserved[4]; +}; +/* Values for the 'type' field */ +#define V4L2_INPUT_TYPE_TUNER 1 +#define V4L2_INPUT_TYPE_CAMERA 2 + +/* field 'status' - general */ +#define V4L2_IN_ST_NO_POWER 0x00000001 /* Attached device is off */ +#define V4L2_IN_ST_NO_SIGNAL 0x00000002 +#define V4L2_IN_ST_NO_COLOR 0x00000004 + +/* field 'status' - analog */ +#define V4L2_IN_ST_NO_H_LOCK 0x00000100 /* No horizontal sync lock */ +#define V4L2_IN_ST_COLOR_KILL 0x00000200 /* Color killer is active */ + +/* field 'status' - digital */ +#define V4L2_IN_ST_NO_SYNC 0x00010000 /* No synchronization lock */ +#define V4L2_IN_ST_NO_EQU 0x00020000 /* No equalizer lock */ +#define V4L2_IN_ST_NO_CARRIER 0x00040000 /* Carrier recovery failed */ + +/* field 'status' - VCR and set-top box */ +#define V4L2_IN_ST_MACROVISION 0x01000000 /* Macrovision detected */ +#define V4L2_IN_ST_NO_ACCESS 0x02000000 /* Conditional access denied */ +#define V4L2_IN_ST_VTR 0x04000000 /* VTR time constant */ + +/* + * V I D E O O U T P U T S + */ +struct v4l2_output +{ + __u32 index; /* Which output */ + __u8 name[32]; /* Label */ + __u32 type; /* Type of output */ + __u32 audioset; /* Associated audios (bitfield) */ + __u32 modulator; /* Associated modulator */ + v4l2_std_id std; + __u32 reserved[4]; +}; +/* Values for the 'type' field */ +#define V4L2_OUTPUT_TYPE_MODULATOR 1 +#define V4L2_OUTPUT_TYPE_ANALOG 2 +#define V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY 3 + +/* + * C O N T R O L S + */ +struct v4l2_control +{ + __u32 id; + __s32 value; +}; + +/* Used in the VIDIOC_QUERYCTRL ioctl for querying controls */ +struct v4l2_queryctrl +{ + __u32 id; + enum v4l2_ctrl_type type; + __u8 name[32]; /* Whatever */ + __s32 minimum; /* Note signedness */ + __s32 maximum; + __s32 step; + __s32 default_value; + __u32 flags; + __u32 reserved[2]; +}; + +/* Used in the VIDIOC_QUERYMENU ioctl for querying menu items */ +struct v4l2_querymenu +{ + __u32 id; + __u32 index; + __u8 name[32]; /* Whatever */ + __u32 reserved; +}; + +/* Control flags */ +#define V4L2_CTRL_FLAG_DISABLED 0x0001 +#define V4L2_CTRL_FLAG_GRABBED 0x0002 + +/* Control IDs defined by V4L2 */ +#define V4L2_CID_BASE 0x00980900 +/* IDs reserved for driver specific controls */ +#define V4L2_CID_PRIVATE_BASE 0x08000000 + +#define V4L2_CID_BRIGHTNESS (V4L2_CID_BASE+0) +#define V4L2_CID_CONTRAST (V4L2_CID_BASE+1) +#define V4L2_CID_SATURATION (V4L2_CID_BASE+2) +#define V4L2_CID_HUE (V4L2_CID_BASE+3) +#define V4L2_CID_AUDIO_VOLUME (V4L2_CID_BASE+5) +#define V4L2_CID_AUDIO_BALANCE (V4L2_CID_BASE+6) +#define V4L2_CID_AUDIO_BASS (V4L2_CID_BASE+7) +#define V4L2_CID_AUDIO_TREBLE (V4L2_CID_BASE+8) +#define V4L2_CID_AUDIO_MUTE (V4L2_CID_BASE+9) +#define V4L2_CID_AUDIO_LOUDNESS (V4L2_CID_BASE+10) +#define V4L2_CID_BLACK_LEVEL (V4L2_CID_BASE+11) +#define V4L2_CID_AUTO_WHITE_BALANCE (V4L2_CID_BASE+12) +#define V4L2_CID_DO_WHITE_BALANCE (V4L2_CID_BASE+13) +#define V4L2_CID_RED_BALANCE (V4L2_CID_BASE+14) +#define V4L2_CID_BLUE_BALANCE (V4L2_CID_BASE+15) +#define V4L2_CID_GAMMA (V4L2_CID_BASE+16) +#define V4L2_CID_WHITENESS (V4L2_CID_GAMMA) /* ? Not sure */ +#define V4L2_CID_EXPOSURE (V4L2_CID_BASE+17) +#define V4L2_CID_AUTOGAIN (V4L2_CID_BASE+18) +#define V4L2_CID_GAIN (V4L2_CID_BASE+19) +#define V4L2_CID_HFLIP (V4L2_CID_BASE+20) +#define V4L2_CID_VFLIP (V4L2_CID_BASE+21) +#define V4L2_CID_HCENTER (V4L2_CID_BASE+22) +#define V4L2_CID_VCENTER (V4L2_CID_BASE+23) +#define V4L2_CID_LASTP1 (V4L2_CID_BASE+24) /* last CID + 1 */ + +/* + * T U N I N G + */ +struct v4l2_tuner +{ + __u32 index; + __u8 name[32]; + enum v4l2_tuner_type type; + __u32 capability; + __u32 rangelow; + __u32 rangehigh; + __u32 rxsubchans; + __u32 audmode; + __s32 signal; + __s32 afc; + __u32 reserved[4]; +}; + +struct v4l2_modulator +{ + __u32 index; + __u8 name[32]; + __u32 capability; + __u32 rangelow; + __u32 rangehigh; + __u32 txsubchans; + __u32 reserved[4]; +}; + +/* Flags for the 'capability' field */ +#define V4L2_TUNER_CAP_LOW 0x0001 +#define V4L2_TUNER_CAP_NORM 0x0002 +#define V4L2_TUNER_CAP_STEREO 0x0010 +#define V4L2_TUNER_CAP_LANG2 0x0020 +#define V4L2_TUNER_CAP_SAP 0x0020 +#define V4L2_TUNER_CAP_LANG1 0x0040 + +/* Flags for the 'rxsubchans' field */ +#define V4L2_TUNER_SUB_MONO 0x0001 +#define V4L2_TUNER_SUB_STEREO 0x0002 +#define V4L2_TUNER_SUB_LANG2 0x0004 +#define V4L2_TUNER_SUB_SAP 0x0004 +#define V4L2_TUNER_SUB_LANG1 0x0008 + +/* Values for the 'audmode' field */ +#define V4L2_TUNER_MODE_MONO 0x0000 +#define V4L2_TUNER_MODE_STEREO 0x0001 +#define V4L2_TUNER_MODE_LANG2 0x0002 +#define V4L2_TUNER_MODE_SAP 0x0002 +#define V4L2_TUNER_MODE_LANG1 0x0003 + +struct v4l2_frequency +{ + __u32 tuner; + enum v4l2_tuner_type type; + __u32 frequency; + __u32 reserved[8]; +}; + +/* + * A U D I O + */ +struct v4l2_audio +{ + __u32 index; + __u8 name[32]; + __u32 capability; + __u32 mode; + __u32 reserved[2]; +}; +/* Flags for the 'capability' field */ +#define V4L2_AUDCAP_STEREO 0x00001 +#define V4L2_AUDCAP_AVL 0x00002 + +/* Flags for the 'mode' field */ +#define V4L2_AUDMODE_AVL 0x00001 + +struct v4l2_audioout +{ + __u32 index; + __u8 name[32]; + __u32 capability; + __u32 mode; + __u32 reserved[2]; +}; + +/* + * D A T A S E R V I C E S ( V B I ) + * + * Data services API by Michael Schimek + */ + +struct v4l2_vbi_format +{ + __u32 sampling_rate; /* in 1 Hz */ + __u32 offset; + __u32 samples_per_line; + __u32 sample_format; /* V4L2_PIX_FMT_* */ + __s32 start[2]; + __u32 count[2]; + __u32 flags; /* V4L2_VBI_* */ + __u32 reserved[2]; /* must be zero */ +}; + +/* VBI flags */ +#define V4L2_VBI_UNSYNC (1<< 0) +#define V4L2_VBI_INTERLACED (1<< 1) + + +/* + * A G G R E G A T E S T R U C T U R E S + */ + +/* Stream data format + */ +struct v4l2_format +{ + enum v4l2_buf_type type; + union + { + struct v4l2_pix_format pix; // V4L2_BUF_TYPE_VIDEO_CAPTURE + struct v4l2_window win; // V4L2_BUF_TYPE_VIDEO_OVERLAY + struct v4l2_vbi_format vbi; // V4L2_BUF_TYPE_VBI_CAPTURE + __u8 raw_data[200]; // user-defined + } fmt; +}; + + +/* Stream type-dependent parameters + */ +struct v4l2_streamparm +{ + enum v4l2_buf_type type; + union + { + struct v4l2_captureparm capture; + struct v4l2_outputparm output; + __u8 raw_data[200]; /* user-defined */ + } parm; +}; + + + +/* + * I O C T L C O D E S F O R V I D E O D E V I C E S + * + */ +#define VIDIOC_QUERYCAP _IOR ('V', 0, struct v4l2_capability) +#define VIDIOC_RESERVED _IO ('V', 1) +#define VIDIOC_ENUM_FMT _IOWR ('V', 2, struct v4l2_fmtdesc) +#define VIDIOC_G_FMT _IOWR ('V', 4, struct v4l2_format) +#define VIDIOC_S_FMT _IOWR ('V', 5, struct v4l2_format) +#if 0 +#define VIDIOC_G_COMP _IOR ('V', 6, struct v4l2_compression) +#define VIDIOC_S_COMP _IOW ('V', 7, struct v4l2_compression) +#endif +#define VIDIOC_REQBUFS _IOWR ('V', 8, struct v4l2_requestbuffers) +#define VIDIOC_QUERYBUF _IOWR ('V', 9, struct v4l2_buffer) +#define VIDIOC_G_FBUF _IOR ('V', 10, struct v4l2_framebuffer) +#define VIDIOC_S_FBUF _IOW ('V', 11, struct v4l2_framebuffer) +#define VIDIOC_OVERLAY _IOW ('V', 14, int) +#define VIDIOC_QBUF _IOWR ('V', 15, struct v4l2_buffer) +#define VIDIOC_DQBUF _IOWR ('V', 17, struct v4l2_buffer) +#define VIDIOC_STREAMON _IOW ('V', 18, int) +#define VIDIOC_STREAMOFF _IOW ('V', 19, int) +#define VIDIOC_G_PARM _IOWR ('V', 21, struct v4l2_streamparm) +#define VIDIOC_S_PARM _IOWR ('V', 22, struct v4l2_streamparm) +#define VIDIOC_G_STD _IOR ('V', 23, v4l2_std_id) +#define VIDIOC_S_STD _IOW ('V', 24, v4l2_std_id) +#define VIDIOC_ENUMSTD _IOWR ('V', 25, struct v4l2_standard) +#define VIDIOC_ENUMINPUT _IOWR ('V', 26, struct v4l2_input) +#define VIDIOC_G_CTRL _IOWR ('V', 27, struct v4l2_control) +#define VIDIOC_S_CTRL _IOWR ('V', 28, struct v4l2_control) +#define VIDIOC_G_TUNER _IOWR ('V', 29, struct v4l2_tuner) +#define VIDIOC_S_TUNER _IOW ('V', 30, struct v4l2_tuner) +#define VIDIOC_G_AUDIO _IOR ('V', 33, struct v4l2_audio) +#define VIDIOC_S_AUDIO _IOW ('V', 34, struct v4l2_audio) +#define VIDIOC_QUERYCTRL _IOWR ('V', 36, struct v4l2_queryctrl) +#define VIDIOC_QUERYMENU _IOWR ('V', 37, struct v4l2_querymenu) +#define VIDIOC_G_INPUT _IOR ('V', 38, int) +#define VIDIOC_S_INPUT _IOWR ('V', 39, int) +#define VIDIOC_G_OUTPUT _IOR ('V', 46, int) +#define VIDIOC_S_OUTPUT _IOWR ('V', 47, int) +#define VIDIOC_ENUMOUTPUT _IOWR ('V', 48, struct v4l2_output) +#define VIDIOC_G_AUDOUT _IOR ('V', 49, struct v4l2_audioout) +#define VIDIOC_S_AUDOUT _IOW ('V', 50, struct v4l2_audioout) +#define VIDIOC_G_MODULATOR _IOWR ('V', 54, struct v4l2_modulator) +#define VIDIOC_S_MODULATOR _IOW ('V', 55, struct v4l2_modulator) +#define VIDIOC_G_FREQUENCY _IOWR ('V', 56, struct v4l2_frequency) +#define VIDIOC_S_FREQUENCY _IOW ('V', 57, struct v4l2_frequency) +#define VIDIOC_CROPCAP _IOR ('V', 58, struct v4l2_cropcap) +#define VIDIOC_G_CROP _IOWR ('V', 59, struct v4l2_crop) +#define VIDIOC_S_CROP _IOW ('V', 60, struct v4l2_crop) +#define VIDIOC_G_JPEGCOMP _IOR ('V', 61, struct v4l2_jpegcompression) +#define VIDIOC_S_JPEGCOMP _IOW ('V', 62, struct v4l2_jpegcompression) +#define VIDIOC_QUERYSTD _IOR ('V', 63, v4l2_std_id) +#define VIDIOC_TRY_FMT _IOWR ('V', 64, struct v4l2_format) +#define VIDIOC_ENUMAUDIO _IOWR ('V', 65, struct v4l2_audio) +#define VIDIOC_ENUMAUDOUT _IOWR ('V', 66, struct v4l2_audioout) +#define VIDIOC_G_PRIORITY _IOR ('V', 67, enum v4l2_priority) +#define VIDIOC_S_PRIORITY _IOW ('V', 68, enum v4l2_priority) + +/* for compatibility, will go away some day */ +#define VIDIOC_OVERLAY_OLD _IOWR ('V', 14, int) +#define VIDIOC_S_PARM_OLD _IOW ('V', 22, struct v4l2_streamparm) +#define VIDIOC_S_CTRL_OLD _IOW ('V', 28, struct v4l2_control) +#define VIDIOC_G_AUDIO_OLD _IOWR ('V', 33, struct v4l2_audio) +#define VIDIOC_G_AUDOUT_OLD _IOWR ('V', 49, struct v4l2_audioout) + +#define BASE_VIDIOC_PRIVATE 192 /* 192-255 are private */ + + +#ifdef __KERNEL__ +/* + * + * V 4 L 2 D R I V E R H E L P E R A P I + * + * Some commonly needed functions for drivers (v4l2-common.o module) + */ +#include <linux/fs.h> + +/* Video standard functions */ +extern unsigned int v4l2_video_std_fps(struct v4l2_standard *vs); +extern int v4l2_video_std_construct(struct v4l2_standard *vs, + int id, char *name); + +/* prority handling */ +struct v4l2_prio_state { + atomic_t prios[4]; +}; +int v4l2_prio_init(struct v4l2_prio_state *global); +int v4l2_prio_change(struct v4l2_prio_state *global, enum v4l2_priority *local, + enum v4l2_priority new); +int v4l2_prio_open(struct v4l2_prio_state *global, enum v4l2_priority *local); +int v4l2_prio_close(struct v4l2_prio_state *global, enum v4l2_priority *local); +enum v4l2_priority v4l2_prio_max(struct v4l2_prio_state *global); +int v4l2_prio_check(struct v4l2_prio_state *global, enum v4l2_priority *local); + +/* names for fancy debug output */ +extern char *v4l2_field_names[]; +extern char *v4l2_type_names[]; +extern char *v4l2_ioctl_names[]; + +/* Compatibility layer interface -- v4l1-compat module */ +typedef int (*v4l2_kioctl)(struct inode *inode, struct file *file, + unsigned int cmd, void *arg); +int v4l_compat_translate_ioctl(struct inode *inode, struct file *file, + int cmd, void *arg, v4l2_kioctl driver_ioctl); + +#endif /* __KERNEL__ */ +#endif /* __LINUX_VIDEODEV2_H */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/include/media/audiochip.h b/linux/include/media/audiochip.h new file mode 100644 index 000000000..fadc32dab --- /dev/null +++ b/linux/include/media/audiochip.h @@ -0,0 +1,35 @@ +#ifndef AUDIOCHIP_H +#define AUDIOCHIP_H + +/* ---------------------------------------------------------------------- */ + +/* v4l device was opened in Radio mode */ +#define AUDC_SET_RADIO _IO('m',2) +/* select from TV,radio,extern,MUTE */ +#define AUDC_SET_INPUT _IOW('m',17,int) + +/* audio inputs */ +#define AUDIO_TUNER 0x00 +#define AUDIO_RADIO 0x01 +#define AUDIO_EXTERN 0x02 +#define AUDIO_INTERN 0x03 +#define AUDIO_OFF 0x04 +#define AUDIO_ON 0x05 +#define AUDIO_EXTERN_1 AUDIO_EXTERN +#define AUDIO_EXTERN_2 0x06 +#define AUDIO_MUTE 0x80 +#define AUDIO_UNMUTE 0x81 + +/* all the stuff below is obsolete and just here for reference. I'll + * remove it once the driver is tested and works fine. + * + * Instead creating alot of tiny API's for all kinds of different + * chips, we'll just pass throuth the v4l ioctl structs (v4l2 not + * yet...). It is a bit less flexible, but most/all used i2c chips + * make sense in v4l context only. So I think that's acceptable... + */ + +/* misc stuff to pass around config info to i2c chips */ +#define AUDC_CONFIG_PINNACLE _IOW('m',32,int) + +#endif /* AUDIOCHIP_H */ diff --git a/linux/include/media/i2c-compat.h b/linux/include/media/i2c-compat.h new file mode 100644 index 000000000..4e93d6ea0 --- /dev/null +++ b/linux/include/media/i2c-compat.h @@ -0,0 +1,55 @@ +/* + * some i2c layer compatibility stuff -- to avoid cluttering up the + * i2c modules with tons of #ifdefs + */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,66) + +static inline void *i2c_get_adapdata(struct i2c_adapter *dev) +{ + return dev->data; +} + +static inline void i2c_set_adapdata(struct i2c_adapter *dev, void *data) +{ + dev->data = data; +} + +static inline void *i2c_get_clientdata(struct i2c_client *dev) +{ + return dev->data; +} + +static inline void i2c_set_clientdata(struct i2c_client *dev, void *data) +{ + dev->data = data; +} + +#define I2C_DEVNAME(str) .name = str + +static inline char *i2c_clientname(struct i2c_client *c) +{ + return c->name; +} + +static inline void i2c_clients_command(struct i2c_adapter *adap, + unsigned int cmd, void *arg) +{ + int i; + + for (i = 0; i < I2C_CLIENT_MAX; i++) { + if (NULL == adap->clients[i]) + continue; + if (NULL == adap->clients[i]->driver->command) + continue; + adap->clients[i]->driver->command(adap->clients[i],cmd,arg); + } +} + +#endif + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/include/media/id.h b/linux/include/media/id.h new file mode 100644 index 000000000..dca5dcfc0 --- /dev/null +++ b/linux/include/media/id.h @@ -0,0 +1,38 @@ +/* FIXME: this temporarely, until these are included in linux/i2c-id.h */ + +/* drivers */ +#ifndef I2C_DRIVERID_TVMIXER +# define I2C_DRIVERID_TVMIXER I2C_DRIVERID_EXP0 +#endif +#ifndef I2C_DRIVERID_TVAUDIO +# define I2C_DRIVERID_TVAUDIO I2C_DRIVERID_EXP1 +#endif + +/* chips */ +#ifndef I2C_DRIVERID_DPL3518 +# define I2C_DRIVERID_DPL3518 I2C_DRIVERID_EXP2 +#endif +#ifndef I2C_DRIVERID_TDA9873 +# define I2C_DRIVERID_TDA9873 I2C_DRIVERID_EXP3 +#endif +#ifndef I2C_DRIVERID_TDA9875 +# define I2C_DRIVERID_TDA9875 I2C_DRIVERID_EXP0+4 +#endif +#ifndef I2C_DRIVERID_PIC16C54_PV951 +# define I2C_DRIVERID_PIC16C54_PV951 I2C_DRIVERID_EXP0+5 +#endif +#ifndef I2C_DRIVERID_TDA7432 +# define I2C_DRIVERID_TDA7432 I2C_DRIVERID_EXP0+6 +#endif +#ifndef I2C_DRIVERID_TDA9874 +# define I2C_DRIVERID_TDA9874 I2C_DRIVERID_EXP0+7 +#endif +#ifndef I2C_DRIVERID_SAA6752HS +# define I2C_DRIVERID_SAA6752HS I2C_DRIVERID_EXP0+8 +#endif + +/* algorithms */ +#ifndef I2C_ALGO_SAA7134 +# define I2C_ALGO_SAA7134 0x090000 +#endif + diff --git a/linux/include/media/ir-common.h b/linux/include/media/ir-common.h new file mode 100644 index 000000000..e6aa22ef2 --- /dev/null +++ b/linux/include/media/ir-common.h @@ -0,0 +1,82 @@ +/* + * some common structs and functions to handle infrared remotes via + * input layer ... + * + * (c) 2003 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/version.h> +#include <linux/input.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +/* some keys from 2.6.x which are not (yet?) in 2.4.x */ +# define KEY_PLAY 207 +# define KEY_SEARCH 217 +# define KEY_GOTO 0x162 +# define KEY_INFO 0x166 +# define KEY_CHANNEL 0x16b +# define KEY_LANGUAGE 0x170 +# define KEY_ZOOM 0x174 +# define KEY_TV 0x179 +# define KEY_CD 0x17f +# define KEY_TUNER 0x182 +# define KEY_AUDIO 0x188 +# define KEY_VIDEO 0x189 +# define KEY_RED 0x18e +# define KEY_GREEN 0x18f +# define KEY_YELLOW 0x190 +# define KEY_BLUE 0x191 +# define KEY_CHANNELUP 0x192 +# define KEY_CHANNELDOWN 0x193 +#endif + +#define IR_TYPE_RC5 1 +#define IR_TYPE_OTHER 99 + +#define IR_KEYTAB_TYPE u32 +#define IR_KEYTAB_SIZE 64 // enougth for rc5, probably need more some day ... + +#define IR_KEYCODE(tab,code) (((unsigned)code < IR_KEYTAB_SIZE) \ + ? tab[code] : KEY_RESERVED) + +struct ir_input_state { + /* configuration */ + int ir_type; + IR_KEYTAB_TYPE ir_codes[IR_KEYTAB_SIZE]; + + /* key info */ + u32 ir_raw; /* raw data */ + u32 ir_key; /* ir key code */ + u32 keycode; /* linux key code */ + int keypressed; /* current state */ +}; + +extern IR_KEYTAB_TYPE ir_codes_rc5_tv[IR_KEYTAB_SIZE]; +extern IR_KEYTAB_TYPE ir_codes_empty[IR_KEYTAB_SIZE]; + +void ir_input_init(struct input_dev *dev, struct ir_input_state *ir, + int ir_type, IR_KEYTAB_TYPE *ir_codes); +void ir_input_nokey(struct input_dev *dev, struct ir_input_state *ir); +void ir_input_keydown(struct input_dev *dev, struct ir_input_state *ir, + u32 ir_key, u32 ir_raw); +u32 ir_extract_bits(u32 data, u32 mask); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/include/media/saa6752hs.h b/linux/include/media/saa6752hs.h new file mode 100644 index 000000000..8485d2e49 --- /dev/null +++ b/linux/include/media/saa6752hs.h @@ -0,0 +1,58 @@ +/* + saa6752hs.h - definition for saa6752hs MPEG encoder + + Copyright (C) 2003 Andrew de Quincey <adq@lidskialf.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _SAA6752HS_H +#define _SAA6752HS_H + +enum mpeg_bitrate_mode { + MPEG_BITRATE_MODE_VBR = 0, /* Variable bitrate */ + MPEG_BITRATE_MODE_CBR = 1, /* Constant bitrate */ + + MPEG_BITRATE_MODE_MAX +}; + +enum mpeg_audio_bitrate { + MPEG_AUDIO_BITRATE_256 = 0, /* 256 kBit/sec */ + MPEG_AUDIO_BITRATE_384 = 1, /* 384 kBit/sec */ + + MPEG_AUDIO_BITRATE_MAX +}; + +#define MPEG_VIDEO_TARGET_BITRATE_MAX 27000 +#define MPEG_VIDEO_MAX_BITRATE_MAX 27000 +#define MPEG_TOTAL_BITRATE_MAX 27000 + +struct mpeg_params { + enum mpeg_bitrate_mode bitrate_mode; + unsigned int video_target_bitrate; + unsigned int video_max_bitrate; // only used for VBR + enum mpeg_audio_bitrate audio_bitrate; + unsigned int total_bitrate; +}; + +#define MPEG_SETPARAMS _IOW('6',100,struct mpeg_params) + +#endif // _SAA6752HS_H + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/include/media/tuner.h b/linux/include/media/tuner.h new file mode 100644 index 000000000..fb571d596 --- /dev/null +++ b/linux/include/media/tuner.h @@ -0,0 +1,99 @@ +/* + tuner.h - definition for different tuners + + Copyright (C) 1997 Markus Schroeder (schroedm@uni-duesseldorf.de) + minor modifications by Ralph Metzler (rjkm@thp.uni-koeln.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _TUNER_H +#define _TUNER_H + +#include "id.h" + +#define TUNER_TEMIC_PAL 0 /* 4002 FH5 (3X 7756, 9483) */ +#define TUNER_PHILIPS_PAL_I 1 +#define TUNER_PHILIPS_NTSC 2 +#define TUNER_PHILIPS_SECAM 3 /* you must actively select B/G, L, L` */ +#define TUNER_ABSENT 4 +#define TUNER_PHILIPS_PAL 5 +#define TUNER_TEMIC_NTSC 6 /* 4032 FY5 (3X 7004, 9498, 9789) */ +#define TUNER_TEMIC_PAL_I 7 /* 4062 FY5 (3X 8501, 9957) */ +#define TUNER_TEMIC_4036FY5_NTSC 8 /* 4036 FY5 (3X 1223, 1981, 7686) */ +#define TUNER_ALPS_TSBH1_NTSC 9 +#define TUNER_ALPS_TSBE1_PAL 10 +#define TUNER_ALPS_TSBB5_PAL_I 11 +#define TUNER_ALPS_TSBE5_PAL 12 +#define TUNER_ALPS_TSBC5_PAL 13 +#define TUNER_TEMIC_4006FH5_PAL 14 /* 4006 FH5 (3X 9500, 9501, 7291) */ +#define TUNER_ALPS_TSHC6_NTSC 15 +#define TUNER_TEMIC_PAL_DK 16 /* 4016 FY5 (3X 1392, 1393) */ +#define TUNER_PHILIPS_NTSC_M 17 +#define TUNER_TEMIC_4066FY5_PAL_I 18 /* 4066 FY5 (3X 7032, 7035) */ +#define TUNER_TEMIC_4006FN5_MULTI_PAL 19 /* B/G, I and D/K autodetected (3X 7595, 7606, 7657)*/ +#define TUNER_TEMIC_4009FR5_PAL 20 /* incl. FM radio (3X 7607, 7488, 7711)*/ +#define TUNER_TEMIC_4039FR5_NTSC 21 /* incl. FM radio (3X 7246, 7578, 7732)*/ +#define TUNER_TEMIC_4046FM5 22 /* you must actively select B/G, D/K, I, L, L` ! (3X 7804, 7806, 8103, 8104)*/ +#define TUNER_PHILIPS_PAL_DK 23 +#define TUNER_PHILIPS_FQ1216ME 24 /* you must actively select B/G/D/K, I, L, L` */ +#define TUNER_LG_PAL_I_FM 25 +#define TUNER_LG_PAL_I 26 +#define TUNER_LG_NTSC_FM 27 +#define TUNER_LG_PAL_FM 28 +#define TUNER_LG_PAL 29 +#define TUNER_TEMIC_4009FN5_MULTI_PAL_FM 30 /* B/G, I and D/K autodetected (3X 8155, 8160, 8163)*/ +#define TUNER_SHARP_2U5JF5540_NTSC 31 +#define TUNER_Samsung_PAL_TCPM9091PD27 32 +#define TUNER_MT2032 33 +#define TUNER_TEMIC_4106FH5 34 /* 4106 FH5 (3X 7808, 7865)*/ +#define TUNER_TEMIC_4012FY5 35 /* 4012 FY5 (3X 0971, 1099)*/ +#define TUNER_TEMIC_4136FY5 36 /* 4136 FY5 (3X 7708, 7746)*/ +#define TUNER_LG_PAL_NEW_TAPC 37 +#define TUNER_PHILIPS_FM1216ME_MK3 38 +#define TUNER_LG_NTSC_NEW_TAPC 39 +#define TUNER_HITACHI_NTSC 40 +#define TUNER_PHILIPS_PAL_MK 41 +#define TUNER_PHILIPS_ATSC 42 +#define TUNER_PHILIPS_FM1236_MK3 43 +#define TUNER_PHILIPS_4IN1 44 /* ATI TV Wonder Pro - Conexant */ +#define TUNER_MICROTUNE_4049FM5 45 + +#define NOTUNER 0 +#define PAL 1 /* PAL_BG */ +#define PAL_I 2 +#define NTSC 3 +#define SECAM 4 +#define ATSC 5 + +#define NoTuner 0 +#define Philips 1 +#define TEMIC 2 +#define Sony 3 +#define Alps 4 +#define LGINNOTEK 5 +#define SHARP 6 +#define Samsung 7 +#define Microtune 8 +#define HITACHI 9 + +#define TUNER_SET_TYPE _IOW('t',1,int) /* set tuner type */ +#define TUNER_SET_TVFREQ _IOW('t',2,int) /* set tv freq */ +#if 0 /* obsolete */ +# define TUNER_SET_RADIOFREQ _IOW('t',3,int) /* set radio freq */ +# define TUNER_SET_MODE _IOW('t',4,int) /* set tuner mode */ +#endif + +#endif diff --git a/linux/include/media/video-buf.h b/linux/include/media/video-buf.h new file mode 100644 index 000000000..507743a01 --- /dev/null +++ b/linux/include/media/video-buf.h @@ -0,0 +1,246 @@ +/* + * generic helper functions for video4linux capture buffers, to handle + * memory management and PCI DMA. Right now bttv + saa7134 use it. + * + * The functions expect the hardware being able to scatter gatter + * (i.e. the buffers are not linear in physical memory, but fragmented + * into PAGE_SIZE chunks). They also assume the driver does not need + * to touch the video data (thus it is probably not useful for USB as + * data often must be uncompressed by the drivers). + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/videodev.h> + +#define UNSET (-1U) + +/* --------------------------------------------------------------------- */ + +/* + * Return a scatterlist for some page-aligned vmalloc()'ed memory + * block (NULL on errors). Memory for the scatterlist is allocated + * using kmalloc. The caller must free the memory. + */ +struct scatterlist* videobuf_vmalloc_to_sg(unsigned char *virt, int nr_pages); + +/* + * Return a scatterlist for a an array of userpages (NULL on errors). + * Memory for the scatterlist is allocated using kmalloc. The caller + * must free the memory. + */ +struct scatterlist* videobuf_pages_to_sg(struct page **pages, int nr_pages, + int offset); + +/* --------------------------------------------------------------------- */ + +/* + * A small set of helper functions to manage buffers (both userland + * and kernel) for DMA. + * + * videobuf_dma_init_*() + * creates a buffer. The userland version takes a userspace + * pointer + length. The kernel version just wants the size and + * does memory allocation too using vmalloc_32(). + * + * videobuf_dma_pci_*() + * see Documentation/DMA-mapping.txt, these functions to + * basically the same. The map function does also build a + * scatterlist for the buffer (and unmap frees it ...) + * + * videobuf_dma_free() + * no comment ... + * + */ + +struct videobuf_dmabuf { + /* for userland buffer */ + int offset; + struct page **pages; + + /* for kernel buffers */ + void *vmalloc; + + /* for overlay buffers (pci-pci dma) */ + dma_addr_t bus_addr; + + /* common */ + struct scatterlist *sglist; + int sglen; + int nr_pages; + int direction; +}; + +int videobuf_dma_init_user(struct videobuf_dmabuf *dma, int direction, + unsigned long data, unsigned long size); +int videobuf_dma_init_kernel(struct videobuf_dmabuf *dma, int direction, + int nr_pages); +int videobuf_dma_init_overlay(struct videobuf_dmabuf *dma, int direction, + dma_addr_t addr, int nr_pages); +int videobuf_dma_pci_map(struct pci_dev *dev, struct videobuf_dmabuf *dma); +int videobuf_dma_pci_sync(struct pci_dev *dev, + struct videobuf_dmabuf *dma); +int videobuf_dma_pci_unmap(struct pci_dev *dev, struct videobuf_dmabuf *dma); +int videobuf_dma_free(struct videobuf_dmabuf *dma); + +/* --------------------------------------------------------------------- */ + +/* + * A small set of helper functions to manage video4linux buffers. + * + * struct videobuf_buffer holds the data structures used by the helper + * functions, additionally some commonly used fields for v4l buffers + * (width, height, lists, waitqueue) are in there. That struct should + * be used as first element in the drivers buffer struct. + * + * about the mmap helpers (videobuf_mmap_*): + * + * The mmaper function allows to map any subset of contingous buffers. + * This includes one mmap() call for all buffers (which the original + * video4linux API uses) as well as one mmap() for every single buffer + * (which v4l2 uses). + * + * If there is a valid mapping for a buffer, buffer->baddr/bsize holds + * userspace address + size which can be feeded into the + * videobuf_dma_init_user function listed above. + * + */ + +struct videobuf_buffer; +struct videobuf_queue; + +struct videobuf_mapping { + unsigned int count; + int highmem_ok; + unsigned long start; + unsigned long end; + struct videobuf_queue *q; +}; + +enum videobuf_state { + STATE_NEEDS_INIT = 0, + STATE_PREPARED = 1, + STATE_QUEUED = 2, + STATE_ACTIVE = 3, + STATE_DONE = 4, + STATE_ERROR = 5, + STATE_IDLE = 6, +}; + +struct videobuf_buffer { + unsigned int i; + + /* info about the buffer */ + unsigned int width; + unsigned int height; + unsigned int bytesperline; /* use only if != 0 */ + unsigned long size; + unsigned int input; + enum v4l2_field field; + enum videobuf_state state; + struct videobuf_dmabuf dma; + struct list_head stream; /* QBUF/DQBUF list */ + + /* for mmap'ed buffers */ + enum v4l2_memory memory; + size_t boff; /* buffer offset (mmap + overlay) */ + size_t bsize; /* buffer size */ + unsigned long baddr; /* buffer addr (userland ptr!) */ + struct videobuf_mapping *map; + + /* touched by irq handler */ + struct list_head queue; + wait_queue_head_t done; + unsigned int field_count; + struct timeval ts; +}; + +struct videobuf_queue_ops { + int (*buf_setup)(struct file *file, + unsigned int *count, unsigned int *size); + int (*buf_prepare)(struct file *file,struct videobuf_buffer *vb, + enum v4l2_field field); + void (*buf_queue)(struct file *file,struct videobuf_buffer *vb); + void (*buf_release)(struct file *file,struct videobuf_buffer *vb); +}; + +struct videobuf_queue { + struct semaphore lock; + spinlock_t *irqlock; + struct pci_dev *pci; + + enum v4l2_buf_type type; + unsigned int inputs; /* for V4L2_BUF_FLAG_INPUT */ + unsigned int msize; + enum v4l2_field field; + enum v4l2_field last; /* for field=V4L2_FIELD_ALTERNATE */ + struct videobuf_buffer *bufs[VIDEO_MAX_FRAME]; + struct videobuf_queue_ops *ops; + + /* capture via mmap() + ioctl(QBUF/DQBUF) */ + unsigned int streaming; + struct list_head stream; + + /* capture via read() */ + unsigned int reading; + unsigned int read_off; + struct videobuf_buffer *read_buf; +}; + +void* videobuf_alloc(unsigned int size); +int videobuf_waiton(struct videobuf_buffer *vb, int non_blocking, int intr); +int videobuf_iolock(struct pci_dev *pci, struct videobuf_buffer *vb, + struct v4l2_framebuffer *fbuf); + +void videobuf_queue_init(struct videobuf_queue *q, + struct videobuf_queue_ops *ops, + struct pci_dev *pci, spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize); +int videobuf_queue_is_busy(struct videobuf_queue *q); +void videobuf_queue_cancel(struct file *file, struct videobuf_queue *q); + +enum v4l2_field videobuf_next_field(struct videobuf_queue *q); +void videobuf_status(struct v4l2_buffer *b, struct videobuf_buffer *vb, + enum v4l2_buf_type type); +int videobuf_reqbufs(struct file *file, struct videobuf_queue *q, + struct v4l2_requestbuffers *req); +int videobuf_querybuf(struct videobuf_queue *q, struct v4l2_buffer *b); +int videobuf_qbuf(struct file *file, struct videobuf_queue *q, + struct v4l2_buffer *b); +int videobuf_dqbuf(struct file *file, struct videobuf_queue *q, + struct v4l2_buffer *b); +int videobuf_streamon(struct file *file, struct videobuf_queue *q); +int videobuf_streamoff(struct file *file, struct videobuf_queue *q); + +int videobuf_read_start(struct file *file, struct videobuf_queue *q); +void videobuf_read_stop(struct file *file, struct videobuf_queue *q); +ssize_t videobuf_read_stream(struct file *file, struct videobuf_queue *q, + char *data, size_t count, loff_t *ppos, + int vbihack); +ssize_t videobuf_read_one(struct file *file, struct videobuf_queue *q, + char *data, size_t count, loff_t *ppos); +unsigned int videobuf_poll_stream(struct file *file, + struct videobuf_queue *q, + poll_table *wait); + +int videobuf_mmap_setup(struct file *file, struct videobuf_queue *q, + unsigned int bcount, unsigned int bsize, + enum v4l2_memory memory); +int videobuf_mmap_free(struct file *file, struct videobuf_queue *q); +int videobuf_mmap_mapper(struct vm_area_struct *vma, + struct videobuf_queue *q); + +/* --------------------------------------------------------------------- */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/v4l/.bp/2.6/20_v4l2 b/v4l/.bp/2.6/20_v4l2 new file mode 100644 index 000000000..e723cd8ed --- /dev/null +++ b/v4l/.bp/2.6/20_v4l2 @@ -0,0 +1,5 @@ +srcdiff videodev.h include/linux/videodev.h +srcdiff videodev2.h include/linux/videodev2.h +#srcdiff videodev.c drivers/media/video/videodev.c +srcdiff v4l1-compat.c drivers/media/video/v4l1-compat.c +srcdiff v4l2-common.c drivers/media/video/v4l2-common.c diff --git a/v4l/.bp/2.6/30_video-buf b/v4l/.bp/2.6/30_video-buf new file mode 100644 index 000000000..a57df7832 --- /dev/null +++ b/v4l/.bp/2.6/30_video-buf @@ -0,0 +1,2 @@ +srcdiff video-buf.h include/media/video-buf.h +srcdiff video-buf.c drivers/media/video/video-buf.c diff --git a/v4l/.bp/2.6/31_bt832 b/v4l/.bp/2.6/31_bt832 new file mode 100644 index 000000000..c79f408f2 --- /dev/null +++ b/v4l/.bp/2.6/31_bt832 @@ -0,0 +1,2 @@ +srcdiff bt832.c drivers/media/video/bt832.c +srcdiff bt832.h drivers/media/video/bt832.h diff --git a/v4l/.bp/2.6/31_i2c b/v4l/.bp/2.6/31_i2c new file mode 100644 index 000000000..cbc4885fe --- /dev/null +++ b/v4l/.bp/2.6/31_i2c @@ -0,0 +1,10 @@ +srcdiff audiochip.h include/media/audiochip.h +srcdiff id.h include/media/id.h +srcdiff msp3400.c drivers/media/video/msp3400.c +srcdiff msp3400.h drivers/media/video/msp3400.h +srcdiff tda7432.c drivers/media/video/tda7432.c +srcdiff tda9875.c drivers/media/video/tda9875.c +srcdiff tda9887.c drivers/media/video/tda9887.c +srcdiff tvaudio.h drivers/media/video/tvaudio.h +srcdiff tvaudio.c drivers/media/video/tvaudio.c +srcdiff tvmixer.c drivers/media/video/tvmixer.c diff --git a/v4l/.bp/2.6/31_ir-input b/v4l/.bp/2.6/31_ir-input new file mode 100644 index 000000000..d579505a0 --- /dev/null +++ b/v4l/.bp/2.6/31_ir-input @@ -0,0 +1,2 @@ +srcdiff ir-common.h include/media/ir-common.h +srcdiff ir-common.c drivers/media/common/ir-common.c diff --git a/v4l/.bp/2.6/31_tuner b/v4l/.bp/2.6/31_tuner new file mode 100644 index 000000000..e960c4003 --- /dev/null +++ b/v4l/.bp/2.6/31_tuner @@ -0,0 +1,2 @@ +srcdiff tuner.h include/media/tuner.h +srcdiff tuner.c drivers/media/video/tuner.c diff --git a/v4l/.bp/2.6/32_bttv b/v4l/.bp/2.6/32_bttv new file mode 100644 index 000000000..08e87761d --- /dev/null +++ b/v4l/.bp/2.6/32_bttv @@ -0,0 +1,13 @@ +srcdiff bt848.h drivers/media/video/bt848.h +srcdiff bttv.h drivers/media/video/bttv.h +srcdiff bttvp.h drivers/media/video/bttvp.h +srcdiff bttv-driver.c drivers/media/video/bttv-driver.c +srcdiff bttv-cards.c drivers/media/video/bttv-cards.c +srcdiff bttv-if.c drivers/media/video/bttv-if.c +srcdiff bttv-risc.c drivers/media/video/bttv-risc.c +srcdiff bttv-vbi.c drivers/media/video/bttv-vbi.c +srcdiff bttv-gpio.c drivers/media/video/bttv-gpio.c +srcdiff bttv-i2c.c drivers/media/video/bttv-i2c.c + +srcdiff btcx-risc.h drivers/media/video/btcx-risc.h +srcdiff btcx-risc.c drivers/media/video/btcx-risc.c diff --git a/v4l/.bp/2.6/32_bttv-input b/v4l/.bp/2.6/32_bttv-input new file mode 100644 index 000000000..537796211 --- /dev/null +++ b/v4l/.bp/2.6/32_bttv-input @@ -0,0 +1,2 @@ +srcdiff ir-kbd-i2c.c drivers/media/video/ir-kbd-i2c.c +srcdiff ir-kbd-gpio.c drivers/media/video/ir-kbd-gpio.c diff --git a/v4l/.bp/2.6/33_saa7134 b/v4l/.bp/2.6/33_saa7134 new file mode 100644 index 000000000..64192c181 --- /dev/null +++ b/v4l/.bp/2.6/33_saa7134 @@ -0,0 +1,15 @@ +srcdiff saa7134.h drivers/media/video/saa7134/saa7134.h +srcdiff saa7134-reg.h drivers/media/video/saa7134/saa7134-reg.h + +srcdiff saa7134-core.c drivers/media/video/saa7134/saa7134-core.c +srcdiff saa7134-cards.c drivers/media/video/saa7134/saa7134-cards.c +srcdiff saa7134-i2c.c drivers/media/video/saa7134/saa7134-i2c.c +srcdiff saa7134-tvaudio.c drivers/media/video/saa7134/saa7134-tvaudio.c +srcdiff saa7134-video.c drivers/media/video/saa7134/saa7134-video.c +srcdiff saa7134-vbi.c drivers/media/video/saa7134/saa7134-vbi.c +srcdiff saa7134-oss.c drivers/media/video/saa7134/saa7134-oss.c +srcdiff saa7134-ts.c drivers/media/video/saa7134/saa7134-ts.c +srcdiff saa7134-input.c drivers/media/video/saa7134/saa7134-input.c + +srcdiff saa6752hs.h include/media/saa6752hs.h +srcdiff saa6752hs.c drivers/media/video/saa7134/saa6752hs.c diff --git a/v4l/.bp/2.6/34_cx88 b/v4l/.bp/2.6/34_cx88 new file mode 100644 index 000000000..153c085da --- /dev/null +++ b/v4l/.bp/2.6/34_cx88 @@ -0,0 +1,9 @@ +srcdiff cx88.h drivers/media/video/cx88/cx88.h +srcdiff cx88-reg.h drivers/media/video/cx88/cx88-reg.h + +srcdiff cx88-cards.c drivers/media/video/cx88/cx88-cards.c +srcdiff cx88-core.c drivers/media/video/cx88/cx88-core.c +srcdiff cx88-i2c.c drivers/media/video/cx88/cx88-i2c.c +srcdiff cx88-tvaudio.c drivers/media/video/cx88/cx88-tvaudio.c +srcdiff cx88-video.c drivers/media/video/cx88/cx88-video.c + diff --git a/v4l/.bp/2.6/35_documentation b/v4l/.bp/2.6/35_documentation new file mode 100644 index 000000000..45b08f071 --- /dev/null +++ b/v4l/.bp/2.6/35_documentation @@ -0,0 +1,24 @@ +# bttv +srcdiff doc/README.bttv Documentation/video4linux/bttv/README +srcdiff doc/insmod-options Documentation/video4linux/bttv/Insmod-options +srcdiff doc/MAKEDEV Documentation/video4linux/bttv/MAKEDEV +srcdiff doc/Sound-FAQ Documentation/video4linux/bttv/Sound-FAQ +srcdiff doc/Tuners Documentation/video4linux/bttv/Tuners +srcdiff doc/Cards Documentation/video4linux/bttv/Cards + +# other +srcdiff /dev/null Documentation/video4linux/bttv/CARDLIST +srcdiff doc/README.cx88 Documentation/video4linux/README.cx88 +srcdiff doc/README.saa7134 Documentation/video4linux/README.saa7134 +srcdiff doc/README.ir Documentation/video4linux/README.ir + +# cardlists +srcdiff doc/CARDLIST.bttv Documentation/video4linux/CARDLIST.bttv +srcdiff doc/CARDLIST.saa7134 Documentation/video4linux/CARDLIST.saa7134 +srcdiff doc/CARDLIST.tuner Documentation/video4linux/CARDLIST.tuner + +# obsolete / fixme +#srcdiff doc/Specs Documentation/video4linux/bttv/Specs +#srcdiff README.freeze Documentation/video4linux/bttv/README.freeze +#srcdiff README.quirks Documentation/video4linux/bttv/README.quirks +#srcdiff Modules.conf Documentation/video4linux/bttv/Modules.conf diff --git a/v4l/.snapshot b/v4l/.snapshot new file mode 100644 index 000000000..75812c2c5 --- /dev/null +++ b/v4l/.snapshot @@ -0,0 +1 @@ +20040218 diff --git a/v4l/Make.config b/v4l/Make.config new file mode 100644 index 000000000..4d2bd32c8 --- /dev/null +++ b/v4l/Make.config @@ -0,0 +1,9 @@ +MDIR := v4l2 +snap := video4linux + +CONFIG_VIDEO_BTTV := m +CONFIG_VIDEO_CX88 := m +CONFIG_VIDEO_SAA7134 := m +CONFIG_VIDEO_IR := m +CONFIG_VIDEO_TUNER := m +CONFIG_VIDEO_TVAUDIO := m diff --git a/v4l/Makefile b/v4l/Makefile new file mode 100644 index 000000000..e376afeb9 --- /dev/null +++ b/v4l/Makefile @@ -0,0 +1,151 @@ + +ifeq ($(obj),) +obj = . +endif + + +################################################# +# configuration + +include $(obj)/Make.config + +# symbol exports +export-objs := video-buf.o v4l1-compat.o v4l2-common.o +export-objs += ir-common.o +export-objs += bttv-if.o btcx-risc.o +export-objs += cx88-cards.o cx88-core.o + +# drivers objects +bttv-objs := bttv-driver.o bttv-cards.o bttv-risc.o bttv-if.o \ + bttv-vbi.o bttv-i2c.o +saa7134-objs := saa7134-core.o saa7134-i2c.o saa7134-video.o \ + saa7134-vbi.o saa7134-tvaudio.o saa7134-oss.o \ + saa7134-cards.o saa7134-ts.o +cx88xx-objs := cx88-cards.o cx88-core.o +cx8800-objs := cx88-video.o cx88-vbi.o cx88-tvaudio.o cx88-i2c.o +list-multi := bttv.o saa7134.o cx88xx.o cx8800.o + +# what to build +obj-m := video-buf.o v4l1-compat.o v4l2-common.o +obj-$(CONFIG_VIDEO_BTTV) += btcx-risc.o ir-common.o bttv.o +obj-$(CONFIG_VIDEO_CX88) += btcx-risc.o cx88xx.o cx8800.o +obj-$(CONFIG_VIDEO_SAA7134) += saa7134.o saa6752hs.o +obj-$(CONFIG_VIDEO_IR) += ir-common.o +obj-$(CONFIG_VIDEO_TUNER) += tuner.o tda9887.o +obj-$(CONFIG_VIDEO_TVAUDIO) += msp3400.o tvaudio.o tvmixer.o + +# 2.6-only stuff +ifeq ($(VERSION).$(PATCHLEVEL),2.6) + ifeq ($(CONFIG_VIDEO_BTTV),m) + bttv-objs += bttv-gpio.o + obj-$(CONFIG_VIDEO_IR) += ir-kbd-gpio.o ir-kbd-i2c.o + endif +endif + +# ir for saa7134 +ifeq ($(CONFIG_VIDEO_IR),m) + EXTRA_CFLAGS += -DCONFIG_VIDEO_IR=1 + saa7134-objs += saa7134-input.o +endif + + +################################################# +# compile modules + +ifneq ($(KERNELRELEASE),) +# recursive call from kernel build system +dummy := $(shell echo $(KERNELRELEASE) > $(obj)/.version) + +ifeq ($(VERSION).$(PATCHLEVEL),2.6) + export-objs := + list-multi := +else + multi-m := $(filter $(list-multi), $(obj-m)) + int-m := $(sort $(foreach m, $(multi-m), $($(basename $(m))-objs))) + export-objs := $(filter $(int-m) $(obj-m),$(export-objs)) +endif + +CC += -I$(obj) +EXTRA_CFLAGS += -g + +snapshot := $(wildcard $(obj)/.snapshot) +ifneq ($(snapshot),) +SNAPSHOT_CFLAGS := -DSNAPSHOT='$(shell cat $(snapshot))' +EXTRA_CFLAGS += $(SNAPSHOT_CFLAGS) +endif + +bttv.o: $(bttv-objs) + $(LD) -r -o $@ $(bttv-objs) + +cx8800.o: $(cx8800-objs) + $(LD) -g -r -o $@ $(cx8800-objs) + +cx88xx.o: $(cx88xx-objs) + $(LD) -g -r -o $@ $(cx88xx-objs) + +saa7134.o: $(saa7134-objs) + $(LD) -g -r -o $@ $(saa7134-objs) + +-include $(TOPDIR)/Rules.make +else +# take version info from last module build if available +KERNELRELEASE := $(shell cat $(obj)/.version 2>/dev/null || uname -r) +endif + +KDIR := /lib/modules/$(KERNELRELEASE)/build +PWD := $(shell pwd) +DEST := /lib/modules/$(KERNELRELEASE)/$(MDIR) + +# which files to install? +inst-m := $(wildcard *.ko) +ifeq ($(inst-m),) + inst-m := $(obj-m) +endif + +# locales seem to cause trouble sometimes. +LC_ALL = POSIX +export LC_ALL + +default:: + $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules + +install:: + strip --strip-debug $(inst-m) + -su -c "mkdir -p $(DEST); cp -v $(inst-m) $(DEST); depmod -a" + +clean:: + -rm -f *~ *.o *.ko *.mod.c + -rm -f .version .*.o.flags .*.o.d .*.o.cmd .*.ko.cmd + + +################################################# +# build tarballs + +thisdir := $(notdir $(PWD)) +name := $(shell echo $(thisdir) | sed 's/-.*//') +ver := $(shell echo $(thisdir) | sed 's/.*-//') +date := $(shell date +%Y%m%d) +tardest ?= . + +snapdir := $(HOME)/snapshot +snap ?= $(name) + +release: clean + rm -f .snapshot + (cd ..; tar cvzf $(tardest)/$(name)-$(ver).tar.gz $(thisdir)) + +snapshot snap tarball: clean + echo $(date) > .snapshot + (cd ..; tar czf $(snapdir)/$(snap)-$(date).tar.gz $(thisdir)) + $(MAKE) -C $(snapdir) + + +################################################# +# other stuff + +%.asm: %.o + objdump -S $< > $@ + +cardlists: + scripts/cardlist + diff --git a/v4l/README b/v4l/README new file mode 100644 index 000000000..40cca1787 --- /dev/null +++ b/v4l/README @@ -0,0 +1 @@ +check out the doc/ directory ... diff --git a/v4l/i2c-compat.h b/v4l/i2c-compat.h new file mode 100644 index 000000000..4e93d6ea0 --- /dev/null +++ b/v4l/i2c-compat.h @@ -0,0 +1,55 @@ +/* + * some i2c layer compatibility stuff -- to avoid cluttering up the + * i2c modules with tons of #ifdefs + */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,66) + +static inline void *i2c_get_adapdata(struct i2c_adapter *dev) +{ + return dev->data; +} + +static inline void i2c_set_adapdata(struct i2c_adapter *dev, void *data) +{ + dev->data = data; +} + +static inline void *i2c_get_clientdata(struct i2c_client *dev) +{ + return dev->data; +} + +static inline void i2c_set_clientdata(struct i2c_client *dev, void *data) +{ + dev->data = data; +} + +#define I2C_DEVNAME(str) .name = str + +static inline char *i2c_clientname(struct i2c_client *c) +{ + return c->name; +} + +static inline void i2c_clients_command(struct i2c_adapter *adap, + unsigned int cmd, void *arg) +{ + int i; + + for (i = 0; i < I2C_CLIENT_MAX; i++) { + if (NULL == adap->clients[i]) + continue; + if (NULL == adap->clients[i]->driver->command) + continue; + adap->clients[i]->driver->command(adap->clients[i],cmd,arg); + } +} + +#endif + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/v4l/scripts/cardlist b/v4l/scripts/cardlist new file mode 100644 index 000000000..57b903dcb --- /dev/null +++ b/v4l/scripts/cardlist @@ -0,0 +1,15 @@ +#!/bin/sh + +cat bttv-cards.c \ + | grep "\.name.*=" \ + | perl -ne '/"([^"]+)"/; printf("card=%d - %s\n",$i++,$1)' \ + > doc/CARDLIST.bttv + +cat tuner.c \ + | grep "{ \"" \ + | perl -ne '/"([^"]+)"/; printf("tuner=%d - %s\n",$i++,$1)' \ + > doc/CARDLIST.tuner + +scripts/saa7134.pl saa7134-cards.c \ + > doc/CARDLIST.saa7134 + diff --git a/v4l/scripts/release.sh b/v4l/scripts/release.sh new file mode 100644 index 000000000..8d024b42a --- /dev/null +++ b/v4l/scripts/release.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +# config +release="$HOME/kernel/release" +ver_bt="0.9.12" +ver_sa="0.2.9" +ver_cx="0.0.1" + +# common files +files_v4l="v4l*.[ch] video-buf.[ch] videodev*.h" +files_tuner="tuner.[ch] tda9887.[ch]" +files_i2c="id.h audiochip.h i2c-compat.h" +files_common="$files_v4l $files_tuner $files_i2c doc" + +# other files +files_ir="ir-common.[ch]" +files_audio="msp3400.[ch] tvaudio.[ch]" + +files_bttv="bt848.h btcx*.[ch] bttv*.[ch] bt832.[ch] ir-kbd*.c" +files_saa="saa7134*.[ch] saa6752hs.[ch]" +files_cx="btcx*.[ch] cx*.[ch]" + + +###################################################################################### +# helpers + +function build_release () { + local name="$1"; shift + local version="$1"; shift + local files="$*" + local dest="$WORK/$name-$version" + local tarball="$release/$name-$version.tar.gz" + + # copy / prepare stuff + mkdir "$dest" + cp -av $files "$dest" || exit 1 + cp -v Makefile "$dest" + cp -v "scripts/config.$name" "$dest"/Make.config + ln -s . "$dest/media" + ln -s . "$dest/linux" + + # build test + (cd $dest; make) || exit 1 +# (cd $dest; ls *.o; sleep 5) + (cd $dest; make clean) + + # build tarball + tar czCf "$WORK" "$tarball" "$name-$version" +} + + +###################################################################################### +# main + +# tmp dir for my files +WORK="${TMPDIR-/tmp}/${0##*/}-$$" +mkdir "$WORK" || exit 1 +trap 'rm -rf "$WORK"' EXIT + +build_release "bttv" "$ver_bt" \ + "$files_common" "$files_ir" "$files_audio" "$files_bttv" +build_release "saa7134" "$ver_sa" \ + "$files_common" "$files_ir" "$files_audio" "$files_saa" +build_release "cx88" "$ver_cx" \ + "$files_common" "$files_cx" diff --git a/v4l/scripts/saa7134.pl b/v4l/scripts/saa7134.pl new file mode 100644 index 000000000..789d1c851 --- /dev/null +++ b/v4l/scripts/saa7134.pl @@ -0,0 +1,53 @@ +#!/usr/bin/perl -w +use strict; + +my %map = ( + "PCI_ANY_ID" => "0", + "PCI_VENDOR_ID_PHILIPS" => "1131", +); + +sub fix_id($) { + my $id = shift; + $id = $map{$id} if defined($map{$id}); + $id =~ s/^0x//; + return $id; +} + +my $new_entry = -1; +my $nr = 0; +my ($id,$subvendor,$subdevice); +my %data; + +while (<>) { + # saa7134_boards + if (/\[(SAA7134_BOARD_\w+)\]/) { + $id = $1; + $data{$id}->{id} = $id; + $data{$id}->{nr} = $nr++; + }; + next unless defined($id); + + if (!defined($data{$id}) || !defined($data{$id}->{name})) { + $data{$id}->{name} = $1 if (/\.name\s*=\s*\"([^\"]+)\"/); + } + + # saa7134_pci_tbl + $subvendor = fix_id($1) if (/\.subvendor\s*=\s*(\w+),/); + $subdevice = fix_id($1) if (/\.subdevice\s*=\s*(\w+),/); + if (/.driver_data\s*=\s*(\w+),/) { + if (defined($data{$1}) && + defined($subvendor) && $subvendor ne "0" && + defined($subdevice) && $subdevice ne "0") { + push @{$data{$1}->{subid}}, "$subvendor:$subdevice"; + undef $subvendor; + undef $subdevice; + } + } +} + +foreach my $item (sort { $data{$a}->{nr} <=> $data{$b}->{nr} } keys %data) { + printf("%3d -> %-40s", $data{$item}->{nr}, $data{$item}->{name}); + printf(" [%s]",join(",",@{$data{$item}->{subid}})) + if defined($data{$item}->{subid}); + print "\n"; +} diff --git a/v4l/scripts/update b/v4l/scripts/update new file mode 100644 index 000000000..5c41e552a --- /dev/null +++ b/v4l/scripts/update @@ -0,0 +1,63 @@ +#!/bin/sh +source scripts/modules.sh +set -e + +case "`hostname --short`" in + eskarina) +# btopt="card=10" +# btopt="card=6 remote=1" + ;; + bogomips) + saopt="card=7" + ;; +esac + +########################################################################### +# unload + +# bttv +xrmmod ir-kbd-gpio +xrmmod ir-kbd-i2c +xrmmod bttv + +# cx88 +xrmmod cx8800 +xrmmod cx88xx + +# saa7134 +xrmmod saa7134 + +# common +xrmmod btcx-risc +xrmmod ir-common +xrmmod video-buf + +# i2c +xrmmod tuner +xrmmod msp3400 +xrmmod tvaudio +xrmmod tda9887 + +########################################################################## +# reload + +# common +v4l2basic +xinsmod video-buf debug=2 +xinsmod btcx-risc debug=1 +xinsmod ir-common debug=0 repeat=0 +xinsmod tuner debug=0 +xinsmod msp3400 debug=0 + +# bttv +xinsmod bttv bttv_debug=0 vbi_debug=0 irq_debug=0 \ + fdsr=0 chroma_agc=1 vbibufs=4 irq_iswitch=0 $btopt +test -f ir-kbd-i2c.ko && xinsmod ir-kbd-i2c debug=0 +test -f ir-kbd-gpio.ko && xinsmod ir-kbd-gpio debug=0 + +# cx88 +xinsmod cx88xx core_debug=2 +xinsmod cx8800 video_debug=2 + +# saa7134 +xinsmod saa7134 audio_debug=1 $saopt diff --git a/v4l/vbi b/v4l/vbi new file mode 100644 index 000000000..1bb83bb56 --- /dev/null +++ b/v4l/vbi @@ -0,0 +1,8 @@ +#!/bin/sh + +# config +vbi="/dev/vbi1" + +# go! +v4lctl -c "${vbi}" setnorm "pal-bg" +dd if="${vbi}" of=/dev/null bs=$(( 1024 * 64 )) count=1 diff --git a/v4l_experimental/dpl3518.c b/v4l_experimental/dpl3518.c new file mode 100644 index 000000000..5a730b7bd --- /dev/null +++ b/v4l_experimental/dpl3518.c @@ -0,0 +1,413 @@ +/* + * programming the dpl3518a Dolby Pro Logic Processor + * + * WARNING: THIS DRIVER WILL LOAD WITHOUT COMPLAINTS EVEN IF A DIFFERENT + * CHIP IS AT ADDRESS 0x84 (it relies on i2c to make sure that there is a + * device acknowledging that address) + * + * Copyright (C) 1999 Roland Jansen <roland@lut.rwth-aachen.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + * + * --- + * DOLBY and PRO LOGIC are trademarks of + * Dolby Laboratories Licensing Corporation. + */ + +/* FIXME */ +#define DPL_MAJOR 127 + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/fs.h> +#include <asm/uaccess.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include "id.h" +#include "i2c-compat.h" +#else +#include <media/id.h> +#endif +#include "dpl3518.h" + + +/* Addresses to scan */ +#define I2C_DPL3518 0x84 +static unsigned short normal_i2c[] = { + I2C_DPL3518 >> 1, + I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +struct dpl3518 { + struct i2c_client *client; + int mode; /* dolby mode */ +}; + +static struct dpl3518 *dpl_device; +static struct i2c_driver driver; +static struct i2c_client client_template; + +/* ---------------------------------------------------------------------- */ + +/* insmod parameters */ +static int debug = 0; /* debug output */ +MODULE_PARM(debug, "i"); + +MODULE_DESCRIPTION("device driver for dpl3518a Dolby Pro Logic Processor"); +MODULE_AUTHOR("Roland Jansen"); +MODULE_LICENSE("GPL"); + +#define dprintk if (debug) printk + +/* ---------------------------------------------------------------------- */ + +#define I2C_DPL3518_MR 0x10 /* write address MODE_REG */ +#define I2C_DPL3518_DFP 0x12 /* write address DFP */ + +/* ----------------------------------------------------------------------- */ +/* functions for talking to the dpl3518 Sound processor */ + +static int dpl3518_reset(struct i2c_client *client) +{ + static char reset_off[3] = { 0x00, 0x80, 0x00 }; + static char reset_on[3] = { 0x00, 0x00, 0x00 }; + + i2c_master_send(client,reset_off,3); /* ignore errors here */ + if (3 != i2c_master_send(client,reset_on, 3)) { + printk(KERN_ERR "dpl3518: chip reset failed, penguin on i2c bus?\n"); + return -1; + } + return 0; +} + +static int dpl3518_write(struct i2c_client *client, int dev, int addr, int val) +{ + int err; + unsigned char buffer[5]; + + buffer[0] = dev; + buffer[1] = addr >> 8; + buffer[2] = addr & 0xff; + buffer[3] = val >> 8; + buffer[4] = val & 0xff; + + for (err = 0; err < 3;) { + if (5 == i2c_master_send(client, buffer, 5)) + break; + err++; + printk(KERN_WARNING "dpl3518: I/O error #%d (write 0x%02x/0x%02x)\n", + err, dev, addr); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + } + if (3 == err) { + printk(KERN_WARNING "dpl3518: giving up, reseting chip. Sound will go off, sorry folks :-|\n"); + dpl3518_reset(client); + return -1; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* in: 0..100 out: 0x00..0x7f */ +static int p100_to_7f(int i) +{ + int r; + + r = ((i & 0x7f) * 127 + 1) / 100; + if (r > 127) + r = 127; + if (r < 0) + r = 0; + return r; +} + +/* ----------------------------------------------------------------------- */ + +static void dpl3518_ioc_init(struct i2c_client *client) +{ + dprintk("dpl3518 init\n"); + dpl3518_write(client, I2C_DPL3518_MR, 0x0083, 0x0008); /* mode_reg */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x000b, 0x0320); /*I2S1 src: RL */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0012, 0x0000); /*prescale2: off */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0016, 0x1000); /*prescale: 0 dB */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0038, 0x0420); /*I2S2 src: CS */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0040, 0x0000); /*adaptive,surround */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0041, 0x0520); /*i2s1, stereo */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0042, 0x1400); /*delay: 20ms */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0043, 0x0000); /*inp. Bal. auto */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0044, 0x4000); /*spatial 50% */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0045, 0x0000); /*panorama off */ + //dpl3518_write(client,I2C_DPL3518_DFP,0x0045,0x5400); /*panorama 66%*/ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0046, 0x0000); /*reverb off */ +} + +static void dpl3518_prologic_init(struct i2c_client *client) +{ + dprintk("dpl3518 prologic_init\n"); + dpl3518_write(client, I2C_DPL3518_DFP, 0x000b, 0x0320); /*I2S1 src: RL */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0038, 0x0420); /*I2S2 src: CS */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0041, 0x0520); /*i2s1, stereo */ +} + +static void dpl3518_noise_init(struct i2c_client *client) +{ + dprintk("dpl3518 noise_init\n"); + dpl3518_write(client, I2C_DPL3518_DFP, 0x000b, 0x0320); /*I2S1 src: RL */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0038, 0x0420); /*I2S2 src: CS */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0041, 0x0100); /*NOISE */ +} + +static void dpl3518_through_init(struct i2c_client *client) +{ + dprintk("dpl3518 through_init\n"); + dpl3518_write(client, I2C_DPL3518_DFP, 0x000b, 0x0520); /*I2S1 src: S1 */ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0038, 0x0220); /*I2S2 src: scart*/ + dpl3518_write(client, I2C_DPL3518_DFP, 0x0041, 0x0520); /*i2s1, stereo */ +} + +static void dpl3518_setmode(struct i2c_client *client, int mode) +{ + dprintk("dpl3518 setmode: %i\n", mode); + dpl3518_write(client, I2C_DPL3518_DFP, 0x0040, mode << 4); + dpl_device->mode = mode; +} + +static void dpl3518_setnoisemode(struct i2c_client *client, int mode) +{ + static int noise_modes[5] = { 0x0000, 0x01a0, 0x01b0, 0x01c0, 0x01d0 }; + dprintk("dpl3518 setnoisemode: %i\n", mode); + dpl3518_write(client, I2C_DPL3518_DFP, 0x0041, noise_modes[mode]); +} + + +static void dpl3518_delay(struct i2c_client *client, int delay) +{ + dprintk("dpl3518 setdelay: %i\n", delay); + dpl3518_write(client, I2C_DPL3518_DFP, 0x0042, delay << 8); +} + +static void dpl3518_reverb(struct i2c_client *client, int rev) +{ + dprintk("dpl3518 setreverb: %i\n", rev); + dpl3518_write(client, I2C_DPL3518_DFP, 0x0046, p100_to_7f(rev) << 8); +} + +static void dpl3518_panorama(struct i2c_client *client, int pan) +{ + dprintk("dpl3518 panorama: %i\n", pan); + dpl3518_write(client, I2C_DPL3518_DFP, 0x0045, p100_to_7f(pan) << 8); +} + +static void dpl3518_spatial(struct i2c_client *client, int spatial) +{ + dprintk("dpl3518 setspatial: %i\n", spatial); + dpl3518_write(client, I2C_DPL3518_DFP, 0x0045, p100_to_7f(spatial) << 8); +} + +/* ----------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +static int dpl3518_attach(struct i2c_adapter *adap, int addr, int kind) +#else +static int dpl3518_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +#endif +{ + struct dpl3518 *dpl; + struct i2c_client *client; + + client = kmalloc(sizeof *client,GFP_KERNEL); + if (!client) + return -ENOMEM; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->adapter = adap; + client->addr = addr; + + dpl = kmalloc(sizeof *dpl, GFP_KERNEL); + if (NULL == dpl) + return -ENOMEM; + i2c_set_clientdata(client, dpl); + memset(dpl, 0, sizeof(struct dpl3518)); + dpl->mode = 0; /* DOLBY_MODE_NONE */ + dpl->client = client; + + if (-1 == dpl3518_reset(client)) { + kfree(dpl); + kfree(client); + dprintk("dpl3518: no chip found\n"); + return -1; + } + + printk(KERN_INFO "dpl3518: init\n"); + dpl_device = dpl; + dpl3518_ioc_init(client); + + /* done */ + i2c_attach_client(client); + return 0; +} + +static int dpl3518_probe(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, dpl3518_attach); +} + +static int dpl3518_detach(struct i2c_client *client) +{ + struct dpl3518 *dpl = i2c_get_clientdata(client); + + dpl3518_reset(client); + i2c_detach_client(client); + + dpl_device = NULL; + kfree(dpl); + kfree(client); + return 0; +} + +static int dpl3518_dev_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ +// unsigned int minor = MINOR(inode->i_rdev); + int ret = 0; + struct i2c_client *client = dpl_device->client; + + switch (cmd) { + case DPL_IOC_RESET: + dpl3518_reset(client); + break; + case DPL_IOC_VERSION: + if (put_user(DPL_VERSION_CODE, (int *) arg)) + ret = -EFAULT; + break; + case DPL_IOC_INIT: + dpl3518_ioc_init(client); + break; + case DPL_IOC_PROLOGIC_INIT: + dpl3518_prologic_init(client); + break; + case DPL_IOC_NOISE_INIT: + dpl3518_noise_init(client); + break; + case DPL_IOC_THROUGH_INIT: + dpl3518_through_init(client); + break; + case DPL_IOC_MODE: + dpl3518_setmode(client, (int) arg); + break; + case DPL_IOC_GET_MODE: + if (put_user(dpl_device->mode, (int *) arg)) + ret = -EFAULT; + break; + case DPL_IOC_NOISE_MODE: + dpl3518_setnoisemode(client, (int) arg); + break; + case DPL_IOC_DELAY: + dpl3518_delay(client, (int) arg); + break; + case DPL_IOC_REVERB: + dpl3518_reverb(client, (int) arg); + break; + case DPL_IOC_PANORAMA: + dpl3518_panorama(client, (int) arg); + break; + case DPL_IOC_SPATIAL: + dpl3518_spatial(client, (int) arg); + break; + default: + dprintk("dpl3518_ioctl: default %i\n", (int) arg); + ret = -EINVAL; + } + return ret; +} + +static int dpl3518_dev_open(struct inode *inode, struct file *file) +{ + dprintk("dpl3518_dev_open\n"); + return 0; +} + +static int dpl3518_dev_release(struct inode *inode, struct file *file) +{ + dprintk("dpl3518_dev_release\n"); + return 0; +} + +/* ---------------------------------------------------------------------- */ + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif + .name = "i2c dpl3518 driver", + .id = I2C_DRIVERID_DPL3518, + .flags = I2C_DF_NOTIFY, + .attach_adapter = dpl3518_probe, + .detach_client = dpl3518_detach, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("dpl3518"), + .id = -1, + .driver = &driver +}; + +static struct file_operations dpl3518_fops = +{ + .owner = THIS_MODULE, + .ioctl = dpl3518_dev_ioctl, + .open = dpl3518_dev_open, + .release = dpl3518_dev_release, +}; + + +static int dpl3518_init(void) +{ + if (register_chrdev(DPL_MAJOR, "dpl3518", &dpl3518_fops)) { + printk("dpl3518: unable to get major %d\n", DPL_MAJOR); + return -EBUSY; + } + i2c_add_driver(&driver); + return 0; +} + +static void dpl3518_fini(void) +{ + i2c_del_driver(&driver); + unregister_chrdev(DPL_MAJOR, "dpl3518"); +} + +module_init(dpl3518_init); +module_exit(dpl3518_fini); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/v4l_experimental/dpl3518.h b/v4l_experimental/dpl3518.h new file mode 100644 index 000000000..d67a25648 --- /dev/null +++ b/v4l_experimental/dpl3518.h @@ -0,0 +1,44 @@ +#ifndef DPL3518_H +#define DPL3518_H + +#include <linux/ioctl.h> + +#define DPL_VERSION_CODE 0x0002 /* Version 0.2 */ + + +/* IOCTL numbers */ + +/* Reset function (just in case...) */ +#define DPL_IOC_RESET _IO('d',1) + +/* Get driver version */ +#define DPL_IOC_VERSION _IOR('d',2,int) + +/* Dolby Pro Logic modes */ +#define DPL_IOC_INIT _IO('d',3) /* Must be done first */ + +#define DPL_IOC_PROLOGIC_INIT _IO('d',4) +#define DPL_IOC_NOISE_INIT _IO('d',5) +#define DPL_IOC_THROUGH_INIT _IO('d',6) + +#define DPL_IOC_MODE _IOW('d',7,int) /* 0 = Normal */ + /* 1 = Phantom */ + /* 2 = Wide */ + /* 3 = Dolby 3 Stereo */ + /* 4 = Center Off */ + /* 5 = Panorama */ + /* 6 = 3D-Panorama (virtual dolby) */ +#define DPL_IOC_GET_MODE _IOR('d',7,int) + +#define DPL_IOC_NOISE_MODE _IOW('d',8,int) +#define DPL_IOC_GET_NOISE_MODE _IOR('d',8,int) + +#define DPL_IOC_DELAY _IOW('d',9,int) /* 0 .. 15 */ +#define DPL_IOC_REVERB _IOW('d',10,int) /* 0 .. 100 % */ +#define DPL_IOC_PANORAMA _IOW('d',11,int) /* 0 .. 100 % */ +#define DPL_IOC_SPATIAL _IOW('d',12,int) /* 0 .. 100 % */ + + +/* ---------------------------------------------------------------------- */ + +#endif /* DPL3518_H */ diff --git a/v4l_experimental/plx9054.h b/v4l_experimental/plx9054.h new file mode 100644 index 000000000..9a1c8783f --- /dev/null +++ b/v4l_experimental/plx9054.h @@ -0,0 +1,177 @@ +#ifndef _PLX9054_H_ +#define _PLX9054_H_ + +// Definitions for the PLX Configuration space registers. +// All bits numbers are starting from 0. +#define PLX9054_STS_CMD_OFFSET 0x4 + +#define PLX9054_SUBSYSTEM_ID_OFFSET 0x2C +#define SUBSYSTEM_ID_SHIFT 16 + +#define PLX9054_CONF_HOT_SWAP_OFFSET 0x48 +#define HOT_SWAP_LED 0x00080000 + +#define PLX9054_CONF_VPD_OFFSET 0x4C +#define VPD_ADDRESS_SHIFT 16 +#define VPD_ADDRESS 0x7FFF0000 +#define VPD_FLAG 0x80000000 + +#define PLX9054_CONF_VPD_DATA 0x50 + +// Definitions for the PLX internal registers. +#define PLX9054_LAS0RR_OFFSET 0x00 + +#define PLX9054_LB0_BASE_ADR 0x04 +#define SPACE0_ENABLED 0x00000001 +#define LB_SPACE0_ADDRESS_MASK 0xFFFFFFF0 + +#define PLX9054_MARBR_OFFSET 0x08 +#define DIRECT_SLAVE_LOCK_ENABLE (1 << 22) // 22 bit +#define PRIOPITY_MASK 0x00180000 +#define PRIORITY_DMA_0 0x00100000 + +// +#define PLX9054_BIGEND_LMISC_PROTAREA 0x0C +#define PROT_AREA_SHIFT 16 +#define PROT_AREA_MASK 0x007F0000 +#define BIGEND_DIRECT_MASTER 0x00000002 +#define BIGEND_SPACE_1 (1 << 5) // 5 bit +#define BIGEND_DMA0 (1 << 7) + +#define PLX9054_LBRD0_OFFSET 0x18 +#define SPACE0_BUSWIDTH8 0x00000000 +#define SPACE0_BUSWIDTH16 0x00000001 +#define SPACE0_BUSWIDTH32 0x00000003 +#define SPACE0_READY_INPUT_ENABLE 0x00000040 +#define SPACE0_BTERM_INPUT_ENABLE 0x00000080 +#define SPACE0_PREFETCH_DISABLE 0x00000100 +#define SPACE0_BURST_ENABLE 0x01000000 +#define SPACE0_PREFETCH_COUNT_ENABLE 0x00000400 +#define PCI_TARGET_RETRY_DELAY_MAX 0xF0000000 /* 15 * 4 PCI clocks */ + + +#define EROM_BUSWIDTH8 0x00000000 +#define EROM_BUSWIDTH16 0x00010000 +#define EROM_BUSWIDTH32 0x00030000 +#define EROM_READY_INPUT_ENABLE 0x00400000 +#define EROM_BTERM_INPUT_ENABLE 0x00800000 +#define EROM_PREFETCH_DISABLE 0x00000200 +#define EROM_BURST_ENABLE 0x04000000 +#define EROM_PREFETCH_COUNT_ENABLE 0x00000400 + +#define PLX9054_LB_PCI_RANGE 0x1C +#define PLX9054_DMRR_OFFSET PLX9054_LB_PCI_RANGE +#define LB_PCI_RANGE_MASK 0xFFFF0000 + +#define PLX9054_LB_BASE_ADR 0x20 +#define PLX9054_DMLBAM_OFFSET PLX9054_LB_BASE_ADR +#define LB_BASE_ADR_MASK 0xFFFF0000 + +#define PLX9054_DMLBA1_OFFSET 0x24 + +#define PLX9054_PCI_BASE_ADR 0x28 +#define PLX9054_DMPBAM_OFFSET PLX9054_PCI_BASE_ADR +#define LB_PCI_MEM_ENABLE 0x00000001 +#define LB_PCI_IO_ENABLE 0x00000002 +#define PCI_PREFETCH_SIZE_INF 0x00001008 +#define PCI_KEEP_BUS 0x00000010 +#define USE_PCI_BASE_ADR 0x00002000 +#define PCI_BASE_ADR_MASK 0xFFFF0000 + +// INTCSR0 register +#define PLX9054_INT_CNTRL_STS 0x68 +#define LSERR_ENABLE 0x00000003 // 1 and 2 bit. +#define PCI_INT_ENABLE 0x00000100 // 8 bit +#define PCI_DOORBELL_ENABLE 0x00000200 // 9 bit +#define PCI_ABORT_INT_ENABLE (1<<10) // 10 bit +#define LOCAL_INT_INPUT_ENABLE 0x00000800 // 11 bit +#define RETRY_ABORT_ENABLE (1<<12) // 12 bit. +#define LOCAL_INT_ACTIVE 0x00008000 // 15 bit +#define LOCAL_INT_OUTPUT_ENABLE 0x00010000 // 16 bit +#define LOCAL_DOORBELL_ENABLE 0x00020000 // 17 bit +#define DMA0_INT_ENABLE 0x00040000 // 18 bit +#define DMA1_INT_ENABLE 0x00080000 // 19 bit +#define DMA0_INT_ACTIVE 0x00200000 // 21 bit +#define DMA1_INT_ACTIVE 0x00400000 + + +#define PLX9054_CNTRL_OFFSET 0x6C +#define SERIAL_EEPROM_CLK 0x01000000 +#define SERIAL_EEPROM_DATA 0x04000000 +#define SERIAL_EEPROM_CS 0x02000000 +#define GP_OUTPUT 0x00010000 // 16 bit +#define GP_INPUT (1 << 17) // 17 bit +#define GP_OUTPUT_ENABLE 0x00040000 +//#define SERIAL_EEPROM_PESENT + + +// DMAMODE0 register. +#define PLX9054_DMA0_MODE 0x80 +#define DMA0_LB_WIDTH_16 0x1 +#define DMA0_LB_WIDTH_32 0x2 +#define DMA0_LB_WIDTH_MASK 0x3 +#define DMA0_BTERM_ENABLE (1 << 7) // 7 bit +#define DMA0_LOCAL_BURST_ENABLE (1 << 8) // 8 bit +#define DMA0_DONE_ENABLE (1 << 10) // 10 bit +#define DMA0_HOLD_LOCAL_ADRESS (1 << 11) // 11 bit +#define DMA0_DEMAND_MODE (1 << 12) // 12 bit +#define DMA0_FAST_TERMINATION (1 << 15) // 15 bit +#define DMA0_INT_TO_PCI (1 << 17) // 17 bit + + +// DMAPADR0 register +#define PLX9054_DMA0_PCI_ADDRESS 0x84 + + +// DMALADR0 register +#define PLX9054_DMA0_LB_ADDRESS 0x88 + + +// DMASIZ0 register +#define PLX9054_DMA0_SIZE 0x8C + + +// DMADPR0 register +#define PLX9054_DMA0_DESCR_PTR 0x90 +#define DMA0_DESCRIPTOR_INT_ENABLE (1 << 2) // 2 bit +#define DMA0_LOCAL_TO_PCI_TRANSFER (1 << 3) // 3 bit + + +#define PLX9054_DMA1_MODE 0x94 +#define DMA1_DONE_ENABLE 0x00000400 // 10 bit +#define DMA1_INT_TO_PCI 0x00020000 // 18 bit + + +#define PLX9054_DMA1_DESCR_PTR 0xA4 +#define DMA1_INT_TERM_CNT_ENABLE 0x00000004 + +// DMACSR0 register +#define PLX9054_DMA_CMD_STS 0xA8 +#define DMA0_ENABLE 1 // 0 bit +#define DMA0_START (1 << 1) // 1 bit +#define DMA0_ABORT (1 << 2) // 2 bit +#define DMA0_CLEAR_INT (1 << 3) // 3 bit +#define DMA0_TRANSFER_DONE (1 << 4) // 4 bit + +#define DMA1_CLEAR_INT 0x00000800 +#define DMA1_TRANSFER_DONE 0x00001000 + +#define PLX9054_LAS1RR_OFFSET 0xF0 + +#define PLX9054_LB1_BASE_ADR 0xF4 +#define PLX9054_LAS1BA_OFFSET PLX9054_LB1_BASE_ADR +#define SPACE1_ENABLED 0x00000001 +#define LB_SPACE1_ADDRESS_MASK 0xFFFFFFF0 + +#define PLX9054_LBRD1_OFFSET 0xF8 +#define SPACE1_BUSWIDTH8 0x00000000 +#define SPACE1_BUSWIDTH16 0x00000001 +#define SPACE1_BUSWIDTH32 0x00000003 +#define SPACE1_READY_INPUT_ENABLE 0x00000040 +#define SPACE1_BTERM_INPUT_ENABLE 0x00000080 +#define SPACE1_PREFETCH_DISABLE 0x00000200 +#define SPACE1_BURST_ENABLE 0x00000100 +#define SPACE1_PREFETCH_COUNT_ENABLE 0x00000400 + + +#endif diff --git a/v4l_experimental/rds-saa6588.c b/v4l_experimental/rds-saa6588.c new file mode 100644 index 000000000..de4464a08 --- /dev/null +++ b/v4l_experimental/rds-saa6588.c @@ -0,0 +1,134 @@ +/* + * poll i2c RDS receiver [Philips saa6588] + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <linux/types.h> +#include <linux/i2c.h> + +int debug; + +/* ----------------------------------------------------------------- */ + +char rds_psn[9]; +char rds_txt[65]; + +void rds_decode(int blkno,int b1, int b2) +{ + static int group,spare,c1,c2; + + switch (blkno) { + case 0: + if (debug) + fprintf(stderr,"block A - id=%d\n", + (b1 << 8) | b2); + break; + case 1: + if (debug) + fprintf(stderr,"block B - group=%d%c tp=%d pty=%d spare=%d\n", + (b1 >> 4) & 0x0f, + ((b1 >> 3) & 0x01) + 'A', + (b1 >> 2) & 0x01, + ((b1 << 3) & 0x18) | ((b2 >> 5) & 0x07), + b2 & 0x1f); + group = (b1 >> 3) & 0x1f; + spare = b2 & 0x1f; + break; + case 2: + if (debug) + fprintf(stderr,"block C - 0x%02x 0x%02x\n",b1,b2); + c1 = b1; + c2 = b2; + break; + case 3: + if (debug) + fprintf(stderr,"block D - 0x%02x 0x%02x\n",b1,b2); + switch (group) { + case 0: + rds_psn[2*(spare & 0x03)+0] = b1; + rds_psn[2*(spare & 0x03)+1] = b2; + if ((spare & 0x03) == 0x03) + fprintf(stderr,"PSN #>%s<#\n",rds_psn); + break; + case 4: + rds_txt[4*(spare & 0x0f)+0] = c1; + rds_txt[4*(spare & 0x0f)+1] = c2; + rds_txt[4*(spare & 0x0f)+2] = b1; + rds_txt[4*(spare & 0x0f)+3] = b2; + if ((spare & 0x0f) == 0x0f) + fprintf(stderr,"TXT #>%s<#\n",rds_txt); + break; + } + break; + default: + fprintf(stderr,"unknown block [%d]\n",blkno); + } +} + +int +main(int argc, char *argv[]) +{ + int c,f,rc, no, lastno = -1; + unsigned char b[40]; + char *device = "/dev/i2c-0"; + + /* parse options */ + while (-1 != (c=getopt(argc,argv,"hvd:"))) { + switch (c){ + case 'd': + if (optarg) + device = optarg; + break; + case 'v': + debug = 1; + break; + case 'h': + default: + printf("poll i2c RDS receiver [saa6588] via chardev\n"); + printf("usage: %s [ -d i2c-device ]\n",argv[0]); + exit(1); + } + } + + if (-1 == (f = open(device,O_RDWR))) { + fprintf(stderr,"open %s: %s\n",device,strerror(errno)); + exit(1); + } + ioctl(f,I2C_SLAVE,0x20 >> 1); + for (;;) { + memset(b,0,sizeof(b)); + rc = read(f,b,6); + if (6 != rc) { + fprintf(stderr,"oops: read: rc=%d, expected 6 [%s]\n", + rc,strerror(errno)); + break; + } + if (0 == (b[0] & 0x10)) { + fprintf(stderr,"no signal\r"); + continue; + } + if (1 == (b[0] & 0x08)) { + fprintf(stderr,"overflow detected\n"); + } + if (1 == (b[0] & 0x04)) { + fprintf(stderr,"reset detected\n"); + } + if (debug) + fprintf(stderr,"raw: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", + b[0],b[1],b[2],b[3],b[4],b[5]); + no = b[0] >> 5; + if (lastno != no) { + rds_decode(no, b[1], b[2]); + lastno = no; + } + usleep(10*1000); + } + close(f); + exit(0); +} |