Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

892
sound/pci/Kconfig Normal file
View file

@ -0,0 +1,892 @@
# ALSA PCI drivers
menuconfig SND_PCI
bool "PCI sound devices"
depends on PCI
default y
help
Support for sound devices connected via the PCI bus.
if SND_PCI
config SND_AD1889
tristate "Analog Devices AD1889"
select SND_AC97_CODEC
help
Say Y here to include support for the integrated AC97 sound
device found in particular on the Hewlett-Packard [BCJ]-xxx0
class PA-RISC workstations, using the AD1819 codec.
To compile this as a module, choose M here: the module
will be called snd-ad1889.
config SND_ALS300
tristate "Avance Logic ALS300/ALS300+"
select SND_PCM
select SND_AC97_CODEC
select SND_OPL3_LIB
select ZONE_DMA
help
Say 'Y' or 'M' to include support for Avance Logic ALS300/ALS300+
To compile this driver as a module, choose M here: the module
will be called snd-als300
config SND_ALS4000
tristate "Avance Logic ALS4000"
depends on ISA_DMA_API
select SND_OPL3_LIB
select SND_MPU401_UART
select SND_PCM
select SND_SB_COMMON
help
Say Y here to include support for soundcards based on Avance Logic
ALS4000 chips.
To compile this driver as a module, choose M here: the module
will be called snd-als4000.
config SND_ALI5451
tristate "ALi M5451 PCI Audio Controller"
select SND_MPU401_UART
select SND_AC97_CODEC
select ZONE_DMA
help
Say Y here to include support for the integrated AC97 sound
device on motherboards using the ALi M5451 Audio Controller
(M1535/M1535D/M1535+/M1535D+ south bridges). Newer chipsets
use the "Intel/SiS/nVidia/AMD/ALi AC97 Controller" driver.
To compile this driver as a module, choose M here: the module
will be called snd-ali5451.
config SND_ASIHPI
tristate "AudioScience ASIxxxx"
depends on X86
select FW_LOADER
select SND_PCM
select SND_HWDEP
help
Say Y here to include support for AudioScience ASI sound cards.
To compile this driver as a module, choose M here: the module
will be called snd-asihpi.
config SND_ATIIXP
tristate "ATI IXP AC97 Controller"
select SND_AC97_CODEC
help
Say Y here to include support for the integrated AC97 sound
device on motherboards with ATI chipsets (ATI IXP 150/200/250/
300/400).
To compile this driver as a module, choose M here: the module
will be called snd-atiixp.
config SND_ATIIXP_MODEM
tristate "ATI IXP Modem"
select SND_AC97_CODEC
help
Say Y here to include support for the integrated MC97 modem on
motherboards with ATI chipsets (ATI IXP 150/200/250).
To compile this driver as a module, choose M here: the module
will be called snd-atiixp-modem.
config SND_AU8810
tristate "Aureal Advantage"
select SND_MPU401_UART
select SND_AC97_CODEC
help
Say Y here to include support for Aureal Advantage soundcards.
Supported features: Hardware Mixer, SRC, EQ and SPDIF output.
3D support code is in place, but not yet useable. For more info,
email the ALSA developer list, or <mjander@users.sourceforge.net>.
To compile this driver as a module, choose M here: the module
will be called snd-au8810.
config SND_AU8820
tristate "Aureal Vortex"
select SND_MPU401_UART
select SND_AC97_CODEC
help
Say Y here to include support for Aureal Vortex soundcards.
Supported features: Hardware Mixer and SRC. For more info, email
the ALSA developer list, or <mjander@users.sourceforge.net>.
To compile this driver as a module, choose M here: the module
will be called snd-au8820.
config SND_AU8830
tristate "Aureal Vortex 2"
select SND_MPU401_UART
select SND_AC97_CODEC
help
Say Y here to include support for Aureal Vortex 2 soundcards.
Supported features: Hardware Mixer, SRC, EQ and SPDIF output.
3D support code is in place, but not yet useable. For more info,
email the ALSA developer list, or <mjander@users.sourceforge.net>.
To compile this driver as a module, choose M here: the module
will be called snd-au8830.
config SND_AW2
tristate "Emagic Audiowerk 2"
help
Say Y here to include support for Emagic Audiowerk 2 soundcards.
Supported features: Analog and SPDIF output. Analog or SPDIF input.
Note: Switch between analog and digital input does not always work.
It can produce continuous noise. The workaround is to switch again
(and again) between digital and analog input until it works.
To compile this driver as a module, choose M here: the module
will be called snd-aw2.
config SND_AZT3328
tristate "Aztech AZF3328 / PCI168"
select SND_OPL3_LIB
select SND_MPU401_UART
select SND_PCM
select SND_RAWMIDI
select SND_AC97_CODEC
select ZONE_DMA
help
Say Y here to include support for Aztech AZF3328 (PCI168)
soundcards.
Supported features: AC97-"conformant" mixer, MPU401/OPL3, analog I/O
(16bit/8bit, many sample rates [<= 66.2kHz], NO hardware mixing),
Digital Enhanced Game Port, 1.024MHz multimedia sequencer timer,
ext. codec (I2S port), onboard amp (4W/4Ohms/ch), suspend/resume.
To compile this driver as a module, choose M here: the module
will be called snd-azt3328.
config SND_BT87X
tristate "Bt87x Audio Capture"
select SND_PCM
help
If you want to record audio from TV cards based on
Brooktree Bt878/Bt879 chips, say Y here and read
<file:Documentation/sound/alsa/Bt87x.txt>.
To compile this driver as a module, choose M here: the module
will be called snd-bt87x.
config SND_BT87X_OVERCLOCK
bool "Bt87x Audio overclocking"
depends on SND_BT87X
help
Say Y here if 448000 Hz isn't enough for you and you want to
record from the analog input with up to 1792000 Hz.
Higher sample rates won't hurt your hardware, but audio
quality may suffer.
config SND_CA0106
tristate "SB Audigy LS / Live 24bit"
select SND_AC97_CODEC
select SND_RAWMIDI
select SND_VMASTER
help
Say Y here to include support for the Sound Blaster Audigy LS
and Live 24bit.
To compile this driver as a module, choose M here: the module
will be called snd-ca0106.
config SND_CMIPCI
tristate "C-Media 8338, 8738, 8768, 8770"
select SND_OPL3_LIB
select SND_MPU401_UART
select SND_PCM
help
If you want to use soundcards based on C-Media CMI8338, CMI8738,
CMI8768 or CMI8770 chips, say Y here and read
<file:Documentation/sound/alsa/CMIPCI.txt>.
To compile this driver as a module, choose M here: the module
will be called snd-cmipci.
config SND_OXYGEN_LIB
tristate
config SND_OXYGEN
tristate "C-Media 8786, 8787, 8788 (Oxygen)"
select SND_OXYGEN_LIB
select SND_PCM
select SND_MPU401_UART
help
Say Y here to include support for sound cards based on the
C-Media CMI8788 (Oxygen HD Audio) chip:
* Asound A-8788
* Asus Xonar DG/DGX
* AuzenTech X-Meridian
* AuzenTech X-Meridian 2G
* Bgears b-Enspirer
* Club3D Theatron DTS
* HT-Omega Claro (plus)
* HT-Omega Claro halo (XT)
* Kuroutoshikou CMI8787-HG2PCI
* Razer Barracuda AC-1
* Sondigo Inferno
* TempoTec/MediaTek HiFier Fantasia
* TempoTec/MediaTek HiFier Serenade
To compile this driver as a module, choose M here: the module
will be called snd-oxygen.
config SND_CS4281
tristate "Cirrus Logic (Sound Fusion) CS4281"
select SND_OPL3_LIB
select SND_RAWMIDI
select SND_AC97_CODEC
help
Say Y here to include support for Cirrus Logic CS4281 chips.
To compile this driver as a module, choose M here: the module
will be called snd-cs4281.
config SND_CS46XX
tristate "Cirrus Logic (Sound Fusion) CS4280/CS461x/CS462x/CS463x"
select SND_RAWMIDI
select SND_AC97_CODEC
select FW_LOADER
help
Say Y here to include support for Cirrus Logic CS4610/CS4612/
CS4614/CS4615/CS4622/CS4624/CS4630/CS4280 chips.
To compile this driver as a module, choose M here: the module
will be called snd-cs46xx.
config SND_CS46XX_NEW_DSP
bool "Cirrus Logic (Sound Fusion) New DSP support"
depends on SND_CS46XX
default y
help
Say Y here to use a new DSP image for SPDIF and dual codecs.
This works better than the old code, so say Y.
config SND_CS5530
tristate "CS5530 Audio"
depends on ISA_DMA_API && (X86_32 || COMPILE_TEST)
select SND_SB16_DSP
help
Say Y here to include support for audio on Cyrix/NatSemi CS5530 chips.
To compile this driver as a module, choose M here: the module
will be called snd-cs5530.
config SND_CS5535AUDIO
tristate "CS5535/CS5536 Audio"
depends on X86_32 || MIPS || COMPILE_TEST
select SND_PCM
select SND_AC97_CODEC
help
Say Y here to include support for audio on CS5535 chips. It is
referred to as NS CS5535 IO or AMD CS5535 IO companion in
various literature. This driver also supports the CS5536 audio
device. However, for both chips, on certain boards, you may
need to use ac97_quirk=hp_only if your board has physically
mapped headphone out to master output. If that works for you,
send lspci -vvv output to the mailing list so that your board
can be identified in the quirks list.
To compile this driver as a module, choose M here: the module
will be called snd-cs5535audio.
config SND_CTXFI
tristate "Creative Sound Blaster X-Fi"
select SND_PCM
help
If you want to use soundcards based on Creative Sound Blastr X-Fi
boards with 20k1 or 20k2 chips, say Y here.
To compile this driver as a module, choose M here: the module
will be called snd-ctxfi.
config SND_DARLA20
tristate "(Echoaudio) Darla20"
select FW_LOADER
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Darla.
To compile this driver as a module, choose M here: the module
will be called snd-darla20
config SND_GINA20
tristate "(Echoaudio) Gina20"
select FW_LOADER
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Gina.
To compile this driver as a module, choose M here: the module
will be called snd-gina20
config SND_LAYLA20
tristate "(Echoaudio) Layla20"
select FW_LOADER
select SND_RAWMIDI
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Layla.
To compile this driver as a module, choose M here: the module
will be called snd-layla20
config SND_DARLA24
tristate "(Echoaudio) Darla24"
select FW_LOADER
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Darla24.
To compile this driver as a module, choose M here: the module
will be called snd-darla24
config SND_GINA24
tristate "(Echoaudio) Gina24"
select FW_LOADER
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Gina24.
To compile this driver as a module, choose M here: the module
will be called snd-gina24
config SND_LAYLA24
tristate "(Echoaudio) Layla24"
select FW_LOADER
select SND_RAWMIDI
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Layla24.
To compile this driver as a module, choose M here: the module
will be called snd-layla24
config SND_MONA
tristate "(Echoaudio) Mona"
select FW_LOADER
select SND_RAWMIDI
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Mona.
To compile this driver as a module, choose M here: the module
will be called snd-mona
config SND_MIA
tristate "(Echoaudio) Mia"
select FW_LOADER
select SND_RAWMIDI
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Mia and Mia-midi.
To compile this driver as a module, choose M here: the module
will be called snd-mia
config SND_ECHO3G
tristate "(Echoaudio) 3G cards"
select FW_LOADER
select SND_RAWMIDI
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Gina3G and Layla3G.
To compile this driver as a module, choose M here: the module
will be called snd-echo3g
config SND_INDIGO
tristate "(Echoaudio) Indigo"
select FW_LOADER
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Indigo.
To compile this driver as a module, choose M here: the module
will be called snd-indigo
config SND_INDIGOIO
tristate "(Echoaudio) Indigo IO"
select FW_LOADER
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Indigo IO.
To compile this driver as a module, choose M here: the module
will be called snd-indigoio
config SND_INDIGODJ
tristate "(Echoaudio) Indigo DJ"
select FW_LOADER
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Indigo DJ.
To compile this driver as a module, choose M here: the module
will be called snd-indigodj
config SND_INDIGOIOX
tristate "(Echoaudio) Indigo IOx"
select FW_LOADER
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Indigo IOx.
To compile this driver as a module, choose M here: the module
will be called snd-indigoiox
config SND_INDIGODJX
tristate "(Echoaudio) Indigo DJx"
select FW_LOADER
select SND_PCM
help
Say 'Y' or 'M' to include support for Echoaudio Indigo DJx.
To compile this driver as a module, choose M here: the module
will be called snd-indigodjx
config SND_EMU10K1
tristate "Emu10k1 (SB Live!, Audigy, E-mu APS)"
select FW_LOADER
select SND_HWDEP
select SND_RAWMIDI
select SND_AC97_CODEC
select ZONE_DMA
help
Say Y to include support for Sound Blaster PCI 512, Live!,
Audigy and E-mu APS (partially supported) soundcards.
The confusing multitude of mixer controls is documented in
<file:Documentation/sound/alsa/SB-Live-mixer.txt> and
<file:Documentation/sound/alsa/Audigy-mixer.txt>.
To compile this driver as a module, choose M here: the module
will be called snd-emu10k1.
config SND_EMU10K1X
tristate "Emu10k1X (Dell OEM Version)"
select SND_AC97_CODEC
select SND_RAWMIDI
select ZONE_DMA
help
Say Y here to include support for the Dell OEM version of the
Sound Blaster Live!.
To compile this driver as a module, choose M here: the module
will be called snd-emu10k1x.
config SND_ENS1370
tristate "(Creative) Ensoniq AudioPCI 1370"
select SND_RAWMIDI
select SND_PCM
help
Say Y here to include support for Ensoniq AudioPCI ES1370 chips.
To compile this driver as a module, choose M here: the module
will be called snd-ens1370.
config SND_ENS1371
tristate "(Creative) Ensoniq AudioPCI 1371/1373"
select SND_RAWMIDI
select SND_AC97_CODEC
help
Say Y here to include support for Ensoniq AudioPCI ES1371 chips and
Sound Blaster PCI 64 or 128 soundcards.
To compile this driver as a module, choose M here: the module
will be called snd-ens1371.
config SND_ES1938
tristate "ESS ES1938/1946/1969 (Solo-1)"
select SND_OPL3_LIB
select SND_MPU401_UART
select SND_AC97_CODEC
select ZONE_DMA
help
Say Y here to include support for soundcards based on ESS Solo-1
(ES1938, ES1946, ES1969) chips.
To compile this driver as a module, choose M here: the module
will be called snd-es1938.
config SND_ES1968
tristate "ESS ES1968/1978 (Maestro-1/2/2E)"
select SND_MPU401_UART
select SND_AC97_CODEC
select ZONE_DMA
help
Say Y here to include support for soundcards based on ESS Maestro
1/2/2E chips.
To compile this driver as a module, choose M here: the module
will be called snd-es1968.
config SND_ES1968_INPUT
bool "Enable input device for es1968 volume buttons"
depends on SND_ES1968
depends on INPUT=y || INPUT=SND_ES1968
help
If you say Y here, you will get an input device which reports
keypresses for the volume buttons connected to the es1968 chip.
If you say N the buttons will directly control the master volume.
It is recommended to say Y.
config SND_ES1968_RADIO
bool "Enable TEA5757 radio tuner support for es1968"
depends on SND_ES1968
depends on MEDIA_RADIO_SUPPORT
depends on VIDEO_V4L2=y || VIDEO_V4L2=SND_ES1968
select RADIO_ADAPTERS
select RADIO_TEA575X
help
Say Y here to include support for TEA5757 radio tuner integrated on
some MediaForte cards (e.g. SF64-PCE2).
config SND_FM801
tristate "ForteMedia FM801"
select SND_OPL3_LIB
select SND_MPU401_UART
select SND_AC97_CODEC
help
Say Y here to include support for soundcards based on the ForteMedia
FM801 chip.
To compile this driver as a module, choose M here: the module
will be called snd-fm801.
config SND_FM801_TEA575X_BOOL
bool "ForteMedia FM801 + TEA5757 tuner"
depends on SND_FM801
depends on MEDIA_RADIO_SUPPORT
depends on VIDEO_V4L2=y || VIDEO_V4L2=SND_FM801
select RADIO_ADAPTERS
select RADIO_TEA575X
help
Say Y here to include support for soundcards based on the ForteMedia
FM801 chip with a TEA5757 tuner (MediaForte SF256-PCS, SF256-PCP and
SF64-PCR) into the snd-fm801 driver.
config SND_HDSP
tristate "RME Hammerfall DSP Audio"
select FW_LOADER
select SND_HWDEP
select SND_RAWMIDI
select SND_PCM
help
Say Y here to include support for RME Hammerfall DSP Audio
soundcards.
To compile this driver as a module, choose M here: the module
will be called snd-hdsp.
comment "Don't forget to add built-in firmwares for HDSP driver"
depends on SND_HDSP=y
config SND_HDSPM
tristate "RME Hammerfall DSP MADI/RayDAT/AIO"
select SND_HWDEP
select SND_RAWMIDI
select SND_PCM
help
Say Y here to include support for RME Hammerfall DSP MADI,
RayDAT and AIO soundcards.
To compile this driver as a module, choose M here: the module
will be called snd-hdspm.
config SND_ICE1712
tristate "ICEnsemble ICE1712 (Envy24)"
select SND_MPU401_UART
select SND_AC97_CODEC
select BITREVERSE
select ZONE_DMA
help
Say Y here to include support for soundcards based on the
ICE1712 (Envy24) chip.
Currently supported hardware is: M-Audio Delta 1010(LT),
DiO 2496, 66, 44, 410, Audiophile 24/96; Digigram VX442;
TerraTec EWX 24/96, EWS 88MT/D, DMX 6Fire, Phase 88;
Hoontech SoundTrack DSP 24/Value/Media7.1; Event EZ8;
Lionstracs Mediastation, Terrasoniq TS 88.
To compile this driver as a module, choose M here: the module
will be called snd-ice1712.
config SND_ICE1724
tristate "ICE/VT1724/1720 (Envy24HT/PT)"
select SND_RAWMIDI
select SND_AC97_CODEC
select SND_VMASTER
help
Say Y here to include support for soundcards based on
ICE/VT1724/1720 (Envy24HT/PT) chips.
Currently supported hardware is: AMP AUDIO2000; M-Audio
Revolution 5.1, 7.1, Audiophile 192; TerraTec Aureon 5.1 Sky,
7.1 Space/Universe, Phase 22/28; Onkyo SE-90PCI, SE-200PCI;
AudioTrak Prodigy 192, 7.1 (HIFI/LT/XT), HD2; Hercules
Fortissimo IV; ESI Juli@; Pontis MS300; EGO-SYS WaveTerminal
192M; Albatron K8X800 Pro II; Chaintech ZNF3-150/250, 9CJS,
AV-710; Shuttle SN25P; Philips PSC724 Ultimate Edge.
To compile this driver as a module, choose M here: the module
will be called snd-ice1724.
config SND_INTEL8X0
tristate "Intel/SiS/nVidia/AMD/ALi AC97 Controller"
select SND_AC97_CODEC
help
Say Y here to include support for the integrated AC97 sound
device on motherboards with Intel/SiS/nVidia/AMD chipsets, or
ALi chipsets using the M5455 Audio Controller. (There is a
separate driver for ALi M5451 Audio Controllers.)
To compile this driver as a module, choose M here: the module
will be called snd-intel8x0.
config SND_INTEL8X0M
tristate "Intel/SiS/nVidia/AMD MC97 Modem"
select SND_AC97_CODEC
help
Say Y here to include support for the integrated MC97 modem on
motherboards with Intel/SiS/nVidia/AMD chipsets.
To compile this driver as a module, choose M here: the module
will be called snd-intel8x0m.
config SND_KORG1212
tristate "Korg 1212 IO"
select SND_PCM
help
Say Y here to include support for Korg 1212IO soundcards.
To compile this driver as a module, choose M here: the module
will be called snd-korg1212.
config SND_LOLA
tristate "Digigram Lola"
select SND_PCM
help
Say Y to include support for Digigram Lola boards.
To compile this driver as a module, choose M here: the module
will be called snd-lola.
config SND_LX6464ES
tristate "Digigram LX6464ES"
depends on HAS_IOPORT_MAP
select SND_PCM
help
Say Y here to include support for Digigram LX6464ES boards.
To compile this driver as a module, choose M here: the module
will be called snd-lx6464es.
config SND_MAESTRO3
tristate "ESS Allegro/Maestro3"
select SND_AC97_CODEC
select ZONE_DMA
help
Say Y here to include support for soundcards based on ESS Maestro 3
(Allegro) chips.
To compile this driver as a module, choose M here: the module
will be called snd-maestro3.
config SND_MAESTRO3_INPUT
bool "Enable input device for maestro3 volume buttons"
depends on SND_MAESTRO3
depends on INPUT=y || INPUT=SND_MAESTRO3
help
If you say Y here, you will get an input device which reports
keypresses for the volume buttons connected to the maestro3 chip.
If you say N the buttons will directly control the master volume.
It is recommended to say Y.
config SND_MIXART
tristate "Digigram miXart"
select FW_LOADER
select SND_HWDEP
select SND_PCM
help
If you want to use Digigram miXart soundcards, say Y here and
read <file:Documentation/sound/alsa/MIXART.txt>.
To compile this driver as a module, choose M here: the module
will be called snd-mixart.
config SND_NM256
tristate "NeoMagic NM256AV/ZX"
select SND_AC97_CODEC
help
Say Y here to include support for NeoMagic NM256AV/ZX chips.
To compile this driver as a module, choose M here: the module
will be called snd-nm256.
config SND_PCXHR
tristate "Digigram PCXHR"
select FW_LOADER
select SND_PCM
select SND_HWDEP
help
Say Y here to include support for Digigram PCXHR boards.
To compile this driver as a module, choose M here: the module
will be called snd-pcxhr.
config SND_RIPTIDE
tristate "Conexant Riptide"
select FW_LOADER
select SND_OPL3_LIB
select SND_MPU401_UART
select SND_AC97_CODEC
help
Say 'Y' or 'M' to include support for Conexant Riptide chip.
To compile this driver as a module, choose M here: the module
will be called snd-riptide
config SND_RME32
tristate "RME Digi32, 32/8, 32 PRO"
select SND_PCM
help
Say Y to include support for RME Digi32, Digi32 PRO and
Digi32/8 (Sek'd Prodif32, Prodif96 and Prodif Gold) audio
devices.
To compile this driver as a module, choose M here: the module
will be called snd-rme32.
config SND_RME96
tristate "RME Digi96, 96/8, 96/8 PRO"
select SND_PCM
help
Say Y here to include support for RME Digi96, Digi96/8 and
Digi96/8 PRO/PAD/PST soundcards.
To compile this driver as a module, choose M here: the module
will be called snd-rme96.
config SND_RME9652
tristate "RME Digi9652 (Hammerfall)"
select SND_PCM
help
Say Y here to include support for RME Hammerfall (RME
Digi9652/Digi9636) soundcards.
To compile this driver as a module, choose M here: the module
will be called snd-rme9652.
config SND_SIS7019
tristate "SiS 7019 Audio Accelerator"
depends on X86_32
select SND_AC97_CODEC
select ZONE_DMA
help
Say Y here to include support for the SiS 7019 Audio Accelerator.
To compile this driver as a module, choose M here: the module
will be called snd-sis7019.
config SND_SONICVIBES
tristate "S3 SonicVibes"
select SND_OPL3_LIB
select SND_MPU401_UART
select SND_AC97_CODEC
select ZONE_DMA
help
Say Y here to include support for soundcards based on the S3
SonicVibes chip.
To compile this driver as a module, choose M here: the module
will be called snd-sonicvibes.
config SND_TRIDENT
tristate "Trident 4D-Wave DX/NX; SiS 7018"
select SND_MPU401_UART
select SND_AC97_CODEC
select ZONE_DMA
help
Say Y here to include support for soundcards based on Trident
4D-Wave DX/NX or SiS 7018 chips.
To compile this driver as a module, choose M here: the module
will be called snd-trident.
config SND_VIA82XX
tristate "VIA 82C686A/B, 8233/8235 AC97 Controller"
select SND_MPU401_UART
select SND_AC97_CODEC
help
Say Y here to include support for the integrated AC97 sound
device on motherboards with VIA chipsets.
To compile this driver as a module, choose M here: the module
will be called snd-via82xx.
config SND_VIA82XX_MODEM
tristate "VIA 82C686A/B, 8233 based Modems"
select SND_AC97_CODEC
help
Say Y here to include support for the integrated MC97 modem on
motherboards with VIA chipsets.
To compile this driver as a module, choose M here: the module
will be called snd-via82xx-modem.
config SND_VIRTUOSO
tristate "Asus Virtuoso 66/100/200 (Xonar)"
select SND_OXYGEN_LIB
select SND_PCM
select SND_MPU401_UART
select SND_JACK if INPUT=y || INPUT=SND
help
Say Y here to include support for sound cards based on the
Asus AV66/AV100/AV200 chips, i.e., Xonar D1, DX, D2, D2X, DS, DSX,
Essence ST (Deluxe), and Essence STX (II).
Support for the HDAV1.3 (Deluxe) and HDAV1.3 Slim is experimental;
for the Xense, missing.
To compile this driver as a module, choose M here: the module
will be called snd-virtuoso.
config SND_VX222
tristate "Digigram VX222"
select SND_VX_LIB
help
Say Y here to include support for Digigram VX222 soundcards.
To compile this driver as a module, choose M here: the module
will be called snd-vx222.
config SND_YMFPCI
tristate "Yamaha YMF724/740/744/754"
select SND_OPL3_LIB
select SND_MPU401_UART
select SND_AC97_CODEC
help
Say Y here to include support for Yamaha PCI audio chips -
YMF724, YMF724F, YMF740, YMF740C, YMF744, YMF754.
To compile this driver as a module, choose M here: the module
will be called snd-ymfpci.
endif # SND_PCI
source "sound/pci/hda/Kconfig"

82
sound/pci/Makefile Normal file
View file

@ -0,0 +1,82 @@
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
#
snd-ad1889-objs := ad1889.o
snd-als300-objs := als300.o
snd-als4000-objs := als4000.o
snd-atiixp-objs := atiixp.o
snd-atiixp-modem-objs := atiixp_modem.o
snd-azt3328-objs := azt3328.o
snd-bt87x-objs := bt87x.o
snd-cmipci-objs := cmipci.o
snd-cs4281-objs := cs4281.o
snd-cs5530-objs := cs5530.o
snd-ens1370-objs := ens1370.o ak4531_codec.o
snd-ens1371-objs := ens1371.o
snd-es1938-objs := es1938.o
snd-es1968-objs := es1968.o
snd-fm801-objs := fm801.o
snd-intel8x0-objs := intel8x0.o
snd-intel8x0m-objs := intel8x0m.o
snd-maestro3-objs := maestro3.o
snd-rme32-objs := rme32.o
snd-rme96-objs := rme96.o
snd-sis7019-objs := sis7019.o
snd-sonicvibes-objs := sonicvibes.o
snd-via82xx-objs := via82xx.o
snd-via82xx-modem-objs := via82xx_modem.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_AD1889) += snd-ad1889.o
obj-$(CONFIG_SND_ALS300) += snd-als300.o
obj-$(CONFIG_SND_ALS4000) += snd-als4000.o
obj-$(CONFIG_SND_ATIIXP) += snd-atiixp.o
obj-$(CONFIG_SND_ATIIXP_MODEM) += snd-atiixp-modem.o
obj-$(CONFIG_SND_AZT3328) += snd-azt3328.o
obj-$(CONFIG_SND_BT87X) += snd-bt87x.o
obj-$(CONFIG_SND_CMIPCI) += snd-cmipci.o
obj-$(CONFIG_SND_CS4281) += snd-cs4281.o
obj-$(CONFIG_SND_CS5530) += snd-cs5530.o
obj-$(CONFIG_SND_ENS1370) += snd-ens1370.o
obj-$(CONFIG_SND_ENS1371) += snd-ens1371.o
obj-$(CONFIG_SND_ES1938) += snd-es1938.o
obj-$(CONFIG_SND_ES1968) += snd-es1968.o
obj-$(CONFIG_SND_FM801) += snd-fm801.o
obj-$(CONFIG_SND_INTEL8X0) += snd-intel8x0.o
obj-$(CONFIG_SND_INTEL8X0M) += snd-intel8x0m.o
obj-$(CONFIG_SND_MAESTRO3) += snd-maestro3.o
obj-$(CONFIG_SND_RME32) += snd-rme32.o
obj-$(CONFIG_SND_RME96) += snd-rme96.o
obj-$(CONFIG_SND_SIS7019) += snd-sis7019.o
obj-$(CONFIG_SND_SONICVIBES) += snd-sonicvibes.o
obj-$(CONFIG_SND_VIA82XX) += snd-via82xx.o
obj-$(CONFIG_SND_VIA82XX_MODEM) += snd-via82xx-modem.o
obj-$(CONFIG_SND) += \
ac97/ \
ali5451/ \
asihpi/ \
au88x0/ \
aw2/ \
ctxfi/ \
ca0106/ \
cs46xx/ \
cs5535audio/ \
lola/ \
lx6464es/ \
echoaudio/ \
emu10k1/ \
hda/ \
ice1712/ \
korg1212/ \
mixart/ \
nm256/ \
oxygen/ \
pcxhr/ \
riptide/ \
rme9652/ \
trident/ \
ymfpci/ \
vx222/

10
sound/pci/ac97/Makefile Normal file
View file

@ -0,0 +1,10 @@
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
#
snd-ac97-codec-y := ac97_codec.o ac97_pcm.o
snd-ac97-codec-$(CONFIG_PROC_FS) += ac97_proc.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_AC97_CODEC) += snd-ac97-codec.o

2963
sound/pci/ac97/ac97_codec.c Normal file

File diff suppressed because it is too large Load diff

66
sound/pci/ac97/ac97_id.h Normal file
View file

@ -0,0 +1,66 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Universal interface for Audio Codec '97
*
* For more details look to AC '97 component specification revision 2.2
* by Intel Corporation (http://developer.intel.com).
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#define AC97_ID_AK4540 0x414b4d00
#define AC97_ID_AK4542 0x414b4d01
#define AC97_ID_AD1819 0x41445303
#define AC97_ID_AD1881 0x41445340
#define AC97_ID_AD1881A 0x41445348
#define AC97_ID_AD1885 0x41445360
#define AC97_ID_AD1886 0x41445361
#define AC97_ID_AD1887 0x41445362
#define AC97_ID_AD1886A 0x41445363
#define AC97_ID_AD1980 0x41445370
#define AC97_ID_TR28028 0x54524108
#define AC97_ID_STAC9700 0x83847600
#define AC97_ID_STAC9704 0x83847604
#define AC97_ID_STAC9705 0x83847605
#define AC97_ID_STAC9708 0x83847608
#define AC97_ID_STAC9721 0x83847609
#define AC97_ID_STAC9744 0x83847644
#define AC97_ID_STAC9756 0x83847656
#define AC97_ID_CS4297A 0x43525910
#define AC97_ID_CS4299 0x43525930
#define AC97_ID_CS4201 0x43525948
#define AC97_ID_CS4205 0x43525958
#define AC97_ID_CS_MASK 0xfffffff8 /* bit 0-2: rev */
#define AC97_ID_ALC100 0x414c4300
#define AC97_ID_ALC650 0x414c4720
#define AC97_ID_ALC650D 0x414c4721
#define AC97_ID_ALC650E 0x414c4722
#define AC97_ID_ALC650F 0x414c4723
#define AC97_ID_ALC655 0x414c4760
#define AC97_ID_ALC658 0x414c4780
#define AC97_ID_ALC658D 0x414c4781
#define AC97_ID_ALC850 0x414c4790
#define AC97_ID_YMF743 0x594d4800
#define AC97_ID_YMF753 0x594d4803
#define AC97_ID_VT1616 0x49434551
#define AC97_ID_CM9738 0x434d4941
#define AC97_ID_CM9739 0x434d4961
#define AC97_ID_CM9761_78 0x434d4978
#define AC97_ID_CM9761_82 0x434d4982
#define AC97_ID_CM9761_83 0x434d4983
#define AC97_ID_ST7597 0x53544d02
#define AC97_ID_ST_AC97_ID4 0x53544d04

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Universal interface for Audio Codec '97
*
* For more details look to AC '97 component specification revision 2.2
* by Intel Corporation (http://developer.intel.com).
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
void snd_ac97_get_name(struct snd_ac97 *ac97, unsigned int id, char *name,
int modem);
int snd_ac97_update_bits_nolock(struct snd_ac97 *ac97, unsigned short reg,
unsigned short mask, unsigned short value);
/* ac97_proc.c */
#ifdef CONFIG_PROC_FS
void snd_ac97_bus_proc_init(struct snd_ac97_bus * ac97);
void snd_ac97_bus_proc_done(struct snd_ac97_bus * ac97);
void snd_ac97_proc_init(struct snd_ac97 * ac97);
void snd_ac97_proc_done(struct snd_ac97 * ac97);
#else
#define snd_ac97_bus_proc_init(ac97_bus_t) do { } while (0)
#define snd_ac97_bus_proc_done(ac97_bus_t) do { } while (0)
#define snd_ac97_proc_init(ac97_t) do { } while (0)
#define snd_ac97_proc_done(ac97_t) do { } while (0)
#endif

3988
sound/pci/ac97/ac97_patch.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Universal interface for Audio Codec '97
*
* For more details look to AC '97 component specification revision 2.2
* by Intel Corporation (http://developer.intel.com).
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#define AC97_SINGLE_VALUE(reg,shift,mask,invert) \
((reg) | ((shift) << 8) | ((shift) << 12) | ((mask) << 16) | \
((invert) << 24))
#define AC97_PAGE_SINGLE_VALUE(reg,shift,mask,invert,page) \
(AC97_SINGLE_VALUE(reg,shift,mask,invert) | (1<<25) | ((page) << 26))
#define AC97_SINGLE(xname, reg, shift, mask, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_ac97_info_volsw, \
.get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \
.private_value = AC97_SINGLE_VALUE(reg, shift, mask, invert) }
#define AC97_PAGE_SINGLE(xname, reg, shift, mask, invert, page) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_ac97_info_volsw, \
.get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \
.private_value = AC97_PAGE_SINGLE_VALUE(reg, shift, mask, invert, page) }
#define AC97_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
.info = snd_ac97_info_volsw, \
.get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \
.private_value = (reg) | ((shift_left) << 8) | ((shift_right) << 12) | ((mask) << 16) | ((invert) << 24) }
/* enum control */
struct ac97_enum {
unsigned char reg;
unsigned char shift_l;
unsigned char shift_r;
unsigned short mask;
const char **texts;
};
#define AC97_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, xtexts) \
{ .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \
.mask = xmask, .texts = xtexts }
#define AC97_ENUM_SINGLE(xreg, xshift, xmask, xtexts) \
AC97_ENUM_DOUBLE(xreg, xshift, xshift, xmask, xtexts)
#define AC97_ENUM(xname, xenum) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_ac97_info_enum_double, \
.get = snd_ac97_get_enum_double, .put = snd_ac97_put_enum_double, \
.private_value = (unsigned long)&xenum }
/* ac97_codec.c */
static const struct snd_kcontrol_new snd_ac97_controls_3d[];
static const struct snd_kcontrol_new snd_ac97_controls_spdif[];
static struct snd_kcontrol *snd_ac97_cnew(const struct snd_kcontrol_new *_template,
struct snd_ac97 * ac97);
static int snd_ac97_info_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
static int snd_ac97_get_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
static int snd_ac97_put_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
static int snd_ac97_try_bit(struct snd_ac97 * ac97, int reg, int bit);
static int snd_ac97_remove_ctl(struct snd_ac97 *ac97, const char *name,
const char *suffix);
static int snd_ac97_rename_ctl(struct snd_ac97 *ac97, const char *src,
const char *dst, const char *suffix);
static int snd_ac97_swap_ctl(struct snd_ac97 *ac97, const char *s1,
const char *s2, const char *suffix);
static void snd_ac97_rename_vol_ctl(struct snd_ac97 *ac97, const char *src,
const char *dst);
#ifdef CONFIG_PM
static void snd_ac97_restore_status(struct snd_ac97 *ac97);
static void snd_ac97_restore_iec958(struct snd_ac97 *ac97);
#endif
static int snd_ac97_info_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
static int snd_ac97_get_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
static int snd_ac97_put_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);

752
sound/pci/ac97/ac97_pcm.c Normal file
View file

@ -0,0 +1,752 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Universal interface for Audio Codec '97
*
* For more details look to AC '97 component specification revision 2.2
* by Intel Corporation (http://developer.intel.com) and to datasheets
* for specific codecs.
*
*
* 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/delay.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/control.h>
#include <sound/ac97_codec.h>
#include <sound/asoundef.h>
#include "ac97_id.h"
#include "ac97_local.h"
/*
* PCM support
*/
static unsigned char rate_reg_tables[2][4][9] = {
{
/* standard rates */
{
/* 3&4 front, 7&8 rear, 6&9 center/lfe */
AC97_PCM_FRONT_DAC_RATE, /* slot 3 */
AC97_PCM_FRONT_DAC_RATE, /* slot 4 */
0xff, /* slot 5 */
AC97_PCM_LFE_DAC_RATE, /* slot 6 */
AC97_PCM_SURR_DAC_RATE, /* slot 7 */
AC97_PCM_SURR_DAC_RATE, /* slot 8 */
AC97_PCM_LFE_DAC_RATE, /* slot 9 */
0xff, /* slot 10 */
0xff, /* slot 11 */
},
{
/* 7&8 front, 6&9 rear, 10&11 center/lfe */
0xff, /* slot 3 */
0xff, /* slot 4 */
0xff, /* slot 5 */
AC97_PCM_SURR_DAC_RATE, /* slot 6 */
AC97_PCM_FRONT_DAC_RATE, /* slot 7 */
AC97_PCM_FRONT_DAC_RATE, /* slot 8 */
AC97_PCM_SURR_DAC_RATE, /* slot 9 */
AC97_PCM_LFE_DAC_RATE, /* slot 10 */
AC97_PCM_LFE_DAC_RATE, /* slot 11 */
},
{
/* 6&9 front, 10&11 rear, 3&4 center/lfe */
AC97_PCM_LFE_DAC_RATE, /* slot 3 */
AC97_PCM_LFE_DAC_RATE, /* slot 4 */
0xff, /* slot 5 */
AC97_PCM_FRONT_DAC_RATE, /* slot 6 */
0xff, /* slot 7 */
0xff, /* slot 8 */
AC97_PCM_FRONT_DAC_RATE, /* slot 9 */
AC97_PCM_SURR_DAC_RATE, /* slot 10 */
AC97_PCM_SURR_DAC_RATE, /* slot 11 */
},
{
/* 10&11 front, 3&4 rear, 7&8 center/lfe */
AC97_PCM_SURR_DAC_RATE, /* slot 3 */
AC97_PCM_SURR_DAC_RATE, /* slot 4 */
0xff, /* slot 5 */
0xff, /* slot 6 */
AC97_PCM_LFE_DAC_RATE, /* slot 7 */
AC97_PCM_LFE_DAC_RATE, /* slot 8 */
0xff, /* slot 9 */
AC97_PCM_FRONT_DAC_RATE, /* slot 10 */
AC97_PCM_FRONT_DAC_RATE, /* slot 11 */
},
},
{
/* double rates */
{
/* 3&4 front, 7&8 front (t+1) */
AC97_PCM_FRONT_DAC_RATE, /* slot 3 */
AC97_PCM_FRONT_DAC_RATE, /* slot 4 */
0xff, /* slot 5 */
0xff, /* slot 6 */
AC97_PCM_FRONT_DAC_RATE, /* slot 7 */
AC97_PCM_FRONT_DAC_RATE, /* slot 8 */
0xff, /* slot 9 */
0xff, /* slot 10 */
0xff, /* slot 11 */
},
{
/* not specified in the specification */
0xff, /* slot 3 */
0xff, /* slot 4 */
0xff, /* slot 5 */
0xff, /* slot 6 */
0xff, /* slot 7 */
0xff, /* slot 8 */
0xff, /* slot 9 */
0xff, /* slot 10 */
0xff, /* slot 11 */
},
{
0xff, /* slot 3 */
0xff, /* slot 4 */
0xff, /* slot 5 */
0xff, /* slot 6 */
0xff, /* slot 7 */
0xff, /* slot 8 */
0xff, /* slot 9 */
0xff, /* slot 10 */
0xff, /* slot 11 */
},
{
0xff, /* slot 3 */
0xff, /* slot 4 */
0xff, /* slot 5 */
0xff, /* slot 6 */
0xff, /* slot 7 */
0xff, /* slot 8 */
0xff, /* slot 9 */
0xff, /* slot 10 */
0xff, /* slot 11 */
}
}};
/* FIXME: more various mappings for ADC? */
static unsigned char rate_cregs[9] = {
AC97_PCM_LR_ADC_RATE, /* 3 */
AC97_PCM_LR_ADC_RATE, /* 4 */
0xff, /* 5 */
AC97_PCM_MIC_ADC_RATE, /* 6 */
0xff, /* 7 */
0xff, /* 8 */
0xff, /* 9 */
0xff, /* 10 */
0xff, /* 11 */
};
static unsigned char get_slot_reg(struct ac97_pcm *pcm, unsigned short cidx,
unsigned short slot, int dbl)
{
if (slot < 3)
return 0xff;
if (slot > 11)
return 0xff;
if (pcm->spdif)
return AC97_SPDIF; /* pseudo register */
if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK)
return rate_reg_tables[dbl][pcm->r[dbl].rate_table[cidx]][slot - 3];
else
return rate_cregs[slot - 3];
}
static int set_spdif_rate(struct snd_ac97 *ac97, unsigned short rate)
{
unsigned short old, bits, reg, mask;
unsigned int sbits;
if (! (ac97->ext_id & AC97_EI_SPDIF))
return -ENODEV;
/* TODO: double rate support */
if (ac97->flags & AC97_CS_SPDIF) {
switch (rate) {
case 48000: bits = 0; break;
case 44100: bits = 1 << AC97_SC_SPSR_SHIFT; break;
default: /* invalid - disable output */
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
return -EINVAL;
}
reg = AC97_CSR_SPDIF;
mask = 1 << AC97_SC_SPSR_SHIFT;
} else {
if (ac97->id == AC97_ID_CM9739 && rate != 48000) {
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
return -EINVAL;
}
switch (rate) {
case 44100: bits = AC97_SC_SPSR_44K; break;
case 48000: bits = AC97_SC_SPSR_48K; break;
case 32000: bits = AC97_SC_SPSR_32K; break;
default: /* invalid - disable output */
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
return -EINVAL;
}
reg = AC97_SPDIF;
mask = AC97_SC_SPSR_MASK;
}
mutex_lock(&ac97->reg_mutex);
old = snd_ac97_read(ac97, reg) & mask;
if (old != bits) {
snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
snd_ac97_update_bits_nolock(ac97, reg, mask, bits);
/* update the internal spdif bits */
sbits = ac97->spdif_status;
if (sbits & IEC958_AES0_PROFESSIONAL) {
sbits &= ~IEC958_AES0_PRO_FS;
switch (rate) {
case 44100: sbits |= IEC958_AES0_PRO_FS_44100; break;
case 48000: sbits |= IEC958_AES0_PRO_FS_48000; break;
case 32000: sbits |= IEC958_AES0_PRO_FS_32000; break;
}
} else {
sbits &= ~(IEC958_AES3_CON_FS << 24);
switch (rate) {
case 44100: sbits |= IEC958_AES3_CON_FS_44100<<24; break;
case 48000: sbits |= IEC958_AES3_CON_FS_48000<<24; break;
case 32000: sbits |= IEC958_AES3_CON_FS_32000<<24; break;
}
}
ac97->spdif_status = sbits;
}
snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF);
mutex_unlock(&ac97->reg_mutex);
return 0;
}
/**
* snd_ac97_set_rate - change the rate of the given input/output.
* @ac97: the ac97 instance
* @reg: the register to change
* @rate: the sample rate to set
*
* Changes the rate of the given input/output on the codec.
* If the codec doesn't support VAR, the rate must be 48000 (except
* for SPDIF).
*
* The valid registers are AC97_PMC_MIC_ADC_RATE,
* AC97_PCM_FRONT_DAC_RATE, AC97_PCM_LR_ADC_RATE.
* AC97_PCM_SURR_DAC_RATE and AC97_PCM_LFE_DAC_RATE are accepted
* if the codec supports them.
* AC97_SPDIF is accepted as a pseudo register to modify the SPDIF
* status bits.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate)
{
int dbl;
unsigned int tmp;
dbl = rate > 48000;
if (dbl) {
if (!(ac97->flags & AC97_DOUBLE_RATE))
return -EINVAL;
if (reg != AC97_PCM_FRONT_DAC_RATE)
return -EINVAL;
}
snd_ac97_update_power(ac97, reg, 1);
switch (reg) {
case AC97_PCM_MIC_ADC_RATE:
if ((ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_VRM) == 0) /* MIC VRA */
if (rate != 48000)
return -EINVAL;
break;
case AC97_PCM_FRONT_DAC_RATE:
case AC97_PCM_LR_ADC_RATE:
if ((ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_VRA) == 0) /* VRA */
if (rate != 48000 && rate != 96000)
return -EINVAL;
break;
case AC97_PCM_SURR_DAC_RATE:
if (! (ac97->scaps & AC97_SCAP_SURROUND_DAC))
return -EINVAL;
break;
case AC97_PCM_LFE_DAC_RATE:
if (! (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC))
return -EINVAL;
break;
case AC97_SPDIF:
/* special case */
return set_spdif_rate(ac97, rate);
default:
return -EINVAL;
}
if (dbl)
rate /= 2;
tmp = (rate * ac97->bus->clock) / 48000;
if (tmp > 65535)
return -EINVAL;
if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE)
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
AC97_EA_DRA, dbl ? AC97_EA_DRA : 0);
snd_ac97_update(ac97, reg, tmp & 0xffff);
snd_ac97_read(ac97, reg);
if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE) {
/* Intel controllers require double rate data to be put in
* slots 7+8
*/
snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE,
AC97_GP_DRSS_MASK,
dbl ? AC97_GP_DRSS_78 : 0);
snd_ac97_read(ac97, AC97_GENERAL_PURPOSE);
}
return 0;
}
EXPORT_SYMBOL(snd_ac97_set_rate);
static unsigned short get_pslots(struct snd_ac97 *ac97, unsigned char *rate_table, unsigned short *spdif_slots)
{
if (!ac97_is_audio(ac97))
return 0;
if (ac97_is_rev22(ac97) || ac97_can_amap(ac97)) {
unsigned short slots = 0;
if (ac97_is_rev22(ac97)) {
/* Note: it's simply emulation of AMAP behaviour */
u16 es;
es = ac97->regs[AC97_EXTENDED_ID] &= ~AC97_EI_DACS_SLOT_MASK;
switch (ac97->addr) {
case 1:
case 2: es |= (1<<AC97_EI_DACS_SLOT_SHIFT); break;
case 3: es |= (2<<AC97_EI_DACS_SLOT_SHIFT); break;
}
snd_ac97_write_cache(ac97, AC97_EXTENDED_ID, es);
}
switch (ac97->addr) {
case 0:
slots |= (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
if (ac97->ext_id & AC97_EI_SPDIF) {
if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT)|(1<<AC97_SLOT_SPDIF_RIGHT);
else if (!(ac97->scaps & AC97_SCAP_CENTER_LFE_DAC))
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
else
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
}
*rate_table = 0;
break;
case 1:
case 2:
slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
if (ac97->ext_id & AC97_EI_SPDIF) {
if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
else
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
}
*rate_table = 1;
break;
case 3:
slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
if (ac97->ext_id & AC97_EI_SPDIF)
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
*rate_table = 2;
break;
}
return slots;
} else {
unsigned short slots;
slots = (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
if (ac97->ext_id & AC97_EI_SPDIF) {
if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT)|(1<<AC97_SLOT_SPDIF_RIGHT);
else if (!(ac97->scaps & AC97_SCAP_CENTER_LFE_DAC))
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
else
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
}
*rate_table = 0;
return slots;
}
}
static unsigned short get_cslots(struct snd_ac97 *ac97)
{
unsigned short slots;
if (!ac97_is_audio(ac97))
return 0;
slots = (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
slots |= (1<<AC97_SLOT_MIC);
return slots;
}
static unsigned int get_rates(struct ac97_pcm *pcm, unsigned int cidx, unsigned short slots, int dbl)
{
int i, idx;
unsigned int rates = ~0;
unsigned char reg;
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
continue;
reg = get_slot_reg(pcm, cidx, i, dbl);
switch (reg) {
case AC97_PCM_FRONT_DAC_RATE: idx = AC97_RATES_FRONT_DAC; break;
case AC97_PCM_SURR_DAC_RATE: idx = AC97_RATES_SURR_DAC; break;
case AC97_PCM_LFE_DAC_RATE: idx = AC97_RATES_LFE_DAC; break;
case AC97_PCM_LR_ADC_RATE: idx = AC97_RATES_ADC; break;
case AC97_PCM_MIC_ADC_RATE: idx = AC97_RATES_MIC_ADC; break;
default: idx = AC97_RATES_SPDIF; break;
}
rates &= pcm->r[dbl].codec[cidx]->rates[idx];
}
if (!dbl)
rates &= ~(SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000);
return rates;
}
/**
* snd_ac97_pcm_assign - assign AC97 slots to given PCM streams
* @bus: the ac97 bus instance
* @pcms_count: count of PCMs to be assigned
* @pcms: PCMs to be assigned
*
* It assigns available AC97 slots for given PCMs. If none or only
* some slots are available, pcm->xxx.slots and pcm->xxx.rslots[] members
* are reduced and might be zero.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_ac97_pcm_assign(struct snd_ac97_bus *bus,
unsigned short pcms_count,
const struct ac97_pcm *pcms)
{
int i, j, k;
const struct ac97_pcm *pcm;
struct ac97_pcm *rpcms, *rpcm;
unsigned short avail_slots[2][4];
unsigned char rate_table[2][4];
unsigned short tmp, slots;
unsigned short spdif_slots[4];
unsigned int rates;
struct snd_ac97 *codec;
rpcms = kcalloc(pcms_count, sizeof(struct ac97_pcm), GFP_KERNEL);
if (rpcms == NULL)
return -ENOMEM;
memset(avail_slots, 0, sizeof(avail_slots));
memset(rate_table, 0, sizeof(rate_table));
memset(spdif_slots, 0, sizeof(spdif_slots));
for (i = 0; i < 4; i++) {
codec = bus->codec[i];
if (!codec)
continue;
avail_slots[0][i] = get_pslots(codec, &rate_table[0][i], &spdif_slots[i]);
avail_slots[1][i] = get_cslots(codec);
if (!(codec->scaps & AC97_SCAP_INDEP_SDIN)) {
for (j = 0; j < i; j++) {
if (bus->codec[j])
avail_slots[1][i] &= ~avail_slots[1][j];
}
}
}
/* first step - exclusive devices */
for (i = 0; i < pcms_count; i++) {
pcm = &pcms[i];
rpcm = &rpcms[i];
/* low-level driver thinks that it's more clever */
if (pcm->copy_flag) {
*rpcm = *pcm;
continue;
}
rpcm->stream = pcm->stream;
rpcm->exclusive = pcm->exclusive;
rpcm->spdif = pcm->spdif;
rpcm->private_value = pcm->private_value;
rpcm->bus = bus;
rpcm->rates = ~0;
slots = pcm->r[0].slots;
for (j = 0; j < 4 && slots; j++) {
if (!bus->codec[j])
continue;
rates = ~0;
if (pcm->spdif && pcm->stream == 0)
tmp = spdif_slots[j];
else
tmp = avail_slots[pcm->stream][j];
if (pcm->exclusive) {
/* exclusive access */
tmp &= slots;
for (k = 0; k < i; k++) {
if (rpcm->stream == rpcms[k].stream)
tmp &= ~rpcms[k].r[0].rslots[j];
}
} else {
/* non-exclusive access */
tmp &= pcm->r[0].slots;
}
if (tmp) {
rpcm->r[0].rslots[j] = tmp;
rpcm->r[0].codec[j] = bus->codec[j];
rpcm->r[0].rate_table[j] = rate_table[pcm->stream][j];
if (bus->no_vra)
rates = SNDRV_PCM_RATE_48000;
else
rates = get_rates(rpcm, j, tmp, 0);
if (pcm->exclusive)
avail_slots[pcm->stream][j] &= ~tmp;
}
slots &= ~tmp;
rpcm->r[0].slots |= tmp;
rpcm->rates &= rates;
}
/* for double rate, we check the first codec only */
if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK &&
bus->codec[0] && (bus->codec[0]->flags & AC97_DOUBLE_RATE) &&
rate_table[pcm->stream][0] == 0) {
tmp = (1<<AC97_SLOT_PCM_LEFT) | (1<<AC97_SLOT_PCM_RIGHT) |
(1<<AC97_SLOT_PCM_LEFT_0) | (1<<AC97_SLOT_PCM_RIGHT_0);
if ((tmp & pcm->r[1].slots) == tmp) {
rpcm->r[1].slots = tmp;
rpcm->r[1].rslots[0] = tmp;
rpcm->r[1].rate_table[0] = 0;
rpcm->r[1].codec[0] = bus->codec[0];
if (pcm->exclusive)
avail_slots[pcm->stream][0] &= ~tmp;
if (bus->no_vra)
rates = SNDRV_PCM_RATE_96000;
else
rates = get_rates(rpcm, 0, tmp, 1);
rpcm->rates |= rates;
}
}
if (rpcm->rates == ~0)
rpcm->rates = 0; /* not used */
}
bus->pcms_count = pcms_count;
bus->pcms = rpcms;
return 0;
}
EXPORT_SYMBOL(snd_ac97_pcm_assign);
/**
* snd_ac97_pcm_open - opens the given AC97 pcm
* @pcm: the ac97 pcm instance
* @rate: rate in Hz, if codec does not support VRA, this value must be 48000Hz
* @cfg: output stream characteristics
* @slots: a subset of allocated slots (snd_ac97_pcm_assign) for this pcm
*
* It locks the specified slots and sets the given rate to AC97 registers.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_ac97_pcm_open(struct ac97_pcm *pcm, unsigned int rate,
enum ac97_pcm_cfg cfg, unsigned short slots)
{
struct snd_ac97_bus *bus;
int i, cidx, r, ok_flag;
unsigned int reg_ok[4] = {0,0,0,0};
unsigned char reg;
int err = 0;
r = rate > 48000;
bus = pcm->bus;
if (cfg == AC97_PCM_CFG_SPDIF) {
for (cidx = 0; cidx < 4; cidx++)
if (bus->codec[cidx] && (bus->codec[cidx]->ext_id & AC97_EI_SPDIF)) {
err = set_spdif_rate(bus->codec[cidx], rate);
if (err < 0)
return err;
}
}
spin_lock_irq(&pcm->bus->bus_lock);
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
continue;
ok_flag = 0;
for (cidx = 0; cidx < 4; cidx++) {
if (bus->used_slots[pcm->stream][cidx] & (1 << i)) {
spin_unlock_irq(&pcm->bus->bus_lock);
err = -EBUSY;
goto error;
}
if (pcm->r[r].rslots[cidx] & (1 << i)) {
bus->used_slots[pcm->stream][cidx] |= (1 << i);
ok_flag++;
}
}
if (!ok_flag) {
spin_unlock_irq(&pcm->bus->bus_lock);
dev_err(bus->card->dev,
"cannot find configuration for AC97 slot %i\n",
i);
err = -EAGAIN;
goto error;
}
}
pcm->cur_dbl = r;
spin_unlock_irq(&pcm->bus->bus_lock);
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
continue;
for (cidx = 0; cidx < 4; cidx++) {
if (pcm->r[r].rslots[cidx] & (1 << i)) {
reg = get_slot_reg(pcm, cidx, i, r);
if (reg == 0xff) {
dev_err(bus->card->dev,
"invalid AC97 slot %i?\n", i);
continue;
}
if (reg_ok[cidx] & (1 << (reg - AC97_PCM_FRONT_DAC_RATE)))
continue;
dev_dbg(bus->card->dev,
"setting ac97 reg 0x%x to rate %d\n",
reg, rate);
err = snd_ac97_set_rate(pcm->r[r].codec[cidx], reg, rate);
if (err < 0)
dev_err(bus->card->dev,
"error in snd_ac97_set_rate: cidx=%d, reg=0x%x, rate=%d, err=%d\n",
cidx, reg, rate, err);
else
reg_ok[cidx] |= (1 << (reg - AC97_PCM_FRONT_DAC_RATE));
}
}
}
pcm->aslots = slots;
return 0;
error:
pcm->aslots = slots;
snd_ac97_pcm_close(pcm);
return err;
}
EXPORT_SYMBOL(snd_ac97_pcm_open);
/**
* snd_ac97_pcm_close - closes the given AC97 pcm
* @pcm: the ac97 pcm instance
*
* It frees the locked AC97 slots.
*
* Return: Zero.
*/
int snd_ac97_pcm_close(struct ac97_pcm *pcm)
{
struct snd_ac97_bus *bus;
unsigned short slots = pcm->aslots;
int i, cidx;
#ifdef CONFIG_SND_AC97_POWER_SAVE
int r = pcm->cur_dbl;
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
continue;
for (cidx = 0; cidx < 4; cidx++) {
if (pcm->r[r].rslots[cidx] & (1 << i)) {
int reg = get_slot_reg(pcm, cidx, i, r);
snd_ac97_update_power(pcm->r[r].codec[cidx],
reg, 0);
}
}
}
#endif
bus = pcm->bus;
spin_lock_irq(&pcm->bus->bus_lock);
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
continue;
for (cidx = 0; cidx < 4; cidx++)
bus->used_slots[pcm->stream][cidx] &= ~(1 << i);
}
pcm->aslots = 0;
pcm->cur_dbl = 0;
spin_unlock_irq(&pcm->bus->bus_lock);
return 0;
}
EXPORT_SYMBOL(snd_ac97_pcm_close);
static int double_rate_hw_constraint_rate(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
if (channels->min > 2) {
static const struct snd_interval single_rates = {
.min = 1,
.max = 48000,
};
struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
return snd_interval_refine(rate, &single_rates);
}
return 0;
}
static int double_rate_hw_constraint_channels(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
if (rate->min > 48000) {
static const struct snd_interval double_rate_channels = {
.min = 2,
.max = 2,
};
struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
return snd_interval_refine(channels, &double_rate_channels);
}
return 0;
}
/**
* snd_ac97_pcm_double_rate_rules - set double rate constraints
* @runtime: the runtime of the ac97 front playback pcm
*
* Installs the hardware constraint rules to prevent using double rates and
* more than two channels at the same time.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_ac97_pcm_double_rate_rules(struct snd_pcm_runtime *runtime)
{
int err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
double_rate_hw_constraint_rate, NULL,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
double_rate_hw_constraint_channels, NULL,
SNDRV_PCM_HW_PARAM_RATE, -1);
return err;
}
EXPORT_SYMBOL(snd_ac97_pcm_double_rate_rules);

490
sound/pci/ac97/ac97_proc.c Normal file
View file

@ -0,0 +1,490 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Universal interface for Audio Codec '97
*
* For more details look to AC '97 component specification revision 2.2
* by Intel Corporation (http://developer.intel.com).
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/mutex.h>
#include <sound/core.h>
#include <sound/ac97_codec.h>
#include <sound/asoundef.h>
#include "ac97_local.h"
#include "ac97_id.h"
/*
* proc interface
*/
static void snd_ac97_proc_read_functions(struct snd_ac97 *ac97, struct snd_info_buffer *buffer)
{
int header = 0, function;
unsigned short info, sense_info;
static const char *function_names[12] = {
"Master Out", "AUX Out", "Center/LFE Out", "SPDIF Out",
"Phone In", "Mic 1", "Mic 2", "Line In", "CD In", "Video In",
"Aux In", "Mono Out"
};
static const char *locations[8] = {
"Rear I/O Panel", "Front Panel", "Motherboard", "Dock/External",
"reserved", "reserved", "reserved", "NC/unused"
};
for (function = 0; function < 12; ++function) {
snd_ac97_write(ac97, AC97_FUNC_SELECT, function << 1);
info = snd_ac97_read(ac97, AC97_FUNC_INFO);
if (!(info & 0x0001))
continue;
if (!header) {
snd_iprintf(buffer, "\n Gain Inverted Buffer delay Location\n");
header = 1;
}
sense_info = snd_ac97_read(ac97, AC97_SENSE_INFO);
snd_iprintf(buffer, "%-17s: %3d.%d dBV %c %2d/fs %s\n",
function_names[function],
(info & 0x8000 ? -1 : 1) * ((info & 0x7000) >> 12) * 3 / 2,
((info & 0x0800) >> 11) * 5,
info & 0x0400 ? 'X' : '-',
(info & 0x03e0) >> 5,
locations[sense_info >> 13]);
}
}
static const char *snd_ac97_stereo_enhancements[] =
{
/* 0 */ "No 3D Stereo Enhancement",
/* 1 */ "Analog Devices Phat Stereo",
/* 2 */ "Creative Stereo Enhancement",
/* 3 */ "National Semi 3D Stereo Enhancement",
/* 4 */ "YAMAHA Ymersion",
/* 5 */ "BBE 3D Stereo Enhancement",
/* 6 */ "Crystal Semi 3D Stereo Enhancement",
/* 7 */ "Qsound QXpander",
/* 8 */ "Spatializer 3D Stereo Enhancement",
/* 9 */ "SRS 3D Stereo Enhancement",
/* 10 */ "Platform Tech 3D Stereo Enhancement",
/* 11 */ "AKM 3D Audio",
/* 12 */ "Aureal Stereo Enhancement",
/* 13 */ "Aztech 3D Enhancement",
/* 14 */ "Binaura 3D Audio Enhancement",
/* 15 */ "ESS Technology Stereo Enhancement",
/* 16 */ "Harman International VMAx",
/* 17 */ "Nvidea/IC Ensemble/KS Waves 3D Stereo Enhancement",
/* 18 */ "Philips Incredible Sound",
/* 19 */ "Texas Instruments 3D Stereo Enhancement",
/* 20 */ "VLSI Technology 3D Stereo Enhancement",
/* 21 */ "TriTech 3D Stereo Enhancement",
/* 22 */ "Realtek 3D Stereo Enhancement",
/* 23 */ "Samsung 3D Stereo Enhancement",
/* 24 */ "Wolfson Microelectronics 3D Enhancement",
/* 25 */ "Delta Integration 3D Enhancement",
/* 26 */ "SigmaTel 3D Enhancement",
/* 27 */ "IC Ensemble/KS Waves",
/* 28 */ "Rockwell 3D Stereo Enhancement",
/* 29 */ "Reserved 29",
/* 30 */ "Reserved 30",
/* 31 */ "Reserved 31"
};
static void snd_ac97_proc_read_main(struct snd_ac97 *ac97, struct snd_info_buffer *buffer, int subidx)
{
char name[64];
unsigned short val, tmp, ext, mext;
static const char *spdif_slots[4] = { " SPDIF=3/4", " SPDIF=7/8", " SPDIF=6/9", " SPDIF=10/11" };
static const char *spdif_rates[4] = { " Rate=44.1kHz", " Rate=res", " Rate=48kHz", " Rate=32kHz" };
static const char *spdif_rates_cs4205[4] = { " Rate=48kHz", " Rate=44.1kHz", " Rate=res", " Rate=res" };
static const char *double_rate_slots[4] = { "10/11", "7/8", "reserved", "reserved" };
snd_ac97_get_name(NULL, ac97->id, name, 0);
snd_iprintf(buffer, "%d-%d/%d: %s\n\n", ac97->addr, ac97->num, subidx, name);
if ((ac97->scaps & AC97_SCAP_AUDIO) == 0)
goto __modem;
snd_iprintf(buffer, "PCI Subsys Vendor: 0x%04x\n",
ac97->subsystem_vendor);
snd_iprintf(buffer, "PCI Subsys Device: 0x%04x\n\n",
ac97->subsystem_device);
snd_iprintf(buffer, "Flags: %x\n", ac97->flags);
if ((ac97->ext_id & AC97_EI_REV_MASK) >= AC97_EI_REV_23) {
val = snd_ac97_read(ac97, AC97_INT_PAGING);
snd_ac97_update_bits(ac97, AC97_INT_PAGING,
AC97_PAGE_MASK, AC97_PAGE_1);
tmp = snd_ac97_read(ac97, AC97_CODEC_CLASS_REV);
snd_iprintf(buffer, "Revision : 0x%02x\n", tmp & 0xff);
snd_iprintf(buffer, "Compat. Class : 0x%02x\n", (tmp >> 8) & 0x1f);
snd_iprintf(buffer, "Subsys. Vendor ID: 0x%04x\n",
snd_ac97_read(ac97, AC97_PCI_SVID));
snd_iprintf(buffer, "Subsys. ID : 0x%04x\n\n",
snd_ac97_read(ac97, AC97_PCI_SID));
snd_ac97_update_bits(ac97, AC97_INT_PAGING,
AC97_PAGE_MASK, val & AC97_PAGE_MASK);
}
// val = snd_ac97_read(ac97, AC97_RESET);
val = ac97->caps;
snd_iprintf(buffer, "Capabilities :%s%s%s%s%s%s\n",
val & AC97_BC_DEDICATED_MIC ? " -dedicated MIC PCM IN channel-" : "",
val & AC97_BC_RESERVED1 ? " -reserved1-" : "",
val & AC97_BC_BASS_TREBLE ? " -bass & treble-" : "",
val & AC97_BC_SIM_STEREO ? " -simulated stereo-" : "",
val & AC97_BC_HEADPHONE ? " -headphone out-" : "",
val & AC97_BC_LOUDNESS ? " -loudness-" : "");
tmp = ac97->caps & AC97_BC_DAC_MASK;
snd_iprintf(buffer, "DAC resolution : %s%s%s%s\n",
tmp == AC97_BC_16BIT_DAC ? "16-bit" : "",
tmp == AC97_BC_18BIT_DAC ? "18-bit" : "",
tmp == AC97_BC_20BIT_DAC ? "20-bit" : "",
tmp == AC97_BC_DAC_MASK ? "???" : "");
tmp = ac97->caps & AC97_BC_ADC_MASK;
snd_iprintf(buffer, "ADC resolution : %s%s%s%s\n",
tmp == AC97_BC_16BIT_ADC ? "16-bit" : "",
tmp == AC97_BC_18BIT_ADC ? "18-bit" : "",
tmp == AC97_BC_20BIT_ADC ? "20-bit" : "",
tmp == AC97_BC_ADC_MASK ? "???" : "");
snd_iprintf(buffer, "3D enhancement : %s\n",
snd_ac97_stereo_enhancements[(val >> 10) & 0x1f]);
snd_iprintf(buffer, "\nCurrent setup\n");
val = snd_ac97_read(ac97, AC97_MIC);
snd_iprintf(buffer, "Mic gain : %s [%s]\n", val & 0x0040 ? "+20dB" : "+0dB", ac97->regs[AC97_MIC] & 0x0040 ? "+20dB" : "+0dB");
val = snd_ac97_read(ac97, AC97_GENERAL_PURPOSE);
snd_iprintf(buffer, "POP path : %s 3D\n"
"Sim. stereo : %s\n"
"3D enhancement : %s\n"
"Loudness : %s\n"
"Mono output : %s\n"
"Mic select : %s\n"
"ADC/DAC loopback : %s\n",
val & 0x8000 ? "post" : "pre",
val & 0x4000 ? "on" : "off",
val & 0x2000 ? "on" : "off",
val & 0x1000 ? "on" : "off",
val & 0x0200 ? "Mic" : "MIX",
val & 0x0100 ? "Mic2" : "Mic1",
val & 0x0080 ? "on" : "off");
if (ac97->ext_id & AC97_EI_DRA)
snd_iprintf(buffer, "Double rate slots: %s\n",
double_rate_slots[(val >> 10) & 3]);
ext = snd_ac97_read(ac97, AC97_EXTENDED_ID);
if (ext == 0)
goto __modem;
snd_iprintf(buffer, "Extended ID : codec=%i rev=%i%s%s%s%s DSA=%i%s%s%s%s\n",
(ext & AC97_EI_ADDR_MASK) >> AC97_EI_ADDR_SHIFT,
(ext & AC97_EI_REV_MASK) >> AC97_EI_REV_SHIFT,
ext & AC97_EI_AMAP ? " AMAP" : "",
ext & AC97_EI_LDAC ? " LDAC" : "",
ext & AC97_EI_SDAC ? " SDAC" : "",
ext & AC97_EI_CDAC ? " CDAC" : "",
(ext & AC97_EI_DACS_SLOT_MASK) >> AC97_EI_DACS_SLOT_SHIFT,
ext & AC97_EI_VRM ? " VRM" : "",
ext & AC97_EI_SPDIF ? " SPDIF" : "",
ext & AC97_EI_DRA ? " DRA" : "",
ext & AC97_EI_VRA ? " VRA" : "");
val = snd_ac97_read(ac97, AC97_EXTENDED_STATUS);
snd_iprintf(buffer, "Extended status :%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
val & AC97_EA_PRL ? " PRL" : "",
val & AC97_EA_PRK ? " PRK" : "",
val & AC97_EA_PRJ ? " PRJ" : "",
val & AC97_EA_PRI ? " PRI" : "",
val & AC97_EA_SPCV ? " SPCV" : "",
val & AC97_EA_MDAC ? " MADC" : "",
val & AC97_EA_LDAC ? " LDAC" : "",
val & AC97_EA_SDAC ? " SDAC" : "",
val & AC97_EA_CDAC ? " CDAC" : "",
ext & AC97_EI_SPDIF ? spdif_slots[(val & AC97_EA_SPSA_SLOT_MASK) >> AC97_EA_SPSA_SLOT_SHIFT] : "",
val & AC97_EA_VRM ? " VRM" : "",
val & AC97_EA_SPDIF ? " SPDIF" : "",
val & AC97_EA_DRA ? " DRA" : "",
val & AC97_EA_VRA ? " VRA" : "");
if (ext & AC97_EI_VRA) { /* VRA */
val = snd_ac97_read(ac97, AC97_PCM_FRONT_DAC_RATE);
snd_iprintf(buffer, "PCM front DAC : %iHz\n", val);
if (ext & AC97_EI_SDAC) {
val = snd_ac97_read(ac97, AC97_PCM_SURR_DAC_RATE);
snd_iprintf(buffer, "PCM Surr DAC : %iHz\n", val);
}
if (ext & AC97_EI_LDAC) {
val = snd_ac97_read(ac97, AC97_PCM_LFE_DAC_RATE);
snd_iprintf(buffer, "PCM LFE DAC : %iHz\n", val);
}
val = snd_ac97_read(ac97, AC97_PCM_LR_ADC_RATE);
snd_iprintf(buffer, "PCM ADC : %iHz\n", val);
}
if (ext & AC97_EI_VRM) {
val = snd_ac97_read(ac97, AC97_PCM_MIC_ADC_RATE);
snd_iprintf(buffer, "PCM MIC ADC : %iHz\n", val);
}
if ((ext & AC97_EI_SPDIF) || (ac97->flags & AC97_CS_SPDIF) ||
(ac97->id == AC97_ID_YMF743)) {
if (ac97->flags & AC97_CS_SPDIF)
val = snd_ac97_read(ac97, AC97_CSR_SPDIF);
else if (ac97->id == AC97_ID_YMF743) {
val = snd_ac97_read(ac97, AC97_YMF7X3_DIT_CTRL);
val = 0x2000 | (val & 0xff00) >> 4 | (val & 0x38) >> 2;
} else
val = snd_ac97_read(ac97, AC97_SPDIF);
snd_iprintf(buffer, "SPDIF Control :%s%s%s%s Category=0x%x Generation=%i%s%s%s\n",
val & AC97_SC_PRO ? " PRO" : " Consumer",
val & AC97_SC_NAUDIO ? " Non-audio" : " PCM",
val & AC97_SC_COPY ? "" : " Copyright",
val & AC97_SC_PRE ? " Preemph50/15" : "",
(val & AC97_SC_CC_MASK) >> AC97_SC_CC_SHIFT,
(val & AC97_SC_L) >> 11,
(ac97->flags & AC97_CS_SPDIF) ?
spdif_rates_cs4205[(val & AC97_SC_SPSR_MASK) >> AC97_SC_SPSR_SHIFT] :
spdif_rates[(val & AC97_SC_SPSR_MASK) >> AC97_SC_SPSR_SHIFT],
(ac97->flags & AC97_CS_SPDIF) ?
(val & AC97_SC_DRS ? " Validity" : "") :
(val & AC97_SC_DRS ? " DRS" : ""),
(ac97->flags & AC97_CS_SPDIF) ?
(val & AC97_SC_V ? " Enabled" : "") :
(val & AC97_SC_V ? " Validity" : ""));
/* ALC650 specific*/
if ((ac97->id & 0xfffffff0) == 0x414c4720 &&
(snd_ac97_read(ac97, AC97_ALC650_CLOCK) & 0x01)) {
val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS2);
if (val & AC97_ALC650_CLOCK_LOCK) {
val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS1);
snd_iprintf(buffer, "SPDIF In Status :%s%s%s%s Category=0x%x Generation=%i",
val & AC97_ALC650_PRO ? " PRO" : " Consumer",
val & AC97_ALC650_NAUDIO ? " Non-audio" : " PCM",
val & AC97_ALC650_COPY ? "" : " Copyright",
val & AC97_ALC650_PRE ? " Preemph50/15" : "",
(val & AC97_ALC650_CC_MASK) >> AC97_ALC650_CC_SHIFT,
(val & AC97_ALC650_L) >> 15);
val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS2);
snd_iprintf(buffer, "%s Accuracy=%i%s%s\n",
spdif_rates[(val & AC97_ALC650_SPSR_MASK) >> AC97_ALC650_SPSR_SHIFT],
(val & AC97_ALC650_CLOCK_ACCURACY) >> AC97_ALC650_CLOCK_SHIFT,
(val & AC97_ALC650_CLOCK_LOCK ? " Locked" : " Unlocked"),
(val & AC97_ALC650_V ? " Validity?" : ""));
} else {
snd_iprintf(buffer, "SPDIF In Status : Not Locked\n");
}
}
}
if ((ac97->ext_id & AC97_EI_REV_MASK) >= AC97_EI_REV_23) {
val = snd_ac97_read(ac97, AC97_INT_PAGING);
snd_ac97_update_bits(ac97, AC97_INT_PAGING,
AC97_PAGE_MASK, AC97_PAGE_1);
snd_ac97_proc_read_functions(ac97, buffer);
snd_ac97_update_bits(ac97, AC97_INT_PAGING,
AC97_PAGE_MASK, val & AC97_PAGE_MASK);
}
__modem:
mext = snd_ac97_read(ac97, AC97_EXTENDED_MID);
if (mext == 0)
return;
snd_iprintf(buffer, "Extended modem ID: codec=%i%s%s%s%s%s\n",
(mext & AC97_MEI_ADDR_MASK) >> AC97_MEI_ADDR_SHIFT,
mext & AC97_MEI_CID2 ? " CID2" : "",
mext & AC97_MEI_CID1 ? " CID1" : "",
mext & AC97_MEI_HANDSET ? " HSET" : "",
mext & AC97_MEI_LINE2 ? " LIN2" : "",
mext & AC97_MEI_LINE1 ? " LIN1" : "");
val = snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS);
snd_iprintf(buffer, "Modem status :%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
val & AC97_MEA_GPIO ? " GPIO" : "",
val & AC97_MEA_MREF ? " MREF" : "",
val & AC97_MEA_ADC1 ? " ADC1" : "",
val & AC97_MEA_DAC1 ? " DAC1" : "",
val & AC97_MEA_ADC2 ? " ADC2" : "",
val & AC97_MEA_DAC2 ? " DAC2" : "",
val & AC97_MEA_HADC ? " HADC" : "",
val & AC97_MEA_HDAC ? " HDAC" : "",
val & AC97_MEA_PRA ? " PRA(GPIO)" : "",
val & AC97_MEA_PRB ? " PRB(res)" : "",
val & AC97_MEA_PRC ? " PRC(ADC1)" : "",
val & AC97_MEA_PRD ? " PRD(DAC1)" : "",
val & AC97_MEA_PRE ? " PRE(ADC2)" : "",
val & AC97_MEA_PRF ? " PRF(DAC2)" : "",
val & AC97_MEA_PRG ? " PRG(HADC)" : "",
val & AC97_MEA_PRH ? " PRH(HDAC)" : "");
if (mext & AC97_MEI_LINE1) {
val = snd_ac97_read(ac97, AC97_LINE1_RATE);
snd_iprintf(buffer, "Line1 rate : %iHz\n", val);
}
if (mext & AC97_MEI_LINE2) {
val = snd_ac97_read(ac97, AC97_LINE2_RATE);
snd_iprintf(buffer, "Line2 rate : %iHz\n", val);
}
if (mext & AC97_MEI_HANDSET) {
val = snd_ac97_read(ac97, AC97_HANDSET_RATE);
snd_iprintf(buffer, "Headset rate : %iHz\n", val);
}
}
static void snd_ac97_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
struct snd_ac97 *ac97 = entry->private_data;
mutex_lock(&ac97->page_mutex);
if ((ac97->id & 0xffffff40) == AC97_ID_AD1881) { // Analog Devices AD1881/85/86
int idx;
for (idx = 0; idx < 3; idx++)
if (ac97->spec.ad18xx.id[idx]) {
/* select single codec */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
ac97->spec.ad18xx.unchained[idx] | ac97->spec.ad18xx.chained[idx]);
snd_ac97_proc_read_main(ac97, buffer, idx);
snd_iprintf(buffer, "\n\n");
}
/* select all codecs */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
snd_iprintf(buffer, "\nAD18XX configuration\n");
snd_iprintf(buffer, "Unchained : 0x%04x,0x%04x,0x%04x\n",
ac97->spec.ad18xx.unchained[0],
ac97->spec.ad18xx.unchained[1],
ac97->spec.ad18xx.unchained[2]);
snd_iprintf(buffer, "Chained : 0x%04x,0x%04x,0x%04x\n",
ac97->spec.ad18xx.chained[0],
ac97->spec.ad18xx.chained[1],
ac97->spec.ad18xx.chained[2]);
} else {
snd_ac97_proc_read_main(ac97, buffer, 0);
}
mutex_unlock(&ac97->page_mutex);
}
#ifdef CONFIG_SND_DEBUG
/* direct register write for debugging */
static void snd_ac97_proc_regs_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
struct snd_ac97 *ac97 = entry->private_data;
char line[64];
unsigned int reg, val;
mutex_lock(&ac97->page_mutex);
while (!snd_info_get_line(buffer, line, sizeof(line))) {
if (sscanf(line, "%x %x", &reg, &val) != 2)
continue;
/* register must be even */
if (reg < 0x80 && (reg & 1) == 0 && val <= 0xffff)
snd_ac97_write_cache(ac97, reg, val);
}
mutex_unlock(&ac97->page_mutex);
}
#endif
static void snd_ac97_proc_regs_read_main(struct snd_ac97 *ac97, struct snd_info_buffer *buffer, int subidx)
{
int reg, val;
for (reg = 0; reg < 0x80; reg += 2) {
val = snd_ac97_read(ac97, reg);
snd_iprintf(buffer, "%i:%02x = %04x\n", subidx, reg, val);
}
}
static void snd_ac97_proc_regs_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ac97 *ac97 = entry->private_data;
mutex_lock(&ac97->page_mutex);
if ((ac97->id & 0xffffff40) == AC97_ID_AD1881) { // Analog Devices AD1881/85/86
int idx;
for (idx = 0; idx < 3; idx++)
if (ac97->spec.ad18xx.id[idx]) {
/* select single codec */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
ac97->spec.ad18xx.unchained[idx] | ac97->spec.ad18xx.chained[idx]);
snd_ac97_proc_regs_read_main(ac97, buffer, idx);
}
/* select all codecs */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
} else {
snd_ac97_proc_regs_read_main(ac97, buffer, 0);
}
mutex_unlock(&ac97->page_mutex);
}
void snd_ac97_proc_init(struct snd_ac97 * ac97)
{
struct snd_info_entry *entry;
char name[32];
const char *prefix;
if (ac97->bus->proc == NULL)
return;
prefix = ac97_is_audio(ac97) ? "ac97" : "mc97";
sprintf(name, "%s#%d-%d", prefix, ac97->addr, ac97->num);
if ((entry = snd_info_create_card_entry(ac97->bus->card, name, ac97->bus->proc)) != NULL) {
snd_info_set_text_ops(entry, ac97, snd_ac97_proc_read);
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
ac97->proc = entry;
sprintf(name, "%s#%d-%d+regs", prefix, ac97->addr, ac97->num);
if ((entry = snd_info_create_card_entry(ac97->bus->card, name, ac97->bus->proc)) != NULL) {
snd_info_set_text_ops(entry, ac97, snd_ac97_proc_regs_read);
#ifdef CONFIG_SND_DEBUG
entry->mode |= S_IWUSR;
entry->c.text.write = snd_ac97_proc_regs_write;
#endif
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
ac97->proc_regs = entry;
}
void snd_ac97_proc_done(struct snd_ac97 * ac97)
{
snd_info_free_entry(ac97->proc_regs);
ac97->proc_regs = NULL;
snd_info_free_entry(ac97->proc);
ac97->proc = NULL;
}
void snd_ac97_bus_proc_init(struct snd_ac97_bus * bus)
{
struct snd_info_entry *entry;
char name[32];
sprintf(name, "codec97#%d", bus->num);
if ((entry = snd_info_create_card_entry(bus->card, name, bus->card->proc_root)) != NULL) {
entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
bus->proc = entry;
}
void snd_ac97_bus_proc_done(struct snd_ac97_bus * bus)
{
snd_info_free_entry(bus->proc);
bus->proc = NULL;
}

1061
sound/pci/ad1889.c Normal file

File diff suppressed because it is too large Load diff

189
sound/pci/ad1889.h Normal file
View file

@ -0,0 +1,189 @@
/* Analog Devices 1889 audio driver
* Copyright (C) 2004, Kyle McMartin <kyle@parisc-linux.org>
*/
#ifndef __AD1889_H__
#define __AD1889_H__
#define AD_DS_WSMC 0x00 /* wave/synthesis channel mixer control */
#define AD_DS_WSMC_SYEN 0x0004 /* synthesis channel enable */
#define AD_DS_WSMC_SYRQ 0x0030 /* synth. fifo request point */
#define AD_DS_WSMC_WA16 0x0100 /* wave channel 16bit select */
#define AD_DS_WSMC_WAST 0x0200 /* wave channel stereo select */
#define AD_DS_WSMC_WAEN 0x0400 /* wave channel enable */
#define AD_DS_WSMC_WARQ 0x3000 /* wave fifo request point */
#define AD_DS_RAMC 0x02 /* resampler/ADC channel mixer control */
#define AD_DS_RAMC_AD16 0x0001 /* ADC channel 16bit select */
#define AD_DS_RAMC_ADST 0x0002 /* ADC channel stereo select */
#define AD_DS_RAMC_ADEN 0x0004 /* ADC channel enable */
#define AD_DS_RAMC_ACRQ 0x0030 /* ADC fifo request point */
#define AD_DS_RAMC_REEN 0x0400 /* resampler channel enable */
#define AD_DS_RAMC_RERQ 0x3000 /* res. fifo request point */
#define AD_DS_WADA 0x04 /* wave channel mix attenuation */
#define AD_DS_WADA_RWAM 0x0080 /* right wave mute */
#define AD_DS_WADA_RWAA 0x001f /* right wave attenuation */
#define AD_DS_WADA_LWAM 0x8000 /* left wave mute */
#define AD_DS_WADA_LWAA 0x3e00 /* left wave attenuation */
#define AD_DS_SYDA 0x06 /* synthesis channel mix attenuation */
#define AD_DS_SYDA_RSYM 0x0080 /* right synthesis mute */
#define AD_DS_SYDA_RSYA 0x001f /* right synthesis attenuation */
#define AD_DS_SYDA_LSYM 0x8000 /* left synthesis mute */
#define AD_DS_SYDA_LSYA 0x3e00 /* left synthesis attenuation */
#define AD_DS_WAS 0x08 /* wave channel sample rate */
#define AD_DS_WAS_WAS 0xffff /* sample rate mask */
#define AD_DS_RES 0x0a /* resampler channel sample rate */
#define AD_DS_RES_RES 0xffff /* sample rate mask */
#define AD_DS_CCS 0x0c /* chip control/status */
#define AD_DS_CCS_ADO 0x0001 /* ADC channel overflow */
#define AD_DS_CCS_REO 0x0002 /* resampler channel overflow */
#define AD_DS_CCS_SYU 0x0004 /* synthesis channel underflow */
#define AD_DS_CCS_WAU 0x0008 /* wave channel underflow */
/* bits 4 -> 7, 9, 11 -> 14 reserved */
#define AD_DS_CCS_XTD 0x0100 /* xtd delay control (4096 clock cycles) */
#define AD_DS_CCS_PDALL 0x0400 /* power */
#define AD_DS_CCS_CLKEN 0x8000 /* clock */
#define AD_DMA_RESBA 0x40 /* RES base address */
#define AD_DMA_RESCA 0x44 /* RES current address */
#define AD_DMA_RESBC 0x48 /* RES base count */
#define AD_DMA_RESCC 0x4c /* RES current count */
#define AD_DMA_ADCBA 0x50 /* ADC base address */
#define AD_DMA_ADCCA 0x54 /* ADC current address */
#define AD_DMA_ADCBC 0x58 /* ADC base count */
#define AD_DMA_ADCCC 0x5c /* ADC current count */
#define AD_DMA_SYNBA 0x60 /* synth base address */
#define AD_DMA_SYNCA 0x64 /* synth current address */
#define AD_DMA_SYNBC 0x68 /* synth base count */
#define AD_DMA_SYNCC 0x6c /* synth current count */
#define AD_DMA_WAVBA 0x70 /* wave base address */
#define AD_DMA_WAVCA 0x74 /* wave current address */
#define AD_DMA_WAVBC 0x78 /* wave base count */
#define AD_DMA_WAVCC 0x7c /* wave current count */
#define AD_DMA_RESIC 0x80 /* RES dma interrupt current byte count */
#define AD_DMA_RESIB 0x84 /* RES dma interrupt base byte count */
#define AD_DMA_ADCIC 0x88 /* ADC dma interrupt current byte count */
#define AD_DMA_ADCIB 0x8c /* ADC dma interrupt base byte count */
#define AD_DMA_SYNIC 0x90 /* synth dma interrupt current byte count */
#define AD_DMA_SYNIB 0x94 /* synth dma interrupt base byte count */
#define AD_DMA_WAVIC 0x98 /* wave dma interrupt current byte count */
#define AD_DMA_WAVIB 0x9c /* wave dma interrupt base byte count */
#define AD_DMA_ICC 0xffffff /* current byte count mask */
#define AD_DMA_IBC 0xffffff /* base byte count mask */
/* bits 24 -> 31 reserved */
/* 4 bytes pad */
#define AD_DMA_ADC 0xa8 /* ADC dma control and status */
#define AD_DMA_SYNTH 0xb0 /* Synth dma control and status */
#define AD_DMA_WAV 0xb8 /* wave dma control and status */
#define AD_DMA_RES 0xa0 /* Resample dma control and status */
#define AD_DMA_SGDE 0x0001 /* SGD mode enable */
#define AD_DMA_LOOP 0x0002 /* loop enable */
#define AD_DMA_IM 0x000c /* interrupt mode mask */
#define AD_DMA_IM_DIS (~AD_DMA_IM) /* disable */
#define AD_DMA_IM_CNT 0x0004 /* interrupt on count */
#define AD_DMA_IM_SGD 0x0008 /* interrupt on SGD flag */
#define AD_DMA_IM_EOL 0x000c /* interrupt on End of Linked List */
#define AD_DMA_SGDS 0x0030 /* SGD status */
#define AD_DMA_SFLG 0x0040 /* SGD flag */
#define AD_DMA_EOL 0x0080 /* SGD end of list */
/* bits 8 -> 15 reserved */
#define AD_DMA_DISR 0xc0 /* dma interrupt status */
#define AD_DMA_DISR_RESI 0x000001 /* resampler channel interrupt */
#define AD_DMA_DISR_ADCI 0x000002 /* ADC channel interrupt */
#define AD_DMA_DISR_SYNI 0x000004 /* synthesis channel interrupt */
#define AD_DMA_DISR_WAVI 0x000008 /* wave channel interrupt */
/* bits 4, 5 reserved */
#define AD_DMA_DISR_SEPS 0x000040 /* serial eeprom status */
/* bits 7 -> 13 reserved */
#define AD_DMA_DISR_PMAI 0x004000 /* pci master abort interrupt */
#define AD_DMA_DISR_PTAI 0x008000 /* pci target abort interrupt */
#define AD_DMA_DISR_PTAE 0x010000 /* pci target abort interrupt enable */
#define AD_DMA_DISR_PMAE 0x020000 /* pci master abort interrupt enable */
/* bits 19 -> 31 reserved */
/* interrupt mask */
#define AD_INTR_MASK (AD_DMA_DISR_RESI|AD_DMA_DISR_ADCI| \
AD_DMA_DISR_WAVI|AD_DMA_DISR_SYNI| \
AD_DMA_DISR_PMAI|AD_DMA_DISR_PTAI)
#define AD_DMA_CHSS 0xc4 /* dma channel stop status */
#define AD_DMA_CHSS_RESS 0x000001 /* resampler channel stopped */
#define AD_DMA_CHSS_ADCS 0x000002 /* ADC channel stopped */
#define AD_DMA_CHSS_SYNS 0x000004 /* synthesis channel stopped */
#define AD_DMA_CHSS_WAVS 0x000008 /* wave channel stopped */
#define AD_GPIO_IPC 0xc8 /* gpio port control */
#define AD_GPIO_OP 0xca /* gpio output port status */
#define AD_GPIO_IP 0xcc /* gpio input port status */
#define AD_AC97_BASE 0x100 /* ac97 base register */
#define AD_AC97_RESET 0x100 /* reset */
#define AD_AC97_PWR_CTL 0x126 /* == AC97_POWERDOWN */
#define AD_AC97_PWR_ADC 0x0001 /* ADC ready status */
#define AD_AC97_PWR_DAC 0x0002 /* DAC ready status */
#define AD_AC97_PWR_PR0 0x0100 /* PR0 (ADC) powerdown */
#define AD_AC97_PWR_PR1 0x0200 /* PR1 (DAC) powerdown */
#define AD_MISC_CTL 0x176 /* misc control */
#define AD_MISC_CTL_DACZ 0x8000 /* set for zero fill, unset for repeat */
#define AD_MISC_CTL_ARSR 0x0001 /* set for SR1, unset for SR0 */
#define AD_MISC_CTL_ALSR 0x0100
#define AD_MISC_CTL_DLSR 0x0400
#define AD_MISC_CTL_DRSR 0x0004
#define AD_AC97_SR0 0x178 /* sample rate 0, 0xbb80 == 48K */
#define AD_AC97_SR0_48K 0xbb80 /* 48KHz */
#define AD_AC97_SR1 0x17a /* sample rate 1 */
#define AD_AC97_ACIC 0x180 /* ac97 codec interface control */
#define AD_AC97_ACIC_ACIE 0x0001 /* analog codec interface enable */
#define AD_AC97_ACIC_ACRD 0x0002 /* analog codec reset disable */
#define AD_AC97_ACIC_ASOE 0x0004 /* audio stream output enable */
#define AD_AC97_ACIC_VSRM 0x0008 /* variable sample rate mode */
#define AD_AC97_ACIC_FSDH 0x0100 /* force SDATA_OUT high */
#define AD_AC97_ACIC_FSYH 0x0200 /* force sync high */
#define AD_AC97_ACIC_ACRDY 0x8000 /* analog codec ready status */
/* bits 10 -> 14 reserved */
#define AD_DS_MEMSIZE 512
#define AD_OPL_MEMSIZE 16
#define AD_MIDI_MEMSIZE 16
#define AD_WAV_STATE 0
#define AD_ADC_STATE 1
#define AD_MAX_STATES 2
#define AD_CHAN_WAV 0x0001
#define AD_CHAN_ADC 0x0002
#define AD_CHAN_RES 0x0004
#define AD_CHAN_SYN 0x0008
/* The chip would support 4 GB buffers and 16 MB periods,
* but let's not overdo it ... */
#define BUFFER_BYTES_MAX (256 * 1024)
#define PERIOD_BYTES_MIN 32
#define PERIOD_BYTES_MAX (BUFFER_BYTES_MAX / 2)
#define PERIODS_MIN 2
#define PERIODS_MAX (BUFFER_BYTES_MAX / PERIOD_BYTES_MIN)
#endif /* __AD1889_H__ */

494
sound/pci/ak4531_codec.c Normal file
View file

@ -0,0 +1,494 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Universal routines for AK4531 codec
*
*
* 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/delay.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/ak4531_codec.h>
#include <sound/tlv.h>
/*
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Universal routines for AK4531 codec");
MODULE_LICENSE("GPL");
*/
#ifdef CONFIG_PROC_FS
static void snd_ak4531_proc_init(struct snd_card *card, struct snd_ak4531 *ak4531);
#else
#define snd_ak4531_proc_init(card,ak)
#endif
/*
*
*/
#if 0
static void snd_ak4531_dump(struct snd_ak4531 *ak4531)
{
int idx;
for (idx = 0; idx < 0x19; idx++)
printk(KERN_DEBUG "ak4531 0x%x: 0x%x\n",
idx, ak4531->regs[idx]);
}
#endif
/*
*
*/
#define AK4531_SINGLE(xname, xindex, reg, shift, mask, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
.info = snd_ak4531_info_single, \
.get = snd_ak4531_get_single, .put = snd_ak4531_put_single, \
.private_value = reg | (shift << 16) | (mask << 24) | (invert << 22) }
#define AK4531_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
.name = xname, .index = xindex, \
.info = snd_ak4531_info_single, \
.get = snd_ak4531_get_single, .put = snd_ak4531_put_single, \
.private_value = reg | (shift << 16) | (mask << 24) | (invert << 22), \
.tlv = { .p = (xtlv) } }
static int snd_ak4531_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
int mask = (kcontrol->private_value >> 24) & 0xff;
uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask;
return 0;
}
static int snd_ak4531_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 16) & 0x07;
int mask = (kcontrol->private_value >> 24) & 0xff;
int invert = (kcontrol->private_value >> 22) & 1;
int val;
mutex_lock(&ak4531->reg_mutex);
val = (ak4531->regs[reg] >> shift) & mask;
mutex_unlock(&ak4531->reg_mutex);
if (invert) {
val = mask - val;
}
ucontrol->value.integer.value[0] = val;
return 0;
}
static int snd_ak4531_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 16) & 0x07;
int mask = (kcontrol->private_value >> 24) & 0xff;
int invert = (kcontrol->private_value >> 22) & 1;
int change;
int val;
val = ucontrol->value.integer.value[0] & mask;
if (invert) {
val = mask - val;
}
val <<= shift;
mutex_lock(&ak4531->reg_mutex);
val = (ak4531->regs[reg] & ~(mask << shift)) | val;
change = val != ak4531->regs[reg];
ak4531->write(ak4531, reg, ak4531->regs[reg] = val);
mutex_unlock(&ak4531->reg_mutex);
return change;
}
#define AK4531_DOUBLE(xname, xindex, left_reg, right_reg, left_shift, right_shift, mask, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
.info = snd_ak4531_info_double, \
.get = snd_ak4531_get_double, .put = snd_ak4531_put_double, \
.private_value = left_reg | (right_reg << 8) | (left_shift << 16) | (right_shift << 19) | (mask << 24) | (invert << 22) }
#define AK4531_DOUBLE_TLV(xname, xindex, left_reg, right_reg, left_shift, right_shift, mask, invert, xtlv) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
.name = xname, .index = xindex, \
.info = snd_ak4531_info_double, \
.get = snd_ak4531_get_double, .put = snd_ak4531_put_double, \
.private_value = left_reg | (right_reg << 8) | (left_shift << 16) | (right_shift << 19) | (mask << 24) | (invert << 22), \
.tlv = { .p = (xtlv) } }
static int snd_ak4531_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
int mask = (kcontrol->private_value >> 24) & 0xff;
uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask;
return 0;
}
static int snd_ak4531_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
int left_reg = kcontrol->private_value & 0xff;
int right_reg = (kcontrol->private_value >> 8) & 0xff;
int left_shift = (kcontrol->private_value >> 16) & 0x07;
int right_shift = (kcontrol->private_value >> 19) & 0x07;
int mask = (kcontrol->private_value >> 24) & 0xff;
int invert = (kcontrol->private_value >> 22) & 1;
int left, right;
mutex_lock(&ak4531->reg_mutex);
left = (ak4531->regs[left_reg] >> left_shift) & mask;
right = (ak4531->regs[right_reg] >> right_shift) & mask;
mutex_unlock(&ak4531->reg_mutex);
if (invert) {
left = mask - left;
right = mask - right;
}
ucontrol->value.integer.value[0] = left;
ucontrol->value.integer.value[1] = right;
return 0;
}
static int snd_ak4531_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
int left_reg = kcontrol->private_value & 0xff;
int right_reg = (kcontrol->private_value >> 8) & 0xff;
int left_shift = (kcontrol->private_value >> 16) & 0x07;
int right_shift = (kcontrol->private_value >> 19) & 0x07;
int mask = (kcontrol->private_value >> 24) & 0xff;
int invert = (kcontrol->private_value >> 22) & 1;
int change;
int left, right;
left = ucontrol->value.integer.value[0] & mask;
right = ucontrol->value.integer.value[1] & mask;
if (invert) {
left = mask - left;
right = mask - right;
}
left <<= left_shift;
right <<= right_shift;
mutex_lock(&ak4531->reg_mutex);
if (left_reg == right_reg) {
left = (ak4531->regs[left_reg] & ~((mask << left_shift) | (mask << right_shift))) | left | right;
change = left != ak4531->regs[left_reg];
ak4531->write(ak4531, left_reg, ak4531->regs[left_reg] = left);
} else {
left = (ak4531->regs[left_reg] & ~(mask << left_shift)) | left;
right = (ak4531->regs[right_reg] & ~(mask << right_shift)) | right;
change = left != ak4531->regs[left_reg] || right != ak4531->regs[right_reg];
ak4531->write(ak4531, left_reg, ak4531->regs[left_reg] = left);
ak4531->write(ak4531, right_reg, ak4531->regs[right_reg] = right);
}
mutex_unlock(&ak4531->reg_mutex);
return change;
}
#define AK4531_INPUT_SW(xname, xindex, reg1, reg2, left_shift, right_shift) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
.info = snd_ak4531_info_input_sw, \
.get = snd_ak4531_get_input_sw, .put = snd_ak4531_put_input_sw, \
.private_value = reg1 | (reg2 << 8) | (left_shift << 16) | (right_shift << 24) }
static int snd_ak4531_info_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 4;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int snd_ak4531_get_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
int reg1 = kcontrol->private_value & 0xff;
int reg2 = (kcontrol->private_value >> 8) & 0xff;
int left_shift = (kcontrol->private_value >> 16) & 0x0f;
int right_shift = (kcontrol->private_value >> 24) & 0x0f;
mutex_lock(&ak4531->reg_mutex);
ucontrol->value.integer.value[0] = (ak4531->regs[reg1] >> left_shift) & 1;
ucontrol->value.integer.value[1] = (ak4531->regs[reg2] >> left_shift) & 1;
ucontrol->value.integer.value[2] = (ak4531->regs[reg1] >> right_shift) & 1;
ucontrol->value.integer.value[3] = (ak4531->regs[reg2] >> right_shift) & 1;
mutex_unlock(&ak4531->reg_mutex);
return 0;
}
static int snd_ak4531_put_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
int reg1 = kcontrol->private_value & 0xff;
int reg2 = (kcontrol->private_value >> 8) & 0xff;
int left_shift = (kcontrol->private_value >> 16) & 0x0f;
int right_shift = (kcontrol->private_value >> 24) & 0x0f;
int change;
int val1, val2;
mutex_lock(&ak4531->reg_mutex);
val1 = ak4531->regs[reg1] & ~((1 << left_shift) | (1 << right_shift));
val2 = ak4531->regs[reg2] & ~((1 << left_shift) | (1 << right_shift));
val1 |= (ucontrol->value.integer.value[0] & 1) << left_shift;
val2 |= (ucontrol->value.integer.value[1] & 1) << left_shift;
val1 |= (ucontrol->value.integer.value[2] & 1) << right_shift;
val2 |= (ucontrol->value.integer.value[3] & 1) << right_shift;
change = val1 != ak4531->regs[reg1] || val2 != ak4531->regs[reg2];
ak4531->write(ak4531, reg1, ak4531->regs[reg1] = val1);
ak4531->write(ak4531, reg2, ak4531->regs[reg2] = val2);
mutex_unlock(&ak4531->reg_mutex);
return change;
}
static const DECLARE_TLV_DB_SCALE(db_scale_master, -6200, 200, 0);
static const DECLARE_TLV_DB_SCALE(db_scale_mono, -2800, 400, 0);
static const DECLARE_TLV_DB_SCALE(db_scale_input, -5000, 200, 0);
static struct snd_kcontrol_new snd_ak4531_controls[] = {
AK4531_DOUBLE_TLV("Master Playback Switch", 0,
AK4531_LMASTER, AK4531_RMASTER, 7, 7, 1, 1,
db_scale_master),
AK4531_DOUBLE("Master Playback Volume", 0, AK4531_LMASTER, AK4531_RMASTER, 0, 0, 0x1f, 1),
AK4531_SINGLE_TLV("Master Mono Playback Switch", 0, AK4531_MONO_OUT, 7, 1, 1,
db_scale_mono),
AK4531_SINGLE("Master Mono Playback Volume", 0, AK4531_MONO_OUT, 0, 0x07, 1),
AK4531_DOUBLE("PCM Switch", 0, AK4531_LVOICE, AK4531_RVOICE, 7, 7, 1, 1),
AK4531_DOUBLE_TLV("PCM Volume", 0, AK4531_LVOICE, AK4531_RVOICE, 0, 0, 0x1f, 1,
db_scale_input),
AK4531_DOUBLE("PCM Playback Switch", 0, AK4531_OUT_SW2, AK4531_OUT_SW2, 3, 2, 1, 0),
AK4531_DOUBLE("PCM Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 2, 2, 1, 0),
AK4531_DOUBLE("PCM Switch", 1, AK4531_LFM, AK4531_RFM, 7, 7, 1, 1),
AK4531_DOUBLE_TLV("PCM Volume", 1, AK4531_LFM, AK4531_RFM, 0, 0, 0x1f, 1,
db_scale_input),
AK4531_DOUBLE("PCM Playback Switch", 1, AK4531_OUT_SW1, AK4531_OUT_SW1, 6, 5, 1, 0),
AK4531_INPUT_SW("PCM Capture Route", 1, AK4531_LIN_SW1, AK4531_RIN_SW1, 6, 5),
AK4531_DOUBLE("CD Switch", 0, AK4531_LCD, AK4531_RCD, 7, 7, 1, 1),
AK4531_DOUBLE_TLV("CD Volume", 0, AK4531_LCD, AK4531_RCD, 0, 0, 0x1f, 1,
db_scale_input),
AK4531_DOUBLE("CD Playback Switch", 0, AK4531_OUT_SW1, AK4531_OUT_SW1, 2, 1, 1, 0),
AK4531_INPUT_SW("CD Capture Route", 0, AK4531_LIN_SW1, AK4531_RIN_SW1, 2, 1),
AK4531_DOUBLE("Line Switch", 0, AK4531_LLINE, AK4531_RLINE, 7, 7, 1, 1),
AK4531_DOUBLE_TLV("Line Volume", 0, AK4531_LLINE, AK4531_RLINE, 0, 0, 0x1f, 1,
db_scale_input),
AK4531_DOUBLE("Line Playback Switch", 0, AK4531_OUT_SW1, AK4531_OUT_SW1, 4, 3, 1, 0),
AK4531_INPUT_SW("Line Capture Route", 0, AK4531_LIN_SW1, AK4531_RIN_SW1, 4, 3),
AK4531_DOUBLE("Aux Switch", 0, AK4531_LAUXA, AK4531_RAUXA, 7, 7, 1, 1),
AK4531_DOUBLE_TLV("Aux Volume", 0, AK4531_LAUXA, AK4531_RAUXA, 0, 0, 0x1f, 1,
db_scale_input),
AK4531_DOUBLE("Aux Playback Switch", 0, AK4531_OUT_SW2, AK4531_OUT_SW2, 5, 4, 1, 0),
AK4531_INPUT_SW("Aux Capture Route", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 4, 3),
AK4531_SINGLE("Mono Switch", 0, AK4531_MONO1, 7, 1, 1),
AK4531_SINGLE_TLV("Mono Volume", 0, AK4531_MONO1, 0, 0x1f, 1, db_scale_input),
AK4531_SINGLE("Mono Playback Switch", 0, AK4531_OUT_SW2, 0, 1, 0),
AK4531_DOUBLE("Mono Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 0, 0, 1, 0),
AK4531_SINGLE("Mono Switch", 1, AK4531_MONO2, 7, 1, 1),
AK4531_SINGLE_TLV("Mono Volume", 1, AK4531_MONO2, 0, 0x1f, 1, db_scale_input),
AK4531_SINGLE("Mono Playback Switch", 1, AK4531_OUT_SW2, 1, 1, 0),
AK4531_DOUBLE("Mono Capture Switch", 1, AK4531_LIN_SW2, AK4531_RIN_SW2, 1, 1, 1, 0),
AK4531_SINGLE_TLV("Mic Volume", 0, AK4531_MIC, 0, 0x1f, 1, db_scale_input),
AK4531_SINGLE("Mic Switch", 0, AK4531_MIC, 7, 1, 1),
AK4531_SINGLE("Mic Playback Switch", 0, AK4531_OUT_SW1, 0, 1, 0),
AK4531_DOUBLE("Mic Capture Switch", 0, AK4531_LIN_SW1, AK4531_RIN_SW1, 0, 0, 1, 0),
AK4531_DOUBLE("Mic Bypass Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 7, 7, 1, 0),
AK4531_DOUBLE("Mono1 Bypass Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 6, 6, 1, 0),
AK4531_DOUBLE("Mono2 Bypass Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 5, 5, 1, 0),
AK4531_SINGLE("AD Input Select", 0, AK4531_AD_IN, 0, 1, 0),
AK4531_SINGLE("Mic Boost (+30dB)", 0, AK4531_MIC_GAIN, 0, 1, 0)
};
static int snd_ak4531_free(struct snd_ak4531 *ak4531)
{
if (ak4531) {
if (ak4531->private_free)
ak4531->private_free(ak4531);
kfree(ak4531);
}
return 0;
}
static int snd_ak4531_dev_free(struct snd_device *device)
{
struct snd_ak4531 *ak4531 = device->device_data;
return snd_ak4531_free(ak4531);
}
static u8 snd_ak4531_initial_map[0x19 + 1] = {
0x9f, /* 00: Master Volume Lch */
0x9f, /* 01: Master Volume Rch */
0x9f, /* 02: Voice Volume Lch */
0x9f, /* 03: Voice Volume Rch */
0x9f, /* 04: FM Volume Lch */
0x9f, /* 05: FM Volume Rch */
0x9f, /* 06: CD Audio Volume Lch */
0x9f, /* 07: CD Audio Volume Rch */
0x9f, /* 08: Line Volume Lch */
0x9f, /* 09: Line Volume Rch */
0x9f, /* 0a: Aux Volume Lch */
0x9f, /* 0b: Aux Volume Rch */
0x9f, /* 0c: Mono1 Volume */
0x9f, /* 0d: Mono2 Volume */
0x9f, /* 0e: Mic Volume */
0x87, /* 0f: Mono-out Volume */
0x00, /* 10: Output Mixer SW1 */
0x00, /* 11: Output Mixer SW2 */
0x00, /* 12: Lch Input Mixer SW1 */
0x00, /* 13: Rch Input Mixer SW1 */
0x00, /* 14: Lch Input Mixer SW2 */
0x00, /* 15: Rch Input Mixer SW2 */
0x00, /* 16: Reset & Power Down */
0x00, /* 17: Clock Select */
0x00, /* 18: AD Input Select */
0x01 /* 19: Mic Amp Setup */
};
int snd_ak4531_mixer(struct snd_card *card,
struct snd_ak4531 *_ak4531,
struct snd_ak4531 **rak4531)
{
unsigned int idx;
int err;
struct snd_ak4531 *ak4531;
static struct snd_device_ops ops = {
.dev_free = snd_ak4531_dev_free,
};
if (snd_BUG_ON(!card || !_ak4531))
return -EINVAL;
if (rak4531)
*rak4531 = NULL;
ak4531 = kzalloc(sizeof(*ak4531), GFP_KERNEL);
if (ak4531 == NULL)
return -ENOMEM;
*ak4531 = *_ak4531;
mutex_init(&ak4531->reg_mutex);
if ((err = snd_component_add(card, "AK4531")) < 0) {
snd_ak4531_free(ak4531);
return err;
}
strcpy(card->mixername, "Asahi Kasei AK4531");
ak4531->write(ak4531, AK4531_RESET, 0x03); /* no RST, PD */
udelay(100);
ak4531->write(ak4531, AK4531_CLOCK, 0x00); /* CODEC ADC and CODEC DAC use {LR,B}CLK2 and run off LRCLK2 PLL */
for (idx = 0; idx <= 0x19; idx++) {
if (idx == AK4531_RESET || idx == AK4531_CLOCK)
continue;
ak4531->write(ak4531, idx, ak4531->regs[idx] = snd_ak4531_initial_map[idx]); /* recording source is mixer */
}
for (idx = 0; idx < ARRAY_SIZE(snd_ak4531_controls); idx++) {
if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ak4531_controls[idx], ak4531))) < 0) {
snd_ak4531_free(ak4531);
return err;
}
}
snd_ak4531_proc_init(card, ak4531);
if ((err = snd_device_new(card, SNDRV_DEV_CODEC, ak4531, &ops)) < 0) {
snd_ak4531_free(ak4531);
return err;
}
#if 0
snd_ak4531_dump(ak4531);
#endif
if (rak4531)
*rak4531 = ak4531;
return 0;
}
/*
* power management
*/
#ifdef CONFIG_PM
void snd_ak4531_suspend(struct snd_ak4531 *ak4531)
{
/* mute */
ak4531->write(ak4531, AK4531_LMASTER, 0x9f);
ak4531->write(ak4531, AK4531_RMASTER, 0x9f);
/* powerdown */
ak4531->write(ak4531, AK4531_RESET, 0x01);
}
void snd_ak4531_resume(struct snd_ak4531 *ak4531)
{
int idx;
/* initialize */
ak4531->write(ak4531, AK4531_RESET, 0x03);
udelay(100);
ak4531->write(ak4531, AK4531_CLOCK, 0x00);
/* restore mixer registers */
for (idx = 0; idx <= 0x19; idx++) {
if (idx == AK4531_RESET || idx == AK4531_CLOCK)
continue;
ak4531->write(ak4531, idx, ak4531->regs[idx]);
}
}
#endif
#ifdef CONFIG_PROC_FS
/*
* /proc interface
*/
static void snd_ak4531_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ak4531 *ak4531 = entry->private_data;
snd_iprintf(buffer, "Asahi Kasei AK4531\n\n");
snd_iprintf(buffer, "Recording source : %s\n"
"MIC gain : %s\n",
ak4531->regs[AK4531_AD_IN] & 1 ? "external" : "mixer",
ak4531->regs[AK4531_MIC_GAIN] & 1 ? "+30dB" : "+0dB");
}
static void
snd_ak4531_proc_init(struct snd_card *card, struct snd_ak4531 *ak4531)
{
struct snd_info_entry *entry;
if (! snd_card_proc_new(card, "ak4531", &entry))
snd_info_set_text_ops(entry, ak4531, snd_ak4531_proc_read);
}
#endif

View file

@ -0,0 +1,9 @@
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
#
snd-ali5451-objs := ali5451.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_ALI5451) += snd-ali5451.o

2297
sound/pci/ali5451/ali5451.c Normal file

File diff suppressed because it is too large Load diff

832
sound/pci/als300.c Normal file
View file

@ -0,0 +1,832 @@
/*
* als300.c - driver for Avance Logic ALS300/ALS300+ soundcards.
* Copyright (C) 2005 by Ash Willis <ashwillis@programmer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* TODO
* 4 channel playback for ALS300+
* gameport
* mpu401
* opl3
*
* NOTES
* The BLOCK_COUNTER registers for the ALS300(+) return a figure related to
* the position in the current period, NOT the whole buffer. It is important
* to know which period we are in so we can calculate the correct pointer.
* This is why we always use 2 periods. We can then use a flip-flop variable
* to keep track of what period we are in.
*/
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/ac97_codec.h>
#include <sound/opl3.h>
/* snd_als300_set_irq_flag */
#define IRQ_DISABLE 0
#define IRQ_ENABLE 1
/* I/O port layout */
#define AC97_ACCESS 0x00
#define AC97_READ 0x04
#define AC97_STATUS 0x06
#define AC97_DATA_AVAIL (1<<6)
#define AC97_BUSY (1<<7)
#define ALS300_IRQ_STATUS 0x07 /* ALS300 Only */
#define IRQ_PLAYBACK (1<<3)
#define IRQ_CAPTURE (1<<2)
#define GCR_DATA 0x08
#define GCR_INDEX 0x0C
#define ALS300P_DRAM_IRQ_STATUS 0x0D /* ALS300+ Only */
#define MPU_IRQ_STATUS 0x0E /* ALS300 Rev. E+, ALS300+ */
#define ALS300P_IRQ_STATUS 0x0F /* ALS300+ Only */
/* General Control Registers */
#define PLAYBACK_START 0x80
#define PLAYBACK_END 0x81
#define PLAYBACK_CONTROL 0x82
#define TRANSFER_START (1<<16)
#define FIFO_PAUSE (1<<17)
#define RECORD_START 0x83
#define RECORD_END 0x84
#define RECORD_CONTROL 0x85
#define DRAM_WRITE_CONTROL 0x8B
#define WRITE_TRANS_START (1<<16)
#define DRAM_MODE_2 (1<<17)
#define MISC_CONTROL 0x8C
#define IRQ_SET_BIT (1<<15)
#define VMUTE_NORMAL (1<<20)
#define MMUTE_NORMAL (1<<21)
#define MUS_VOC_VOL 0x8E
#define PLAYBACK_BLOCK_COUNTER 0x9A
#define RECORD_BLOCK_COUNTER 0x9B
#define DEBUG_PLAY_REC 0
#if DEBUG_PLAY_REC
#define snd_als300_dbgplay(format, args...) printk(KERN_ERR format, ##args)
#else
#define snd_als300_dbgplay(format, args...)
#endif
enum {DEVICE_ALS300, DEVICE_ALS300_PLUS};
MODULE_AUTHOR("Ash Willis <ashwillis@programmer.net>");
MODULE_DESCRIPTION("Avance Logic ALS300");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{Avance Logic,ALS300},{Avance Logic,ALS300+}}");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for ALS300 sound card.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for ALS300 sound card.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable ALS300 sound card.");
struct snd_als300 {
unsigned long port;
spinlock_t reg_lock;
struct snd_card *card;
struct pci_dev *pci;
struct snd_pcm *pcm;
struct snd_pcm_substream *playback_substream;
struct snd_pcm_substream *capture_substream;
struct snd_ac97 *ac97;
struct snd_opl3 *opl3;
struct resource *res_port;
int irq;
int chip_type; /* ALS300 or ALS300+ */
char revision;
};
struct snd_als300_substream_data {
int period_flipflop;
int control_register;
int block_counter_register;
};
static const struct pci_device_id snd_als300_ids[] = {
{ 0x4005, 0x0300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEVICE_ALS300 },
{ 0x4005, 0x0308, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEVICE_ALS300_PLUS },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, snd_als300_ids);
static inline u32 snd_als300_gcr_read(unsigned long port, unsigned short reg)
{
outb(reg, port+GCR_INDEX);
return inl(port+GCR_DATA);
}
static inline void snd_als300_gcr_write(unsigned long port,
unsigned short reg, u32 val)
{
outb(reg, port+GCR_INDEX);
outl(val, port+GCR_DATA);
}
/* Enable/Disable Interrupts */
static void snd_als300_set_irq_flag(struct snd_als300 *chip, int cmd)
{
u32 tmp = snd_als300_gcr_read(chip->port, MISC_CONTROL);
/* boolean XOR check, since old vs. new hardware have
directly reversed bit setting for ENABLE and DISABLE.
ALS300+ acts like newer versions of ALS300 */
if (((chip->revision > 5 || chip->chip_type == DEVICE_ALS300_PLUS) ^
(cmd == IRQ_ENABLE)) == 0)
tmp |= IRQ_SET_BIT;
else
tmp &= ~IRQ_SET_BIT;
snd_als300_gcr_write(chip->port, MISC_CONTROL, tmp);
}
static int snd_als300_free(struct snd_als300 *chip)
{
snd_als300_set_irq_flag(chip, IRQ_DISABLE);
if (chip->irq >= 0)
free_irq(chip->irq, chip);
pci_release_regions(chip->pci);
pci_disable_device(chip->pci);
kfree(chip);
return 0;
}
static int snd_als300_dev_free(struct snd_device *device)
{
struct snd_als300 *chip = device->device_data;
return snd_als300_free(chip);
}
static irqreturn_t snd_als300_interrupt(int irq, void *dev_id)
{
u8 status;
struct snd_als300 *chip = dev_id;
struct snd_als300_substream_data *data;
status = inb(chip->port+ALS300_IRQ_STATUS);
if (!status) /* shared IRQ, for different device?? Exit ASAP! */
return IRQ_NONE;
/* ACK everything ASAP */
outb(status, chip->port+ALS300_IRQ_STATUS);
if (status & IRQ_PLAYBACK) {
if (chip->pcm && chip->playback_substream) {
data = chip->playback_substream->runtime->private_data;
data->period_flipflop ^= 1;
snd_pcm_period_elapsed(chip->playback_substream);
snd_als300_dbgplay("IRQ_PLAYBACK\n");
}
}
if (status & IRQ_CAPTURE) {
if (chip->pcm && chip->capture_substream) {
data = chip->capture_substream->runtime->private_data;
data->period_flipflop ^= 1;
snd_pcm_period_elapsed(chip->capture_substream);
snd_als300_dbgplay("IRQ_CAPTURE\n");
}
}
return IRQ_HANDLED;
}
static irqreturn_t snd_als300plus_interrupt(int irq, void *dev_id)
{
u8 general, mpu, dram;
struct snd_als300 *chip = dev_id;
struct snd_als300_substream_data *data;
general = inb(chip->port+ALS300P_IRQ_STATUS);
mpu = inb(chip->port+MPU_IRQ_STATUS);
dram = inb(chip->port+ALS300P_DRAM_IRQ_STATUS);
/* shared IRQ, for different device?? Exit ASAP! */
if ((general == 0) && ((mpu & 0x80) == 0) && ((dram & 0x01) == 0))
return IRQ_NONE;
if (general & IRQ_PLAYBACK) {
if (chip->pcm && chip->playback_substream) {
outb(IRQ_PLAYBACK, chip->port+ALS300P_IRQ_STATUS);
data = chip->playback_substream->runtime->private_data;
data->period_flipflop ^= 1;
snd_pcm_period_elapsed(chip->playback_substream);
snd_als300_dbgplay("IRQ_PLAYBACK\n");
}
}
if (general & IRQ_CAPTURE) {
if (chip->pcm && chip->capture_substream) {
outb(IRQ_CAPTURE, chip->port+ALS300P_IRQ_STATUS);
data = chip->capture_substream->runtime->private_data;
data->period_flipflop ^= 1;
snd_pcm_period_elapsed(chip->capture_substream);
snd_als300_dbgplay("IRQ_CAPTURE\n");
}
}
/* FIXME: Ack other interrupt types. Not important right now as
* those other devices aren't enabled. */
return IRQ_HANDLED;
}
static void snd_als300_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
}
static unsigned short snd_als300_ac97_read(struct snd_ac97 *ac97,
unsigned short reg)
{
int i;
struct snd_als300 *chip = ac97->private_data;
for (i = 0; i < 1000; i++) {
if ((inb(chip->port+AC97_STATUS) & (AC97_BUSY)) == 0)
break;
udelay(10);
}
outl((reg << 24) | (1 << 31), chip->port+AC97_ACCESS);
for (i = 0; i < 1000; i++) {
if ((inb(chip->port+AC97_STATUS) & (AC97_DATA_AVAIL)) != 0)
break;
udelay(10);
}
return inw(chip->port+AC97_READ);
}
static void snd_als300_ac97_write(struct snd_ac97 *ac97,
unsigned short reg, unsigned short val)
{
int i;
struct snd_als300 *chip = ac97->private_data;
for (i = 0; i < 1000; i++) {
if ((inb(chip->port+AC97_STATUS) & (AC97_BUSY)) == 0)
break;
udelay(10);
}
outl((reg << 24) | val, chip->port+AC97_ACCESS);
}
static int snd_als300_ac97(struct snd_als300 *chip)
{
struct snd_ac97_bus *bus;
struct snd_ac97_template ac97;
int err;
static struct snd_ac97_bus_ops ops = {
.write = snd_als300_ac97_write,
.read = snd_als300_ac97_read,
};
if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &bus)) < 0)
return err;
memset(&ac97, 0, sizeof(ac97));
ac97.private_data = chip;
return snd_ac97_mixer(bus, &ac97, &chip->ac97);
}
/* hardware definition
*
* In AC97 mode, we always use 48k/16bit/stereo.
* Any request to change data type is ignored by
* the card when it is running outside of legacy
* mode.
*/
static struct snd_pcm_hardware snd_als300_playback_hw =
{
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_S16,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 64 * 1024,
.period_bytes_min = 64,
.period_bytes_max = 32 * 1024,
.periods_min = 2,
.periods_max = 2,
};
static struct snd_pcm_hardware snd_als300_capture_hw =
{
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_S16,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 64 * 1024,
.period_bytes_min = 64,
.period_bytes_max = 32 * 1024,
.periods_min = 2,
.periods_max = 2,
};
static int snd_als300_playback_open(struct snd_pcm_substream *substream)
{
struct snd_als300 *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_als300_substream_data *data = kzalloc(sizeof(*data),
GFP_KERNEL);
if (!data)
return -ENOMEM;
chip->playback_substream = substream;
runtime->hw = snd_als300_playback_hw;
runtime->private_data = data;
data->control_register = PLAYBACK_CONTROL;
data->block_counter_register = PLAYBACK_BLOCK_COUNTER;
return 0;
}
static int snd_als300_playback_close(struct snd_pcm_substream *substream)
{
struct snd_als300 *chip = snd_pcm_substream_chip(substream);
struct snd_als300_substream_data *data;
data = substream->runtime->private_data;
kfree(data);
chip->playback_substream = NULL;
snd_pcm_lib_free_pages(substream);
return 0;
}
static int snd_als300_capture_open(struct snd_pcm_substream *substream)
{
struct snd_als300 *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_als300_substream_data *data = kzalloc(sizeof(*data),
GFP_KERNEL);
if (!data)
return -ENOMEM;
chip->capture_substream = substream;
runtime->hw = snd_als300_capture_hw;
runtime->private_data = data;
data->control_register = RECORD_CONTROL;
data->block_counter_register = RECORD_BLOCK_COUNTER;
return 0;
}
static int snd_als300_capture_close(struct snd_pcm_substream *substream)
{
struct snd_als300 *chip = snd_pcm_substream_chip(substream);
struct snd_als300_substream_data *data;
data = substream->runtime->private_data;
kfree(data);
chip->capture_substream = NULL;
snd_pcm_lib_free_pages(substream);
return 0;
}
static int snd_als300_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
return snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params));
}
static int snd_als300_pcm_hw_free(struct snd_pcm_substream *substream)
{
return snd_pcm_lib_free_pages(substream);
}
static int snd_als300_playback_prepare(struct snd_pcm_substream *substream)
{
u32 tmp;
struct snd_als300 *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned short period_bytes = snd_pcm_lib_period_bytes(substream);
unsigned short buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
spin_lock_irq(&chip->reg_lock);
tmp = snd_als300_gcr_read(chip->port, PLAYBACK_CONTROL);
tmp &= ~TRANSFER_START;
snd_als300_dbgplay("Period bytes: %d Buffer bytes %d\n",
period_bytes, buffer_bytes);
/* set block size */
tmp &= 0xffff0000;
tmp |= period_bytes - 1;
snd_als300_gcr_write(chip->port, PLAYBACK_CONTROL, tmp);
/* set dma area */
snd_als300_gcr_write(chip->port, PLAYBACK_START,
runtime->dma_addr);
snd_als300_gcr_write(chip->port, PLAYBACK_END,
runtime->dma_addr + buffer_bytes - 1);
spin_unlock_irq(&chip->reg_lock);
return 0;
}
static int snd_als300_capture_prepare(struct snd_pcm_substream *substream)
{
u32 tmp;
struct snd_als300 *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned short period_bytes = snd_pcm_lib_period_bytes(substream);
unsigned short buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
spin_lock_irq(&chip->reg_lock);
tmp = snd_als300_gcr_read(chip->port, RECORD_CONTROL);
tmp &= ~TRANSFER_START;
snd_als300_dbgplay("Period bytes: %d Buffer bytes %d\n", period_bytes,
buffer_bytes);
/* set block size */
tmp &= 0xffff0000;
tmp |= period_bytes - 1;
/* set dma area */
snd_als300_gcr_write(chip->port, RECORD_CONTROL, tmp);
snd_als300_gcr_write(chip->port, RECORD_START,
runtime->dma_addr);
snd_als300_gcr_write(chip->port, RECORD_END,
runtime->dma_addr + buffer_bytes - 1);
spin_unlock_irq(&chip->reg_lock);
return 0;
}
static int snd_als300_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_als300 *chip = snd_pcm_substream_chip(substream);
u32 tmp;
struct snd_als300_substream_data *data;
unsigned short reg;
int ret = 0;
data = substream->runtime->private_data;
reg = data->control_register;
spin_lock(&chip->reg_lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
tmp = snd_als300_gcr_read(chip->port, reg);
data->period_flipflop = 1;
snd_als300_gcr_write(chip->port, reg, tmp | TRANSFER_START);
snd_als300_dbgplay("TRIGGER START\n");
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
tmp = snd_als300_gcr_read(chip->port, reg);
snd_als300_gcr_write(chip->port, reg, tmp & ~TRANSFER_START);
snd_als300_dbgplay("TRIGGER STOP\n");
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
tmp = snd_als300_gcr_read(chip->port, reg);
snd_als300_gcr_write(chip->port, reg, tmp | FIFO_PAUSE);
snd_als300_dbgplay("TRIGGER PAUSE\n");
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
tmp = snd_als300_gcr_read(chip->port, reg);
snd_als300_gcr_write(chip->port, reg, tmp & ~FIFO_PAUSE);
snd_als300_dbgplay("TRIGGER RELEASE\n");
break;
default:
snd_als300_dbgplay("TRIGGER INVALID\n");
ret = -EINVAL;
}
spin_unlock(&chip->reg_lock);
return ret;
}
static snd_pcm_uframes_t snd_als300_pointer(struct snd_pcm_substream *substream)
{
u16 current_ptr;
struct snd_als300 *chip = snd_pcm_substream_chip(substream);
struct snd_als300_substream_data *data;
unsigned short period_bytes;
data = substream->runtime->private_data;
period_bytes = snd_pcm_lib_period_bytes(substream);
spin_lock(&chip->reg_lock);
current_ptr = (u16) snd_als300_gcr_read(chip->port,
data->block_counter_register) + 4;
spin_unlock(&chip->reg_lock);
if (current_ptr > period_bytes)
current_ptr = 0;
else
current_ptr = period_bytes - current_ptr;
if (data->period_flipflop == 0)
current_ptr += period_bytes;
snd_als300_dbgplay("Pointer (bytes): %d\n", current_ptr);
return bytes_to_frames(substream->runtime, current_ptr);
}
static struct snd_pcm_ops snd_als300_playback_ops = {
.open = snd_als300_playback_open,
.close = snd_als300_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_als300_pcm_hw_params,
.hw_free = snd_als300_pcm_hw_free,
.prepare = snd_als300_playback_prepare,
.trigger = snd_als300_trigger,
.pointer = snd_als300_pointer,
};
static struct snd_pcm_ops snd_als300_capture_ops = {
.open = snd_als300_capture_open,
.close = snd_als300_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_als300_pcm_hw_params,
.hw_free = snd_als300_pcm_hw_free,
.prepare = snd_als300_capture_prepare,
.trigger = snd_als300_trigger,
.pointer = snd_als300_pointer,
};
static int snd_als300_new_pcm(struct snd_als300 *chip)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(chip->card, "ALS300", 0, 1, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = chip;
strcpy(pcm->name, "ALS300");
chip->pcm = pcm;
/* set operators */
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_als300_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
&snd_als300_capture_ops);
/* pre-allocation of buffers */
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data(chip->pci), 64*1024, 64*1024);
return 0;
}
static void snd_als300_init(struct snd_als300 *chip)
{
unsigned long flags;
u32 tmp;
spin_lock_irqsave(&chip->reg_lock, flags);
chip->revision = (snd_als300_gcr_read(chip->port, MISC_CONTROL) >> 16)
& 0x0000000F;
/* Setup DRAM */
tmp = snd_als300_gcr_read(chip->port, DRAM_WRITE_CONTROL);
snd_als300_gcr_write(chip->port, DRAM_WRITE_CONTROL,
(tmp | DRAM_MODE_2)
& ~WRITE_TRANS_START);
/* Enable IRQ output */
snd_als300_set_irq_flag(chip, IRQ_ENABLE);
/* Unmute hardware devices so their outputs get routed to
* the onboard mixer */
tmp = snd_als300_gcr_read(chip->port, MISC_CONTROL);
snd_als300_gcr_write(chip->port, MISC_CONTROL,
tmp | VMUTE_NORMAL | MMUTE_NORMAL);
/* Reset volumes */
snd_als300_gcr_write(chip->port, MUS_VOC_VOL, 0);
/* Make sure playback transfer is stopped */
tmp = snd_als300_gcr_read(chip->port, PLAYBACK_CONTROL);
snd_als300_gcr_write(chip->port, PLAYBACK_CONTROL,
tmp & ~TRANSFER_START);
spin_unlock_irqrestore(&chip->reg_lock, flags);
}
static int snd_als300_create(struct snd_card *card,
struct pci_dev *pci, int chip_type,
struct snd_als300 **rchip)
{
struct snd_als300 *chip;
void *irq_handler;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_als300_dev_free,
};
*rchip = NULL;
if ((err = pci_enable_device(pci)) < 0)
return err;
if (pci_set_dma_mask(pci, DMA_BIT_MASK(28)) < 0 ||
pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(28)) < 0) {
dev_err(card->dev, "error setting 28bit DMA mask\n");
pci_disable_device(pci);
return -ENXIO;
}
pci_set_master(pci);
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
if (chip == NULL) {
pci_disable_device(pci);
return -ENOMEM;
}
chip->card = card;
chip->pci = pci;
chip->irq = -1;
chip->chip_type = chip_type;
spin_lock_init(&chip->reg_lock);
if ((err = pci_request_regions(pci, "ALS300")) < 0) {
kfree(chip);
pci_disable_device(pci);
return err;
}
chip->port = pci_resource_start(pci, 0);
if (chip->chip_type == DEVICE_ALS300_PLUS)
irq_handler = snd_als300plus_interrupt;
else
irq_handler = snd_als300_interrupt;
if (request_irq(pci->irq, irq_handler, IRQF_SHARED,
KBUILD_MODNAME, chip)) {
dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
snd_als300_free(chip);
return -EBUSY;
}
chip->irq = pci->irq;
snd_als300_init(chip);
err = snd_als300_ac97(chip);
if (err < 0) {
dev_err(card->dev, "Could not create ac97\n");
snd_als300_free(chip);
return err;
}
if ((err = snd_als300_new_pcm(chip)) < 0) {
dev_err(card->dev, "Could not create PCM\n");
snd_als300_free(chip);
return err;
}
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
chip, &ops)) < 0) {
snd_als300_free(chip);
return err;
}
*rchip = chip;
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int snd_als300_suspend(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
struct snd_card *card = dev_get_drvdata(dev);
struct snd_als300 *chip = card->private_data;
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
snd_pcm_suspend_all(chip->pcm);
snd_ac97_suspend(chip->ac97);
pci_disable_device(pci);
pci_save_state(pci);
pci_set_power_state(pci, PCI_D3hot);
return 0;
}
static int snd_als300_resume(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
struct snd_card *card = dev_get_drvdata(dev);
struct snd_als300 *chip = card->private_data;
pci_set_power_state(pci, PCI_D0);
pci_restore_state(pci);
if (pci_enable_device(pci) < 0) {
dev_err(dev, "pci_enable_device failed, disabling device\n");
snd_card_disconnect(card);
return -EIO;
}
pci_set_master(pci);
snd_als300_init(chip);
snd_ac97_resume(chip->ac97);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
}
static SIMPLE_DEV_PM_OPS(snd_als300_pm, snd_als300_suspend, snd_als300_resume);
#define SND_ALS300_PM_OPS &snd_als300_pm
#else
#define SND_ALS300_PM_OPS NULL
#endif
static int snd_als300_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
struct snd_card *card;
struct snd_als300 *chip;
int err, chip_type;
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0)
return err;
chip_type = pci_id->driver_data;
if ((err = snd_als300_create(card, pci, chip_type, &chip)) < 0) {
snd_card_free(card);
return err;
}
card->private_data = chip;
strcpy(card->driver, "ALS300");
if (chip->chip_type == DEVICE_ALS300_PLUS)
/* don't know much about ALS300+ yet
* print revision number for now */
sprintf(card->shortname, "ALS300+ (Rev. %d)", chip->revision);
else
sprintf(card->shortname, "ALS300 (Rev. %c)", 'A' +
chip->revision - 1);
sprintf(card->longname, "%s at 0x%lx irq %i",
card->shortname, chip->port, chip->irq);
if ((err = snd_card_register(card)) < 0) {
snd_card_free(card);
return err;
}
pci_set_drvdata(pci, card);
dev++;
return 0;
}
static struct pci_driver als300_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_als300_ids,
.probe = snd_als300_probe,
.remove = snd_als300_remove,
.driver = {
.pm = SND_ALS300_PM_OPS,
},
};
module_pci_driver(als300_driver);

1052
sound/pci/als4000.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
snd-asihpi-objs := asihpi.o hpioctl.o hpimsginit.o\
hpicmn.o hpifunc.o hpidebug.o hpidspcd.o\
hpios.o hpi6000.o hpi6205.o hpimsgx.o
obj-$(CONFIG_SND_ASIHPI) += snd-asihpi.o

2995
sound/pci/asihpi/asihpi.c Normal file

File diff suppressed because it is too large Load diff

1727
sound/pci/asihpi/hpi.h Normal file

File diff suppressed because it is too large Load diff

1809
sound/pci/asihpi/hpi6000.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,70 @@
/*****************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
Public declarations for DSP Proramming Interface to TI C6701
Shared between hpi6000.c and DSP code
(C) Copyright AudioScience Inc. 1998-2003
******************************************************************************/
#ifndef _HPI6000_H_
#define _HPI6000_H_
#define HPI_NMIXER_CONTROLS 200
/*
* Control caching is always supported in the HPI code.
* The DSP should make sure that dwControlCacheSizeInBytes is initialized to 0
* during boot to make it in-active.
*/
struct hpi_hif_6000 {
u32 host_cmd;
u32 dsp_ack;
u32 address;
u32 length;
u32 message_buffer_address;
u32 response_buffer_address;
u32 dsp_number;
u32 adapter_info;
u32 control_cache_is_dirty;
u32 control_cache_address;
u32 control_cache_size_in_bytes;
u32 control_cache_count;
};
#define HPI_HIF_PACK_ADAPTER_INFO(adapter, version_major, version_minor) \
((adapter << 16) | (version_major << 8) | version_minor)
#define HPI_HIF_ADAPTER_INFO_EXTRACT_ADAPTER(adapterinfo) \
((adapterinfo >> 16) & 0xffff)
#define HPI_HIF_ADAPTER_INFO_EXTRACT_HWVERSION_MAJOR(adapterinfo) \
((adapterinfo >> 8) & 0xff)
#define HPI_HIF_ADAPTER_INFO_EXTRACT_HWVERSION_MINOR(adapterinfo) \
(adapterinfo & 0xff)
/* Command/status exchanged between host and DSP */
#define HPI_HIF_IDLE 0
#define HPI_HIF_SEND_MSG 1
#define HPI_HIF_GET_RESP 2
#define HPI_HIF_DATA_MASK 0x10
#define HPI_HIF_SEND_DATA 0x13
#define HPI_HIF_GET_DATA 0x14
#define HPI_HIF_SEND_DONE 5
#define HPI_HIF_RESET 9
#endif /* _HPI6000_H_ */

2208
sound/pci/asihpi/hpi6205.c Normal file

File diff suppressed because it is too large Load diff

103
sound/pci/asihpi/hpi6205.h Normal file
View file

@ -0,0 +1,103 @@
/*****************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
Host Interface module for an ASI6205 based
bus mastering PCI adapter.
Copyright AudioScience, Inc., 2003
******************************************************************************/
#ifndef _HPI6205_H_
#define _HPI6205_H_
#include "hpi_internal.h"
/***********************************************************
Defines used for basic messaging
************************************************************/
#define H620_HIF_RESET 0
#define H620_HIF_IDLE 1
#define H620_HIF_GET_RESP 2
#define H620_HIF_DATA_DONE 3
#define H620_HIF_DATA_MASK 0x10
#define H620_HIF_SEND_DATA 0x14
#define H620_HIF_GET_DATA 0x15
#define H620_HIF_UNKNOWN 0x0000ffff
/***********************************************************
Types used for mixer control caching
************************************************************/
#define H620_MAX_ISTREAMS 32
#define H620_MAX_OSTREAMS 32
#define HPI_NMIXER_CONTROLS 2048
/*********************************************************************
This is used for dynamic control cache allocation
**********************************************************************/
struct controlcache_6205 {
u32 number_of_controls;
u32 physical_address32;
u32 size_in_bytes;
};
/*********************************************************************
This is used for dynamic allocation of async event array
**********************************************************************/
struct async_event_buffer_6205 {
u32 physical_address32;
u32 spare;
struct hpi_fifo_buffer b;
};
/***********************************************************
The Host located memory buffer that the 6205 will bus master
in and out of.
************************************************************/
#define HPI6205_SIZEOF_DATA (16*1024)
struct message_buffer_6205 {
struct hpi_message message;
char data[256];
};
struct response_buffer_6205 {
struct hpi_response response;
char data[256];
};
union buffer_6205 {
struct message_buffer_6205 message_buffer;
struct response_buffer_6205 response_buffer;
u8 b_data[HPI6205_SIZEOF_DATA];
};
struct bus_master_interface {
u32 host_cmd;
u32 dsp_ack;
u32 transfer_size_in_bytes;
union buffer_6205 u;
struct controlcache_6205 control_cache;
struct async_event_buffer_6205 async_buffer;
struct hpi_hostbuffer_status
instream_host_buffer_status[H620_MAX_ISTREAMS];
struct hpi_hostbuffer_status
outstream_host_buffer_status[H620_MAX_OSTREAMS];
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
/** HPI Version Definitions
Development releases have odd minor version.
Production releases have even minor version.
\file hpi_version.h
*/
#ifndef _HPI_VERSION_H
#define _HPI_VERSION_H
/* Use single digits for versions less that 10 to avoid octal. */
/* *** HPI_VER is the only edit required to update version *** */
/** HPI version */
#define HPI_VER HPI_VERSION_CONSTRUCTOR(4, 10, 1)
/** HPI version string in dotted decimal format */
#define HPI_VER_STRING "4.10.01"
/** Library version as documented in hpi-api-versions.txt */
#define HPI_LIB_VER HPI_VERSION_CONSTRUCTOR(10, 2, 0)
/** Construct hpi version number from major, minor, release numbers */
#define HPI_VERSION_CONSTRUCTOR(maj, min, r) ((maj << 16) + (min << 8) + r)
/** Extract major version from hpi version number */
#define HPI_VER_MAJOR(v) ((int)(v >> 16))
/** Extract minor version from hpi version number */
#define HPI_VER_MINOR(v) ((int)((v >> 8) & 0xFF))
/** Extract release from hpi version number */
#define HPI_VER_RELEASE(v) ((int)(v & 0xFF))
#endif

703
sound/pci/asihpi/hpicmn.c Normal file
View file

@ -0,0 +1,703 @@
/******************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
\file hpicmn.c
Common functions used by hpixxxx.c modules
(C) Copyright AudioScience Inc. 1998-2003
*******************************************************************************/
#define SOURCEFILE_NAME "hpicmn.c"
#include "hpi_internal.h"
#include "hpidebug.h"
#include "hpimsginit.h"
#include "hpicmn.h"
struct hpi_adapters_list {
struct hpios_spinlock list_lock;
struct hpi_adapter_obj adapter[HPI_MAX_ADAPTERS];
u16 gw_num_adapters;
};
static struct hpi_adapters_list adapters;
/**
* Given an HPI Message that was sent out and a response that was received,
* validate that the response has the correct fields filled in,
* i.e ObjectType, Function etc
**/
u16 hpi_validate_response(struct hpi_message *phm, struct hpi_response *phr)
{
if (phr->type != HPI_TYPE_RESPONSE) {
HPI_DEBUG_LOG(ERROR, "header type %d invalid\n", phr->type);
return HPI_ERROR_INVALID_RESPONSE;
}
if (phr->object != phm->object) {
HPI_DEBUG_LOG(ERROR, "header object %d invalid\n",
phr->object);
return HPI_ERROR_INVALID_RESPONSE;
}
if (phr->function != phm->function) {
HPI_DEBUG_LOG(ERROR, "header function %d invalid\n",
phr->function);
return HPI_ERROR_INVALID_RESPONSE;
}
return 0;
}
u16 hpi_add_adapter(struct hpi_adapter_obj *pao)
{
u16 retval = 0;
/*HPI_ASSERT(pao->type); */
hpios_alistlock_lock(&adapters);
if (pao->index >= HPI_MAX_ADAPTERS) {
retval = HPI_ERROR_BAD_ADAPTER_NUMBER;
goto unlock;
}
if (adapters.adapter[pao->index].type) {
int a;
for (a = HPI_MAX_ADAPTERS - 1; a >= 0; a--) {
if (!adapters.adapter[a].type) {
HPI_DEBUG_LOG(WARNING,
"ASI%X duplicate index %d moved to %d\n",
pao->type, pao->index, a);
pao->index = a;
break;
}
}
if (a < 0) {
retval = HPI_ERROR_DUPLICATE_ADAPTER_NUMBER;
goto unlock;
}
}
adapters.adapter[pao->index] = *pao;
hpios_dsplock_init(&adapters.adapter[pao->index]);
adapters.gw_num_adapters++;
unlock:
hpios_alistlock_unlock(&adapters);
return retval;
}
void hpi_delete_adapter(struct hpi_adapter_obj *pao)
{
if (!pao->type) {
HPI_DEBUG_LOG(ERROR, "removing null adapter?\n");
return;
}
hpios_alistlock_lock(&adapters);
if (adapters.adapter[pao->index].type)
adapters.gw_num_adapters--;
memset(&adapters.adapter[pao->index], 0, sizeof(adapters.adapter[0]));
hpios_alistlock_unlock(&adapters);
}
/**
* FindAdapter returns a pointer to the struct hpi_adapter_obj with
* index wAdapterIndex in an HPI_ADAPTERS_LIST structure.
*
*/
struct hpi_adapter_obj *hpi_find_adapter(u16 adapter_index)
{
struct hpi_adapter_obj *pao = NULL;
if (adapter_index >= HPI_MAX_ADAPTERS) {
HPI_DEBUG_LOG(VERBOSE, "find_adapter invalid index %d\n",
adapter_index);
return NULL;
}
pao = &adapters.adapter[adapter_index];
if (pao->type != 0) {
/*
HPI_DEBUG_LOG(VERBOSE, "Found adapter index %d\n",
wAdapterIndex);
*/
return pao;
} else {
/*
HPI_DEBUG_LOG(VERBOSE, "No adapter index %d\n",
wAdapterIndex);
*/
return NULL;
}
}
/**
*
* wipe an HPI_ADAPTERS_LIST structure.
*
**/
static void wipe_adapter_list(void)
{
memset(&adapters, 0, sizeof(adapters));
}
static void subsys_get_adapter(struct hpi_message *phm,
struct hpi_response *phr)
{
int count = phm->obj_index;
u16 index = 0;
/* find the nCount'th nonzero adapter in array */
for (index = 0; index < HPI_MAX_ADAPTERS; index++) {
if (adapters.adapter[index].type) {
if (!count)
break;
count--;
}
}
if (index < HPI_MAX_ADAPTERS) {
phr->u.s.adapter_index = adapters.adapter[index].index;
phr->u.s.adapter_type = adapters.adapter[index].type;
} else {
phr->u.s.adapter_index = 0;
phr->u.s.adapter_type = 0;
phr->error = HPI_ERROR_INVALID_OBJ_INDEX;
}
}
static unsigned int control_cache_alloc_check(struct hpi_control_cache *pC)
{
unsigned int i;
int cached = 0;
if (!pC)
return 0;
if (pC->init)
return pC->init;
if (!pC->p_cache)
return 0;
if (pC->control_count && pC->cache_size_in_bytes) {
char *p_master_cache;
unsigned int byte_count = 0;
p_master_cache = (char *)pC->p_cache;
HPI_DEBUG_LOG(DEBUG, "check %d controls\n",
pC->control_count);
for (i = 0; i < pC->control_count; i++) {
struct hpi_control_cache_info *info =
(struct hpi_control_cache_info *)
&p_master_cache[byte_count];
if (!info->size_in32bit_words) {
if (!i) {
HPI_DEBUG_LOG(INFO,
"adap %d cache not ready?\n",
pC->adap_idx);
return 0;
}
/* The cache is invalid.
* Minimum valid entry size is
* sizeof(struct hpi_control_cache_info)
*/
HPI_DEBUG_LOG(ERROR,
"adap %d zero size cache entry %d\n",
pC->adap_idx, i);
break;
}
if (info->control_type) {
pC->p_info[info->control_index] = info;
cached++;
} else { /* dummy cache entry */
pC->p_info[info->control_index] = NULL;
}
byte_count += info->size_in32bit_words * 4;
HPI_DEBUG_LOG(VERBOSE,
"cached %d, pinfo %p index %d type %d size %d\n",
cached, pC->p_info[info->control_index],
info->control_index, info->control_type,
info->size_in32bit_words);
/* quit loop early if whole cache has been scanned.
* dwControlCount is the maximum possible entries
* but some may be absent from the cache
*/
if (byte_count >= pC->cache_size_in_bytes)
break;
/* have seen last control index */
if (info->control_index == pC->control_count - 1)
break;
}
if (byte_count != pC->cache_size_in_bytes)
HPI_DEBUG_LOG(WARNING,
"adap %d bytecount %d != cache size %d\n",
pC->adap_idx, byte_count,
pC->cache_size_in_bytes);
else
HPI_DEBUG_LOG(DEBUG,
"adap %d cache good, bytecount == cache size = %d\n",
pC->adap_idx, byte_count);
pC->init = (u16)cached;
}
return pC->init;
}
/** Find a control.
*/
static short find_control(u16 control_index,
struct hpi_control_cache *p_cache, struct hpi_control_cache_info **pI)
{
if (!control_cache_alloc_check(p_cache)) {
HPI_DEBUG_LOG(VERBOSE,
"control_cache_alloc_check() failed %d\n",
control_index);
return 0;
}
*pI = p_cache->p_info[control_index];
if (!*pI) {
HPI_DEBUG_LOG(VERBOSE, "Uncached Control %d\n",
control_index);
return 0;
} else {
HPI_DEBUG_LOG(VERBOSE, "find_control() type %d\n",
(*pI)->control_type);
}
return 1;
}
/* allow unified treatment of several string fields within struct */
#define HPICMN_PAD_OFS_AND_SIZE(m) {\
offsetof(struct hpi_control_cache_pad, m), \
sizeof(((struct hpi_control_cache_pad *)(NULL))->m) }
struct pad_ofs_size {
unsigned int offset;
unsigned int field_size;
};
static const struct pad_ofs_size pad_desc[] = {
HPICMN_PAD_OFS_AND_SIZE(c_channel), /* HPI_PAD_CHANNEL_NAME */
HPICMN_PAD_OFS_AND_SIZE(c_artist), /* HPI_PAD_ARTIST */
HPICMN_PAD_OFS_AND_SIZE(c_title), /* HPI_PAD_TITLE */
HPICMN_PAD_OFS_AND_SIZE(c_comment), /* HPI_PAD_COMMENT */
};
/** CheckControlCache checks the cache and fills the struct hpi_response
* accordingly. It returns one if a cache hit occurred, zero otherwise.
*/
short hpi_check_control_cache(struct hpi_control_cache *p_cache,
struct hpi_message *phm, struct hpi_response *phr)
{
short found = 1;
struct hpi_control_cache_info *pI;
struct hpi_control_cache_single *pC;
size_t response_size;
if (!find_control(phm->obj_index, p_cache, &pI)) {
HPI_DEBUG_LOG(VERBOSE,
"HPICMN find_control() failed for adap %d\n",
phm->adapter_index);
return 0;
}
phr->error = 0;
phr->specific_error = 0;
phr->version = 0;
/* set the default response size */
response_size =
sizeof(struct hpi_response_header) +
sizeof(struct hpi_control_res);
/* pC is the default cached control strucure. May be cast to
something else in the following switch statement.
*/
pC = (struct hpi_control_cache_single *)pI;
switch (pI->control_type) {
case HPI_CONTROL_METER:
if (phm->u.c.attribute == HPI_METER_PEAK) {
phr->u.c.an_log_value[0] = pC->u.meter.an_log_peak[0];
phr->u.c.an_log_value[1] = pC->u.meter.an_log_peak[1];
} else if (phm->u.c.attribute == HPI_METER_RMS) {
if (pC->u.meter.an_logRMS[0] ==
HPI_CACHE_INVALID_SHORT) {
phr->error =
HPI_ERROR_INVALID_CONTROL_ATTRIBUTE;
phr->u.c.an_log_value[0] = HPI_METER_MINIMUM;
phr->u.c.an_log_value[1] = HPI_METER_MINIMUM;
} else {
phr->u.c.an_log_value[0] =
pC->u.meter.an_logRMS[0];
phr->u.c.an_log_value[1] =
pC->u.meter.an_logRMS[1];
}
} else
found = 0;
break;
case HPI_CONTROL_VOLUME:
if (phm->u.c.attribute == HPI_VOLUME_GAIN) {
phr->u.c.an_log_value[0] = pC->u.vol.an_log[0];
phr->u.c.an_log_value[1] = pC->u.vol.an_log[1];
} else if (phm->u.c.attribute == HPI_VOLUME_MUTE) {
if (pC->u.vol.flags & HPI_VOLUME_FLAG_HAS_MUTE) {
if (pC->u.vol.flags & HPI_VOLUME_FLAG_MUTED)
phr->u.c.param1 =
HPI_BITMASK_ALL_CHANNELS;
else
phr->u.c.param1 = 0;
} else {
phr->error =
HPI_ERROR_INVALID_CONTROL_ATTRIBUTE;
phr->u.c.param1 = 0;
}
} else {
found = 0;
}
break;
case HPI_CONTROL_MULTIPLEXER:
if (phm->u.c.attribute == HPI_MULTIPLEXER_SOURCE) {
phr->u.c.param1 = pC->u.mux.source_node_type;
phr->u.c.param2 = pC->u.mux.source_node_index;
} else {
found = 0;
}
break;
case HPI_CONTROL_CHANNEL_MODE:
if (phm->u.c.attribute == HPI_CHANNEL_MODE_MODE)
phr->u.c.param1 = pC->u.mode.mode;
else
found = 0;
break;
case HPI_CONTROL_LEVEL:
if (phm->u.c.attribute == HPI_LEVEL_GAIN) {
phr->u.c.an_log_value[0] = pC->u.level.an_log[0];
phr->u.c.an_log_value[1] = pC->u.level.an_log[1];
} else
found = 0;
break;
case HPI_CONTROL_TUNER:
if (phm->u.c.attribute == HPI_TUNER_FREQ)
phr->u.c.param1 = pC->u.tuner.freq_ink_hz;
else if (phm->u.c.attribute == HPI_TUNER_BAND)
phr->u.c.param1 = pC->u.tuner.band;
else if (phm->u.c.attribute == HPI_TUNER_LEVEL_AVG)
if (pC->u.tuner.s_level_avg ==
HPI_CACHE_INVALID_SHORT) {
phr->u.cu.tuner.s_level = 0;
phr->error =
HPI_ERROR_INVALID_CONTROL_ATTRIBUTE;
} else
phr->u.cu.tuner.s_level =
pC->u.tuner.s_level_avg;
else
found = 0;
break;
case HPI_CONTROL_AESEBU_RECEIVER:
if (phm->u.c.attribute == HPI_AESEBURX_ERRORSTATUS)
phr->u.c.param1 = pC->u.aes3rx.error_status;
else if (phm->u.c.attribute == HPI_AESEBURX_FORMAT)
phr->u.c.param1 = pC->u.aes3rx.format;
else
found = 0;
break;
case HPI_CONTROL_AESEBU_TRANSMITTER:
if (phm->u.c.attribute == HPI_AESEBUTX_FORMAT)
phr->u.c.param1 = pC->u.aes3tx.format;
else
found = 0;
break;
case HPI_CONTROL_TONEDETECTOR:
if (phm->u.c.attribute == HPI_TONEDETECTOR_STATE)
phr->u.c.param1 = pC->u.tone.state;
else
found = 0;
break;
case HPI_CONTROL_SILENCEDETECTOR:
if (phm->u.c.attribute == HPI_SILENCEDETECTOR_STATE) {
phr->u.c.param1 = pC->u.silence.state;
} else
found = 0;
break;
case HPI_CONTROL_MICROPHONE:
if (phm->u.c.attribute == HPI_MICROPHONE_PHANTOM_POWER)
phr->u.c.param1 = pC->u.microphone.phantom_state;
else
found = 0;
break;
case HPI_CONTROL_SAMPLECLOCK:
if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE)
phr->u.c.param1 = pC->u.clk.source;
else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE_INDEX) {
if (pC->u.clk.source_index ==
HPI_CACHE_INVALID_UINT16) {
phr->u.c.param1 = 0;
phr->error =
HPI_ERROR_INVALID_CONTROL_ATTRIBUTE;
} else
phr->u.c.param1 = pC->u.clk.source_index;
} else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SAMPLERATE)
phr->u.c.param1 = pC->u.clk.sample_rate;
else
found = 0;
break;
case HPI_CONTROL_PAD:{
struct hpi_control_cache_pad *p_pad;
p_pad = (struct hpi_control_cache_pad *)pI;
if (!(p_pad->field_valid_flags & (1 <<
HPI_CTL_ATTR_INDEX(phm->u.c.
attribute)))) {
phr->error =
HPI_ERROR_INVALID_CONTROL_ATTRIBUTE;
break;
}
if (phm->u.c.attribute == HPI_PAD_PROGRAM_ID)
phr->u.c.param1 = p_pad->pI;
else if (phm->u.c.attribute == HPI_PAD_PROGRAM_TYPE)
phr->u.c.param1 = p_pad->pTY;
else {
unsigned int index =
HPI_CTL_ATTR_INDEX(phm->u.c.
attribute) - 1;
unsigned int offset = phm->u.c.param1;
unsigned int pad_string_len, field_size;
char *pad_string;
unsigned int tocopy;
if (index > ARRAY_SIZE(pad_desc) - 1) {
phr->error =
HPI_ERROR_INVALID_CONTROL_ATTRIBUTE;
break;
}
pad_string =
((char *)p_pad) +
pad_desc[index].offset;
field_size = pad_desc[index].field_size;
/* Ensure null terminator */
pad_string[field_size - 1] = 0;
pad_string_len = strlen(pad_string) + 1;
if (offset > pad_string_len) {
phr->error =
HPI_ERROR_INVALID_CONTROL_VALUE;
break;
}
tocopy = pad_string_len - offset;
if (tocopy > sizeof(phr->u.cu.chars8.sz_data))
tocopy = sizeof(phr->u.cu.chars8.
sz_data);
memcpy(phr->u.cu.chars8.sz_data,
&pad_string[offset], tocopy);
phr->u.cu.chars8.remaining_chars =
pad_string_len - offset - tocopy;
}
}
break;
default:
found = 0;
break;
}
HPI_DEBUG_LOG(VERBOSE, "%s Adap %d, Ctl %d, Type %d, Attr %d\n",
found ? "Cached" : "Uncached", phm->adapter_index,
pI->control_index, pI->control_type, phm->u.c.attribute);
if (found) {
phr->size = (u16)response_size;
phr->type = HPI_TYPE_RESPONSE;
phr->object = phm->object;
phr->function = phm->function;
}
return found;
}
/** Updates the cache with Set values.
Only update if no error.
Volume and Level return the limited values in the response, so use these
Multiplexer does so use sent values
*/
void hpi_cmn_control_cache_sync_to_msg(struct hpi_control_cache *p_cache,
struct hpi_message *phm, struct hpi_response *phr)
{
struct hpi_control_cache_single *pC;
struct hpi_control_cache_info *pI;
if (phr->error)
return;
if (!find_control(phm->obj_index, p_cache, &pI)) {
HPI_DEBUG_LOG(VERBOSE,
"HPICMN find_control() failed for adap %d\n",
phm->adapter_index);
return;
}
/* pC is the default cached control strucure.
May be cast to something else in the following switch statement.
*/
pC = (struct hpi_control_cache_single *)pI;
switch (pI->control_type) {
case HPI_CONTROL_VOLUME:
if (phm->u.c.attribute == HPI_VOLUME_GAIN) {
pC->u.vol.an_log[0] = phr->u.c.an_log_value[0];
pC->u.vol.an_log[1] = phr->u.c.an_log_value[1];
} else if (phm->u.c.attribute == HPI_VOLUME_MUTE) {
if (phm->u.c.param1)
pC->u.vol.flags |= HPI_VOLUME_FLAG_MUTED;
else
pC->u.vol.flags &= ~HPI_VOLUME_FLAG_MUTED;
}
break;
case HPI_CONTROL_MULTIPLEXER:
/* mux does not return its setting on Set command. */
if (phm->u.c.attribute == HPI_MULTIPLEXER_SOURCE) {
pC->u.mux.source_node_type = (u16)phm->u.c.param1;
pC->u.mux.source_node_index = (u16)phm->u.c.param2;
}
break;
case HPI_CONTROL_CHANNEL_MODE:
/* mode does not return its setting on Set command. */
if (phm->u.c.attribute == HPI_CHANNEL_MODE_MODE)
pC->u.mode.mode = (u16)phm->u.c.param1;
break;
case HPI_CONTROL_LEVEL:
if (phm->u.c.attribute == HPI_LEVEL_GAIN) {
pC->u.vol.an_log[0] = phr->u.c.an_log_value[0];
pC->u.vol.an_log[1] = phr->u.c.an_log_value[1];
}
break;
case HPI_CONTROL_MICROPHONE:
if (phm->u.c.attribute == HPI_MICROPHONE_PHANTOM_POWER)
pC->u.microphone.phantom_state = (u16)phm->u.c.param1;
break;
case HPI_CONTROL_AESEBU_TRANSMITTER:
if (phm->u.c.attribute == HPI_AESEBUTX_FORMAT)
pC->u.aes3tx.format = phm->u.c.param1;
break;
case HPI_CONTROL_AESEBU_RECEIVER:
if (phm->u.c.attribute == HPI_AESEBURX_FORMAT)
pC->u.aes3rx.format = phm->u.c.param1;
break;
case HPI_CONTROL_SAMPLECLOCK:
if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE)
pC->u.clk.source = (u16)phm->u.c.param1;
else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE_INDEX)
pC->u.clk.source_index = (u16)phm->u.c.param1;
else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SAMPLERATE)
pC->u.clk.sample_rate = phm->u.c.param1;
break;
default:
break;
}
}
/** Allocate control cache.
\return Cache pointer, or NULL if allocation fails.
*/
struct hpi_control_cache *hpi_alloc_control_cache(const u32 control_count,
const u32 size_in_bytes, u8 *p_dsp_control_buffer)
{
struct hpi_control_cache *p_cache =
kmalloc(sizeof(*p_cache), GFP_KERNEL);
if (!p_cache)
return NULL;
p_cache->p_info = kcalloc(control_count, sizeof(*p_cache->p_info),
GFP_KERNEL);
if (!p_cache->p_info) {
kfree(p_cache);
return NULL;
}
p_cache->cache_size_in_bytes = size_in_bytes;
p_cache->control_count = control_count;
p_cache->p_cache = p_dsp_control_buffer;
p_cache->init = 0;
return p_cache;
}
void hpi_free_control_cache(struct hpi_control_cache *p_cache)
{
if (p_cache) {
kfree(p_cache->p_info);
kfree(p_cache);
}
}
static void subsys_message(struct hpi_message *phm, struct hpi_response *phr)
{
hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, phm->function, 0);
switch (phm->function) {
case HPI_SUBSYS_OPEN:
case HPI_SUBSYS_CLOSE:
case HPI_SUBSYS_DRIVER_UNLOAD:
break;
case HPI_SUBSYS_DRIVER_LOAD:
wipe_adapter_list();
hpios_alistlock_init(&adapters);
break;
case HPI_SUBSYS_GET_ADAPTER:
subsys_get_adapter(phm, phr);
break;
case HPI_SUBSYS_GET_NUM_ADAPTERS:
phr->u.s.num_adapters = adapters.gw_num_adapters;
break;
case HPI_SUBSYS_CREATE_ADAPTER:
break;
default:
phr->error = HPI_ERROR_INVALID_FUNC;
break;
}
}
void HPI_COMMON(struct hpi_message *phm, struct hpi_response *phr)
{
switch (phm->type) {
case HPI_TYPE_REQUEST:
switch (phm->object) {
case HPI_OBJ_SUBSYSTEM:
subsys_message(phm, phr);
break;
}
break;
default:
phr->error = HPI_ERROR_INVALID_TYPE;
break;
}
}

67
sound/pci/asihpi/hpicmn.h Normal file
View file

@ -0,0 +1,67 @@
/**
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
*/
struct hpi_adapter_obj;
/* a function that takes an adapter obj and returns an int */
typedef int adapter_int_func(struct hpi_adapter_obj *pao);
struct hpi_adapter_obj {
struct hpi_pci pci; /* PCI info - bus#,dev#,address etc */
u16 type; /* 0x6644 == ASI6644 etc */
u16 index;
struct hpios_spinlock dsp_lock;
u16 dsp_crashed;
u16 has_control_cache;
void *priv;
};
struct hpi_control_cache {
/** indicates whether the structures are initialized */
u16 init;
u16 adap_idx;
u32 control_count;
u32 cache_size_in_bytes;
/** pointer to allocated memory of lookup pointers. */
struct hpi_control_cache_info **p_info;
/** pointer to DSP's control cache. */
u8 *p_cache;
};
struct hpi_adapter_obj *hpi_find_adapter(u16 adapter_index);
u16 hpi_add_adapter(struct hpi_adapter_obj *pao);
void hpi_delete_adapter(struct hpi_adapter_obj *pao);
short hpi_check_control_cache(struct hpi_control_cache *pC,
struct hpi_message *phm, struct hpi_response *phr);
struct hpi_control_cache *hpi_alloc_control_cache(const u32
number_of_controls, const u32 size_in_bytes, u8 *pDSP_control_buffer);
void hpi_free_control_cache(struct hpi_control_cache *p_cache);
void hpi_cmn_control_cache_sync_to_msg(struct hpi_control_cache *pC,
struct hpi_message *phm, struct hpi_response *phr);
u16 hpi_validate_response(struct hpi_message *phm, struct hpi_response *phr);
hpi_handler_func HPI_COMMON;

View file

@ -0,0 +1,78 @@
/************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
Debug macro translation.
************************************************************************/
#include "hpi_internal.h"
#include "hpidebug.h"
/* Debug level; 0 quiet; 1 informative, 2 debug, 3 verbose debug. */
int hpi_debug_level = HPI_DEBUG_LEVEL_DEFAULT;
void hpi_debug_init(void)
{
printk(KERN_INFO "debug start\n");
}
int hpi_debug_level_set(int level)
{
int old_level;
old_level = hpi_debug_level;
hpi_debug_level = level;
return old_level;
}
int hpi_debug_level_get(void)
{
return hpi_debug_level;
}
void hpi_debug_message(struct hpi_message *phm, char *sz_fileline)
{
if (phm) {
printk(KERN_DEBUG "HPI_MSG%d,%d,%d,%d,%d\n", phm->version,
phm->adapter_index, phm->obj_index, phm->function,
phm->u.c.attribute);
}
}
void hpi_debug_data(u16 *pdata, u32 len)
{
u32 i;
int j;
int k;
int lines;
int cols = 8;
lines = (len + cols - 1) / cols;
if (lines > 8)
lines = 8;
for (i = 0, j = 0; j < lines; j++) {
printk(KERN_DEBUG "%p:", (pdata + i));
for (k = 0; k < cols && i < len; i++, k++)
printk("%s%04x", k == 0 ? "" : " ", pdata[i]);
printk("\n");
}
}

102
sound/pci/asihpi/hpidebug.h Normal file
View file

@ -0,0 +1,102 @@
/*****************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
Debug macros.
*****************************************************************************/
#ifndef _HPIDEBUG_H
#define _HPIDEBUG_H
#include "hpi_internal.h"
/* Define debugging levels. */
enum { HPI_DEBUG_LEVEL_ERROR = 0, /* always log errors */
HPI_DEBUG_LEVEL_WARNING = 1,
HPI_DEBUG_LEVEL_NOTICE = 2,
HPI_DEBUG_LEVEL_INFO = 3,
HPI_DEBUG_LEVEL_DEBUG = 4,
HPI_DEBUG_LEVEL_VERBOSE = 5 /* same printk level as DEBUG */
};
#define HPI_DEBUG_LEVEL_DEFAULT HPI_DEBUG_LEVEL_NOTICE
/* an OS can define an extra flag string that is appended to
the start of each message, eg see linux kernel hpios.h */
#ifdef SOURCEFILE_NAME
#define FILE_LINE SOURCEFILE_NAME ":" __stringify(__LINE__) " "
#else
#define FILE_LINE __FILE__ ":" __stringify(__LINE__) " "
#endif
#define HPI_DEBUG_ASSERT(expression) \
do { \
if (!(expression)) { \
printk(KERN_ERR FILE_LINE \
"ASSERT " __stringify(expression)); \
} \
} while (0)
#define HPI_DEBUG_LOG(level, ...) \
do { \
if (hpi_debug_level >= HPI_DEBUG_LEVEL_##level) { \
printk(HPI_DEBUG_FLAG_##level \
FILE_LINE __VA_ARGS__); \
} \
} while (0)
void hpi_debug_init(void);
int hpi_debug_level_set(int level);
int hpi_debug_level_get(void);
/* needed by Linux driver for dynamic debug level changes */
extern int hpi_debug_level;
void hpi_debug_message(struct hpi_message *phm, char *sz_fileline);
void hpi_debug_data(u16 *pdata, u32 len);
#define HPI_DEBUG_DATA(pdata, len) \
do { \
if (hpi_debug_level >= HPI_DEBUG_LEVEL_VERBOSE) \
hpi_debug_data(pdata, len); \
} while (0)
#define HPI_DEBUG_MESSAGE(level, phm) \
do { \
if (hpi_debug_level >= HPI_DEBUG_LEVEL_##level) { \
hpi_debug_message(phm, HPI_DEBUG_FLAG_##level \
FILE_LINE __stringify(level)); \
} \
} while (0)
#define HPI_DEBUG_RESPONSE(phr) \
do { \
if (((hpi_debug_level >= HPI_DEBUG_LEVEL_DEBUG) && \
(phr->error)) ||\
(hpi_debug_level >= HPI_DEBUG_LEVEL_VERBOSE)) \
printk(KERN_DEBUG "HPI_RES%d,%d,%d\n", \
phr->version, phr->error, phr->specific_error); \
} while (0)
#ifndef compile_time_assert
#define compile_time_assert(cond, msg) \
typedef char msg[(cond) ? 1 : -1]
#endif
#endif /* _HPIDEBUG_H_ */

144
sound/pci/asihpi/hpidspcd.c Normal file
View file

@ -0,0 +1,144 @@
/***********************************************************************/
/**
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
\file
Functions for reading DSP code using
hotplug firmware loader from individual dsp code files
*/
/***********************************************************************/
#define SOURCEFILE_NAME "hpidspcd.c"
#include "hpidspcd.h"
#include "hpidebug.h"
#include "hpi_version.h"
struct dsp_code_private {
/** Firmware descriptor */
const struct firmware *firmware;
struct pci_dev *dev;
};
/*-------------------------------------------------------------------*/
short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code,
u32 *os_error_code)
{
const struct firmware *firmware;
struct pci_dev *dev = os_data;
struct code_header header;
char fw_name[20];
short err_ret = HPI_ERROR_DSP_FILE_NOT_FOUND;
int err;
sprintf(fw_name, "asihpi/dsp%04x.bin", adapter);
err = request_firmware(&firmware, fw_name, &dev->dev);
if (err || !firmware) {
dev_err(&dev->dev, "%d, request_firmware failed for %s\n",
err, fw_name);
goto error1;
}
if (firmware->size < sizeof(header)) {
dev_err(&dev->dev, "Header size too small %s\n", fw_name);
goto error2;
}
memcpy(&header, firmware->data, sizeof(header));
if ((header.type != 0x45444F43) || /* "CODE" */
(header.adapter != adapter)
|| (header.size != firmware->size)) {
dev_err(&dev->dev,
"Invalid firmware header size %d != file %zd\n",
header.size, firmware->size);
goto error2;
}
if ((header.version >> 9) != (HPI_VER >> 9)) {
/* Consider even and subsequent odd minor versions to be compatible */
dev_err(&dev->dev, "Incompatible firmware version DSP image %X != Driver %X\n",
header.version, HPI_VER);
goto error2;
}
if (header.version != HPI_VER) {
dev_info(&dev->dev,
"Firmware: release version mismatch DSP image %X != Driver %X\n",
header.version, HPI_VER);
}
HPI_DEBUG_LOG(DEBUG, "dsp code %s opened\n", fw_name);
dsp_code->pvt = kmalloc(sizeof(*dsp_code->pvt), GFP_KERNEL);
if (!dsp_code->pvt) {
err_ret = HPI_ERROR_MEMORY_ALLOC;
goto error2;
}
dsp_code->pvt->dev = dev;
dsp_code->pvt->firmware = firmware;
dsp_code->header = header;
dsp_code->block_length = header.size / sizeof(u32);
dsp_code->word_count = sizeof(header) / sizeof(u32);
return 0;
error2:
release_firmware(firmware);
error1:
dsp_code->block_length = 0;
return err_ret;
}
/*-------------------------------------------------------------------*/
void hpi_dsp_code_close(struct dsp_code *dsp_code)
{
HPI_DEBUG_LOG(DEBUG, "dsp code closed\n");
release_firmware(dsp_code->pvt->firmware);
kfree(dsp_code->pvt);
}
/*-------------------------------------------------------------------*/
void hpi_dsp_code_rewind(struct dsp_code *dsp_code)
{
/* Go back to start of data, after header */
dsp_code->word_count = sizeof(struct code_header) / sizeof(u32);
}
/*-------------------------------------------------------------------*/
short hpi_dsp_code_read_word(struct dsp_code *dsp_code, u32 *pword)
{
if (dsp_code->word_count + 1 > dsp_code->block_length)
return HPI_ERROR_DSP_FILE_FORMAT;
*pword = ((u32 *)(dsp_code->pvt->firmware->data))[dsp_code->
word_count];
dsp_code->word_count++;
return 0;
}
/*-------------------------------------------------------------------*/
short hpi_dsp_code_read_block(size_t words_requested,
struct dsp_code *dsp_code, u32 **ppblock)
{
if (dsp_code->word_count + words_requested > dsp_code->block_length)
return HPI_ERROR_DSP_FILE_FORMAT;
*ppblock =
((u32 *)(dsp_code->pvt->firmware->data)) +
dsp_code->word_count;
dsp_code->word_count += words_requested;
return 0;
}

106
sound/pci/asihpi/hpidspcd.h Normal file
View file

@ -0,0 +1,106 @@
/***********************************************************************/
/**
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
\file
Functions for reading DSP code to load into DSP
*/
/***********************************************************************/
#ifndef _HPIDSPCD_H_
#define _HPIDSPCD_H_
#include "hpi_internal.h"
/** Header structure for dsp firmware file
This structure must match that used in s2bin.c for generation of asidsp.bin
*/
/*#ifndef DISABLE_PRAGMA_PACK1 */
/*#pragma pack(push, 1) */
/*#endif */
struct code_header {
/** Size in bytes including header */
u32 size;
/** File type tag "CODE" == 0x45444F43 */
u32 type;
/** Adapter model number */
u32 adapter;
/** Firmware version*/
u32 version;
/** Data checksum */
u32 checksum;
};
/*#ifndef DISABLE_PRAGMA_PACK1 */
/*#pragma pack(pop) */
/*#endif */
/*? Don't need the pragmas? */
compile_time_assert((sizeof(struct code_header) == 20), code_header_size);
/** Descriptor for dspcode from firmware loader */
struct dsp_code {
/** copy of file header */
struct code_header header;
/** Expected number of words in the whole dsp code,INCL header */
u32 block_length;
/** Number of words read so far */
u32 word_count;
/** internal state of DSP code reader */
struct dsp_code_private *pvt;
};
/** Prepare *psDspCode to refer to the requested adapter's firmware.
Code file name is obtained from HpiOs_GetDspCodePath
\return 0 for success, or error code if requested code is not available
*/
short hpi_dsp_code_open(
/** Code identifier, usually adapter family */
u32 adapter, void *pci_dev,
/** Pointer to DSP code control structure */
struct dsp_code *ps_dsp_code,
/** Pointer to dword to receive OS specific error code */
u32 *pos_error_code);
/** Close the DSP code file */
void hpi_dsp_code_close(struct dsp_code *ps_dsp_code);
/** Rewind to the beginning of the DSP code file (for verify) */
void hpi_dsp_code_rewind(struct dsp_code *ps_dsp_code);
/** Read one word from the dsp code file
\return 0 for success, or error code if eof, or block length exceeded
*/
short hpi_dsp_code_read_word(struct dsp_code *ps_dsp_code,
/**< DSP code descriptor */
u32 *pword /**< Where to store the read word */
);
/** Get a block of dsp code into an internal buffer, and provide a pointer to
that buffer. (If dsp code is already an array in memory, it is referenced,
not copied.)
\return Error if requested number of words are not available
*/
short hpi_dsp_code_read_block(size_t words_requested,
struct dsp_code *ps_dsp_code,
/* Pointer to store (Pointer to code buffer) */
u32 **ppblock);
#endif

2871
sound/pci/asihpi/hpifunc.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,116 @@
/******************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
Hardware Programming Interface (HPI) Utility functions.
(C) Copyright AudioScience Inc. 2007
*******************************************************************************/
#include "hpi_internal.h"
#include "hpimsginit.h"
/* The actual message size for each object type */
static u16 msg_size[HPI_OBJ_MAXINDEX + 1] = HPI_MESSAGE_SIZE_BY_OBJECT;
/* The actual response size for each object type */
static u16 res_size[HPI_OBJ_MAXINDEX + 1] = HPI_RESPONSE_SIZE_BY_OBJECT;
/* Flag to enable alternate message type for SSX2 bypass. */
static u16 gwSSX2_bypass;
/** \internal
* initialize the HPI message structure
*/
static void hpi_init_message(struct hpi_message *phm, u16 object,
u16 function)
{
memset(phm, 0, sizeof(*phm));
if ((object > 0) && (object <= HPI_OBJ_MAXINDEX))
phm->size = msg_size[object];
else
phm->size = sizeof(*phm);
if (gwSSX2_bypass)
phm->type = HPI_TYPE_SSX2BYPASS_MESSAGE;
else
phm->type = HPI_TYPE_REQUEST;
phm->object = object;
phm->function = function;
phm->version = 0;
phm->adapter_index = HPI_ADAPTER_INDEX_INVALID;
/* Expect actual adapter index to be set by caller */
}
/** \internal
* initialize the HPI response structure
*/
void hpi_init_response(struct hpi_response *phr, u16 object, u16 function,
u16 error)
{
memset(phr, 0, sizeof(*phr));
phr->type = HPI_TYPE_RESPONSE;
if ((object > 0) && (object <= HPI_OBJ_MAXINDEX))
phr->size = res_size[object];
else
phr->size = sizeof(*phr);
phr->object = object;
phr->function = function;
phr->error = error;
phr->specific_error = 0;
phr->version = 0;
}
void hpi_init_message_response(struct hpi_message *phm,
struct hpi_response *phr, u16 object, u16 function)
{
hpi_init_message(phm, object, function);
/* default error return if the response is
not filled in by the callee */
hpi_init_response(phr, object, function,
HPI_ERROR_PROCESSING_MESSAGE);
}
static void hpi_init_messageV1(struct hpi_message_header *phm, u16 size,
u16 object, u16 function)
{
memset(phm, 0, sizeof(*phm));
if ((object > 0) && (object <= HPI_OBJ_MAXINDEX)) {
phm->size = size;
phm->type = HPI_TYPE_REQUEST;
phm->object = object;
phm->function = function;
phm->version = 1;
/* Expect adapter index to be set by caller */
}
}
void hpi_init_responseV1(struct hpi_response_header *phr, u16 size,
u16 object, u16 function)
{
memset(phr, 0, sizeof(*phr));
phr->size = size;
phr->version = 1;
phr->type = HPI_TYPE_RESPONSE;
phr->error = HPI_ERROR_PROCESSING_MESSAGE;
}
void hpi_init_message_responseV1(struct hpi_message_header *phm, u16 msg_size,
struct hpi_response_header *phr, u16 res_size, u16 object,
u16 function)
{
hpi_init_messageV1(phm, msg_size, object, function);
hpi_init_responseV1(phr, res_size, object, function);
}

View file

@ -0,0 +1,46 @@
/******************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
Hardware Programming Interface (HPI) Utility functions
(C) Copyright AudioScience Inc. 2007
*******************************************************************************/
/* Initialise response headers, or msg/response pairs.
Note that it is valid to just init a response e.g. when a lower level is
preparing a response to a message.
However, when sending a message, a matching response buffer must always be
prepared.
*/
#ifndef _HPIMSGINIT_H_
#define _HPIMSGINIT_H_
void hpi_init_response(struct hpi_response *phr, u16 object, u16 function,
u16 error);
void hpi_init_message_response(struct hpi_message *phm,
struct hpi_response *phr, u16 object, u16 function);
void hpi_init_responseV1(struct hpi_response_header *phr, u16 size,
u16 object, u16 function);
void hpi_init_message_responseV1(struct hpi_message_header *phm, u16 msg_size,
struct hpi_response_header *phr, u16 res_size, u16 object,
u16 function);
#endif /* _HPIMSGINIT_H_ */

800
sound/pci/asihpi/hpimsgx.c Normal file
View file

@ -0,0 +1,800 @@
/******************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
Extended Message Function With Response Caching
(C) Copyright AudioScience Inc. 2002
*****************************************************************************/
#define SOURCEFILE_NAME "hpimsgx.c"
#include "hpi_internal.h"
#include "hpi_version.h"
#include "hpimsginit.h"
#include "hpicmn.h"
#include "hpimsgx.h"
#include "hpidebug.h"
static struct pci_device_id asihpi_pci_tbl[] = {
#include "hpipcida.h"
};
static struct hpios_spinlock msgx_lock;
static hpi_handler_func *hpi_entry_points[HPI_MAX_ADAPTERS];
static hpi_handler_func *hpi_lookup_entry_point_function(const struct hpi_pci
*pci_info)
{
int i;
for (i = 0; asihpi_pci_tbl[i].vendor != 0; i++) {
if (asihpi_pci_tbl[i].vendor != PCI_ANY_ID
&& asihpi_pci_tbl[i].vendor !=
pci_info->pci_dev->vendor)
continue;
if (asihpi_pci_tbl[i].device != PCI_ANY_ID
&& asihpi_pci_tbl[i].device !=
pci_info->pci_dev->device)
continue;
if (asihpi_pci_tbl[i].subvendor != PCI_ANY_ID
&& asihpi_pci_tbl[i].subvendor !=
pci_info->pci_dev->subsystem_vendor)
continue;
if (asihpi_pci_tbl[i].subdevice != PCI_ANY_ID
&& asihpi_pci_tbl[i].subdevice !=
pci_info->pci_dev->subsystem_device)
continue;
/* HPI_DEBUG_LOG(DEBUG, " %x,%lx\n", i,
asihpi_pci_tbl[i].driver_data); */
return (hpi_handler_func *) asihpi_pci_tbl[i].driver_data;
}
return NULL;
}
static inline void hw_entry_point(struct hpi_message *phm,
struct hpi_response *phr)
{
if ((phm->adapter_index < HPI_MAX_ADAPTERS)
&& hpi_entry_points[phm->adapter_index])
hpi_entry_points[phm->adapter_index] (phm, phr);
else
hpi_init_response(phr, phm->object, phm->function,
HPI_ERROR_PROCESSING_MESSAGE);
}
static void adapter_open(struct hpi_message *phm, struct hpi_response *phr);
static void adapter_close(struct hpi_message *phm, struct hpi_response *phr);
static void mixer_open(struct hpi_message *phm, struct hpi_response *phr);
static void mixer_close(struct hpi_message *phm, struct hpi_response *phr);
static void outstream_open(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner);
static void outstream_close(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner);
static void instream_open(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner);
static void instream_close(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner);
static void HPIMSGX__reset(u16 adapter_index);
static u16 HPIMSGX__init(struct hpi_message *phm, struct hpi_response *phr);
static void HPIMSGX__cleanup(u16 adapter_index, void *h_owner);
#ifndef DISABLE_PRAGMA_PACK1
#pragma pack(push, 1)
#endif
struct hpi_subsys_response {
struct hpi_response_header h;
struct hpi_subsys_res s;
};
struct hpi_adapter_response {
struct hpi_response_header h;
struct hpi_adapter_res a;
};
struct hpi_mixer_response {
struct hpi_response_header h;
struct hpi_mixer_res m;
};
struct hpi_stream_response {
struct hpi_response_header h;
struct hpi_stream_res d;
};
struct adapter_info {
u16 type;
u16 num_instreams;
u16 num_outstreams;
};
struct asi_open_state {
int open_flag;
void *h_owner;
};
#ifndef DISABLE_PRAGMA_PACK1
#pragma pack(pop)
#endif
/* Globals */
static struct hpi_adapter_response rESP_HPI_ADAPTER_OPEN[HPI_MAX_ADAPTERS];
static struct hpi_stream_response
rESP_HPI_OSTREAM_OPEN[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS];
static struct hpi_stream_response
rESP_HPI_ISTREAM_OPEN[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS];
static struct hpi_mixer_response rESP_HPI_MIXER_OPEN[HPI_MAX_ADAPTERS];
static struct adapter_info aDAPTER_INFO[HPI_MAX_ADAPTERS];
/* use these to keep track of opens from user mode apps/DLLs */
static struct asi_open_state
outstream_user_open[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS];
static struct asi_open_state
instream_user_open[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS];
static void subsys_message(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner)
{
if (phm->adapter_index != HPI_ADAPTER_INDEX_INVALID)
HPI_DEBUG_LOG(WARNING,
"suspicious adapter index %d in subsys message 0x%x.\n",
phm->adapter_index, phm->function);
switch (phm->function) {
case HPI_SUBSYS_GET_VERSION:
hpi_init_response(phr, HPI_OBJ_SUBSYSTEM,
HPI_SUBSYS_GET_VERSION, 0);
phr->u.s.version = HPI_VER >> 8; /* return major.minor */
phr->u.s.data = HPI_VER; /* return major.minor.release */
break;
case HPI_SUBSYS_OPEN:
/*do not propagate the message down the chain */
hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_OPEN, 0);
break;
case HPI_SUBSYS_CLOSE:
/*do not propagate the message down the chain */
hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_CLOSE,
0);
HPIMSGX__cleanup(HPIMSGX_ALLADAPTERS, h_owner);
break;
case HPI_SUBSYS_DRIVER_LOAD:
/* Initialize this module's internal state */
hpios_msgxlock_init(&msgx_lock);
memset(&hpi_entry_points, 0, sizeof(hpi_entry_points));
/* Init subsys_findadapters response to no-adapters */
HPIMSGX__reset(HPIMSGX_ALLADAPTERS);
hpi_init_response(phr, HPI_OBJ_SUBSYSTEM,
HPI_SUBSYS_DRIVER_LOAD, 0);
/* individual HPIs dont implement driver load */
HPI_COMMON(phm, phr);
break;
case HPI_SUBSYS_DRIVER_UNLOAD:
HPI_COMMON(phm, phr);
HPIMSGX__cleanup(HPIMSGX_ALLADAPTERS, h_owner);
hpi_init_response(phr, HPI_OBJ_SUBSYSTEM,
HPI_SUBSYS_DRIVER_UNLOAD, 0);
return;
case HPI_SUBSYS_GET_NUM_ADAPTERS:
case HPI_SUBSYS_GET_ADAPTER:
HPI_COMMON(phm, phr);
break;
case HPI_SUBSYS_CREATE_ADAPTER:
HPIMSGX__init(phm, phr);
break;
default:
/* Must explicitly handle every subsys message in this switch */
hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, phm->function,
HPI_ERROR_INVALID_FUNC);
break;
}
}
static void adapter_message(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner)
{
switch (phm->function) {
case HPI_ADAPTER_OPEN:
adapter_open(phm, phr);
break;
case HPI_ADAPTER_CLOSE:
adapter_close(phm, phr);
break;
case HPI_ADAPTER_DELETE:
HPIMSGX__cleanup(phm->adapter_index, h_owner);
{
struct hpi_message hm;
struct hpi_response hr;
hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
HPI_ADAPTER_CLOSE);
hm.adapter_index = phm->adapter_index;
hw_entry_point(&hm, &hr);
}
hw_entry_point(phm, phr);
break;
default:
hw_entry_point(phm, phr);
break;
}
}
static void mixer_message(struct hpi_message *phm, struct hpi_response *phr)
{
switch (phm->function) {
case HPI_MIXER_OPEN:
mixer_open(phm, phr);
break;
case HPI_MIXER_CLOSE:
mixer_close(phm, phr);
break;
default:
hw_entry_point(phm, phr);
break;
}
}
static void outstream_message(struct hpi_message *phm,
struct hpi_response *phr, void *h_owner)
{
if (phm->obj_index >= aDAPTER_INFO[phm->adapter_index].num_outstreams) {
hpi_init_response(phr, HPI_OBJ_OSTREAM, phm->function,
HPI_ERROR_INVALID_OBJ_INDEX);
return;
}
switch (phm->function) {
case HPI_OSTREAM_OPEN:
outstream_open(phm, phr, h_owner);
break;
case HPI_OSTREAM_CLOSE:
outstream_close(phm, phr, h_owner);
break;
default:
hw_entry_point(phm, phr);
break;
}
}
static void instream_message(struct hpi_message *phm,
struct hpi_response *phr, void *h_owner)
{
if (phm->obj_index >= aDAPTER_INFO[phm->adapter_index].num_instreams) {
hpi_init_response(phr, HPI_OBJ_ISTREAM, phm->function,
HPI_ERROR_INVALID_OBJ_INDEX);
return;
}
switch (phm->function) {
case HPI_ISTREAM_OPEN:
instream_open(phm, phr, h_owner);
break;
case HPI_ISTREAM_CLOSE:
instream_close(phm, phr, h_owner);
break;
default:
hw_entry_point(phm, phr);
break;
}
}
/* NOTE: HPI_Message() must be defined in the driver as a wrapper for
* HPI_MessageEx so that functions in hpifunc.c compile.
*/
void hpi_send_recv_ex(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner)
{
HPI_DEBUG_MESSAGE(DEBUG, phm);
if (phm->type != HPI_TYPE_REQUEST) {
hpi_init_response(phr, phm->object, phm->function,
HPI_ERROR_INVALID_TYPE);
return;
}
if (phm->adapter_index >= HPI_MAX_ADAPTERS
&& phm->adapter_index != HPIMSGX_ALLADAPTERS) {
hpi_init_response(phr, phm->object, phm->function,
HPI_ERROR_BAD_ADAPTER_NUMBER);
return;
}
switch (phm->object) {
case HPI_OBJ_SUBSYSTEM:
subsys_message(phm, phr, h_owner);
break;
case HPI_OBJ_ADAPTER:
adapter_message(phm, phr, h_owner);
break;
case HPI_OBJ_MIXER:
mixer_message(phm, phr);
break;
case HPI_OBJ_OSTREAM:
outstream_message(phm, phr, h_owner);
break;
case HPI_OBJ_ISTREAM:
instream_message(phm, phr, h_owner);
break;
default:
hw_entry_point(phm, phr);
break;
}
HPI_DEBUG_RESPONSE(phr);
}
static void adapter_open(struct hpi_message *phm, struct hpi_response *phr)
{
HPI_DEBUG_LOG(VERBOSE, "adapter_open\n");
memcpy(phr, &rESP_HPI_ADAPTER_OPEN[phm->adapter_index],
sizeof(rESP_HPI_ADAPTER_OPEN[0]));
}
static void adapter_close(struct hpi_message *phm, struct hpi_response *phr)
{
HPI_DEBUG_LOG(VERBOSE, "adapter_close\n");
hpi_init_response(phr, HPI_OBJ_ADAPTER, HPI_ADAPTER_CLOSE, 0);
}
static void mixer_open(struct hpi_message *phm, struct hpi_response *phr)
{
memcpy(phr, &rESP_HPI_MIXER_OPEN[phm->adapter_index],
sizeof(rESP_HPI_MIXER_OPEN[0]));
}
static void mixer_close(struct hpi_message *phm, struct hpi_response *phr)
{
hpi_init_response(phr, HPI_OBJ_MIXER, HPI_MIXER_CLOSE, 0);
}
static void instream_open(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner)
{
struct hpi_message hm;
struct hpi_response hr;
hpi_init_response(phr, HPI_OBJ_ISTREAM, HPI_ISTREAM_OPEN, 0);
hpios_msgxlock_lock(&msgx_lock);
if (instream_user_open[phm->adapter_index][phm->obj_index].open_flag)
phr->error = HPI_ERROR_OBJ_ALREADY_OPEN;
else if (rESP_HPI_ISTREAM_OPEN[phm->adapter_index]
[phm->obj_index].h.error)
memcpy(phr,
&rESP_HPI_ISTREAM_OPEN[phm->adapter_index][phm->
obj_index],
sizeof(rESP_HPI_ISTREAM_OPEN[0][0]));
else {
instream_user_open[phm->adapter_index][phm->
obj_index].open_flag = 1;
hpios_msgxlock_unlock(&msgx_lock);
/* issue a reset */
hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
HPI_ISTREAM_RESET);
hm.adapter_index = phm->adapter_index;
hm.obj_index = phm->obj_index;
hw_entry_point(&hm, &hr);
hpios_msgxlock_lock(&msgx_lock);
if (hr.error) {
instream_user_open[phm->adapter_index][phm->
obj_index].open_flag = 0;
phr->error = hr.error;
} else {
instream_user_open[phm->adapter_index][phm->
obj_index].open_flag = 1;
instream_user_open[phm->adapter_index][phm->
obj_index].h_owner = h_owner;
memcpy(phr,
&rESP_HPI_ISTREAM_OPEN[phm->adapter_index]
[phm->obj_index],
sizeof(rESP_HPI_ISTREAM_OPEN[0][0]));
}
}
hpios_msgxlock_unlock(&msgx_lock);
}
static void instream_close(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner)
{
struct hpi_message hm;
struct hpi_response hr;
hpi_init_response(phr, HPI_OBJ_ISTREAM, HPI_ISTREAM_CLOSE, 0);
hpios_msgxlock_lock(&msgx_lock);
if (h_owner ==
instream_user_open[phm->adapter_index][phm->
obj_index].h_owner) {
/* HPI_DEBUG_LOG(INFO,"closing adapter %d "
"instream %d owned by %p\n",
phm->wAdapterIndex, phm->wObjIndex, hOwner); */
instream_user_open[phm->adapter_index][phm->
obj_index].h_owner = NULL;
hpios_msgxlock_unlock(&msgx_lock);
/* issue a reset */
hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
HPI_ISTREAM_RESET);
hm.adapter_index = phm->adapter_index;
hm.obj_index = phm->obj_index;
hw_entry_point(&hm, &hr);
hpios_msgxlock_lock(&msgx_lock);
if (hr.error) {
instream_user_open[phm->adapter_index][phm->
obj_index].h_owner = h_owner;
phr->error = hr.error;
} else {
instream_user_open[phm->adapter_index][phm->
obj_index].open_flag = 0;
instream_user_open[phm->adapter_index][phm->
obj_index].h_owner = NULL;
}
} else {
HPI_DEBUG_LOG(WARNING,
"%p trying to close %d instream %d owned by %p\n",
h_owner, phm->adapter_index, phm->obj_index,
instream_user_open[phm->adapter_index][phm->
obj_index].h_owner);
phr->error = HPI_ERROR_OBJ_NOT_OPEN;
}
hpios_msgxlock_unlock(&msgx_lock);
}
static void outstream_open(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner)
{
struct hpi_message hm;
struct hpi_response hr;
hpi_init_response(phr, HPI_OBJ_OSTREAM, HPI_OSTREAM_OPEN, 0);
hpios_msgxlock_lock(&msgx_lock);
if (outstream_user_open[phm->adapter_index][phm->obj_index].open_flag)
phr->error = HPI_ERROR_OBJ_ALREADY_OPEN;
else if (rESP_HPI_OSTREAM_OPEN[phm->adapter_index]
[phm->obj_index].h.error)
memcpy(phr,
&rESP_HPI_OSTREAM_OPEN[phm->adapter_index][phm->
obj_index],
sizeof(rESP_HPI_OSTREAM_OPEN[0][0]));
else {
outstream_user_open[phm->adapter_index][phm->
obj_index].open_flag = 1;
hpios_msgxlock_unlock(&msgx_lock);
/* issue a reset */
hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
HPI_OSTREAM_RESET);
hm.adapter_index = phm->adapter_index;
hm.obj_index = phm->obj_index;
hw_entry_point(&hm, &hr);
hpios_msgxlock_lock(&msgx_lock);
if (hr.error) {
outstream_user_open[phm->adapter_index][phm->
obj_index].open_flag = 0;
phr->error = hr.error;
} else {
outstream_user_open[phm->adapter_index][phm->
obj_index].open_flag = 1;
outstream_user_open[phm->adapter_index][phm->
obj_index].h_owner = h_owner;
memcpy(phr,
&rESP_HPI_OSTREAM_OPEN[phm->adapter_index]
[phm->obj_index],
sizeof(rESP_HPI_OSTREAM_OPEN[0][0]));
}
}
hpios_msgxlock_unlock(&msgx_lock);
}
static void outstream_close(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner)
{
struct hpi_message hm;
struct hpi_response hr;
hpi_init_response(phr, HPI_OBJ_OSTREAM, HPI_OSTREAM_CLOSE, 0);
hpios_msgxlock_lock(&msgx_lock);
if (h_owner ==
outstream_user_open[phm->adapter_index][phm->
obj_index].h_owner) {
/* HPI_DEBUG_LOG(INFO,"closing adapter %d "
"outstream %d owned by %p\n",
phm->wAdapterIndex, phm->wObjIndex, hOwner); */
outstream_user_open[phm->adapter_index][phm->
obj_index].h_owner = NULL;
hpios_msgxlock_unlock(&msgx_lock);
/* issue a reset */
hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
HPI_OSTREAM_RESET);
hm.adapter_index = phm->adapter_index;
hm.obj_index = phm->obj_index;
hw_entry_point(&hm, &hr);
hpios_msgxlock_lock(&msgx_lock);
if (hr.error) {
outstream_user_open[phm->adapter_index][phm->
obj_index].h_owner = h_owner;
phr->error = hr.error;
} else {
outstream_user_open[phm->adapter_index][phm->
obj_index].open_flag = 0;
outstream_user_open[phm->adapter_index][phm->
obj_index].h_owner = NULL;
}
} else {
HPI_DEBUG_LOG(WARNING,
"%p trying to close %d outstream %d owned by %p\n",
h_owner, phm->adapter_index, phm->obj_index,
outstream_user_open[phm->adapter_index][phm->
obj_index].h_owner);
phr->error = HPI_ERROR_OBJ_NOT_OPEN;
}
hpios_msgxlock_unlock(&msgx_lock);
}
static u16 adapter_prepare(u16 adapter)
{
struct hpi_message hm;
struct hpi_response hr;
/* Open the adapter and streams */
u16 i;
/* call to HPI_ADAPTER_OPEN */
hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
HPI_ADAPTER_OPEN);
hm.adapter_index = adapter;
hw_entry_point(&hm, &hr);
memcpy(&rESP_HPI_ADAPTER_OPEN[adapter], &hr,
sizeof(rESP_HPI_ADAPTER_OPEN[0]));
if (hr.error)
return hr.error;
/* call to HPI_ADAPTER_GET_INFO */
hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
HPI_ADAPTER_GET_INFO);
hm.adapter_index = adapter;
hw_entry_point(&hm, &hr);
if (hr.error)
return hr.error;
aDAPTER_INFO[adapter].num_outstreams = hr.u.ax.info.num_outstreams;
aDAPTER_INFO[adapter].num_instreams = hr.u.ax.info.num_instreams;
aDAPTER_INFO[adapter].type = hr.u.ax.info.adapter_type;
/* call to HPI_OSTREAM_OPEN */
for (i = 0; i < aDAPTER_INFO[adapter].num_outstreams; i++) {
hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
HPI_OSTREAM_OPEN);
hm.adapter_index = adapter;
hm.obj_index = i;
hw_entry_point(&hm, &hr);
memcpy(&rESP_HPI_OSTREAM_OPEN[adapter][i], &hr,
sizeof(rESP_HPI_OSTREAM_OPEN[0][0]));
outstream_user_open[adapter][i].open_flag = 0;
outstream_user_open[adapter][i].h_owner = NULL;
}
/* call to HPI_ISTREAM_OPEN */
for (i = 0; i < aDAPTER_INFO[adapter].num_instreams; i++) {
hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
HPI_ISTREAM_OPEN);
hm.adapter_index = adapter;
hm.obj_index = i;
hw_entry_point(&hm, &hr);
memcpy(&rESP_HPI_ISTREAM_OPEN[adapter][i], &hr,
sizeof(rESP_HPI_ISTREAM_OPEN[0][0]));
instream_user_open[adapter][i].open_flag = 0;
instream_user_open[adapter][i].h_owner = NULL;
}
/* call to HPI_MIXER_OPEN */
hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN);
hm.adapter_index = adapter;
hw_entry_point(&hm, &hr);
memcpy(&rESP_HPI_MIXER_OPEN[adapter], &hr,
sizeof(rESP_HPI_MIXER_OPEN[0]));
return 0;
}
static void HPIMSGX__reset(u16 adapter_index)
{
int i;
u16 adapter;
struct hpi_response hr;
if (adapter_index == HPIMSGX_ALLADAPTERS) {
for (adapter = 0; adapter < HPI_MAX_ADAPTERS; adapter++) {
hpi_init_response(&hr, HPI_OBJ_ADAPTER,
HPI_ADAPTER_OPEN, HPI_ERROR_BAD_ADAPTER);
memcpy(&rESP_HPI_ADAPTER_OPEN[adapter], &hr,
sizeof(rESP_HPI_ADAPTER_OPEN[adapter]));
hpi_init_response(&hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN,
HPI_ERROR_INVALID_OBJ);
memcpy(&rESP_HPI_MIXER_OPEN[adapter], &hr,
sizeof(rESP_HPI_MIXER_OPEN[adapter]));
for (i = 0; i < HPI_MAX_STREAMS; i++) {
hpi_init_response(&hr, HPI_OBJ_OSTREAM,
HPI_OSTREAM_OPEN,
HPI_ERROR_INVALID_OBJ);
memcpy(&rESP_HPI_OSTREAM_OPEN[adapter][i],
&hr,
sizeof(rESP_HPI_OSTREAM_OPEN[adapter]
[i]));
hpi_init_response(&hr, HPI_OBJ_ISTREAM,
HPI_ISTREAM_OPEN,
HPI_ERROR_INVALID_OBJ);
memcpy(&rESP_HPI_ISTREAM_OPEN[adapter][i],
&hr,
sizeof(rESP_HPI_ISTREAM_OPEN[adapter]
[i]));
}
}
} else if (adapter_index < HPI_MAX_ADAPTERS) {
rESP_HPI_ADAPTER_OPEN[adapter_index].h.error =
HPI_ERROR_BAD_ADAPTER;
rESP_HPI_MIXER_OPEN[adapter_index].h.error =
HPI_ERROR_INVALID_OBJ;
for (i = 0; i < HPI_MAX_STREAMS; i++) {
rESP_HPI_OSTREAM_OPEN[adapter_index][i].h.error =
HPI_ERROR_INVALID_OBJ;
rESP_HPI_ISTREAM_OPEN[adapter_index][i].h.error =
HPI_ERROR_INVALID_OBJ;
}
}
}
static u16 HPIMSGX__init(struct hpi_message *phm,
/* HPI_SUBSYS_CREATE_ADAPTER structure with */
/* resource list or NULL=find all */
struct hpi_response *phr
/* response from HPI_ADAPTER_GET_INFO */
)
{
hpi_handler_func *entry_point_func;
struct hpi_response hr;
/* Init response here so we can pass in previous adapter list */
hpi_init_response(&hr, phm->object, phm->function,
HPI_ERROR_INVALID_OBJ);
entry_point_func =
hpi_lookup_entry_point_function(phm->u.s.resource.r.pci);
if (entry_point_func) {
HPI_DEBUG_MESSAGE(DEBUG, phm);
entry_point_func(phm, &hr);
} else {
phr->error = HPI_ERROR_PROCESSING_MESSAGE;
return phr->error;
}
if (hr.error == 0) {
/* the adapter was created successfully
save the mapping for future use */
hpi_entry_points[hr.u.s.adapter_index] = entry_point_func;
/* prepare adapter (pre-open streams etc.) */
HPI_DEBUG_LOG(DEBUG,
"HPI_SUBSYS_CREATE_ADAPTER successful,"
" preparing adapter\n");
adapter_prepare(hr.u.s.adapter_index);
}
memcpy(phr, &hr, hr.size);
return phr->error;
}
static void HPIMSGX__cleanup(u16 adapter_index, void *h_owner)
{
int i, adapter, adapter_limit;
if (!h_owner)
return;
if (adapter_index == HPIMSGX_ALLADAPTERS) {
adapter = 0;
adapter_limit = HPI_MAX_ADAPTERS;
} else {
adapter = adapter_index;
adapter_limit = adapter + 1;
}
for (; adapter < adapter_limit; adapter++) {
/* printk(KERN_INFO "Cleanup adapter #%d\n",wAdapter); */
for (i = 0; i < HPI_MAX_STREAMS; i++) {
if (h_owner ==
outstream_user_open[adapter][i].h_owner) {
struct hpi_message hm;
struct hpi_response hr;
HPI_DEBUG_LOG(DEBUG,
"Close adapter %d ostream %d\n",
adapter, i);
hpi_init_message_response(&hm, &hr,
HPI_OBJ_OSTREAM, HPI_OSTREAM_RESET);
hm.adapter_index = (u16)adapter;
hm.obj_index = (u16)i;
hw_entry_point(&hm, &hr);
hm.function = HPI_OSTREAM_HOSTBUFFER_FREE;
hw_entry_point(&hm, &hr);
hm.function = HPI_OSTREAM_GROUP_RESET;
hw_entry_point(&hm, &hr);
outstream_user_open[adapter][i].open_flag = 0;
outstream_user_open[adapter][i].h_owner =
NULL;
}
if (h_owner == instream_user_open[adapter][i].h_owner) {
struct hpi_message hm;
struct hpi_response hr;
HPI_DEBUG_LOG(DEBUG,
"Close adapter %d istream %d\n",
adapter, i);
hpi_init_message_response(&hm, &hr,
HPI_OBJ_ISTREAM, HPI_ISTREAM_RESET);
hm.adapter_index = (u16)adapter;
hm.obj_index = (u16)i;
hw_entry_point(&hm, &hr);
hm.function = HPI_ISTREAM_HOSTBUFFER_FREE;
hw_entry_point(&hm, &hr);
hm.function = HPI_ISTREAM_GROUP_RESET;
hw_entry_point(&hm, &hr);
instream_user_open[adapter][i].open_flag = 0;
instream_user_open[adapter][i].h_owner = NULL;
}
}
}
}

View file

@ -0,0 +1,36 @@
/******************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
HPI Extended Message Handler Functions
(C) Copyright AudioScience Inc. 1997-2003
******************************************************************************/
#ifndef _HPIMSGX_H_
#define _HPIMSGX_H_
#include "hpi_internal.h"
#define HPIMSGX_ALLADAPTERS (0xFFFF)
void hpi_send_recv_ex(struct hpi_message *phm, struct hpi_response *phr,
void *h_owner);
#define HPI_MESSAGE_LOWER_LAYER hpi_send_recv_ex
#endif /* _HPIMSGX_H_ */

480
sound/pci/asihpi/hpioctl.c Normal file
View file

@ -0,0 +1,480 @@
/*******************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
Common Linux HPI ioctl and module probe/remove functions
*******************************************************************************/
#define SOURCEFILE_NAME "hpioctl.c"
#include "hpi_internal.h"
#include "hpi_version.h"
#include "hpimsginit.h"
#include "hpidebug.h"
#include "hpimsgx.h"
#include "hpioctl.h"
#include "hpicmn.h"
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/moduleparam.h>
#include <asm/uaccess.h>
#include <linux/pci.h>
#include <linux/stringify.h>
#include <linux/module.h>
#ifdef MODULE_FIRMWARE
MODULE_FIRMWARE("asihpi/dsp5000.bin");
MODULE_FIRMWARE("asihpi/dsp6200.bin");
MODULE_FIRMWARE("asihpi/dsp6205.bin");
MODULE_FIRMWARE("asihpi/dsp6400.bin");
MODULE_FIRMWARE("asihpi/dsp6600.bin");
MODULE_FIRMWARE("asihpi/dsp8700.bin");
MODULE_FIRMWARE("asihpi/dsp8900.bin");
#endif
static int prealloc_stream_buf;
module_param(prealloc_stream_buf, int, S_IRUGO);
MODULE_PARM_DESC(prealloc_stream_buf,
"Preallocate size for per-adapter stream buffer");
/* Allow the debug level to be changed after module load.
E.g. echo 2 > /sys/module/asihpi/parameters/hpiDebugLevel
*/
module_param(hpi_debug_level, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(hpi_debug_level, "debug verbosity 0..5");
/* List of adapters found */
static struct hpi_adapter adapters[HPI_MAX_ADAPTERS];
/* Wrapper function to HPI_Message to enable dumping of the
message and response types.
*/
static void hpi_send_recv_f(struct hpi_message *phm, struct hpi_response *phr,
struct file *file)
{
if ((phm->adapter_index >= HPI_MAX_ADAPTERS)
&& (phm->object != HPI_OBJ_SUBSYSTEM))
phr->error = HPI_ERROR_INVALID_OBJ_INDEX;
else
hpi_send_recv_ex(phm, phr, file);
}
/* This is called from hpifunc.c functions, called by ALSA
* (or other kernel process) In this case there is no file descriptor
* available for the message cache code
*/
void hpi_send_recv(struct hpi_message *phm, struct hpi_response *phr)
{
hpi_send_recv_f(phm, phr, HOWNER_KERNEL);
}
EXPORT_SYMBOL(hpi_send_recv);
/* for radio-asihpi */
int asihpi_hpi_release(struct file *file)
{
struct hpi_message hm;
struct hpi_response hr;
/* HPI_DEBUG_LOG(INFO,"hpi_release file %p, pid %d\n", file, current->pid); */
/* close the subsystem just in case the application forgot to. */
hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
HPI_SUBSYS_CLOSE);
hpi_send_recv_ex(&hm, &hr, file);
return 0;
}
long asihpi_hpi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct hpi_ioctl_linux __user *phpi_ioctl_data;
void __user *puhm;
void __user *puhr;
union hpi_message_buffer_v1 *hm;
union hpi_response_buffer_v1 *hr;
u16 res_max_size;
u32 uncopied_bytes;
int err = 0;
if (cmd != HPI_IOCTL_LINUX)
return -EINVAL;
hm = kmalloc(sizeof(*hm), GFP_KERNEL);
hr = kmalloc(sizeof(*hr), GFP_KERNEL);
if (!hm || !hr) {
err = -ENOMEM;
goto out;
}
phpi_ioctl_data = (struct hpi_ioctl_linux __user *)arg;
/* Read the message and response pointers from user space. */
if (get_user(puhm, &phpi_ioctl_data->phm)
|| get_user(puhr, &phpi_ioctl_data->phr)) {
err = -EFAULT;
goto out;
}
/* Now read the message size and data from user space. */
if (get_user(hm->h.size, (u16 __user *)puhm)) {
err = -EFAULT;
goto out;
}
if (hm->h.size > sizeof(*hm))
hm->h.size = sizeof(*hm);
/* printk(KERN_INFO "message size %d\n", hm->h.wSize); */
uncopied_bytes = copy_from_user(hm, puhm, hm->h.size);
if (uncopied_bytes) {
HPI_DEBUG_LOG(ERROR, "uncopied bytes %d\n", uncopied_bytes);
err = -EFAULT;
goto out;
}
if (get_user(res_max_size, (u16 __user *)puhr)) {
err = -EFAULT;
goto out;
}
/* printk(KERN_INFO "user response size %d\n", res_max_size); */
if (res_max_size < sizeof(struct hpi_response_header)) {
HPI_DEBUG_LOG(WARNING, "small res size %d\n", res_max_size);
err = -EFAULT;
goto out;
}
switch (hm->h.function) {
case HPI_SUBSYS_CREATE_ADAPTER:
case HPI_ADAPTER_DELETE:
/* Application must not use these functions! */
hr->h.size = sizeof(hr->h);
hr->h.error = HPI_ERROR_INVALID_OPERATION;
hr->h.function = hm->h.function;
uncopied_bytes = copy_to_user(puhr, hr, hr->h.size);
if (uncopied_bytes)
err = -EFAULT;
else
err = 0;
goto out;
}
hr->h.size = res_max_size;
if (hm->h.object == HPI_OBJ_SUBSYSTEM) {
hpi_send_recv_f(&hm->m0, &hr->r0, file);
} else {
u16 __user *ptr = NULL;
u32 size = 0;
/* -1=no data 0=read from user mem, 1=write to user mem */
int wrflag = -1;
struct hpi_adapter *pa = NULL;
if (hm->h.adapter_index < ARRAY_SIZE(adapters))
pa = &adapters[hm->h.adapter_index];
if (!pa || !pa->adapter || !pa->adapter->type) {
hpi_init_response(&hr->r0, hm->h.object,
hm->h.function, HPI_ERROR_BAD_ADAPTER_NUMBER);
uncopied_bytes =
copy_to_user(puhr, hr, sizeof(hr->h));
if (uncopied_bytes)
err = -EFAULT;
else
err = 0;
goto out;
}
if (mutex_lock_interruptible(&pa->mutex)) {
err = -EINTR;
goto out;
}
/* Dig out any pointers embedded in the message. */
switch (hm->h.function) {
case HPI_OSTREAM_WRITE:
case HPI_ISTREAM_READ:{
/* Yes, sparse, this is correct. */
ptr = (u16 __user *)hm->m0.u.d.u.data.pb_data;
size = hm->m0.u.d.u.data.data_size;
/* Allocate buffer according to application request.
?Is it better to alloc/free for the duration
of the transaction?
*/
if (pa->buffer_size < size) {
HPI_DEBUG_LOG(DEBUG,
"Realloc adapter %d stream "
"buffer from %zd to %d\n",
hm->h.adapter_index,
pa->buffer_size, size);
if (pa->p_buffer) {
pa->buffer_size = 0;
vfree(pa->p_buffer);
}
pa->p_buffer = vmalloc(size);
if (pa->p_buffer)
pa->buffer_size = size;
else {
HPI_DEBUG_LOG(ERROR,
"HPI could not allocate "
"stream buffer size %d\n",
size);
mutex_unlock(&pa->mutex);
err = -EINVAL;
goto out;
}
}
hm->m0.u.d.u.data.pb_data = pa->p_buffer;
if (hm->h.function == HPI_ISTREAM_READ)
/* from card, WRITE to user mem */
wrflag = 1;
else
wrflag = 0;
break;
}
default:
size = 0;
break;
}
if (size && (wrflag == 0)) {
uncopied_bytes =
copy_from_user(pa->p_buffer, ptr, size);
if (uncopied_bytes)
HPI_DEBUG_LOG(WARNING,
"Missed %d of %d "
"bytes from user\n", uncopied_bytes,
size);
}
hpi_send_recv_f(&hm->m0, &hr->r0, file);
if (size && (wrflag == 1)) {
uncopied_bytes =
copy_to_user(ptr, pa->p_buffer, size);
if (uncopied_bytes)
HPI_DEBUG_LOG(WARNING,
"Missed %d of %d " "bytes to user\n",
uncopied_bytes, size);
}
mutex_unlock(&pa->mutex);
}
/* on return response size must be set */
/*printk(KERN_INFO "response size %d\n", hr->h.wSize); */
if (!hr->h.size) {
HPI_DEBUG_LOG(ERROR, "response zero size\n");
err = -EFAULT;
goto out;
}
if (hr->h.size > res_max_size) {
HPI_DEBUG_LOG(ERROR, "response too big %d %d\n", hr->h.size,
res_max_size);
hr->h.error = HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL;
hr->h.specific_error = hr->h.size;
hr->h.size = sizeof(hr->h);
}
uncopied_bytes = copy_to_user(puhr, hr, hr->h.size);
if (uncopied_bytes) {
HPI_DEBUG_LOG(ERROR, "uncopied bytes %d\n", uncopied_bytes);
err = -EFAULT;
goto out;
}
out:
kfree(hm);
kfree(hr);
return err;
}
int asihpi_adapter_probe(struct pci_dev *pci_dev,
const struct pci_device_id *pci_id)
{
int idx, nm;
int adapter_index;
unsigned int memlen;
struct hpi_message hm;
struct hpi_response hr;
struct hpi_adapter adapter;
struct hpi_pci pci;
memset(&adapter, 0, sizeof(adapter));
dev_printk(KERN_DEBUG, &pci_dev->dev,
"probe %04x:%04x,%04x:%04x,%04x\n", pci_dev->vendor,
pci_dev->device, pci_dev->subsystem_vendor,
pci_dev->subsystem_device, pci_dev->devfn);
if (pci_enable_device(pci_dev) < 0) {
dev_err(&pci_dev->dev,
"pci_enable_device failed, disabling device\n");
return -EIO;
}
pci_set_master(pci_dev); /* also sets latency timer if < 16 */
hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
HPI_SUBSYS_CREATE_ADAPTER);
hpi_init_response(&hr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_CREATE_ADAPTER,
HPI_ERROR_PROCESSING_MESSAGE);
hm.adapter_index = HPI_ADAPTER_INDEX_INVALID;
nm = HPI_MAX_ADAPTER_MEM_SPACES;
for (idx = 0; idx < nm; idx++) {
HPI_DEBUG_LOG(INFO, "resource %d %pR\n", idx,
&pci_dev->resource[idx]);
if (pci_resource_flags(pci_dev, idx) & IORESOURCE_MEM) {
memlen = pci_resource_len(pci_dev, idx);
pci.ap_mem_base[idx] =
ioremap(pci_resource_start(pci_dev, idx),
memlen);
if (!pci.ap_mem_base[idx]) {
HPI_DEBUG_LOG(ERROR,
"ioremap failed, aborting\n");
/* unmap previously mapped pci mem space */
goto err;
}
}
}
pci.pci_dev = pci_dev;
hm.u.s.resource.bus_type = HPI_BUS_PCI;
hm.u.s.resource.r.pci = &pci;
/* call CreateAdapterObject on the relevant hpi module */
hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL);
if (hr.error)
goto err;
adapter_index = hr.u.s.adapter_index;
adapter.adapter = hpi_find_adapter(adapter_index);
if (prealloc_stream_buf) {
adapter.p_buffer = vmalloc(prealloc_stream_buf);
if (!adapter.p_buffer) {
HPI_DEBUG_LOG(ERROR,
"HPI could not allocate "
"kernel buffer size %d\n",
prealloc_stream_buf);
goto err;
}
}
hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
HPI_ADAPTER_OPEN);
hm.adapter_index = adapter.adapter->index;
hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL);
if (hr.error)
goto err;
/* WARNING can't init mutex in 'adapter'
* and then copy it to adapters[] ?!?!
*/
adapters[adapter_index] = adapter;
mutex_init(&adapters[adapter_index].mutex);
pci_set_drvdata(pci_dev, &adapters[adapter_index]);
dev_info(&pci_dev->dev, "probe succeeded for ASI%04X HPI index %d\n",
adapter.adapter->type, adapter_index);
return 0;
err:
for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; idx++) {
if (pci.ap_mem_base[idx]) {
iounmap(pci.ap_mem_base[idx]);
pci.ap_mem_base[idx] = NULL;
}
}
if (adapter.p_buffer) {
adapter.buffer_size = 0;
vfree(adapter.p_buffer);
}
HPI_DEBUG_LOG(ERROR, "adapter_probe failed\n");
return -ENODEV;
}
void asihpi_adapter_remove(struct pci_dev *pci_dev)
{
int idx;
struct hpi_message hm;
struct hpi_response hr;
struct hpi_adapter *pa;
struct hpi_pci pci;
pa = pci_get_drvdata(pci_dev);
pci = pa->adapter->pci;
hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
HPI_ADAPTER_DELETE);
hm.adapter_index = pa->adapter->index;
hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL);
/* unmap PCI memory space, mapped during device init. */
for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; idx++) {
if (pci.ap_mem_base[idx])
iounmap(pci.ap_mem_base[idx]);
}
if (pa->p_buffer)
vfree(pa->p_buffer);
if (1)
dev_info(&pci_dev->dev,
"remove %04x:%04x,%04x:%04x,%04x, HPI index %d\n",
pci_dev->vendor, pci_dev->device,
pci_dev->subsystem_vendor, pci_dev->subsystem_device,
pci_dev->devfn, pa->adapter->index);
memset(pa, 0, sizeof(*pa));
}
void __init asihpi_init(void)
{
struct hpi_message hm;
struct hpi_response hr;
memset(adapters, 0, sizeof(adapters));
printk(KERN_INFO "ASIHPI driver " HPI_VER_STRING "\n");
hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
HPI_SUBSYS_DRIVER_LOAD);
hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL);
}
void asihpi_exit(void)
{
struct hpi_message hm;
struct hpi_response hr;
hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
HPI_SUBSYS_DRIVER_UNLOAD);
hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL);
}

View file

@ -0,0 +1,38 @@
/*******************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
Linux HPI ioctl, and shared module init functions
*******************************************************************************/
int asihpi_adapter_probe(struct pci_dev *pci_dev,
const struct pci_device_id *pci_id);
void asihpi_adapter_remove(struct pci_dev *pci_dev);
void __init asihpi_init(void);
void __exit asihpi_exit(void);
int asihpi_hpi_release(struct file *file);
long asihpi_hpi_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
/* This is called from hpifunc.c functions, called by ALSA
* (or other kernel process) In this case there is no file descriptor
* available for the message cache code
*/
void hpi_send_recv(struct hpi_message *phm, struct hpi_response *phr);
#define HOWNER_KERNEL ((void *)-1)

83
sound/pci/asihpi/hpios.c Normal file
View file

@ -0,0 +1,83 @@
/******************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2012 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
HPI Operating System function implementation for Linux
(C) Copyright AudioScience Inc. 1997-2003
******************************************************************************/
#define SOURCEFILE_NAME "hpios.c"
#include "hpi_internal.h"
#include "hpidebug.h"
#include <linux/delay.h>
#include <linux/sched.h>
void hpios_delay_micro_seconds(u32 num_micro_sec)
{
if ((usecs_to_jiffies(num_micro_sec) > 1) && !in_interrupt()) {
/* MUST NOT SCHEDULE IN INTERRUPT CONTEXT! */
schedule_timeout_uninterruptible(usecs_to_jiffies
(num_micro_sec));
} else if (num_micro_sec <= 2000)
udelay(num_micro_sec);
else
mdelay(num_micro_sec / 1000);
}
/** Allocate an area of locked memory for bus master DMA operations.
If allocation fails, return 1, and *pMemArea.size = 0
*/
u16 hpios_locked_mem_alloc(struct consistent_dma_area *p_mem_area, u32 size,
struct pci_dev *pdev)
{
/*?? any benefit in using managed dmam_alloc_coherent? */
p_mem_area->vaddr =
dma_alloc_coherent(&pdev->dev, size, &p_mem_area->dma_handle,
GFP_DMA32 | GFP_KERNEL);
if (p_mem_area->vaddr) {
HPI_DEBUG_LOG(DEBUG, "allocated %d bytes, dma 0x%x vma %p\n",
size, (unsigned int)p_mem_area->dma_handle,
p_mem_area->vaddr);
p_mem_area->pdev = &pdev->dev;
p_mem_area->size = size;
return 0;
} else {
HPI_DEBUG_LOG(WARNING,
"failed to allocate %d bytes locked memory\n", size);
p_mem_area->size = 0;
return 1;
}
}
u16 hpios_locked_mem_free(struct consistent_dma_area *p_mem_area)
{
if (p_mem_area->size) {
dma_free_coherent(p_mem_area->pdev, p_mem_area->size,
p_mem_area->vaddr, p_mem_area->dma_handle);
HPI_DEBUG_LOG(DEBUG, "freed %lu bytes, dma 0x%x vma %p\n",
(unsigned long)p_mem_area->size,
(unsigned int)p_mem_area->dma_handle,
p_mem_area->vaddr);
p_mem_area->size = 0;
return 0;
} else {
return 1;
}
}

165
sound/pci/asihpi/hpios.h Normal file
View file

@ -0,0 +1,165 @@
/******************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
HPI Operating System Specific macros for Linux Kernel driver
(C) Copyright AudioScience Inc. 1997-2003
******************************************************************************/
#ifndef _HPIOS_H_
#define _HPIOS_H_
#undef HPI_OS_LINUX_KERNEL
#define HPI_OS_LINUX_KERNEL
#define HPI_OS_DEFINED
#define HPI_BUILD_KERNEL_MODE
#include <linux/io.h>
#include <linux/ioctl.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/mutex.h>
#define HPI_NO_OS_FILE_OPS
#ifdef CONFIG_64BIT
#define HPI64BIT
#endif
/** Details of a memory area allocated with pci_alloc_consistent
Need all info for parameters to pci_free_consistent
*/
struct consistent_dma_area {
struct device *pdev;
/* looks like dma-mapping dma_devres ?! */
size_t size;
void *vaddr;
dma_addr_t dma_handle;
};
static inline u16 hpios_locked_mem_get_phys_addr(struct consistent_dma_area
*locked_mem_handle, u32 *p_physical_addr)
{
*p_physical_addr = locked_mem_handle->dma_handle;
return 0;
}
static inline u16 hpios_locked_mem_get_virt_addr(struct consistent_dma_area
*locked_mem_handle, void **pp_virtual_addr)
{
*pp_virtual_addr = locked_mem_handle->vaddr;
return 0;
}
static inline u16 hpios_locked_mem_valid(struct consistent_dma_area
*locked_mem_handle)
{
return locked_mem_handle->size != 0;
}
struct hpi_ioctl_linux {
void __user *phm;
void __user *phr;
};
/* Conflict?: H is already used by a number of drivers hid, bluetooth hci,
and some sound drivers sb16, hdsp, emu10k. AFAIK 0xFC is ununsed command
*/
#define HPI_IOCTL_LINUX _IOWR('H', 0xFC, struct hpi_ioctl_linux)
#define HPI_DEBUG_FLAG_ERROR KERN_ERR
#define HPI_DEBUG_FLAG_WARNING KERN_WARNING
#define HPI_DEBUG_FLAG_NOTICE KERN_NOTICE
#define HPI_DEBUG_FLAG_INFO KERN_INFO
#define HPI_DEBUG_FLAG_DEBUG KERN_DEBUG
#define HPI_DEBUG_FLAG_VERBOSE KERN_DEBUG /* kernel has no verbose */
#include <linux/spinlock.h>
#define HPI_LOCKING
struct hpios_spinlock {
spinlock_t lock; /* SEE hpios_spinlock */
int lock_context;
};
/* The reason for all this evilness is that ALSA calls some of a drivers
* operators in atomic context, and some not. But all our functions channel
* through the HPI_Message conduit, so we can't handle the different context
* per function
*/
#define IN_LOCK_BH 1
#define IN_LOCK_IRQ 0
static inline void cond_lock(struct hpios_spinlock *l)
{
if (irqs_disabled()) {
/* NO bh or isr can execute on this processor,
so ordinary lock will do
*/
spin_lock(&((l)->lock));
l->lock_context = IN_LOCK_IRQ;
} else {
spin_lock_bh(&((l)->lock));
l->lock_context = IN_LOCK_BH;
}
}
static inline void cond_unlock(struct hpios_spinlock *l)
{
if (l->lock_context == IN_LOCK_BH)
spin_unlock_bh(&((l)->lock));
else
spin_unlock(&((l)->lock));
}
#define hpios_msgxlock_init(obj) spin_lock_init(&(obj)->lock)
#define hpios_msgxlock_lock(obj) cond_lock(obj)
#define hpios_msgxlock_unlock(obj) cond_unlock(obj)
#define hpios_dsplock_init(obj) spin_lock_init(&(obj)->dsp_lock.lock)
#define hpios_dsplock_lock(obj) cond_lock(&(obj)->dsp_lock)
#define hpios_dsplock_unlock(obj) cond_unlock(&(obj)->dsp_lock)
#ifdef CONFIG_SND_DEBUG
#define HPI_BUILD_DEBUG
#endif
#define HPI_ALIST_LOCKING
#define hpios_alistlock_init(obj) spin_lock_init(&((obj)->list_lock.lock))
#define hpios_alistlock_lock(obj) spin_lock(&((obj)->list_lock.lock))
#define hpios_alistlock_unlock(obj) spin_unlock(&((obj)->list_lock.lock))
struct snd_card;
/** pci drvdata points to an instance of this struct */
struct hpi_adapter {
struct hpi_adapter_obj *adapter;
struct snd_card *snd_card;
/* mutex prevents contention for one card
between multiple user programs (via ioctl) */
struct mutex mutex;
char *p_buffer;
size_t buffer_size;
};
#endif

View file

@ -0,0 +1,37 @@
/******************************************************************************
AudioScience HPI driver
Copyright (C) 1997-2011 AudioScience Inc. <support@audioscience.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation;
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
Array initializer for PCI card IDs
(C) Copyright AudioScience Inc. 1998-2003
*******************************************************************************/
/*NOTE: when adding new lines to this header file
they MUST be grouped by HPI entry point.
*/
{
HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DSP6205,
HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
(kernel_ulong_t) HPI_6205}
, {
HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_PCI2040,
HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
(kernel_ulong_t) HPI_6000}
, {
0}

1728
sound/pci/atiixp.c Normal file

File diff suppressed because it is too large Load diff

1349
sound/pci/atiixp_modem.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
snd-au8810-objs := au8810.o
snd-au8820-objs := au8820.o
snd-au8830-objs := au8830.o
obj-$(CONFIG_SND_AU8810) += snd-au8810.o
obj-$(CONFIG_SND_AU8820) += snd-au8820.o
obj-$(CONFIG_SND_AU8830) += snd-au8830.o

16
sound/pci/au88x0/au8810.c Normal file
View file

@ -0,0 +1,16 @@
#include "au8810.h"
#include "au88x0.h"
static const struct pci_device_id snd_vortex_ids[] = {
{PCI_VDEVICE(AUREAL, PCI_DEVICE_ID_AUREAL_ADVANTAGE), 1,},
{0,}
};
#include "au88x0_core.c"
#include "au88x0_pcm.c"
#include "au88x0_mixer.c"
#include "au88x0_mpu401.c"
#include "au88x0_game.c"
#include "au88x0_eq.c"
#include "au88x0_a3d.c"
#include "au88x0_xtalk.c"
#include "au88x0.c"

224
sound/pci/au88x0/au8810.h Normal file
View file

@ -0,0 +1,224 @@
/*
Aureal Advantage Soundcard driver.
*/
#define CHIP_AU8810
#define CARD_NAME "Aureal Advantage"
#define CARD_NAME_SHORT "au8810"
#define NR_ADB 0x10
#define NR_WT 0x00
#define NR_SRC 0x10
#define NR_A3D 0x10
#define NR_MIXIN 0x20
#define NR_MIXOUT 0x10
/* ADBDMA */
#define VORTEX_ADBDMA_STAT 0x27e00 /* read only, subbuffer, DMA pos */
#define POS_MASK 0x00000fff
#define POS_SHIFT 0x0
#define ADB_SUBBUF_MASK 0x00003000 /* ADB only. */
#define ADB_SUBBUF_SHIFT 0xc /* ADB only. */
#define VORTEX_ADBDMA_CTRL 0x27180 /* write only; format, flags, DMA pos */
#define OFFSET_MASK 0x00000fff
#define OFFSET_SHIFT 0x0
#define IE_MASK 0x00001000 /* interrupt enable. */
#define IE_SHIFT 0xc
#define DIR_MASK 0x00002000 /* Direction */
#define DIR_SHIFT 0xd
#define FMT_MASK 0x0003c000
#define FMT_SHIFT 0xe
// The ADB masks and shift also are valid for the wtdma, except if specified otherwise.
#define VORTEX_ADBDMA_BUFCFG0 0x27100
#define VORTEX_ADBDMA_BUFCFG1 0x27104
#define VORTEX_ADBDMA_BUFBASE 0x27000
#define VORTEX_ADBDMA_START 0x27c00 /* Which subbuffer starts */
#define VORTEX_ADBDMA_STATUS 0x27A90 /* stored at AdbDma->this_10 / 2 DWORD in size. */
/* WTDMA */
#define VORTEX_WTDMA_CTRL 0x27fd8 /* format, DMA pos */
#define VORTEX_WTDMA_STAT 0x27fe8 /* DMA subbuf, DMA pos */
#define WT_SUBBUF_MASK 0x3
#define WT_SUBBUF_SHIFT 0xc
#define VORTEX_WTDMA_BUFBASE 0x27fc0
#define VORTEX_WTDMA_BUFCFG0 0x27fd0
#define VORTEX_WTDMA_BUFCFG1 0x27fd4
#define VORTEX_WTDMA_START 0x27fe4 /* which subbuffer is first */
/* ADB */
#define VORTEX_ADB_SR 0x28400 /* Samplerates enable/disable */
#define VORTEX_ADB_RTBASE 0x28000
#define VORTEX_ADB_RTBASE_COUNT 173
#define VORTEX_ADB_CHNBASE 0x282b4
#define VORTEX_ADB_CHNBASE_COUNT 24
#define ROUTE_MASK 0xffff
#define SOURCE_MASK 0xff00
#define ADB_MASK 0xff
#define ADB_SHIFT 0x8
/* ADB address */
#define OFFSET_ADBDMA 0x00
#define OFFSET_SRCIN 0x40
#define OFFSET_SRCOUT 0x20
#define OFFSET_MIXIN 0x50
#define OFFSET_MIXOUT 0x30
#define OFFSET_CODECIN 0x70
#define OFFSET_CODECOUT 0x88
#define OFFSET_SPORTIN 0x78 /* ch 0x13 */
#define OFFSET_SPORTOUT 0x90
#define OFFSET_SPDIFOUT 0x92 /* ch 0x14 check this! */
#define OFFSET_EQIN 0xa0
#define OFFSET_EQOUT 0x7e /* 2 routes on ch 0x11 */
#define OFFSET_XTALKOUT 0x66 /* crosstalk canceller (source) */
#define OFFSET_XTALKIN 0x96 /* crosstalk canceller (sink) */
#define OFFSET_A3DIN 0x70 /* ADB sink. */
#define OFFSET_A3DOUT 0xA6 /* ADB source. 2 routes per slice = 8 */
#define OFFSET_EFXIN 0x80 /* ADB sink. */
#define OFFSET_EFXOUT 0x68 /* ADB source. */
/* ADB route translate helper */
#define ADB_DMA(x) (x)
#define ADB_SRCOUT(x) (x + OFFSET_SRCOUT)
#define ADB_SRCIN(x) (x + OFFSET_SRCIN)
#define ADB_MIXOUT(x) (x + OFFSET_MIXOUT)
#define ADB_MIXIN(x) (x + OFFSET_MIXIN)
#define ADB_CODECIN(x) (x + OFFSET_CODECIN)
#define ADB_CODECOUT(x) (x + OFFSET_CODECOUT)
#define ADB_SPORTIN(x) (x + OFFSET_SPORTIN)
#define ADB_SPORTOUT(x) (x + OFFSET_SPORTOUT)
#define ADB_SPDIFOUT(x) (x + OFFSET_SPDIFOUT)
#define ADB_EQIN(x) (x + OFFSET_EQIN)
#define ADB_EQOUT(x) (x + OFFSET_EQOUT)
#define ADB_A3DOUT(x) (x + OFFSET_A3DOUT) /* 0x10 A3D blocks */
#define ADB_A3DIN(x) (x + OFFSET_A3DIN)
#define ADB_XTALKIN(x) (x + OFFSET_XTALKIN)
#define ADB_XTALKOUT(x) (x + OFFSET_XTALKOUT)
#define MIX_OUTL 0xe
#define MIX_OUTR 0xf
#define MIX_INL 0x1e
#define MIX_INR 0x1f
#define MIX_DEFIGAIN 0x08 /* 0x8 => 6dB */
#define MIX_DEFOGAIN 0x08
/* MIXER */
#define VORTEX_MIXER_SR 0x21f00
#define VORTEX_MIXER_CLIP 0x21f80
#define VORTEX_MIXER_CHNBASE 0x21e40
#define VORTEX_MIXER_RTBASE 0x21e00
#define MIXER_RTBASE_SIZE 0x38
#define VORTEX_MIX_ENIN 0x21a00 /* Input enable bits. 4 bits wide. */
#define VORTEX_MIX_SMP 0x21c00 /* AU8820: 0x9c00 */
/* MIX */
#define VORTEX_MIX_INVOL_A 0x21000 /* in? */
#define VORTEX_MIX_INVOL_B 0x20000 /* out? */
#define VORTEX_MIX_VOL_A 0x21800
#define VORTEX_MIX_VOL_B 0x20800
#define VOL_MIN 0x80 /* Input volume when muted. */
#define VOL_MAX 0x7f /* FIXME: Not confirmed! Just guessed. */
/* SRC */
#define VORTEX_SRC_CHNBASE 0x26c40
#define VORTEX_SRC_RTBASE 0x26c00
#define VORTEX_SRCBLOCK_SR 0x26cc0
#define VORTEX_SRC_SOURCE 0x26cc4
#define VORTEX_SRC_SOURCESIZE 0x26cc8
/* Params
0x26e00 : 1 U0
0x26e40 : 2 CR
0x26e80 : 3 U3
0x26ec0 : 4 DRIFT1
0x26f00 : 5 U1
0x26f40 : 6 DRIFT2
0x26f80 : 7 U2 : Target rate, direction
*/
#define VORTEX_SRC_CONVRATIO 0x26e40
#define VORTEX_SRC_DRIFT0 0x26e80
#define VORTEX_SRC_DRIFT1 0x26ec0
#define VORTEX_SRC_DRIFT2 0x26f40
#define VORTEX_SRC_U0 0x26e00
#define U0_SLOWLOCK 0x200
#define VORTEX_SRC_U1 0x26f00
#define VORTEX_SRC_U2 0x26f80
#define VORTEX_SRC_DATA 0x26800 /* 0xc800 */
#define VORTEX_SRC_DATA0 0x26000
/* FIFO */
#define VORTEX_FIFO_ADBCTRL 0x16100 /* Control bits. */
#define VORTEX_FIFO_WTCTRL 0x16000
#define FIFO_RDONLY 0x00000001
#define FIFO_CTRL 0x00000002 /* Allow ctrl. ? */
#define FIFO_VALID 0x00000010
#define FIFO_EMPTY 0x00000020
#define FIFO_U0 0x00001000 /* Unknown. */
#define FIFO_U1 0x00010000
#define FIFO_SIZE_BITS 5
#define FIFO_SIZE (1<<FIFO_SIZE_BITS) // 0x20
#define FIFO_MASK (FIFO_SIZE-1) //0x1f /* at shift left 0xc */
//#define FIFO_MASK 0x1f /* at shift left 0xb */
//#define FIFO_SIZE 0x20
#define FIFO_BITS 0x03880000
#define VORTEX_FIFO_ADBDATA 0x14000
#define VORTEX_FIFO_WTDATA 0x10000
/* CODEC */
#define VORTEX_CODEC_CTRL 0x29184
#define VORTEX_CODEC_EN 0x29190
#define EN_CODEC0 0x00000300
#define EN_AC98 0x00000c00 /* Modem AC98 slots. */
#define EN_CODEC1 0x00003000
#define EN_CODEC (EN_CODEC0 | EN_CODEC1)
#define EN_SPORT 0x00030000
#define EN_SPDIF 0x000c0000
#define VORTEX_CODEC_CHN 0x29080
#define VORTEX_CODEC_IO 0x29188
/* SPDIF */
#define VORTEX_SPDIF_FLAGS 0x2205c
#define VORTEX_SPDIF_CFG0 0x291D0
#define VORTEX_SPDIF_CFG1 0x291D4
#define VORTEX_SPDIF_SMPRATE 0x29194
/* Sample timer */
#define VORTEX_SMP_TIME 0x29198
#define VORTEX_MODEM_CTRL 0x291ac
/* IRQ */
#define VORTEX_IRQ_SOURCE 0x2a000 /* Interrupt source flags. */
#define VORTEX_IRQ_CTRL 0x2a004 /* Interrupt source mask. */
#define VORTEX_STAT 0x2a008 /* Status */
#define VORTEX_CTRL 0x2a00c
#define CTRL_MIDI_EN 0x00000001
#define CTRL_MIDI_PORT 0x00000060
#define CTRL_GAME_EN 0x00000008
#define CTRL_GAME_PORT 0x00000e00
//#define CTRL_IRQ_ENABLE 0x01004000
#define CTRL_IRQ_ENABLE 0x00004000
/* write: Timer period config / read: TIMER IRQ ack. */
#define VORTEX_IRQ_STAT 0x2919c
/* DMA */
#define VORTEX_ENGINE_CTRL 0x27ae8
#define ENGINE_INIT 0x1380000
/* MIDI *//* GAME. */
#define VORTEX_MIDI_DATA 0x28800
#define VORTEX_MIDI_CMD 0x28804 /* Write command / Read status */
#define VORTEX_CTRL2 0x2880c
#define CTRL2_GAME_ADCMODE 0x40
#define VORTEX_GAME_LEGACY 0x28808
#define VORTEX_GAME_AXIS 0x28810
#define AXIS_SIZE 4
#define AXIS_RANGE 0x1fff

14
sound/pci/au88x0/au8820.c Normal file
View file

@ -0,0 +1,14 @@
#include "au8820.h"
#include "au88x0.h"
static const struct pci_device_id snd_vortex_ids[] = {
{PCI_VDEVICE(AUREAL, PCI_DEVICE_ID_AUREAL_VORTEX_1), 0,},
{0,}
};
#include "au88x0_synth.c"
#include "au88x0_core.c"
#include "au88x0_pcm.c"
#include "au88x0_mpu401.c"
#include "au88x0_game.c"
#include "au88x0_mixer.c"
#include "au88x0.c"

204
sound/pci/au88x0/au8820.h Normal file
View file

@ -0,0 +1,204 @@
/*
Aureal Vortex Soundcard driver.
IO addr collected from asp4core.vxd:
function address
0005D5A0 13004
00080674 14004
00080AFF 12818
*/
#define CHIP_AU8820
#define CARD_NAME "Aureal Vortex"
#define CARD_NAME_SHORT "au8820"
/* Number of ADB and WT channels */
#define NR_ADB 0x10
#define NR_WT 0x20
#define NR_SRC 0x10
#define NR_A3D 0x00
#define NR_MIXIN 0x10
#define NR_MIXOUT 0x10
/* ADBDMA */
#define VORTEX_ADBDMA_STAT 0x105c0 /* read only, subbuffer, DMA pos */
#define POS_MASK 0x00000fff
#define POS_SHIFT 0x0
#define ADB_SUBBUF_MASK 0x00003000 /* ADB only. */
#define ADB_SUBBUF_SHIFT 0xc /* ADB only. */
#define VORTEX_ADBDMA_CTRL 0x10580 /* write only, format, flags, DMA pos */
#define OFFSET_MASK 0x00000fff
#define OFFSET_SHIFT 0x0
#define IE_MASK 0x00001000 /* interrupt enable. */
#define IE_SHIFT 0xc
#define DIR_MASK 0x00002000 /* Direction. */
#define DIR_SHIFT 0xd
#define FMT_MASK 0x0003c000
#define FMT_SHIFT 0xe
// The masks and shift also work for the wtdma, if not specified otherwise.
#define VORTEX_ADBDMA_BUFCFG0 0x10400
#define VORTEX_ADBDMA_BUFCFG1 0x10404
#define VORTEX_ADBDMA_BUFBASE 0x10200
#define VORTEX_ADBDMA_START 0x106c0 /* Which subbuffer starts */
#define VORTEX_ADBDMA_STATUS 0x10600 /* stored at AdbDma->this_10 / 2 DWORD in size. */
/* ADB */
#define VORTEX_ADB_SR 0x10a00 /* Samplerates enable/disable */
#define VORTEX_ADB_RTBASE 0x10800
#define VORTEX_ADB_RTBASE_COUNT 103
#define VORTEX_ADB_CHNBASE 0x1099c
#define VORTEX_ADB_CHNBASE_COUNT 22
#define ROUTE_MASK 0x3fff
#define ADB_MASK 0x7f
#define ADB_SHIFT 0x7
//#define ADB_MIX_MASK 0xf
/* ADB address */
#define OFFSET_ADBDMA 0x00
#define OFFSET_SRCOUT 0x10 /* on channel 0x11 */
#define OFFSET_SRCIN 0x10 /* on channel < 0x11 */
#define OFFSET_MIXOUT 0x20 /* source */
#define OFFSET_MIXIN 0x30 /* sink */
#define OFFSET_CODECIN 0x48 /* ADB source */
#define OFFSET_CODECOUT 0x58 /* ADB sink/target */
#define OFFSET_SPORTOUT 0x60 /* sink */
#define OFFSET_SPORTIN 0x50 /* source */
#define OFFSET_EFXOUT 0x50 /* sink */
#define OFFSET_EFXIN 0x40 /* source */
#define OFFSET_A3DOUT 0x00 /* This card has no HRTF :( */
#define OFFSET_A3DIN 0x00
#define OFFSET_WTOUT 0x58 /* */
/* ADB route translate helper */
#define ADB_DMA(x) (x + OFFSET_ADBDMA)
#define ADB_SRCOUT(x) (x + OFFSET_SRCOUT)
#define ADB_SRCIN(x) (x + OFFSET_SRCIN)
#define ADB_MIXOUT(x) (x + OFFSET_MIXOUT)
#define ADB_MIXIN(x) (x + OFFSET_MIXIN)
#define ADB_CODECIN(x) (x + OFFSET_CODECIN)
#define ADB_CODECOUT(x) (x + OFFSET_CODECOUT)
#define ADB_SPORTOUT(x) (x + OFFSET_SPORTOUT)
#define ADB_SPORTIN(x) (x + OFFSET_SPORTIN) /* */
#define ADB_A3DOUT(x) (x + OFFSET_A3DOUT) /* 8 A3D blocks */
#define ADB_A3DIN(x) (x + OFFSET_A3DIN)
#define ADB_WTOUT(x,y) (y + OFFSET_WTOUT)
/* WTDMA */
#define VORTEX_WTDMA_CTRL 0x10500 /* format, DMA pos */
#define VORTEX_WTDMA_STAT 0x10500 /* DMA subbuf, DMA pos */
#define WT_SUBBUF_MASK (0x3 << WT_SUBBUF_SHIFT)
#define WT_SUBBUF_SHIFT 0x15
#define VORTEX_WTDMA_BUFBASE 0x10000
#define VORTEX_WTDMA_BUFCFG0 0x10300
#define VORTEX_WTDMA_BUFCFG1 0x10304
#define VORTEX_WTDMA_START 0x10640 /* which subbuffer is first */
#define VORTEX_WT_BASE 0x9000
/* MIXER */
#define VORTEX_MIXER_SR 0x9f00
#define VORTEX_MIXER_CLIP 0x9f80
#define VORTEX_MIXER_CHNBASE 0x9e40
#define VORTEX_MIXER_RTBASE 0x9e00
#define MIXER_RTBASE_SIZE 0x26
#define VORTEX_MIX_ENIN 0x9a00 /* Input enable bits. 4 bits wide. */
#define VORTEX_MIX_SMP 0x9c00
/* MIX */
#define VORTEX_MIX_INVOL_A 0x9000 /* in? */
#define VORTEX_MIX_INVOL_B 0x8000 /* out? */
#define VORTEX_MIX_VOL_A 0x9800
#define VORTEX_MIX_VOL_B 0x8800
#define VOL_MIN 0x80 /* Input volume when muted. */
#define VOL_MAX 0x7f /* FIXME: Not confirmed! Just guessed. */
//#define MIX_OUTL 0xe
//#define MIX_OUTR 0xf
//#define MIX_INL 0xe
//#define MIX_INR 0xf
#define MIX_DEFIGAIN 0x08 /* 0x8 => 6dB */
#define MIX_DEFOGAIN 0x08
/* SRC */
#define VORTEX_SRCBLOCK_SR 0xccc0
#define VORTEX_SRC_CHNBASE 0xcc40
#define VORTEX_SRC_RTBASE 0xcc00
#define VORTEX_SRC_SOURCE 0xccc4
#define VORTEX_SRC_SOURCESIZE 0xccc8
#define VORTEX_SRC_U0 0xce00
#define VORTEX_SRC_DRIFT0 0xce80
#define VORTEX_SRC_DRIFT1 0xcec0
#define VORTEX_SRC_U1 0xcf00
#define VORTEX_SRC_DRIFT2 0xcf40
#define VORTEX_SRC_U2 0xcf80
#define VORTEX_SRC_DATA 0xc800
#define VORTEX_SRC_DATA0 0xc000
#define VORTEX_SRC_CONVRATIO 0xce40
//#define SRC_RATIO(x) ((((x<<15)/48000) + 1)/2) /* Playback */
//#define SRC_RATIO2(x) ((((48000<<15)/x) + 1)/2) /* Recording */
/* FIFO */
#define VORTEX_FIFO_ADBCTRL 0xf800 /* Control bits. */
#define VORTEX_FIFO_WTCTRL 0xf840
#define FIFO_RDONLY 0x00000001
#define FIFO_CTRL 0x00000002 /* Allow ctrl. ? */
#define FIFO_VALID 0x00000010
#define FIFO_EMPTY 0x00000020
#define FIFO_U0 0x00001000 /* Unknown. */
#define FIFO_U1 0x00010000
#define FIFO_SIZE_BITS 5
#define FIFO_SIZE (1<<FIFO_SIZE_BITS) // 0x20
#define FIFO_MASK (FIFO_SIZE-1) //0x1f /* at shift left 0xc */
#define VORTEX_FIFO_ADBDATA 0xe000
#define VORTEX_FIFO_WTDATA 0xe800
/* CODEC */
#define VORTEX_CODEC_CTRL 0x11984
#define VORTEX_CODEC_EN 0x11990
#define EN_CODEC 0x00000300
#define EN_SPORT 0x00030000
#define EN_SPDIF 0x000c0000
#define VORTEX_CODEC_CHN 0x11880
#define VORTEX_CODEC_IO 0x11988
#define VORTEX_SPDIF_FLAGS 0x1005c /* FIXME */
#define VORTEX_SPDIF_CFG0 0x119D0
#define VORTEX_SPDIF_CFG1 0x119D4
#define VORTEX_SPDIF_SMPRATE 0x11994
/* Sample timer */
#define VORTEX_SMP_TIME 0x11998
/* IRQ */
#define VORTEX_IRQ_SOURCE 0x12800 /* Interrupt source flags. */
#define VORTEX_IRQ_CTRL 0x12804 /* Interrupt source mask. */
#define VORTEX_STAT 0x12808 /* ?? */
#define VORTEX_CTRL 0x1280c
#define CTRL_MIDI_EN 0x00000001
#define CTRL_MIDI_PORT 0x00000060
#define CTRL_GAME_EN 0x00000008
#define CTRL_GAME_PORT 0x00000e00
#define CTRL_IRQ_ENABLE 0x4000
/* write: Timer period config / read: TIMER IRQ ack. */
#define VORTEX_IRQ_STAT 0x1199c
/* DMA */
#define VORTEX_DMA_BUFFER 0x10200
#define VORTEX_ENGINE_CTRL 0x1060c
#define ENGINE_INIT 0x0L
/* MIDI *//* GAME. */
#define VORTEX_MIDI_DATA 0x11000
#define VORTEX_MIDI_CMD 0x11004 /* Write command / Read status */
#define VORTEX_GAME_LEGACY 0x11008
#define VORTEX_CTRL2 0x1100c
#define CTRL2_GAME_ADCMODE 0x40
#define VORTEX_GAME_AXIS 0x11010
#define AXIS_SIZE 4
#define AXIS_RANGE 0x1fff

17
sound/pci/au88x0/au8830.c Normal file
View file

@ -0,0 +1,17 @@
#include "au8830.h"
#include "au88x0.h"
static const struct pci_device_id snd_vortex_ids[] = {
{PCI_VDEVICE(AUREAL, PCI_DEVICE_ID_AUREAL_VORTEX_2), 0,},
{0,}
};
#include "au88x0_synth.c"
#include "au88x0_core.c"
#include "au88x0_pcm.c"
#include "au88x0_mixer.c"
#include "au88x0_mpu401.c"
#include "au88x0_game.c"
#include "au88x0_eq.c"
#include "au88x0_a3d.c"
#include "au88x0_xtalk.c"
#include "au88x0.c"

251
sound/pci/au88x0/au8830.h Normal file
View file

@ -0,0 +1,251 @@
/*
Aureal Vortex Soundcard driver.
IO addr collected from asp4core.vxd:
function address
0005D5A0 13004
00080674 14004
00080AFF 12818
*/
#define CHIP_AU8830
#define CARD_NAME "Aureal Vortex 2"
#define CARD_NAME_SHORT "au8830"
#define NR_ADB 0x20
#define NR_SRC 0x10
#define NR_A3D 0x10
#define NR_MIXIN 0x20
#define NR_MIXOUT 0x10
#define NR_WT 0x40
/* ADBDMA */
#define VORTEX_ADBDMA_STAT 0x27e00 /* read only, subbuffer, DMA pos */
#define POS_MASK 0x00000fff
#define POS_SHIFT 0x0
#define ADB_SUBBUF_MASK 0x00003000 /* ADB only. */
#define ADB_SUBBUF_SHIFT 0xc /* ADB only. */
#define VORTEX_ADBDMA_CTRL 0x27a00 /* write only; format, flags, DMA pos */
#define OFFSET_MASK 0x00000fff
#define OFFSET_SHIFT 0x0
#define IE_MASK 0x00001000 /* interrupt enable. */
#define IE_SHIFT 0xc
#define DIR_MASK 0x00002000 /* Direction. */
#define DIR_SHIFT 0xd
#define FMT_MASK 0x0003c000
#define FMT_SHIFT 0xe
#define ADB_FIFO_EN_SHIFT 0x15
#define ADB_FIFO_EN (1 << 0x15)
// The ADB masks and shift also are valid for the wtdma, except if specified otherwise.
#define VORTEX_ADBDMA_BUFCFG0 0x27800
#define VORTEX_ADBDMA_BUFCFG1 0x27804
#define VORTEX_ADBDMA_BUFBASE 0x27400
#define VORTEX_ADBDMA_START 0x27c00 /* Which subbuffer starts */
#define VORTEX_ADBDMA_STATUS 0x27A90 /* stored at AdbDma->this_10 / 2 DWORD in size. */
/* Starting at the MSB, each pair of bits seem to be the current DMA page. */
/* This current page bits are consistent (same value) with VORTEX_ADBDMA_STAT) */
/* DMA */
#define VORTEX_ENGINE_CTRL 0x27ae8
#define ENGINE_INIT 0x1380000
/* WTDMA */
#define VORTEX_WTDMA_CTRL 0x27900 /* format, DMA pos */
#define VORTEX_WTDMA_STAT 0x27d00 /* DMA subbuf, DMA pos */
#define WT_SUBBUF_MASK 0x3
#define WT_SUBBUF_SHIFT 0xc
#define VORTEX_WTDMA_BUFBASE 0x27000
#define VORTEX_WTDMA_BUFCFG0 0x27600
#define VORTEX_WTDMA_BUFCFG1 0x27604
#define VORTEX_WTDMA_START 0x27b00 /* which subbuffer is first */
/* ADB */
#define VORTEX_ADB_SR 0x28400 /* Samplerates enable/disable */
#define VORTEX_ADB_RTBASE 0x28000
#define VORTEX_ADB_RTBASE_COUNT 173
#define VORTEX_ADB_CHNBASE 0x282b4
#define VORTEX_ADB_CHNBASE_COUNT 24
#define ROUTE_MASK 0xffff
#define SOURCE_MASK 0xff00
#define ADB_MASK 0xff
#define ADB_SHIFT 0x8
/* ADB address */
#define OFFSET_ADBDMA 0x00
#define OFFSET_ADBDMAB 0x20
#define OFFSET_SRCIN 0x40
#define OFFSET_SRCOUT 0x20 /* ch 0x11 */
#define OFFSET_MIXIN 0x50 /* ch 0x11 */
#define OFFSET_MIXOUT 0x30 /* ch 0x11 */
#define OFFSET_CODECIN 0x70 /* ch 0x11 */ /* adb source */
#define OFFSET_CODECOUT 0x88 /* ch 0x11 */ /* adb target */
#define OFFSET_SPORTIN 0x78 /* ch 0x13 ADB source. 2 routes. */
#define OFFSET_SPORTOUT 0x90 /* ch 0x13 ADB sink. 2 routes. */
#define OFFSET_SPDIFIN 0x7A /* ch 0x14 ADB source. */
#define OFFSET_SPDIFOUT 0x92 /* ch 0x14 ADB sink. */
#define OFFSET_AC98IN 0x7c /* ch 0x14 ADB source. */
#define OFFSET_AC98OUT 0x94 /* ch 0x14 ADB sink. */
#define OFFSET_EQIN 0xa0 /* ch 0x11 */
#define OFFSET_EQOUT 0x7e /* ch 0x11 */ /* 2 routes on ch 0x11 */
#define OFFSET_A3DIN 0x70 /* ADB sink. */
#define OFFSET_A3DOUT 0xA6 /* ADB source. 2 routes per slice = 8 */
#define OFFSET_WT0 0x40 /* WT bank 0 output. 0x40 - 0x65 */
#define OFFSET_WT1 0x80 /* WT bank 1 output. 0x80 - 0xA5 */
/* WT sources offset : 0x00-0x1f Direct stream. */
/* WT sources offset : 0x20-0x25 Mixed Output. */
#define OFFSET_XTALKOUT 0x66 /* crosstalk canceller (source) 2 routes */
#define OFFSET_XTALKIN 0x96 /* crosstalk canceller (sink). 10 routes */
#define OFFSET_EFXOUT 0x68 /* ADB source. 8 routes. */
#define OFFSET_EFXIN 0x80 /* ADB sink. 8 routes. */
/* ADB route translate helper */
#define ADB_DMA(x) (x)
#define ADB_SRCOUT(x) (x + OFFSET_SRCOUT)
#define ADB_SRCIN(x) (x + OFFSET_SRCIN)
#define ADB_MIXOUT(x) (x + OFFSET_MIXOUT)
#define ADB_MIXIN(x) (x + OFFSET_MIXIN)
#define ADB_CODECIN(x) (x + OFFSET_CODECIN)
#define ADB_CODECOUT(x) (x + OFFSET_CODECOUT)
#define ADB_SPORTIN(x) (x + OFFSET_SPORTIN)
#define ADB_SPORTOUT(x) (x + OFFSET_SPORTOUT)
#define ADB_SPDIFIN(x) (x + OFFSET_SPDIFIN)
#define ADB_SPDIFOUT(x) (x + OFFSET_SPDIFOUT)
#define ADB_EQIN(x) (x + OFFSET_EQIN)
#define ADB_EQOUT(x) (x + OFFSET_EQOUT)
#define ADB_A3DOUT(x) (x + OFFSET_A3DOUT) /* 0x10 A3D blocks */
#define ADB_A3DIN(x) (x + OFFSET_A3DIN)
//#define ADB_WTOUT(x) ((x<x20)?(x + OFFSET_WT0):(x + OFFSET_WT1))
#define ADB_WTOUT(x,y) (((x)==0)?((y) + OFFSET_WT0):((y) + OFFSET_WT1))
#define ADB_XTALKIN(x) ((x) + OFFSET_XTALKIN)
#define ADB_XTALKOUT(x) ((x) + OFFSET_XTALKOUT)
#define MIX_DEFIGAIN 0x08
#define MIX_DEFOGAIN 0x08 /* 0x8->6dB (6dB = x4) 16 to 18 bit conversion? */
/* MIXER */
#define VORTEX_MIXER_SR 0x21f00
#define VORTEX_MIXER_CLIP 0x21f80
#define VORTEX_MIXER_CHNBASE 0x21e40
#define VORTEX_MIXER_RTBASE 0x21e00
#define MIXER_RTBASE_SIZE 0x38
#define VORTEX_MIX_ENIN 0x21a00 /* Input enable bits. 4 bits wide. */
#define VORTEX_MIX_SMP 0x21c00 /* wave data buffers. AU8820: 0x9c00 */
/* MIX */
#define VORTEX_MIX_INVOL_B 0x20000 /* Input volume current */
#define VORTEX_MIX_VOL_B 0x20800 /* Output Volume current */
#define VORTEX_MIX_INVOL_A 0x21000 /* Input Volume target */
#define VORTEX_MIX_VOL_A 0x21800 /* Output Volume target */
#define VOL_MIN 0x80 /* Input volume when muted. */
#define VOL_MAX 0x7f /* FIXME: Not confirmed! Just guessed. */
/* SRC */
#define VORTEX_SRC_CHNBASE 0x26c40
#define VORTEX_SRC_RTBASE 0x26c00
#define VORTEX_SRCBLOCK_SR 0x26cc0
#define VORTEX_SRC_SOURCE 0x26cc4
#define VORTEX_SRC_SOURCESIZE 0x26cc8
/* Params
0x26e00 : 1 U0
0x26e40 : 2 CR
0x26e80 : 3 U3
0x26ec0 : 4 DRIFT1
0x26f00 : 5 U1
0x26f40 : 6 DRIFT2
0x26f80 : 7 U2 : Target rate, direction
*/
#define VORTEX_SRC_CONVRATIO 0x26e40
#define VORTEX_SRC_DRIFT0 0x26e80
#define VORTEX_SRC_DRIFT1 0x26ec0
#define VORTEX_SRC_DRIFT2 0x26f40
#define VORTEX_SRC_U0 0x26e00
#define U0_SLOWLOCK 0x200
#define VORTEX_SRC_U1 0x26f00
#define VORTEX_SRC_U2 0x26f80
#define VORTEX_SRC_DATA 0x26800 /* 0xc800 */
#define VORTEX_SRC_DATA0 0x26000
/* FIFO */
#define VORTEX_FIFO_ADBCTRL 0x16100 /* Control bits. */
#define VORTEX_FIFO_WTCTRL 0x16000
#define FIFO_RDONLY 0x00000001
#define FIFO_CTRL 0x00000002 /* Allow ctrl. ? */
#define FIFO_VALID 0x00000010
#define FIFO_EMPTY 0x00000020
#define FIFO_U0 0x00002000 /* Unknown. */
#define FIFO_U1 0x00040000
#define FIFO_SIZE_BITS 6
#define FIFO_SIZE (1<<(FIFO_SIZE_BITS)) // 0x40
#define FIFO_MASK (FIFO_SIZE-1) //0x3f /* at shift left 0xc */
#define FIFO_BITS 0x1c400000
#define VORTEX_FIFO_ADBDATA 0x14000
#define VORTEX_FIFO_WTDATA 0x10000
#define VORTEX_FIFO_GIRT 0x17000 /* wt0, wt1, adb */
#define GIRT_COUNT 3
/* CODEC */
#define VORTEX_CODEC_CHN 0x29080 /* The name "CHN" is wrong. */
#define VORTEX_CODEC_CTRL 0x29184
#define VORTEX_CODEC_IO 0x29188
#define VORTEX_CODEC_SPORTCTRL 0x2918c
#define VORTEX_CODEC_EN 0x29190
#define EN_AUDIO0 0x00000300
#define EN_MODEM 0x00000c00
#define EN_AUDIO1 0x00003000
#define EN_SPORT 0x00030000
#define EN_SPDIF 0x000c0000
#define EN_CODEC (EN_AUDIO1 | EN_AUDIO0)
#define VORTEX_SPDIF_SMPRATE 0x29194
#define VORTEX_SPDIF_FLAGS 0x2205c
#define VORTEX_SPDIF_CFG0 0x291D0 /* status data */
#define VORTEX_SPDIF_CFG1 0x291D4
#define VORTEX_SMP_TIME 0x29198 /* Sample counter/timer */
#define VORTEX_SMP_TIMER 0x2919c
#define VORTEX_CODEC2_CTRL 0x291a0
#define VORTEX_MODEM_CTRL 0x291ac
/* IRQ */
#define VORTEX_IRQ_SOURCE 0x2a000 /* Interrupt source flags. */
#define VORTEX_IRQ_CTRL 0x2a004 /* Interrupt source mask. */
//#define VORTEX_IRQ_U0 0x2a008 /* ?? */
#define VORTEX_STAT 0x2a008 /* Some sort of status */
#define STAT_IRQ 0x00000001 /* This bitis set if the IRQ is valid. */
#define VORTEX_CTRL 0x2a00c
#define CTRL_MIDI_EN 0x00000001
#define CTRL_MIDI_PORT 0x00000060
#define CTRL_GAME_EN 0x00000008
#define CTRL_GAME_PORT 0x00000e00
#define CTRL_IRQ_ENABLE 0x00004000
#define CTRL_SPDIF 0x00000000 /* unknown. Please find this value */
#define CTRL_SPORT 0x00200000
#define CTRL_RST 0x00800000
#define CTRL_UNKNOWN 0x01000000
/* write: Timer period config / read: TIMER IRQ ack. */
#define VORTEX_IRQ_STAT 0x2919c
/* MIDI *//* GAME. */
#define VORTEX_MIDI_DATA 0x28800
#define VORTEX_MIDI_CMD 0x28804 /* Write command / Read status */
#define VORTEX_GAME_LEGACY 0x28808
#define VORTEX_CTRL2 0x2880c
#define CTRL2_GAME_ADCMODE 0x40
#define VORTEX_GAME_AXIS 0x28810 /* Axis base register. 4 axis's */
#define AXIS_SIZE 4
#define AXIS_RANGE 0x1fff

383
sound/pci/au88x0/au88x0.c Normal file
View file

@ -0,0 +1,383 @@
/*
* ALSA driver for the Aureal Vortex family of soundprocessors.
* Author: Manuel Jander (mjander@embedded.cl)
*
* This driver is the result of the OpenVortex Project from Savannah
* (savannah.nongnu.org/projects/openvortex). I would like to thank
* the developers of OpenVortex, Jeff Muizelaar and Kester Maddock, from
* whom i got plenty of help, and their codebase was invaluable.
* Thanks to the ALSA developers, they helped a lot working out
* the ALSA part.
* Thanks also to Sourceforge for maintaining the old binary drivers,
* and the forum, where developers could comunicate.
*
* Now at least i can play Legacy DOOM with MIDI music :-)
*/
#include "au88x0.h"
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <sound/initval.h>
// module parameters (see "Module Parameters")
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
static int pcifix[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 255 };
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
module_param_array(pcifix, int, NULL, 0444);
MODULE_PARM_DESC(pcifix, "Enable VIA-workaround for " CARD_NAME " soundcard.");
MODULE_DESCRIPTION("Aureal vortex");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{Aureal Semiconductor Inc., Aureal Vortex Sound Processor}}");
MODULE_DEVICE_TABLE(pci, snd_vortex_ids);
static void vortex_fix_latency(struct pci_dev *vortex)
{
int rc;
if (!(rc = pci_write_config_byte(vortex, 0x40, 0xff))) {
pr_info( CARD_NAME
": vortex latency is 0xff\n");
} else {
pr_warn( CARD_NAME
": could not set vortex latency: pci error 0x%x\n", rc);
}
}
static void vortex_fix_agp_bridge(struct pci_dev *via)
{
int rc;
u8 value;
/*
* only set the bit (Extend PCI#2 Internal Master for
* Efficient Handling of Dummy Requests) if the can
* read the config and it is not already set
*/
if (!(rc = pci_read_config_byte(via, 0x42, &value))
&& ((value & 0x10)
|| !(rc = pci_write_config_byte(via, 0x42, value | 0x10)))) {
pr_info( CARD_NAME
": bridge config is 0x%x\n", value | 0x10);
} else {
pr_warn( CARD_NAME
": could not set vortex latency: pci error 0x%x\n", rc);
}
}
static void snd_vortex_workaround(struct pci_dev *vortex, int fix)
{
struct pci_dev *via = NULL;
/* autodetect if workarounds are required */
if (fix == 255) {
/* VIA KT133 */
via = pci_get_device(PCI_VENDOR_ID_VIA,
PCI_DEVICE_ID_VIA_8365_1, NULL);
/* VIA Apollo */
if (via == NULL) {
via = pci_get_device(PCI_VENDOR_ID_VIA,
PCI_DEVICE_ID_VIA_82C598_1, NULL);
/* AMD Irongate */
if (via == NULL)
via = pci_get_device(PCI_VENDOR_ID_AMD,
PCI_DEVICE_ID_AMD_FE_GATE_7007, NULL);
}
if (via) {
pr_info( CARD_NAME ": Activating latency workaround...\n");
vortex_fix_latency(vortex);
vortex_fix_agp_bridge(via);
}
} else {
if (fix & 0x1)
vortex_fix_latency(vortex);
if ((fix & 0x2) && (via = pci_get_device(PCI_VENDOR_ID_VIA,
PCI_DEVICE_ID_VIA_8365_1, NULL)))
vortex_fix_agp_bridge(via);
if ((fix & 0x4) && (via = pci_get_device(PCI_VENDOR_ID_VIA,
PCI_DEVICE_ID_VIA_82C598_1, NULL)))
vortex_fix_agp_bridge(via);
if ((fix & 0x8) && (via = pci_get_device(PCI_VENDOR_ID_AMD,
PCI_DEVICE_ID_AMD_FE_GATE_7007, NULL)))
vortex_fix_agp_bridge(via);
}
pci_dev_put(via);
}
// component-destructor
// (see "Management of Cards and Components")
static int snd_vortex_dev_free(struct snd_device *device)
{
vortex_t *vortex = device->device_data;
vortex_gameport_unregister(vortex);
vortex_core_shutdown(vortex);
// Take down PCI interface.
free_irq(vortex->irq, vortex);
iounmap(vortex->mmio);
pci_release_regions(vortex->pci_dev);
pci_disable_device(vortex->pci_dev);
kfree(vortex);
return 0;
}
// chip-specific constructor
// (see "Management of Cards and Components")
static int
snd_vortex_create(struct snd_card *card, struct pci_dev *pci, vortex_t ** rchip)
{
vortex_t *chip;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_vortex_dev_free,
};
*rchip = NULL;
// check PCI availability (DMA).
if ((err = pci_enable_device(pci)) < 0)
return err;
if (pci_set_dma_mask(pci, DMA_BIT_MASK(32)) < 0 ||
pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32)) < 0) {
pr_err( "error to set DMA mask\n");
pci_disable_device(pci);
return -ENXIO;
}
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
if (chip == NULL) {
pci_disable_device(pci);
return -ENOMEM;
}
chip->card = card;
// initialize the stuff
chip->pci_dev = pci;
chip->io = pci_resource_start(pci, 0);
chip->vendor = pci->vendor;
chip->device = pci->device;
chip->card = card;
chip->irq = -1;
// (1) PCI resource allocation
// Get MMIO area
//
if ((err = pci_request_regions(pci, CARD_NAME_SHORT)) != 0)
goto regions_out;
chip->mmio = pci_ioremap_bar(pci, 0);
if (!chip->mmio) {
pr_err( "MMIO area remap failed.\n");
err = -ENOMEM;
goto ioremap_out;
}
/* Init audio core.
* This must be done before we do request_irq otherwise we can get spurious
* interrupts that we do not handle properly and make a mess of things */
if ((err = vortex_core_init(chip)) != 0) {
pr_err( "hw core init failed\n");
goto core_out;
}
if ((err = request_irq(pci->irq, vortex_interrupt,
IRQF_SHARED, KBUILD_MODNAME,
chip)) != 0) {
pr_err( "cannot grab irq\n");
goto irq_out;
}
chip->irq = pci->irq;
pci_set_master(pci);
// End of PCI setup.
// Register alsa root device.
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
goto alloc_out;
}
*rchip = chip;
return 0;
alloc_out:
free_irq(chip->irq, chip);
irq_out:
vortex_core_shutdown(chip);
core_out:
iounmap(chip->mmio);
ioremap_out:
pci_release_regions(chip->pci_dev);
regions_out:
pci_disable_device(chip->pci_dev);
//FIXME: this not the right place to unregister the gameport
vortex_gameport_unregister(chip);
kfree(chip);
return err;
}
// constructor -- see "Constructor" sub-section
static int
snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
static int dev;
struct snd_card *card;
vortex_t *chip;
int err;
// (1)
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
// (2)
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0)
return err;
// (3)
if ((err = snd_vortex_create(card, pci, &chip)) < 0) {
snd_card_free(card);
return err;
}
snd_vortex_workaround(pci, pcifix[dev]);
// Card details needed in snd_vortex_midi
strcpy(card->driver, CARD_NAME_SHORT);
sprintf(card->shortname, "Aureal Vortex %s", CARD_NAME_SHORT);
sprintf(card->longname, "%s at 0x%lx irq %i",
card->shortname, chip->io, chip->irq);
// (4) Alloc components.
err = snd_vortex_mixer(chip);
if (err < 0) {
snd_card_free(card);
return err;
}
// ADB pcm.
err = snd_vortex_new_pcm(chip, VORTEX_PCM_ADB, NR_PCM);
if (err < 0) {
snd_card_free(card);
return err;
}
#ifndef CHIP_AU8820
// ADB SPDIF
if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_SPDIF, 1)) < 0) {
snd_card_free(card);
return err;
}
// A3D
if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_A3D, NR_A3D)) < 0) {
snd_card_free(card);
return err;
}
#endif
/*
// ADB I2S
if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_I2S, 1)) < 0) {
snd_card_free(card);
return err;
}
*/
#ifndef CHIP_AU8810
// WT pcm.
if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_WT, NR_WT)) < 0) {
snd_card_free(card);
return err;
}
#endif
if ((err = snd_vortex_midi(chip)) < 0) {
snd_card_free(card);
return err;
}
vortex_gameport_register(chip);
#if 0
if (snd_seq_device_new(card, 1, SNDRV_SEQ_DEV_ID_VORTEX_SYNTH,
sizeof(snd_vortex_synth_arg_t), &wave) < 0
|| wave == NULL) {
snd_printk(KERN_ERR "Can't initialize Aureal wavetable synth\n");
} else {
snd_vortex_synth_arg_t *arg;
arg = SNDRV_SEQ_DEVICE_ARGPTR(wave);
strcpy(wave->name, "Aureal Synth");
arg->hwptr = vortex;
arg->index = 1;
arg->seq_ports = seq_ports[dev];
arg->max_voices = max_synth_voices[dev];
}
#endif
// (5)
if ((err = pci_read_config_word(pci, PCI_DEVICE_ID,
&(chip->device))) < 0) {
snd_card_free(card);
return err;
}
if ((err = pci_read_config_word(pci, PCI_VENDOR_ID,
&(chip->vendor))) < 0) {
snd_card_free(card);
return err;
}
chip->rev = pci->revision;
#ifdef CHIP_AU8830
if ((chip->rev) != 0xfe && (chip->rev) != 0xfa) {
pr_alert(
"vortex: The revision (%x) of your card has not been seen before.\n",
chip->rev);
pr_alert(
"vortex: Please email the results of 'lspci -vv' to openvortex-dev@nongnu.org.\n");
snd_card_free(card);
err = -ENODEV;
return err;
}
#endif
// (6)
if ((err = snd_card_register(card)) < 0) {
snd_card_free(card);
return err;
}
// (7)
pci_set_drvdata(pci, card);
dev++;
vortex_connect_default(chip, 1);
vortex_enable_int(chip);
return 0;
}
// destructor -- see "Destructor" sub-section
static void snd_vortex_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
}
// pci_driver definition
static struct pci_driver vortex_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_vortex_ids,
.probe = snd_vortex_probe,
.remove = snd_vortex_remove,
};
module_pci_driver(vortex_driver);

295
sound/pci/au88x0/au88x0.h Normal file
View file

@ -0,0 +1,295 @@
/*
* 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 Library 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.
*/
#ifndef __SOUND_AU88X0_H
#define __SOUND_AU88X0_H
#ifdef __KERNEL__
#include <linux/pci.h>
#include <asm/io.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/rawmidi.h>
#include <sound/mpu401.h>
#include <sound/hwdep.h>
#include <sound/ac97_codec.h>
#include <sound/tlv.h>
#endif
#ifndef CHIP_AU8820
#include "au88x0_eq.h"
#include "au88x0_a3d.h"
#endif
#ifndef CHIP_AU8810
#include "au88x0_wt.h"
#endif
#define hwread(x,y) readl((x)+(y))
#define hwwrite(x,y,z) writel((z),(x)+(y))
/* Vortex MPU401 defines. */
#define MIDI_CLOCK_DIV 0x61
/* Standart MPU401 defines. */
#define MPU401_RESET 0xff
#define MPU401_ENTER_UART 0x3f
#define MPU401_ACK 0xfe
// Get src register value to convert from x to y.
#define SRC_RATIO(x,y) ((((x<<15)/y) + 1)/2)
/* FIFO software state constants. */
#define FIFO_STOP 0
#define FIFO_START 1
#define FIFO_PAUSE 2
/* IRQ flags */
#define IRQ_ERR_MASK 0x00ff
#define IRQ_FATAL 0x0001
#define IRQ_PARITY 0x0002
#define IRQ_REG 0x0004
#define IRQ_FIFO 0x0008
#define IRQ_DMA 0x0010
#define IRQ_PCMOUT 0x0020 /* PCM OUT page crossing */
#define IRQ_TIMER 0x1000
#define IRQ_MIDI 0x2000
#define IRQ_MODEM 0x4000
/* ADB Resource */
#define VORTEX_RESOURCE_DMA 0x00000000
#define VORTEX_RESOURCE_SRC 0x00000001
#define VORTEX_RESOURCE_MIXIN 0x00000002
#define VORTEX_RESOURCE_MIXOUT 0x00000003
#define VORTEX_RESOURCE_A3D 0x00000004
#define VORTEX_RESOURCE_LAST 0x00000005
/* codec io: VORTEX_CODEC_IO bits */
#define VORTEX_CODEC_ID_SHIFT 24
#define VORTEX_CODEC_WRITE 0x00800000
#define VORTEX_CODEC_ADDSHIFT 16
#define VORTEX_CODEC_ADDMASK 0x7f0000
#define VORTEX_CODEC_DATSHIFT 0
#define VORTEX_CODEC_DATMASK 0xffff
/* Check for SDAC bit in "Extended audio ID" AC97 register */
//#define VORTEX_IS_QUAD(x) (((x)->codec == NULL) ? 0 : ((x)->codec->ext_id&0x80))
#define VORTEX_IS_QUAD(x) ((x)->isquad)
/* Check if chip has bug. */
#define IS_BAD_CHIP(x) (\
(x->rev == 0xfe && x->device == PCI_DEVICE_ID_AUREAL_VORTEX_2) || \
(x->rev == 0xfe && x->device == PCI_DEVICE_ID_AUREAL_ADVANTAGE))
/* PCM devices */
#define VORTEX_PCM_ADB 0
#define VORTEX_PCM_SPDIF 1
#define VORTEX_PCM_A3D 2
#define VORTEX_PCM_WT 3
#define VORTEX_PCM_I2S 4
#define VORTEX_PCM_LAST 5
#define MIX_CAPT(x) (vortex->mixcapt[x])
#define MIX_PLAYB(x) (vortex->mixplayb[x])
#define MIX_SPDIF(x) (vortex->mixspdif[x])
#define NR_WTPB 0x20 /* WT channels per each bank. */
#define NR_PCM 0x10
struct pcm_vol {
struct snd_kcontrol *kctl;
int active;
int dma;
int mixin[4];
int vol[4];
};
/* Structs */
typedef struct {
//int this_08; /* Still unknown */
int fifo_enabled; /* this_24 */
int fifo_status; /* this_1c */
u32 dma_ctrl; /* this_78 (ADB), this_7c (WT) */
int dma_unknown; /* this_74 (ADB), this_78 (WT). WDM: +8 */
int cfg0;
int cfg1;
int nr_ch; /* Nr of PCM channels in use */
int type; /* Output type (ac97, a3d, spdif, i2s, dsp) */
int dma; /* Hardware DMA index. */
int dir; /* Stream Direction. */
u32 resources[5];
/* Virtual page extender stuff */
int nr_periods;
int period_bytes;
int period_real;
int period_virt;
struct snd_pcm_substream *substream;
} stream_t;
typedef struct snd_vortex vortex_t;
struct snd_vortex {
/* ALSA structs. */
struct snd_card *card;
struct snd_pcm *pcm[VORTEX_PCM_LAST];
struct snd_rawmidi *rmidi; /* Legacy Midi interface. */
struct snd_ac97 *codec;
/* Stream structs. */
stream_t dma_adb[NR_ADB];
int spdif_sr;
#ifndef CHIP_AU8810
stream_t dma_wt[NR_WT];
wt_voice_t wt_voice[NR_WT]; /* WT register cache. */
char mixwt[(NR_WT / NR_WTPB) * 6]; /* WT mixin objects */
#endif
/* Global resources */
s8 mixcapt[2];
s8 mixplayb[4];
#ifndef CHIP_AU8820
s8 mixspdif[2];
s8 mixa3d[2]; /* mixers which collect all a3d streams. */
s8 mixxtlk[2]; /* crosstalk canceler mixer inputs. */
#endif
u32 fixed_res[5];
#ifndef CHIP_AU8820
/* Hardware equalizer structs */
eqlzr_t eq;
/* A3D structs */
a3dsrc_t a3d[NR_A3D];
/* Xtalk canceler */
int xt_mode; /* 1: speakers, 0:headphones. */
#endif
struct pcm_vol pcm_vol[NR_PCM];
int isquad; /* cache of extended ID codec flag. */
/* Gameport stuff. */
struct gameport *gameport;
/* PCI hardware resources */
unsigned long io;
void __iomem *mmio;
unsigned int irq;
spinlock_t lock;
/* PCI device */
struct pci_dev *pci_dev;
u16 vendor;
u16 device;
u8 rev;
};
/* Functions. */
/* SRC */
static void vortex_adb_setsrc(vortex_t * vortex, int adbdma,
unsigned int cvrt, int dir);
/* DMA Engines. */
static void vortex_adbdma_setbuffers(vortex_t * vortex, int adbdma,
int size, int count);
static void vortex_adbdma_setmode(vortex_t * vortex, int adbdma, int ie,
int dir, int fmt, int d,
u32 offset);
static void vortex_adbdma_setstartbuffer(vortex_t * vortex, int adbdma, int sb);
#ifndef CHIP_AU8810
static void vortex_wtdma_setbuffers(vortex_t * vortex, int wtdma,
int size, int count);
static void vortex_wtdma_setmode(vortex_t * vortex, int wtdma, int ie, int fmt, int d, /*int e, */
u32 offset);
static void vortex_wtdma_setstartbuffer(vortex_t * vortex, int wtdma, int sb);
#endif
static void vortex_adbdma_startfifo(vortex_t * vortex, int adbdma);
//static void vortex_adbdma_stopfifo(vortex_t *vortex, int adbdma);
static void vortex_adbdma_pausefifo(vortex_t * vortex, int adbdma);
static void vortex_adbdma_resumefifo(vortex_t * vortex, int adbdma);
static inline int vortex_adbdma_getlinearpos(vortex_t * vortex, int adbdma);
static void vortex_adbdma_resetup(vortex_t *vortex, int adbdma);
#ifndef CHIP_AU8810
static void vortex_wtdma_startfifo(vortex_t * vortex, int wtdma);
static void vortex_wtdma_stopfifo(vortex_t * vortex, int wtdma);
static void vortex_wtdma_pausefifo(vortex_t * vortex, int wtdma);
static void vortex_wtdma_resumefifo(vortex_t * vortex, int wtdma);
static inline int vortex_wtdma_getlinearpos(vortex_t * vortex, int wtdma);
#endif
/* global stuff. */
static void vortex_codec_init(vortex_t * vortex);
static void vortex_codec_write(struct snd_ac97 * codec, unsigned short addr,
unsigned short data);
static unsigned short vortex_codec_read(struct snd_ac97 * codec, unsigned short addr);
static void vortex_spdif_init(vortex_t * vortex, int spdif_sr, int spdif_mode);
static int vortex_core_init(vortex_t * card);
static int vortex_core_shutdown(vortex_t * card);
static void vortex_enable_int(vortex_t * card);
static irqreturn_t vortex_interrupt(int irq, void *dev_id);
static int vortex_alsafmt_aspfmt(int alsafmt);
/* Connection stuff. */
static void vortex_connect_default(vortex_t * vortex, int en);
static int vortex_adb_allocroute(vortex_t * vortex, int dma, int nr_ch,
int dir, int type, int subdev);
static char vortex_adb_checkinout(vortex_t * vortex, int resmap[], int out,
int restype);
#ifndef CHIP_AU8810
static int vortex_wt_allocroute(vortex_t * vortex, int dma, int nr_ch);
static void vortex_wt_connect(vortex_t * vortex, int en);
static void vortex_wt_init(vortex_t * vortex);
#endif
static void vortex_route(vortex_t * vortex, int en, unsigned char channel,
unsigned char source, unsigned char dest);
#if 0
static void vortex_routes(vortex_t * vortex, int en, unsigned char channel,
unsigned char source, unsigned char dest0,
unsigned char dest1);
#endif
static void vortex_connection_mixin_mix(vortex_t * vortex, int en,
unsigned char mixin,
unsigned char mix, int a);
static void vortex_mix_setinputvolumebyte(vortex_t * vortex,
unsigned char mix, int mixin,
unsigned char vol);
static void vortex_mix_setvolumebyte(vortex_t * vortex, unsigned char mix,
unsigned char vol);
/* A3D functions. */
#ifndef CHIP_AU8820
static void vortex_Vort3D_enable(vortex_t * v);
static void vortex_Vort3D_disable(vortex_t * v);
static void vortex_Vort3D_connect(vortex_t * vortex, int en);
static void vortex_Vort3D_InitializeSource(a3dsrc_t * a, int en);
#endif
/* Driver stuff. */
static int vortex_gameport_register(vortex_t * card);
static void vortex_gameport_unregister(vortex_t * card);
#ifndef CHIP_AU8820
static int vortex_eq_init(vortex_t * vortex);
static int vortex_eq_free(vortex_t * vortex);
#endif
/* ALSA stuff. */
static int snd_vortex_new_pcm(vortex_t * vortex, int idx, int nr);
static int snd_vortex_mixer(vortex_t * vortex);
static int snd_vortex_midi(vortex_t * vortex);
#endif

View file

@ -0,0 +1,914 @@
/***************************************************************************
* au88x0_a3d.c
*
* Fri Jul 18 14:16:22 2003
* Copyright 2003 mjander
* mjander@users.sourceforge.net
*
* A3D. You may think i'm crazy, but this may work someday. Who knows...
****************************************************************************/
/*
* 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 Library 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 "au88x0_a3d.h"
#include "au88x0_a3ddata.c"
#include "au88x0_xtalk.h"
#include "au88x0.h"
static void
a3dsrc_SetTimeConsts(a3dsrc_t * a, short HrtfTrack, short ItdTrack,
short GTrack, short CTrack)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_HrtfTrackTC), HrtfTrack);
hwwrite(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_ITDTrackTC), ItdTrack);
hwwrite(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_GainTrackTC), GTrack);
hwwrite(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_CoeffTrackTC), CTrack);
}
#if 0
static void
a3dsrc_GetTimeConsts(a3dsrc_t * a, short *HrtfTrack, short *ItdTrack,
short *GTrack, short *CTrack)
{
// stub!
}
#endif
/* Atmospheric absorption. */
static void
a3dsrc_SetAtmosTarget(a3dsrc_t * a, short aa, short b, short c, short d,
short e)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_A21Target),
(e << 0x10) | d);
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_B10Target),
(b << 0x10) | aa);
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_B2Target), c);
}
static void
a3dsrc_SetAtmosCurrent(a3dsrc_t * a, short aa, short b, short c, short d,
short e)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_A12Current),
(e << 0x10) | d);
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_B01Current),
(b << 0x10) | aa);
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_B2Current), c);
}
static void
a3dsrc_SetAtmosState(a3dsrc_t * a, short x1, short x2, short y1, short y2)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_x1), x1);
hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_x2), x2);
hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_y1), y1);
hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_y2), y2);
}
#if 0
static void
a3dsrc_GetAtmosTarget(a3dsrc_t * a, short *aa, short *b, short *c,
short *d, short *e)
{
}
static void
a3dsrc_GetAtmosCurrent(a3dsrc_t * a, short *bb01, short *ab01, short *b2,
short *aa12, short *ba12)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
*aa12 =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_A12Current));
*ba12 =
hwread(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_A12Current));
*ab01 =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_B01Current));
*bb01 =
hwread(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_B01Current));
*b2 =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_B2Current));
}
static void
a3dsrc_GetAtmosState(a3dsrc_t * a, short *x1, short *x2, short *y1, short *y2)
{
}
#endif
/* HRTF */
static void
a3dsrc_SetHrtfTarget(a3dsrc_t * a, a3d_Hrtf_t const aa, a3d_Hrtf_t const b)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
int i;
for (i = 0; i < HRTF_SZ; i++)
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source,
A3D_B_HrtfTarget) + (i << 2),
(b[i] << 0x10) | aa[i]);
}
static void
a3dsrc_SetHrtfCurrent(a3dsrc_t * a, a3d_Hrtf_t const aa, a3d_Hrtf_t const b)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
int i;
for (i = 0; i < HRTF_SZ; i++)
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source,
A3D_B_HrtfCurrent) + (i << 2),
(b[i] << 0x10) | aa[i]);
}
static void
a3dsrc_SetHrtfState(a3dsrc_t * a, a3d_Hrtf_t const aa, a3d_Hrtf_t const b)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
int i;
for (i = 0; i < HRTF_SZ; i++)
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source,
A3D_B_HrtfDelayLine) + (i << 2),
(b[i] << 0x10) | aa[i]);
}
static void a3dsrc_SetHrtfOutput(a3dsrc_t * a, short left, short right)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_HrtfOutL), left);
hwwrite(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_HrtfOutR), right);
}
#if 0
static void a3dsrc_GetHrtfTarget(a3dsrc_t * a, a3d_Hrtf_t aa, a3d_Hrtf_t b)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
int i;
for (i = 0; i < HRTF_SZ; i++)
aa[i] =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source,
A3D_A_HrtfTarget + (i << 2)));
for (i = 0; i < HRTF_SZ; i++)
b[i] =
hwread(vortex->mmio,
a3d_addrB(a->slice, a->source,
A3D_B_HrtfTarget + (i << 2)));
}
static void a3dsrc_GetHrtfCurrent(a3dsrc_t * a, a3d_Hrtf_t aa, a3d_Hrtf_t b)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
int i;
for (i = 0; i < HRTF_SZ; i++)
aa[i] =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source,
A3D_A_HrtfCurrent + (i << 2)));
for (i = 0; i < HRTF_SZ; i++)
b[i] =
hwread(vortex->mmio,
a3d_addrB(a->slice, a->source,
A3D_B_HrtfCurrent + (i << 2)));
}
static void a3dsrc_GetHrtfState(a3dsrc_t * a, a3d_Hrtf_t aa, a3d_Hrtf_t b)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
int i;
// FIXME: verify this!
for (i = 0; i < HRTF_SZ; i++)
aa[i] =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source,
A3D_A_HrtfDelayLine + (i << 2)));
for (i = 0; i < HRTF_SZ; i++)
b[i] =
hwread(vortex->mmio,
a3d_addrB(a->slice, a->source,
A3D_B_HrtfDelayLine + (i << 2)));
}
static void a3dsrc_GetHrtfOutput(a3dsrc_t * a, short *left, short *right)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
*left =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_HrtfOutL));
*right =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_HrtfOutR));
}
#endif
/* Interaural Time Difference.
* "The other main clue that humans use to locate sounds, is called
* Interaural Time Difference (ITD). The differences in distance from
* the sound source to a listeners ears means that the sound will
* reach one ear slightly before the other....", found somewhere with google.*/
static void a3dsrc_SetItdTarget(a3dsrc_t * a, short litd, short ritd)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
if (litd < 0)
litd = 0;
if (litd > 0x57FF)
litd = 0x57FF;
if (ritd < 0)
ritd = 0;
if (ritd > 0x57FF)
ritd = 0x57FF;
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_ITDTarget),
(ritd << 0x10) | litd);
//hwwrite(vortex->mmio, addr(0x191DF+5, this04, this08), (ritd<<0x10)|litd);
}
static void a3dsrc_SetItdCurrent(a3dsrc_t * a, short litd, short ritd)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
if (litd < 0)
litd = 0;
if (litd > 0x57FF)
litd = 0x57FF;
if (ritd < 0)
ritd = 0;
if (ritd > 0x57FF)
ritd = 0x57FF;
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_ITDCurrent),
(ritd << 0x10) | litd);
//hwwrite(vortex->mmio, addr(0x191DF+1, this04, this08), (ritd<<0x10)|litd);
}
static void a3dsrc_SetItdDline(a3dsrc_t * a, a3d_ItdDline_t const dline)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
int i;
/* 45 != 40 -> Check this ! */
for (i = 0; i < DLINE_SZ; i++)
hwwrite(vortex->mmio,
a3d_addrA(a->slice, a->source,
A3D_A_ITDDelayLine) + (i << 2), dline[i]);
}
#if 0
static void a3dsrc_GetItdTarget(a3dsrc_t * a, short *litd, short *ritd)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
*ritd =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_ITDTarget));
*litd =
hwread(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_ITDTarget));
}
static void a3dsrc_GetItdCurrent(a3dsrc_t * a, short *litd, short *ritd)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
*ritd =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_ITDCurrent));
*litd =
hwread(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_ITDCurrent));
}
static void a3dsrc_GetItdDline(a3dsrc_t * a, a3d_ItdDline_t dline)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
int i;
for (i = 0; i < DLINE_SZ; i++)
dline[i] =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source,
A3D_A_ITDDelayLine + (i << 2)));
}
#endif
/* This is may be used for ILD Interaural Level Difference. */
static void a3dsrc_SetGainTarget(a3dsrc_t * a, short left, short right)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_GainTarget),
(right << 0x10) | left);
}
static void a3dsrc_SetGainCurrent(a3dsrc_t * a, short left, short right)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_GainCurrent),
(right << 0x10) | left);
}
#if 0
static void a3dsrc_GetGainTarget(a3dsrc_t * a, short *left, short *right)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
*right =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_GainTarget));
*left =
hwread(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_GainTarget));
}
static void a3dsrc_GetGainCurrent(a3dsrc_t * a, short *left, short *right)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
*right =
hwread(vortex->mmio,
a3d_addrA(a->slice, a->source, A3D_A_GainCurrent));
*left =
hwread(vortex->mmio,
a3d_addrB(a->slice, a->source, A3D_B_GainCurrent));
}
/* CA3dIO this func seems to be inlined all over this place. */
static void CA3dIO_WriteReg(a3dsrc_t * a, unsigned long addr, short aa, short b)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio, addr, (aa << 0x10) | b);
}
#endif
/* Generic A3D stuff */
static void a3dsrc_SetA3DSampleRate(a3dsrc_t * a, int sr)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
int esp0 = 0;
esp0 = (((esp0 & 0x7fffffff) | 0xB8000000) & 0x7) | ((sr & 0x1f) << 3);
hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd), esp0);
//hwwrite(vortex->mmio, 0x19C38 + (this08<<0xd), esp0);
}
static void a3dsrc_EnableA3D(a3dsrc_t * a)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd),
0xF0000001);
//hwwrite(vortex->mmio, 0x19C38 + (this08<<0xd), 0xF0000001);
}
static void a3dsrc_DisableA3D(a3dsrc_t * a)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd),
0xF0000000);
}
static void a3dsrc_SetA3DControlReg(a3dsrc_t * a, unsigned long ctrl)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd), ctrl);
}
static void a3dsrc_SetA3DPointerReg(a3dsrc_t * a, unsigned long ptr)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
hwwrite(vortex->mmio, A3D_SLICE_Pointers + ((a->slice) << 0xd), ptr);
}
#if 0
static void a3dsrc_GetA3DSampleRate(a3dsrc_t * a, int *sr)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
*sr = ((hwread(vortex->mmio, A3D_SLICE_Control + (a->slice << 0xd))
>> 3) & 0x1f);
//*sr = ((hwread(vortex->mmio, 0x19C38 + (this08<<0xd))>>3)&0x1f);
}
static void a3dsrc_GetA3DControlReg(a3dsrc_t * a, unsigned long *ctrl)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
*ctrl = hwread(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd));
}
static void a3dsrc_GetA3DPointerReg(a3dsrc_t * a, unsigned long *ptr)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
*ptr = hwread(vortex->mmio, A3D_SLICE_Pointers + ((a->slice) << 0xd));
}
#endif
static void a3dsrc_ZeroSliceIO(a3dsrc_t * a)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
int i;
for (i = 0; i < 8; i++)
hwwrite(vortex->mmio,
A3D_SLICE_VDBDest +
((((a->slice) << 0xb) + i) << 2), 0);
for (i = 0; i < 4; i++)
hwwrite(vortex->mmio,
A3D_SLICE_VDBSource +
((((a->slice) << 0xb) + i) << 2), 0);
}
/* Reset Single A3D source. */
static void a3dsrc_ZeroState(a3dsrc_t * a)
{
/*
pr_debug( "vortex: ZeroState slice: %d, source %d\n",
a->slice, a->source);
*/
a3dsrc_SetAtmosState(a, 0, 0, 0, 0);
a3dsrc_SetHrtfState(a, A3dHrirZeros, A3dHrirZeros);
a3dsrc_SetItdDline(a, A3dItdDlineZeros);
a3dsrc_SetHrtfOutput(a, 0, 0);
a3dsrc_SetTimeConsts(a, 0, 0, 0, 0);
a3dsrc_SetAtmosCurrent(a, 0, 0, 0, 0, 0);
a3dsrc_SetAtmosTarget(a, 0, 0, 0, 0, 0);
a3dsrc_SetItdCurrent(a, 0, 0);
a3dsrc_SetItdTarget(a, 0, 0);
a3dsrc_SetGainCurrent(a, 0, 0);
a3dsrc_SetGainTarget(a, 0, 0);
a3dsrc_SetHrtfCurrent(a, A3dHrirZeros, A3dHrirZeros);
a3dsrc_SetHrtfTarget(a, A3dHrirZeros, A3dHrirZeros);
}
/* Reset entire A3D engine */
static void a3dsrc_ZeroStateA3D(a3dsrc_t * a)
{
int i, var, var2;
if ((a->vortex) == NULL) {
pr_err( "vortex: ZeroStateA3D: ERROR: a->vortex is NULL\n");
return;
}
a3dsrc_SetA3DControlReg(a, 0);
a3dsrc_SetA3DPointerReg(a, 0);
var = a->slice;
var2 = a->source;
for (i = 0; i < 4; i++) {
a->slice = i;
a3dsrc_ZeroSliceIO(a);
//a3dsrc_ZeroState(a);
}
a->source = var2;
a->slice = var;
}
/* Program A3D block as pass through */
static void a3dsrc_ProgramPipe(a3dsrc_t * a)
{
a3dsrc_SetTimeConsts(a, 0, 0, 0, 0);
a3dsrc_SetAtmosCurrent(a, 0, 0x4000, 0, 0, 0);
a3dsrc_SetAtmosTarget(a, 0x4000, 0, 0, 0, 0);
a3dsrc_SetItdCurrent(a, 0, 0);
a3dsrc_SetItdTarget(a, 0, 0);
a3dsrc_SetGainCurrent(a, 0x7fff, 0x7fff);
a3dsrc_SetGainTarget(a, 0x7fff, 0x7fff);
/* SET HRTF HERE */
/* Single spike leads to identity transfer function. */
a3dsrc_SetHrtfCurrent(a, A3dHrirImpulse, A3dHrirImpulse);
a3dsrc_SetHrtfTarget(a, A3dHrirImpulse, A3dHrirImpulse);
/* Test: Sounds saturated. */
//a3dsrc_SetHrtfCurrent(a, A3dHrirSatTest, A3dHrirSatTest);
//a3dsrc_SetHrtfTarget(a, A3dHrirSatTest, A3dHrirSatTest);
}
/* VDB = Vortex audio Dataflow Bus */
#if 0
static void a3dsrc_ClearVDBData(a3dsrc_t * a, unsigned long aa)
{
vortex_t *vortex = (vortex_t *) (a->vortex);
// ((aa >> 2) << 8) - (aa >> 2)
hwwrite(vortex->mmio,
a3d_addrS(a->slice, A3D_SLICE_VDBDest) + (a->source << 2), 0);
hwwrite(vortex->mmio,
a3d_addrS(a->slice,
A3D_SLICE_VDBDest + 4) + (a->source << 2), 0);
/*
hwwrite(vortex->mmio, 0x19c00 + (((aa>>2)*255*4)+aa)*8, 0);
hwwrite(vortex->mmio, 0x19c04 + (((aa>>2)*255*4)+aa)*8, 0);
*/
}
#endif
/* A3D HwSource stuff. */
static void vortex_A3dSourceHw_Initialize(vortex_t * v, int source, int slice)
{
a3dsrc_t *a3dsrc = &(v->a3d[source + (slice * 4)]);
//a3dsrc_t *a3dsrc = &(v->a3d[source + (slice*4)]);
a3dsrc->vortex = (void *)v;
a3dsrc->source = source; /* source */
a3dsrc->slice = slice; /* slice */
a3dsrc_ZeroState(a3dsrc);
/* Added by me. */
a3dsrc_SetA3DSampleRate(a3dsrc, 0x11);
}
static int Vort3DRend_Initialize(vortex_t * v, unsigned short mode)
{
v->xt_mode = mode; /* this_14 */
vortex_XtalkHw_init(v);
vortex_XtalkHw_SetGainsAllChan(v);
switch (v->xt_mode) {
case XT_SPEAKER0:
vortex_XtalkHw_ProgramXtalkNarrow(v);
break;
case XT_SPEAKER1:
vortex_XtalkHw_ProgramXtalkWide(v);
break;
default:
case XT_HEADPHONE:
vortex_XtalkHw_ProgramPipe(v);
break;
case XT_DIAMOND:
vortex_XtalkHw_ProgramDiamondXtalk(v);
break;
}
vortex_XtalkHw_SetSampleRate(v, 0x11);
vortex_XtalkHw_Enable(v);
return 0;
}
/* 3D Sound entry points. */
static int vortex_a3d_register_controls(vortex_t * vortex);
static void vortex_a3d_unregister_controls(vortex_t * vortex);
/* A3D base support init/shudown */
static void vortex_Vort3D_enable(vortex_t *v)
{
int i;
Vort3DRend_Initialize(v, XT_HEADPHONE);
for (i = 0; i < NR_A3D; i++) {
vortex_A3dSourceHw_Initialize(v, i % 4, i >> 2);
a3dsrc_ZeroStateA3D(&(v->a3d[0]));
}
/* Register ALSA controls */
vortex_a3d_register_controls(v);
}
static void vortex_Vort3D_disable(vortex_t * v)
{
vortex_XtalkHw_Disable(v);
vortex_a3d_unregister_controls(v);
}
/* Make A3D subsystem connections. */
static void vortex_Vort3D_connect(vortex_t * v, int en)
{
int i;
// Disable AU8810 routes, since they seem to be wrong (in au8810.h).
#ifdef CHIP_AU8810
return;
#endif
#if 1
/* Alloc Xtalk mixin resources */
v->mixxtlk[0] =
vortex_adb_checkinout(v, v->fixed_res, en, VORTEX_RESOURCE_MIXIN);
if (v->mixxtlk[0] < 0) {
pr_warn
("vortex: vortex_Vort3D: ERROR: not enough free mixer resources.\n");
return;
}
v->mixxtlk[1] =
vortex_adb_checkinout(v, v->fixed_res, en, VORTEX_RESOURCE_MIXIN);
if (v->mixxtlk[1] < 0) {
pr_warn
("vortex: vortex_Vort3D: ERROR: not enough free mixer resources.\n");
return;
}
#endif
/* Connect A3D -> XTALK */
for (i = 0; i < 4; i++) {
// 2 outputs per each A3D slice.
vortex_route(v, en, 0x11, ADB_A3DOUT(i * 2), ADB_XTALKIN(i));
vortex_route(v, en, 0x11, ADB_A3DOUT(i * 2) + 1, ADB_XTALKIN(5 + i));
}
#if 0
vortex_route(v, en, 0x11, ADB_XTALKOUT(0), ADB_EQIN(2));
vortex_route(v, en, 0x11, ADB_XTALKOUT(1), ADB_EQIN(3));
#else
/* Connect XTalk -> mixer */
vortex_route(v, en, 0x11, ADB_XTALKOUT(0), ADB_MIXIN(v->mixxtlk[0]));
vortex_route(v, en, 0x11, ADB_XTALKOUT(1), ADB_MIXIN(v->mixxtlk[1]));
vortex_connection_mixin_mix(v, en, v->mixxtlk[0], v->mixplayb[0], 0);
vortex_connection_mixin_mix(v, en, v->mixxtlk[1], v->mixplayb[1], 0);
vortex_mix_setinputvolumebyte(v, v->mixplayb[0], v->mixxtlk[0],
en ? MIX_DEFIGAIN : VOL_MIN);
vortex_mix_setinputvolumebyte(v, v->mixplayb[1], v->mixxtlk[1],
en ? MIX_DEFIGAIN : VOL_MIN);
if (VORTEX_IS_QUAD(v)) {
vortex_connection_mixin_mix(v, en, v->mixxtlk[0],
v->mixplayb[2], 0);
vortex_connection_mixin_mix(v, en, v->mixxtlk[1],
v->mixplayb[3], 0);
vortex_mix_setinputvolumebyte(v, v->mixplayb[2],
v->mixxtlk[0],
en ? MIX_DEFIGAIN : VOL_MIN);
vortex_mix_setinputvolumebyte(v, v->mixplayb[3],
v->mixxtlk[1],
en ? MIX_DEFIGAIN : VOL_MIN);
}
#endif
}
/* Initialize one single A3D source. */
static void vortex_Vort3D_InitializeSource(a3dsrc_t * a, int en)
{
if (a->vortex == NULL) {
pr_warn
("vortex: Vort3D_InitializeSource: A3D source not initialized\n");
return;
}
if (en) {
a3dsrc_ProgramPipe(a);
a3dsrc_SetA3DSampleRate(a, 0x11);
a3dsrc_SetTimeConsts(a, HrtfTCDefault,
ItdTCDefault, GainTCDefault,
CoefTCDefault);
/* Remark: zero gain is muted. */
//a3dsrc_SetGainTarget(a,0,0);
//a3dsrc_SetGainCurrent(a,0,0);
a3dsrc_EnableA3D(a);
} else {
a3dsrc_DisableA3D(a);
a3dsrc_ZeroState(a);
}
}
/* Conversion of coordinates into 3D parameters. */
static void vortex_a3d_coord2hrtf(a3d_Hrtf_t hrtf, int *coord)
{
/* FIXME: implement this. */
}
static void vortex_a3d_coord2itd(a3d_Itd_t itd, int *coord)
{
/* FIXME: implement this. */
}
static void vortex_a3d_coord2ild(a3d_LRGains_t ild, int left, int right)
{
/* FIXME: implement this. */
}
static void vortex_a3d_translate_filter(a3d_atmos_t filter, int *params)
{
/* FIXME: implement this. */
}
/* ALSA control interface. */
static int
snd_vortex_a3d_hrtf_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 6;
uinfo->value.integer.min = 0x00000000;
uinfo->value.integer.max = 0xffffffff;
return 0;
}
static int
snd_vortex_a3d_itd_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0x00000000;
uinfo->value.integer.max = 0xffffffff;
return 0;
}
static int
snd_vortex_a3d_ild_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0x00000000;
uinfo->value.integer.max = 0xffffffff;
return 0;
}
static int
snd_vortex_a3d_filter_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 4;
uinfo->value.integer.min = 0x00000000;
uinfo->value.integer.max = 0xffffffff;
return 0;
}
static int
snd_vortex_a3d_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
//a3dsrc_t *a = kcontrol->private_data;
/* No read yet. Would this be really useable/needed ? */
return 0;
}
static int
snd_vortex_a3d_hrtf_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
a3dsrc_t *a = kcontrol->private_data;
int changed = 1, i;
int coord[6];
for (i = 0; i < 6; i++)
coord[i] = ucontrol->value.integer.value[i];
/* Translate orientation coordinates to a3d params. */
vortex_a3d_coord2hrtf(a->hrtf[0], coord);
vortex_a3d_coord2hrtf(a->hrtf[1], coord);
a3dsrc_SetHrtfTarget(a, a->hrtf[0], a->hrtf[1]);
a3dsrc_SetHrtfCurrent(a, a->hrtf[0], a->hrtf[1]);
return changed;
}
static int
snd_vortex_a3d_itd_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
a3dsrc_t *a = kcontrol->private_data;
int coord[6];
int i, changed = 1;
for (i = 0; i < 6; i++)
coord[i] = ucontrol->value.integer.value[i];
/* Translate orientation coordinates to a3d params. */
vortex_a3d_coord2itd(a->hrtf[0], coord);
vortex_a3d_coord2itd(a->hrtf[1], coord);
/* Inter aural time difference. */
a3dsrc_SetItdTarget(a, a->itd[0], a->itd[1]);
a3dsrc_SetItdCurrent(a, a->itd[0], a->itd[1]);
a3dsrc_SetItdDline(a, a->dline);
return changed;
}
static int
snd_vortex_a3d_ild_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
a3dsrc_t *a = kcontrol->private_data;
int changed = 1;
int l, r;
/* There may be some scale tranlation needed here. */
l = ucontrol->value.integer.value[0];
r = ucontrol->value.integer.value[1];
vortex_a3d_coord2ild(a->ild, l, r);
/* Left Right panning. */
a3dsrc_SetGainTarget(a, l, r);
a3dsrc_SetGainCurrent(a, l, r);
return changed;
}
static int
snd_vortex_a3d_filter_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
a3dsrc_t *a = kcontrol->private_data;
int i, changed = 1;
int params[6];
for (i = 0; i < 6; i++)
params[i] = ucontrol->value.integer.value[i];
/* Translate generic filter params to a3d filter params. */
vortex_a3d_translate_filter(a->filter, params);
/* Atmospheric absorption and filtering. */
a3dsrc_SetAtmosTarget(a, a->filter[0],
a->filter[1], a->filter[2],
a->filter[3], a->filter[4]);
a3dsrc_SetAtmosCurrent(a, a->filter[0],
a->filter[1], a->filter[2],
a->filter[3], a->filter[4]);
return changed;
}
static struct snd_kcontrol_new vortex_a3d_kcontrol = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "Playback PCM advanced processing",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = snd_vortex_a3d_hrtf_info,
.get = snd_vortex_a3d_get,
.put = snd_vortex_a3d_hrtf_put,
};
/* Control (un)registration. */
static int vortex_a3d_register_controls(vortex_t *vortex)
{
struct snd_kcontrol *kcontrol;
int err, i;
/* HRTF controls. */
for (i = 0; i < NR_A3D; i++) {
if ((kcontrol =
snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
return -ENOMEM;
kcontrol->id.numid = CTRLID_HRTF;
kcontrol->info = snd_vortex_a3d_hrtf_info;
kcontrol->put = snd_vortex_a3d_hrtf_put;
if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
return err;
}
/* ITD controls. */
for (i = 0; i < NR_A3D; i++) {
if ((kcontrol =
snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
return -ENOMEM;
kcontrol->id.numid = CTRLID_ITD;
kcontrol->info = snd_vortex_a3d_itd_info;
kcontrol->put = snd_vortex_a3d_itd_put;
if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
return err;
}
/* ILD (gains) controls. */
for (i = 0; i < NR_A3D; i++) {
if ((kcontrol =
snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
return -ENOMEM;
kcontrol->id.numid = CTRLID_GAINS;
kcontrol->info = snd_vortex_a3d_ild_info;
kcontrol->put = snd_vortex_a3d_ild_put;
if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
return err;
}
/* Filter controls. */
for (i = 0; i < NR_A3D; i++) {
if ((kcontrol =
snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
return -ENOMEM;
kcontrol->id.numid = CTRLID_FILTER;
kcontrol->info = snd_vortex_a3d_filter_info;
kcontrol->put = snd_vortex_a3d_filter_put;
if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
return err;
}
return 0;
}
static void vortex_a3d_unregister_controls(vortex_t * vortex)
{
}
/* End of File*/

View file

@ -0,0 +1,123 @@
/***************************************************************************
* au88x0_a3d.h
*
* Fri Jul 18 14:16:03 2003
* Copyright 2003 mjander
* mjander@users.sourceforge.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 Library 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.
*/
#ifndef _AU88X0_A3D_H
#define _AU88X0_A3D_H
//#include <openal.h>
#define HRTF_SZ 0x38
#define DLINE_SZ 0x28
#define CTRLID_HRTF 1
#define CTRLID_ITD 2
#define CTRLID_ILD 4
#define CTRLID_FILTER 8
#define CTRLID_GAINS 16
/* 3D parameter structs */
typedef unsigned short int a3d_Hrtf_t[HRTF_SZ];
typedef unsigned short int a3d_ItdDline_t[DLINE_SZ];
typedef unsigned short int a3d_atmos_t[5];
typedef unsigned short int a3d_LRGains_t[2];
typedef unsigned short int a3d_Itd_t[2];
typedef unsigned short int a3d_Ild_t[2];
typedef struct {
void *vortex; // Formerly CAsp4HwIO*, now vortex_t*.
unsigned int source; /* this_04 */
unsigned int slice; /* this_08 */
a3d_Hrtf_t hrtf[2];
a3d_Itd_t itd;
a3d_Ild_t ild;
a3d_ItdDline_t dline;
a3d_atmos_t filter;
} a3dsrc_t;
/* First Register bank */
#define A3D_A_HrtfCurrent 0x18000 /* 56 ULONG */
#define A3D_A_GainCurrent 0x180E0
#define A3D_A_GainTarget 0x180E4
#define A3D_A_A12Current 0x180E8 /* Atmospheric current. */
#define A3D_A_A21Target 0x180EC /* Atmospheric target */
#define A3D_A_B01Current 0x180F0 /* Atmospheric current */
#define A3D_A_B10Target 0x180F4 /* Atmospheric target */
#define A3D_A_B2Current 0x180F8 /* Atmospheric current */
#define A3D_A_B2Target 0x180FC /* Atmospheric target */
#define A3D_A_HrtfTarget 0x18100 /* 56 ULONG */
#define A3D_A_ITDCurrent 0x181E0
#define A3D_A_ITDTarget 0x181E4
#define A3D_A_HrtfDelayLine 0x181E8 /* 56 ULONG */
#define A3D_A_ITDDelayLine 0x182C8 /* 40/45 ULONG */
#define A3D_A_HrtfTrackTC 0x1837C /* Time Constants */
#define A3D_A_GainTrackTC 0x18380
#define A3D_A_CoeffTrackTC 0x18384
#define A3D_A_ITDTrackTC 0x18388
#define A3D_A_x1 0x1838C
#define A3D_A_x2 0x18390
#define A3D_A_y1 0x18394
#define A3D_A_y2 0x18398
#define A3D_A_HrtfOutL 0x1839C
#define A3D_A_HrtfOutR 0x183A0
#define A3D_A_TAIL 0x183A4
/* Second register bank */
#define A3D_B_HrtfCurrent 0x19000 /* 56 ULONG */
#define A3D_B_GainCurrent 0x190E0
#define A3D_B_GainTarget 0x190E4
#define A3D_B_A12Current 0x190E8
#define A3D_B_A21Target 0x190EC
#define A3D_B_B01Current 0x190F0
#define A3D_B_B10Target 0x190F4
#define A3D_B_B2Current 0x190F8
#define A3D_B_B2Target 0x190FC
#define A3D_B_HrtfTarget 0x19100 /* 56 ULONG */
#define A3D_B_ITDCurrent 0x191E0
#define A3D_B_ITDTarget 0x191E4
#define A3D_B_HrtfDelayLine 0x191E8 /* 56 ULONG */
#define A3D_B_TAIL 0x192C8
/* There are 4 slices, 4 a3d each = 16 a3d sources. */
#define A3D_SLICE_BANK_A 0x18000 /* 4 sources */
#define A3D_SLICE_BANK_B 0x19000 /* 4 sources */
#define A3D_SLICE_VDBDest 0x19C00 /* 8 ULONG */
#define A3D_SLICE_VDBSource 0x19C20 /* 4 ULONG */
#define A3D_SLICE_ABReg 0x19C30
#define A3D_SLICE_CReg 0x19C34
#define A3D_SLICE_Control 0x19C38
#define A3D_SLICE_DebugReserved 0x19C3c /* Dangerous! */
#define A3D_SLICE_Pointers 0x19C40
#define A3D_SLICE_TAIL 0x1A000
// Slice size: 0x2000
// Source size: 0x3A4, 0x2C8
/* Address generator macro. */
#define a3d_addrA(slice,source,reg) (((slice)<<0xd)+((source)*0x3A4)+(reg))
#define a3d_addrB(slice,source,reg) (((slice)<<0xd)+((source)*0x2C8)+(reg))
#define a3d_addrS(slice,reg) (((slice)<<0xd)+(reg))
//#define a3d_addr(slice,source,reg) (((reg)>=0x19000) ? a3d_addr2((slice),(source),(reg)) : a3d_addr1((slice),(source),(reg)))
#endif /* _AU88X0_A3D_H */

View file

@ -0,0 +1,91 @@
/***************************************************************************
* au88x0_a3ddata.c
*
* Wed Nov 19 21:11:32 2003
* Copyright 2003 mjander
* mjander@users.sourceforge.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 Library 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.
*/
/* Constant initializer values. */
static const a3d_Hrtf_t A3dHrirZeros = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0
};
static const a3d_Hrtf_t A3dHrirImpulse = {
0x7fff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0
};
static const a3d_Hrtf_t A3dHrirOnes = {
0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
0x7fff,
0x7fff,
0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
0x7fff,
0x7fff,
0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
0x7fff,
0x7fff,
0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
0x7fff,
0x7fff,
0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff
};
static const a3d_Hrtf_t A3dHrirSatTest = {
0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
0x7fff,
0x7fff,
0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x8001,
0x8001,
0x7fff, 0x0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static const a3d_Hrtf_t A3dHrirDImpulse = {
0, 0x7fff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0
};
static const a3d_ItdDline_t A3dItdDlineZeros = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static short const GainTCDefault = 0x300;
static short const ItdTCDefault = 0x0C8;
static short const HrtfTCDefault = 0x147;
static short const CoefTCDefault = 0x300;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,929 @@
/***************************************************************************
* au88x0_eq.c
* Aureal Vortex Hardware EQ control/access.
*
* Sun Jun 8 18:19:19 2003
* 2003 Manuel Jander (mjander@users.sourceforge.net)
*
* 02 July 2003: First time something works :)
* November 2003: A3D Bypass code completed but untested.
*
* TODO:
* - Debug (testing)
* - Test peak visualization support.
*
****************************************************************************/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library 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.
*/
/*
The Aureal Hardware EQ is found on AU8810 and AU8830 chips only.
it has 4 inputs (2 for general mix, 2 for A3D) and 2 outputs (supposed
to be routed to the codec).
*/
#include "au88x0.h"
#include "au88x0_eq.h"
#include "au88x0_eqdata.c"
#define VORTEX_EQ_BASE 0x2b000
#define VORTEX_EQ_DEST (VORTEX_EQ_BASE + 0x410)
#define VORTEX_EQ_SOURCE (VORTEX_EQ_BASE + 0x430)
#define VORTEX_EQ_CTRL (VORTEX_EQ_BASE + 0x440)
#define VORTEX_BAND_COEFF_SIZE 0x30
/* CEqHw.s */
static void vortex_EqHw_SetTimeConsts(vortex_t * vortex, u16 gain, u16 level)
{
hwwrite(vortex->mmio, 0x2b3c4, gain);
hwwrite(vortex->mmio, 0x2b3c8, level);
}
static inline u16 sign_invert(u16 a)
{
/* -(-32768) -> -32768 so we do -(-32768) -> 32767 to make the result positive */
if (a == (u16)-32768)
return 32767;
else
return -a;
}
static void vortex_EqHw_SetLeftCoefs(vortex_t * vortex, u16 coefs[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int i = 0, n /*esp2c */;
for (n = 0; n < eqhw->this04; n++) {
hwwrite(vortex->mmio, 0x2b000 + n * 0x30, coefs[i + 0]);
hwwrite(vortex->mmio, 0x2b004 + n * 0x30, coefs[i + 1]);
if (eqhw->this08 == 0) {
hwwrite(vortex->mmio, 0x2b008 + n * 0x30, coefs[i + 2]);
hwwrite(vortex->mmio, 0x2b00c + n * 0x30, coefs[i + 3]);
hwwrite(vortex->mmio, 0x2b010 + n * 0x30, coefs[i + 4]);
} else {
hwwrite(vortex->mmio, 0x2b008 + n * 0x30, sign_invert(coefs[2 + i]));
hwwrite(vortex->mmio, 0x2b00c + n * 0x30, sign_invert(coefs[3 + i]));
hwwrite(vortex->mmio, 0x2b010 + n * 0x30, sign_invert(coefs[4 + i]));
}
i += 5;
}
}
static void vortex_EqHw_SetRightCoefs(vortex_t * vortex, u16 coefs[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int i = 0, n /*esp2c */;
for (n = 0; n < eqhw->this04; n++) {
hwwrite(vortex->mmio, 0x2b1e0 + n * 0x30, coefs[0 + i]);
hwwrite(vortex->mmio, 0x2b1e4 + n * 0x30, coefs[1 + i]);
if (eqhw->this08 == 0) {
hwwrite(vortex->mmio, 0x2b1e8 + n * 0x30, coefs[2 + i]);
hwwrite(vortex->mmio, 0x2b1ec + n * 0x30, coefs[3 + i]);
hwwrite(vortex->mmio, 0x2b1f0 + n * 0x30, coefs[4 + i]);
} else {
hwwrite(vortex->mmio, 0x2b1e8 + n * 0x30, sign_invert(coefs[2 + i]));
hwwrite(vortex->mmio, 0x2b1ec + n * 0x30, sign_invert(coefs[3 + i]));
hwwrite(vortex->mmio, 0x2b1f0 + n * 0x30, sign_invert(coefs[4 + i]));
}
i += 5;
}
}
static void vortex_EqHw_SetLeftStates(vortex_t * vortex, u16 a[], u16 b[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int i = 0, ebx;
hwwrite(vortex->mmio, 0x2b3fc, a[0]);
hwwrite(vortex->mmio, 0x2b400, a[1]);
for (ebx = 0; ebx < eqhw->this04; ebx++) {
hwwrite(vortex->mmio, 0x2b014 + (i * 0xc), b[i]);
hwwrite(vortex->mmio, 0x2b018 + (i * 0xc), b[1 + i]);
hwwrite(vortex->mmio, 0x2b01c + (i * 0xc), b[2 + i]);
hwwrite(vortex->mmio, 0x2b020 + (i * 0xc), b[3 + i]);
i += 4;
}
}
static void vortex_EqHw_SetRightStates(vortex_t * vortex, u16 a[], u16 b[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int i = 0, ebx;
hwwrite(vortex->mmio, 0x2b404, a[0]);
hwwrite(vortex->mmio, 0x2b408, a[1]);
for (ebx = 0; ebx < eqhw->this04; ebx++) {
hwwrite(vortex->mmio, 0x2b1f4 + (i * 0xc), b[i]);
hwwrite(vortex->mmio, 0x2b1f8 + (i * 0xc), b[1 + i]);
hwwrite(vortex->mmio, 0x2b1fc + (i * 0xc), b[2 + i]);
hwwrite(vortex->mmio, 0x2b200 + (i * 0xc), b[3 + i]);
i += 4;
}
}
#if 0
static void vortex_EqHw_GetTimeConsts(vortex_t * vortex, u16 * a, u16 * b)
{
*a = hwread(vortex->mmio, 0x2b3c4);
*b = hwread(vortex->mmio, 0x2b3c8);
}
static void vortex_EqHw_GetLeftCoefs(vortex_t * vortex, u16 a[])
{
}
static void vortex_EqHw_GetRightCoefs(vortex_t * vortex, u16 a[])
{
}
static void vortex_EqHw_GetLeftStates(vortex_t * vortex, u16 * a, u16 b[])
{
}
static void vortex_EqHw_GetRightStates(vortex_t * vortex, u16 * a, u16 b[])
{
}
#endif
/* Mix Gains */
static void vortex_EqHw_SetBypassGain(vortex_t * vortex, u16 a, u16 b)
{
eqhw_t *eqhw = &(vortex->eq.this04);
if (eqhw->this08 == 0) {
hwwrite(vortex->mmio, 0x2b3d4, a);
hwwrite(vortex->mmio, 0x2b3ec, b);
} else {
hwwrite(vortex->mmio, 0x2b3d4, sign_invert(a));
hwwrite(vortex->mmio, 0x2b3ec, sign_invert(b));
}
}
static void vortex_EqHw_SetA3DBypassGain(vortex_t * vortex, u16 a, u16 b)
{
hwwrite(vortex->mmio, 0x2b3e0, a);
hwwrite(vortex->mmio, 0x2b3f8, b);
}
#if 0
static void vortex_EqHw_SetCurrBypassGain(vortex_t * vortex, u16 a, u16 b)
{
hwwrite(vortex->mmio, 0x2b3d0, a);
hwwrite(vortex->mmio, 0x2b3e8, b);
}
static void vortex_EqHw_SetCurrA3DBypassGain(vortex_t * vortex, u16 a, u16 b)
{
hwwrite(vortex->mmio, 0x2b3dc, a);
hwwrite(vortex->mmio, 0x2b3f4, b);
}
#endif
static void
vortex_EqHw_SetLeftGainsSingleTarget(vortex_t * vortex, u16 index, u16 b)
{
hwwrite(vortex->mmio, 0x2b02c + (index * 0x30), b);
}
static void
vortex_EqHw_SetRightGainsSingleTarget(vortex_t * vortex, u16 index, u16 b)
{
hwwrite(vortex->mmio, 0x2b20c + (index * 0x30), b);
}
static void vortex_EqHw_SetLeftGainsTarget(vortex_t * vortex, u16 a[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int ebx;
for (ebx = 0; ebx < eqhw->this04; ebx++) {
hwwrite(vortex->mmio, 0x2b02c + ebx * 0x30, a[ebx]);
}
}
static void vortex_EqHw_SetRightGainsTarget(vortex_t * vortex, u16 a[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int ebx;
for (ebx = 0; ebx < eqhw->this04; ebx++) {
hwwrite(vortex->mmio, 0x2b20c + ebx * 0x30, a[ebx]);
}
}
static void vortex_EqHw_SetLeftGainsCurrent(vortex_t * vortex, u16 a[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int ebx;
for (ebx = 0; ebx < eqhw->this04; ebx++) {
hwwrite(vortex->mmio, 0x2b028 + ebx * 0x30, a[ebx]);
}
}
static void vortex_EqHw_SetRightGainsCurrent(vortex_t * vortex, u16 a[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int ebx;
for (ebx = 0; ebx < eqhw->this04; ebx++) {
hwwrite(vortex->mmio, 0x2b208 + ebx * 0x30, a[ebx]);
}
}
#if 0
static void vortex_EqHw_GetLeftGainsTarget(vortex_t * vortex, u16 a[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int ebx = 0;
if (eqhw->this04 < 0)
return;
do {
a[ebx] = hwread(vortex->mmio, 0x2b02c + ebx * 0x30);
ebx++;
}
while (ebx < eqhw->this04);
}
static void vortex_EqHw_GetRightGainsTarget(vortex_t * vortex, u16 a[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int ebx = 0;
if (eqhw->this04 < 0)
return;
do {
a[ebx] = hwread(vortex->mmio, 0x2b20c + ebx * 0x30);
ebx++;
}
while (ebx < eqhw->this04);
}
static void vortex_EqHw_GetLeftGainsCurrent(vortex_t * vortex, u16 a[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int ebx = 0;
if (eqhw->this04 < 0)
return;
do {
a[ebx] = hwread(vortex->mmio, 0x2b028 + ebx * 0x30);
ebx++;
}
while (ebx < eqhw->this04);
}
static void vortex_EqHw_GetRightGainsCurrent(vortex_t * vortex, u16 a[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int ebx = 0;
if (eqhw->this04 < 0)
return;
do {
a[ebx] = hwread(vortex->mmio, 0x2b208 + ebx * 0x30);
ebx++;
}
while (ebx < eqhw->this04);
}
#endif
/* EQ band levels settings */
static void vortex_EqHw_SetLevels(vortex_t * vortex, u16 peaks[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int i;
/* set left peaks */
for (i = 0; i < eqhw->this04; i++) {
hwwrite(vortex->mmio, 0x2b024 + i * VORTEX_BAND_COEFF_SIZE, peaks[i]);
}
hwwrite(vortex->mmio, 0x2b3cc, peaks[eqhw->this04]);
hwwrite(vortex->mmio, 0x2b3d8, peaks[eqhw->this04 + 1]);
/* set right peaks */
for (i = 0; i < eqhw->this04; i++) {
hwwrite(vortex->mmio, 0x2b204 + i * VORTEX_BAND_COEFF_SIZE,
peaks[i + (eqhw->this04 + 2)]);
}
hwwrite(vortex->mmio, 0x2b3e4, peaks[2 + (eqhw->this04 * 2)]);
hwwrite(vortex->mmio, 0x2b3f0, peaks[3 + (eqhw->this04 * 2)]);
}
#if 0
static void vortex_EqHw_GetLevels(vortex_t * vortex, u16 a[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int ebx;
if (eqhw->this04 < 0)
return;
ebx = 0;
do {
a[ebx] = hwread(vortex->mmio, 0x2b024 + ebx * 0x30);
ebx++;
}
while (ebx < eqhw->this04);
a[eqhw->this04] = hwread(vortex->mmio, 0x2b3cc);
a[eqhw->this04 + 1] = hwread(vortex->mmio, 0x2b3d8);
ebx = 0;
do {
a[ebx + (eqhw->this04 + 2)] =
hwread(vortex->mmio, 0x2b204 + ebx * 0x30);
ebx++;
}
while (ebx < eqhw->this04);
a[2 + (eqhw->this04 * 2)] = hwread(vortex->mmio, 0x2b3e4);
a[3 + (eqhw->this04 * 2)] = hwread(vortex->mmio, 0x2b3f0);
}
#endif
/* Global Control */
static void vortex_EqHw_SetControlReg(vortex_t * vortex, u32 reg)
{
hwwrite(vortex->mmio, 0x2b440, reg);
}
static void vortex_EqHw_SetSampleRate(vortex_t * vortex, u32 sr)
{
hwwrite(vortex->mmio, 0x2b440, ((sr & 0x1f) << 3) | 0xb800);
}
#if 0
static void vortex_EqHw_GetControlReg(vortex_t * vortex, u32 *reg)
{
*reg = hwread(vortex->mmio, 0x2b440);
}
static void vortex_EqHw_GetSampleRate(vortex_t * vortex, u32 *sr)
{
*sr = (hwread(vortex->mmio, 0x2b440) >> 3) & 0x1f;
}
#endif
static void vortex_EqHw_Enable(vortex_t * vortex)
{
hwwrite(vortex->mmio, VORTEX_EQ_CTRL, 0xf001);
}
static void vortex_EqHw_Disable(vortex_t * vortex)
{
hwwrite(vortex->mmio, VORTEX_EQ_CTRL, 0xf000);
}
/* Reset (zero) buffers */
static void vortex_EqHw_ZeroIO(vortex_t * vortex)
{
int i;
for (i = 0; i < 0x8; i++)
hwwrite(vortex->mmio, VORTEX_EQ_DEST + (i << 2), 0x0);
for (i = 0; i < 0x4; i++)
hwwrite(vortex->mmio, VORTEX_EQ_SOURCE + (i << 2), 0x0);
}
static void vortex_EqHw_ZeroA3DIO(vortex_t * vortex)
{
int i;
for (i = 0; i < 0x4; i++)
hwwrite(vortex->mmio, VORTEX_EQ_DEST + (i << 2), 0x0);
}
static void vortex_EqHw_ZeroState(vortex_t * vortex)
{
vortex_EqHw_SetControlReg(vortex, 0);
vortex_EqHw_ZeroIO(vortex);
hwwrite(vortex->mmio, 0x2b3c0, 0);
vortex_EqHw_SetTimeConsts(vortex, 0, 0);
vortex_EqHw_SetLeftCoefs(vortex, asEqCoefsZeros);
vortex_EqHw_SetRightCoefs(vortex, asEqCoefsZeros);
vortex_EqHw_SetLeftGainsCurrent(vortex, eq_gains_zero);
vortex_EqHw_SetRightGainsCurrent(vortex, eq_gains_zero);
vortex_EqHw_SetLeftGainsTarget(vortex, eq_gains_zero);
vortex_EqHw_SetRightGainsTarget(vortex, eq_gains_zero);
vortex_EqHw_SetBypassGain(vortex, 0, 0);
//vortex_EqHw_SetCurrBypassGain(vortex, 0, 0);
vortex_EqHw_SetA3DBypassGain(vortex, 0, 0);
//vortex_EqHw_SetCurrA3DBypassGain(vortex, 0, 0);
vortex_EqHw_SetLeftStates(vortex, eq_states_zero, asEqOutStateZeros);
vortex_EqHw_SetRightStates(vortex, eq_states_zero, asEqOutStateZeros);
vortex_EqHw_SetLevels(vortex, (u16 *) eq_levels);
}
/* Program coeficients as pass through */
static void vortex_EqHw_ProgramPipe(vortex_t * vortex)
{
vortex_EqHw_SetTimeConsts(vortex, 0, 0);
vortex_EqHw_SetLeftCoefs(vortex, asEqCoefsPipes);
vortex_EqHw_SetRightCoefs(vortex, asEqCoefsPipes);
vortex_EqHw_SetLeftGainsCurrent(vortex, eq_gains_current);
vortex_EqHw_SetRightGainsCurrent(vortex, eq_gains_current);
vortex_EqHw_SetLeftGainsTarget(vortex, eq_gains_current);
vortex_EqHw_SetRightGainsTarget(vortex, eq_gains_current);
}
/* Program EQ block as 10 band Equalizer */
static void
vortex_EqHw_Program10Band(vortex_t * vortex, auxxEqCoeffSet_t * coefset)
{
vortex_EqHw_SetTimeConsts(vortex, 0xc, 0x7fe0);
vortex_EqHw_SetLeftCoefs(vortex, coefset->LeftCoefs);
vortex_EqHw_SetRightCoefs(vortex, coefset->RightCoefs);
vortex_EqHw_SetLeftGainsCurrent(vortex, coefset->LeftGains);
vortex_EqHw_SetRightGainsTarget(vortex, coefset->RightGains);
vortex_EqHw_SetLeftGainsTarget(vortex, coefset->LeftGains);
vortex_EqHw_SetRightGainsCurrent(vortex, coefset->RightGains);
}
/* Read all EQ peaks. (think VU meter) */
static void vortex_EqHw_GetTenBandLevels(vortex_t * vortex, u16 peaks[])
{
eqhw_t *eqhw = &(vortex->eq.this04);
int i;
if (eqhw->this04 <= 0)
return;
for (i = 0; i < eqhw->this04; i++)
peaks[i] = hwread(vortex->mmio, 0x2B024 + i * 0x30);
for (i = 0; i < eqhw->this04; i++)
peaks[i + eqhw->this04] =
hwread(vortex->mmio, 0x2B204 + i * 0x30);
}
/* CEqlzr.s */
static int vortex_Eqlzr_GetLeftGain(vortex_t * vortex, u16 index, u16 * gain)
{
eqlzr_t *eq = &(vortex->eq);
if (eq->this28) {
*gain = eq->this130[index];
return 0;
}
return 1;
}
static void vortex_Eqlzr_SetLeftGain(vortex_t * vortex, u16 index, u16 gain)
{
eqlzr_t *eq = &(vortex->eq);
if (eq->this28 == 0)
return;
eq->this130[index] = gain;
if (eq->this54)
return;
vortex_EqHw_SetLeftGainsSingleTarget(vortex, index, gain);
}
static int vortex_Eqlzr_GetRightGain(vortex_t * vortex, u16 index, u16 * gain)
{
eqlzr_t *eq = &(vortex->eq);
if (eq->this28) {
*gain = eq->this130[index + eq->this10];
return 0;
}
return 1;
}
static void vortex_Eqlzr_SetRightGain(vortex_t * vortex, u16 index, u16 gain)
{
eqlzr_t *eq = &(vortex->eq);
if (eq->this28 == 0)
return;
eq->this130[index + eq->this10] = gain;
if (eq->this54)
return;
vortex_EqHw_SetRightGainsSingleTarget(vortex, index, gain);
}
#if 0
static int
vortex_Eqlzr_GetAllBands(vortex_t * vortex, u16 * gains, s32 *cnt)
{
eqlzr_t *eq = &(vortex->eq);
int si = 0;
if (eq->this10 == 0)
return 1;
{
if (vortex_Eqlzr_GetLeftGain(vortex, si, &gains[si]))
return 1;
if (vortex_Eqlzr_GetRightGain
(vortex, si, &gains[si + eq->this10]))
return 1;
si++;
}
while (eq->this10 > si) ;
*cnt = si * 2;
return 0;
}
#endif
static int vortex_Eqlzr_SetAllBandsFromActiveCoeffSet(vortex_t * vortex)
{
eqlzr_t *eq = &(vortex->eq);
vortex_EqHw_SetLeftGainsTarget(vortex, eq->this130);
vortex_EqHw_SetRightGainsTarget(vortex, &(eq->this130[eq->this10]));
return 0;
}
static int
vortex_Eqlzr_SetAllBands(vortex_t * vortex, u16 gains[], s32 count)
{
eqlzr_t *eq = &(vortex->eq);
int i;
if (((eq->this10) * 2 != count) || (eq->this28 == 0))
return 1;
for (i = 0; i < count; i++) {
eq->this130[i] = gains[i];
}
if (eq->this54)
return 0;
return vortex_Eqlzr_SetAllBandsFromActiveCoeffSet(vortex);
}
static void
vortex_Eqlzr_SetA3dBypassGain(vortex_t * vortex, u32 a, u32 b)
{
eqlzr_t *eq = &(vortex->eq);
u32 eax, ebx;
eq->this58 = a;
eq->this5c = b;
if (eq->this54)
eax = eq->this0e;
else
eax = eq->this0a;
ebx = (eax * eq->this58) >> 0x10;
eax = (eax * eq->this5c) >> 0x10;
vortex_EqHw_SetA3DBypassGain(vortex, ebx, eax);
}
static void vortex_Eqlzr_ProgramA3dBypassGain(vortex_t * vortex)
{
eqlzr_t *eq = &(vortex->eq);
u32 eax, ebx;
if (eq->this54)
eax = eq->this0e;
else
eax = eq->this0a;
ebx = (eax * eq->this58) >> 0x10;
eax = (eax * eq->this5c) >> 0x10;
vortex_EqHw_SetA3DBypassGain(vortex, ebx, eax);
}
static void vortex_Eqlzr_ShutDownA3d(vortex_t * vortex)
{
if (vortex != NULL)
vortex_EqHw_ZeroA3DIO(vortex);
}
static void vortex_Eqlzr_SetBypass(vortex_t * vortex, u32 bp)
{
eqlzr_t *eq = &(vortex->eq);
if ((eq->this28) && (bp == 0)) {
/* EQ enabled */
vortex_Eqlzr_SetAllBandsFromActiveCoeffSet(vortex);
vortex_EqHw_SetBypassGain(vortex, eq->this08, eq->this08);
} else {
/* EQ disabled. */
vortex_EqHw_SetLeftGainsTarget(vortex, eq->this14_array);
vortex_EqHw_SetRightGainsTarget(vortex, eq->this14_array);
vortex_EqHw_SetBypassGain(vortex, eq->this0c, eq->this0c);
}
vortex_Eqlzr_ProgramA3dBypassGain(vortex);
}
static void vortex_Eqlzr_ReadAndSetActiveCoefSet(vortex_t * vortex)
{
eqlzr_t *eq = &(vortex->eq);
/* Set EQ BiQuad filter coeficients */
memcpy(&(eq->coefset), &asEqCoefsNormal, sizeof(auxxEqCoeffSet_t));
/* Set EQ Band gain levels and dump into hardware registers. */
vortex_Eqlzr_SetAllBands(vortex, eq_gains_normal, eq->this10 * 2);
}
static int vortex_Eqlzr_GetAllPeaks(vortex_t * vortex, u16 * peaks, int *count)
{
eqlzr_t *eq = &(vortex->eq);
if (eq->this10 == 0)
return 1;
*count = eq->this10 * 2;
vortex_EqHw_GetTenBandLevels(vortex, peaks);
return 0;
}
#if 0
static auxxEqCoeffSet_t *vortex_Eqlzr_GetActiveCoefSet(vortex_t * vortex)
{
eqlzr_t *eq = &(vortex->eq);
return (&(eq->coefset));
}
#endif
static void vortex_Eqlzr_init(vortex_t * vortex)
{
eqlzr_t *eq = &(vortex->eq);
/* Object constructor */
//eq->this04 = 0;
eq->this08 = 0; /* Bypass gain with EQ in use. */
eq->this0a = 0x5999;
eq->this0c = 0x5999; /* Bypass gain with EQ disabled. */
eq->this0e = 0x5999;
eq->this10 = 0xa; /* 10 eq frequency bands. */
eq->this04.this04 = eq->this10;
eq->this28 = 0x1; /* if 1 => Allow read access to this130 (gains) */
eq->this54 = 0x0; /* if 1 => Dont Allow access to hardware (gains) */
eq->this58 = 0xffff;
eq->this5c = 0xffff;
/* Set gains. */
memset(eq->this14_array, 0, sizeof(eq->this14_array));
/* Actual init. */
vortex_EqHw_ZeroState(vortex);
vortex_EqHw_SetSampleRate(vortex, 0x11);
vortex_Eqlzr_ReadAndSetActiveCoefSet(vortex);
vortex_EqHw_Program10Band(vortex, &(eq->coefset));
vortex_Eqlzr_SetBypass(vortex, eq->this54);
vortex_Eqlzr_SetA3dBypassGain(vortex, 0, 0);
vortex_EqHw_Enable(vortex);
}
static void vortex_Eqlzr_shutdown(vortex_t * vortex)
{
vortex_Eqlzr_ShutDownA3d(vortex);
vortex_EqHw_ProgramPipe(vortex);
vortex_EqHw_Disable(vortex);
}
/* ALSA interface */
/* Control interface */
#define snd_vortex_eqtoggle_info snd_ctl_boolean_mono_info
static int
snd_vortex_eqtoggle_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
eqlzr_t *eq = &(vortex->eq);
//int i = kcontrol->private_value;
ucontrol->value.integer.value[0] = eq->this54 ? 0 : 1;
return 0;
}
static int
snd_vortex_eqtoggle_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
eqlzr_t *eq = &(vortex->eq);
//int i = kcontrol->private_value;
eq->this54 = ucontrol->value.integer.value[0] ? 0 : 1;
vortex_Eqlzr_SetBypass(vortex, eq->this54);
return 1; /* Allways changes */
}
static struct snd_kcontrol_new vortex_eqtoggle_kcontrol = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "EQ Enable",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.private_value = 0,
.info = snd_vortex_eqtoggle_info,
.get = snd_vortex_eqtoggle_get,
.put = snd_vortex_eqtoggle_put
};
static int
snd_vortex_eq_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0x0000;
uinfo->value.integer.max = 0x7fff;
return 0;
}
static int
snd_vortex_eq_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
int i = kcontrol->private_value;
u16 gainL = 0, gainR = 0;
vortex_Eqlzr_GetLeftGain(vortex, i, &gainL);
vortex_Eqlzr_GetRightGain(vortex, i, &gainR);
ucontrol->value.integer.value[0] = gainL;
ucontrol->value.integer.value[1] = gainR;
return 0;
}
static int
snd_vortex_eq_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
int changed = 0, i = kcontrol->private_value;
u16 gainL = 0, gainR = 0;
vortex_Eqlzr_GetLeftGain(vortex, i, &gainL);
vortex_Eqlzr_GetRightGain(vortex, i, &gainR);
if (gainL != ucontrol->value.integer.value[0]) {
vortex_Eqlzr_SetLeftGain(vortex, i,
ucontrol->value.integer.value[0]);
changed = 1;
}
if (gainR != ucontrol->value.integer.value[1]) {
vortex_Eqlzr_SetRightGain(vortex, i,
ucontrol->value.integer.value[1]);
changed = 1;
}
return changed;
}
static struct snd_kcontrol_new vortex_eq_kcontrol = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = " .",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.private_value = 0,
.info = snd_vortex_eq_info,
.get = snd_vortex_eq_get,
.put = snd_vortex_eq_put
};
static int
snd_vortex_peaks_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 20;
uinfo->value.integer.min = 0x0000;
uinfo->value.integer.max = 0x7fff;
return 0;
}
static int
snd_vortex_peaks_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
int i, count = 0;
u16 peaks[20];
vortex_Eqlzr_GetAllPeaks(vortex, peaks, &count);
if (count != 20) {
pr_err( "vortex: peak count error 20 != %d \n", count);
return -1;
}
for (i = 0; i < 20; i++)
ucontrol->value.integer.value[i] = peaks[i];
return 0;
}
static struct snd_kcontrol_new vortex_levels_kcontrol = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "EQ Peaks",
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = snd_vortex_peaks_info,
.get = snd_vortex_peaks_get,
};
/* EQ band gain labels. */
static char *EqBandLabels[10] = {
"EQ0 31Hz\0",
"EQ1 63Hz\0",
"EQ2 125Hz\0",
"EQ3 250Hz\0",
"EQ4 500Hz\0",
"EQ5 1KHz\0",
"EQ6 2KHz\0",
"EQ7 4KHz\0",
"EQ8 8KHz\0",
"EQ9 16KHz\0",
};
/* ALSA driver entry points. Init and exit. */
static int vortex_eq_init(vortex_t *vortex)
{
struct snd_kcontrol *kcontrol;
int err, i;
vortex_Eqlzr_init(vortex);
if ((kcontrol =
snd_ctl_new1(&vortex_eqtoggle_kcontrol, vortex)) == NULL)
return -ENOMEM;
kcontrol->private_value = 0;
if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
return err;
/* EQ gain controls */
for (i = 0; i < 10; i++) {
if ((kcontrol =
snd_ctl_new1(&vortex_eq_kcontrol, vortex)) == NULL)
return -ENOMEM;
snprintf(kcontrol->id.name, sizeof(kcontrol->id.name),
"%s Playback Volume", EqBandLabels[i]);
kcontrol->private_value = i;
if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
return err;
//vortex->eqctrl[i] = kcontrol;
}
/* EQ band levels */
if ((kcontrol = snd_ctl_new1(&vortex_levels_kcontrol, vortex)) == NULL)
return -ENOMEM;
if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
return err;
return 0;
}
static int vortex_eq_free(vortex_t * vortex)
{
/*
//FIXME: segfault because vortex->eqctrl[i] == 4
int i;
for (i=0; i<10; i++) {
if (vortex->eqctrl[i])
snd_ctl_remove(vortex->card, vortex->eqctrl[i]);
}
*/
vortex_Eqlzr_shutdown(vortex);
return 0;
}
/* End */

View file

@ -0,0 +1,43 @@
#ifndef AU88X0_EQ_H
#define AU88X0_EQ_H
/***************************************************************************
* au88x0_eq.h
*
* Definitions and constant data for the Aureal Hardware EQ.
*
* Sun Jun 8 18:23:38 2003
* Author: Manuel Jander (mjander@users.sourceforge.net)
****************************************************************************/
typedef struct {
u16 LeftCoefs[50]; //0x4
u16 RightCoefs[50]; // 0x68
u16 LeftGains[10]; //0xd0
u16 RightGains[10]; //0xe4
} auxxEqCoeffSet_t;
typedef struct {
s32 this04; /* How many filters for each side (default = 10) */
s32 this08; /* inited to cero. Stereo flag? */
} eqhw_t;
typedef struct {
eqhw_t this04; /* CHwEq */
u16 this08; /* Bad codec flag ? SetBypassGain: bypass gain */
u16 this0a;
u16 this0c; /* SetBypassGain: bypass gain when this28 is not set. */
u16 this0e;
s32 this10; /* How many gains are used for each side (right or left). */
u16 this14_array[10]; /* SetLeftGainsTarget: Left (and right?) EQ gains */
s32 this28; /* flag related to EQ enabled or not. Gang flag ? */
s32 this54; /* SetBypass */
s32 this58;
s32 this5c;
/*0x60 */ auxxEqCoeffSet_t coefset;
/* 50 u16 word each channel. */
u16 this130[20]; /* Left and Right gains */
} eqlzr_t;
#endif

View file

@ -0,0 +1,116 @@
/* Data structs */
static u16 asEqCoefsZeros[50] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
};
static u16 asEqCoefsPipes[64] = {
0x0000, 0x0000,
0x0000, 0x0666, 0x0000, 0x0000, 0x0666,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0666, 0x0000, 0x0000, 0x0666,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0666, 0x0000, 0x0000, 0x0666,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0666, 0x0000, 0x0000, 0x0666,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0666, 0x0000, 0x0000, 0x066a,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000
};
/* More coef sets can be found in the win2k "inf" file. */
static auxxEqCoeffSet_t asEqCoefsNormal = {
.LeftCoefs = {
0x7e60, 0xc19e, 0x0001, 0x0002, 0x0001,
0x7fa0, 0xc05f, 0x004f, 0x0000, 0xffb1,
0x7f3f, 0xc0bc, 0x00c2, 0x0000, 0xff3e,
0x7e78, 0xc177, 0x011f, 0x0000, 0xfee1,
0x7cd6, 0xc2e5, 0x025c, 0x0000, 0xfda4,
0x7949, 0xc5aa, 0x0467, 0x0000, 0xfb99,
0x7120, 0xcadf, 0x0864, 0x0000, 0xf79c,
0x5d33, 0xd430, 0x0f7e, 0x0000, 0xf082,
0x2beb, 0xe3ca, 0x1bd3, 0x0000, 0xe42d,
0xd740, 0xf01d, 0x2ac5, 0x0000, 0xd53b},
.RightCoefs = {
0x7e60, 0xc19e, 0x0001, 0x0002, 0x0001,
0x7fa0, 0xc05f, 0x004f, 0x0000, 0xffb1,
0x7f3f, 0xc0bc, 0x00c2, 0x0000, 0xff3e,
0x7e78, 0xc177, 0x011f, 0x0000, 0xfee1,
0x7cd6, 0xc2e5, 0x025c, 0x0000, 0xfda4,
0x7949, 0xc5aa, 0x0467, 0x0000, 0xfb99,
0x7120, 0xcadf, 0x0864, 0x0000, 0xf79c,
0x5d33, 0xd430, 0x0f7e, 0x0000, 0xf082,
0x2beb, 0xe3ca, 0x1bd3, 0x0000, 0xe42d,
0xd740, 0xf01d, 0x2ac5, 0x0000, 0xd53b},
.LeftGains = {
0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96,
0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96},
.RightGains = {
0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96,
0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96}
};
static u16 eq_gains_normal[20] = {
0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96,
0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96,
0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96,
0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96
};
/* _rodatab60 */
static u16 eq_gains_zero[10] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};
/* _rodatab7c: ProgramPipe */
static u16 eq_gains_current[12] = {
0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
0x7fff,
0x7fff, 0x7fff, 0x7fff
};
/* _rodatab78 */
static u16 eq_states_zero[2] = { 0x0000, 0x0000 };
static u16 asEqOutStateZeros[48] = {
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000
};
/*_rodataba0:*/
static u16 eq_levels[64] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};

View file

@ -0,0 +1,133 @@
/*
* Manuel Jander.
*
* Based on the work of:
* Vojtech Pavlik
* Raymond Ingles
*
* 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
*
* Should you need to contact me, the author, you can do so either by
* e-mail - mail your message to <vojtech@suse.cz>, or by paper mail:
* Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00 Czech Republic
*
* Based 90% on Vojtech Pavlik pcigame driver.
* Merged and modified by Manuel Jander, for the OpenVortex
* driver. (email: mjander@embedded.cl).
*/
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <sound/core.h>
#include "au88x0.h"
#include <linux/gameport.h>
#include <linux/export.h>
#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
#define VORTEX_GAME_DWAIT 20 /* 20 ms */
static unsigned char vortex_game_read(struct gameport *gameport)
{
vortex_t *vortex = gameport_get_port_data(gameport);
return hwread(vortex->mmio, VORTEX_GAME_LEGACY);
}
static void vortex_game_trigger(struct gameport *gameport)
{
vortex_t *vortex = gameport_get_port_data(gameport);
hwwrite(vortex->mmio, VORTEX_GAME_LEGACY, 0xff);
}
static int
vortex_game_cooked_read(struct gameport *gameport, int *axes, int *buttons)
{
vortex_t *vortex = gameport_get_port_data(gameport);
int i;
*buttons = (~hwread(vortex->mmio, VORTEX_GAME_LEGACY) >> 4) & 0xf;
for (i = 0; i < 4; i++) {
axes[i] =
hwread(vortex->mmio, VORTEX_GAME_AXIS + (i * AXIS_SIZE));
if (axes[i] == AXIS_RANGE)
axes[i] = -1;
}
return 0;
}
static int vortex_game_open(struct gameport *gameport, int mode)
{
vortex_t *vortex = gameport_get_port_data(gameport);
switch (mode) {
case GAMEPORT_MODE_COOKED:
hwwrite(vortex->mmio, VORTEX_CTRL2,
hwread(vortex->mmio,
VORTEX_CTRL2) | CTRL2_GAME_ADCMODE);
msleep(VORTEX_GAME_DWAIT);
return 0;
case GAMEPORT_MODE_RAW:
hwwrite(vortex->mmio, VORTEX_CTRL2,
hwread(vortex->mmio,
VORTEX_CTRL2) & ~CTRL2_GAME_ADCMODE);
return 0;
default:
return -1;
}
return 0;
}
static int vortex_gameport_register(vortex_t *vortex)
{
struct gameport *gp;
vortex->gameport = gp = gameport_allocate_port();
if (!gp) {
pr_err( "vortex: cannot allocate memory for gameport\n");
return -ENOMEM;
}
gameport_set_name(gp, "AU88x0 Gameport");
gameport_set_phys(gp, "pci%s/gameport0", pci_name(vortex->pci_dev));
gameport_set_dev_parent(gp, &vortex->pci_dev->dev);
gp->read = vortex_game_read;
gp->trigger = vortex_game_trigger;
gp->cooked_read = vortex_game_cooked_read;
gp->open = vortex_game_open;
gameport_set_port_data(gp, vortex);
gp->fuzz = 64;
gameport_register_port(gp);
return 0;
}
static void vortex_gameport_unregister(vortex_t * vortex)
{
if (vortex->gameport) {
gameport_unregister_port(vortex->gameport);
vortex->gameport = NULL;
}
}
#else
static inline int vortex_gameport_register(vortex_t * vortex) { return -ENOSYS; }
static inline void vortex_gameport_unregister(vortex_t * vortex) { }
#endif

View file

@ -0,0 +1,43 @@
/*
* Vortex Mixer support.
*
* There is much more than just the AC97 mixer...
*
*/
#include <linux/time.h>
#include <linux/init.h>
#include <sound/core.h>
#include "au88x0.h"
static int remove_ctl(struct snd_card *card, const char *name)
{
struct snd_ctl_elem_id id;
memset(&id, 0, sizeof(id));
strcpy(id.name, name);
id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
return snd_ctl_remove_id(card, &id);
}
static int snd_vortex_mixer(vortex_t *vortex)
{
struct snd_ac97_bus *pbus;
struct snd_ac97_template ac97;
int err;
static struct snd_ac97_bus_ops ops = {
.write = vortex_codec_write,
.read = vortex_codec_read,
};
if ((err = snd_ac97_bus(vortex->card, 0, &ops, NULL, &pbus)) < 0)
return err;
memset(&ac97, 0, sizeof(ac97));
// Initialize AC97 codec stuff.
ac97.private_data = vortex;
ac97.scaps = AC97_SCAP_NO_SPDIF;
err = snd_ac97_mixer(pbus, &ac97, &vortex->codec);
vortex->isquad = ((vortex->codec == NULL) ? 0 : (vortex->codec->ext_id&0x80));
remove_ctl(vortex->card, "Master Mono Playback Volume");
remove_ctl(vortex->card, "Master Mono Playback Switch");
return err;
}

View file

@ -0,0 +1,112 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Routines for control of MPU-401 in UART mode
*
* Modified for the Aureal Vortex based Soundcards
* by Manuel Jander (mjande@embedded.cl).
*
* 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/time.h>
#include <linux/init.h>
#include <sound/core.h>
#include <sound/mpu401.h>
#include "au88x0.h"
/* Check for mpu401 mmio support. */
/* MPU401 legacy support is only provided as a emergency fallback *
* for older versions of ALSA. Its usage is strongly discouraged. */
#ifndef MPU401_HW_AUREAL
#define VORTEX_MPU401_LEGACY
#endif
/* Vortex MPU401 defines. */
#define MIDI_CLOCK_DIV 0x61
/* Standart MPU401 defines. */
#define MPU401_RESET 0xff
#define MPU401_ENTER_UART 0x3f
#define MPU401_ACK 0xfe
static int snd_vortex_midi(vortex_t *vortex)
{
struct snd_rawmidi *rmidi;
int temp, mode;
struct snd_mpu401 *mpu;
unsigned long port;
#ifdef VORTEX_MPU401_LEGACY
/* EnableHardCodedMPU401Port() */
/* Enable Legacy MIDI Interface port. */
port = (0x03 << 5); /* FIXME: static address. 0x330 */
temp =
(hwread(vortex->mmio, VORTEX_CTRL) & ~CTRL_MIDI_PORT) |
CTRL_MIDI_EN | port;
hwwrite(vortex->mmio, VORTEX_CTRL, temp);
#else
/* Disable Legacy MIDI Interface port. */
temp =
(hwread(vortex->mmio, VORTEX_CTRL) & ~CTRL_MIDI_PORT) &
~CTRL_MIDI_EN;
hwwrite(vortex->mmio, VORTEX_CTRL, temp);
#endif
/* Mpu401UartInit() */
mode = 1;
temp = hwread(vortex->mmio, VORTEX_CTRL2) & 0xffff00cf;
temp |= (MIDI_CLOCK_DIV << 8) | ((mode >> 24) & 0xff) << 4;
hwwrite(vortex->mmio, VORTEX_CTRL2, temp);
hwwrite(vortex->mmio, VORTEX_MIDI_CMD, MPU401_RESET);
/* Check if anything is OK. */
temp = hwread(vortex->mmio, VORTEX_MIDI_DATA);
if (temp != MPU401_ACK /*0xfe */ ) {
pr_err( "midi port doesn't acknowledge!\n");
return -ENODEV;
}
/* Enable MPU401 interrupts. */
hwwrite(vortex->mmio, VORTEX_IRQ_CTRL,
hwread(vortex->mmio, VORTEX_IRQ_CTRL) | IRQ_MIDI);
/* Create MPU401 instance. */
#ifdef VORTEX_MPU401_LEGACY
if ((temp =
snd_mpu401_uart_new(vortex->card, 0, MPU401_HW_MPU401, 0x330,
MPU401_INFO_IRQ_HOOK, -1, &rmidi)) != 0) {
hwwrite(vortex->mmio, VORTEX_CTRL,
(hwread(vortex->mmio, VORTEX_CTRL) &
~CTRL_MIDI_PORT) & ~CTRL_MIDI_EN);
return temp;
}
#else
port = (unsigned long)(vortex->mmio + VORTEX_MIDI_DATA);
if ((temp =
snd_mpu401_uart_new(vortex->card, 0, MPU401_HW_AUREAL, port,
MPU401_INFO_INTEGRATED | MPU401_INFO_MMIO |
MPU401_INFO_IRQ_HOOK, -1, &rmidi)) != 0) {
hwwrite(vortex->mmio, VORTEX_CTRL,
(hwread(vortex->mmio, VORTEX_CTRL) &
~CTRL_MIDI_PORT) & ~CTRL_MIDI_EN);
return temp;
}
mpu = rmidi->private_data;
mpu->cport = (unsigned long)(vortex->mmio + VORTEX_MIDI_CMD);
#endif
/* Overwrite MIDI name */
snprintf(rmidi->name, sizeof(rmidi->name), "%s MIDI %d", CARD_NAME_SHORT , vortex->card->number);
vortex->rmidi = rmidi;
return 0;
}

View file

@ -0,0 +1,701 @@
/*
* 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 Library 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.
*/
/*
* Vortex PCM ALSA driver.
*
* Supports ADB and WT DMA. Unfortunately, WT channels do not run yet.
* It remains stuck,and DMA transfers do not happen.
*/
#include <sound/asoundef.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "au88x0.h"
#define VORTEX_PCM_TYPE(x) (x->name[40])
/* hardware definition */
static struct snd_pcm_hardware snd_vortex_playback_hw_adb = {
.info =
(SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID),
.formats =
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 0x10000,
.period_bytes_min = 0x20,
.period_bytes_max = 0x1000,
.periods_min = 2,
.periods_max = 1024,
};
#ifndef CHIP_AU8820
static struct snd_pcm_hardware snd_vortex_playback_hw_a3d = {
.info =
(SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID),
.formats =
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 1,
.buffer_bytes_max = 0x10000,
.period_bytes_min = 0x100,
.period_bytes_max = 0x1000,
.periods_min = 2,
.periods_max = 64,
};
#endif
static struct snd_pcm_hardware snd_vortex_playback_hw_spdif = {
.info =
(SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID),
.formats =
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE | SNDRV_PCM_FMTBIT_MU_LAW |
SNDRV_PCM_FMTBIT_A_LAW,
.rates =
SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
.rate_min = 32000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 0x10000,
.period_bytes_min = 0x100,
.period_bytes_max = 0x1000,
.periods_min = 2,
.periods_max = 64,
};
#ifndef CHIP_AU8810
static struct snd_pcm_hardware snd_vortex_playback_hw_wt = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS, // SNDRV_PCM_RATE_48000,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 0x10000,
.period_bytes_min = 0x0400,
.period_bytes_max = 0x1000,
.periods_min = 2,
.periods_max = 64,
};
#endif
#ifdef CHIP_AU8830
static unsigned int au8830_channels[3] = {
1, 2, 4,
};
static struct snd_pcm_hw_constraint_list hw_constraints_au8830_channels = {
.count = ARRAY_SIZE(au8830_channels),
.list = au8830_channels,
.mask = 0,
};
#endif
static void vortex_notify_pcm_vol_change(struct snd_card *card,
struct snd_kcontrol *kctl, int activate)
{
if (activate)
kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
else
kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE |
SNDRV_CTL_EVENT_MASK_INFO, &(kctl->id));
}
/* open callback */
static int snd_vortex_pcm_open(struct snd_pcm_substream *substream)
{
vortex_t *vortex = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
/* Force equal size periods */
if ((err =
snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
return err;
/* Avoid PAGE_SIZE boundary to fall inside of a period. */
if ((err =
snd_pcm_hw_constraint_pow2(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES)) < 0)
return err;
snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 64);
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
#ifndef CHIP_AU8820
if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_A3D) {
runtime->hw = snd_vortex_playback_hw_a3d;
}
#endif
if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_SPDIF) {
runtime->hw = snd_vortex_playback_hw_spdif;
switch (vortex->spdif_sr) {
case 32000:
runtime->hw.rates = SNDRV_PCM_RATE_32000;
break;
case 44100:
runtime->hw.rates = SNDRV_PCM_RATE_44100;
break;
case 48000:
runtime->hw.rates = SNDRV_PCM_RATE_48000;
break;
}
}
if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB
|| VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_I2S)
runtime->hw = snd_vortex_playback_hw_adb;
#ifdef CHIP_AU8830
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
VORTEX_IS_QUAD(vortex) &&
VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB) {
runtime->hw.channels_max = 4;
snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS,
&hw_constraints_au8830_channels);
}
#endif
substream->runtime->private_data = NULL;
}
#ifndef CHIP_AU8810
else {
runtime->hw = snd_vortex_playback_hw_wt;
substream->runtime->private_data = NULL;
}
#endif
return 0;
}
/* close callback */
static int snd_vortex_pcm_close(struct snd_pcm_substream *substream)
{
//vortex_t *chip = snd_pcm_substream_chip(substream);
stream_t *stream = (stream_t *) substream->runtime->private_data;
// the hardware-specific codes will be here
if (stream != NULL) {
stream->substream = NULL;
stream->nr_ch = 0;
}
substream->runtime->private_data = NULL;
return 0;
}
/* hw_params callback */
static int
snd_vortex_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
vortex_t *chip = snd_pcm_substream_chip(substream);
stream_t *stream = (stream_t *) (substream->runtime->private_data);
int err;
// Alloc buffer memory.
err =
snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
if (err < 0) {
pr_err( "Vortex: pcm page alloc failed!\n");
return err;
}
/*
pr_info( "Vortex: periods %d, period_bytes %d, channels = %d\n", params_periods(hw_params),
params_period_bytes(hw_params), params_channels(hw_params));
*/
spin_lock_irq(&chip->lock);
// Make audio routes and config buffer DMA.
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
int dma, type = VORTEX_PCM_TYPE(substream->pcm);
/* Dealloc any routes. */
if (stream != NULL)
vortex_adb_allocroute(chip, stream->dma,
stream->nr_ch, stream->dir,
stream->type,
substream->number);
/* Alloc routes. */
dma =
vortex_adb_allocroute(chip, -1,
params_channels(hw_params),
substream->stream, type,
substream->number);
if (dma < 0) {
spin_unlock_irq(&chip->lock);
return dma;
}
stream = substream->runtime->private_data = &chip->dma_adb[dma];
stream->substream = substream;
/* Setup Buffers. */
vortex_adbdma_setbuffers(chip, dma,
params_period_bytes(hw_params),
params_periods(hw_params));
if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB) {
chip->pcm_vol[substream->number].active = 1;
vortex_notify_pcm_vol_change(chip->card,
chip->pcm_vol[substream->number].kctl, 1);
}
}
#ifndef CHIP_AU8810
else {
/* if (stream != NULL)
vortex_wt_allocroute(chip, substream->number, 0); */
vortex_wt_allocroute(chip, substream->number,
params_channels(hw_params));
stream = substream->runtime->private_data =
&chip->dma_wt[substream->number];
stream->dma = substream->number;
stream->substream = substream;
vortex_wtdma_setbuffers(chip, substream->number,
params_period_bytes(hw_params),
params_periods(hw_params));
}
#endif
spin_unlock_irq(&chip->lock);
return 0;
}
/* hw_free callback */
static int snd_vortex_pcm_hw_free(struct snd_pcm_substream *substream)
{
vortex_t *chip = snd_pcm_substream_chip(substream);
stream_t *stream = (stream_t *) (substream->runtime->private_data);
spin_lock_irq(&chip->lock);
// Delete audio routes.
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
if (stream != NULL) {
if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB) {
chip->pcm_vol[substream->number].active = 0;
vortex_notify_pcm_vol_change(chip->card,
chip->pcm_vol[substream->number].kctl,
0);
}
vortex_adb_allocroute(chip, stream->dma,
stream->nr_ch, stream->dir,
stream->type,
substream->number);
}
}
#ifndef CHIP_AU8810
else {
if (stream != NULL)
vortex_wt_allocroute(chip, stream->dma, 0);
}
#endif
substream->runtime->private_data = NULL;
spin_unlock_irq(&chip->lock);
return snd_pcm_lib_free_pages(substream);
}
/* prepare callback */
static int snd_vortex_pcm_prepare(struct snd_pcm_substream *substream)
{
vortex_t *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
stream_t *stream = (stream_t *) substream->runtime->private_data;
int dma = stream->dma, fmt, dir;
// set up the hardware with the current configuration.
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dir = 1;
else
dir = 0;
fmt = vortex_alsafmt_aspfmt(runtime->format);
spin_lock_irq(&chip->lock);
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
vortex_adbdma_setmode(chip, dma, 1, dir, fmt,
runtime->channels == 1 ? 0 : 1, 0);
vortex_adbdma_setstartbuffer(chip, dma, 0);
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_SPDIF)
vortex_adb_setsrc(chip, dma, runtime->rate, dir);
}
#ifndef CHIP_AU8810
else {
vortex_wtdma_setmode(chip, dma, 1, fmt, 0, 0);
// FIXME: Set rate (i guess using vortex_wt_writereg() somehow).
vortex_wtdma_setstartbuffer(chip, dma, 0);
}
#endif
spin_unlock_irq(&chip->lock);
return 0;
}
/* trigger callback */
static int snd_vortex_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
vortex_t *chip = snd_pcm_substream_chip(substream);
stream_t *stream = (stream_t *) substream->runtime->private_data;
int dma = stream->dma;
spin_lock(&chip->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
// do something to start the PCM engine
//printk(KERN_INFO "vortex: start %d\n", dma);
stream->fifo_enabled = 1;
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
vortex_adbdma_resetup(chip, dma);
vortex_adbdma_startfifo(chip, dma);
}
#ifndef CHIP_AU8810
else {
pr_info( "vortex: wt start %d\n", dma);
vortex_wtdma_startfifo(chip, dma);
}
#endif
break;
case SNDRV_PCM_TRIGGER_STOP:
// do something to stop the PCM engine
//printk(KERN_INFO "vortex: stop %d\n", dma);
stream->fifo_enabled = 0;
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
vortex_adbdma_stopfifo(chip, dma);
#ifndef CHIP_AU8810
else {
pr_info( "vortex: wt stop %d\n", dma);
vortex_wtdma_stopfifo(chip, dma);
}
#endif
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
//printk(KERN_INFO "vortex: pause %d\n", dma);
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
vortex_adbdma_pausefifo(chip, dma);
#ifndef CHIP_AU8810
else
vortex_wtdma_pausefifo(chip, dma);
#endif
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
//printk(KERN_INFO "vortex: resume %d\n", dma);
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
vortex_adbdma_resumefifo(chip, dma);
#ifndef CHIP_AU8810
else
vortex_wtdma_resumefifo(chip, dma);
#endif
break;
default:
spin_unlock(&chip->lock);
return -EINVAL;
}
spin_unlock(&chip->lock);
return 0;
}
/* pointer callback */
static snd_pcm_uframes_t snd_vortex_pcm_pointer(struct snd_pcm_substream *substream)
{
vortex_t *chip = snd_pcm_substream_chip(substream);
stream_t *stream = (stream_t *) substream->runtime->private_data;
int dma = stream->dma;
snd_pcm_uframes_t current_ptr = 0;
spin_lock(&chip->lock);
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
current_ptr = vortex_adbdma_getlinearpos(chip, dma);
#ifndef CHIP_AU8810
else
current_ptr = vortex_wtdma_getlinearpos(chip, dma);
#endif
//printk(KERN_INFO "vortex: pointer = 0x%x\n", current_ptr);
spin_unlock(&chip->lock);
return (bytes_to_frames(substream->runtime, current_ptr));
}
/* operators */
static struct snd_pcm_ops snd_vortex_playback_ops = {
.open = snd_vortex_pcm_open,
.close = snd_vortex_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_vortex_pcm_hw_params,
.hw_free = snd_vortex_pcm_hw_free,
.prepare = snd_vortex_pcm_prepare,
.trigger = snd_vortex_pcm_trigger,
.pointer = snd_vortex_pcm_pointer,
.page = snd_pcm_sgbuf_ops_page,
};
/*
* definitions of capture are omitted here...
*/
static char *vortex_pcm_prettyname[VORTEX_PCM_LAST] = {
CARD_NAME " ADB",
CARD_NAME " SPDIF",
CARD_NAME " A3D",
CARD_NAME " WT",
CARD_NAME " I2S",
};
static char *vortex_pcm_name[VORTEX_PCM_LAST] = {
"adb",
"spdif",
"a3d",
"wt",
"i2s",
};
/* SPDIF kcontrol */
static int snd_vortex_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int snd_vortex_spdif_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.iec958.status[0] = 0xff;
ucontrol->value.iec958.status[1] = 0xff;
ucontrol->value.iec958.status[2] = 0xff;
ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS;
return 0;
}
static int snd_vortex_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
ucontrol->value.iec958.status[0] = 0x00;
ucontrol->value.iec958.status[1] = IEC958_AES1_CON_ORIGINAL|IEC958_AES1_CON_DIGDIGCONV_ID;
ucontrol->value.iec958.status[2] = 0x00;
switch (vortex->spdif_sr) {
case 32000: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_32000; break;
case 44100: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_44100; break;
case 48000: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_48000; break;
}
return 0;
}
static int snd_vortex_spdif_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
int spdif_sr = 48000;
switch (ucontrol->value.iec958.status[3] & IEC958_AES3_CON_FS) {
case IEC958_AES3_CON_FS_32000: spdif_sr = 32000; break;
case IEC958_AES3_CON_FS_44100: spdif_sr = 44100; break;
case IEC958_AES3_CON_FS_48000: spdif_sr = 48000; break;
}
if (spdif_sr == vortex->spdif_sr)
return 0;
vortex->spdif_sr = spdif_sr;
vortex_spdif_init(vortex, vortex->spdif_sr, 1);
return 1;
}
/* spdif controls */
static struct snd_kcontrol_new snd_vortex_mixer_spdif[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
.info = snd_vortex_spdif_info,
.get = snd_vortex_spdif_get,
.put = snd_vortex_spdif_put,
},
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
.info = snd_vortex_spdif_info,
.get = snd_vortex_spdif_mask_get
},
};
/* subdevice PCM Volume control */
static int snd_vortex_pcm_vol_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = (VORTEX_IS_QUAD(vortex) ? 4 : 2);
uinfo->value.integer.min = -128;
uinfo->value.integer.max = 32;
return 0;
}
static int snd_vortex_pcm_vol_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
int subdev = kcontrol->id.subdevice;
struct pcm_vol *p = &vortex->pcm_vol[subdev];
int max_chn = (VORTEX_IS_QUAD(vortex) ? 4 : 2);
for (i = 0; i < max_chn; i++)
ucontrol->value.integer.value[i] = p->vol[i];
return 0;
}
static int snd_vortex_pcm_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
int changed = 0;
int mixin;
unsigned char vol;
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
int subdev = kcontrol->id.subdevice;
struct pcm_vol *p = &vortex->pcm_vol[subdev];
int max_chn = (VORTEX_IS_QUAD(vortex) ? 4 : 2);
for (i = 0; i < max_chn; i++) {
if (p->vol[i] != ucontrol->value.integer.value[i]) {
p->vol[i] = ucontrol->value.integer.value[i];
if (p->active) {
switch (vortex->dma_adb[p->dma].nr_ch) {
case 1:
mixin = p->mixin[0];
break;
case 2:
default:
mixin = p->mixin[(i < 2) ? i : (i - 2)];
break;
case 4:
mixin = p->mixin[i];
break;
}
vol = p->vol[i];
vortex_mix_setinputvolumebyte(vortex,
vortex->mixplayb[i], mixin, vol);
}
changed = 1;
}
}
return changed;
}
static const DECLARE_TLV_DB_MINMAX(vortex_pcm_vol_db_scale, -9600, 2400);
static struct snd_kcontrol_new snd_vortex_pcm_vol = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "PCM Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ |
SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.info = snd_vortex_pcm_vol_info,
.get = snd_vortex_pcm_vol_get,
.put = snd_vortex_pcm_vol_put,
.tlv = { .p = vortex_pcm_vol_db_scale },
};
/* create a pcm device */
static int snd_vortex_new_pcm(vortex_t *chip, int idx, int nr)
{
struct snd_pcm *pcm;
struct snd_kcontrol *kctl;
int i;
int err, nr_capt;
if (!chip || idx < 0 || idx >= VORTEX_PCM_LAST)
return -ENODEV;
/* idx indicates which kind of PCM device. ADB, SPDIF, I2S and A3D share the
* same dma engine. WT uses it own separate dma engine which can't capture. */
if (idx == VORTEX_PCM_ADB)
nr_capt = nr;
else
nr_capt = 0;
err = snd_pcm_new(chip->card, vortex_pcm_prettyname[idx], idx, nr,
nr_capt, &pcm);
if (err < 0)
return err;
snprintf(pcm->name, sizeof(pcm->name),
"%s %s", CARD_NAME_SHORT, vortex_pcm_name[idx]);
chip->pcm[idx] = pcm;
// This is an evil hack, but it saves a lot of duplicated code.
VORTEX_PCM_TYPE(pcm) = idx;
pcm->private_data = chip;
/* set operators */
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_vortex_playback_ops);
if (idx == VORTEX_PCM_ADB)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
&snd_vortex_playback_ops);
/* pre-allocation of Scatter-Gather buffers */
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
snd_dma_pci_data(chip->pci_dev),
0x10000, 0x10000);
switch (VORTEX_PCM_TYPE(pcm)) {
case VORTEX_PCM_ADB:
err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
snd_pcm_std_chmaps,
VORTEX_IS_QUAD(chip) ? 4 : 2,
0, NULL);
if (err < 0)
return err;
err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_CAPTURE,
snd_pcm_std_chmaps, 2, 0, NULL);
if (err < 0)
return err;
break;
#ifdef CHIP_AU8830
case VORTEX_PCM_A3D:
err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
snd_pcm_std_chmaps, 1, 0, NULL);
if (err < 0)
return err;
break;
#endif
}
if (VORTEX_PCM_TYPE(pcm) == VORTEX_PCM_SPDIF) {
for (i = 0; i < ARRAY_SIZE(snd_vortex_mixer_spdif); i++) {
kctl = snd_ctl_new1(&snd_vortex_mixer_spdif[i], chip);
if (!kctl)
return -ENOMEM;
if ((err = snd_ctl_add(chip->card, kctl)) < 0)
return err;
}
}
if (VORTEX_PCM_TYPE(pcm) == VORTEX_PCM_ADB) {
for (i = 0; i < NR_PCM; i++) {
chip->pcm_vol[i].active = 0;
chip->pcm_vol[i].dma = -1;
kctl = snd_ctl_new1(&snd_vortex_pcm_vol, chip);
if (!kctl)
return -ENOMEM;
chip->pcm_vol[i].kctl = kctl;
kctl->id.device = 0;
kctl->id.subdevice = i;
err = snd_ctl_add(chip->card, kctl);
if (err < 0)
return err;
}
}
return 0;
}

View file

@ -0,0 +1,409 @@
/*
* 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 Library 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.
*/
/*
* Someday its supposed to make use of the WT DMA engine
* for a Wavetable synthesizer.
*/
#include "au88x0.h"
#include "au88x0_wt.h"
static void vortex_fifo_setwtvalid(vortex_t * vortex, int fifo, int en);
static void vortex_connection_adb_mixin(vortex_t * vortex, int en,
unsigned char channel,
unsigned char source,
unsigned char mixin);
static void vortex_connection_mixin_mix(vortex_t * vortex, int en,
unsigned char mixin,
unsigned char mix, int a);
static void vortex_fifo_wtinitialize(vortex_t * vortex, int fifo, int j);
static int vortex_wt_SetReg(vortex_t * vortex, unsigned char reg, int wt,
u32 val);
/* WT */
/* Put 2 WT channels together for one stereo interlaced channel. */
static void vortex_wt_setstereo(vortex_t * vortex, u32 wt, u32 stereo)
{
int temp;
//temp = hwread(vortex->mmio, 0x80 + ((wt >> 0x5)<< 0xf) + (((wt & 0x1f) >> 1) << 2));
temp = hwread(vortex->mmio, WT_STEREO(wt));
temp = (temp & 0xfe) | (stereo & 1);
//hwwrite(vortex->mmio, 0x80 + ((wt >> 0x5)<< 0xf) + (((wt & 0x1f) >> 1) << 2), temp);
hwwrite(vortex->mmio, WT_STEREO(wt), temp);
}
/* Join to mixdown route. */
static void vortex_wt_setdsout(vortex_t * vortex, u32 wt, int en)
{
int temp;
/* There is one DSREG register for each bank (32 voices each). */
temp = hwread(vortex->mmio, WT_DSREG((wt >= 0x20) ? 1 : 0));
if (en)
temp |= (1 << (wt & 0x1f));
else
temp &= ~(1 << (wt & 0x1f));
hwwrite(vortex->mmio, WT_DSREG((wt >= 0x20) ? 1 : 0), temp);
}
/* Setup WT route. */
static int vortex_wt_allocroute(vortex_t * vortex, int wt, int nr_ch)
{
wt_voice_t *voice = &(vortex->wt_voice[wt]);
int temp;
//FIXME: WT audio routing.
if (nr_ch) {
vortex_fifo_wtinitialize(vortex, wt, 1);
vortex_fifo_setwtvalid(vortex, wt, 1);
vortex_wt_setstereo(vortex, wt, nr_ch - 1);
} else
vortex_fifo_setwtvalid(vortex, wt, 0);
/* Set mixdown mode. */
vortex_wt_setdsout(vortex, wt, 1);
/* Set other parameter registers. */
hwwrite(vortex->mmio, WT_SRAMP(0), 0x880000);
//hwwrite(vortex->mmio, WT_GMODE(0), 0xffffffff);
#ifdef CHIP_AU8830
hwwrite(vortex->mmio, WT_SRAMP(1), 0x880000);
//hwwrite(vortex->mmio, WT_GMODE(1), 0xffffffff);
#endif
hwwrite(vortex->mmio, WT_PARM(wt, 0), 0);
hwwrite(vortex->mmio, WT_PARM(wt, 1), 0);
hwwrite(vortex->mmio, WT_PARM(wt, 2), 0);
temp = hwread(vortex->mmio, WT_PARM(wt, 3));
pr_debug( "vortex: WT PARM3: %x\n", temp);
//hwwrite(vortex->mmio, WT_PARM(wt, 3), temp);
hwwrite(vortex->mmio, WT_DELAY(wt, 0), 0);
hwwrite(vortex->mmio, WT_DELAY(wt, 1), 0);
hwwrite(vortex->mmio, WT_DELAY(wt, 2), 0);
hwwrite(vortex->mmio, WT_DELAY(wt, 3), 0);
pr_debug( "vortex: WT GMODE: %x\n", hwread(vortex->mmio, WT_GMODE(wt)));
hwwrite(vortex->mmio, WT_PARM(wt, 2), 0xffffffff);
hwwrite(vortex->mmio, WT_PARM(wt, 3), 0xcff1c810);
voice->parm0 = voice->parm1 = 0xcfb23e2f;
hwwrite(vortex->mmio, WT_PARM(wt, 0), voice->parm0);
hwwrite(vortex->mmio, WT_PARM(wt, 1), voice->parm1);
pr_debug( "vortex: WT GMODE 2 : %x\n", hwread(vortex->mmio, WT_GMODE(wt)));
return 0;
}
static void vortex_wt_connect(vortex_t * vortex, int en)
{
int i, ii, mix;
#define NR_WTROUTES 6
#ifdef CHIP_AU8830
#define NR_WTBLOCKS 2
#else
#define NR_WTBLOCKS 1
#endif
for (i = 0; i < NR_WTBLOCKS; i++) {
for (ii = 0; ii < NR_WTROUTES; ii++) {
mix =
vortex_adb_checkinout(vortex,
vortex->fixed_res, en,
VORTEX_RESOURCE_MIXIN);
vortex->mixwt[(i * NR_WTROUTES) + ii] = mix;
vortex_route(vortex, en, 0x11,
ADB_WTOUT(i, ii + 0x20), ADB_MIXIN(mix));
vortex_connection_mixin_mix(vortex, en, mix,
vortex->mixplayb[ii % 2], 0);
if (VORTEX_IS_QUAD(vortex))
vortex_connection_mixin_mix(vortex, en,
mix,
vortex->mixplayb[2 +
(ii % 2)], 0);
}
}
for (i = 0; i < NR_WT; i++) {
hwwrite(vortex->mmio, WT_RUN(i), 1);
}
}
/* Read WT Register */
#if 0
static int vortex_wt_GetReg(vortex_t * vortex, char reg, int wt)
{
//int eax, esi;
if (reg == 4) {
return hwread(vortex->mmio, WT_PARM(wt, 3));
}
if (reg == 7) {
return hwread(vortex->mmio, WT_GMODE(wt));
}
return 0;
}
/* WT hardware abstraction layer generic register interface. */
static int
vortex_wt_SetReg2(vortex_t * vortex, unsigned char reg, int wt,
u16 val)
{
/*
int eax, edx;
if (wt >= NR_WT) // 0x40 -> NR_WT
return 0;
if ((reg - 0x20) > 0) {
if ((reg - 0x21) != 0)
return 0;
eax = ((((b & 0xff) << 0xb) + (edx & 0xff)) << 4) + 0x208; // param 2
} else {
eax = ((((b & 0xff) << 0xb) + (edx & 0xff)) << 4) + 0x20a; // param 3
}
hwwrite(vortex->mmio, eax, c);
*/
return 1;
}
/*public: static void __thiscall CWTHal::SetReg(unsigned char,int,unsigned long) */
#endif
static int
vortex_wt_SetReg(vortex_t * vortex, unsigned char reg, int wt,
u32 val)
{
int ecx;
if ((reg == 5) || ((reg >= 7) && (reg <= 10)) || (reg == 0xc)) {
if (wt >= (NR_WT / NR_WT_PB)) {
pr_warn
("vortex: WT SetReg: bank out of range. reg=0x%x, wt=%d\n",
reg, wt);
return 0;
}
} else {
if (wt >= NR_WT) {
pr_err( "vortex: WT SetReg: voice out of range\n");
return 0;
}
}
if (reg > 0xc)
return 0;
switch (reg) {
/* Voice specific parameters */
case 0: /* running */
/*
pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n",
WT_RUN(wt), (int)val);
*/
hwwrite(vortex->mmio, WT_RUN(wt), val);
return 0xc;
case 1: /* param 0 */
/*
pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n",
WT_PARM(wt,0), (int)val);
*/
hwwrite(vortex->mmio, WT_PARM(wt, 0), val);
return 0xc;
case 2: /* param 1 */
/*
pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n",
WT_PARM(wt,1), (int)val);
*/
hwwrite(vortex->mmio, WT_PARM(wt, 1), val);
return 0xc;
case 3: /* param 2 */
/*
pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n",
WT_PARM(wt,2), (int)val);
*/
hwwrite(vortex->mmio, WT_PARM(wt, 2), val);
return 0xc;
case 4: /* param 3 */
/*
pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n",
WT_PARM(wt,3), (int)val);
*/
hwwrite(vortex->mmio, WT_PARM(wt, 3), val);
return 0xc;
case 6: /* mute */
/*
pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n",
WT_MUTE(wt), (int)val);
*/
hwwrite(vortex->mmio, WT_MUTE(wt), val);
return 0xc;
case 0xb:
/* delay */
/*
pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n",
WT_DELAY(wt,0), (int)val);
*/
hwwrite(vortex->mmio, WT_DELAY(wt, 3), val);
hwwrite(vortex->mmio, WT_DELAY(wt, 2), val);
hwwrite(vortex->mmio, WT_DELAY(wt, 1), val);
hwwrite(vortex->mmio, WT_DELAY(wt, 0), val);
return 0xc;
/* Global WT block parameters */
case 5: /* sramp */
ecx = WT_SRAMP(wt);
break;
case 8: /* aramp */
ecx = WT_ARAMP(wt);
break;
case 9: /* mramp */
ecx = WT_MRAMP(wt);
break;
case 0xa: /* ctrl */
ecx = WT_CTRL(wt);
break;
case 0xc: /* ds_reg */
ecx = WT_DSREG(wt);
break;
default:
return 0;
}
/*
pr_debug( "vortex: WT SetReg(0x%x) = 0x%08x\n", ecx, (int)val);
*/
hwwrite(vortex->mmio, ecx, val);
return 1;
}
static void vortex_wt_init(vortex_t * vortex)
{
u32 var4, var8, varc, var10 = 0, edi;
var10 &= 0xFFFFFFE3;
var10 |= 0x22;
var10 &= 0xFFFFFEBF;
var10 |= 0x80;
var10 |= 0x200;
var10 &= 0xfffffffe;
var10 &= 0xfffffbff;
var10 |= 0x1800;
// var10 = 0x1AA2
var4 = 0x10000000;
varc = 0x00830000;
var8 = 0x00830000;
/* Init Bank registers. */
for (edi = 0; edi < (NR_WT / NR_WT_PB); edi++) {
vortex_wt_SetReg(vortex, 0xc, edi, 0); /* ds_reg */
vortex_wt_SetReg(vortex, 0xa, edi, var10); /* ctrl */
vortex_wt_SetReg(vortex, 0x9, edi, var4); /* mramp */
vortex_wt_SetReg(vortex, 0x8, edi, varc); /* aramp */
vortex_wt_SetReg(vortex, 0x5, edi, var8); /* sramp */
}
/* Init Voice registers. */
for (edi = 0; edi < NR_WT; edi++) {
vortex_wt_SetReg(vortex, 0x4, edi, 0); /* param 3 0x20c */
vortex_wt_SetReg(vortex, 0x3, edi, 0); /* param 2 0x208 */
vortex_wt_SetReg(vortex, 0x2, edi, 0); /* param 1 0x204 */
vortex_wt_SetReg(vortex, 0x1, edi, 0); /* param 0 0x200 */
vortex_wt_SetReg(vortex, 0xb, edi, 0); /* delay 0x400 - 0x40c */
}
var10 |= 1;
for (edi = 0; edi < (NR_WT / NR_WT_PB); edi++)
vortex_wt_SetReg(vortex, 0xa, edi, var10); /* ctrl */
}
/* Extract of CAdbTopology::SetVolume(struct _ASPVOLUME *) */
#if 0
static void vortex_wt_SetVolume(vortex_t * vortex, int wt, int vol[])
{
wt_voice_t *voice = &(vortex->wt_voice[wt]);
int ecx = vol[1], eax = vol[0];
/* This is pure guess */
voice->parm0 &= 0xff00ffff;
voice->parm0 |= (vol[0] & 0xff) << 0x10;
voice->parm1 &= 0xff00ffff;
voice->parm1 |= (vol[1] & 0xff) << 0x10;
/* This is real */
hwwrite(vortex, WT_PARM(wt, 0), voice->parm0);
hwwrite(vortex, WT_PARM(wt, 1), voice->parm0);
if (voice->this_1D0 & 4) {
eax >>= 8;
ecx = eax;
if (ecx < 0x80)
ecx = 0x7f;
voice->parm3 &= 0xFFFFC07F;
voice->parm3 |= (ecx & 0x7f) << 7;
voice->parm3 &= 0xFFFFFF80;
voice->parm3 |= (eax & 0x7f);
} else {
voice->parm3 &= 0xFFE03FFF;
voice->parm3 |= (eax & 0xFE00) << 5;
}
hwwrite(vortex, WT_PARM(wt, 3), voice->parm3);
}
/* Extract of CAdbTopology::SetFrequency(unsigned long arg_0) */
static void vortex_wt_SetFrequency(vortex_t * vortex, int wt, unsigned int sr)
{
wt_voice_t *voice = &(vortex->wt_voice[wt]);
u32 eax, edx;
//FIXME: 64 bit operation.
eax = ((sr << 0xf) * 0x57619F1) & 0xffffffff;
edx = (((sr << 0xf) * 0x57619F1)) >> 0x20;
edx >>= 0xa;
edx <<= 1;
if (edx) {
if (edx & 0x0FFF80000)
eax = 0x7fff;
else {
edx <<= 0xd;
eax = 7;
while ((edx & 0x80000000) == 0) {
edx <<= 1;
eax--;
if (eax == 0)
break;
}
if (eax)
edx <<= 1;
eax <<= 0xc;
edx >>= 0x14;
eax |= edx;
}
} else
eax = 0;
voice->parm0 &= 0xffff0001;
voice->parm0 |= (eax & 0x7fff) << 1;
voice->parm1 = voice->parm0 | 1;
// Wt: this_1D4
//AuWt::WriteReg((ulong)(this_1DC<<4)+0x200, (ulong)this_1E4);
//AuWt::WriteReg((ulong)(this_1DC<<4)+0x204, (ulong)this_1E8);
hwwrite(vortex->mmio, WT_PARM(wt, 0), voice->parm0);
hwwrite(vortex->mmio, WT_PARM(wt, 1), voice->parm1);
}
#endif
/* End of File */

View file

@ -0,0 +1,65 @@
/***************************************************************************
* WT register offsets.
*
* Wed Oct 22 13:50:20 2003
* Copyright 2003 mjander
* mjander@users.sourceforge.org
****************************************************************************/
#ifndef _AU88X0_WT_H
#define _AU88X0_WT_H
/* WT channels are grouped in banks. Each bank has 0x20 channels. */
/* Bank register address boundary is 0x8000 */
#define NR_WT_PB 0x20
/* WT bank base register (as dword address). */
#define WT_BAR(x) (((x)&0xffe0)<<0x8)
#define WT_BANK(x) (x>>5)
/* WT Bank registers */
#define WT_CTRL(bank) (((((bank)&1)<<0xd) + 0x00)<<2) /* 0x0000 */
#define WT_SRAMP(bank) (((((bank)&1)<<0xd) + 0x01)<<2) /* 0x0004 */
#define WT_DSREG(bank) (((((bank)&1)<<0xd) + 0x02)<<2) /* 0x0008 */
#define WT_MRAMP(bank) (((((bank)&1)<<0xd) + 0x03)<<2) /* 0x000c */
#define WT_GMODE(bank) (((((bank)&1)<<0xd) + 0x04)<<2) /* 0x0010 */
#define WT_ARAMP(bank) (((((bank)&1)<<0xd) + 0x05)<<2) /* 0x0014 */
/* WT Voice registers */
#define WT_STEREO(voice) ((WT_BAR(voice)+ 0x20 +(((voice)&0x1f)>>1))<<2) /* 0x0080 */
#define WT_MUTE(voice) ((WT_BAR(voice)+ 0x40 +((voice)&0x1f))<<2) /* 0x0100 */
#define WT_RUN(voice) ((WT_BAR(voice)+ 0x60 +((voice)&0x1f))<<2) /* 0x0180 */
/* Some kind of parameters. */
/* PARM0, PARM1 : Filter (0xFF000000), SampleRate (0x0000FFFF) */
/* PARM2, PARM3 : Still unknown */
#define WT_PARM(x,y) (((WT_BAR(x))+ 0x80 +(((x)&0x1f)<<2)+(y))<<2) /* 0x0200 */
#define WT_DELAY(x,y) (((WT_BAR(x))+ 0x100 +(((x)&0x1f)<<2)+(y))<<2) /* 0x0400 */
/* Numeric indexes used by SetReg() and GetReg() */
#if 0
enum {
run = 0, /* 0 W 1:run 0:stop */
parm0, /* 1 W filter, samplerate */
parm1, /* 2 W filter, samplerate */
parm2, /* 3 W */
parm3, /* 4 RW volume. This value is calculated using floating point ops. */
sramp, /* 5 W */
mute, /* 6 W 1:mute, 0:unmute */
gmode, /* 7 RO Looks like only bit0 is used. */
aramp, /* 8 W */
mramp, /* 9 W */
ctrl, /* a W */
delay, /* b W All 4 values are written at once with same value. */
dsreg, /* c (R)W */
} wt_reg;
#endif
typedef struct {
u32 parm0; /* this_1E4 */
u32 parm1; /* this_1E8 */
u32 parm2; /* this_1EC */
u32 parm3; /* this_1F0 */
u32 this_1D0;
} wt_voice_t;
#endif /* _AU88X0_WT_H */
/* End of file */

View file

@ -0,0 +1,795 @@
/***************************************************************************
* au88x0_cxtalk.c
*
* Wed Nov 19 16:29:47 2003
* Copyright 2003 mjander
* mjander@users.sourceforge.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 Library 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 "au88x0_xtalk.h"
/* Data (a whole lot of data.... ) */
static short const sXtalkWideKLeftEq = 0x269C;
static short const sXtalkWideKRightEq = 0x269C;
static short const sXtalkWideKLeftXt = 0xF25E;
static short const sXtalkWideKRightXt = 0xF25E;
static short const sXtalkWideShiftLeftEq = 1;
static short const sXtalkWideShiftRightEq = 1;
static short const sXtalkWideShiftLeftXt = 0;
static short const sXtalkWideShiftRightXt = 0;
static unsigned short const wXtalkWideLeftDelay = 0xd;
static unsigned short const wXtalkWideRightDelay = 0xd;
static short const sXtalkNarrowKLeftEq = 0x468D;
static short const sXtalkNarrowKRightEq = 0x468D;
static short const sXtalkNarrowKLeftXt = 0xF82E;
static short const sXtalkNarrowKRightXt = 0xF82E;
static short const sXtalkNarrowShiftLeftEq = 0x3;
static short const sXtalkNarrowShiftRightEq = 0x3;
static short const sXtalkNarrowShiftLeftXt = 0;
static short const sXtalkNarrowShiftRightXt = 0;
static unsigned short const wXtalkNarrowLeftDelay = 0x7;
static unsigned short const wXtalkNarrowRightDelay = 0x7;
static xtalk_gains_t const asXtalkGainsDefault = {
0x4000, 0x4000, 0x4000, 0x4000, 0x4000,
0x4000, 0x4000, 0x4000, 0x4000, 0x4000
};
static xtalk_gains_t const asXtalkGainsTest = {
0x7fff, 0x8000, 0x0000, 0x0000, 0x0001,
0xffff, 0x4000, 0xc000, 0x0002, 0xfffe
};
static xtalk_gains_t const asXtalkGains1Chan = {
0x7FFF, 0, 0, 0, 0,
0x7FFF, 0, 0, 0, 0,
};
// Input gain for 4 A3D slices. One possible input pair is left zero.
static xtalk_gains_t const asXtalkGainsAllChan = {
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0
};
static xtalk_gains_t const asXtalkGainsZeros = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static xtalk_dline_t const alXtalkDlineZeros = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static xtalk_dline_t const alXtalkDlineTest = {
0x0000fc18, 0xfff03e8, 0x000186a0, 0xfffe7960, 1, 0xffffffff, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
static xtalk_instate_t const asXtalkInStateZeros = {
0, 0, 0, 0
};
static xtalk_instate_t const asXtalkInStateTest = {
0x0080, 0xff80, 0x0001, 0xffff
};
static xtalk_state_t const asXtalkOutStateZeros = {
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
};
static short const sDiamondKLeftEq = 0x401d;
static short const sDiamondKRightEq = 0x401d;
static short const sDiamondKLeftXt = 0xF90E;
static short const sDiamondKRightXt = 0xF90E;
static short const sDiamondShiftLeftEq = 1;
static short const sDiamondShiftRightEq = 1;
static short const sDiamondShiftLeftXt = 0;
static short const sDiamondShiftRightXt = 0;
static unsigned short const wDiamondLeftDelay = 0xb;
static unsigned short const wDiamondRightDelay = 0xb;
static xtalk_coefs_t const asXtalkWideCoefsLeftEq = {
{0xEC4C, 0xDCE9, 0xFDC2, 0xFEEC, 0},
{0x5F60, 0xCBCB, 0xFC26, 0x0305, 0},
{0x340B, 0xe8f5, 0x236c, 0xe40d, 0},
{0x76d5, 0xc78d, 0x05ac, 0xfa5b, 0},
{0x7F04, 0xC0FA, 0x0263, 0xFDA2, 0}
};
static xtalk_coefs_t const asXtalkWideCoefsRightEq = {
{0xEC4C, 0xDCE9, 0xFDC2, 0xFEEC, 0},
{0x5F60, 0xCBCB, 0xFC26, 0x0305, 0},
{0x340B, 0xe8f5, 0x236c, 0xe40d, 0},
{0x76d5, 0xc78d, 0x05ac, 0xfa5b, 0},
{0x7F04, 0xC0FA, 0x0263, 0xFDA2, 0}
};
static xtalk_coefs_t const asXtalkWideCoefsLeftXt = {
{0x55c6, 0xc97b, 0x005b, 0x0047, 0},
{0x6a60, 0xca20, 0xffc6, 0x0040, 0},
{0x6411, 0xd711, 0xfca1, 0x0190, 0},
{0x77dc, 0xc79e, 0xffb8, 0x000a, 0},
{0, 0, 0, 0, 0}
};
static xtalk_coefs_t const asXtalkWideCoefsRightXt = {
{0x55c6, 0xc97b, 0x005b, 0x0047, 0},
{0x6a60, 0xca20, 0xffc6, 0x0040, 0},
{0x6411, 0xd711, 0xfca1, 0x0190, 0},
{0x77dc, 0xc79e, 0xffb8, 0x000a, 0},
{0, 0, 0, 0, 0}
};
static xtalk_coefs_t const asXtalkNarrowCoefsLeftEq = {
{0x50B5, 0xD07C, 0x026D, 0xFD21, 0},
{0x460F, 0xE44F, 0xF75E, 0xEFA6, 0},
{0x556D, 0xDCAB, 0x2098, 0xF0F2, 0},
{0x7E03, 0xC1F0, 0x007D, 0xFF89, 0},
{0x383E, 0xFD9D, 0xB278, 0x4547, 0}
};
static xtalk_coefs_t const asXtalkNarrowCoefsRightEq = {
{0x50B5, 0xD07C, 0x026D, 0xFD21, 0},
{0x460F, 0xE44F, 0xF75E, 0xEFA6, 0},
{0x556D, 0xDCAB, 0x2098, 0xF0F2, 0},
{0x7E03, 0xC1F0, 0x007D, 0xFF89, 0},
{0x383E, 0xFD9D, 0xB278, 0x4547, 0}
};
static xtalk_coefs_t const asXtalkNarrowCoefsLeftXt = {
{0x3CB2, 0xDF49, 0xF6EA, 0x095B, 0},
{0x6777, 0xC915, 0xFEAF, 0x00B1, 0},
{0x7762, 0xC7D9, 0x025B, 0xFDA6, 0},
{0x6B7A, 0xD2AA, 0xF2FB, 0x0B64, 0},
{0, 0, 0, 0, 0}
};
static xtalk_coefs_t const asXtalkNarrowCoefsRightXt = {
{0x3CB2, 0xDF49, 0xF6EA, 0x095B, 0},
{0x6777, 0xC915, 0xFEAF, 0x00B1, 0},
{0x7762, 0xC7D9, 0x025B, 0xFDA6, 0},
{0x6B7A, 0xD2AA, 0xF2FB, 0x0B64, 0},
{0, 0, 0, 0, 0}
};
static xtalk_coefs_t const asXtalkCoefsZeros = {
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
static xtalk_coefs_t const asXtalkCoefsPipe = {
{0, 0, 0x0FA0, 0, 0},
{0, 0, 0x0FA0, 0, 0},
{0, 0, 0x0FA0, 0, 0},
{0, 0, 0x0FA0, 0, 0},
{0, 0, 0x1180, 0, 0},
};
static xtalk_coefs_t const asXtalkCoefsNegPipe = {
{0, 0, 0xF380, 0, 0},
{0, 0, 0xF380, 0, 0},
{0, 0, 0xF380, 0, 0},
{0, 0, 0xF380, 0, 0},
{0, 0, 0xF200, 0, 0}
};
static xtalk_coefs_t const asXtalkCoefsNumTest = {
{0, 0, 0xF380, 0x8000, 0x6D60},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
static xtalk_coefs_t const asXtalkCoefsDenTest = {
{0xC000, 0x2000, 0x4000, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
static xtalk_state_t const asXtalkOutStateTest = {
{0x7FFF, 0x0004, 0xFFFC, 0},
{0xFE00, 0x0008, 0xFFF8, 0x4000},
{0x0200, 0x0010, 0xFFF0, 0xC000},
{0x8000, 0x0020, 0xFFE0, 0},
{0, 0, 0, 0}
};
static xtalk_coefs_t const asDiamondCoefsLeftEq = {
{0x0F1E, 0x2D05, 0xF8E3, 0x07C8, 0},
{0x45E2, 0xCA51, 0x0448, 0xFCE7, 0},
{0xA93E, 0xDBD5, 0x022C, 0x028A, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
static xtalk_coefs_t const asDiamondCoefsRightEq = {
{0x0F1E, 0x2D05, 0xF8E3, 0x07C8, 0},
{0x45E2, 0xCA51, 0x0448, 0xFCE7, 0},
{0xA93E, 0xDBD5, 0x022C, 0x028A, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
static xtalk_coefs_t const asDiamondCoefsLeftXt = {
{0x3B50, 0xFE08, 0xF959, 0x0060, 0},
{0x9FCB, 0xD8F1, 0x00A2, 0x003A, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
static xtalk_coefs_t const asDiamondCoefsRightXt = {
{0x3B50, 0xFE08, 0xF959, 0x0060, 0},
{0x9FCB, 0xD8F1, 0x00A2, 0x003A, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
/**/
/* XTalk EQ and XT */
static void
vortex_XtalkHw_SetLeftEQ(vortex_t * vortex, short arg_0, short arg_4,
xtalk_coefs_t const coefs)
{
int i;
for (i = 0; i < 5; i++) {
hwwrite(vortex->mmio, 0x24200 + i * 0x24, coefs[i][0]);
hwwrite(vortex->mmio, 0x24204 + i * 0x24, coefs[i][1]);
hwwrite(vortex->mmio, 0x24208 + i * 0x24, coefs[i][2]);
hwwrite(vortex->mmio, 0x2420c + i * 0x24, coefs[i][3]);
hwwrite(vortex->mmio, 0x24210 + i * 0x24, coefs[i][4]);
}
hwwrite(vortex->mmio, 0x24538, arg_0 & 0xffff);
hwwrite(vortex->mmio, 0x2453C, arg_4 & 0xffff);
}
static void
vortex_XtalkHw_SetRightEQ(vortex_t * vortex, short arg_0, short arg_4,
xtalk_coefs_t const coefs)
{
int i;
for (i = 0; i < 5; i++) {
hwwrite(vortex->mmio, 0x242b4 + i * 0x24, coefs[i][0]);
hwwrite(vortex->mmio, 0x242b8 + i * 0x24, coefs[i][1]);
hwwrite(vortex->mmio, 0x242bc + i * 0x24, coefs[i][2]);
hwwrite(vortex->mmio, 0x242c0 + i * 0x24, coefs[i][3]);
hwwrite(vortex->mmio, 0x242c4 + i * 0x24, coefs[i][4]);
}
hwwrite(vortex->mmio, 0x24540, arg_0 & 0xffff);
hwwrite(vortex->mmio, 0x24544, arg_4 & 0xffff);
}
static void
vortex_XtalkHw_SetLeftXT(vortex_t * vortex, short arg_0, short arg_4,
xtalk_coefs_t const coefs)
{
int i;
for (i = 0; i < 5; i++) {
hwwrite(vortex->mmio, 0x24368 + i * 0x24, coefs[i][0]);
hwwrite(vortex->mmio, 0x2436c + i * 0x24, coefs[i][1]);
hwwrite(vortex->mmio, 0x24370 + i * 0x24, coefs[i][2]);
hwwrite(vortex->mmio, 0x24374 + i * 0x24, coefs[i][3]);
hwwrite(vortex->mmio, 0x24378 + i * 0x24, coefs[i][4]);
}
hwwrite(vortex->mmio, 0x24548, arg_0 & 0xffff);
hwwrite(vortex->mmio, 0x2454C, arg_4 & 0xffff);
}
static void
vortex_XtalkHw_SetRightXT(vortex_t * vortex, short arg_0, short arg_4,
xtalk_coefs_t const coefs)
{
int i;
for (i = 0; i < 5; i++) {
hwwrite(vortex->mmio, 0x2441C + i * 0x24, coefs[i][0]);
hwwrite(vortex->mmio, 0x24420 + i * 0x24, coefs[i][1]);
hwwrite(vortex->mmio, 0x24424 + i * 0x24, coefs[i][2]);
hwwrite(vortex->mmio, 0x24428 + i * 0x24, coefs[i][3]);
hwwrite(vortex->mmio, 0x2442C + i * 0x24, coefs[i][4]);
}
hwwrite(vortex->mmio, 0x24550, arg_0 & 0xffff);
hwwrite(vortex->mmio, 0x24554, arg_4 & 0xffff);
}
static void
vortex_XtalkHw_SetLeftEQStates(vortex_t * vortex,
xtalk_instate_t const arg_0,
xtalk_state_t const coefs)
{
int i;
for (i = 0; i < 5; i++) {
hwwrite(vortex->mmio, 0x24214 + i * 0x24, coefs[i][0]);
hwwrite(vortex->mmio, 0x24218 + i * 0x24, coefs[i][1]);
hwwrite(vortex->mmio, 0x2421C + i * 0x24, coefs[i][2]);
hwwrite(vortex->mmio, 0x24220 + i * 0x24, coefs[i][3]);
}
hwwrite(vortex->mmio, 0x244F8, arg_0[0]);
hwwrite(vortex->mmio, 0x244FC, arg_0[1]);
hwwrite(vortex->mmio, 0x24500, arg_0[2]);
hwwrite(vortex->mmio, 0x24504, arg_0[3]);
}
static void
vortex_XtalkHw_SetRightEQStates(vortex_t * vortex,
xtalk_instate_t const arg_0,
xtalk_state_t const coefs)
{
int i;
for (i = 0; i < 5; i++) {
hwwrite(vortex->mmio, 0x242C8 + i * 0x24, coefs[i][0]);
hwwrite(vortex->mmio, 0x242CC + i * 0x24, coefs[i][1]);
hwwrite(vortex->mmio, 0x242D0 + i * 0x24, coefs[i][2]);
hwwrite(vortex->mmio, 0x244D4 + i * 0x24, coefs[i][3]);
}
hwwrite(vortex->mmio, 0x24508, arg_0[0]);
hwwrite(vortex->mmio, 0x2450C, arg_0[1]);
hwwrite(vortex->mmio, 0x24510, arg_0[2]);
hwwrite(vortex->mmio, 0x24514, arg_0[3]);
}
static void
vortex_XtalkHw_SetLeftXTStates(vortex_t * vortex,
xtalk_instate_t const arg_0,
xtalk_state_t const coefs)
{
int i;
for (i = 0; i < 5; i++) {
hwwrite(vortex->mmio, 0x2437C + i * 0x24, coefs[i][0]);
hwwrite(vortex->mmio, 0x24380 + i * 0x24, coefs[i][1]);
hwwrite(vortex->mmio, 0x24384 + i * 0x24, coefs[i][2]);
hwwrite(vortex->mmio, 0x24388 + i * 0x24, coefs[i][3]);
}
hwwrite(vortex->mmio, 0x24518, arg_0[0]);
hwwrite(vortex->mmio, 0x2451C, arg_0[1]);
hwwrite(vortex->mmio, 0x24520, arg_0[2]);
hwwrite(vortex->mmio, 0x24524, arg_0[3]);
}
static void
vortex_XtalkHw_SetRightXTStates(vortex_t * vortex,
xtalk_instate_t const arg_0,
xtalk_state_t const coefs)
{
int i;
for (i = 0; i < 5; i++) {
hwwrite(vortex->mmio, 0x24430 + i * 0x24, coefs[i][0]);
hwwrite(vortex->mmio, 0x24434 + i * 0x24, coefs[i][1]);
hwwrite(vortex->mmio, 0x24438 + i * 0x24, coefs[i][2]);
hwwrite(vortex->mmio, 0x2443C + i * 0x24, coefs[i][3]);
}
hwwrite(vortex->mmio, 0x24528, arg_0[0]);
hwwrite(vortex->mmio, 0x2452C, arg_0[1]);
hwwrite(vortex->mmio, 0x24530, arg_0[2]);
hwwrite(vortex->mmio, 0x24534, arg_0[3]);
}
#if 0
static void
vortex_XtalkHw_GetLeftEQ(vortex_t * vortex, short *arg_0, short *arg_4,
xtalk_coefs_t coefs)
{
int i;
for (i = 0; i < 5; i++) {
coefs[i][0] = hwread(vortex->mmio, 0x24200 + i * 0x24);
coefs[i][1] = hwread(vortex->mmio, 0x24204 + i * 0x24);
coefs[i][2] = hwread(vortex->mmio, 0x24208 + i * 0x24);
coefs[i][3] = hwread(vortex->mmio, 0x2420c + i * 0x24);
coefs[i][4] = hwread(vortex->mmio, 0x24210 + i * 0x24);
}
*arg_0 = hwread(vortex->mmio, 0x24538) & 0xffff;
*arg_4 = hwread(vortex->mmio, 0x2453c) & 0xffff;
}
static void
vortex_XtalkHw_GetRightEQ(vortex_t * vortex, short *arg_0, short *arg_4,
xtalk_coefs_t coefs)
{
int i;
for (i = 0; i < 5; i++) {
coefs[i][0] = hwread(vortex->mmio, 0x242b4 + i * 0x24);
coefs[i][1] = hwread(vortex->mmio, 0x242b8 + i * 0x24);
coefs[i][2] = hwread(vortex->mmio, 0x242bc + i * 0x24);
coefs[i][3] = hwread(vortex->mmio, 0x242c0 + i * 0x24);
coefs[i][4] = hwread(vortex->mmio, 0x242c4 + i * 0x24);
}
*arg_0 = hwread(vortex->mmio, 0x24540) & 0xffff;
*arg_4 = hwread(vortex->mmio, 0x24544) & 0xffff;
}
static void
vortex_XtalkHw_GetLeftXT(vortex_t * vortex, short *arg_0, short *arg_4,
xtalk_coefs_t coefs)
{
int i;
for (i = 0; i < 5; i++) {
coefs[i][0] = hwread(vortex->mmio, 0x24368 + i * 0x24);
coefs[i][1] = hwread(vortex->mmio, 0x2436C + i * 0x24);
coefs[i][2] = hwread(vortex->mmio, 0x24370 + i * 0x24);
coefs[i][3] = hwread(vortex->mmio, 0x24374 + i * 0x24);
coefs[i][4] = hwread(vortex->mmio, 0x24378 + i * 0x24);
}
*arg_0 = hwread(vortex->mmio, 0x24548) & 0xffff;
*arg_4 = hwread(vortex->mmio, 0x2454C) & 0xffff;
}
static void
vortex_XtalkHw_GetRightXT(vortex_t * vortex, short *arg_0, short *arg_4,
xtalk_coefs_t coefs)
{
int i;
for (i = 0; i < 5; i++) {
coefs[i][0] = hwread(vortex->mmio, 0x2441C + i * 0x24);
coefs[i][1] = hwread(vortex->mmio, 0x24420 + i * 0x24);
coefs[i][2] = hwread(vortex->mmio, 0x24424 + i * 0x24);
coefs[i][3] = hwread(vortex->mmio, 0x24428 + i * 0x24);
coefs[i][4] = hwread(vortex->mmio, 0x2442C + i * 0x24);
}
*arg_0 = hwread(vortex->mmio, 0x24550) & 0xffff;
*arg_4 = hwread(vortex->mmio, 0x24554) & 0xffff;
}
static void
vortex_XtalkHw_GetLeftEQStates(vortex_t * vortex, xtalk_instate_t arg_0,
xtalk_state_t coefs)
{
int i;
for (i = 0; i < 5; i++) {
coefs[i][0] = hwread(vortex->mmio, 0x24214 + i * 0x24);
coefs[i][1] = hwread(vortex->mmio, 0x24218 + i * 0x24);
coefs[i][2] = hwread(vortex->mmio, 0x2421C + i * 0x24);
coefs[i][3] = hwread(vortex->mmio, 0x24220 + i * 0x24);
}
arg_0[0] = hwread(vortex->mmio, 0x244F8);
arg_0[1] = hwread(vortex->mmio, 0x244FC);
arg_0[2] = hwread(vortex->mmio, 0x24500);
arg_0[3] = hwread(vortex->mmio, 0x24504);
}
static void
vortex_XtalkHw_GetRightEQStates(vortex_t * vortex, xtalk_instate_t arg_0,
xtalk_state_t coefs)
{
int i;
for (i = 0; i < 5; i++) {
coefs[i][0] = hwread(vortex->mmio, 0x242C8 + i * 0x24);
coefs[i][1] = hwread(vortex->mmio, 0x242CC + i * 0x24);
coefs[i][2] = hwread(vortex->mmio, 0x242D0 + i * 0x24);
coefs[i][3] = hwread(vortex->mmio, 0x242D4 + i * 0x24);
}
arg_0[0] = hwread(vortex->mmio, 0x24508);
arg_0[1] = hwread(vortex->mmio, 0x2450C);
arg_0[2] = hwread(vortex->mmio, 0x24510);
arg_0[3] = hwread(vortex->mmio, 0x24514);
}
static void
vortex_XtalkHw_GetLeftXTStates(vortex_t * vortex, xtalk_instate_t arg_0,
xtalk_state_t coefs)
{
int i;
for (i = 0; i < 5; i++) {
coefs[i][0] = hwread(vortex->mmio, 0x2437C + i * 0x24);
coefs[i][1] = hwread(vortex->mmio, 0x24380 + i * 0x24);
coefs[i][2] = hwread(vortex->mmio, 0x24384 + i * 0x24);
coefs[i][3] = hwread(vortex->mmio, 0x24388 + i * 0x24);
}
arg_0[0] = hwread(vortex->mmio, 0x24518);
arg_0[1] = hwread(vortex->mmio, 0x2451C);
arg_0[2] = hwread(vortex->mmio, 0x24520);
arg_0[3] = hwread(vortex->mmio, 0x24524);
}
static void
vortex_XtalkHw_GetRightXTStates(vortex_t * vortex, xtalk_instate_t arg_0,
xtalk_state_t coefs)
{
int i;
for (i = 0; i < 5; i++) {
coefs[i][0] = hwread(vortex->mmio, 0x24430 + i * 0x24);
coefs[i][1] = hwread(vortex->mmio, 0x24434 + i * 0x24);
coefs[i][2] = hwread(vortex->mmio, 0x24438 + i * 0x24);
coefs[i][3] = hwread(vortex->mmio, 0x2443C + i * 0x24);
}
arg_0[0] = hwread(vortex->mmio, 0x24528);
arg_0[1] = hwread(vortex->mmio, 0x2452C);
arg_0[2] = hwread(vortex->mmio, 0x24530);
arg_0[3] = hwread(vortex->mmio, 0x24534);
}
#endif
/* Gains */
static void
vortex_XtalkHw_SetGains(vortex_t * vortex, xtalk_gains_t const gains)
{
int i;
for (i = 0; i < XTGAINS_SZ; i++) {
hwwrite(vortex->mmio, 0x244D0 + (i * 4), gains[i]);
}
}
static void
vortex_XtalkHw_SetGainsAllChan(vortex_t * vortex)
{
vortex_XtalkHw_SetGains(vortex, asXtalkGainsAllChan);
}
#if 0
static void vortex_XtalkHw_GetGains(vortex_t * vortex, xtalk_gains_t gains)
{
int i;
for (i = 0; i < XTGAINS_SZ; i++)
gains[i] = hwread(vortex->mmio, 0x244D0 + i * 4);
}
#endif
/* Delay parameters */
static void
vortex_XtalkHw_SetDelay(vortex_t * vortex, unsigned short right,
unsigned short left)
{
u32 esp0 = 0;
esp0 &= 0x1FFFFFFF;
esp0 |= 0xA0000000;
esp0 = (esp0 & 0xffffE0ff) | ((right & 0x1F) << 8);
esp0 = (esp0 & 0xfffc1fff) | ((left & 0x1F) << 0xd);
hwwrite(vortex->mmio, 0x24660, esp0);
}
static void
vortex_XtalkHw_SetLeftDline(vortex_t * vortex, xtalk_dline_t const dline)
{
int i;
for (i = 0; i < 0x20; i++) {
hwwrite(vortex->mmio, 0x24000 + (i << 2), dline[i] & 0xffff);
hwwrite(vortex->mmio, 0x24080 + (i << 2), dline[i] >> 0x10);
}
}
static void
vortex_XtalkHw_SetRightDline(vortex_t * vortex, xtalk_dline_t const dline)
{
int i;
for (i = 0; i < 0x20; i++) {
hwwrite(vortex->mmio, 0x24100 + (i << 2), dline[i] & 0xffff);
hwwrite(vortex->mmio, 0x24180 + (i << 2), dline[i] >> 0x10);
}
}
#if 0
static void
vortex_XtalkHw_GetDelay(vortex_t * vortex, unsigned short *right,
unsigned short *left)
{
int esp0;
esp0 = hwread(vortex->mmio, 0x24660);
*right = (esp0 >> 8) & 0x1f;
*left = (esp0 >> 0xd) & 0x1f;
}
static void vortex_XtalkHw_GetLeftDline(vortex_t * vortex, xtalk_dline_t dline)
{
int i;
for (i = 0; i < 0x20; i++) {
dline[i] =
(hwread(vortex->mmio, 0x24000 + (i << 2)) & 0xffff) |
(hwread(vortex->mmio, 0x24080 + (i << 2)) << 0x10);
}
}
static void vortex_XtalkHw_GetRightDline(vortex_t * vortex, xtalk_dline_t dline)
{
int i;
for (i = 0; i < 0x20; i++) {
dline[i] =
(hwread(vortex->mmio, 0x24100 + (i << 2)) & 0xffff) |
(hwread(vortex->mmio, 0x24180 + (i << 2)) << 0x10);
}
}
#endif
/* Control/Global stuff */
#if 0
static void vortex_XtalkHw_SetControlReg(vortex_t * vortex, u32 ctrl)
{
hwwrite(vortex->mmio, 0x24660, ctrl);
}
static void vortex_XtalkHw_GetControlReg(vortex_t * vortex, u32 *ctrl)
{
*ctrl = hwread(vortex->mmio, 0x24660);
}
#endif
static void vortex_XtalkHw_SetSampleRate(vortex_t * vortex, u32 sr)
{
u32 temp;
temp = (hwread(vortex->mmio, 0x24660) & 0x1FFFFFFF) | 0xC0000000;
temp = (temp & 0xffffff07) | ((sr & 0x1f) << 3);
hwwrite(vortex->mmio, 0x24660, temp);
}
#if 0
static void vortex_XtalkHw_GetSampleRate(vortex_t * vortex, u32 *sr)
{
*sr = (hwread(vortex->mmio, 0x24660) >> 3) & 0x1f;
}
#endif
static void vortex_XtalkHw_Enable(vortex_t * vortex)
{
u32 temp;
temp = (hwread(vortex->mmio, 0x24660) & 0x1FFFFFFF) | 0xC0000000;
temp |= 1;
hwwrite(vortex->mmio, 0x24660, temp);
}
static void vortex_XtalkHw_Disable(vortex_t * vortex)
{
u32 temp;
temp = (hwread(vortex->mmio, 0x24660) & 0x1FFFFFFF) | 0xC0000000;
temp &= 0xfffffffe;
hwwrite(vortex->mmio, 0x24660, temp);
}
static void vortex_XtalkHw_ZeroIO(vortex_t * vortex)
{
int i;
for (i = 0; i < 20; i++)
hwwrite(vortex->mmio, 0x24600 + (i << 2), 0);
for (i = 0; i < 4; i++)
hwwrite(vortex->mmio, 0x24650 + (i << 2), 0);
}
static void vortex_XtalkHw_ZeroState(vortex_t * vortex)
{
vortex_XtalkHw_ZeroIO(vortex); // inlined
vortex_XtalkHw_SetLeftEQ(vortex, 0, 0, asXtalkCoefsZeros);
vortex_XtalkHw_SetRightEQ(vortex, 0, 0, asXtalkCoefsZeros);
vortex_XtalkHw_SetLeftXT(vortex, 0, 0, asXtalkCoefsZeros);
vortex_XtalkHw_SetRightXT(vortex, 0, 0, asXtalkCoefsZeros);
vortex_XtalkHw_SetGains(vortex, asXtalkGainsZeros); // inlined
vortex_XtalkHw_SetDelay(vortex, 0, 0); // inlined
vortex_XtalkHw_SetLeftDline(vortex, alXtalkDlineZeros); // inlined
vortex_XtalkHw_SetRightDline(vortex, alXtalkDlineZeros); // inlined
vortex_XtalkHw_SetLeftDline(vortex, alXtalkDlineZeros); // inlined
vortex_XtalkHw_SetRightDline(vortex, alXtalkDlineZeros); // inlined
vortex_XtalkHw_SetLeftEQStates(vortex, asXtalkInStateZeros,
asXtalkOutStateZeros);
vortex_XtalkHw_SetRightEQStates(vortex, asXtalkInStateZeros,
asXtalkOutStateZeros);
vortex_XtalkHw_SetLeftXTStates(vortex, asXtalkInStateZeros,
asXtalkOutStateZeros);
vortex_XtalkHw_SetRightXTStates(vortex, asXtalkInStateZeros,
asXtalkOutStateZeros);
}
static void vortex_XtalkHw_ProgramPipe(vortex_t * vortex)
{
vortex_XtalkHw_SetLeftEQ(vortex, 0, 1, asXtalkCoefsPipe);
vortex_XtalkHw_SetRightEQ(vortex, 0, 1, asXtalkCoefsPipe);
vortex_XtalkHw_SetLeftXT(vortex, 0, 0, asXtalkCoefsZeros);
vortex_XtalkHw_SetRightXT(vortex, 0, 0, asXtalkCoefsZeros);
vortex_XtalkHw_SetDelay(vortex, 0, 0); // inlined
}
static void vortex_XtalkHw_ProgramXtalkWide(vortex_t * vortex)
{
vortex_XtalkHw_SetLeftEQ(vortex, sXtalkWideKLeftEq,
sXtalkWideShiftLeftEq, asXtalkWideCoefsLeftEq);
vortex_XtalkHw_SetRightEQ(vortex, sXtalkWideKRightEq,
sXtalkWideShiftRightEq,
asXtalkWideCoefsRightEq);
vortex_XtalkHw_SetLeftXT(vortex, sXtalkWideKLeftXt,
sXtalkWideShiftLeftXt, asXtalkWideCoefsLeftXt);
vortex_XtalkHw_SetRightXT(vortex, sXtalkWideKLeftXt,
sXtalkWideShiftLeftXt,
asXtalkWideCoefsLeftXt);
vortex_XtalkHw_SetDelay(vortex, wXtalkWideRightDelay, wXtalkWideLeftDelay); // inlined
}
static void vortex_XtalkHw_ProgramXtalkNarrow(vortex_t * vortex)
{
vortex_XtalkHw_SetLeftEQ(vortex, sXtalkNarrowKLeftEq,
sXtalkNarrowShiftLeftEq,
asXtalkNarrowCoefsLeftEq);
vortex_XtalkHw_SetRightEQ(vortex, sXtalkNarrowKRightEq,
sXtalkNarrowShiftRightEq,
asXtalkNarrowCoefsRightEq);
vortex_XtalkHw_SetLeftXT(vortex, sXtalkNarrowKLeftXt,
sXtalkNarrowShiftLeftXt,
asXtalkNarrowCoefsLeftXt);
vortex_XtalkHw_SetRightXT(vortex, sXtalkNarrowKLeftXt,
sXtalkNarrowShiftLeftXt,
asXtalkNarrowCoefsLeftXt);
vortex_XtalkHw_SetDelay(vortex, wXtalkNarrowRightDelay, wXtalkNarrowLeftDelay); // inlined
}
static void vortex_XtalkHw_ProgramDiamondXtalk(vortex_t * vortex)
{
//sDiamondKLeftEq,sDiamondKRightXt,asDiamondCoefsLeftEq
vortex_XtalkHw_SetLeftEQ(vortex, sDiamondKLeftEq,
sDiamondShiftLeftEq, asDiamondCoefsLeftEq);
vortex_XtalkHw_SetRightEQ(vortex, sDiamondKRightEq,
sDiamondShiftRightEq, asDiamondCoefsRightEq);
vortex_XtalkHw_SetLeftXT(vortex, sDiamondKLeftXt,
sDiamondShiftLeftXt, asDiamondCoefsLeftXt);
vortex_XtalkHw_SetRightXT(vortex, sDiamondKLeftXt,
sDiamondShiftLeftXt, asDiamondCoefsLeftXt);
vortex_XtalkHw_SetDelay(vortex, wDiamondRightDelay, wDiamondLeftDelay); // inlined
}
static void vortex_XtalkHw_init(vortex_t * vortex)
{
vortex_XtalkHw_ZeroState(vortex);
}
/* End of file */

View file

@ -0,0 +1,61 @@
/***************************************************************************
* au88x0_cxtalk.h
*
* Wed Nov 19 19:07:17 2003
* Copyright 2003 mjander
* mjander@users.sourceforge.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 Library 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.
*/
/* The crosstalk canceler supports 5 stereo input channels. The result is
available at one single output route pair (stereo). */
#ifndef _AU88X0_CXTALK_H
#define _AU88X0_CXTALK_H
#include "au88x0.h"
#define XTDLINE_SZ 32
#define XTGAINS_SZ 10
#define XTINST_SZ 4
#define XT_HEADPHONE 1
#define XT_SPEAKER0 2
#define XT_SPEAKER1 3
#define XT_DIAMOND 4
typedef u32 xtalk_dline_t[XTDLINE_SZ];
typedef u16 xtalk_gains_t[XTGAINS_SZ];
typedef u16 xtalk_instate_t[XTINST_SZ];
typedef u16 xtalk_coefs_t[5][5];
typedef u16 xtalk_state_t[5][4];
static void vortex_XtalkHw_SetGains(vortex_t * vortex,
xtalk_gains_t const gains);
static void vortex_XtalkHw_SetGainsAllChan(vortex_t * vortex);
static void vortex_XtalkHw_SetSampleRate(vortex_t * vortex, u32 sr);
static void vortex_XtalkHw_ProgramPipe(vortex_t * vortex);
static void vortex_XtalkHw_ProgramPipe(vortex_t * vortex);
static void vortex_XtalkHw_ProgramXtalkWide(vortex_t * vortex);
static void vortex_XtalkHw_ProgramXtalkNarrow(vortex_t * vortex);
static void vortex_XtalkHw_ProgramDiamondXtalk(vortex_t * vortex);
static void vortex_XtalkHw_Enable(vortex_t * vortex);
static void vortex_XtalkHw_Disable(vortex_t * vortex);
static void vortex_XtalkHw_init(vortex_t * vortex);
#endif /* _AU88X0_CXTALK_H */

3
sound/pci/aw2/Makefile Normal file
View file

@ -0,0 +1,3 @@
snd-aw2-objs := aw2-alsa.o aw2-saa7146.o
obj-$(CONFIG_SND_AW2) += snd-aw2.o

772
sound/pci/aw2/aw2-alsa.c Normal file
View file

@ -0,0 +1,772 @@
/*****************************************************************************
*
* Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
* Jean-Christian Hassler <jhassler@free.fr>
*
* This file is part of the Audiowerk2 ALSA driver
*
* The Audiowerk2 ALSA driver 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; version 2.
*
* The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*****************************************************************************/
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/control.h>
#include "saa7146.h"
#include "aw2-saa7146.h"
MODULE_AUTHOR("Cedric Bregardis <cedric.bregardis@free.fr>, "
"Jean-Christian Hassler <jhassler@free.fr>");
MODULE_DESCRIPTION("Emagic Audiowerk 2 sound driver");
MODULE_LICENSE("GPL");
/*********************************
* DEFINES
********************************/
#define CTL_ROUTE_ANALOG 0
#define CTL_ROUTE_DIGITAL 1
/*********************************
* TYPEDEFS
********************************/
/* hardware definition */
static struct snd_pcm_hardware snd_aw2_playback_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_44100,
.rate_min = 44100,
.rate_max = 44100,
.channels_min = 2,
.channels_max = 4,
.buffer_bytes_max = 32768,
.period_bytes_min = 4096,
.period_bytes_max = 32768,
.periods_min = 1,
.periods_max = 1024,
};
static struct snd_pcm_hardware snd_aw2_capture_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_44100,
.rate_min = 44100,
.rate_max = 44100,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 32768,
.period_bytes_min = 4096,
.period_bytes_max = 32768,
.periods_min = 1,
.periods_max = 1024,
};
struct aw2_pcm_device {
struct snd_pcm *pcm;
unsigned int stream_number;
struct aw2 *chip;
};
struct aw2 {
struct snd_aw2_saa7146 saa7146;
struct pci_dev *pci;
int irq;
spinlock_t reg_lock;
struct mutex mtx;
unsigned long iobase_phys;
void __iomem *iobase_virt;
struct snd_card *card;
struct aw2_pcm_device device_playback[NB_STREAM_PLAYBACK];
struct aw2_pcm_device device_capture[NB_STREAM_CAPTURE];
};
/*********************************
* FUNCTION DECLARATIONS
********************************/
static int snd_aw2_dev_free(struct snd_device *device);
static int snd_aw2_create(struct snd_card *card,
struct pci_dev *pci, struct aw2 **rchip);
static int snd_aw2_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id);
static void snd_aw2_remove(struct pci_dev *pci);
static int snd_aw2_pcm_playback_open(struct snd_pcm_substream *substream);
static int snd_aw2_pcm_playback_close(struct snd_pcm_substream *substream);
static int snd_aw2_pcm_capture_open(struct snd_pcm_substream *substream);
static int snd_aw2_pcm_capture_close(struct snd_pcm_substream *substream);
static int snd_aw2_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params);
static int snd_aw2_pcm_hw_free(struct snd_pcm_substream *substream);
static int snd_aw2_pcm_prepare_playback(struct snd_pcm_substream *substream);
static int snd_aw2_pcm_prepare_capture(struct snd_pcm_substream *substream);
static int snd_aw2_pcm_trigger_playback(struct snd_pcm_substream *substream,
int cmd);
static int snd_aw2_pcm_trigger_capture(struct snd_pcm_substream *substream,
int cmd);
static snd_pcm_uframes_t snd_aw2_pcm_pointer_playback(struct snd_pcm_substream
*substream);
static snd_pcm_uframes_t snd_aw2_pcm_pointer_capture(struct snd_pcm_substream
*substream);
static int snd_aw2_new_pcm(struct aw2 *chip);
static int snd_aw2_control_switch_capture_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
static int snd_aw2_control_switch_capture_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value
*ucontrol);
static int snd_aw2_control_switch_capture_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value
*ucontrol);
/*********************************
* VARIABLES
********************************/
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for Audiowerk2 soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for the Audiowerk2 soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable Audiowerk2 soundcard.");
static const struct pci_device_id snd_aw2_ids[] = {
{PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, 0, 0,
0, 0, 0},
{0}
};
MODULE_DEVICE_TABLE(pci, snd_aw2_ids);
/* pci_driver definition */
static struct pci_driver aw2_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_aw2_ids,
.probe = snd_aw2_probe,
.remove = snd_aw2_remove,
};
module_pci_driver(aw2_driver);
/* operators for playback PCM alsa interface */
static struct snd_pcm_ops snd_aw2_playback_ops = {
.open = snd_aw2_pcm_playback_open,
.close = snd_aw2_pcm_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_aw2_pcm_hw_params,
.hw_free = snd_aw2_pcm_hw_free,
.prepare = snd_aw2_pcm_prepare_playback,
.trigger = snd_aw2_pcm_trigger_playback,
.pointer = snd_aw2_pcm_pointer_playback,
};
/* operators for capture PCM alsa interface */
static struct snd_pcm_ops snd_aw2_capture_ops = {
.open = snd_aw2_pcm_capture_open,
.close = snd_aw2_pcm_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_aw2_pcm_hw_params,
.hw_free = snd_aw2_pcm_hw_free,
.prepare = snd_aw2_pcm_prepare_capture,
.trigger = snd_aw2_pcm_trigger_capture,
.pointer = snd_aw2_pcm_pointer_capture,
};
static struct snd_kcontrol_new aw2_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Capture Route",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.private_value = 0xffff,
.info = snd_aw2_control_switch_capture_info,
.get = snd_aw2_control_switch_capture_get,
.put = snd_aw2_control_switch_capture_put
};
/*********************************
* FUNCTION IMPLEMENTATIONS
********************************/
/* component-destructor */
static int snd_aw2_dev_free(struct snd_device *device)
{
struct aw2 *chip = device->device_data;
/* Free hardware */
snd_aw2_saa7146_free(&chip->saa7146);
/* release the irq */
if (chip->irq >= 0)
free_irq(chip->irq, (void *)chip);
/* release the i/o ports & memory */
if (chip->iobase_virt)
iounmap(chip->iobase_virt);
pci_release_regions(chip->pci);
/* disable the PCI entry */
pci_disable_device(chip->pci);
/* release the data */
kfree(chip);
return 0;
}
/* chip-specific constructor */
static int snd_aw2_create(struct snd_card *card,
struct pci_dev *pci, struct aw2 **rchip)
{
struct aw2 *chip;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_aw2_dev_free,
};
*rchip = NULL;
/* initialize the PCI entry */
err = pci_enable_device(pci);
if (err < 0)
return err;
pci_set_master(pci);
/* check PCI availability (32bit DMA) */
if ((pci_set_dma_mask(pci, DMA_BIT_MASK(32)) < 0) ||
(pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32)) < 0)) {
dev_err(card->dev, "Impossible to set 32bit mask DMA\n");
pci_disable_device(pci);
return -ENXIO;
}
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
if (chip == NULL) {
pci_disable_device(pci);
return -ENOMEM;
}
/* initialize the stuff */
chip->card = card;
chip->pci = pci;
chip->irq = -1;
/* (1) PCI resource allocation */
err = pci_request_regions(pci, "Audiowerk2");
if (err < 0) {
pci_disable_device(pci);
kfree(chip);
return err;
}
chip->iobase_phys = pci_resource_start(pci, 0);
chip->iobase_virt =
ioremap_nocache(chip->iobase_phys,
pci_resource_len(pci, 0));
if (chip->iobase_virt == NULL) {
dev_err(card->dev, "unable to remap memory region");
pci_release_regions(pci);
pci_disable_device(pci);
kfree(chip);
return -ENOMEM;
}
/* (2) initialization of the chip hardware */
snd_aw2_saa7146_setup(&chip->saa7146, chip->iobase_virt);
if (request_irq(pci->irq, snd_aw2_saa7146_interrupt,
IRQF_SHARED, KBUILD_MODNAME, chip)) {
dev_err(card->dev, "Cannot grab irq %d\n", pci->irq);
iounmap(chip->iobase_virt);
pci_release_regions(chip->pci);
pci_disable_device(chip->pci);
kfree(chip);
return -EBUSY;
}
chip->irq = pci->irq;
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
if (err < 0) {
free_irq(chip->irq, (void *)chip);
iounmap(chip->iobase_virt);
pci_release_regions(chip->pci);
pci_disable_device(chip->pci);
kfree(chip);
return err;
}
*rchip = chip;
dev_info(card->dev,
"Audiowerk 2 sound card (saa7146 chipset) detected and managed\n");
return 0;
}
/* constructor */
static int snd_aw2_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
struct snd_card *card;
struct aw2 *chip;
int err;
/* (1) Continue if device is not enabled, else inc dev */
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
/* (2) Create card instance */
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0)
return err;
/* (3) Create main component */
err = snd_aw2_create(card, pci, &chip);
if (err < 0) {
snd_card_free(card);
return err;
}
/* initialize mutex */
mutex_init(&chip->mtx);
/* init spinlock */
spin_lock_init(&chip->reg_lock);
/* (4) Define driver ID and name string */
strcpy(card->driver, "aw2");
strcpy(card->shortname, "Audiowerk2");
sprintf(card->longname, "%s with SAA7146 irq %i",
card->shortname, chip->irq);
/* (5) Create other components */
snd_aw2_new_pcm(chip);
/* (6) Register card instance */
err = snd_card_register(card);
if (err < 0) {
snd_card_free(card);
return err;
}
/* (7) Set PCI driver data */
pci_set_drvdata(pci, card);
dev++;
return 0;
}
/* destructor */
static void snd_aw2_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
}
/* open callback */
static int snd_aw2_pcm_playback_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
dev_dbg(substream->pcm->card->dev, "Playback_open\n");
runtime->hw = snd_aw2_playback_hw;
return 0;
}
/* close callback */
static int snd_aw2_pcm_playback_close(struct snd_pcm_substream *substream)
{
return 0;
}
static int snd_aw2_pcm_capture_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
dev_dbg(substream->pcm->card->dev, "Capture_open\n");
runtime->hw = snd_aw2_capture_hw;
return 0;
}
/* close callback */
static int snd_aw2_pcm_capture_close(struct snd_pcm_substream *substream)
{
/* TODO: something to do ? */
return 0;
}
/* hw_params callback */
static int snd_aw2_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
return snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params));
}
/* hw_free callback */
static int snd_aw2_pcm_hw_free(struct snd_pcm_substream *substream)
{
return snd_pcm_lib_free_pages(substream);
}
/* prepare callback for playback */
static int snd_aw2_pcm_prepare_playback(struct snd_pcm_substream *substream)
{
struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
struct aw2 *chip = pcm_device->chip;
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned long period_size, buffer_size;
mutex_lock(&chip->mtx);
period_size = snd_pcm_lib_period_bytes(substream);
buffer_size = snd_pcm_lib_buffer_bytes(substream);
snd_aw2_saa7146_pcm_init_playback(&chip->saa7146,
pcm_device->stream_number,
runtime->dma_addr, period_size,
buffer_size);
/* Define Interrupt callback */
snd_aw2_saa7146_define_it_playback_callback(pcm_device->stream_number,
(snd_aw2_saa7146_it_cb)
snd_pcm_period_elapsed,
(void *)substream);
mutex_unlock(&chip->mtx);
return 0;
}
/* prepare callback for capture */
static int snd_aw2_pcm_prepare_capture(struct snd_pcm_substream *substream)
{
struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
struct aw2 *chip = pcm_device->chip;
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned long period_size, buffer_size;
mutex_lock(&chip->mtx);
period_size = snd_pcm_lib_period_bytes(substream);
buffer_size = snd_pcm_lib_buffer_bytes(substream);
snd_aw2_saa7146_pcm_init_capture(&chip->saa7146,
pcm_device->stream_number,
runtime->dma_addr, period_size,
buffer_size);
/* Define Interrupt callback */
snd_aw2_saa7146_define_it_capture_callback(pcm_device->stream_number,
(snd_aw2_saa7146_it_cb)
snd_pcm_period_elapsed,
(void *)substream);
mutex_unlock(&chip->mtx);
return 0;
}
/* playback trigger callback */
static int snd_aw2_pcm_trigger_playback(struct snd_pcm_substream *substream,
int cmd)
{
int status = 0;
struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
struct aw2 *chip = pcm_device->chip;
spin_lock(&chip->reg_lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
snd_aw2_saa7146_pcm_trigger_start_playback(&chip->saa7146,
pcm_device->
stream_number);
break;
case SNDRV_PCM_TRIGGER_STOP:
snd_aw2_saa7146_pcm_trigger_stop_playback(&chip->saa7146,
pcm_device->
stream_number);
break;
default:
status = -EINVAL;
}
spin_unlock(&chip->reg_lock);
return status;
}
/* capture trigger callback */
static int snd_aw2_pcm_trigger_capture(struct snd_pcm_substream *substream,
int cmd)
{
int status = 0;
struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
struct aw2 *chip = pcm_device->chip;
spin_lock(&chip->reg_lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
snd_aw2_saa7146_pcm_trigger_start_capture(&chip->saa7146,
pcm_device->
stream_number);
break;
case SNDRV_PCM_TRIGGER_STOP:
snd_aw2_saa7146_pcm_trigger_stop_capture(&chip->saa7146,
pcm_device->
stream_number);
break;
default:
status = -EINVAL;
}
spin_unlock(&chip->reg_lock);
return status;
}
/* playback pointer callback */
static snd_pcm_uframes_t snd_aw2_pcm_pointer_playback(struct snd_pcm_substream
*substream)
{
struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
struct aw2 *chip = pcm_device->chip;
unsigned int current_ptr;
/* get the current hardware pointer */
struct snd_pcm_runtime *runtime = substream->runtime;
current_ptr =
snd_aw2_saa7146_get_hw_ptr_playback(&chip->saa7146,
pcm_device->stream_number,
runtime->dma_area,
runtime->buffer_size);
return bytes_to_frames(substream->runtime, current_ptr);
}
/* capture pointer callback */
static snd_pcm_uframes_t snd_aw2_pcm_pointer_capture(struct snd_pcm_substream
*substream)
{
struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
struct aw2 *chip = pcm_device->chip;
unsigned int current_ptr;
/* get the current hardware pointer */
struct snd_pcm_runtime *runtime = substream->runtime;
current_ptr =
snd_aw2_saa7146_get_hw_ptr_capture(&chip->saa7146,
pcm_device->stream_number,
runtime->dma_area,
runtime->buffer_size);
return bytes_to_frames(substream->runtime, current_ptr);
}
/* create a pcm device */
static int snd_aw2_new_pcm(struct aw2 *chip)
{
struct snd_pcm *pcm_playback_ana;
struct snd_pcm *pcm_playback_num;
struct snd_pcm *pcm_capture;
struct aw2_pcm_device *pcm_device;
int err = 0;
/* Create new Alsa PCM device */
err = snd_pcm_new(chip->card, "Audiowerk2 analog playback", 0, 1, 0,
&pcm_playback_ana);
if (err < 0) {
dev_err(chip->card->dev, "snd_pcm_new error (0x%X)\n", err);
return err;
}
/* Creation ok */
pcm_device = &chip->device_playback[NUM_STREAM_PLAYBACK_ANA];
/* Set PCM device name */
strcpy(pcm_playback_ana->name, "Analog playback");
/* Associate private data to PCM device */
pcm_playback_ana->private_data = pcm_device;
/* set operators of PCM device */
snd_pcm_set_ops(pcm_playback_ana, SNDRV_PCM_STREAM_PLAYBACK,
&snd_aw2_playback_ops);
/* store PCM device */
pcm_device->pcm = pcm_playback_ana;
/* give base chip pointer to our internal pcm device
structure */
pcm_device->chip = chip;
/* Give stream number to PCM device */
pcm_device->stream_number = NUM_STREAM_PLAYBACK_ANA;
/* pre-allocation of buffers */
/* Preallocate continuous pages. */
err = snd_pcm_lib_preallocate_pages_for_all(pcm_playback_ana,
SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data
(chip->pci),
64 * 1024, 64 * 1024);
if (err)
dev_err(chip->card->dev,
"snd_pcm_lib_preallocate_pages_for_all error (0x%X)\n",
err);
err = snd_pcm_new(chip->card, "Audiowerk2 digital playback", 1, 1, 0,
&pcm_playback_num);
if (err < 0) {
dev_err(chip->card->dev, "snd_pcm_new error (0x%X)\n", err);
return err;
}
/* Creation ok */
pcm_device = &chip->device_playback[NUM_STREAM_PLAYBACK_DIG];
/* Set PCM device name */
strcpy(pcm_playback_num->name, "Digital playback");
/* Associate private data to PCM device */
pcm_playback_num->private_data = pcm_device;
/* set operators of PCM device */
snd_pcm_set_ops(pcm_playback_num, SNDRV_PCM_STREAM_PLAYBACK,
&snd_aw2_playback_ops);
/* store PCM device */
pcm_device->pcm = pcm_playback_num;
/* give base chip pointer to our internal pcm device
structure */
pcm_device->chip = chip;
/* Give stream number to PCM device */
pcm_device->stream_number = NUM_STREAM_PLAYBACK_DIG;
/* pre-allocation of buffers */
/* Preallocate continuous pages. */
err = snd_pcm_lib_preallocate_pages_for_all(pcm_playback_num,
SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data
(chip->pci),
64 * 1024, 64 * 1024);
if (err)
dev_err(chip->card->dev,
"snd_pcm_lib_preallocate_pages_for_all error (0x%X)\n",
err);
err = snd_pcm_new(chip->card, "Audiowerk2 capture", 2, 0, 1,
&pcm_capture);
if (err < 0) {
dev_err(chip->card->dev, "snd_pcm_new error (0x%X)\n", err);
return err;
}
/* Creation ok */
pcm_device = &chip->device_capture[NUM_STREAM_CAPTURE_ANA];
/* Set PCM device name */
strcpy(pcm_capture->name, "Capture");
/* Associate private data to PCM device */
pcm_capture->private_data = pcm_device;
/* set operators of PCM device */
snd_pcm_set_ops(pcm_capture, SNDRV_PCM_STREAM_CAPTURE,
&snd_aw2_capture_ops);
/* store PCM device */
pcm_device->pcm = pcm_capture;
/* give base chip pointer to our internal pcm device
structure */
pcm_device->chip = chip;
/* Give stream number to PCM device */
pcm_device->stream_number = NUM_STREAM_CAPTURE_ANA;
/* pre-allocation of buffers */
/* Preallocate continuous pages. */
err = snd_pcm_lib_preallocate_pages_for_all(pcm_capture,
SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data
(chip->pci),
64 * 1024, 64 * 1024);
if (err)
dev_err(chip->card->dev,
"snd_pcm_lib_preallocate_pages_for_all error (0x%X)\n",
err);
/* Create control */
err = snd_ctl_add(chip->card, snd_ctl_new1(&aw2_control, chip));
if (err < 0) {
dev_err(chip->card->dev, "snd_ctl_add error (0x%X)\n", err);
return err;
}
return 0;
}
static int snd_aw2_control_switch_capture_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static char *texts[2] = {
"Analog", "Digital"
};
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 2;
if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) {
uinfo->value.enumerated.item =
uinfo->value.enumerated.items - 1;
}
strcpy(uinfo->value.enumerated.name,
texts[uinfo->value.enumerated.item]);
return 0;
}
static int snd_aw2_control_switch_capture_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value
*ucontrol)
{
struct aw2 *chip = snd_kcontrol_chip(kcontrol);
if (snd_aw2_saa7146_is_using_digital_input(&chip->saa7146))
ucontrol->value.enumerated.item[0] = CTL_ROUTE_DIGITAL;
else
ucontrol->value.enumerated.item[0] = CTL_ROUTE_ANALOG;
return 0;
}
static int snd_aw2_control_switch_capture_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value
*ucontrol)
{
struct aw2 *chip = snd_kcontrol_chip(kcontrol);
int changed = 0;
int is_disgital =
snd_aw2_saa7146_is_using_digital_input(&chip->saa7146);
if (((ucontrol->value.integer.value[0] == CTL_ROUTE_DIGITAL)
&& !is_disgital)
|| ((ucontrol->value.integer.value[0] == CTL_ROUTE_ANALOG)
&& is_disgital)) {
snd_aw2_saa7146_use_digital_input(&chip->saa7146, !is_disgital);
changed = 1;
}
return changed;
}

461
sound/pci/aw2/aw2-saa7146.c Normal file
View file

@ -0,0 +1,461 @@
/*****************************************************************************
*
* Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
* Jean-Christian Hassler <jhassler@free.fr>
*
* This file is part of the Audiowerk2 ALSA driver
*
* The Audiowerk2 ALSA driver 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; version 2.
*
* The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*****************************************************************************/
#define AW2_SAA7146_M
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "saa7146.h"
#include "aw2-saa7146.h"
#include "aw2-tsl.c"
#define WRITEREG(value, addr) writel((value), chip->base_addr + (addr))
#define READREG(addr) readl(chip->base_addr + (addr))
static struct snd_aw2_saa7146_cb_param
arr_substream_it_playback_cb[NB_STREAM_PLAYBACK];
static struct snd_aw2_saa7146_cb_param
arr_substream_it_capture_cb[NB_STREAM_CAPTURE];
static int snd_aw2_saa7146_get_limit(int size);
/* chip-specific destructor */
int snd_aw2_saa7146_free(struct snd_aw2_saa7146 *chip)
{
/* disable all irqs */
WRITEREG(0, IER);
/* reset saa7146 */
WRITEREG((MRST_N << 16), MC1);
/* Unset base addr */
chip->base_addr = NULL;
return 0;
}
void snd_aw2_saa7146_setup(struct snd_aw2_saa7146 *chip,
void __iomem *pci_base_addr)
{
/* set PCI burst/threshold
Burst length definition
VALUE BURST LENGTH
000 1 Dword
001 2 Dwords
010 4 Dwords
011 8 Dwords
100 16 Dwords
101 32 Dwords
110 64 Dwords
111 128 Dwords
Threshold definition
VALUE WRITE MODE READ MODE
00 1 Dword of valid data 1 empty Dword
01 4 Dwords of valid data 4 empty Dwords
10 8 Dwords of valid data 8 empty Dwords
11 16 Dwords of valid data 16 empty Dwords */
unsigned int acon2;
unsigned int acon1 = 0;
int i;
/* Set base addr */
chip->base_addr = pci_base_addr;
/* disable all irqs */
WRITEREG(0, IER);
/* reset saa7146 */
WRITEREG((MRST_N << 16), MC1);
/* enable audio interface */
#ifdef __BIG_ENDIAN
acon1 |= A1_SWAP;
acon1 |= A2_SWAP;
#endif
/* WS0_CTRL, WS0_SYNC: input TSL1, I2S */
/* At initialization WS1 and WS2 are disabled (configured as input) */
acon1 |= 0 * WS1_CTRL;
acon1 |= 0 * WS2_CTRL;
/* WS4 is not used. So it must not restart A2.
This is why it is configured as output (force to low) */
acon1 |= 3 * WS4_CTRL;
/* WS3_CTRL, WS3_SYNC: output TSL2, I2S */
acon1 |= 2 * WS3_CTRL;
/* A1 and A2 are active and asynchronous */
acon1 |= 3 * AUDIO_MODE;
WRITEREG(acon1, ACON1);
/* The following comes from original windows driver.
It is needed to have a correct behavior of input and output
simultenously, but I don't know why ! */
WRITEREG(3 * (BurstA1_in) + 3 * (ThreshA1_in) +
3 * (BurstA1_out) + 3 * (ThreshA1_out) +
3 * (BurstA2_out) + 3 * (ThreshA2_out), PCI_BT_A);
/* enable audio port pins */
WRITEREG((EAP << 16) | EAP, MC1);
/* enable I2C */
WRITEREG((EI2C << 16) | EI2C, MC1);
/* enable interrupts */
WRITEREG(A1_out | A2_out | A1_in | IIC_S | IIC_E, IER);
/* audio configuration */
acon2 = A2_CLKSRC | BCLK1_OEN;
WRITEREG(acon2, ACON2);
/* By default use analog input */
snd_aw2_saa7146_use_digital_input(chip, 0);
/* TSL setup */
for (i = 0; i < 8; ++i) {
WRITEREG(tsl1[i], TSL1 + (i * 4));
WRITEREG(tsl2[i], TSL2 + (i * 4));
}
}
void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip,
int stream_number,
unsigned long dma_addr,
unsigned long period_size,
unsigned long buffer_size)
{
unsigned long dw_page, dw_limit;
/* Configure DMA for substream
Configuration informations: ALSA has allocated continuous memory
pages. So we don't need to use MMU of saa7146.
*/
/* No MMU -> nothing to do with PageA1, we only configure the limit of
PageAx_out register */
/* Disable MMU */
dw_page = (0L << 11);
/* Configure Limit for DMA access.
The limit register defines an address limit, which generates
an interrupt if passed by the actual PCI address pointer.
'0001' means an interrupt will be generated if the lower
6 bits (64 bytes) of the PCI address are zero. '0010'
defines a limit of 128 bytes, '0011' one of 256 bytes, and
so on up to 1 Mbyte defined by '1111'. This interrupt range
can be calculated as follows:
Range = 2^(5 + Limit) bytes.
*/
dw_limit = snd_aw2_saa7146_get_limit(period_size);
dw_page |= (dw_limit << 4);
if (stream_number == 0) {
WRITEREG(dw_page, PageA2_out);
/* Base address for DMA transfert. */
/* This address has been reserved by ALSA. */
/* This is a physical address */
WRITEREG(dma_addr, BaseA2_out);
/* Define upper limit for DMA access */
WRITEREG(dma_addr + buffer_size, ProtA2_out);
} else if (stream_number == 1) {
WRITEREG(dw_page, PageA1_out);
/* Base address for DMA transfert. */
/* This address has been reserved by ALSA. */
/* This is a physical address */
WRITEREG(dma_addr, BaseA1_out);
/* Define upper limit for DMA access */
WRITEREG(dma_addr + buffer_size, ProtA1_out);
} else {
pr_err("aw2: snd_aw2_saa7146_pcm_init_playback: "
"Substream number is not 0 or 1 -> not managed\n");
}
}
void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip,
int stream_number, unsigned long dma_addr,
unsigned long period_size,
unsigned long buffer_size)
{
unsigned long dw_page, dw_limit;
/* Configure DMA for substream
Configuration informations: ALSA has allocated continuous memory
pages. So we don't need to use MMU of saa7146.
*/
/* No MMU -> nothing to do with PageA1, we only configure the limit of
PageAx_out register */
/* Disable MMU */
dw_page = (0L << 11);
/* Configure Limit for DMA access.
The limit register defines an address limit, which generates
an interrupt if passed by the actual PCI address pointer.
'0001' means an interrupt will be generated if the lower
6 bits (64 bytes) of the PCI address are zero. '0010'
defines a limit of 128 bytes, '0011' one of 256 bytes, and
so on up to 1 Mbyte defined by '1111'. This interrupt range
can be calculated as follows:
Range = 2^(5 + Limit) bytes.
*/
dw_limit = snd_aw2_saa7146_get_limit(period_size);
dw_page |= (dw_limit << 4);
if (stream_number == 0) {
WRITEREG(dw_page, PageA1_in);
/* Base address for DMA transfert. */
/* This address has been reserved by ALSA. */
/* This is a physical address */
WRITEREG(dma_addr, BaseA1_in);
/* Define upper limit for DMA access */
WRITEREG(dma_addr + buffer_size, ProtA1_in);
} else {
pr_err("aw2: snd_aw2_saa7146_pcm_init_capture: "
"Substream number is not 0 -> not managed\n");
}
}
void snd_aw2_saa7146_define_it_playback_callback(unsigned int stream_number,
snd_aw2_saa7146_it_cb
p_it_callback,
void *p_callback_param)
{
if (stream_number < NB_STREAM_PLAYBACK) {
arr_substream_it_playback_cb[stream_number].p_it_callback =
(snd_aw2_saa7146_it_cb) p_it_callback;
arr_substream_it_playback_cb[stream_number].p_callback_param =
(void *)p_callback_param;
}
}
void snd_aw2_saa7146_define_it_capture_callback(unsigned int stream_number,
snd_aw2_saa7146_it_cb
p_it_callback,
void *p_callback_param)
{
if (stream_number < NB_STREAM_CAPTURE) {
arr_substream_it_capture_cb[stream_number].p_it_callback =
(snd_aw2_saa7146_it_cb) p_it_callback;
arr_substream_it_capture_cb[stream_number].p_callback_param =
(void *)p_callback_param;
}
}
void snd_aw2_saa7146_pcm_trigger_start_playback(struct snd_aw2_saa7146 *chip,
int stream_number)
{
unsigned int acon1 = 0;
/* In aw8 driver, dma transfert is always active. It is
started and stopped in a larger "space" */
acon1 = READREG(ACON1);
if (stream_number == 0) {
WRITEREG((TR_E_A2_OUT << 16) | TR_E_A2_OUT, MC1);
/* WS2_CTRL, WS2_SYNC: output TSL2, I2S */
acon1 |= 2 * WS2_CTRL;
WRITEREG(acon1, ACON1);
} else if (stream_number == 1) {
WRITEREG((TR_E_A1_OUT << 16) | TR_E_A1_OUT, MC1);
/* WS1_CTRL, WS1_SYNC: output TSL1, I2S */
acon1 |= 1 * WS1_CTRL;
WRITEREG(acon1, ACON1);
}
}
void snd_aw2_saa7146_pcm_trigger_stop_playback(struct snd_aw2_saa7146 *chip,
int stream_number)
{
unsigned int acon1 = 0;
acon1 = READREG(ACON1);
if (stream_number == 0) {
/* WS2_CTRL, WS2_SYNC: output TSL2, I2S */
acon1 &= ~(3 * WS2_CTRL);
WRITEREG(acon1, ACON1);
WRITEREG((TR_E_A2_OUT << 16), MC1);
} else if (stream_number == 1) {
/* WS1_CTRL, WS1_SYNC: output TSL1, I2S */
acon1 &= ~(3 * WS1_CTRL);
WRITEREG(acon1, ACON1);
WRITEREG((TR_E_A1_OUT << 16), MC1);
}
}
void snd_aw2_saa7146_pcm_trigger_start_capture(struct snd_aw2_saa7146 *chip,
int stream_number)
{
/* In aw8 driver, dma transfert is always active. It is
started and stopped in a larger "space" */
if (stream_number == 0)
WRITEREG((TR_E_A1_IN << 16) | TR_E_A1_IN, MC1);
}
void snd_aw2_saa7146_pcm_trigger_stop_capture(struct snd_aw2_saa7146 *chip,
int stream_number)
{
if (stream_number == 0)
WRITEREG((TR_E_A1_IN << 16), MC1);
}
irqreturn_t snd_aw2_saa7146_interrupt(int irq, void *dev_id)
{
unsigned int isr;
unsigned int iicsta;
struct snd_aw2_saa7146 *chip = dev_id;
isr = READREG(ISR);
if (!isr)
return IRQ_NONE;
WRITEREG(isr, ISR);
if (isr & (IIC_S | IIC_E)) {
iicsta = READREG(IICSTA);
WRITEREG(0x100, IICSTA);
}
if (isr & A1_out) {
if (arr_substream_it_playback_cb[1].p_it_callback != NULL) {
arr_substream_it_playback_cb[1].
p_it_callback(arr_substream_it_playback_cb[1].
p_callback_param);
}
}
if (isr & A2_out) {
if (arr_substream_it_playback_cb[0].p_it_callback != NULL) {
arr_substream_it_playback_cb[0].
p_it_callback(arr_substream_it_playback_cb[0].
p_callback_param);
}
}
if (isr & A1_in) {
if (arr_substream_it_capture_cb[0].p_it_callback != NULL) {
arr_substream_it_capture_cb[0].
p_it_callback(arr_substream_it_capture_cb[0].
p_callback_param);
}
}
return IRQ_HANDLED;
}
unsigned int snd_aw2_saa7146_get_hw_ptr_playback(struct snd_aw2_saa7146 *chip,
int stream_number,
unsigned char *start_addr,
unsigned int buffer_size)
{
long pci_adp = 0;
size_t ptr = 0;
if (stream_number == 0) {
pci_adp = READREG(PCI_ADP3);
ptr = pci_adp - (long)start_addr;
if (ptr == buffer_size)
ptr = 0;
}
if (stream_number == 1) {
pci_adp = READREG(PCI_ADP1);
ptr = pci_adp - (size_t) start_addr;
if (ptr == buffer_size)
ptr = 0;
}
return ptr;
}
unsigned int snd_aw2_saa7146_get_hw_ptr_capture(struct snd_aw2_saa7146 *chip,
int stream_number,
unsigned char *start_addr,
unsigned int buffer_size)
{
size_t pci_adp = 0;
size_t ptr = 0;
if (stream_number == 0) {
pci_adp = READREG(PCI_ADP2);
ptr = pci_adp - (size_t) start_addr;
if (ptr == buffer_size)
ptr = 0;
}
return ptr;
}
void snd_aw2_saa7146_use_digital_input(struct snd_aw2_saa7146 *chip,
int use_digital)
{
/* FIXME: switch between analog and digital input does not always work.
It can produce a kind of white noise. It seams that received data
are inverted sometime (endian inversion). Why ? I don't know, maybe
a problem of synchronization... However for the time being I have
not found the problem. Workaround: switch again (and again) between
digital and analog input until it works. */
if (use_digital)
WRITEREG(0x40, GPIO_CTRL);
else
WRITEREG(0x50, GPIO_CTRL);
}
int snd_aw2_saa7146_is_using_digital_input(struct snd_aw2_saa7146 *chip)
{
unsigned int reg_val = READREG(GPIO_CTRL);
if ((reg_val & 0xFF) == 0x40)
return 1;
else
return 0;
}
static int snd_aw2_saa7146_get_limit(int size)
{
int limitsize = 32;
int limit = 0;
while (limitsize < size) {
limitsize *= 2;
limit++;
}
return limit;
}

105
sound/pci/aw2/aw2-saa7146.h Normal file
View file

@ -0,0 +1,105 @@
/*****************************************************************************
*
* Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
* Jean-Christian Hassler <jhassler@free.fr>
*
* This file is part of the Audiowerk2 ALSA driver
*
* The Audiowerk2 ALSA driver 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; version 2.
*
* The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*****************************************************************************/
#ifndef AW2_SAA7146_H
#define AW2_SAA7146_H
#define NB_STREAM_PLAYBACK 2
#define NB_STREAM_CAPTURE 1
#define NUM_STREAM_PLAYBACK_ANA 0
#define NUM_STREAM_PLAYBACK_DIG 1
#define NUM_STREAM_CAPTURE_ANA 0
typedef void (*snd_aw2_saa7146_it_cb) (void *);
struct snd_aw2_saa7146_cb_param {
snd_aw2_saa7146_it_cb p_it_callback;
void *p_callback_param;
};
/* definition of the chip-specific record */
struct snd_aw2_saa7146 {
void __iomem *base_addr;
};
extern void snd_aw2_saa7146_setup(struct snd_aw2_saa7146 *chip,
void __iomem *pci_base_addr);
extern int snd_aw2_saa7146_free(struct snd_aw2_saa7146 *chip);
extern void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip,
int stream_number,
unsigned long dma_addr,
unsigned long period_size,
unsigned long buffer_size);
extern void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip,
int stream_number,
unsigned long dma_addr,
unsigned long period_size,
unsigned long buffer_size);
extern void snd_aw2_saa7146_define_it_playback_callback(unsigned int
stream_number,
snd_aw2_saa7146_it_cb
p_it_callback,
void *p_callback_param);
extern void snd_aw2_saa7146_define_it_capture_callback(unsigned int
stream_number,
snd_aw2_saa7146_it_cb
p_it_callback,
void *p_callback_param);
extern void snd_aw2_saa7146_pcm_trigger_start_capture(struct snd_aw2_saa7146
*chip, int stream_number);
extern void snd_aw2_saa7146_pcm_trigger_stop_capture(struct snd_aw2_saa7146
*chip, int stream_number);
extern void snd_aw2_saa7146_pcm_trigger_start_playback(struct snd_aw2_saa7146
*chip,
int stream_number);
extern void snd_aw2_saa7146_pcm_trigger_stop_playback(struct snd_aw2_saa7146
*chip, int stream_number);
extern irqreturn_t snd_aw2_saa7146_interrupt(int irq, void *dev_id);
extern unsigned int snd_aw2_saa7146_get_hw_ptr_playback(struct snd_aw2_saa7146
*chip,
int stream_number,
unsigned char
*start_addr,
unsigned int
buffer_size);
extern unsigned int snd_aw2_saa7146_get_hw_ptr_capture(struct snd_aw2_saa7146
*chip,
int stream_number,
unsigned char
*start_addr,
unsigned int
buffer_size);
extern void snd_aw2_saa7146_use_digital_input(struct snd_aw2_saa7146 *chip,
int use_digital);
extern int snd_aw2_saa7146_is_using_digital_input(struct snd_aw2_saa7146
*chip);
#endif

110
sound/pci/aw2/aw2-tsl.c Normal file
View file

@ -0,0 +1,110 @@
/*****************************************************************************
*
* Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
* Jean-Christian Hassler <jhassler@free.fr>
* Copyright 1998 Emagic Soft- und Hardware GmbH
* Copyright 2002 Martijn Sipkema
*
* This file is part of the Audiowerk2 ALSA driver
*
* The Audiowerk2 ALSA driver 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; version 2.
*
* The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*****************************************************************************/
#define TSL_WS0 (1UL << 31)
#define TSL_WS1 (1UL << 30)
#define TSL_WS2 (1UL << 29)
#define TSL_WS3 (1UL << 28)
#define TSL_WS4 (1UL << 27)
#define TSL_DIS_A1 (1UL << 24)
#define TSL_SDW_A1 (1UL << 23)
#define TSL_SIB_A1 (1UL << 22)
#define TSL_SF_A1 (1UL << 21)
#define TSL_LF_A1 (1UL << 20)
#define TSL_BSEL_A1 (1UL << 17)
#define TSL_DOD_A1 (1UL << 15)
#define TSL_LOW_A1 (1UL << 14)
#define TSL_DIS_A2 (1UL << 11)
#define TSL_SDW_A2 (1UL << 10)
#define TSL_SIB_A2 (1UL << 9)
#define TSL_SF_A2 (1UL << 8)
#define TSL_LF_A2 (1UL << 7)
#define TSL_BSEL_A2 (1UL << 4)
#define TSL_DOD_A2 (1UL << 2)
#define TSL_LOW_A2 (1UL << 1)
#define TSL_EOS (1UL << 0)
/* Audiowerk8 hardware setup: */
/* WS0, SD4, TSL1 - Analog/ digital in */
/* WS1, SD0, TSL1 - Analog out #1, digital out */
/* WS2, SD2, TSL1 - Analog out #2 */
/* WS3, SD1, TSL2 - Analog out #3 */
/* WS4, SD3, TSL2 - Analog out #4 */
/* Audiowerk8 timing: */
/* Timeslot: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ... */
/* A1_INPUT: */
/* SD4: <_ADC-L_>-------<_ADC-R_>-------< */
/* WS0: _______________/---------------\_ */
/* A1_OUTPUT: */
/* SD0: <_1-L___>-------<_1-R___>-------< */
/* WS1: _______________/---------------\_ */
/* SD2: >-------<_2-L___>-------<_2-R___> */
/* WS2: -------\_______________/--------- */
/* A2_OUTPUT: */
/* SD1: <_3-L___>-------<_3-R___>-------< */
/* WS3: _______________/---------------\_ */
/* SD3: >-------<_4-L___>-------<_4-R___> */
/* WS4: -------\_______________/--------- */
static int tsl1[8] = {
1 * TSL_SDW_A1 | 3 * TSL_BSEL_A1 |
0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_LF_A1,
1 * TSL_SDW_A1 | 2 * TSL_BSEL_A1 |
0 * TSL_DIS_A1 | 0 * TSL_DOD_A1,
0 * TSL_SDW_A1 | 3 * TSL_BSEL_A1 |
0 * TSL_DIS_A1 | 0 * TSL_DOD_A1,
0 * TSL_SDW_A1 | 2 * TSL_BSEL_A1 |
0 * TSL_DIS_A1 | 0 * TSL_DOD_A1,
1 * TSL_SDW_A1 | 1 * TSL_BSEL_A1 |
0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0,
1 * TSL_SDW_A1 | 0 * TSL_BSEL_A1 |
0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0,
0 * TSL_SDW_A1 | 1 * TSL_BSEL_A1 |
0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0,
0 * TSL_SDW_A1 | 0 * TSL_BSEL_A1 | 0 * TSL_DIS_A1 |
0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0 | TSL_SF_A1 | TSL_EOS,
};
static int tsl2[8] = {
0 * TSL_SDW_A2 | 3 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_LF_A2,
0 * TSL_SDW_A2 | 2 * TSL_BSEL_A2 | 2 * TSL_DOD_A2,
0 * TSL_SDW_A2 | 3 * TSL_BSEL_A2 | 2 * TSL_DOD_A2,
0 * TSL_SDW_A2 | 2 * TSL_BSEL_A2 | 2 * TSL_DOD_A2,
0 * TSL_SDW_A2 | 1 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2,
0 * TSL_SDW_A2 | 0 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2,
0 * TSL_SDW_A2 | 1 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2,
0 * TSL_SDW_A2 | 0 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2 | TSL_EOS
};

168
sound/pci/aw2/saa7146.h Normal file
View file

@ -0,0 +1,168 @@
/*****************************************************************************
*
* Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
* Jean-Christian Hassler <jhassler@free.fr>
*
* This file is part of the Audiowerk2 ALSA driver
*
* The Audiowerk2 ALSA driver 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; version 2.
*
* The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*****************************************************************************/
/* SAA7146 registers */
#define PCI_BT_A 0x4C
#define IICTFR 0x8C
#define IICSTA 0x90
#define BaseA1_in 0x94
#define ProtA1_in 0x98
#define PageA1_in 0x9C
#define BaseA1_out 0xA0
#define ProtA1_out 0xA4
#define PageA1_out 0xA8
#define BaseA2_in 0xAC
#define ProtA2_in 0xB0
#define PageA2_in 0xB4
#define BaseA2_out 0xB8
#define ProtA2_out 0xBC
#define PageA2_out 0xC0
#define IER 0xDC
#define GPIO_CTRL 0xE0
#define ACON1 0xF4
#define ACON2 0xF8
#define MC1 0xFC
#define MC2 0x100
#define ISR 0x10C
#define PSR 0x110
#define SSR 0x114
#define PCI_ADP1 0x12C
#define PCI_ADP2 0x130
#define PCI_ADP3 0x134
#define PCI_ADP4 0x138
#define LEVEL_REP 0x140
#define FB_BUFFER1 0x144
#define FB_BUFFER2 0x148
#define TSL1 0x180
#define TSL2 0x1C0
#define ME (1UL << 11)
#define LIMIT (1UL << 4)
#define PV (1UL << 3)
/* PSR/ISR/IER */
#define PPEF (1UL << 31)
#define PABO (1UL << 30)
#define IIC_S (1UL << 17)
#define IIC_E (1UL << 16)
#define A2_in (1UL << 15)
#define A2_out (1UL << 14)
#define A1_in (1UL << 13)
#define A1_out (1UL << 12)
#define AFOU (1UL << 11)
#define PIN3 (1UL << 6)
#define PIN2 (1UL << 5)
#define PIN1 (1UL << 4)
#define PIN0 (1UL << 3)
#define ECS (1UL << 2)
#define EC3S (1UL << 1)
#define EC0S (1UL << 0)
/* SSR */
#define PRQ (1UL << 31)
#define PMA (1UL << 30)
#define IIC_EA (1UL << 21)
#define IIC_EW (1UL << 20)
#define IIC_ER (1UL << 19)
#define IIC_EL (1UL << 18)
#define IIC_EF (1UL << 17)
#define AF2_in (1UL << 10)
#define AF2_out (1UL << 9)
#define AF1_in (1UL << 8)
#define AF1_out (1UL << 7)
#define EC5S (1UL << 3)
#define EC4S (1UL << 2)
#define EC2S (1UL << 1)
#define EC1S (1UL << 0)
/* PCI_BT_A */
#define BurstA1_in (1UL << 26)
#define ThreshA1_in (1UL << 24)
#define BurstA1_out (1UL << 18)
#define ThreshA1_out (1UL << 16)
#define BurstA2_in (1UL << 10)
#define ThreshA2_in (1UL << 8)
#define BurstA2_out (1UL << 2)
#define ThreshA2_out (1UL << 0)
/* MC1 */
#define MRST_N (1UL << 15)
#define EAP (1UL << 9)
#define EI2C (1UL << 8)
#define TR_E_A2_OUT (1UL << 3)
#define TR_E_A2_IN (1UL << 2)
#define TR_E_A1_OUT (1UL << 1)
#define TR_E_A1_IN (1UL << 0)
/* MC2 */
#define UPLD_IIC (1UL << 0)
/* ACON1 */
#define AUDIO_MODE (1UL << 29)
#define MAXLEVEL (1UL << 22)
#define A1_SWAP (1UL << 21)
#define A2_SWAP (1UL << 20)
#define WS0_CTRL (1UL << 18)
#define WS0_SYNC (1UL << 16)
#define WS1_CTRL (1UL << 14)
#define WS1_SYNC (1UL << 12)
#define WS2_CTRL (1UL << 10)
#define WS2_SYNC (1UL << 8)
#define WS3_CTRL (1UL << 6)
#define WS3_SYNC (1UL << 4)
#define WS4_CTRL (1UL << 2)
#define WS4_SYNC (1UL << 0)
/* ACON2 */
#define A1_CLKSRC (1UL << 27)
#define A2_CLKSRC (1UL << 22)
#define INVERT_BCLK1 (1UL << 21)
#define INVERT_BCLK2 (1UL << 20)
#define BCLK1_OEN (1UL << 19)
#define BCLK2_OEN (1UL << 18)
/* IICSTA */
#define IICCC (1UL << 8)
#define ABORT (1UL << 7)
#define SPERR (1UL << 6)
#define APERR (1UL << 5)
#define DTERR (1UL << 4)
#define DRERR (1UL << 3)
#define AL (1UL << 2)
#define ERR (1UL << 1)
#define BUSY (1UL << 0)
/* IICTFR */
#define BYTE2 (1UL << 24)
#define BYTE1 (1UL << 16)
#define BYTE0 (1UL << 8)
#define ATRR2 (1UL << 6)
#define ATRR1 (1UL << 4)
#define ATRR0 (1UL << 2)
#define ERR (1UL << 1)
#define BUSY (1UL << 0)
#define START 3
#define CONT 2
#define STOP 1
#define NOP 0

2783
sound/pci/azt3328.c Normal file

File diff suppressed because it is too large Load diff

342
sound/pci/azt3328.h Normal file
View file

@ -0,0 +1,342 @@
#ifndef __SOUND_AZT3328_H
#define __SOUND_AZT3328_H
/* "PU" == "power-up value", as tested on PCI168 PCI rev. 10
* "WRITE_ONLY" == register does not indicate actual bit values */
/*** main I/O area port indices ***/
/* (only 0x70 of 0x80 bytes saved/restored by Windows driver) */
#define AZF_IO_SIZE_CTRL 0x80
#define AZF_IO_SIZE_CTRL_PM 0x70
/* the driver initialisation suggests a layout of 4 areas
* within the main card control I/O:
* from 0x00 (playback codec), from 0x20 (recording codec)
* and from 0x40 (most certainly I2S out codec).
* And another area from 0x60 to 0x6f (DirectX timer, IRQ management,
* power management etc.???). */
#define AZF_IO_OFFS_CODEC_PLAYBACK 0x00
#define AZF_IO_OFFS_CODEC_CAPTURE 0x20
#define AZF_IO_OFFS_CODEC_I2S_OUT 0x40
#define IDX_IO_CODEC_DMA_FLAGS 0x00 /* PU:0x0000 */
/* able to reactivate output after output muting due to 8/16bit
* output change, just like 0x0002.
* 0x0001 is the only bit that's able to start the DMA counter */
#define DMA_RESUME 0x0001 /* paused if cleared? */
/* 0x0002 *temporarily* set during DMA stopping. hmm
* both 0x0002 and 0x0004 set in playback setup. */
/* able to reactivate output after output muting due to 8/16bit
* output change, just like 0x0001. */
#define DMA_RUN_SOMETHING1 0x0002 /* \ alternated (toggled) */
/* 0x0004: NOT able to reactivate output */
#define DMA_RUN_SOMETHING2 0x0004 /* / bits */
#define SOMETHING_ALMOST_ALWAYS_SET 0x0008 /* ???; can be modified */
#define DMA_EPILOGUE_SOMETHING 0x0010
#define DMA_SOMETHING_ELSE 0x0020 /* ??? */
#define SOMETHING_UNMODIFIABLE 0xffc0 /* unused? not modifiable */
#define IDX_IO_CODEC_IRQTYPE 0x02 /* PU:0x0001 */
/* write back to flags in case flags are set, in order to ACK IRQ in handler
* (bit 1 of port 0x64 indicates interrupt for one of these three types)
* sometimes in this case it just writes 0xffff to globally ACK all IRQs
* settings written are not reflected when reading back, though.
* seems to be IRQ, too (frequently used: port |= 0x07 !), but who knows? */
#define IRQ_SOMETHING 0x0001 /* something & ACK */
#define IRQ_FINISHED_DMABUF_1 0x0002 /* 1st dmabuf finished & ACK */
#define IRQ_FINISHED_DMABUF_2 0x0004 /* 2nd dmabuf finished & ACK */
#define IRQMASK_SOME_STATUS_1 0x0008 /* \ related bits */
#define IRQMASK_SOME_STATUS_2 0x0010 /* / (checked together in loop) */
#define IRQMASK_UNMODIFIABLE 0xffe0 /* unused? not modifiable */
/* start address of 1st DMA transfer area, PU:0x00000000 */
#define IDX_IO_CODEC_DMA_START_1 0x04
/* start address of 2nd DMA transfer area, PU:0x00000000 */
#define IDX_IO_CODEC_DMA_START_2 0x08
/* both lengths of DMA transfer areas, PU:0x00000000
length1: offset 0x0c, length2: offset 0x0e */
#define IDX_IO_CODEC_DMA_LENGTHS 0x0c
#define IDX_IO_CODEC_DMA_CURRPOS 0x10 /* current DMA position, PU:0x00000000 */
/* offset within current DMA transfer area, PU:0x0000 */
#define IDX_IO_CODEC_DMA_CURROFS 0x14
#define IDX_IO_CODEC_SOUNDFORMAT 0x16 /* PU:0x0010 */
/* all unspecified bits can't be modified */
#define SOUNDFORMAT_FREQUENCY_MASK 0x000f
#define SOUNDFORMAT_XTAL1 0x00
#define SOUNDFORMAT_XTAL2 0x01
/* all _SUSPECTED_ values are not used by Windows drivers, so we don't
* have any hard facts, only rough measurements.
* All we know is that the crystal used on the board has 24.576MHz,
* like many soundcards (which results in the frequencies below when
* using certain divider values selected by the values below) */
#define SOUNDFORMAT_FREQ_SUSPECTED_4000 0x0c | SOUNDFORMAT_XTAL1
#define SOUNDFORMAT_FREQ_SUSPECTED_4800 0x0a | SOUNDFORMAT_XTAL1
#define SOUNDFORMAT_FREQ_5510 0x0c | SOUNDFORMAT_XTAL2
#define SOUNDFORMAT_FREQ_6620 0x0a | SOUNDFORMAT_XTAL2
#define SOUNDFORMAT_FREQ_8000 0x00 | SOUNDFORMAT_XTAL1 /* also 0x0e | SOUNDFORMAT_XTAL1? */
#define SOUNDFORMAT_FREQ_9600 0x08 | SOUNDFORMAT_XTAL1
#define SOUNDFORMAT_FREQ_11025 0x00 | SOUNDFORMAT_XTAL2 /* also 0x0e | SOUNDFORMAT_XTAL2? */
#define SOUNDFORMAT_FREQ_SUSPECTED_13240 0x08 | SOUNDFORMAT_XTAL2 /* seems to be 6620 *2 */
#define SOUNDFORMAT_FREQ_16000 0x02 | SOUNDFORMAT_XTAL1
#define SOUNDFORMAT_FREQ_22050 0x02 | SOUNDFORMAT_XTAL2
#define SOUNDFORMAT_FREQ_32000 0x04 | SOUNDFORMAT_XTAL1
#define SOUNDFORMAT_FREQ_44100 0x04 | SOUNDFORMAT_XTAL2
#define SOUNDFORMAT_FREQ_48000 0x06 | SOUNDFORMAT_XTAL1
#define SOUNDFORMAT_FREQ_SUSPECTED_66200 0x06 | SOUNDFORMAT_XTAL2 /* 66200 (13240 * 5); 64000 may have been nicer :-\ */
#define SOUNDFORMAT_FLAG_16BIT 0x0010
#define SOUNDFORMAT_FLAG_2CHANNELS 0x0020
/* define frequency helpers, for maximum value safety */
enum azf_freq_t {
#define AZF_FREQ(rate) AZF_FREQ_##rate = rate
AZF_FREQ(4000),
AZF_FREQ(4800),
AZF_FREQ(5512),
AZF_FREQ(6620),
AZF_FREQ(8000),
AZF_FREQ(9600),
AZF_FREQ(11025),
AZF_FREQ(13240),
AZF_FREQ(16000),
AZF_FREQ(22050),
AZF_FREQ(32000),
AZF_FREQ(44100),
AZF_FREQ(48000),
AZF_FREQ(66200),
#undef AZF_FREQ
};
/** DirectX timer, main interrupt area (FIXME: and something else?) **/
#define IDX_IO_TIMER_VALUE 0x60 /* found this timer area by pure luck :-) */
/* timer countdown value; triggers IRQ when timer is finished */
#define TIMER_VALUE_MASK 0x000fffffUL
/* activate timer countdown */
#define TIMER_COUNTDOWN_ENABLE 0x01000000UL
/* trigger timer IRQ on zero transition */
#define TIMER_IRQ_ENABLE 0x02000000UL
/* being set in IRQ handler in case port 0x00 (hmm, not port 0x64!?!?)
* had 0x0020 set upon IRQ handler */
#define TIMER_IRQ_ACK 0x04000000UL
#define IDX_IO_IRQSTATUS 0x64
/* some IRQ bit in here might also be used to signal a power-management timer
* timeout, to request shutdown of the chip (e.g. AD1815JS has such a thing).
* OPL3 hardware contains several timers which confusingly in most cases
* are NOT routed to an IRQ, but some designs (e.g. LM4560) DO support that,
* so I wouldn't be surprised at all to discover that AZF3328
* supports that thing as well... */
#define IRQ_PLAYBACK 0x0001
#define IRQ_RECORDING 0x0002
#define IRQ_I2S_OUT 0x0004 /* this IS I2S, right!? (untested) */
#define IRQ_GAMEPORT 0x0008 /* Interrupt of Digital(ly) Enhanced Game Port */
#define IRQ_MPU401 0x0010
#define IRQ_TIMER 0x0020 /* DirectX timer */
#define IRQ_UNKNOWN2 0x0040 /* probably unused, or possibly OPL3 timer? */
#define IRQ_UNKNOWN3 0x0080 /* probably unused, or possibly OPL3 timer? */
#define IDX_IO_66H 0x66 /* writing 0xffff returns 0x0000 */
/* this is set to e.g. 0x3ff or 0x300, and writable;
* maybe some buffer limit, but I couldn't find out more, PU:0x00ff: */
#define IDX_IO_SOME_VALUE 0x68
#define IO_68_RANDOM_TOGGLE1 0x0100 /* toggles randomly */
#define IO_68_RANDOM_TOGGLE2 0x0200 /* toggles randomly */
/* umm, nope, behaviour of these bits changes depending on what we wrote
* to 0x6b!!
* And they change upon playback/stop, too:
* Writing a value to 0x68 will display this exact value during playback,
* too but when stopped it can fall back to a rather different
* seemingly random value). Hmm, possibly this is a register which
* has a remote shadow which needs proper device supply which only exists
* in case playback is active? Or is this driver-induced?
*/
/* this WORD can be set to have bits 0x0028 activated (FIXME: correct??);
* actually inhibits PCM playback!!! maybe power management??: */
#define IDX_IO_6AH 0x6A /* WRITE_ONLY! */
/* bit 5: enabling this will activate permanent counting of bytes 2/3
* at gameport I/O (0xb402/3) (equal values each) and cause
* gameport legacy I/O at 0x0200 to be _DISABLED_!
* Is this Digital Enhanced Game Port Enable??? Or maybe it's Testmode
* for Enhanced Digital Gameport (see 4D Wave DX card): */
#define IO_6A_SOMETHING1_GAMEPORT 0x0020
/* bit 8; sure, this _pauses_ playback (later resumes at same spot!),
* but what the heck is this really about??: */
#define IO_6A_PAUSE_PLAYBACK_BIT8 0x0100
/* bit 9; sure, this _pauses_ playback (later resumes at same spot!),
* but what the heck is this really about??: */
#define IO_6A_PAUSE_PLAYBACK_BIT9 0x0200
/* BIT8 and BIT9 are _NOT_ able to affect OPL3 MIDI playback,
* thus it suggests influence on PCM only!!
* However OTOH there seems to be no bit anywhere around here
* which is able to disable OPL3... */
/* bit 10: enabling this actually changes values at legacy gameport
* I/O address (0x200); is this enabling of the Digital Enhanced Game Port???
* Or maybe this simply switches off the NE558 circuit, since enabling this
* still lets us evaluate button states, but not axis states */
#define IO_6A_SOMETHING2_GAMEPORT 0x0400
/* writing 0x0300: causes quite some crackling during
* PC activity such as switching windows (PCI traffic??
* --> FIFO/timing settings???) */
/* writing 0x0100 plus/or 0x0200 inhibits playback */
/* since the Windows .INF file has Flag_Enable_JoyStick and
* Flag_Enable_SB_DOS_Emulation directly together, it stands to reason
* that some other bit in this same register might be responsible
* for SB DOS Emulation activation (note that the file did NOT define
* a switch for OPL3!) */
#define IDX_IO_6CH 0x6C /* unknown; fully read-writable */
#define IDX_IO_6EH 0x6E
/* writing 0xffff returns 0x83fe (or 0x03fe only).
* writing 0x83 (and only 0x83!!) to 0x6f will cause 0x6c to switch
* from 0000 to ffff. */
/* further I/O indices not saved/restored and not readable after writing,
* so probably not used */
/*** Gameport area port indices ***/
/* (only 0x06 of 0x08 bytes saved/restored by Windows driver) */
#define AZF_IO_SIZE_GAME 0x08
#define AZF_IO_SIZE_GAME_PM 0x06
enum {
AZF_GAME_LEGACY_IO_PORT = 0x200
};
#define IDX_GAME_LEGACY_COMPATIBLE 0x00
/* in some operation mode, writing anything to this port
* triggers an interrupt:
* yup, that's in case IDX_GAME_01H has one of the
* axis measurement bits enabled
* (and of course one needs to have GAME_HWCFG_IRQ_ENABLE, too) */
#define IDX_GAME_AXES_CONFIG 0x01
/* NOTE: layout of this register awfully similar (read: "identical??")
* to AD1815JS.pdf (p.29) */
/* enables axis 1 (X axis) measurement: */
#define GAME_AXES_ENABLE_1 0x01
/* enables axis 2 (Y axis) measurement: */
#define GAME_AXES_ENABLE_2 0x02
/* enables axis 3 (X axis) measurement: */
#define GAME_AXES_ENABLE_3 0x04
/* enables axis 4 (Y axis) measurement: */
#define GAME_AXES_ENABLE_4 0x08
/* selects the current axis to read the measured value of
* (at IDX_GAME_AXIS_VALUE):
* 00 = axis 1, 01 = axis 2, 10 = axis 3, 11 = axis 4: */
#define GAME_AXES_READ_MASK 0x30
/* enable to have the latch continuously accept ADC values
* (and continuously cause interrupts in case interrupts are enabled);
* AD1815JS.pdf says it's ~16ms interval there: */
#define GAME_AXES_LATCH_ENABLE 0x40
/* joystick data (measured axes) ready for reading: */
#define GAME_AXES_SAMPLING_READY 0x80
/* NOTE: other card specs (SiS960 and others!) state that the
* game position latches should be frozen when reading and be freed
* (== reset?) after reading!!!
* Freezing most likely means disabling 0x40 (GAME_AXES_LATCH_ENABLE),
* but how to free the value? */
/* An internet search for "gameport latch ADC" should provide some insight
* into how to program such a gameport system. */
/* writing 0xf0 to 01H once reset both counters to 0, in some special mode!?
* yup, in case 6AH 0x20 is not enabled
* (and 0x40 is sufficient, 0xf0 is not needed) */
#define IDX_GAME_AXIS_VALUE 0x02
/* R: value of currently configured axis (word value!);
* W: trigger axis measurement */
#define IDX_GAME_HWCONFIG 0x04
/* note: bits 4 to 7 are never set (== 0) when reading!
* --> reserved bits? */
/* enables IRQ notification upon axes measurement ready: */
#define GAME_HWCFG_IRQ_ENABLE 0x01
/* these bits choose a different frequency for the
* internal ADC counter increment.
* hmm, seems to be a combo of bits:
* 00 --> standard frequency
* 10 --> 1/2
* 01 --> 1/20
* 11 --> 1/200: */
#define GAME_HWCFG_ADC_COUNTER_FREQ_MASK 0x06
/* FIXME: these values might be reversed... */
#define GAME_HWCFG_ADC_COUNTER_FREQ_STD 0
#define GAME_HWCFG_ADC_COUNTER_FREQ_1_2 1
#define GAME_HWCFG_ADC_COUNTER_FREQ_1_20 2
#define GAME_HWCFG_ADC_COUNTER_FREQ_1_200 3
/* enable gameport legacy I/O address (0x200)
* I was unable to locate any configurability for a different address: */
#define GAME_HWCFG_LEGACY_ADDRESS_ENABLE 0x08
/*** MPU401 ***/
#define AZF_IO_SIZE_MPU 0x04
#define AZF_IO_SIZE_MPU_PM 0x04
/*** OPL3 synth ***/
/* (only 0x06 of 0x08 bytes saved/restored by Windows driver) */
#define AZF_IO_SIZE_OPL3 0x08
#define AZF_IO_SIZE_OPL3_PM 0x06
/* hmm, given that a standard OPL3 has 4 registers only,
* there might be some enhanced functionality lurking at the end
* (especially since register 0x04 has a "non-empty" value 0xfe) */
/*** mixer I/O area port indices ***/
/* (only 0x22 of 0x40 bytes saved/restored by Windows driver)
* UNFORTUNATELY azf3328 is NOT truly AC97 compliant: see main file intro */
#define AZF_IO_SIZE_MIXER 0x40
#define AZF_IO_SIZE_MIXER_PM 0x22
#define MIXER_VOLUME_RIGHT_MASK 0x001f
#define MIXER_VOLUME_LEFT_MASK 0x1f00
#define MIXER_MUTE_MASK 0x8000
#define IDX_MIXER_RESET 0x00 /* does NOT seem to have AC97 ID bits */
#define IDX_MIXER_PLAY_MASTER 0x02
#define IDX_MIXER_MODEMOUT 0x04
#define IDX_MIXER_BASSTREBLE 0x06
#define MIXER_BASSTREBLE_TREBLE_VOLUME_MASK 0x000e
#define MIXER_BASSTREBLE_BASS_VOLUME_MASK 0x0e00
#define IDX_MIXER_PCBEEP 0x08
#define IDX_MIXER_MODEMIN 0x0a
#define IDX_MIXER_MIC 0x0c
#define MIXER_MIC_MICGAIN_20DB_ENHANCEMENT_MASK 0x0040
#define IDX_MIXER_LINEIN 0x0e
#define IDX_MIXER_CDAUDIO 0x10
#define IDX_MIXER_VIDEO 0x12
#define IDX_MIXER_AUX 0x14
#define IDX_MIXER_WAVEOUT 0x16
#define IDX_MIXER_FMSYNTH 0x18
#define IDX_MIXER_REC_SELECT 0x1a
#define MIXER_REC_SELECT_MIC 0x00
#define MIXER_REC_SELECT_CD 0x01
#define MIXER_REC_SELECT_VIDEO 0x02
#define MIXER_REC_SELECT_AUX 0x03
#define MIXER_REC_SELECT_LINEIN 0x04
#define MIXER_REC_SELECT_MIXSTEREO 0x05
#define MIXER_REC_SELECT_MIXMONO 0x06
#define MIXER_REC_SELECT_MONOIN 0x07
#define IDX_MIXER_REC_VOLUME 0x1c
#define IDX_MIXER_ADVCTL1 0x1e
/* unlisted bits are unmodifiable */
#define MIXER_ADVCTL1_3DWIDTH_MASK 0x000e
#define MIXER_ADVCTL1_HIFI3D_MASK 0x0300 /* yup, this is missing the high bit that official AC97 contains, plus it doesn't have linear bit value range behaviour but instead acts weirdly (possibly we're dealing with two *different* 3D settings here??) */
#define IDX_MIXER_ADVCTL2 0x20 /* subset of AC97_GENERAL_PURPOSE reg! */
/* unlisted bits are unmodifiable */
#define MIXER_ADVCTL2_LPBK 0x0080 /* Loopback mode -- Win driver: "WaveOut3DBypass"? mutes WaveOut at LineOut */
#define MIXER_ADVCTL2_MS 0x0100 /* Mic Select 0=Mic1, 1=Mic2 -- Win driver: "ModemOutSelect"?? */
#define MIXER_ADVCTL2_MIX 0x0200 /* Mono output select 0=Mix, 1=Mic; Win driver: "MonoSelectSource"?? */
#define MIXER_ADVCTL2_3D 0x2000 /* 3D Enhancement 1=on */
#define MIXER_ADVCTL2_POP 0x8000 /* Pcm Out Path, 0=pre 3D, 1=post 3D */
#define IDX_MIXER_SOMETHING30H 0x30 /* used, but unknown??? */
/* driver internal flags */
#define SET_CHAN_LEFT 1
#define SET_CHAN_RIGHT 2
/* helper macro to align I/O port ranges to 32bit I/O width */
#define AZF_ALIGN(x) (((x) + 3) & (~3))
#endif /* __SOUND_AZT3328_H */

995
sound/pci/bt87x.c Normal file
View file

@ -0,0 +1,995 @@
/*
* bt87x.c - Brooktree Bt878/Bt879 driver for ALSA
*
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
*
* based on btaudio.c by Gerd Knorr <kraxel@bytesex.org>
*
*
* This driver is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This driver 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/init.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/bitops.h>
#include <asm/io.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/control.h>
#include <sound/initval.h>
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_DESCRIPTION("Brooktree Bt87x audio driver");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{Brooktree,Bt878},"
"{Brooktree,Bt879}}");
static int index[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -2}; /* Exclude the first card */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
static int digital_rate[SNDRV_CARDS]; /* digital input rate */
static bool load_all; /* allow to load the non-whitelisted cards */
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for Bt87x soundcard");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for Bt87x soundcard");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable Bt87x soundcard");
module_param_array(digital_rate, int, NULL, 0444);
MODULE_PARM_DESC(digital_rate, "Digital input rate for Bt87x soundcard");
module_param(load_all, bool, 0444);
MODULE_PARM_DESC(load_all, "Allow to load the non-whitelisted cards");
/* register offsets */
#define REG_INT_STAT 0x100 /* interrupt status */
#define REG_INT_MASK 0x104 /* interrupt mask */
#define REG_GPIO_DMA_CTL 0x10c /* audio control */
#define REG_PACKET_LEN 0x110 /* audio packet lengths */
#define REG_RISC_STRT_ADD 0x114 /* RISC program start address */
#define REG_RISC_COUNT 0x120 /* RISC program counter */
/* interrupt bits */
#define INT_OFLOW (1 << 3) /* audio A/D overflow */
#define INT_RISCI (1 << 11) /* RISC instruction IRQ bit set */
#define INT_FBUS (1 << 12) /* FIFO overrun due to bus access latency */
#define INT_FTRGT (1 << 13) /* FIFO overrun due to target latency */
#define INT_FDSR (1 << 14) /* FIFO data stream resynchronization */
#define INT_PPERR (1 << 15) /* PCI parity error */
#define INT_RIPERR (1 << 16) /* RISC instruction parity error */
#define INT_PABORT (1 << 17) /* PCI master or target abort */
#define INT_OCERR (1 << 18) /* invalid opcode */
#define INT_SCERR (1 << 19) /* sync counter overflow */
#define INT_RISC_EN (1 << 27) /* DMA controller running */
#define INT_RISCS_SHIFT 28 /* RISC status bits */
/* audio control bits */
#define CTL_FIFO_ENABLE (1 << 0) /* enable audio data FIFO */
#define CTL_RISC_ENABLE (1 << 1) /* enable audio DMA controller */
#define CTL_PKTP_4 (0 << 2) /* packet mode FIFO trigger point - 4 DWORDs */
#define CTL_PKTP_8 (1 << 2) /* 8 DWORDs */
#define CTL_PKTP_16 (2 << 2) /* 16 DWORDs */
#define CTL_ACAP_EN (1 << 4) /* enable audio capture */
#define CTL_DA_APP (1 << 5) /* GPIO input */
#define CTL_DA_IOM_AFE (0 << 6) /* audio A/D input */
#define CTL_DA_IOM_DA (1 << 6) /* digital audio input */
#define CTL_DA_SDR_SHIFT 8 /* DDF first stage decimation rate */
#define CTL_DA_SDR_MASK (0xf<< 8)
#define CTL_DA_LMT (1 << 12) /* limit audio data values */
#define CTL_DA_ES2 (1 << 13) /* enable DDF stage 2 */
#define CTL_DA_SBR (1 << 14) /* samples rounded to 8 bits */
#define CTL_DA_DPM (1 << 15) /* data packet mode */
#define CTL_DA_LRD_SHIFT 16 /* ALRCK delay */
#define CTL_DA_MLB (1 << 21) /* MSB/LSB format */
#define CTL_DA_LRI (1 << 22) /* left/right indication */
#define CTL_DA_SCE (1 << 23) /* sample clock edge */
#define CTL_A_SEL_STV (0 << 24) /* TV tuner audio input */
#define CTL_A_SEL_SFM (1 << 24) /* FM audio input */
#define CTL_A_SEL_SML (2 << 24) /* mic/line audio input */
#define CTL_A_SEL_SMXC (3 << 24) /* MUX bypass */
#define CTL_A_SEL_SHIFT 24
#define CTL_A_SEL_MASK (3 << 24)
#define CTL_A_PWRDN (1 << 26) /* analog audio power-down */
#define CTL_A_G2X (1 << 27) /* audio gain boost */
#define CTL_A_GAIN_SHIFT 28 /* audio input gain */
#define CTL_A_GAIN_MASK (0xf<<28)
/* RISC instruction opcodes */
#define RISC_WRITE (0x1 << 28) /* write FIFO data to memory at address */
#define RISC_WRITEC (0x5 << 28) /* write FIFO data to memory at current address */
#define RISC_SKIP (0x2 << 28) /* skip FIFO data */
#define RISC_JUMP (0x7 << 28) /* jump to address */
#define RISC_SYNC (0x8 << 28) /* synchronize with FIFO */
/* RISC instruction bits */
#define RISC_BYTES_ENABLE (0xf << 12) /* byte enable bits */
#define RISC_RESYNC ( 1 << 15) /* disable FDSR errors */
#define RISC_SET_STATUS_SHIFT 16 /* set status bits */
#define RISC_RESET_STATUS_SHIFT 20 /* clear status bits */
#define RISC_IRQ ( 1 << 24) /* interrupt */
#define RISC_EOL ( 1 << 26) /* end of line */
#define RISC_SOL ( 1 << 27) /* start of line */
/* SYNC status bits values */
#define RISC_SYNC_FM1 0x6
#define RISC_SYNC_VRO 0xc
#define ANALOG_CLOCK 1792000
#ifdef CONFIG_SND_BT87X_OVERCLOCK
#define CLOCK_DIV_MIN 1
#else
#define CLOCK_DIV_MIN 4
#endif
#define CLOCK_DIV_MAX 15
#define ERROR_INTERRUPTS (INT_FBUS | INT_FTRGT | INT_PPERR | \
INT_RIPERR | INT_PABORT | INT_OCERR)
#define MY_INTERRUPTS (INT_RISCI | ERROR_INTERRUPTS)
/* SYNC, one WRITE per line, one extra WRITE per page boundary, SYNC, JUMP */
#define MAX_RISC_SIZE ((1 + 255 + (PAGE_ALIGN(255 * 4092) / PAGE_SIZE - 1) + 1 + 1) * 8)
/* Cards with configuration information */
enum snd_bt87x_boardid {
SND_BT87X_BOARD_UNKNOWN,
SND_BT87X_BOARD_GENERIC, /* both an & dig interfaces, 32kHz */
SND_BT87X_BOARD_ANALOG, /* board with no external A/D */
SND_BT87X_BOARD_OSPREY2x0,
SND_BT87X_BOARD_OSPREY440,
SND_BT87X_BOARD_AVPHONE98,
};
/* Card configuration */
struct snd_bt87x_board {
int dig_rate; /* Digital input sampling rate */
u32 digital_fmt; /* Register settings for digital input */
unsigned no_analog:1; /* No analog input */
unsigned no_digital:1; /* No digital input */
};
static struct snd_bt87x_board snd_bt87x_boards[] = {
[SND_BT87X_BOARD_UNKNOWN] = {
.dig_rate = 32000, /* just a guess */
},
[SND_BT87X_BOARD_GENERIC] = {
.dig_rate = 32000,
},
[SND_BT87X_BOARD_ANALOG] = {
.no_digital = 1,
},
[SND_BT87X_BOARD_OSPREY2x0] = {
.dig_rate = 44100,
.digital_fmt = CTL_DA_LRI | (1 << CTL_DA_LRD_SHIFT),
},
[SND_BT87X_BOARD_OSPREY440] = {
.dig_rate = 32000,
.digital_fmt = CTL_DA_LRI | (1 << CTL_DA_LRD_SHIFT),
.no_analog = 1,
},
[SND_BT87X_BOARD_AVPHONE98] = {
.dig_rate = 48000,
},
};
struct snd_bt87x {
struct snd_card *card;
struct pci_dev *pci;
struct snd_bt87x_board board;
void __iomem *mmio;
int irq;
spinlock_t reg_lock;
unsigned long opened;
struct snd_pcm_substream *substream;
struct snd_dma_buffer dma_risc;
unsigned int line_bytes;
unsigned int lines;
u32 reg_control;
u32 interrupt_mask;
int current_line;
int pci_parity_errors;
};
enum { DEVICE_DIGITAL, DEVICE_ANALOG };
static inline u32 snd_bt87x_readl(struct snd_bt87x *chip, u32 reg)
{
return readl(chip->mmio + reg);
}
static inline void snd_bt87x_writel(struct snd_bt87x *chip, u32 reg, u32 value)
{
writel(value, chip->mmio + reg);
}
static int snd_bt87x_create_risc(struct snd_bt87x *chip, struct snd_pcm_substream *substream,
unsigned int periods, unsigned int period_bytes)
{
unsigned int i, offset;
u32 *risc;
if (chip->dma_risc.area == NULL) {
if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
PAGE_ALIGN(MAX_RISC_SIZE), &chip->dma_risc) < 0)
return -ENOMEM;
}
risc = (u32 *)chip->dma_risc.area;
offset = 0;
*risc++ = cpu_to_le32(RISC_SYNC | RISC_SYNC_FM1);
*risc++ = cpu_to_le32(0);
for (i = 0; i < periods; ++i) {
u32 rest;
rest = period_bytes;
do {
u32 cmd, len;
unsigned int addr;
len = PAGE_SIZE - (offset % PAGE_SIZE);
if (len > rest)
len = rest;
cmd = RISC_WRITE | len;
if (rest == period_bytes) {
u32 block = i * 16 / periods;
cmd |= RISC_SOL;
cmd |= block << RISC_SET_STATUS_SHIFT;
cmd |= (~block & 0xf) << RISC_RESET_STATUS_SHIFT;
}
if (len == rest)
cmd |= RISC_EOL | RISC_IRQ;
*risc++ = cpu_to_le32(cmd);
addr = snd_pcm_sgbuf_get_addr(substream, offset);
*risc++ = cpu_to_le32(addr);
offset += len;
rest -= len;
} while (rest > 0);
}
*risc++ = cpu_to_le32(RISC_SYNC | RISC_SYNC_VRO);
*risc++ = cpu_to_le32(0);
*risc++ = cpu_to_le32(RISC_JUMP);
*risc++ = cpu_to_le32(chip->dma_risc.addr);
chip->line_bytes = period_bytes;
chip->lines = periods;
return 0;
}
static void snd_bt87x_free_risc(struct snd_bt87x *chip)
{
if (chip->dma_risc.area) {
snd_dma_free_pages(&chip->dma_risc);
chip->dma_risc.area = NULL;
}
}
static void snd_bt87x_pci_error(struct snd_bt87x *chip, unsigned int status)
{
u16 pci_status;
pci_read_config_word(chip->pci, PCI_STATUS, &pci_status);
pci_status &= PCI_STATUS_PARITY | PCI_STATUS_SIG_TARGET_ABORT |
PCI_STATUS_REC_TARGET_ABORT | PCI_STATUS_REC_MASTER_ABORT |
PCI_STATUS_SIG_SYSTEM_ERROR | PCI_STATUS_DETECTED_PARITY;
pci_write_config_word(chip->pci, PCI_STATUS, pci_status);
if (pci_status != PCI_STATUS_DETECTED_PARITY)
dev_err(chip->card->dev,
"Aieee - PCI error! status %#08x, PCI status %#04x\n",
status & ERROR_INTERRUPTS, pci_status);
else {
dev_err(chip->card->dev,
"Aieee - PCI parity error detected!\n");
/* error 'handling' similar to aic7xxx_pci.c: */
chip->pci_parity_errors++;
if (chip->pci_parity_errors > 20) {
dev_err(chip->card->dev,
"Too many PCI parity errors observed.\n");
dev_err(chip->card->dev,
"Some device on this bus is generating bad parity.\n");
dev_err(chip->card->dev,
"This is an error *observed by*, not *generated by*, this card.\n");
dev_err(chip->card->dev,
"PCI parity error checking has been disabled.\n");
chip->interrupt_mask &= ~(INT_PPERR | INT_RIPERR);
snd_bt87x_writel(chip, REG_INT_MASK, chip->interrupt_mask);
}
}
}
static irqreturn_t snd_bt87x_interrupt(int irq, void *dev_id)
{
struct snd_bt87x *chip = dev_id;
unsigned int status, irq_status;
status = snd_bt87x_readl(chip, REG_INT_STAT);
irq_status = status & chip->interrupt_mask;
if (!irq_status)
return IRQ_NONE;
snd_bt87x_writel(chip, REG_INT_STAT, irq_status);
if (irq_status & ERROR_INTERRUPTS) {
if (irq_status & (INT_FBUS | INT_FTRGT))
dev_warn(chip->card->dev,
"FIFO overrun, status %#08x\n", status);
if (irq_status & INT_OCERR)
dev_err(chip->card->dev,
"internal RISC error, status %#08x\n", status);
if (irq_status & (INT_PPERR | INT_RIPERR | INT_PABORT))
snd_bt87x_pci_error(chip, irq_status);
}
if ((irq_status & INT_RISCI) && (chip->reg_control & CTL_ACAP_EN)) {
int current_block, irq_block;
/* assume that exactly one line has been recorded */
chip->current_line = (chip->current_line + 1) % chip->lines;
/* but check if some interrupts have been skipped */
current_block = chip->current_line * 16 / chip->lines;
irq_block = status >> INT_RISCS_SHIFT;
if (current_block != irq_block)
chip->current_line = (irq_block * chip->lines + 15) / 16;
snd_pcm_period_elapsed(chip->substream);
}
return IRQ_HANDLED;
}
static struct snd_pcm_hardware snd_bt87x_digital_hw = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BATCH,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = 0, /* set at runtime */
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 255 * 4092,
.period_bytes_min = 32,
.period_bytes_max = 4092,
.periods_min = 2,
.periods_max = 255,
};
static struct snd_pcm_hardware snd_bt87x_analog_hw = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BATCH,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
.rates = SNDRV_PCM_RATE_KNOT,
.rate_min = ANALOG_CLOCK / CLOCK_DIV_MAX,
.rate_max = ANALOG_CLOCK / CLOCK_DIV_MIN,
.channels_min = 1,
.channels_max = 1,
.buffer_bytes_max = 255 * 4092,
.period_bytes_min = 32,
.period_bytes_max = 4092,
.periods_min = 2,
.periods_max = 255,
};
static int snd_bt87x_set_digital_hw(struct snd_bt87x *chip, struct snd_pcm_runtime *runtime)
{
chip->reg_control |= CTL_DA_IOM_DA | CTL_A_PWRDN;
runtime->hw = snd_bt87x_digital_hw;
runtime->hw.rates = snd_pcm_rate_to_rate_bit(chip->board.dig_rate);
runtime->hw.rate_min = chip->board.dig_rate;
runtime->hw.rate_max = chip->board.dig_rate;
return 0;
}
static int snd_bt87x_set_analog_hw(struct snd_bt87x *chip, struct snd_pcm_runtime *runtime)
{
static struct snd_ratnum analog_clock = {
.num = ANALOG_CLOCK,
.den_min = CLOCK_DIV_MIN,
.den_max = CLOCK_DIV_MAX,
.den_step = 1
};
static struct snd_pcm_hw_constraint_ratnums constraint_rates = {
.nrats = 1,
.rats = &analog_clock
};
chip->reg_control &= ~(CTL_DA_IOM_DA | CTL_A_PWRDN);
runtime->hw = snd_bt87x_analog_hw;
return snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraint_rates);
}
static int snd_bt87x_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
if (test_and_set_bit(0, &chip->opened))
return -EBUSY;
if (substream->pcm->device == DEVICE_DIGITAL)
err = snd_bt87x_set_digital_hw(chip, runtime);
else
err = snd_bt87x_set_analog_hw(chip, runtime);
if (err < 0)
goto _error;
err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
if (err < 0)
goto _error;
chip->substream = substream;
return 0;
_error:
clear_bit(0, &chip->opened);
smp_mb__after_atomic();
return err;
}
static int snd_bt87x_close(struct snd_pcm_substream *substream)
{
struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
spin_lock_irq(&chip->reg_lock);
chip->reg_control |= CTL_A_PWRDN;
snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
spin_unlock_irq(&chip->reg_lock);
chip->substream = NULL;
clear_bit(0, &chip->opened);
smp_mb__after_atomic();
return 0;
}
static int snd_bt87x_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
int err;
err = snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params));
if (err < 0)
return err;
return snd_bt87x_create_risc(chip, substream,
params_periods(hw_params),
params_period_bytes(hw_params));
}
static int snd_bt87x_hw_free(struct snd_pcm_substream *substream)
{
struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
snd_bt87x_free_risc(chip);
snd_pcm_lib_free_pages(substream);
return 0;
}
static int snd_bt87x_prepare(struct snd_pcm_substream *substream)
{
struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int decimation;
spin_lock_irq(&chip->reg_lock);
chip->reg_control &= ~(CTL_DA_SDR_MASK | CTL_DA_SBR);
decimation = (ANALOG_CLOCK + runtime->rate / 4) / runtime->rate;
chip->reg_control |= decimation << CTL_DA_SDR_SHIFT;
if (runtime->format == SNDRV_PCM_FORMAT_S8)
chip->reg_control |= CTL_DA_SBR;
snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
spin_unlock_irq(&chip->reg_lock);
return 0;
}
static int snd_bt87x_start(struct snd_bt87x *chip)
{
spin_lock(&chip->reg_lock);
chip->current_line = 0;
chip->reg_control |= CTL_FIFO_ENABLE | CTL_RISC_ENABLE | CTL_ACAP_EN;
snd_bt87x_writel(chip, REG_RISC_STRT_ADD, chip->dma_risc.addr);
snd_bt87x_writel(chip, REG_PACKET_LEN,
chip->line_bytes | (chip->lines << 16));
snd_bt87x_writel(chip, REG_INT_MASK, chip->interrupt_mask);
snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
spin_unlock(&chip->reg_lock);
return 0;
}
static int snd_bt87x_stop(struct snd_bt87x *chip)
{
spin_lock(&chip->reg_lock);
chip->reg_control &= ~(CTL_FIFO_ENABLE | CTL_RISC_ENABLE | CTL_ACAP_EN);
snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
snd_bt87x_writel(chip, REG_INT_MASK, 0);
snd_bt87x_writel(chip, REG_INT_STAT, MY_INTERRUPTS);
spin_unlock(&chip->reg_lock);
return 0;
}
static int snd_bt87x_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
return snd_bt87x_start(chip);
case SNDRV_PCM_TRIGGER_STOP:
return snd_bt87x_stop(chip);
default:
return -EINVAL;
}
}
static snd_pcm_uframes_t snd_bt87x_pointer(struct snd_pcm_substream *substream)
{
struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
return (snd_pcm_uframes_t)bytes_to_frames(runtime, chip->current_line * chip->line_bytes);
}
static struct snd_pcm_ops snd_bt87x_pcm_ops = {
.open = snd_bt87x_pcm_open,
.close = snd_bt87x_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_bt87x_hw_params,
.hw_free = snd_bt87x_hw_free,
.prepare = snd_bt87x_prepare,
.trigger = snd_bt87x_trigger,
.pointer = snd_bt87x_pointer,
.page = snd_pcm_sgbuf_ops_page,
};
static int snd_bt87x_capture_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *info)
{
info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
info->count = 1;
info->value.integer.min = 0;
info->value.integer.max = 15;
return 0;
}
static int snd_bt87x_capture_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *value)
{
struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
value->value.integer.value[0] = (chip->reg_control & CTL_A_GAIN_MASK) >> CTL_A_GAIN_SHIFT;
return 0;
}
static int snd_bt87x_capture_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *value)
{
struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
u32 old_control;
int changed;
spin_lock_irq(&chip->reg_lock);
old_control = chip->reg_control;
chip->reg_control = (chip->reg_control & ~CTL_A_GAIN_MASK)
| (value->value.integer.value[0] << CTL_A_GAIN_SHIFT);
snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
changed = old_control != chip->reg_control;
spin_unlock_irq(&chip->reg_lock);
return changed;
}
static struct snd_kcontrol_new snd_bt87x_capture_volume = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Volume",
.info = snd_bt87x_capture_volume_info,
.get = snd_bt87x_capture_volume_get,
.put = snd_bt87x_capture_volume_put,
};
#define snd_bt87x_capture_boost_info snd_ctl_boolean_mono_info
static int snd_bt87x_capture_boost_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *value)
{
struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
value->value.integer.value[0] = !! (chip->reg_control & CTL_A_G2X);
return 0;
}
static int snd_bt87x_capture_boost_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *value)
{
struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
u32 old_control;
int changed;
spin_lock_irq(&chip->reg_lock);
old_control = chip->reg_control;
chip->reg_control = (chip->reg_control & ~CTL_A_G2X)
| (value->value.integer.value[0] ? CTL_A_G2X : 0);
snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
changed = chip->reg_control != old_control;
spin_unlock_irq(&chip->reg_lock);
return changed;
}
static struct snd_kcontrol_new snd_bt87x_capture_boost = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Boost",
.info = snd_bt87x_capture_boost_info,
.get = snd_bt87x_capture_boost_get,
.put = snd_bt87x_capture_boost_put,
};
static int snd_bt87x_capture_source_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *info)
{
static const char *const texts[3] = {"TV Tuner", "FM", "Mic/Line"};
return snd_ctl_enum_info(info, 1, 3, texts);
}
static int snd_bt87x_capture_source_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *value)
{
struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
value->value.enumerated.item[0] = (chip->reg_control & CTL_A_SEL_MASK) >> CTL_A_SEL_SHIFT;
return 0;
}
static int snd_bt87x_capture_source_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *value)
{
struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
u32 old_control;
int changed;
spin_lock_irq(&chip->reg_lock);
old_control = chip->reg_control;
chip->reg_control = (chip->reg_control & ~CTL_A_SEL_MASK)
| (value->value.enumerated.item[0] << CTL_A_SEL_SHIFT);
snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
changed = chip->reg_control != old_control;
spin_unlock_irq(&chip->reg_lock);
return changed;
}
static struct snd_kcontrol_new snd_bt87x_capture_source = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Source",
.info = snd_bt87x_capture_source_info,
.get = snd_bt87x_capture_source_get,
.put = snd_bt87x_capture_source_put,
};
static int snd_bt87x_free(struct snd_bt87x *chip)
{
if (chip->mmio)
snd_bt87x_stop(chip);
if (chip->irq >= 0)
free_irq(chip->irq, chip);
if (chip->mmio)
iounmap(chip->mmio);
pci_release_regions(chip->pci);
pci_disable_device(chip->pci);
kfree(chip);
return 0;
}
static int snd_bt87x_dev_free(struct snd_device *device)
{
struct snd_bt87x *chip = device->device_data;
return snd_bt87x_free(chip);
}
static int snd_bt87x_pcm(struct snd_bt87x *chip, int device, char *name)
{
int err;
struct snd_pcm *pcm;
err = snd_pcm_new(chip->card, name, device, 0, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = chip;
strcpy(pcm->name, name);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_bt87x_pcm_ops);
return snd_pcm_lib_preallocate_pages_for_all(pcm,
SNDRV_DMA_TYPE_DEV_SG,
snd_dma_pci_data(chip->pci),
128 * 1024,
ALIGN(255 * 4092, 1024));
}
static int snd_bt87x_create(struct snd_card *card,
struct pci_dev *pci,
struct snd_bt87x **rchip)
{
struct snd_bt87x *chip;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_bt87x_dev_free
};
*rchip = NULL;
err = pci_enable_device(pci);
if (err < 0)
return err;
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
if (!chip) {
pci_disable_device(pci);
return -ENOMEM;
}
chip->card = card;
chip->pci = pci;
chip->irq = -1;
spin_lock_init(&chip->reg_lock);
if ((err = pci_request_regions(pci, "Bt87x audio")) < 0) {
kfree(chip);
pci_disable_device(pci);
return err;
}
chip->mmio = pci_ioremap_bar(pci, 0);
if (!chip->mmio) {
dev_err(card->dev, "cannot remap io memory\n");
err = -ENOMEM;
goto fail;
}
chip->reg_control = CTL_A_PWRDN | CTL_DA_ES2 |
CTL_PKTP_16 | (15 << CTL_DA_SDR_SHIFT);
chip->interrupt_mask = MY_INTERRUPTS;
snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
snd_bt87x_writel(chip, REG_INT_MASK, 0);
snd_bt87x_writel(chip, REG_INT_STAT, MY_INTERRUPTS);
err = request_irq(pci->irq, snd_bt87x_interrupt, IRQF_SHARED,
KBUILD_MODNAME, chip);
if (err < 0) {
dev_err(card->dev, "cannot grab irq %d\n", pci->irq);
goto fail;
}
chip->irq = pci->irq;
pci_set_master(pci);
synchronize_irq(chip->irq);
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
if (err < 0)
goto fail;
*rchip = chip;
return 0;
fail:
snd_bt87x_free(chip);
return err;
}
#define BT_DEVICE(chip, subvend, subdev, id) \
{ .vendor = PCI_VENDOR_ID_BROOKTREE, \
.device = chip, \
.subvendor = subvend, .subdevice = subdev, \
.driver_data = SND_BT87X_BOARD_ ## id }
/* driver_data is the card id for that device */
static const struct pci_device_id snd_bt87x_ids[] = {
/* Hauppauge WinTV series */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x0070, 0x13eb, GENERIC),
/* Hauppauge WinTV series */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_879, 0x0070, 0x13eb, GENERIC),
/* Viewcast Osprey 200 */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x0070, 0xff01, OSPREY2x0),
/* Viewcast Osprey 440 (rate is configurable via gpio) */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x0070, 0xff07, OSPREY440),
/* ATI TV-Wonder */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x1002, 0x0001, GENERIC),
/* Leadtek Winfast tv 2000xp delux */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x107d, 0x6606, GENERIC),
/* Pinnacle PCTV */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x11bd, 0x0012, GENERIC),
/* Voodoo TV 200 */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x121a, 0x3000, GENERIC),
/* Askey Computer Corp. MagicTView'99 */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x144f, 0x3000, GENERIC),
/* AVerMedia Studio No. 103, 203, ...? */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x1461, 0x0003, AVPHONE98),
/* Prolink PixelView PV-M4900 */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x1554, 0x4011, GENERIC),
/* Pinnacle Studio PCTV rave */
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0xbd11, 0x1200, GENERIC),
{ }
};
MODULE_DEVICE_TABLE(pci, snd_bt87x_ids);
/* cards known not to have audio
* (DVB cards use the audio function to transfer MPEG data) */
static struct {
unsigned short subvendor, subdevice;
} blacklist[] = {
{0x0071, 0x0101}, /* Nebula Electronics DigiTV */
{0x11bd, 0x001c}, /* Pinnacle PCTV Sat */
{0x11bd, 0x0026}, /* Pinnacle PCTV SAT CI */
{0x1461, 0x0761}, /* AVermedia AverTV DVB-T */
{0x1461, 0x0771}, /* AVermedia DVB-T 771 */
{0x1822, 0x0001}, /* Twinhan VisionPlus DVB-T */
{0x18ac, 0xd500}, /* DVICO FusionHDTV 5 Lite */
{0x18ac, 0xdb10}, /* DVICO FusionHDTV DVB-T Lite */
{0x18ac, 0xdb11}, /* Ultraview DVB-T Lite */
{0x270f, 0xfc00}, /* Chaintech Digitop DST-1000 DVB-S */
{0x7063, 0x2000}, /* pcHDTV HD-2000 TV */
};
static struct pci_driver driver;
/* return the id of the card, or a negative value if it's blacklisted */
static int snd_bt87x_detect_card(struct pci_dev *pci)
{
int i;
const struct pci_device_id *supported;
supported = pci_match_id(snd_bt87x_ids, pci);
if (supported && supported->driver_data > 0)
return supported->driver_data;
for (i = 0; i < ARRAY_SIZE(blacklist); ++i)
if (blacklist[i].subvendor == pci->subsystem_vendor &&
blacklist[i].subdevice == pci->subsystem_device) {
dev_dbg(&pci->dev,
"card %#04x-%#04x:%#04x has no audio\n",
pci->device, pci->subsystem_vendor, pci->subsystem_device);
return -EBUSY;
}
dev_info(&pci->dev, "unknown card %#04x-%#04x:%#04x\n",
pci->device, pci->subsystem_vendor, pci->subsystem_device);
dev_info(&pci->dev, "please mail id, board name, and, "
"if it works, the correct digital_rate option to "
"<alsa-devel@alsa-project.org>\n");
return SND_BT87X_BOARD_UNKNOWN;
}
static int snd_bt87x_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
struct snd_card *card;
struct snd_bt87x *chip;
int err;
enum snd_bt87x_boardid boardid;
if (!pci_id->driver_data) {
err = snd_bt87x_detect_card(pci);
if (err < 0)
return -ENODEV;
boardid = err;
} else
boardid = pci_id->driver_data;
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
++dev;
return -ENOENT;
}
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0)
return err;
err = snd_bt87x_create(card, pci, &chip);
if (err < 0)
goto _error;
memcpy(&chip->board, &snd_bt87x_boards[boardid], sizeof(chip->board));
if (!chip->board.no_digital) {
if (digital_rate[dev] > 0)
chip->board.dig_rate = digital_rate[dev];
chip->reg_control |= chip->board.digital_fmt;
err = snd_bt87x_pcm(chip, DEVICE_DIGITAL, "Bt87x Digital");
if (err < 0)
goto _error;
}
if (!chip->board.no_analog) {
err = snd_bt87x_pcm(chip, DEVICE_ANALOG, "Bt87x Analog");
if (err < 0)
goto _error;
err = snd_ctl_add(card, snd_ctl_new1(
&snd_bt87x_capture_volume, chip));
if (err < 0)
goto _error;
err = snd_ctl_add(card, snd_ctl_new1(
&snd_bt87x_capture_boost, chip));
if (err < 0)
goto _error;
err = snd_ctl_add(card, snd_ctl_new1(
&snd_bt87x_capture_source, chip));
if (err < 0)
goto _error;
}
dev_info(card->dev, "bt87x%d: Using board %d, %sanalog, %sdigital "
"(rate %d Hz)\n", dev, boardid,
chip->board.no_analog ? "no " : "",
chip->board.no_digital ? "no " : "", chip->board.dig_rate);
strcpy(card->driver, "Bt87x");
sprintf(card->shortname, "Brooktree Bt%x", pci->device);
sprintf(card->longname, "%s at %#llx, irq %i",
card->shortname, (unsigned long long)pci_resource_start(pci, 0),
chip->irq);
strcpy(card->mixername, "Bt87x");
err = snd_card_register(card);
if (err < 0)
goto _error;
pci_set_drvdata(pci, card);
++dev;
return 0;
_error:
snd_card_free(card);
return err;
}
static void snd_bt87x_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
}
/* default entries for all Bt87x cards - it's not exported */
/* driver_data is set to 0 to call detection */
static const struct pci_device_id snd_bt87x_default_ids[] = {
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, PCI_ANY_ID, PCI_ANY_ID, UNKNOWN),
BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_879, PCI_ANY_ID, PCI_ANY_ID, UNKNOWN),
{ }
};
static struct pci_driver driver = {
.name = KBUILD_MODNAME,
.id_table = snd_bt87x_ids,
.probe = snd_bt87x_probe,
.remove = snd_bt87x_remove,
};
static int __init alsa_card_bt87x_init(void)
{
if (load_all)
driver.id_table = snd_bt87x_default_ids;
return pci_register_driver(&driver);
}
static void __exit alsa_card_bt87x_exit(void)
{
pci_unregister_driver(&driver);
}
module_init(alsa_card_bt87x_init)
module_exit(alsa_card_bt87x_exit)

View file

@ -0,0 +1,3 @@
snd-ca0106-objs := ca0106_main.o ca0106_proc.o ca0106_mixer.o ca_midi.o
obj-$(CONFIG_SND_CA0106) += snd-ca0106.o

742
sound/pci/ca0106/ca0106.h Normal file
View file

@ -0,0 +1,742 @@
/*
* Copyright (c) 2004 James Courtier-Dutton <James@superbug.demon.co.uk>
* Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit
* Version: 0.0.22
*
* FEATURES currently supported:
* See ca0106_main.c for features.
*
* Changelog:
* Support interrupts per period.
* Removed noise from Center/LFE channel when in Analog mode.
* Rename and remove mixer controls.
* 0.0.6
* Use separate card based DMA buffer for periods table list.
* 0.0.7
* Change remove and rename ctrls into lists.
* 0.0.8
* Try to fix capture sources.
* 0.0.9
* Fix AC3 output.
* Enable S32_LE format support.
* 0.0.10
* Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".)
* 0.0.11
* Add Model name recognition.
* 0.0.12
* Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period.
* Remove redundent "voice" handling.
* 0.0.13
* Single trigger call for multi channels.
* 0.0.14
* Set limits based on what the sound card hardware can do.
* playback periods_min=2, periods_max=8
* capture hw constraints require period_size = n * 64 bytes.
* playback hw constraints require period_size = n * 64 bytes.
* 0.0.15
* Separated ca0106.c into separate functional .c files.
* 0.0.16
* Implement 192000 sample rate.
* 0.0.17
* Add support for SB0410 and SB0413.
* 0.0.18
* Modified Copyright message.
* 0.0.19
* Added I2C and SPI registers. Filled in interrupt enable.
* 0.0.20
* Added GPIO info for SB Live 24bit.
* 0.0.21
* Implement support for Line-in capture on SB Live 24bit.
* 0.0.22
* Add support for mute control on SB Live 24bit (cards w/ SPI DAC)
*
*
* This code was initially based on code from ALSA's emu10k1x.c which is:
* Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/************************************************************************************************/
/* PCI function 0 registers, address = <val> + PCIBASE0 */
/************************************************************************************************/
#define PTR 0x00 /* Indexed register set pointer register */
/* NOTE: The CHANNELNUM and ADDRESS words can */
/* be modified independently of each other. */
/* CNL[1:0], ADDR[27:16] */
#define DATA 0x04 /* Indexed register set data register */
/* DATA[31:0] */
#define IPR 0x08 /* Global interrupt pending register */
/* Clear pending interrupts by writing a 1 to */
/* the relevant bits and zero to the other bits */
#define IPR_MIDI_RX_B 0x00020000 /* MIDI UART-B Receive buffer non-empty */
#define IPR_MIDI_TX_B 0x00010000 /* MIDI UART-B Transmit buffer empty */
#define IPR_SPDIF_IN_USER 0x00004000 /* SPDIF input user data has 16 more bits */
#define IPR_SPDIF_OUT_USER 0x00002000 /* SPDIF output user data needs 16 more bits */
#define IPR_SPDIF_OUT_FRAME 0x00001000 /* SPDIF frame about to start */
#define IPR_SPI 0x00000800 /* SPI transaction completed */
#define IPR_I2C_EEPROM 0x00000400 /* I2C EEPROM transaction completed */
#define IPR_I2C_DAC 0x00000200 /* I2C DAC transaction completed */
#define IPR_AI 0x00000100 /* Audio pending register changed. See PTR reg 0x76 */
#define IPR_GPI 0x00000080 /* General Purpose input changed */
#define IPR_SRC_LOCKED 0x00000040 /* SRC lock status changed */
#define IPR_SPDIF_STATUS 0x00000020 /* SPDIF status changed */
#define IPR_TIMER2 0x00000010 /* 192000Hz Timer */
#define IPR_TIMER1 0x00000008 /* 44100Hz Timer */
#define IPR_MIDI_RX_A 0x00000004 /* MIDI UART-A Receive buffer non-empty */
#define IPR_MIDI_TX_A 0x00000002 /* MIDI UART-A Transmit buffer empty */
#define IPR_PCI 0x00000001 /* PCI Bus error */
#define INTE 0x0c /* Interrupt enable register */
#define INTE_MIDI_RX_B 0x00020000 /* MIDI UART-B Receive buffer non-empty */
#define INTE_MIDI_TX_B 0x00010000 /* MIDI UART-B Transmit buffer empty */
#define INTE_SPDIF_IN_USER 0x00004000 /* SPDIF input user data has 16 more bits */
#define INTE_SPDIF_OUT_USER 0x00002000 /* SPDIF output user data needs 16 more bits */
#define INTE_SPDIF_OUT_FRAME 0x00001000 /* SPDIF frame about to start */
#define INTE_SPI 0x00000800 /* SPI transaction completed */
#define INTE_I2C_EEPROM 0x00000400 /* I2C EEPROM transaction completed */
#define INTE_I2C_DAC 0x00000200 /* I2C DAC transaction completed */
#define INTE_AI 0x00000100 /* Audio pending register changed. See PTR reg 0x75 */
#define INTE_GPI 0x00000080 /* General Purpose input changed */
#define INTE_SRC_LOCKED 0x00000040 /* SRC lock status changed */
#define INTE_SPDIF_STATUS 0x00000020 /* SPDIF status changed */
#define INTE_TIMER2 0x00000010 /* 192000Hz Timer */
#define INTE_TIMER1 0x00000008 /* 44100Hz Timer */
#define INTE_MIDI_RX_A 0x00000004 /* MIDI UART-A Receive buffer non-empty */
#define INTE_MIDI_TX_A 0x00000002 /* MIDI UART-A Transmit buffer empty */
#define INTE_PCI 0x00000001 /* PCI Bus error */
#define UNKNOWN10 0x10 /* Unknown ??. Defaults to 0 */
#define HCFG 0x14 /* Hardware config register */
/* 0x1000 causes AC3 to fails. It adds a dither bit. */
#define HCFG_STAC 0x10000000 /* Special mode for STAC9460 Codec. */
#define HCFG_CAPTURE_I2S_BYPASS 0x08000000 /* 1 = bypass I2S input async SRC. */
#define HCFG_CAPTURE_SPDIF_BYPASS 0x04000000 /* 1 = bypass SPDIF input async SRC. */
#define HCFG_PLAYBACK_I2S_BYPASS 0x02000000 /* 0 = I2S IN mixer output, 1 = I2S IN1. */
#define HCFG_FORCE_LOCK 0x01000000 /* For test only. Force input SRC tracker to lock. */
#define HCFG_PLAYBACK_ATTENUATION 0x00006000 /* Playback attenuation mask. 0 = 0dB, 1 = 6dB, 2 = 12dB, 3 = Mute. */
#define HCFG_PLAYBACK_DITHER 0x00001000 /* 1 = Add dither bit to all playback channels. */
#define HCFG_PLAYBACK_S32_LE 0x00000800 /* 1 = S32_LE, 0 = S16_LE */
#define HCFG_CAPTURE_S32_LE 0x00000400 /* 1 = S32_LE, 0 = S16_LE (S32_LE current not working) */
#define HCFG_8_CHANNEL_PLAY 0x00000200 /* 1 = 8 channels, 0 = 2 channels per substream.*/
#define HCFG_8_CHANNEL_CAPTURE 0x00000100 /* 1 = 8 channels, 0 = 2 channels per substream.*/
#define HCFG_MONO 0x00000080 /* 1 = I2S Input mono */
#define HCFG_I2S_OUTPUT 0x00000010 /* 1 = I2S Output disabled */
#define HCFG_AC97 0x00000008 /* 0 = AC97 1.0, 1 = AC97 2.0 */
#define HCFG_LOCK_PLAYBACK_CACHE 0x00000004 /* 1 = Cancel bustmaster accesses to soundcache */
/* NOTE: This should generally never be used. */
#define HCFG_LOCK_CAPTURE_CACHE 0x00000002 /* 1 = Cancel bustmaster accesses to soundcache */
/* NOTE: This should generally never be used. */
#define HCFG_AUDIOENABLE 0x00000001 /* 0 = CODECs transmit zero-valued samples */
/* Should be set to 1 when the EMU10K1 is */
/* completely initialized. */
#define GPIO 0x18 /* Defaults: 005f03a3-Analog, 005f02a2-SPDIF. */
/* Here pins 0,1,2,3,4,,6 are output. 5,7 are input */
/* For the Audigy LS, pin 0 (or bit 8) controls the SPDIF/Analog jack. */
/* SB Live 24bit:
* bit 8 0 = SPDIF in and out / 1 = Analog (Mic or Line)-in.
* bit 9 0 = Mute / 1 = Analog out.
* bit 10 0 = Line-in / 1 = Mic-in.
* bit 11 0 = ? / 1 = ?
* bit 12 0 = 48 Khz / 1 = 96 Khz Analog out on SB Live 24bit.
* bit 13 0 = ? / 1 = ?
* bit 14 0 = Mute / 1 = Analog out
* bit 15 0 = ? / 1 = ?
* Both bit 9 and bit 14 have to be set for analog sound to work on the SB Live 24bit.
*/
/* 8 general purpose programmable In/Out pins.
* GPI [8:0] Read only. Default 0.
* GPO [15:8] Default 0x9. (Default to SPDIF jack enabled for SPDIF)
* GPO Enable [23:16] Default 0x0f. Setting a bit to 1, causes the pin to be an output pin.
*/
#define AC97DATA 0x1c /* AC97 register set data register (16 bit) */
#define AC97ADDRESS 0x1e /* AC97 register set address register (8 bit) */
/********************************************************************************************************/
/* CA0106 pointer-offset register set, accessed through the PTR and DATA registers */
/********************************************************************************************************/
/* Initially all registers from 0x00 to 0x3f have zero contents. */
#define PLAYBACK_LIST_ADDR 0x00 /* Base DMA address of a list of pointers to each period/size */
/* One list entry: 4 bytes for DMA address,
* 4 bytes for period_size << 16.
* One list entry is 8 bytes long.
* One list entry for each period in the buffer.
*/
/* ADDR[31:0], Default: 0x0 */
#define PLAYBACK_LIST_SIZE 0x01 /* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000 */
/* SIZE[21:16], Default: 0x8 */
#define PLAYBACK_LIST_PTR 0x02 /* Pointer to the current period being played */
/* PTR[5:0], Default: 0x0 */
#define PLAYBACK_UNKNOWN3 0x03 /* Not used ?? */
#define PLAYBACK_DMA_ADDR 0x04 /* Playback DMA address */
/* DMA[31:0], Default: 0x0 */
#define PLAYBACK_PERIOD_SIZE 0x05 /* Playback period size. win2000 uses 0x04000000 */
/* SIZE[31:16], Default: 0x0 */
#define PLAYBACK_POINTER 0x06 /* Playback period pointer. Used with PLAYBACK_LIST_PTR to determine buffer position currently in DAC */
/* POINTER[15:0], Default: 0x0 */
#define PLAYBACK_PERIOD_END_ADDR 0x07 /* Playback fifo end address */
/* END_ADDR[15:0], FLAG[16] 0 = don't stop, 1 = stop */
#define PLAYBACK_FIFO_OFFSET_ADDRESS 0x08 /* Current fifo offset address [21:16] */
/* Cache size valid [5:0] */
#define PLAYBACK_UNKNOWN9 0x09 /* 0x9 to 0xf Unused */
#define CAPTURE_DMA_ADDR 0x10 /* Capture DMA address */
/* DMA[31:0], Default: 0x0 */
#define CAPTURE_BUFFER_SIZE 0x11 /* Capture buffer size */
/* SIZE[31:16], Default: 0x0 */
#define CAPTURE_POINTER 0x12 /* Capture buffer pointer. Sample currently in ADC */
/* POINTER[15:0], Default: 0x0 */
#define CAPTURE_FIFO_OFFSET_ADDRESS 0x13 /* Current fifo offset address [21:16] */
/* Cache size valid [5:0] */
#define PLAYBACK_LAST_SAMPLE 0x20 /* The sample currently being played */
/* 0x21 - 0x3f unused */
#define BASIC_INTERRUPT 0x40 /* Used by both playback and capture interrupt handler */
/* Playback (0x1<<channel_id) */
/* Capture (0x100<<channel_id) */
/* Playback sample rate 96000 = 0x20000 */
/* Start Playback [3:0] (one bit per channel)
* Start Capture [11:8] (one bit per channel)
* Playback rate [23:16] (2 bits per channel) (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz)
* Playback mixer in enable [27:24] (one bit per channel)
* Playback mixer out enable [31:28] (one bit per channel)
*/
/* The Digital out jack is shared with the Center/LFE Analogue output.
* The jack has 4 poles. I will call 1 - Tip, 2 - Next to 1, 3 - Next to 2, 4 - Next to 3
* For Analogue: 1 -> Center Speaker, 2 -> Sub Woofer, 3 -> Ground, 4 -> Ground
* For Digital: 1 -> Front SPDIF, 2 -> Rear SPDIF, 3 -> Center/Subwoofer SPDIF, 4 -> Ground.
* Standard 4 pole Video A/V cable with RCA outputs: 1 -> White, 2 -> Yellow, 3 -> Shield on all three, 4 -> Red.
* So, from this you can see that you cannot use a Standard 4 pole Video A/V cable with the SB Audigy LS card.
*/
/* The Front SPDIF PCM gets mixed with samples from the AC97 codec, so can only work for Stereo PCM and not AC3/DTS
* The Rear SPDIF can be used for Stereo PCM and also AC3/DTS
* The Center/LFE SPDIF cannot be used for AC3/DTS, but can be used for Stereo PCM.
* Summary: For ALSA we use the Rear channel for SPDIF Digital AC3/DTS output
*/
/* A standard 2 pole mono mini-jack to RCA plug can be used for SPDIF Stereo PCM output from the Front channel.
* A standard 3 pole stereo mini-jack to 2 RCA plugs can be used for SPDIF AC3/DTS and Stereo PCM output utilising the Rear channel and just one of the RCA plugs.
*/
#define SPCS0 0x41 /* SPDIF output Channel Status 0 register. For Rear. default=0x02108004, non-audio=0x02108006 */
#define SPCS1 0x42 /* SPDIF output Channel Status 1 register. For Front */
#define SPCS2 0x43 /* SPDIF output Channel Status 2 register. For Center/LFE */
#define SPCS3 0x44 /* SPDIF output Channel Status 3 register. Unknown */
/* When Channel set to 0: */
#define SPCS_CLKACCYMASK 0x30000000 /* Clock accuracy */
#define SPCS_CLKACCY_1000PPM 0x00000000 /* 1000 parts per million */
#define SPCS_CLKACCY_50PPM 0x10000000 /* 50 parts per million */
#define SPCS_CLKACCY_VARIABLE 0x20000000 /* Variable accuracy */
#define SPCS_SAMPLERATEMASK 0x0f000000 /* Sample rate */
#define SPCS_SAMPLERATE_44 0x00000000 /* 44.1kHz sample rate */
#define SPCS_SAMPLERATE_48 0x02000000 /* 48kHz sample rate */
#define SPCS_SAMPLERATE_32 0x03000000 /* 32kHz sample rate */
#define SPCS_CHANNELNUMMASK 0x00f00000 /* Channel number */
#define SPCS_CHANNELNUM_UNSPEC 0x00000000 /* Unspecified channel number */
#define SPCS_CHANNELNUM_LEFT 0x00100000 /* Left channel */
#define SPCS_CHANNELNUM_RIGHT 0x00200000 /* Right channel */
#define SPCS_SOURCENUMMASK 0x000f0000 /* Source number */
#define SPCS_SOURCENUM_UNSPEC 0x00000000 /* Unspecified source number */
#define SPCS_GENERATIONSTATUS 0x00008000 /* Originality flag (see IEC-958 spec) */
#define SPCS_CATEGORYCODEMASK 0x00007f00 /* Category code (see IEC-958 spec) */
#define SPCS_MODEMASK 0x000000c0 /* Mode (see IEC-958 spec) */
#define SPCS_EMPHASISMASK 0x00000038 /* Emphasis */
#define SPCS_EMPHASIS_NONE 0x00000000 /* No emphasis */
#define SPCS_EMPHASIS_50_15 0x00000008 /* 50/15 usec 2 channel */
#define SPCS_COPYRIGHT 0x00000004 /* Copyright asserted flag -- do not modify */
#define SPCS_NOTAUDIODATA 0x00000002 /* 0 = Digital audio, 1 = not audio */
#define SPCS_PROFESSIONAL 0x00000001 /* 0 = Consumer (IEC-958), 1 = pro (AES3-1992) */
/* When Channel set to 1: */
#define SPCS_WORD_LENGTH_MASK 0x0000000f /* Word Length Mask */
#define SPCS_WORD_LENGTH_16 0x00000008 /* Word Length 16 bit */
#define SPCS_WORD_LENGTH_17 0x00000006 /* Word Length 17 bit */
#define SPCS_WORD_LENGTH_18 0x00000004 /* Word Length 18 bit */
#define SPCS_WORD_LENGTH_19 0x00000002 /* Word Length 19 bit */
#define SPCS_WORD_LENGTH_20A 0x0000000a /* Word Length 20 bit */
#define SPCS_WORD_LENGTH_20 0x00000009 /* Word Length 20 bit (both 0xa and 0x9 are 20 bit) */
#define SPCS_WORD_LENGTH_21 0x00000007 /* Word Length 21 bit */
#define SPCS_WORD_LENGTH_22 0x00000005 /* Word Length 22 bit */
#define SPCS_WORD_LENGTH_23 0x00000003 /* Word Length 23 bit */
#define SPCS_WORD_LENGTH_24 0x0000000b /* Word Length 24 bit */
#define SPCS_ORIGINAL_SAMPLE_RATE_MASK 0x000000f0 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_NONE 0x00000000 /* Original Sample rate not indicated */
#define SPCS_ORIGINAL_SAMPLE_RATE_16000 0x00000010 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_RES1 0x00000020 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_32000 0x00000030 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_12000 0x00000040 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_11025 0x00000050 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_8000 0x00000060 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_RES2 0x00000070 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_192000 0x00000080 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_24000 0x00000090 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_96000 0x000000a0 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_48000 0x000000b0 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_176400 0x000000c0 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_22050 0x000000d0 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_88200 0x000000e0 /* Original Sample rate */
#define SPCS_ORIGINAL_SAMPLE_RATE_44100 0x000000f0 /* Original Sample rate */
#define SPDIF_SELECT1 0x45 /* Enables SPDIF or Analogue outputs 0-SPDIF, 0xf00-Analogue */
/* 0x100 - Front, 0x800 - Rear, 0x200 - Center/LFE.
* But as the jack is shared, use 0xf00.
* The Windows2000 driver uses 0x0000000f for both digital and analog.
* 0xf00 introduces interesting noises onto the Center/LFE.
* If you turn the volume up, you hear computer noise,
* e.g. mouse moving, changing between app windows etc.
* So, I am going to set this to 0x0000000f all the time now,
* same as the windows driver does.
* Use register SPDIF_SELECT2(0x72) to switch between SPDIF and Analog.
*/
/* When Channel = 0:
* Wide SPDIF format [3:0] (one bit for each channel) (0=20bit, 1=24bit)
* Tristate SPDIF Output [11:8] (one bit for each channel) (0=Not tristate, 1=Tristate)
* SPDIF Bypass enable [19:16] (one bit for each channel) (0=Not bypass, 1=Bypass)
*/
/* When Channel = 1:
* SPDIF 0 User data [7:0]
* SPDIF 1 User data [15:8]
* SPDIF 0 User data [23:16]
* SPDIF 0 User data [31:24]
* User data can be sent by using the SPDIF output frame pending and SPDIF output user bit interrupts.
*/
#define WATERMARK 0x46 /* Test bit to indicate cache usage level */
#define SPDIF_INPUT_STATUS 0x49 /* SPDIF Input status register. Bits the same as SPCS.
* When Channel = 0: Bits the same as SPCS channel 0.
* When Channel = 1: Bits the same as SPCS channel 1.
* When Channel = 2:
* SPDIF Input User data [16:0]
* SPDIF Input Frame count [21:16]
*/
#define CAPTURE_CACHE_DATA 0x50 /* 0x50-0x5f Recorded samples. */
#define CAPTURE_SOURCE 0x60 /* Capture Source 0 = MIC */
#define CAPTURE_SOURCE_CHANNEL0 0xf0000000 /* Mask for selecting the Capture sources */
#define CAPTURE_SOURCE_CHANNEL1 0x0f000000 /* 0 - SPDIF mixer output. */
#define CAPTURE_SOURCE_CHANNEL2 0x00f00000 /* 1 - What you hear or . 2 - ?? */
#define CAPTURE_SOURCE_CHANNEL3 0x000f0000 /* 3 - Mic in, Line in, TAD in, Aux in. */
#define CAPTURE_SOURCE_RECORD_MAP 0x0000ffff /* Default 0x00e4 */
/* Record Map [7:0] (2 bits per channel) 0=mapped to channel 0, 1=mapped to channel 1, 2=mapped to channel2, 3=mapped to channel3
* Record source select for channel 0 [18:16]
* Record source select for channel 1 [22:20]
* Record source select for channel 2 [26:24]
* Record source select for channel 3 [30:28]
* 0 - SPDIF mixer output.
* 1 - i2s mixer output.
* 2 - SPDIF input.
* 3 - i2s input.
* 4 - AC97 capture.
* 5 - SRC output.
*/
#define CAPTURE_VOLUME1 0x61 /* Capture volume per channel 0-3 */
#define CAPTURE_VOLUME2 0x62 /* Capture volume per channel 4-7 */
#define PLAYBACK_ROUTING1 0x63 /* Playback routing of channels 0-7. Effects AC3 output. Default 0x32765410 */
#define ROUTING1_REAR 0x77000000 /* Channel_id 0 sends to 10, Channel_id 1 sends to 32 */
#define ROUTING1_NULL 0x00770000 /* Channel_id 2 sends to 54, Channel_id 3 sends to 76 */
#define ROUTING1_CENTER_LFE 0x00007700 /* 0x32765410 means, send Channel_id 0 to FRONT, Channel_id 1 to REAR */
#define ROUTING1_FRONT 0x00000077 /* Channel_id 2 to CENTER_LFE, Channel_id 3 to NULL. */
/* Channel_id's handle stereo channels. Channel X is a single mono channel */
/* Host is input from the PCI bus. */
/* Host channel 0 [2:0] -> SPDIF Mixer/Router channel 0-7.
* Host channel 1 [6:4] -> SPDIF Mixer/Router channel 0-7.
* Host channel 2 [10:8] -> SPDIF Mixer/Router channel 0-7.
* Host channel 3 [14:12] -> SPDIF Mixer/Router channel 0-7.
* Host channel 4 [18:16] -> SPDIF Mixer/Router channel 0-7.
* Host channel 5 [22:20] -> SPDIF Mixer/Router channel 0-7.
* Host channel 6 [26:24] -> SPDIF Mixer/Router channel 0-7.
* Host channel 7 [30:28] -> SPDIF Mixer/Router channel 0-7.
*/
#define PLAYBACK_ROUTING2 0x64 /* Playback Routing . Feeding Capture channels back into Playback. Effects AC3 output. Default 0x76767676 */
/* SRC is input from the capture inputs. */
/* SRC channel 0 [2:0] -> SPDIF Mixer/Router channel 0-7.
* SRC channel 1 [6:4] -> SPDIF Mixer/Router channel 0-7.
* SRC channel 2 [10:8] -> SPDIF Mixer/Router channel 0-7.
* SRC channel 3 [14:12] -> SPDIF Mixer/Router channel 0-7.
* SRC channel 4 [18:16] -> SPDIF Mixer/Router channel 0-7.
* SRC channel 5 [22:20] -> SPDIF Mixer/Router channel 0-7.
* SRC channel 6 [26:24] -> SPDIF Mixer/Router channel 0-7.
* SRC channel 7 [30:28] -> SPDIF Mixer/Router channel 0-7.
*/
#define PLAYBACK_MUTE 0x65 /* Unknown. While playing 0x0, while silent 0x00fc0000 */
/* SPDIF Mixer input control:
* Invert SRC to SPDIF Mixer [7-0] (One bit per channel)
* Invert Host to SPDIF Mixer [15:8] (One bit per channel)
* SRC to SPDIF Mixer disable [23:16] (One bit per channel)
* Host to SPDIF Mixer disable [31:24] (One bit per channel)
*/
#define PLAYBACK_VOLUME1 0x66 /* Playback SPDIF volume per channel. Set to the same PLAYBACK_VOLUME(0x6a) */
/* PLAYBACK_VOLUME1 must be set to 30303030 for SPDIF AC3 Playback */
/* SPDIF mixer input volume. 0=12dB, 0x30=0dB, 0xFE=-51.5dB, 0xff=Mute */
/* One register for each of the 4 stereo streams. */
/* SRC Right volume [7:0]
* SRC Left volume [15:8]
* Host Right volume [23:16]
* Host Left volume [31:24]
*/
#define CAPTURE_ROUTING1 0x67 /* Capture Routing. Default 0x32765410 */
/* Similar to register 0x63, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */
#define CAPTURE_ROUTING2 0x68 /* Unknown Routing. Default 0x76767676 */
/* Similar to register 0x64, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */
#define CAPTURE_MUTE 0x69 /* Unknown. While capturing 0x0, while silent 0x00fc0000 */
/* Similar to register 0x65, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */
#define PLAYBACK_VOLUME2 0x6a /* Playback Analog volume per channel. Does not effect AC3 output */
/* Similar to register 0x66, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */
#define UNKNOWN6b 0x6b /* Unknown. Readonly. Default 00400000 00400000 00400000 00400000 */
#define MIDI_UART_A_DATA 0x6c /* Midi Uart A Data */
#define MIDI_UART_A_CMD 0x6d /* Midi Uart A Command/Status */
#define MIDI_UART_B_DATA 0x6e /* Midi Uart B Data (currently unused) */
#define MIDI_UART_B_CMD 0x6f /* Midi Uart B Command/Status (currently unused) */
/* unique channel identifier for midi->channel */
#define CA0106_MIDI_CHAN_A 0x1
#define CA0106_MIDI_CHAN_B 0x2
/* from mpu401 */
#define CA0106_MIDI_INPUT_AVAIL 0x80
#define CA0106_MIDI_OUTPUT_READY 0x40
#define CA0106_MPU401_RESET 0xff
#define CA0106_MPU401_ENTER_UART 0x3f
#define CA0106_MPU401_ACK 0xfe
#define SAMPLE_RATE_TRACKER_STATUS 0x70 /* Readonly. Default 00108000 00108000 00500000 00500000 */
/* Estimated sample rate [19:0] Relative to 48kHz. 0x8000 = 1.0
* Rate Locked [20]
* SPDIF Locked [21] For SPDIF channel only.
* Valid Audio [22] For SPDIF channel only.
*/
#define CAPTURE_CONTROL 0x71 /* Some sort of routing. default = 40c81000 30303030 30300000 00700000 */
/* Channel_id 0: 0x40c81000 must be changed to 0x40c80000 for SPDIF AC3 input or output. */
/* Channel_id 1: 0xffffffff(mute) 0x30303030(max) controls CAPTURE feedback into PLAYBACK. */
/* Sample rate output control register Channel=0
* Sample output rate [1:0] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz)
* Sample input rate [3:2] (0=48kHz, 1=Not available, 2=96kHz, 3=192Khz)
* SRC input source select [4] 0=Audio from digital mixer, 1=Audio from analog source.
* Record rate [9:8] (0=48kHz, 1=Not available, 2=96kHz, 3=192Khz)
* Record mixer output enable [12:10]
* I2S input rate master mode [15:14] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz)
* I2S output rate [17:16] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz)
* I2S output source select [18] (0=Audio from host, 1=Audio from SRC)
* Record mixer I2S enable [20:19] (enable/disable i2sin1 and i2sin0)
* I2S output master clock select [21] (0=256*I2S output rate, 1=512*I2S output rate.)
* I2S input master clock select [22] (0=256*I2S input rate, 1=512*I2S input rate.)
* I2S input mode [23] (0=Slave, 1=Master)
* SPDIF output rate [25:24] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz)
* SPDIF output source select [26] (0=host, 1=SRC)
* Not used [27]
* Record Source 0 input [29:28] (0=SPDIF in, 1=I2S in, 2=AC97 Mic, 3=AC97 PCM)
* Record Source 1 input [31:30] (0=SPDIF in, 1=I2S in, 2=AC97 Mic, 3=AC97 PCM)
*/
/* Sample rate output control register Channel=1
* I2S Input 0 volume Right [7:0]
* I2S Input 0 volume Left [15:8]
* I2S Input 1 volume Right [23:16]
* I2S Input 1 volume Left [31:24]
*/
/* Sample rate output control register Channel=2
* SPDIF Input volume Right [23:16]
* SPDIF Input volume Left [31:24]
*/
/* Sample rate output control register Channel=3
* No used
*/
#define SPDIF_SELECT2 0x72 /* Some sort of routing. Channel_id 0 only. default = 0x0f0f003f. Analog 0x000b0000, Digital 0x0b000000 */
#define ROUTING2_FRONT_MASK 0x00010000 /* Enable for Front speakers. */
#define ROUTING2_CENTER_LFE_MASK 0x00020000 /* Enable for Center/LFE speakers. */
#define ROUTING2_REAR_MASK 0x00080000 /* Enable for Rear speakers. */
/* Audio output control
* AC97 output enable [5:0]
* I2S output enable [19:16]
* SPDIF output enable [27:24]
*/
#define UNKNOWN73 0x73 /* Unknown. Readonly. Default 0x0 */
#define CHIP_VERSION 0x74 /* P17 Chip version. Channel_id 0 only. Default 00000071 */
#define EXTENDED_INT_MASK 0x75 /* Used by both playback and capture interrupt handler */
/* Sets which Interrupts are enabled. */
/* 0x00000001 = Half period. Playback.
* 0x00000010 = Full period. Playback.
* 0x00000100 = Half buffer. Playback.
* 0x00001000 = Full buffer. Playback.
* 0x00010000 = Half buffer. Capture.
* 0x00100000 = Full buffer. Capture.
* Capture can only do 2 periods.
* 0x01000000 = End audio. Playback.
* 0x40000000 = Half buffer Playback,Caputre xrun.
* 0x80000000 = Full buffer Playback,Caputre xrun.
*/
#define EXTENDED_INT 0x76 /* Used by both playback and capture interrupt handler */
/* Shows which interrupts are active at the moment. */
/* Same bit layout as EXTENDED_INT_MASK */
#define COUNTER77 0x77 /* Counter range 0 to 0x3fffff, 192000 counts per second. */
#define COUNTER78 0x78 /* Counter range 0 to 0x3fffff, 44100 counts per second. */
#define EXTENDED_INT_TIMER 0x79 /* Channel_id 0 only. Used by both playback and capture interrupt handler */
/* Causes interrupts based on timer intervals. */
#define SPI 0x7a /* SPI: Serial Interface Register */
#define I2C_A 0x7b /* I2C Address. 32 bit */
#define I2C_D0 0x7c /* I2C Data Port 0. 32 bit */
#define I2C_D1 0x7d /* I2C Data Port 1. 32 bit */
//I2C values
#define I2C_A_ADC_ADD_MASK 0x000000fe //The address is a 7 bit address
#define I2C_A_ADC_RW_MASK 0x00000001 //bit mask for R/W
#define I2C_A_ADC_TRANS_MASK 0x00000010 //Bit mask for I2c address DAC value
#define I2C_A_ADC_ABORT_MASK 0x00000020 //Bit mask for I2C transaction abort flag
#define I2C_A_ADC_LAST_MASK 0x00000040 //Bit mask for Last word transaction
#define I2C_A_ADC_BYTE_MASK 0x00000080 //Bit mask for Byte Mode
#define I2C_A_ADC_ADD 0x00000034 //This is the Device address for ADC
#define I2C_A_ADC_READ 0x00000001 //To perform a read operation
#define I2C_A_ADC_START 0x00000100 //Start I2C transaction
#define I2C_A_ADC_ABORT 0x00000200 //I2C transaction abort
#define I2C_A_ADC_LAST 0x00000400 //I2C last transaction
#define I2C_A_ADC_BYTE 0x00000800 //I2C one byte mode
#define I2C_D_ADC_REG_MASK 0xfe000000 //ADC address register
#define I2C_D_ADC_DAT_MASK 0x01ff0000 //ADC data register
#define ADC_TIMEOUT 0x00000007 //ADC Timeout Clock Disable
#define ADC_IFC_CTRL 0x0000000b //ADC Interface Control
#define ADC_MASTER 0x0000000c //ADC Master Mode Control
#define ADC_POWER 0x0000000d //ADC PowerDown Control
#define ADC_ATTEN_ADCL 0x0000000e //ADC Attenuation ADCL
#define ADC_ATTEN_ADCR 0x0000000f //ADC Attenuation ADCR
#define ADC_ALC_CTRL1 0x00000010 //ADC ALC Control 1
#define ADC_ALC_CTRL2 0x00000011 //ADC ALC Control 2
#define ADC_ALC_CTRL3 0x00000012 //ADC ALC Control 3
#define ADC_NOISE_CTRL 0x00000013 //ADC Noise Gate Control
#define ADC_LIMIT_CTRL 0x00000014 //ADC Limiter Control
#define ADC_MUX 0x00000015 //ADC Mux offset
#if 0
/* FIXME: Not tested yet. */
#define ADC_GAIN_MASK 0x000000ff //Mask for ADC Gain
#define ADC_ZERODB 0x000000cf //Value to set ADC to 0dB
#define ADC_MUTE_MASK 0x000000c0 //Mask for ADC mute
#define ADC_MUTE 0x000000c0 //Value to mute ADC
#define ADC_OSR 0x00000008 //Mask for ADC oversample rate select
#define ADC_TIMEOUT_DISABLE 0x00000008 //Value and mask to disable Timeout clock
#define ADC_HPF_DISABLE 0x00000100 //Value and mask to disable High pass filter
#define ADC_TRANWIN_MASK 0x00000070 //Mask for Length of Transient Window
#endif
#define ADC_MUX_MASK 0x0000000f //Mask for ADC Mux
#define ADC_MUX_PHONE 0x00000001 //Value to select TAD at ADC Mux (Not used)
#define ADC_MUX_MIC 0x00000002 //Value to select Mic at ADC Mux
#define ADC_MUX_LINEIN 0x00000004 //Value to select LineIn at ADC Mux
#define ADC_MUX_AUX 0x00000008 //Value to select Aux at ADC Mux
#define SET_CHANNEL 0 /* Testing channel outputs 0=Front, 1=Center/LFE, 2=Unknown, 3=Rear */
#define PCM_FRONT_CHANNEL 0
#define PCM_REAR_CHANNEL 1
#define PCM_CENTER_LFE_CHANNEL 2
#define PCM_UNKNOWN_CHANNEL 3
#define CONTROL_FRONT_CHANNEL 0
#define CONTROL_REAR_CHANNEL 3
#define CONTROL_CENTER_LFE_CHANNEL 1
#define CONTROL_UNKNOWN_CHANNEL 2
/* Based on WM8768 Datasheet Rev 4.2 page 32 */
#define SPI_REG_MASK 0x1ff /* 16-bit SPI writes have a 7-bit address */
#define SPI_REG_SHIFT 9 /* followed by 9 bits of data */
#define SPI_LDA1_REG 0 /* digital attenuation */
#define SPI_RDA1_REG 1
#define SPI_LDA2_REG 4
#define SPI_RDA2_REG 5
#define SPI_LDA3_REG 6
#define SPI_RDA3_REG 7
#define SPI_LDA4_REG 13
#define SPI_RDA4_REG 14
#define SPI_MASTDA_REG 8
#define SPI_DA_BIT_UPDATE (1<<8) /* update attenuation values */
#define SPI_DA_BIT_0dB 0xff /* 0 dB */
#define SPI_DA_BIT_infdB 0x00 /* inf dB attenuation (mute) */
#define SPI_PL_REG 2
#define SPI_PL_BIT_L_M (0<<5) /* left channel = mute */
#define SPI_PL_BIT_L_L (1<<5) /* left channel = left */
#define SPI_PL_BIT_L_R (2<<5) /* left channel = right */
#define SPI_PL_BIT_L_C (3<<5) /* left channel = (L+R)/2 */
#define SPI_PL_BIT_R_M (0<<7) /* right channel = mute */
#define SPI_PL_BIT_R_L (1<<7) /* right channel = left */
#define SPI_PL_BIT_R_R (2<<7) /* right channel = right */
#define SPI_PL_BIT_R_C (3<<7) /* right channel = (L+R)/2 */
#define SPI_IZD_REG 2
#define SPI_IZD_BIT (1<<4) /* infinite zero detect */
#define SPI_FMT_REG 3
#define SPI_FMT_BIT_RJ (0<<0) /* right justified mode */
#define SPI_FMT_BIT_LJ (1<<0) /* left justified mode */
#define SPI_FMT_BIT_I2S (2<<0) /* I2S mode */
#define SPI_FMT_BIT_DSP (3<<0) /* DSP Modes A or B */
#define SPI_LRP_REG 3
#define SPI_LRP_BIT (1<<2) /* invert LRCLK polarity */
#define SPI_BCP_REG 3
#define SPI_BCP_BIT (1<<3) /* invert BCLK polarity */
#define SPI_IWL_REG 3
#define SPI_IWL_BIT_16 (0<<4) /* 16-bit world length */
#define SPI_IWL_BIT_20 (1<<4) /* 20-bit world length */
#define SPI_IWL_BIT_24 (2<<4) /* 24-bit world length */
#define SPI_IWL_BIT_32 (3<<4) /* 32-bit world length */
#define SPI_MS_REG 10
#define SPI_MS_BIT (1<<5) /* master mode */
#define SPI_RATE_REG 10 /* only applies in master mode */
#define SPI_RATE_BIT_128 (0<<6) /* MCLK = LRCLK * 128 */
#define SPI_RATE_BIT_192 (1<<6)
#define SPI_RATE_BIT_256 (2<<6)
#define SPI_RATE_BIT_384 (3<<6)
#define SPI_RATE_BIT_512 (4<<6)
#define SPI_RATE_BIT_768 (5<<6)
/* They really do label the bit for the 4th channel "4" and not "3" */
#define SPI_DMUTE0_REG 9
#define SPI_DMUTE1_REG 9
#define SPI_DMUTE2_REG 9
#define SPI_DMUTE4_REG 15
#define SPI_DMUTE0_BIT (1<<3)
#define SPI_DMUTE1_BIT (1<<4)
#define SPI_DMUTE2_BIT (1<<5)
#define SPI_DMUTE4_BIT (1<<2)
#define SPI_PHASE0_REG 3
#define SPI_PHASE1_REG 3
#define SPI_PHASE2_REG 3
#define SPI_PHASE4_REG 15
#define SPI_PHASE0_BIT (1<<6)
#define SPI_PHASE1_BIT (1<<7)
#define SPI_PHASE2_BIT (1<<8)
#define SPI_PHASE4_BIT (1<<3)
#define SPI_PDWN_REG 2 /* power down all DACs */
#define SPI_PDWN_BIT (1<<2)
#define SPI_DACD0_REG 10 /* power down individual DACs */
#define SPI_DACD1_REG 10
#define SPI_DACD2_REG 10
#define SPI_DACD4_REG 15
#define SPI_DACD0_BIT (1<<1)
#define SPI_DACD1_BIT (1<<2)
#define SPI_DACD2_BIT (1<<3)
#define SPI_DACD4_BIT (1<<0) /* datasheet error says it's 1 */
#define SPI_PWRDNALL_REG 10 /* power down everything */
#define SPI_PWRDNALL_BIT (1<<4)
#include "ca_midi.h"
struct snd_ca0106;
struct snd_ca0106_channel {
struct snd_ca0106 *emu;
int number;
int use;
void (*interrupt)(struct snd_ca0106 *emu, struct snd_ca0106_channel *channel);
struct snd_ca0106_pcm *epcm;
};
struct snd_ca0106_pcm {
struct snd_ca0106 *emu;
struct snd_pcm_substream *substream;
int channel_id;
unsigned short running;
};
struct snd_ca0106_details {
u32 serial;
char * name;
int ac97; /* ac97 = 0 -> Select MIC, Line in, TAD in, AUX in.
ac97 = 1 -> Default to AC97 in. */
int gpio_type; /* gpio_type = 1 -> shared mic-in/line-in
gpio_type = 2 -> shared side-out/line-in. */
int i2c_adc; /* with i2c_adc=1, the driver adds some capture volume
controls, phone, mic, line-in and aux. */
u16 spi_dac; /* spi_dac = 0 -> no spi interface for DACs
spi_dac = 0x<front><rear><center-lfe><side>
-> specifies DAC id for each channel pair. */
};
// definition of the chip-specific record
struct snd_ca0106 {
struct snd_card *card;
struct snd_ca0106_details *details;
struct pci_dev *pci;
unsigned long port;
struct resource *res_port;
int irq;
unsigned int serial; /* serial number */
unsigned short model; /* subsystem id */
spinlock_t emu_lock;
struct snd_ac97 *ac97;
struct snd_pcm *pcm[4];
struct snd_ca0106_channel playback_channels[4];
struct snd_ca0106_channel capture_channels[4];
u32 spdif_bits[4]; /* s/pdif out default setup */
u32 spdif_str_bits[4]; /* s/pdif out per-stream setup */
int spdif_enable;
int capture_source;
int i2c_capture_source;
u8 i2c_capture_volume[4][2];
int capture_mic_line_in;
struct snd_dma_buffer buffer;
struct snd_ca_midi midi;
struct snd_ca_midi midi2;
u16 spi_dac_reg[16];
#ifdef CONFIG_PM_SLEEP
#define NUM_SAVED_VOLUMES 9
unsigned int saved_vol[NUM_SAVED_VOLUMES];
#endif
};
int snd_ca0106_mixer(struct snd_ca0106 *emu);
int snd_ca0106_proc_init(struct snd_ca0106 * emu);
unsigned int snd_ca0106_ptr_read(struct snd_ca0106 * emu,
unsigned int reg,
unsigned int chn);
void snd_ca0106_ptr_write(struct snd_ca0106 *emu,
unsigned int reg,
unsigned int chn,
unsigned int data);
int snd_ca0106_i2c_write(struct snd_ca0106 *emu, u32 reg, u32 value);
int snd_ca0106_spi_write(struct snd_ca0106 * emu,
unsigned int data);
#ifdef CONFIG_PM_SLEEP
void snd_ca0106_mixer_suspend(struct snd_ca0106 *chip);
void snd_ca0106_mixer_resume(struct snd_ca0106 *chip);
#else
#define snd_ca0106_mixer_suspend(chip) do { } while (0)
#define snd_ca0106_mixer_resume(chip) do { } while (0)
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,956 @@
/*
* Copyright (c) 2004 James Courtier-Dutton <James@superbug.demon.co.uk>
* Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit
* Version: 0.0.18
*
* FEATURES currently supported:
* See ca0106_main.c for features.
*
* Changelog:
* Support interrupts per period.
* Removed noise from Center/LFE channel when in Analog mode.
* Rename and remove mixer controls.
* 0.0.6
* Use separate card based DMA buffer for periods table list.
* 0.0.7
* Change remove and rename ctrls into lists.
* 0.0.8
* Try to fix capture sources.
* 0.0.9
* Fix AC3 output.
* Enable S32_LE format support.
* 0.0.10
* Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".)
* 0.0.11
* Add Model name recognition.
* 0.0.12
* Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period.
* Remove redundent "voice" handling.
* 0.0.13
* Single trigger call for multi channels.
* 0.0.14
* Set limits based on what the sound card hardware can do.
* playback periods_min=2, periods_max=8
* capture hw constraints require period_size = n * 64 bytes.
* playback hw constraints require period_size = n * 64 bytes.
* 0.0.15
* Separated ca0106.c into separate functional .c files.
* 0.0.16
* Modified Copyright message.
* 0.0.17
* Implement Mic and Line in Capture.
* 0.0.18
* Add support for mute control on SB Live 24bit (cards w/ SPI DAC)
*
* This code was initially based on code from ALSA's emu10k1x.c which is:
* Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
#include <sound/info.h>
#include <sound/tlv.h>
#include <asm/io.h>
#include "ca0106.h"
static void ca0106_spdif_enable(struct snd_ca0106 *emu)
{
unsigned int val;
if (emu->spdif_enable) {
/* Digital */
snd_ca0106_ptr_write(emu, SPDIF_SELECT1, 0, 0xf);
snd_ca0106_ptr_write(emu, SPDIF_SELECT2, 0, 0x0b000000);
val = snd_ca0106_ptr_read(emu, CAPTURE_CONTROL, 0) & ~0x1000;
snd_ca0106_ptr_write(emu, CAPTURE_CONTROL, 0, val);
val = inl(emu->port + GPIO) & ~0x101;
outl(val, emu->port + GPIO);
} else {
/* Analog */
snd_ca0106_ptr_write(emu, SPDIF_SELECT1, 0, 0xf);
snd_ca0106_ptr_write(emu, SPDIF_SELECT2, 0, 0x000f0000);
val = snd_ca0106_ptr_read(emu, CAPTURE_CONTROL, 0) | 0x1000;
snd_ca0106_ptr_write(emu, CAPTURE_CONTROL, 0, val);
val = inl(emu->port + GPIO) | 0x101;
outl(val, emu->port + GPIO);
}
}
static void ca0106_set_capture_source(struct snd_ca0106 *emu)
{
unsigned int val = emu->capture_source;
unsigned int source, mask;
source = (val << 28) | (val << 24) | (val << 20) | (val << 16);
mask = snd_ca0106_ptr_read(emu, CAPTURE_SOURCE, 0) & 0xffff;
snd_ca0106_ptr_write(emu, CAPTURE_SOURCE, 0, source | mask);
}
static void ca0106_set_i2c_capture_source(struct snd_ca0106 *emu,
unsigned int val, int force)
{
unsigned int ngain, ogain;
u32 source;
snd_ca0106_i2c_write(emu, ADC_MUX, 0); /* Mute input */
ngain = emu->i2c_capture_volume[val][0]; /* Left */
ogain = emu->i2c_capture_volume[emu->i2c_capture_source][0]; /* Left */
if (force || ngain != ogain)
snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCL, ngain & 0xff);
ngain = emu->i2c_capture_volume[val][1]; /* Right */
ogain = emu->i2c_capture_volume[emu->i2c_capture_source][1]; /* Right */
if (force || ngain != ogain)
snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCR, ngain & 0xff);
source = 1 << val;
snd_ca0106_i2c_write(emu, ADC_MUX, source); /* Set source */
emu->i2c_capture_source = val;
}
static void ca0106_set_capture_mic_line_in(struct snd_ca0106 *emu)
{
u32 tmp;
if (emu->capture_mic_line_in) {
/* snd_ca0106_i2c_write(emu, ADC_MUX, 0); */ /* Mute input */
tmp = inl(emu->port+GPIO) & ~0x400;
tmp = tmp | 0x400;
outl(tmp, emu->port+GPIO);
/* snd_ca0106_i2c_write(emu, ADC_MUX, ADC_MUX_MIC); */
} else {
/* snd_ca0106_i2c_write(emu, ADC_MUX, 0); */ /* Mute input */
tmp = inl(emu->port+GPIO) & ~0x400;
outl(tmp, emu->port+GPIO);
/* snd_ca0106_i2c_write(emu, ADC_MUX, ADC_MUX_LINEIN); */
}
}
static void ca0106_set_spdif_bits(struct snd_ca0106 *emu, int idx)
{
snd_ca0106_ptr_write(emu, SPCS0 + idx, 0, emu->spdif_str_bits[idx]);
}
/*
*/
static const DECLARE_TLV_DB_SCALE(snd_ca0106_db_scale1, -5175, 25, 1);
static const DECLARE_TLV_DB_SCALE(snd_ca0106_db_scale2, -10350, 50, 1);
#define snd_ca0106_shared_spdif_info snd_ctl_boolean_mono_info
static int snd_ca0106_shared_spdif_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = emu->spdif_enable;
return 0;
}
static int snd_ca0106_shared_spdif_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int val;
int change = 0;
val = !!ucontrol->value.integer.value[0];
change = (emu->spdif_enable != val);
if (change) {
emu->spdif_enable = val;
ca0106_spdif_enable(emu);
}
return change;
}
static int snd_ca0106_capture_source_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static char *texts[6] = {
"IEC958 out", "i2s mixer out", "IEC958 in", "i2s in", "AC97 in", "SRC out"
};
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 6;
if (uinfo->value.enumerated.item > 5)
uinfo->value.enumerated.item = 5;
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
return 0;
}
static int snd_ca0106_capture_source_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = emu->capture_source;
return 0;
}
static int snd_ca0106_capture_source_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int val;
int change = 0;
val = ucontrol->value.enumerated.item[0] ;
if (val >= 6)
return -EINVAL;
change = (emu->capture_source != val);
if (change) {
emu->capture_source = val;
ca0106_set_capture_source(emu);
}
return change;
}
static int snd_ca0106_i2c_capture_source_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static char *texts[6] = {
"Phone", "Mic", "Line in", "Aux"
};
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 4;
if (uinfo->value.enumerated.item > 3)
uinfo->value.enumerated.item = 3;
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
return 0;
}
static int snd_ca0106_i2c_capture_source_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = emu->i2c_capture_source;
return 0;
}
static int snd_ca0106_i2c_capture_source_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int source_id;
int change = 0;
/* If the capture source has changed,
* update the capture volume from the cached value
* for the particular source.
*/
source_id = ucontrol->value.enumerated.item[0] ;
if (source_id >= 4)
return -EINVAL;
change = (emu->i2c_capture_source != source_id);
if (change) {
ca0106_set_i2c_capture_source(emu, source_id, 0);
}
return change;
}
static int snd_ca0106_capture_line_in_side_out_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static char *texts[2] = { "Side out", "Line in" };
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 2;
if (uinfo->value.enumerated.item > 1)
uinfo->value.enumerated.item = 1;
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
return 0;
}
static int snd_ca0106_capture_mic_line_in_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static char *texts[2] = { "Line in", "Mic in" };
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 2;
if (uinfo->value.enumerated.item > 1)
uinfo->value.enumerated.item = 1;
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
return 0;
}
static int snd_ca0106_capture_mic_line_in_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = emu->capture_mic_line_in;
return 0;
}
static int snd_ca0106_capture_mic_line_in_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int val;
int change = 0;
val = ucontrol->value.enumerated.item[0] ;
if (val > 1)
return -EINVAL;
change = (emu->capture_mic_line_in != val);
if (change) {
emu->capture_mic_line_in = val;
ca0106_set_capture_mic_line_in(emu);
}
return change;
}
static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Shared Mic/Line in Capture Switch",
.info = snd_ca0106_capture_mic_line_in_info,
.get = snd_ca0106_capture_mic_line_in_get,
.put = snd_ca0106_capture_mic_line_in_put
};
static struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Shared Line in/Side out Capture Switch",
.info = snd_ca0106_capture_line_in_side_out_info,
.get = snd_ca0106_capture_mic_line_in_get,
.put = snd_ca0106_capture_mic_line_in_put
};
static int snd_ca0106_spdif_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static void decode_spdif_bits(unsigned char *status, unsigned int bits)
{
status[0] = (bits >> 0) & 0xff;
status[1] = (bits >> 8) & 0xff;
status[2] = (bits >> 16) & 0xff;
status[3] = (bits >> 24) & 0xff;
}
static int snd_ca0106_spdif_get_default(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
decode_spdif_bits(ucontrol->value.iec958.status,
emu->spdif_bits[idx]);
return 0;
}
static int snd_ca0106_spdif_get_stream(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
decode_spdif_bits(ucontrol->value.iec958.status,
emu->spdif_str_bits[idx]);
return 0;
}
static int snd_ca0106_spdif_get_mask(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.iec958.status[0] = 0xff;
ucontrol->value.iec958.status[1] = 0xff;
ucontrol->value.iec958.status[2] = 0xff;
ucontrol->value.iec958.status[3] = 0xff;
return 0;
}
static unsigned int encode_spdif_bits(unsigned char *status)
{
return ((unsigned int)status[0] << 0) |
((unsigned int)status[1] << 8) |
((unsigned int)status[2] << 16) |
((unsigned int)status[3] << 24);
}
static int snd_ca0106_spdif_put_default(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
unsigned int val;
val = encode_spdif_bits(ucontrol->value.iec958.status);
if (val != emu->spdif_bits[idx]) {
emu->spdif_bits[idx] = val;
/* FIXME: this isn't safe, but needed to keep the compatibility
* with older alsa-lib config
*/
emu->spdif_str_bits[idx] = val;
ca0106_set_spdif_bits(emu, idx);
return 1;
}
return 0;
}
static int snd_ca0106_spdif_put_stream(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
unsigned int val;
val = encode_spdif_bits(ucontrol->value.iec958.status);
if (val != emu->spdif_str_bits[idx]) {
emu->spdif_str_bits[idx] = val;
ca0106_set_spdif_bits(emu, idx);
return 1;
}
return 0;
}
static int snd_ca0106_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 255;
return 0;
}
static int snd_ca0106_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int value;
int channel_id, reg;
channel_id = (kcontrol->private_value >> 8) & 0xff;
reg = kcontrol->private_value & 0xff;
value = snd_ca0106_ptr_read(emu, reg, channel_id);
ucontrol->value.integer.value[0] = 0xff - ((value >> 24) & 0xff); /* Left */
ucontrol->value.integer.value[1] = 0xff - ((value >> 16) & 0xff); /* Right */
return 0;
}
static int snd_ca0106_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int oval, nval;
int channel_id, reg;
channel_id = (kcontrol->private_value >> 8) & 0xff;
reg = kcontrol->private_value & 0xff;
oval = snd_ca0106_ptr_read(emu, reg, channel_id);
nval = ((0xff - ucontrol->value.integer.value[0]) << 24) |
((0xff - ucontrol->value.integer.value[1]) << 16);
nval |= ((0xff - ucontrol->value.integer.value[0]) << 8) |
((0xff - ucontrol->value.integer.value[1]) );
if (oval == nval)
return 0;
snd_ca0106_ptr_write(emu, reg, channel_id, nval);
return 1;
}
static int snd_ca0106_i2c_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 255;
return 0;
}
static int snd_ca0106_i2c_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
int source_id;
source_id = kcontrol->private_value;
ucontrol->value.integer.value[0] = emu->i2c_capture_volume[source_id][0];
ucontrol->value.integer.value[1] = emu->i2c_capture_volume[source_id][1];
return 0;
}
static int snd_ca0106_i2c_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int ogain;
unsigned int ngain;
int source_id;
int change = 0;
source_id = kcontrol->private_value;
ogain = emu->i2c_capture_volume[source_id][0]; /* Left */
ngain = ucontrol->value.integer.value[0];
if (ngain > 0xff)
return -EINVAL;
if (ogain != ngain) {
if (emu->i2c_capture_source == source_id)
snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCL, ((ngain) & 0xff) );
emu->i2c_capture_volume[source_id][0] = ucontrol->value.integer.value[0];
change = 1;
}
ogain = emu->i2c_capture_volume[source_id][1]; /* Right */
ngain = ucontrol->value.integer.value[1];
if (ngain > 0xff)
return -EINVAL;
if (ogain != ngain) {
if (emu->i2c_capture_source == source_id)
snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCR, ((ngain) & 0xff));
emu->i2c_capture_volume[source_id][1] = ucontrol->value.integer.value[1];
change = 1;
}
return change;
}
#define spi_mute_info snd_ctl_boolean_mono_info
static int spi_mute_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int reg = kcontrol->private_value >> SPI_REG_SHIFT;
unsigned int bit = kcontrol->private_value & SPI_REG_MASK;
ucontrol->value.integer.value[0] = !(emu->spi_dac_reg[reg] & bit);
return 0;
}
static int spi_mute_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
unsigned int reg = kcontrol->private_value >> SPI_REG_SHIFT;
unsigned int bit = kcontrol->private_value & SPI_REG_MASK;
int ret;
ret = emu->spi_dac_reg[reg] & bit;
if (ucontrol->value.integer.value[0]) {
if (!ret) /* bit already cleared, do nothing */
return 0;
emu->spi_dac_reg[reg] &= ~bit;
} else {
if (ret) /* bit already set, do nothing */
return 0;
emu->spi_dac_reg[reg] |= bit;
}
ret = snd_ca0106_spi_write(emu, emu->spi_dac_reg[reg]);
return ret ? -EINVAL : 1;
}
#define CA_VOLUME(xname,chid,reg) \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
.info = snd_ca0106_volume_info, \
.get = snd_ca0106_volume_get, \
.put = snd_ca0106_volume_put, \
.tlv = { .p = snd_ca0106_db_scale1 }, \
.private_value = ((chid) << 8) | (reg) \
}
static struct snd_kcontrol_new snd_ca0106_volume_ctls[] = {
CA_VOLUME("Analog Front Playback Volume",
CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME2),
CA_VOLUME("Analog Rear Playback Volume",
CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME2),
CA_VOLUME("Analog Center/LFE Playback Volume",
CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME2),
CA_VOLUME("Analog Side Playback Volume",
CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME2),
CA_VOLUME("IEC958 Front Playback Volume",
CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME1),
CA_VOLUME("IEC958 Rear Playback Volume",
CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME1),
CA_VOLUME("IEC958 Center/LFE Playback Volume",
CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME1),
CA_VOLUME("IEC958 Unknown Playback Volume",
CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME1),
CA_VOLUME("CAPTURE feedback Playback Volume",
1, CAPTURE_CONTROL),
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
.count = 4,
.info = snd_ca0106_spdif_info,
.get = snd_ca0106_spdif_get_mask
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "IEC958 Playback Switch",
.info = snd_ca0106_shared_spdif_info,
.get = snd_ca0106_shared_spdif_get,
.put = snd_ca0106_shared_spdif_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Digital Source Capture Enum",
.info = snd_ca0106_capture_source_info,
.get = snd_ca0106_capture_source_get,
.put = snd_ca0106_capture_source_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Analog Source Capture Enum",
.info = snd_ca0106_i2c_capture_source_info,
.get = snd_ca0106_i2c_capture_source_get,
.put = snd_ca0106_i2c_capture_source_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
.count = 4,
.info = snd_ca0106_spdif_info,
.get = snd_ca0106_spdif_get_default,
.put = snd_ca0106_spdif_put_default
},
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
.count = 4,
.info = snd_ca0106_spdif_info,
.get = snd_ca0106_spdif_get_stream,
.put = snd_ca0106_spdif_put_stream
},
};
#define I2C_VOLUME(xname,chid) \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
.info = snd_ca0106_i2c_volume_info, \
.get = snd_ca0106_i2c_volume_get, \
.put = snd_ca0106_i2c_volume_put, \
.tlv = { .p = snd_ca0106_db_scale2 }, \
.private_value = chid \
}
static struct snd_kcontrol_new snd_ca0106_volume_i2c_adc_ctls[] = {
I2C_VOLUME("Phone Capture Volume", 0),
I2C_VOLUME("Mic Capture Volume", 1),
I2C_VOLUME("Line in Capture Volume", 2),
I2C_VOLUME("Aux Capture Volume", 3),
};
static const int spi_dmute_reg[] = {
SPI_DMUTE0_REG,
SPI_DMUTE1_REG,
SPI_DMUTE2_REG,
0,
SPI_DMUTE4_REG,
};
static const int spi_dmute_bit[] = {
SPI_DMUTE0_BIT,
SPI_DMUTE1_BIT,
SPI_DMUTE2_BIT,
0,
SPI_DMUTE4_BIT,
};
static struct snd_kcontrol_new
snd_ca0106_volume_spi_dac_ctl(struct snd_ca0106_details *details,
int channel_id)
{
struct snd_kcontrol_new spi_switch = {0};
int reg, bit;
int dac_id;
spi_switch.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
spi_switch.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
spi_switch.info = spi_mute_info;
spi_switch.get = spi_mute_get;
spi_switch.put = spi_mute_put;
switch (channel_id) {
case PCM_FRONT_CHANNEL:
spi_switch.name = "Analog Front Playback Switch";
dac_id = (details->spi_dac & 0xf000) >> (4 * 3);
break;
case PCM_REAR_CHANNEL:
spi_switch.name = "Analog Rear Playback Switch";
dac_id = (details->spi_dac & 0x0f00) >> (4 * 2);
break;
case PCM_CENTER_LFE_CHANNEL:
spi_switch.name = "Analog Center/LFE Playback Switch";
dac_id = (details->spi_dac & 0x00f0) >> (4 * 1);
break;
case PCM_UNKNOWN_CHANNEL:
spi_switch.name = "Analog Side Playback Switch";
dac_id = (details->spi_dac & 0x000f) >> (4 * 0);
break;
default:
/* Unused channel */
spi_switch.name = NULL;
dac_id = 0;
}
reg = spi_dmute_reg[dac_id];
bit = spi_dmute_bit[dac_id];
spi_switch.private_value = (reg << SPI_REG_SHIFT) | bit;
return spi_switch;
}
static int remove_ctl(struct snd_card *card, const char *name)
{
struct snd_ctl_elem_id id;
memset(&id, 0, sizeof(id));
strcpy(id.name, name);
id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
return snd_ctl_remove_id(card, &id);
}
static struct snd_kcontrol *ctl_find(struct snd_card *card, const char *name)
{
struct snd_ctl_elem_id sid;
memset(&sid, 0, sizeof(sid));
/* FIXME: strcpy is bad. */
strcpy(sid.name, name);
sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
return snd_ctl_find_id(card, &sid);
}
static int rename_ctl(struct snd_card *card, const char *src, const char *dst)
{
struct snd_kcontrol *kctl = ctl_find(card, src);
if (kctl) {
strcpy(kctl->id.name, dst);
return 0;
}
return -ENOENT;
}
#define ADD_CTLS(emu, ctls) \
do { \
int i, _err; \
for (i = 0; i < ARRAY_SIZE(ctls); i++) { \
_err = snd_ctl_add(card, snd_ctl_new1(&ctls[i], emu)); \
if (_err < 0) \
return _err; \
} \
} while (0)
static
DECLARE_TLV_DB_SCALE(snd_ca0106_master_db_scale, -6375, 25, 1);
static char *slave_vols[] = {
"Analog Front Playback Volume",
"Analog Rear Playback Volume",
"Analog Center/LFE Playback Volume",
"Analog Side Playback Volume",
"IEC958 Front Playback Volume",
"IEC958 Rear Playback Volume",
"IEC958 Center/LFE Playback Volume",
"IEC958 Unknown Playback Volume",
"CAPTURE feedback Playback Volume",
NULL
};
static char *slave_sws[] = {
"Analog Front Playback Switch",
"Analog Rear Playback Switch",
"Analog Center/LFE Playback Switch",
"Analog Side Playback Switch",
"IEC958 Playback Switch",
NULL
};
static void add_slaves(struct snd_card *card,
struct snd_kcontrol *master, char **list)
{
for (; *list; list++) {
struct snd_kcontrol *slave = ctl_find(card, *list);
if (slave)
snd_ctl_add_slave(master, slave);
}
}
int snd_ca0106_mixer(struct snd_ca0106 *emu)
{
int err;
struct snd_card *card = emu->card;
char **c;
struct snd_kcontrol *vmaster;
static char *ca0106_remove_ctls[] = {
"Master Mono Playback Switch",
"Master Mono Playback Volume",
"3D Control - Switch",
"3D Control Sigmatel - Depth",
"PCM Playback Switch",
"PCM Playback Volume",
"CD Playback Switch",
"CD Playback Volume",
"Phone Playback Switch",
"Phone Playback Volume",
"Video Playback Switch",
"Video Playback Volume",
"Beep Playback Switch",
"Beep Playback Volume",
"Mono Output Select",
"Capture Source",
"Capture Switch",
"Capture Volume",
"External Amplifier",
"Sigmatel 4-Speaker Stereo Playback Switch",
"Surround Phase Inversion Playback Switch",
NULL
};
static char *ca0106_rename_ctls[] = {
"Master Playback Switch", "Capture Switch",
"Master Playback Volume", "Capture Volume",
"Line Playback Switch", "AC97 Line Capture Switch",
"Line Playback Volume", "AC97 Line Capture Volume",
"Aux Playback Switch", "AC97 Aux Capture Switch",
"Aux Playback Volume", "AC97 Aux Capture Volume",
"Mic Playback Switch", "AC97 Mic Capture Switch",
"Mic Playback Volume", "AC97 Mic Capture Volume",
"Mic Select", "AC97 Mic Select",
"Mic Boost (+20dB)", "AC97 Mic Boost (+20dB)",
NULL
};
#if 1
for (c = ca0106_remove_ctls; *c; c++)
remove_ctl(card, *c);
for (c = ca0106_rename_ctls; *c; c += 2)
rename_ctl(card, c[0], c[1]);
#endif
ADD_CTLS(emu, snd_ca0106_volume_ctls);
if (emu->details->i2c_adc == 1) {
ADD_CTLS(emu, snd_ca0106_volume_i2c_adc_ctls);
if (emu->details->gpio_type == 1)
err = snd_ctl_add(card, snd_ctl_new1(&snd_ca0106_capture_mic_line_in, emu));
else /* gpio_type == 2 */
err = snd_ctl_add(card, snd_ctl_new1(&snd_ca0106_capture_line_in_side_out, emu));
if (err < 0)
return err;
}
if (emu->details->spi_dac) {
int i;
for (i = 0;; i++) {
struct snd_kcontrol_new ctl;
ctl = snd_ca0106_volume_spi_dac_ctl(emu->details, i);
if (!ctl.name)
break;
err = snd_ctl_add(card, snd_ctl_new1(&ctl, emu));
if (err < 0)
return err;
}
}
/* Create virtual master controls */
vmaster = snd_ctl_make_virtual_master("Master Playback Volume",
snd_ca0106_master_db_scale);
if (!vmaster)
return -ENOMEM;
err = snd_ctl_add(card, vmaster);
if (err < 0)
return err;
add_slaves(card, vmaster, slave_vols);
if (emu->details->spi_dac) {
vmaster = snd_ctl_make_virtual_master("Master Playback Switch",
NULL);
if (!vmaster)
return -ENOMEM;
err = snd_ctl_add(card, vmaster);
if (err < 0)
return err;
add_slaves(card, vmaster, slave_sws);
}
strcpy(card->mixername, "CA0106");
return 0;
}
#ifdef CONFIG_PM_SLEEP
struct ca0106_vol_tbl {
unsigned int channel_id;
unsigned int reg;
};
static struct ca0106_vol_tbl saved_volumes[NUM_SAVED_VOLUMES] = {
{ CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME2 },
{ CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME2 },
{ CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME2 },
{ CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME2 },
{ CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME1 },
{ CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME1 },
{ CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME1 },
{ CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME1 },
{ 1, CAPTURE_CONTROL },
};
void snd_ca0106_mixer_suspend(struct snd_ca0106 *chip)
{
int i;
/* save volumes */
for (i = 0; i < NUM_SAVED_VOLUMES; i++)
chip->saved_vol[i] =
snd_ca0106_ptr_read(chip, saved_volumes[i].reg,
saved_volumes[i].channel_id);
}
void snd_ca0106_mixer_resume(struct snd_ca0106 *chip)
{
int i;
for (i = 0; i < NUM_SAVED_VOLUMES; i++)
snd_ca0106_ptr_write(chip, saved_volumes[i].reg,
saved_volumes[i].channel_id,
chip->saved_vol[i]);
ca0106_spdif_enable(chip);
ca0106_set_capture_source(chip);
ca0106_set_i2c_capture_source(chip, chip->i2c_capture_source, 1);
for (i = 0; i < 4; i++)
ca0106_set_spdif_bits(chip, i);
if (chip->details->i2c_adc)
ca0106_set_capture_mic_line_in(chip);
}
#endif /* CONFIG_PM_SLEEP */

View file

@ -0,0 +1,457 @@
/*
* Copyright (c) 2004 James Courtier-Dutton <James@superbug.demon.co.uk>
* Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit
* Version: 0.0.18
*
* FEATURES currently supported:
* See ca0106_main.c for features.
*
* Changelog:
* Support interrupts per period.
* Removed noise from Center/LFE channel when in Analog mode.
* Rename and remove mixer controls.
* 0.0.6
* Use separate card based DMA buffer for periods table list.
* 0.0.7
* Change remove and rename ctrls into lists.
* 0.0.8
* Try to fix capture sources.
* 0.0.9
* Fix AC3 output.
* Enable S32_LE format support.
* 0.0.10
* Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".)
* 0.0.11
* Add Model name recognition.
* 0.0.12
* Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period.
* Remove redundent "voice" handling.
* 0.0.13
* Single trigger call for multi channels.
* 0.0.14
* Set limits based on what the sound card hardware can do.
* playback periods_min=2, periods_max=8
* capture hw constraints require period_size = n * 64 bytes.
* playback hw constraints require period_size = n * 64 bytes.
* 0.0.15
* Separate ca0106.c into separate functional .c files.
* 0.0.16
* Modified Copyright message.
* 0.0.17
* Add iec958 file in proc file system to show status of SPDIF in.
* 0.0.18
* Implement support for Line-in capture on SB Live 24bit.
*
* This code was initially based on code from ALSA's emu10k1x.c which is:
* Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
#include <sound/info.h>
#include <sound/asoundef.h>
#include <asm/io.h>
#include "ca0106.h"
#ifdef CONFIG_PROC_FS
struct snd_ca0106_category_str {
int val;
const char *name;
};
static struct snd_ca0106_category_str snd_ca0106_con_category[] = {
{ IEC958_AES1_CON_DAT, "DAT" },
{ IEC958_AES1_CON_VCR, "VCR" },
{ IEC958_AES1_CON_MICROPHONE, "microphone" },
{ IEC958_AES1_CON_SYNTHESIZER, "synthesizer" },
{ IEC958_AES1_CON_RATE_CONVERTER, "rate converter" },
{ IEC958_AES1_CON_MIXER, "mixer" },
{ IEC958_AES1_CON_SAMPLER, "sampler" },
{ IEC958_AES1_CON_PCM_CODER, "PCM coder" },
{ IEC958_AES1_CON_IEC908_CD, "CD" },
{ IEC958_AES1_CON_NON_IEC908_CD, "non-IEC908 CD" },
{ IEC958_AES1_CON_GENERAL, "general" },
};
static void snd_ca0106_proc_dump_iec958( struct snd_info_buffer *buffer, u32 value)
{
int i;
u32 status[4];
status[0] = value & 0xff;
status[1] = (value >> 8) & 0xff;
status[2] = (value >> 16) & 0xff;
status[3] = (value >> 24) & 0xff;
if (! (status[0] & IEC958_AES0_PROFESSIONAL)) {
/* consumer */
snd_iprintf(buffer, "Mode: consumer\n");
snd_iprintf(buffer, "Data: ");
if (!(status[0] & IEC958_AES0_NONAUDIO)) {
snd_iprintf(buffer, "audio\n");
} else {
snd_iprintf(buffer, "non-audio\n");
}
snd_iprintf(buffer, "Rate: ");
switch (status[3] & IEC958_AES3_CON_FS) {
case IEC958_AES3_CON_FS_44100:
snd_iprintf(buffer, "44100 Hz\n");
break;
case IEC958_AES3_CON_FS_48000:
snd_iprintf(buffer, "48000 Hz\n");
break;
case IEC958_AES3_CON_FS_32000:
snd_iprintf(buffer, "32000 Hz\n");
break;
default:
snd_iprintf(buffer, "unknown\n");
break;
}
snd_iprintf(buffer, "Copyright: ");
if (status[0] & IEC958_AES0_CON_NOT_COPYRIGHT) {
snd_iprintf(buffer, "permitted\n");
} else {
snd_iprintf(buffer, "protected\n");
}
snd_iprintf(buffer, "Emphasis: ");
if ((status[0] & IEC958_AES0_CON_EMPHASIS) != IEC958_AES0_CON_EMPHASIS_5015) {
snd_iprintf(buffer, "none\n");
} else {
snd_iprintf(buffer, "50/15us\n");
}
snd_iprintf(buffer, "Category: ");
for (i = 0; i < ARRAY_SIZE(snd_ca0106_con_category); i++) {
if ((status[1] & IEC958_AES1_CON_CATEGORY) == snd_ca0106_con_category[i].val) {
snd_iprintf(buffer, "%s\n", snd_ca0106_con_category[i].name);
break;
}
}
if (i >= ARRAY_SIZE(snd_ca0106_con_category)) {
snd_iprintf(buffer, "unknown 0x%x\n", status[1] & IEC958_AES1_CON_CATEGORY);
}
snd_iprintf(buffer, "Original: ");
if (status[1] & IEC958_AES1_CON_ORIGINAL) {
snd_iprintf(buffer, "original\n");
} else {
snd_iprintf(buffer, "1st generation\n");
}
snd_iprintf(buffer, "Clock: ");
switch (status[3] & IEC958_AES3_CON_CLOCK) {
case IEC958_AES3_CON_CLOCK_1000PPM:
snd_iprintf(buffer, "1000 ppm\n");
break;
case IEC958_AES3_CON_CLOCK_50PPM:
snd_iprintf(buffer, "50 ppm\n");
break;
case IEC958_AES3_CON_CLOCK_VARIABLE:
snd_iprintf(buffer, "variable pitch\n");
break;
default:
snd_iprintf(buffer, "unknown\n");
break;
}
} else {
snd_iprintf(buffer, "Mode: professional\n");
snd_iprintf(buffer, "Data: ");
if (!(status[0] & IEC958_AES0_NONAUDIO)) {
snd_iprintf(buffer, "audio\n");
} else {
snd_iprintf(buffer, "non-audio\n");
}
snd_iprintf(buffer, "Rate: ");
switch (status[0] & IEC958_AES0_PRO_FS) {
case IEC958_AES0_PRO_FS_44100:
snd_iprintf(buffer, "44100 Hz\n");
break;
case IEC958_AES0_PRO_FS_48000:
snd_iprintf(buffer, "48000 Hz\n");
break;
case IEC958_AES0_PRO_FS_32000:
snd_iprintf(buffer, "32000 Hz\n");
break;
default:
snd_iprintf(buffer, "unknown\n");
break;
}
snd_iprintf(buffer, "Rate Locked: ");
if (status[0] & IEC958_AES0_PRO_FREQ_UNLOCKED)
snd_iprintf(buffer, "no\n");
else
snd_iprintf(buffer, "yes\n");
snd_iprintf(buffer, "Emphasis: ");
switch (status[0] & IEC958_AES0_PRO_EMPHASIS) {
case IEC958_AES0_PRO_EMPHASIS_CCITT:
snd_iprintf(buffer, "CCITT J.17\n");
break;
case IEC958_AES0_PRO_EMPHASIS_NONE:
snd_iprintf(buffer, "none\n");
break;
case IEC958_AES0_PRO_EMPHASIS_5015:
snd_iprintf(buffer, "50/15us\n");
break;
case IEC958_AES0_PRO_EMPHASIS_NOTID:
default:
snd_iprintf(buffer, "unknown\n");
break;
}
snd_iprintf(buffer, "Stereophonic: ");
if ((status[1] & IEC958_AES1_PRO_MODE) == IEC958_AES1_PRO_MODE_STEREOPHONIC) {
snd_iprintf(buffer, "stereo\n");
} else {
snd_iprintf(buffer, "not indicated\n");
}
snd_iprintf(buffer, "Userbits: ");
switch (status[1] & IEC958_AES1_PRO_USERBITS) {
case IEC958_AES1_PRO_USERBITS_192:
snd_iprintf(buffer, "192bit\n");
break;
case IEC958_AES1_PRO_USERBITS_UDEF:
snd_iprintf(buffer, "user-defined\n");
break;
default:
snd_iprintf(buffer, "unknown\n");
break;
}
snd_iprintf(buffer, "Sample Bits: ");
switch (status[2] & IEC958_AES2_PRO_SBITS) {
case IEC958_AES2_PRO_SBITS_20:
snd_iprintf(buffer, "20 bit\n");
break;
case IEC958_AES2_PRO_SBITS_24:
snd_iprintf(buffer, "24 bit\n");
break;
case IEC958_AES2_PRO_SBITS_UDEF:
snd_iprintf(buffer, "user defined\n");
break;
default:
snd_iprintf(buffer, "unknown\n");
break;
}
snd_iprintf(buffer, "Word Length: ");
switch (status[2] & IEC958_AES2_PRO_WORDLEN) {
case IEC958_AES2_PRO_WORDLEN_22_18:
snd_iprintf(buffer, "22 bit or 18 bit\n");
break;
case IEC958_AES2_PRO_WORDLEN_23_19:
snd_iprintf(buffer, "23 bit or 19 bit\n");
break;
case IEC958_AES2_PRO_WORDLEN_24_20:
snd_iprintf(buffer, "24 bit or 20 bit\n");
break;
case IEC958_AES2_PRO_WORDLEN_20_16:
snd_iprintf(buffer, "20 bit or 16 bit\n");
break;
default:
snd_iprintf(buffer, "unknown\n");
break;
}
}
}
static void snd_ca0106_proc_iec958(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ca0106 *emu = entry->private_data;
u32 value;
value = snd_ca0106_ptr_read(emu, SAMPLE_RATE_TRACKER_STATUS, 0);
snd_iprintf(buffer, "Status: %s, %s, %s\n",
(value & 0x100000) ? "Rate Locked" : "Not Rate Locked",
(value & 0x200000) ? "SPDIF Locked" : "No SPDIF Lock",
(value & 0x400000) ? "Audio Valid" : "No valid audio" );
snd_iprintf(buffer, "Estimated sample rate: %u\n",
((value & 0xfffff) * 48000) / 0x8000 );
if (value & 0x200000) {
snd_iprintf(buffer, "IEC958/SPDIF input status:\n");
value = snd_ca0106_ptr_read(emu, SPDIF_INPUT_STATUS, 0);
snd_ca0106_proc_dump_iec958(buffer, value);
}
snd_iprintf(buffer, "\n");
}
static void snd_ca0106_proc_reg_write32(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ca0106 *emu = entry->private_data;
unsigned long flags;
char line[64];
u32 reg, val;
while (!snd_info_get_line(buffer, line, sizeof(line))) {
if (sscanf(line, "%x %x", &reg, &val) != 2)
continue;
if (reg < 0x40 && val <= 0xffffffff) {
spin_lock_irqsave(&emu->emu_lock, flags);
outl(val, emu->port + (reg & 0xfffffffc));
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
}
}
static void snd_ca0106_proc_reg_read32(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ca0106 *emu = entry->private_data;
unsigned long value;
unsigned long flags;
int i;
snd_iprintf(buffer, "Registers:\n\n");
for(i = 0; i < 0x20; i+=4) {
spin_lock_irqsave(&emu->emu_lock, flags);
value = inl(emu->port + i);
spin_unlock_irqrestore(&emu->emu_lock, flags);
snd_iprintf(buffer, "Register %02X: %08lX\n", i, value);
}
}
static void snd_ca0106_proc_reg_read16(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ca0106 *emu = entry->private_data;
unsigned int value;
unsigned long flags;
int i;
snd_iprintf(buffer, "Registers:\n\n");
for(i = 0; i < 0x20; i+=2) {
spin_lock_irqsave(&emu->emu_lock, flags);
value = inw(emu->port + i);
spin_unlock_irqrestore(&emu->emu_lock, flags);
snd_iprintf(buffer, "Register %02X: %04X\n", i, value);
}
}
static void snd_ca0106_proc_reg_read8(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ca0106 *emu = entry->private_data;
unsigned int value;
unsigned long flags;
int i;
snd_iprintf(buffer, "Registers:\n\n");
for(i = 0; i < 0x20; i+=1) {
spin_lock_irqsave(&emu->emu_lock, flags);
value = inb(emu->port + i);
spin_unlock_irqrestore(&emu->emu_lock, flags);
snd_iprintf(buffer, "Register %02X: %02X\n", i, value);
}
}
static void snd_ca0106_proc_reg_read1(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ca0106 *emu = entry->private_data;
unsigned long value;
int i,j;
snd_iprintf(buffer, "Registers\n");
for(i = 0; i < 0x40; i++) {
snd_iprintf(buffer, "%02X: ",i);
for (j = 0; j < 4; j++) {
value = snd_ca0106_ptr_read(emu, i, j);
snd_iprintf(buffer, "%08lX ", value);
}
snd_iprintf(buffer, "\n");
}
}
static void snd_ca0106_proc_reg_read2(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ca0106 *emu = entry->private_data;
unsigned long value;
int i,j;
snd_iprintf(buffer, "Registers\n");
for(i = 0x40; i < 0x80; i++) {
snd_iprintf(buffer, "%02X: ",i);
for (j = 0; j < 4; j++) {
value = snd_ca0106_ptr_read(emu, i, j);
snd_iprintf(buffer, "%08lX ", value);
}
snd_iprintf(buffer, "\n");
}
}
static void snd_ca0106_proc_reg_write(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ca0106 *emu = entry->private_data;
char line[64];
unsigned int reg, channel_id , val;
while (!snd_info_get_line(buffer, line, sizeof(line))) {
if (sscanf(line, "%x %x %x", &reg, &channel_id, &val) != 3)
continue;
if (reg < 0x80 && val <= 0xffffffff && channel_id <= 3)
snd_ca0106_ptr_write(emu, reg, channel_id, val);
}
}
static void snd_ca0106_proc_i2c_write(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ca0106 *emu = entry->private_data;
char line[64];
unsigned int reg, val;
while (!snd_info_get_line(buffer, line, sizeof(line))) {
if (sscanf(line, "%x %x", &reg, &val) != 2)
continue;
if ((reg <= 0x7f) || (val <= 0x1ff)) {
snd_ca0106_i2c_write(emu, reg, val);
}
}
}
int snd_ca0106_proc_init(struct snd_ca0106 *emu)
{
struct snd_info_entry *entry;
if(! snd_card_proc_new(emu->card, "iec958", &entry))
snd_info_set_text_ops(entry, emu, snd_ca0106_proc_iec958);
if(! snd_card_proc_new(emu->card, "ca0106_reg32", &entry)) {
snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read32);
entry->c.text.write = snd_ca0106_proc_reg_write32;
entry->mode |= S_IWUSR;
}
if(! snd_card_proc_new(emu->card, "ca0106_reg16", &entry))
snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read16);
if(! snd_card_proc_new(emu->card, "ca0106_reg8", &entry))
snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read8);
if(! snd_card_proc_new(emu->card, "ca0106_regs1", &entry)) {
snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read1);
entry->c.text.write = snd_ca0106_proc_reg_write;
entry->mode |= S_IWUSR;
}
if(! snd_card_proc_new(emu->card, "ca0106_i2c", &entry)) {
entry->c.text.write = snd_ca0106_proc_i2c_write;
entry->private_data = emu;
entry->mode |= S_IWUSR;
}
if(! snd_card_proc_new(emu->card, "ca0106_regs2", &entry))
snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read2);
return 0;
}
#endif /* CONFIG_PROC_FS */

316
sound/pci/ca0106/ca_midi.c Normal file
View file

@ -0,0 +1,316 @@
/*
* Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de>
* Creative Audio MIDI, for the CA0106 Driver
* Version: 0.0.1
*
* Changelog:
* Implementation is based on mpu401 and emu10k1x and
* tested with ca0106.
* mpu401: Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* emu10k1x: Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
*/
#include <linux/spinlock.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
#include "ca_midi.h"
#define ca_midi_write_data(midi, data) midi->write(midi, data, 0)
#define ca_midi_write_cmd(midi, data) midi->write(midi, data, 1)
#define ca_midi_read_data(midi) midi->read(midi, 0)
#define ca_midi_read_stat(midi) midi->read(midi, 1)
#define ca_midi_input_avail(midi) (!(ca_midi_read_stat(midi) & midi->input_avail))
#define ca_midi_output_ready(midi) (!(ca_midi_read_stat(midi) & midi->output_ready))
static void ca_midi_clear_rx(struct snd_ca_midi *midi)
{
int timeout = 100000;
for (; timeout > 0 && ca_midi_input_avail(midi); timeout--)
ca_midi_read_data(midi);
#ifdef CONFIG_SND_DEBUG
if (timeout <= 0)
pr_err("ca_midi_clear_rx: timeout (status = 0x%x)\n",
ca_midi_read_stat(midi));
#endif
}
static void ca_midi_interrupt(struct snd_ca_midi *midi, unsigned int status)
{
unsigned char byte;
if (midi->rmidi == NULL) {
midi->interrupt_disable(midi,midi->tx_enable | midi->rx_enable);
return;
}
spin_lock(&midi->input_lock);
if ((status & midi->ipr_rx) && ca_midi_input_avail(midi)) {
if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
ca_midi_clear_rx(midi);
} else {
byte = ca_midi_read_data(midi);
if(midi->substream_input)
snd_rawmidi_receive(midi->substream_input, &byte, 1);
}
}
spin_unlock(&midi->input_lock);
spin_lock(&midi->output_lock);
if ((status & midi->ipr_tx) && ca_midi_output_ready(midi)) {
if (midi->substream_output &&
snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) {
ca_midi_write_data(midi, byte);
} else {
midi->interrupt_disable(midi,midi->tx_enable);
}
}
spin_unlock(&midi->output_lock);
}
static void ca_midi_cmd(struct snd_ca_midi *midi, unsigned char cmd, int ack)
{
unsigned long flags;
int timeout, ok;
spin_lock_irqsave(&midi->input_lock, flags);
ca_midi_write_data(midi, 0x00);
/* ca_midi_clear_rx(midi); */
ca_midi_write_cmd(midi, cmd);
if (ack) {
ok = 0;
timeout = 10000;
while (!ok && timeout-- > 0) {
if (ca_midi_input_avail(midi)) {
if (ca_midi_read_data(midi) == midi->ack)
ok = 1;
}
}
if (!ok && ca_midi_read_data(midi) == midi->ack)
ok = 1;
} else {
ok = 1;
}
spin_unlock_irqrestore(&midi->input_lock, flags);
if (!ok)
pr_err("ca_midi_cmd: 0x%x failed at 0x%x (status = 0x%x, data = 0x%x)!!!\n",
cmd,
midi->get_dev_id_port(midi->dev_id),
ca_midi_read_stat(midi),
ca_midi_read_data(midi));
}
static int ca_midi_input_open(struct snd_rawmidi_substream *substream)
{
struct snd_ca_midi *midi = substream->rmidi->private_data;
unsigned long flags;
if (snd_BUG_ON(!midi->dev_id))
return -ENXIO;
spin_lock_irqsave(&midi->open_lock, flags);
midi->midi_mode |= CA_MIDI_MODE_INPUT;
midi->substream_input = substream;
if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {
spin_unlock_irqrestore(&midi->open_lock, flags);
ca_midi_cmd(midi, midi->reset, 1);
ca_midi_cmd(midi, midi->enter_uart, 1);
} else {
spin_unlock_irqrestore(&midi->open_lock, flags);
}
return 0;
}
static int ca_midi_output_open(struct snd_rawmidi_substream *substream)
{
struct snd_ca_midi *midi = substream->rmidi->private_data;
unsigned long flags;
if (snd_BUG_ON(!midi->dev_id))
return -ENXIO;
spin_lock_irqsave(&midi->open_lock, flags);
midi->midi_mode |= CA_MIDI_MODE_OUTPUT;
midi->substream_output = substream;
if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
spin_unlock_irqrestore(&midi->open_lock, flags);
ca_midi_cmd(midi, midi->reset, 1);
ca_midi_cmd(midi, midi->enter_uart, 1);
} else {
spin_unlock_irqrestore(&midi->open_lock, flags);
}
return 0;
}
static int ca_midi_input_close(struct snd_rawmidi_substream *substream)
{
struct snd_ca_midi *midi = substream->rmidi->private_data;
unsigned long flags;
if (snd_BUG_ON(!midi->dev_id))
return -ENXIO;
spin_lock_irqsave(&midi->open_lock, flags);
midi->interrupt_disable(midi,midi->rx_enable);
midi->midi_mode &= ~CA_MIDI_MODE_INPUT;
midi->substream_input = NULL;
if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {
spin_unlock_irqrestore(&midi->open_lock, flags);
ca_midi_cmd(midi, midi->reset, 0);
} else {
spin_unlock_irqrestore(&midi->open_lock, flags);
}
return 0;
}
static int ca_midi_output_close(struct snd_rawmidi_substream *substream)
{
struct snd_ca_midi *midi = substream->rmidi->private_data;
unsigned long flags;
if (snd_BUG_ON(!midi->dev_id))
return -ENXIO;
spin_lock_irqsave(&midi->open_lock, flags);
midi->interrupt_disable(midi,midi->tx_enable);
midi->midi_mode &= ~CA_MIDI_MODE_OUTPUT;
midi->substream_output = NULL;
if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
spin_unlock_irqrestore(&midi->open_lock, flags);
ca_midi_cmd(midi, midi->reset, 0);
} else {
spin_unlock_irqrestore(&midi->open_lock, flags);
}
return 0;
}
static void ca_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct snd_ca_midi *midi = substream->rmidi->private_data;
if (snd_BUG_ON(!midi->dev_id))
return;
if (up) {
midi->interrupt_enable(midi,midi->rx_enable);
} else {
midi->interrupt_disable(midi, midi->rx_enable);
}
}
static void ca_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct snd_ca_midi *midi = substream->rmidi->private_data;
unsigned long flags;
if (snd_BUG_ON(!midi->dev_id))
return;
if (up) {
int max = 4;
unsigned char byte;
spin_lock_irqsave(&midi->output_lock, flags);
/* try to send some amount of bytes here before interrupts */
while (max > 0) {
if (ca_midi_output_ready(midi)) {
if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT) ||
snd_rawmidi_transmit(substream, &byte, 1) != 1) {
/* no more data */
spin_unlock_irqrestore(&midi->output_lock, flags);
return;
}
ca_midi_write_data(midi, byte);
max--;
} else {
break;
}
}
spin_unlock_irqrestore(&midi->output_lock, flags);
midi->interrupt_enable(midi,midi->tx_enable);
} else {
midi->interrupt_disable(midi,midi->tx_enable);
}
}
static struct snd_rawmidi_ops ca_midi_output =
{
.open = ca_midi_output_open,
.close = ca_midi_output_close,
.trigger = ca_midi_output_trigger,
};
static struct snd_rawmidi_ops ca_midi_input =
{
.open = ca_midi_input_open,
.close = ca_midi_input_close,
.trigger = ca_midi_input_trigger,
};
static void ca_midi_free(struct snd_ca_midi *midi)
{
midi->interrupt = NULL;
midi->interrupt_enable = NULL;
midi->interrupt_disable = NULL;
midi->read = NULL;
midi->write = NULL;
midi->get_dev_id_card = NULL;
midi->get_dev_id_port = NULL;
midi->rmidi = NULL;
}
static void ca_rmidi_free(struct snd_rawmidi *rmidi)
{
ca_midi_free(rmidi->private_data);
}
int ca_midi_init(void *dev_id, struct snd_ca_midi *midi, int device, char *name)
{
struct snd_rawmidi *rmidi;
int err;
if ((err = snd_rawmidi_new(midi->get_dev_id_card(midi->dev_id), name, device, 1, 1, &rmidi)) < 0)
return err;
midi->dev_id = dev_id;
midi->interrupt = ca_midi_interrupt;
spin_lock_init(&midi->open_lock);
spin_lock_init(&midi->input_lock);
spin_lock_init(&midi->output_lock);
strcpy(rmidi->name, name);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &ca_midi_output);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &ca_midi_input);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
rmidi->private_data = midi;
rmidi->private_free = ca_rmidi_free;
midi->rmidi = rmidi;
return 0;
}

View file

@ -0,0 +1,66 @@
/*
* Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de>
* Creative Audio MIDI, for the CA0106 Driver
* Version: 0.0.1
*
* Changelog:
* See ca_midi.c
*
* 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/spinlock.h>
#include <sound/rawmidi.h>
#include <sound/mpu401.h>
#define CA_MIDI_MODE_INPUT MPU401_MODE_INPUT
#define CA_MIDI_MODE_OUTPUT MPU401_MODE_OUTPUT
struct snd_ca_midi {
struct snd_rawmidi *rmidi;
struct snd_rawmidi_substream *substream_input;
struct snd_rawmidi_substream *substream_output;
void *dev_id;
spinlock_t input_lock;
spinlock_t output_lock;
spinlock_t open_lock;
unsigned int channel;
unsigned int midi_mode;
int port;
int tx_enable, rx_enable;
int ipr_tx, ipr_rx;
int input_avail, output_ready;
int ack, reset, enter_uart;
void (*interrupt)(struct snd_ca_midi *midi, unsigned int status);
void (*interrupt_enable)(struct snd_ca_midi *midi, int intr);
void (*interrupt_disable)(struct snd_ca_midi *midi, int intr);
unsigned char (*read)(struct snd_ca_midi *midi, int idx);
void (*write)(struct snd_ca_midi *midi, int data, int idx);
/* get info from dev_id */
struct snd_card *(*get_dev_id_card)(void *dev_id);
int (*get_dev_id_port)(void *dev_id);
};
int ca_midi_init(void *card, struct snd_ca_midi *midi, int device, char *name);

3424
sound/pci/cmipci.c Normal file

File diff suppressed because it is too large Load diff

2112
sound/pci/cs4281.c Normal file

File diff suppressed because it is too large Load diff

10
sound/pci/cs46xx/Makefile Normal file
View file

@ -0,0 +1,10 @@
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
#
snd-cs46xx-y := cs46xx.o cs46xx_lib.o
snd-cs46xx-$(CONFIG_SND_CS46XX_NEW_DSP) += dsp_spos.o dsp_spos_scb_lib.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_CS46XX) += snd-cs46xx.o

176
sound/pci/cs46xx/cs46xx.c Normal file
View file

@ -0,0 +1,176 @@
/*
* The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
*
* 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
*
*/
/*
NOTES:
- sometimes the sound is metallic and sibilant, unloading and
reloading the module may solve this.
*/
#include <linux/pci.h>
#include <linux/time.h>
#include <linux/init.h>
#include <linux/module.h>
#include <sound/core.h>
#include "cs46xx.h"
#include <sound/initval.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Cirrus Logic Sound Fusion CS46XX");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{Cirrus Logic,Sound Fusion (CS4280)},"
"{Cirrus Logic,Sound Fusion (CS4610)},"
"{Cirrus Logic,Sound Fusion (CS4612)},"
"{Cirrus Logic,Sound Fusion (CS4615)},"
"{Cirrus Logic,Sound Fusion (CS4622)},"
"{Cirrus Logic,Sound Fusion (CS4624)},"
"{Cirrus Logic,Sound Fusion (CS4630)}}");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
static bool external_amp[SNDRV_CARDS];
static bool thinkpad[SNDRV_CARDS];
static bool mmap_valid[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for the CS46xx soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for the CS46xx soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable CS46xx soundcard.");
module_param_array(external_amp, bool, NULL, 0444);
MODULE_PARM_DESC(external_amp, "Force to enable external amplifer.");
module_param_array(thinkpad, bool, NULL, 0444);
MODULE_PARM_DESC(thinkpad, "Force to enable Thinkpad's CLKRUN control.");
module_param_array(mmap_valid, bool, NULL, 0444);
MODULE_PARM_DESC(mmap_valid, "Support OSS mmap.");
static const struct pci_device_id snd_cs46xx_ids[] = {
{ PCI_VDEVICE(CIRRUS, 0x6001), 0, }, /* CS4280 */
{ PCI_VDEVICE(CIRRUS, 0x6003), 0, }, /* CS4612 */
{ PCI_VDEVICE(CIRRUS, 0x6004), 0, }, /* CS4615 */
{ 0, }
};
MODULE_DEVICE_TABLE(pci, snd_cs46xx_ids);
static int snd_card_cs46xx_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
struct snd_card *card;
struct snd_cs46xx *chip;
int err;
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0)
return err;
if ((err = snd_cs46xx_create(card, pci,
external_amp[dev], thinkpad[dev],
&chip)) < 0) {
snd_card_free(card);
return err;
}
card->private_data = chip;
chip->accept_valid = mmap_valid[dev];
if ((err = snd_cs46xx_pcm(chip, 0, NULL)) < 0) {
snd_card_free(card);
return err;
}
#ifdef CONFIG_SND_CS46XX_NEW_DSP
if ((err = snd_cs46xx_pcm_rear(chip,1, NULL)) < 0) {
snd_card_free(card);
return err;
}
if ((err = snd_cs46xx_pcm_iec958(chip,2,NULL)) < 0) {
snd_card_free(card);
return err;
}
#endif
if ((err = snd_cs46xx_mixer(chip, 2)) < 0) {
snd_card_free(card);
return err;
}
#ifdef CONFIG_SND_CS46XX_NEW_DSP
if (chip->nr_ac97_codecs ==2) {
if ((err = snd_cs46xx_pcm_center_lfe(chip,3,NULL)) < 0) {
snd_card_free(card);
return err;
}
}
#endif
if ((err = snd_cs46xx_midi(chip, 0, NULL)) < 0) {
snd_card_free(card);
return err;
}
if ((err = snd_cs46xx_start_dsp(chip)) < 0) {
snd_card_free(card);
return err;
}
snd_cs46xx_gameport(chip);
strcpy(card->driver, "CS46xx");
strcpy(card->shortname, "Sound Fusion CS46xx");
sprintf(card->longname, "%s at 0x%lx/0x%lx, irq %i",
card->shortname,
chip->ba0_addr,
chip->ba1_addr,
chip->irq);
if ((err = snd_card_register(card)) < 0) {
snd_card_free(card);
return err;
}
pci_set_drvdata(pci, card);
dev++;
return 0;
}
static void snd_card_cs46xx_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
}
static struct pci_driver cs46xx_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_cs46xx_ids,
.probe = snd_card_cs46xx_probe,
.remove = snd_card_cs46xx_remove,
#ifdef CONFIG_PM_SLEEP
.driver = {
.pm = &snd_cs46xx_pm,
},
#endif
};
module_pci_driver(cs46xx_driver);

1749
sound/pci/cs46xx/cs46xx.h Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,234 @@
/*
* The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
*
* 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
*
*/
#ifndef __CS46XX_DSP_SPOS_H__
#define __CS46XX_DSP_SPOS_H__
#include "cs46xx_dsp_scb_types.h"
#include "cs46xx_dsp_task_types.h"
#define SYMBOL_CONSTANT 0x0
#define SYMBOL_SAMPLE 0x1
#define SYMBOL_PARAMETER 0x2
#define SYMBOL_CODE 0x3
#define SEGTYPE_SP_PROGRAM 0x00000001
#define SEGTYPE_SP_PARAMETER 0x00000002
#define SEGTYPE_SP_SAMPLE 0x00000003
#define SEGTYPE_SP_COEFFICIENT 0x00000004
#define DSP_SPOS_UU 0x0deadul /* unused */
#define DSP_SPOS_DC 0x0badul /* don't care */
#define DSP_SPOS_DC_DC 0x0bad0badul /* don't care */
#define DSP_SPOS_UUUU 0xdeadc0edul /* unused */
#define DSP_SPOS_UUHI 0xdeadul
#define DSP_SPOS_UULO 0xc0edul
#define DSP_SPOS_DCDC 0x0badf1d0ul /* don't care */
#define DSP_SPOS_DCDCHI 0x0badul
#define DSP_SPOS_DCDCLO 0xf1d0ul
#define DSP_MAX_TASK_NAME 60
#define DSP_MAX_SYMBOL_NAME 100
#define DSP_MAX_SCB_NAME 60
#define DSP_MAX_SCB_DESC 200
#define DSP_MAX_TASK_DESC 50
#define DSP_MAX_PCM_CHANNELS 32
#define DSP_MAX_SRC_NR 14
#define DSP_PCM_MAIN_CHANNEL 1
#define DSP_PCM_REAR_CHANNEL 2
#define DSP_PCM_CENTER_LFE_CHANNEL 3
#define DSP_PCM_S71_CHANNEL 4 /* surround 7.1 */
#define DSP_IEC958_CHANNEL 5
#define DSP_SPDIF_STATUS_OUTPUT_ENABLED 1
#define DSP_SPDIF_STATUS_PLAYBACK_OPEN 2
#define DSP_SPDIF_STATUS_HW_ENABLED 4
#define DSP_SPDIF_STATUS_INPUT_CTRL_ENABLED 8
struct dsp_symbol_entry {
u32 address;
char symbol_name[DSP_MAX_SYMBOL_NAME];
int symbol_type;
/* initialized by driver */
struct dsp_module_desc * module;
int deleted;
};
struct dsp_symbol_desc {
int nsymbols;
struct dsp_symbol_entry *symbols;
/* initialized by driver */
int highest_frag_index;
};
struct dsp_segment_desc {
int segment_type;
u32 offset;
u32 size;
u32 * data;
};
struct dsp_module_desc {
char * module_name;
struct dsp_symbol_desc symbol_table;
int nsegments;
struct dsp_segment_desc * segments;
/* initialized by driver */
u32 overlay_begin_address;
u32 load_address;
int nfixups;
};
struct dsp_scb_descriptor {
char scb_name[DSP_MAX_SCB_NAME];
u32 address;
int index;
u32 *data;
struct dsp_scb_descriptor * sub_list_ptr;
struct dsp_scb_descriptor * next_scb_ptr;
struct dsp_scb_descriptor * parent_scb_ptr;
struct dsp_symbol_entry * task_entry;
struct dsp_symbol_entry * scb_symbol;
struct snd_info_entry *proc_info;
int ref_count;
u16 volume[2];
unsigned int deleted :1;
unsigned int updated :1;
unsigned int volume_set :1;
};
struct dsp_task_descriptor {
char task_name[DSP_MAX_TASK_NAME];
int size;
u32 address;
int index;
u32 *data;
};
struct dsp_pcm_channel_descriptor {
int active;
int src_slot;
int pcm_slot;
u32 sample_rate;
u32 unlinked;
struct dsp_scb_descriptor * pcm_reader_scb;
struct dsp_scb_descriptor * src_scb;
struct dsp_scb_descriptor * mixer_scb;
void * private_data;
};
struct dsp_spos_instance {
struct dsp_symbol_desc symbol_table; /* currently available loaded symbols in SP */
int nmodules;
struct dsp_module_desc * modules; /* modules loaded into SP */
struct dsp_segment_desc code;
/* Main PCM playback mixer */
struct dsp_scb_descriptor * master_mix_scb;
u16 dac_volume_right;
u16 dac_volume_left;
/* Rear/surround PCM playback mixer */
struct dsp_scb_descriptor * rear_mix_scb;
/* Center/LFE mixer */
struct dsp_scb_descriptor * center_lfe_mix_scb;
int npcm_channels;
int nsrc_scb;
struct dsp_pcm_channel_descriptor pcm_channels[DSP_MAX_PCM_CHANNELS];
int src_scb_slots[DSP_MAX_SRC_NR];
/* cache this symbols */
struct dsp_symbol_entry * null_algorithm; /* used by PCMreaderSCB's */
struct dsp_symbol_entry * s16_up; /* used by SRCtaskSCB's */
/* proc fs */
struct snd_card *snd_card;
struct snd_info_entry * proc_dsp_dir;
struct snd_info_entry * proc_sym_info_entry;
struct snd_info_entry * proc_modules_info_entry;
struct snd_info_entry * proc_parameter_dump_info_entry;
struct snd_info_entry * proc_sample_dump_info_entry;
/* SCB's descriptors */
int nscb;
int scb_highest_frag_index;
struct dsp_scb_descriptor scbs[DSP_MAX_SCB_DESC];
struct snd_info_entry * proc_scb_info_entry;
struct dsp_scb_descriptor * the_null_scb;
/* Task's descriptors */
int ntask;
struct dsp_task_descriptor tasks[DSP_MAX_TASK_DESC];
struct snd_info_entry * proc_task_info_entry;
/* SPDIF status */
int spdif_status_out;
int spdif_status_in;
u16 spdif_input_volume_right;
u16 spdif_input_volume_left;
/* spdif channel status,
left right and user validity bits */
unsigned int spdif_csuv_default;
unsigned int spdif_csuv_stream;
/* SPDIF input sample rate converter */
struct dsp_scb_descriptor * spdif_in_src;
/* SPDIF input asynch. receiver */
struct dsp_scb_descriptor * asynch_rx_scb;
/* Capture record mixer SCB */
struct dsp_scb_descriptor * record_mixer_scb;
/* CODEC input SCB */
struct dsp_scb_descriptor * codec_in_scb;
/* reference snooper */
struct dsp_scb_descriptor * ref_snoop_scb;
/* SPDIF output PCM reference */
struct dsp_scb_descriptor * spdif_pcm_input_scb;
/* asynch TX task */
struct dsp_scb_descriptor * asynch_tx_scb;
/* record sources */
struct dsp_scb_descriptor * pcm_input;
struct dsp_scb_descriptor * adc_input;
int spdif_in_sample_rate;
};
#endif /* __DSP_SPOS_H__ */

View file

@ -0,0 +1,252 @@
/*
* The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
*
* 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
*
*
* NOTE: comments are copy/paste from cwcemb80.lst
* provided by Tom Woller at Cirrus (my only
* documentation about the SP OS running inside
* the DSP)
*/
#ifndef __CS46XX_DSP_TASK_TYPES_H__
#define __CS46XX_DSP_TASK_TYPES_H__
#include "cs46xx_dsp_scb_types.h"
/*********************************************************************************************
Example hierarchy of stream control blocks in the SP
hfgTree
Ptr____Call (c)
\
-------+------ ------------- ------------- ------------- -----
| SBlaster IF |______\| Foreground |___\| Middlegr'nd |___\| Background |___\| Nul |
| |Goto /| tree header |g /| tree header |g /| tree header |g /| SCB |r
-------------- (g) ------------- ------------- ------------- -----
|c |c |c |c
| | | |
\/ ------------- ------------- -------------
| Foreground |_\ | Middlegr'nd |_\ | Background |_\
| tree |g/ | tree |g/ | tree |g/
------------- ------------- -------------
|c |c |c
| | |
\/ \/ \/
*********************************************************************************************/
#define HFG_FIRST_EXECUTE_MODE 0x0001
#define HFG_FIRST_EXECUTE_MODE_BIT 0
#define HFG_CONTEXT_SWITCH_MODE 0x0002
#define HFG_CONTEXT_SWITCH_MODE_BIT 1
#define MAX_FG_STACK_SIZE 32 /* THESE NEED TO BE COMPUTED PROPERLY */
#define MAX_MG_STACK_SIZE 16
#define MAX_BG_STACK_SIZE 9
#define MAX_HFG_STACK_SIZE 4
#define SLEEP_ACTIVE_INCREMENT 0 /* Enable task tree thread to go to sleep
This should only ever be used on the Background thread */
#define STANDARD_ACTIVE_INCREMENT 1 /* Task tree thread normal operation */
#define SUSPEND_ACTIVE_INCREMENT 2 /* Cause execution to suspend in the task tree thread
This should only ever be used on the Background thread */
#define HOSTFLAGS_DISABLE_BG_SLEEP 0 /* Host-controlled flag that determines whether we go to sleep
at the end of BG */
/* Minimal context save area for Hyper Forground */
struct dsp_hf_save_area {
u32 r10_save;
u32 r54_save;
u32 r98_save;
___DSP_DUAL_16BIT_ALLOC(
status_save,
ind_save
)
___DSP_DUAL_16BIT_ALLOC(
rci1_save,
rci0_save
)
u32 r32_save;
u32 r76_save;
u32 rsd2_save;
___DSP_DUAL_16BIT_ALLOC(
rsi2_save, /* See TaskTreeParameterBlock for
remainder of registers */
rsa2Save
)
/* saved as part of HFG context */
};
/* Task link data structure */
struct dsp_tree_link {
___DSP_DUAL_16BIT_ALLOC(
/* Pointer to sibling task control block */
next_scb,
/* Pointer to child task control block */
sub_ptr
)
___DSP_DUAL_16BIT_ALLOC(
/* Pointer to code entry point */
entry_point,
/* Pointer to local data */
this_spb
)
};
struct dsp_task_tree_data {
___DSP_DUAL_16BIT_ALLOC(
/* Initial tock count; controls task tree execution rate */
tock_count_limit,
/* Tock down counter */
tock_count
)
/* Add to ActiveCount when TockCountLimit reached:
Subtract on task tree termination */
___DSP_DUAL_16BIT_ALLOC(
active_tncrement,
/* Number of pending activations for task tree */
active_count
)
___DSP_DUAL_16BIT_ALLOC(
/* BitNumber to enable modification of correct bit in ActiveTaskFlags */
active_bit,
/* Pointer to OS location for indicating current activity on task level */
active_task_flags_ptr
)
/* Data structure for controlling movement of memory blocks:-
currently unused */
___DSP_DUAL_16BIT_ALLOC(
mem_upd_ptr,
/* Data structure for controlling synchronous link update */
link_upd_ptr
)
___DSP_DUAL_16BIT_ALLOC(
/* Save area for remainder of full context. */
save_area,
/* Address of start of local stack for data storage */
data_stack_base_ptr
)
};
struct dsp_interval_timer_data
{
/* These data items have the same relative locations to those */
___DSP_DUAL_16BIT_ALLOC(
interval_timer_period,
itd_unused
)
/* used for this data in the SPOS control block for SPOS 1.0 */
___DSP_DUAL_16BIT_ALLOC(
num_FG_ticks_this_interval,
num_intervals
)
};
/* This structure contains extra storage for the task tree
Currently, this additional data is related only to a full context save */
struct dsp_task_tree_context_block {
/* Up to 10 values are saved onto the stack. 8 for the task tree, 1 for
The access to the context switch (call or interrupt), and 1 spare that
users should never use. This last may be required by the system */
___DSP_DUAL_16BIT_ALLOC(
stack1,
stack0
)
___DSP_DUAL_16BIT_ALLOC(
stack3,
stack2
)
___DSP_DUAL_16BIT_ALLOC(
stack5,
stack4
)
___DSP_DUAL_16BIT_ALLOC(
stack7,
stack6
)
___DSP_DUAL_16BIT_ALLOC(
stack9,
stack8
)
u32 saverfe;
/* Value may be overwriten by stack save algorithm.
Retain the size of the stack data saved here if used */
___DSP_DUAL_16BIT_ALLOC(
reserved1,
stack_size
)
u32 saverba; /* (HFG) */
u32 saverdc;
u32 savers_config_23; /* (HFG) */
u32 savers_DMA23; /* (HFG) */
u32 saversa0;
u32 saversi0;
u32 saversa1;
u32 saversi1;
u32 saversa3;
u32 saversd0;
u32 saversd1;
u32 saversd3;
u32 savers_config01;
u32 savers_DMA01;
u32 saveacc0hl;
u32 saveacc1hl;
u32 saveacc0xacc1x;
u32 saveacc2hl;
u32 saveacc3hl;
u32 saveacc2xacc3x;
u32 saveaux0hl;
u32 saveaux1hl;
u32 saveaux0xaux1x;
u32 saveaux2hl;
u32 saveaux3hl;
u32 saveaux2xaux3x;
u32 savershouthl;
u32 savershoutxmacmode;
};
struct dsp_task_tree_control_block {
struct dsp_hf_save_area context;
struct dsp_tree_link links;
struct dsp_task_tree_data data;
struct dsp_task_tree_context_block context_blk;
struct dsp_interval_timer_data int_timer;
};
#endif /* __DSP_TASK_TYPES_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,210 @@
/*
* The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
*
* 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
*
*/
#ifndef __CS46XX_LIB_H__
#define __CS46XX_LIB_H__
/*
* constants
*/
#define CS46XX_BA0_SIZE 0x1000
#define CS46XX_BA1_DATA0_SIZE 0x3000
#define CS46XX_BA1_DATA1_SIZE 0x3800
#define CS46XX_BA1_PRG_SIZE 0x7000
#define CS46XX_BA1_REG_SIZE 0x0100
#ifdef CONFIG_SND_CS46XX_NEW_DSP
#define CS46XX_MIN_PERIOD_SIZE 64
#define CS46XX_MAX_PERIOD_SIZE 1024*1024
#else
#define CS46XX_MIN_PERIOD_SIZE 2048
#define CS46XX_MAX_PERIOD_SIZE 2048
#endif
#define CS46XX_FRAGS 2
/* #define CS46XX_BUFFER_SIZE CS46XX_MAX_PERIOD_SIZE * CS46XX_FRAGS */
#define SCB_NO_PARENT 0
#define SCB_ON_PARENT_NEXT_SCB 1
#define SCB_ON_PARENT_SUBLIST_SCB 2
/* 3*1024 parameter, 3.5*1024 sample, 2*3.5*1024 code */
#define BA1_DWORD_SIZE (13 * 1024 + 512)
#define BA1_MEMORY_COUNT 3
/*
* common I/O routines
*/
static inline void snd_cs46xx_poke(struct snd_cs46xx *chip, unsigned long reg, unsigned int val)
{
unsigned int bank = reg >> 16;
unsigned int offset = reg & 0xffff;
/*
if (bank == 0)
printk(KERN_DEBUG "snd_cs46xx_poke: %04X - %08X\n",
reg >> 2,val);
*/
writel(val, chip->region.idx[bank+1].remap_addr + offset);
}
static inline unsigned int snd_cs46xx_peek(struct snd_cs46xx *chip, unsigned long reg)
{
unsigned int bank = reg >> 16;
unsigned int offset = reg & 0xffff;
return readl(chip->region.idx[bank+1].remap_addr + offset);
}
static inline void snd_cs46xx_pokeBA0(struct snd_cs46xx *chip, unsigned long offset, unsigned int val)
{
writel(val, chip->region.name.ba0.remap_addr + offset);
}
static inline unsigned int snd_cs46xx_peekBA0(struct snd_cs46xx *chip, unsigned long offset)
{
return readl(chip->region.name.ba0.remap_addr + offset);
}
struct dsp_spos_instance *cs46xx_dsp_spos_create (struct snd_cs46xx * chip);
void cs46xx_dsp_spos_destroy (struct snd_cs46xx * chip);
int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * module);
#ifdef CONFIG_PM_SLEEP
int cs46xx_dsp_resume(struct snd_cs46xx * chip);
#endif
struct dsp_symbol_entry *cs46xx_dsp_lookup_symbol (struct snd_cs46xx * chip, char * symbol_name,
int symbol_type);
#ifdef CONFIG_PROC_FS
int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip);
int cs46xx_dsp_proc_done (struct snd_cs46xx *chip);
#else
#define cs46xx_dsp_proc_init(card, chip)
#define cs46xx_dsp_proc_done(chip)
#endif
int cs46xx_dsp_scb_and_task_init (struct snd_cs46xx *chip);
int snd_cs46xx_download (struct snd_cs46xx *chip, u32 *src, unsigned long offset,
unsigned long len);
int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip, unsigned long offset, unsigned long len);
int cs46xx_dsp_enable_spdif_out (struct snd_cs46xx *chip);
int cs46xx_dsp_enable_spdif_hw (struct snd_cs46xx *chip);
int cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip);
int cs46xx_dsp_enable_spdif_in (struct snd_cs46xx *chip);
int cs46xx_dsp_disable_spdif_in (struct snd_cs46xx *chip);
int cs46xx_dsp_enable_pcm_capture (struct snd_cs46xx *chip);
int cs46xx_dsp_disable_pcm_capture (struct snd_cs46xx *chip);
int cs46xx_dsp_enable_adc_capture (struct snd_cs46xx *chip);
int cs46xx_dsp_disable_adc_capture (struct snd_cs46xx *chip);
int cs46xx_poke_via_dsp (struct snd_cs46xx *chip, u32 address, u32 data);
struct dsp_scb_descriptor * cs46xx_dsp_create_scb (struct snd_cs46xx *chip, char * name,
u32 * scb_data, u32 dest);
#ifdef CONFIG_PROC_FS
void cs46xx_dsp_proc_free_scb_desc (struct dsp_scb_descriptor * scb);
void cs46xx_dsp_proc_register_scb_desc (struct snd_cs46xx *chip,
struct dsp_scb_descriptor * scb);
#else
#define cs46xx_dsp_proc_free_scb_desc(scb)
#define cs46xx_dsp_proc_register_scb_desc(chip, scb)
#endif
struct dsp_scb_descriptor * cs46xx_dsp_create_timing_master_scb (struct snd_cs46xx *chip);
struct dsp_scb_descriptor *
cs46xx_dsp_create_codec_out_scb(struct snd_cs46xx * chip,
char * codec_name, u16 channel_disp, u16 fifo_addr,
u16 child_scb_addr, u32 dest,
struct dsp_scb_descriptor * parent_scb,
int scb_child_type);
struct dsp_scb_descriptor *
cs46xx_dsp_create_codec_in_scb(struct snd_cs46xx * chip, char * codec_name,
u16 channel_disp, u16 fifo_addr,
u16 sample_buffer_addr, u32 dest,
struct dsp_scb_descriptor * parent_scb,
int scb_child_type);
void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip,
struct dsp_scb_descriptor * scb);
struct dsp_scb_descriptor *
cs46xx_dsp_create_codec_in_scb(struct snd_cs46xx * chip, char * codec_name,
u16 channel_disp, u16 fifo_addr,
u16 sample_buffer_addr, u32 dest,
struct dsp_scb_descriptor * parent_scb,
int scb_child_type);
struct dsp_scb_descriptor *
cs46xx_dsp_create_src_task_scb(struct snd_cs46xx * chip, char * scb_name,
int sample_rate, u16 src_buffer_addr,
u16 src_delay_buffer_addr, u32 dest,
struct dsp_scb_descriptor * parent_scb,
int scb_child_type, int pass_through);
struct dsp_scb_descriptor *
cs46xx_dsp_create_mix_only_scb(struct snd_cs46xx * chip, char * scb_name,
u16 mix_buffer_addr, u32 dest,
struct dsp_scb_descriptor * parent_scb,
int scb_child_type);
struct dsp_scb_descriptor *
cs46xx_dsp_create_vari_decimate_scb(struct snd_cs46xx * chip, char * scb_name,
u16 vari_buffer_addr0, u16 vari_buffer_addr1, u32 dest,
struct dsp_scb_descriptor * parent_scb,
int scb_child_type);
struct dsp_scb_descriptor *
cs46xx_dsp_create_asynch_fg_rx_scb(struct snd_cs46xx * chip, char * scb_name,
u32 dest, u16 hfg_scb_address, u16 asynch_buffer_address,
struct dsp_scb_descriptor * parent_scb,
int scb_child_type);
struct dsp_scb_descriptor *
cs46xx_dsp_create_spio_write_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest,
struct dsp_scb_descriptor * parent_scb,
int scb_child_type);
struct dsp_scb_descriptor *
cs46xx_dsp_create_mix_to_ostream_scb(struct snd_cs46xx * chip, char * scb_name,
u16 mix_buffer_addr, u16 writeback_spb, u32 dest,
struct dsp_scb_descriptor * parent_scb,
int scb_child_type);
struct dsp_scb_descriptor *
cs46xx_dsp_create_magic_snoop_scb(struct snd_cs46xx * chip, char * scb_name,
u32 dest, u16 snoop_buffer_address,
struct dsp_scb_descriptor * snoop_scb,
struct dsp_scb_descriptor * parent_scb,
int scb_child_type);
struct dsp_pcm_channel_descriptor *
cs46xx_dsp_create_pcm_channel (struct snd_cs46xx * chip, u32 sample_rate,
void * private_data, u32 hw_dma_addr,
int pcm_channel_id);
void cs46xx_dsp_destroy_pcm_channel (struct snd_cs46xx * chip,
struct dsp_pcm_channel_descriptor * pcm_channel);
int cs46xx_dsp_pcm_unlink (struct snd_cs46xx * chip,
struct dsp_pcm_channel_descriptor * pcm_channel);
int cs46xx_dsp_pcm_link (struct snd_cs46xx * chip,
struct dsp_pcm_channel_descriptor * pcm_channel);
struct dsp_scb_descriptor *
cs46xx_add_record_source (struct snd_cs46xx *chip, struct dsp_scb_descriptor * source,
u16 addr, char * scb_name);
int cs46xx_src_unlink(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src);
int cs46xx_src_link(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src);
int cs46xx_iec958_pre_open (struct snd_cs46xx *chip);
int cs46xx_iec958_post_close (struct snd_cs46xx *chip);
int cs46xx_dsp_pcm_channel_set_period (struct snd_cs46xx * chip,
struct dsp_pcm_channel_descriptor * pcm_channel,
int period_size);
int cs46xx_dsp_pcm_ostream_set_period (struct snd_cs46xx * chip, int period_size);
int cs46xx_dsp_set_dac_volume (struct snd_cs46xx * chip, u16 left, u16 right);
int cs46xx_dsp_set_iec958_volume (struct snd_cs46xx * chip, u16 left, u16 right);
#endif /* __CS46XX_LIB_H__ */

2049
sound/pci/cs46xx/dsp_spos.c Normal file

File diff suppressed because it is too large Load diff

231
sound/pci/cs46xx/dsp_spos.h Normal file
View file

@ -0,0 +1,231 @@
/*
* The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
*
* 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
*
*/
/*
* 2002-07 Benny Sjostrand benny@hostmobility.com
*/
#ifdef CONFIG_SND_CS46XX_NEW_DSP /* hack ... */
#ifndef __DSP_SPOS_H__
#define __DSP_SPOS_H__
#define DSP_MAX_SYMBOLS 1024
#define DSP_MAX_MODULES 64
#define DSP_CODE_BYTE_SIZE 0x00007000UL
#define DSP_PARAMETER_BYTE_SIZE 0x00003000UL
#define DSP_SAMPLE_BYTE_SIZE 0x00003800UL
#define DSP_PARAMETER_BYTE_OFFSET 0x00000000UL
#define DSP_SAMPLE_BYTE_OFFSET 0x00010000UL
#define DSP_CODE_BYTE_OFFSET 0x00020000UL
#define WIDE_INSTR_MASK 0x0040
#define WIDE_LADD_INSTR_MASK 0x0380
/* this instruction types
needs to be reallocated when load
code into DSP */
enum wide_opcode {
WIDE_FOR_BEGIN_LOOP = 0x20,
WIDE_FOR_BEGIN_LOOP2,
WIDE_COND_GOTO_ADDR = 0x30,
WIDE_COND_GOTO_CALL,
WIDE_TBEQ_COND_GOTO_ADDR = 0x70,
WIDE_TBEQ_COND_CALL_ADDR,
WIDE_TBEQ_NCOND_GOTO_ADDR,
WIDE_TBEQ_NCOND_CALL_ADDR,
WIDE_TBEQ_COND_GOTO1_ADDR,
WIDE_TBEQ_COND_CALL1_ADDR,
WIDE_TBEQ_NCOND_GOTOI_ADDR,
WIDE_TBEQ_NCOND_CALL1_ADDR,
};
/* SAMPLE segment */
#define VARI_DECIMATE_BUF1 0x0000
#define WRITE_BACK_BUF1 0x0400
#define CODEC_INPUT_BUF1 0x0500
#define PCM_READER_BUF1 0x0600
#define SRC_DELAY_BUF1 0x0680
#define VARI_DECIMATE_BUF0 0x0780
#define SRC_OUTPUT_BUF1 0x07A0
#define ASYNC_IP_OUTPUT_BUFFER1 0x0A00
#define OUTPUT_SNOOP_BUFFER 0x0B00
#define SPDIFI_IP_OUTPUT_BUFFER1 0x0E00
#define SPDIFO_IP_OUTPUT_BUFFER1 0x1000
#define MIX_SAMPLE_BUF1 0x1400
#define MIX_SAMPLE_BUF2 0x2E80
#define MIX_SAMPLE_BUF3 0x2F00
#define MIX_SAMPLE_BUF4 0x2F80
#define MIX_SAMPLE_BUF5 0x3000
/* Task stack address */
#define HFG_STACK 0x066A
#define FG_STACK 0x066E
#define BG_STACK 0x068E
/* SCB's addresses */
#define SPOSCB_ADDR 0x070
#define BG_TREE_SCB_ADDR 0x635
#define NULL_SCB_ADDR 0x000
#define TIMINGMASTER_SCB_ADDR 0x010
#define CODECOUT_SCB_ADDR 0x020
#define PCMREADER_SCB_ADDR 0x030
#define WRITEBACK_SCB_ADDR 0x040
#define CODECIN_SCB_ADDR 0x080
#define MASTERMIX_SCB_ADDR 0x090
#define SRCTASK_SCB_ADDR 0x0A0
#define VARIDECIMATE_SCB_ADDR 0x0B0
#define PCMSERIALIN_SCB_ADDR 0x0C0
#define FG_TASK_HEADER_ADDR 0x600
#define ASYNCTX_SCB_ADDR 0x0E0
#define ASYNCRX_SCB_ADDR 0x0F0
#define SRCTASKII_SCB_ADDR 0x100
#define OUTPUTSNOOP_SCB_ADDR 0x110
#define PCMSERIALINII_SCB_ADDR 0x120
#define SPIOWRITE_SCB_ADDR 0x130
#define REAR_CODECOUT_SCB_ADDR 0x140
#define OUTPUTSNOOPII_SCB_ADDR 0x150
#define PCMSERIALIN_PCM_SCB_ADDR 0x160
#define RECORD_MIXER_SCB_ADDR 0x170
#define REAR_MIXER_SCB_ADDR 0x180
#define CLFE_MIXER_SCB_ADDR 0x190
#define CLFE_CODEC_SCB_ADDR 0x1A0
/* hyperforground SCB's*/
#define HFG_TREE_SCB 0xBA0
#define SPDIFI_SCB_INST 0xBB0
#define SPDIFO_SCB_INST 0xBC0
#define WRITE_BACK_SPB 0x0D0
/* offsets */
#define AsyncCIOFIFOPointer 0xd
#define SPDIFOFIFOPointer 0xd
#define SPDIFIFIFOPointer 0xd
#define TCBData 0xb
#define HFGFlags 0xa
#define TCBContextBlk 0x10
#define AFGTxAccumPhi 0x4
#define SCBsubListPtr 0x9
#define SCBfuncEntryPtr 0xA
#define SRCCorPerGof 0x2
#define SRCPhiIncr6Int26Frac 0xd
#define SCBVolumeCtrl 0xe
/* conf */
#define UseASER1Input 1
/*
* The following defines are for the flags in the rsConfig01/23 registers of
* the SP.
*/
#define RSCONFIG_MODULO_SIZE_MASK 0x0000000FL
#define RSCONFIG_MODULO_16 0x00000001L
#define RSCONFIG_MODULO_32 0x00000002L
#define RSCONFIG_MODULO_64 0x00000003L
#define RSCONFIG_MODULO_128 0x00000004L
#define RSCONFIG_MODULO_256 0x00000005L
#define RSCONFIG_MODULO_512 0x00000006L
#define RSCONFIG_MODULO_1024 0x00000007L
#define RSCONFIG_MODULO_4 0x00000008L
#define RSCONFIG_MODULO_8 0x00000009L
#define RSCONFIG_SAMPLE_SIZE_MASK 0x000000C0L
#define RSCONFIG_SAMPLE_8MONO 0x00000000L
#define RSCONFIG_SAMPLE_8STEREO 0x00000040L
#define RSCONFIG_SAMPLE_16MONO 0x00000080L
#define RSCONFIG_SAMPLE_16STEREO 0x000000C0L
#define RSCONFIG_UNDERRUN_ZERO 0x00004000L
#define RSCONFIG_DMA_TO_HOST 0x00008000L
#define RSCONFIG_STREAM_NUM_MASK 0x00FF0000L
#define RSCONFIG_MAX_DMA_SIZE_MASK 0x1F000000L
#define RSCONFIG_DMA_ENABLE 0x20000000L
#define RSCONFIG_PRIORITY_MASK 0xC0000000L
#define RSCONFIG_PRIORITY_HIGH 0x00000000L
#define RSCONFIG_PRIORITY_MEDIUM_HIGH 0x40000000L
#define RSCONFIG_PRIORITY_MEDIUM_LOW 0x80000000L
#define RSCONFIG_PRIORITY_LOW 0xC0000000L
#define RSCONFIG_STREAM_NUM_SHIFT 16L
#define RSCONFIG_MAX_DMA_SIZE_SHIFT 24L
/* SP constants */
#define FG_INTERVAL_TIMER_PERIOD 0x0051
#define BG_INTERVAL_TIMER_PERIOD 0x0100
/* Only SP accessible registers */
#define SP_ASER_COUNTDOWN 0x8040
#define SP_SPDOUT_FIFO 0x0108
#define SP_SPDIN_MI_FIFO 0x01E0
#define SP_SPDIN_D_FIFO 0x01F0
#define SP_SPDIN_STATUS 0x8048
#define SP_SPDIN_CONTROL 0x8049
#define SP_SPDIN_FIFOPTR 0x804A
#define SP_SPDOUT_STATUS 0x804C
#define SP_SPDOUT_CONTROL 0x804D
#define SP_SPDOUT_CSUV 0x808E
static inline u8 _wrap_all_bits (u8 val)
{
u8 wrapped;
/* wrap all 8 bits */
wrapped =
((val & 0x1 ) << 7) |
((val & 0x2 ) << 5) |
((val & 0x4 ) << 3) |
((val & 0x8 ) << 1) |
((val & 0x10) >> 1) |
((val & 0x20) >> 3) |
((val & 0x40) >> 5) |
((val & 0x80) >> 7);
return wrapped;
}
static inline void cs46xx_dsp_spos_update_scb (struct snd_cs46xx * chip,
struct dsp_scb_descriptor * scb)
{
/* update nextSCB and subListPtr in SCB */
snd_cs46xx_poke(chip,
(scb->address + SCBsubListPtr) << 2,
(scb->sub_list_ptr->address << 0x10) |
(scb->next_scb_ptr->address));
scb->updated = 1;
}
static inline void cs46xx_dsp_scb_set_volume (struct snd_cs46xx * chip,
struct dsp_scb_descriptor * scb,
u16 left, u16 right)
{
unsigned int val = ((0xffff - left) << 16 | (0xffff - right));
snd_cs46xx_poke(chip, (scb->address + SCBVolumeCtrl) << 2, val);
snd_cs46xx_poke(chip, (scb->address + SCBVolumeCtrl + 1) << 2, val);
scb->volume_set = 1;
scb->volume[0] = left;
scb->volume[1] = right;
}
#endif /* __DSP_SPOS_H__ */
#endif /* CONFIG_SND_CS46XX_NEW_DSP */

File diff suppressed because it is too large Load diff

298
sound/pci/cs5530.c Normal file
View file

@ -0,0 +1,298 @@
/*
* cs5530.c - Initialisation code for Cyrix/NatSemi VSA1 softaudio
*
* (C) Copyright 2007 Ash Willis <ashwillis@programmer.net>
* (C) Copyright 2003 Red Hat Inc <alan@lxorguk.ukuu.org.uk>
*
* This driver was ported (shamelessly ripped ;) from oss/kahlua.c but I did
* mess with it a bit. The chip seems to have to have trouble with full duplex
* mode. If we're recording in 8bit 8000kHz, say, and we then attempt to
* simultaneously play back audio at 16bit 44100kHz, the device actually plays
* back in the same format in which it is capturing. By forcing the chip to
* always play/capture in 16/44100, we can let alsa-lib convert the samples and
* that way we can hack up some full duplex audio.
*
* XpressAudio(tm) is used on the Cyrix MediaGX (now NatSemi Geode) systems.
* The older version (VSA1) provides fairly good soundblaster emulation
* although there are a couple of bugs: large DMA buffers break record,
* and the MPU event handling seems suspect. VSA2 allows the native driver
* to control the AC97 audio engine directly and requires a different driver.
*
* Thanks to National Semiconductor for providing the needed information
* on the XpressAudio(tm) internals.
*
* 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, 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.
*
* TO DO:
* Investigate whether we can portably support Cognac (5520) in the
* same manner.
*/
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/sb.h>
#include <sound/initval.h>
MODULE_AUTHOR("Ash Willis");
MODULE_DESCRIPTION("CS5530 Audio");
MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for CS5530 Audio driver.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for CS5530 Audio driver.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable CS5530 Audio driver.");
struct snd_cs5530 {
struct snd_card *card;
struct pci_dev *pci;
struct snd_sb *sb;
unsigned long pci_base;
};
static const struct pci_device_id snd_cs5530_ids[] = {
{PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO, PCI_ANY_ID,
PCI_ANY_ID, 0, 0},
{0,}
};
MODULE_DEVICE_TABLE(pci, snd_cs5530_ids);
static int snd_cs5530_free(struct snd_cs5530 *chip)
{
pci_release_regions(chip->pci);
pci_disable_device(chip->pci);
kfree(chip);
return 0;
}
static int snd_cs5530_dev_free(struct snd_device *device)
{
struct snd_cs5530 *chip = device->device_data;
return snd_cs5530_free(chip);
}
static void snd_cs5530_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
}
static u8 snd_cs5530_mixer_read(unsigned long io, u8 reg)
{
outb(reg, io + 4);
udelay(20);
reg = inb(io + 5);
udelay(20);
return reg;
}
static int snd_cs5530_create(struct snd_card *card,
struct pci_dev *pci,
struct snd_cs5530 **rchip)
{
struct snd_cs5530 *chip;
unsigned long sb_base;
u8 irq, dma8, dma16 = 0;
u16 map;
void __iomem *mem;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_cs5530_dev_free,
};
*rchip = NULL;
err = pci_enable_device(pci);
if (err < 0)
return err;
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
if (chip == NULL) {
pci_disable_device(pci);
return -ENOMEM;
}
chip->card = card;
chip->pci = pci;
err = pci_request_regions(pci, "CS5530");
if (err < 0) {
kfree(chip);
pci_disable_device(pci);
return err;
}
chip->pci_base = pci_resource_start(pci, 0);
mem = pci_ioremap_bar(pci, 0);
if (mem == NULL) {
snd_cs5530_free(chip);
return -EBUSY;
}
map = readw(mem + 0x18);
iounmap(mem);
/* Map bits
0:1 * 0x20 + 0x200 = sb base
2 sb enable
3 adlib enable
5 MPU enable 0x330
6 MPU enable 0x300
The other bits may be used internally so must be masked */
sb_base = 0x220 + 0x20 * (map & 3);
if (map & (1<<2))
dev_info(card->dev, "XpressAudio at 0x%lx\n", sb_base);
else {
dev_err(card->dev, "Could not find XpressAudio!\n");
snd_cs5530_free(chip);
return -ENODEV;
}
if (map & (1<<5))
dev_info(card->dev, "MPU at 0x300\n");
else if (map & (1<<6))
dev_info(card->dev, "MPU at 0x330\n");
irq = snd_cs5530_mixer_read(sb_base, 0x80) & 0x0F;
dma8 = snd_cs5530_mixer_read(sb_base, 0x81);
if (dma8 & 0x20)
dma16 = 5;
else if (dma8 & 0x40)
dma16 = 6;
else if (dma8 & 0x80)
dma16 = 7;
else {
dev_err(card->dev, "No 16bit DMA enabled\n");
snd_cs5530_free(chip);
return -ENODEV;
}
if (dma8 & 0x01)
dma8 = 0;
else if (dma8 & 02)
dma8 = 1;
else if (dma8 & 0x08)
dma8 = 3;
else {
dev_err(card->dev, "No 8bit DMA enabled\n");
snd_cs5530_free(chip);
return -ENODEV;
}
if (irq & 1)
irq = 9;
else if (irq & 2)
irq = 5;
else if (irq & 4)
irq = 7;
else if (irq & 8)
irq = 10;
else {
dev_err(card->dev, "SoundBlaster IRQ not set\n");
snd_cs5530_free(chip);
return -ENODEV;
}
dev_info(card->dev, "IRQ: %d DMA8: %d DMA16: %d\n", irq, dma8, dma16);
err = snd_sbdsp_create(card, sb_base, irq, snd_sb16dsp_interrupt, dma8,
dma16, SB_HW_CS5530, &chip->sb);
if (err < 0) {
dev_err(card->dev, "Could not create SoundBlaster\n");
snd_cs5530_free(chip);
return err;
}
err = snd_sb16dsp_pcm(chip->sb, 0, &chip->sb->pcm);
if (err < 0) {
dev_err(card->dev, "Could not create PCM\n");
snd_cs5530_free(chip);
return err;
}
err = snd_sbmixer_new(chip->sb);
if (err < 0) {
dev_err(card->dev, "Could not create Mixer\n");
snd_cs5530_free(chip);
return err;
}
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
if (err < 0) {
snd_cs5530_free(chip);
return err;
}
*rchip = chip;
return 0;
}
static int snd_cs5530_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
struct snd_card *card;
struct snd_cs5530 *chip = NULL;
int err;
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0)
return err;
err = snd_cs5530_create(card, pci, &chip);
if (err < 0) {
snd_card_free(card);
return err;
}
strcpy(card->driver, "CS5530");
strcpy(card->shortname, "CS5530 Audio");
sprintf(card->longname, "%s at 0x%lx", card->shortname, chip->pci_base);
err = snd_card_register(card);
if (err < 0) {
snd_card_free(card);
return err;
}
pci_set_drvdata(pci, card);
dev++;
return 0;
}
static struct pci_driver cs5530_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_cs5530_ids,
.probe = snd_cs5530_probe,
.remove = snd_cs5530_remove,
};
module_pci_driver(cs5530_driver);

View file

@ -0,0 +1,10 @@
#
# Makefile for cs5535audio
#
snd-cs5535audio-y := cs5535audio.o cs5535audio_pcm.o
snd-cs5535audio-$(CONFIG_PM_SLEEP) += cs5535audio_pm.o
snd-cs5535audio-$(CONFIG_OLPC) += cs5535audio_olpc.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_CS5535AUDIO) += snd-cs5535audio.o

View file

@ -0,0 +1,416 @@
/*
* Driver for audio on multifunction CS5535/6 companion device
* Copyright (C) Jaya Kumar
*
* Based on Jaroslav Kysela and Takashi Iwai's examples.
* This work was sponsored by CIS(M) Sdn Bhd.
*
* 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/delay.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <asm/io.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/rawmidi.h>
#include <sound/ac97_codec.h>
#include <sound/initval.h>
#include <sound/asoundef.h>
#include "cs5535audio.h"
#define DRIVER_NAME "cs5535audio"
static char *ac97_quirk;
module_param(ac97_quirk, charp, 0444);
MODULE_PARM_DESC(ac97_quirk, "AC'97 board specific workarounds.");
static struct ac97_quirk ac97_quirks[] = {
#if 0 /* Not yet confirmed if all 5536 boards are HP only */
{
.subvendor = PCI_VENDOR_ID_AMD,
.subdevice = PCI_DEVICE_ID_AMD_CS5536_AUDIO,
.name = "AMD RDK",
.type = AC97_TUNE_HP_ONLY
},
#endif
{}
};
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for " DRIVER_NAME);
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for " DRIVER_NAME);
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable " DRIVER_NAME);
static const struct pci_device_id snd_cs5535audio_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_AUDIO) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_AUDIO) },
{}
};
MODULE_DEVICE_TABLE(pci, snd_cs5535audio_ids);
static void wait_till_cmd_acked(struct cs5535audio *cs5535au, unsigned long timeout)
{
unsigned int tmp;
do {
tmp = cs_readl(cs5535au, ACC_CODEC_CNTL);
if (!(tmp & CMD_NEW))
break;
udelay(1);
} while (--timeout);
if (!timeout)
dev_err(cs5535au->card->dev,
"Failure writing to cs5535 codec\n");
}
static unsigned short snd_cs5535audio_codec_read(struct cs5535audio *cs5535au,
unsigned short reg)
{
unsigned int regdata;
unsigned int timeout;
unsigned int val;
regdata = ((unsigned int) reg) << 24;
regdata |= ACC_CODEC_CNTL_RD_CMD;
regdata |= CMD_NEW;
cs_writel(cs5535au, ACC_CODEC_CNTL, regdata);
wait_till_cmd_acked(cs5535au, 50);
timeout = 50;
do {
val = cs_readl(cs5535au, ACC_CODEC_STATUS);
if ((val & STS_NEW) && reg == (val >> 24))
break;
udelay(1);
} while (--timeout);
if (!timeout)
dev_err(cs5535au->card->dev,
"Failure reading codec reg 0x%x, Last value=0x%x\n",
reg, val);
return (unsigned short) val;
}
static void snd_cs5535audio_codec_write(struct cs5535audio *cs5535au,
unsigned short reg, unsigned short val)
{
unsigned int regdata;
regdata = ((unsigned int) reg) << 24;
regdata |= val;
regdata &= CMD_MASK;
regdata |= CMD_NEW;
regdata &= ACC_CODEC_CNTL_WR_CMD;
cs_writel(cs5535au, ACC_CODEC_CNTL, regdata);
wait_till_cmd_acked(cs5535au, 50);
}
static void snd_cs5535audio_ac97_codec_write(struct snd_ac97 *ac97,
unsigned short reg, unsigned short val)
{
struct cs5535audio *cs5535au = ac97->private_data;
snd_cs5535audio_codec_write(cs5535au, reg, val);
}
static unsigned short snd_cs5535audio_ac97_codec_read(struct snd_ac97 *ac97,
unsigned short reg)
{
struct cs5535audio *cs5535au = ac97->private_data;
return snd_cs5535audio_codec_read(cs5535au, reg);
}
static int snd_cs5535audio_mixer(struct cs5535audio *cs5535au)
{
struct snd_card *card = cs5535au->card;
struct snd_ac97_bus *pbus;
struct snd_ac97_template ac97;
int err;
static struct snd_ac97_bus_ops ops = {
.write = snd_cs5535audio_ac97_codec_write,
.read = snd_cs5535audio_ac97_codec_read,
};
if ((err = snd_ac97_bus(card, 0, &ops, NULL, &pbus)) < 0)
return err;
memset(&ac97, 0, sizeof(ac97));
ac97.scaps = AC97_SCAP_AUDIO | AC97_SCAP_SKIP_MODEM
| AC97_SCAP_POWER_SAVE;
ac97.private_data = cs5535au;
ac97.pci = cs5535au->pci;
/* set any OLPC-specific scaps */
olpc_prequirks(card, &ac97);
if ((err = snd_ac97_mixer(pbus, &ac97, &cs5535au->ac97)) < 0) {
dev_err(card->dev, "mixer failed\n");
return err;
}
snd_ac97_tune_hardware(cs5535au->ac97, ac97_quirks, ac97_quirk);
err = olpc_quirks(card, cs5535au->ac97);
if (err < 0) {
dev_err(card->dev, "olpc quirks failed\n");
return err;
}
return 0;
}
static void process_bm0_irq(struct cs5535audio *cs5535au)
{
u8 bm_stat;
spin_lock(&cs5535au->reg_lock);
bm_stat = cs_readb(cs5535au, ACC_BM0_STATUS);
spin_unlock(&cs5535au->reg_lock);
if (bm_stat & EOP) {
struct cs5535audio_dma *dma;
dma = cs5535au->playback_substream->runtime->private_data;
snd_pcm_period_elapsed(cs5535au->playback_substream);
} else {
dev_err(cs5535au->card->dev,
"unexpected bm0 irq src, bm_stat=%x\n",
bm_stat);
}
}
static void process_bm1_irq(struct cs5535audio *cs5535au)
{
u8 bm_stat;
spin_lock(&cs5535au->reg_lock);
bm_stat = cs_readb(cs5535au, ACC_BM1_STATUS);
spin_unlock(&cs5535au->reg_lock);
if (bm_stat & EOP) {
struct cs5535audio_dma *dma;
dma = cs5535au->capture_substream->runtime->private_data;
snd_pcm_period_elapsed(cs5535au->capture_substream);
}
}
static irqreturn_t snd_cs5535audio_interrupt(int irq, void *dev_id)
{
u16 acc_irq_stat;
unsigned char count;
struct cs5535audio *cs5535au = dev_id;
if (cs5535au == NULL)
return IRQ_NONE;
acc_irq_stat = cs_readw(cs5535au, ACC_IRQ_STATUS);
if (!acc_irq_stat)
return IRQ_NONE;
for (count = 0; count < 4; count++) {
if (acc_irq_stat & (1 << count)) {
switch (count) {
case IRQ_STS:
cs_readl(cs5535au, ACC_GPIO_STATUS);
break;
case WU_IRQ_STS:
cs_readl(cs5535au, ACC_GPIO_STATUS);
break;
case BM0_IRQ_STS:
process_bm0_irq(cs5535au);
break;
case BM1_IRQ_STS:
process_bm1_irq(cs5535au);
break;
default:
dev_err(cs5535au->card->dev,
"Unexpected irq src: 0x%x\n",
acc_irq_stat);
break;
}
}
}
return IRQ_HANDLED;
}
static int snd_cs5535audio_free(struct cs5535audio *cs5535au)
{
synchronize_irq(cs5535au->irq);
pci_set_power_state(cs5535au->pci, PCI_D3hot);
if (cs5535au->irq >= 0)
free_irq(cs5535au->irq, cs5535au);
pci_release_regions(cs5535au->pci);
pci_disable_device(cs5535au->pci);
kfree(cs5535au);
return 0;
}
static int snd_cs5535audio_dev_free(struct snd_device *device)
{
struct cs5535audio *cs5535au = device->device_data;
return snd_cs5535audio_free(cs5535au);
}
static int snd_cs5535audio_create(struct snd_card *card,
struct pci_dev *pci,
struct cs5535audio **rcs5535au)
{
struct cs5535audio *cs5535au;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_cs5535audio_dev_free,
};
*rcs5535au = NULL;
if ((err = pci_enable_device(pci)) < 0)
return err;
if (pci_set_dma_mask(pci, DMA_BIT_MASK(32)) < 0 ||
pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32)) < 0) {
dev_warn(card->dev, "unable to get 32bit dma\n");
err = -ENXIO;
goto pcifail;
}
cs5535au = kzalloc(sizeof(*cs5535au), GFP_KERNEL);
if (cs5535au == NULL) {
err = -ENOMEM;
goto pcifail;
}
spin_lock_init(&cs5535au->reg_lock);
cs5535au->card = card;
cs5535au->pci = pci;
cs5535au->irq = -1;
if ((err = pci_request_regions(pci, "CS5535 Audio")) < 0) {
kfree(cs5535au);
goto pcifail;
}
cs5535au->port = pci_resource_start(pci, 0);
if (request_irq(pci->irq, snd_cs5535audio_interrupt,
IRQF_SHARED, KBUILD_MODNAME, cs5535au)) {
dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
err = -EBUSY;
goto sndfail;
}
cs5535au->irq = pci->irq;
pci_set_master(pci);
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
cs5535au, &ops)) < 0)
goto sndfail;
*rcs5535au = cs5535au;
return 0;
sndfail: /* leave the device alive, just kill the snd */
snd_cs5535audio_free(cs5535au);
return err;
pcifail:
pci_disable_device(pci);
return err;
}
static int snd_cs5535audio_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
struct snd_card *card;
struct cs5535audio *cs5535au;
int err;
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0)
return err;
if ((err = snd_cs5535audio_create(card, pci, &cs5535au)) < 0)
goto probefail_out;
card->private_data = cs5535au;
if ((err = snd_cs5535audio_mixer(cs5535au)) < 0)
goto probefail_out;
if ((err = snd_cs5535audio_pcm(cs5535au)) < 0)
goto probefail_out;
strcpy(card->driver, DRIVER_NAME);
strcpy(card->shortname, "CS5535 Audio");
sprintf(card->longname, "%s %s at 0x%lx, irq %i",
card->shortname, card->driver,
cs5535au->port, cs5535au->irq);
if ((err = snd_card_register(card)) < 0)
goto probefail_out;
pci_set_drvdata(pci, card);
dev++;
return 0;
probefail_out:
snd_card_free(card);
return err;
}
static void snd_cs5535audio_remove(struct pci_dev *pci)
{
olpc_quirks_cleanup();
snd_card_free(pci_get_drvdata(pci));
}
static struct pci_driver cs5535audio_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_cs5535audio_ids,
.probe = snd_cs5535audio_probe,
.remove = snd_cs5535audio_remove,
#ifdef CONFIG_PM_SLEEP
.driver = {
.pm = &snd_cs5535audio_pm,
},
#endif
};
module_pci_driver(cs5535audio_driver);
MODULE_AUTHOR("Jaya Kumar");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CS5535 Audio");
MODULE_SUPPORTED_DEVICE("CS5535 Audio");

Some files were not shown because too many files have changed in this diff Show more