diff --git a/CREDITS b/CREDITS index 79fd13dbb8e4..10c214dc95e7 100644 --- a/CREDITS +++ b/CREDITS @@ -2212,13 +2212,13 @@ S: 2300 Copenhagen S S: Denmark N: Claudio S. Matsuoka -E: claudio@conectiva.com -E: claudio@helllabs.org +E: cmatsuoka@gmail.com +E: claudio@mandriva.com W: http://helllabs.org/~claudio -D: V4L, OV511 driver hacks +D: V4L, OV511 and HDA-codec hacks S: Conectiva S.A. -S: R. Tocantins 89 -S: 80050-430 Curitiba PR +S: Souza Naves 1250 +S: 80050-040 Curitiba PR S: Brazil N: Heinz Mauelshagen diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 355ff0a2bb7c..241e26c4ff92 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -467,7 +467,12 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. above explicitly. The power-management is supported. - + + Module snd-cs5530 + _________________ + + Module for Cyrix/NatSemi Geode 5530 chip. + Module snd-cs5535audio ---------------------- @@ -759,6 +764,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. model - force the model name position_fix - Fix DMA pointer (0 = auto, 1 = none, 2 = POSBUF, 3 = FIFO size) + probe_mask - Bitmask to probe codecs (default = -1, meaning all slots) single_cmd - Use single immediate commands to communicate with codecs (for debugging only) enable_msi - Enable Message Signaled Interrupt (MSI) (default = off) @@ -803,6 +809,8 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. hp-3013 HP machines (3013-variant) fujitsu Fujitsu S7020 acer Acer TravelMate + will Will laptops (PB V7900) + replacer Replacer 672V basic fixed pin assignment (old default model) auto auto-config reading BIOS (default) @@ -811,16 +819,31 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. hp-bpc HP xw4400/6400/8400/9400 laptops hp-bpc-d7000 HP BPC D7000 benq Benq ED8 + benq-t31 Benq T31 hippo Hippo (ATI) with jack detection, Sony UX-90s hippo_1 Hippo (Benq) with jack detection + sony-assamd Sony ASSAMD basic fixed pin assignment w/o SPDIF auto auto-config reading BIOS (default) + ALC268 + 3stack 3-stack model + auto auto-config reading BIOS (default) + + ALC662 + 3stack-dig 3-stack (2-channel) with SPDIF + 3stack-6ch 3-stack (6-channel) + 3stack-6ch-dig 3-stack (6-channel) with SPDIF + 6stack-dig 6-stack with SPDIF + lenovo-101e Lenovo laptop + auto auto-config reading BIOS (default) + ALC882/885 3stack-dig 3-jack with SPDIF I/O 6stack-dig 6-jack digital with SPDIF I/O arima Arima W820Di1 macpro MacPro support + imac24 iMac 24'' with jack detection w2jc ASUS W2JC auto auto-config reading BIOS (default) @@ -832,9 +855,15 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. 6stack-dig-demo 6-jack digital for Intel demo board acer Acer laptops (Travelmate 3012WTMi, Aspire 5600, etc) medion Medion Laptops + medion-md2 Medion MD2 targa-dig Targa/MSI targa-2ch-dig Targs/MSI with 2-channel laptop-eapd 3-jack with SPDIF I/O and EAPD (Clevo M540JE, M550JE) + lenovo-101e Lenovo 101E + lenovo-nb0763 Lenovo NB0763 + lenovo-ms7195-dig Lenovo MS7195 + 6stack-hp HP machines with 6stack (Nettle boards) + 3stack-hp HP machines with 3stack (Lucknow, Samba boards) auto auto-config reading BIOS (default) ALC861/660 @@ -853,7 +882,9 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. 3stack-dig 3-jack with SPDIF OUT 6stack-dig 6-jack with SPDIF OUT 3stack-660 3-jack (for ALC660VD) + 3stack-660-digout 3-jack with SPDIF OUT (for ALC660VD) lenovo Lenovo 3000 C200 + dallas Dallas laptops auto auto-config reading BIOS (default) CMI9880 @@ -864,12 +895,26 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. allout 5-jack in back, 2-jack in front, SPDIF out auto auto-config reading BIOS (default) + AD1882 + 3stack 3-stack mode (default) + 6stack 6-stack mode + + AD1884 + N/A + AD1981 basic 3-jack (default) hp HP nx6320 thinkpad Lenovo Thinkpad T60/X60/Z60 toshiba Toshiba U205 + AD1983 + N/A + + AD1984 + basic default configuration + thinkpad Lenovo Thinkpad T61/X61 + AD1986A 6stack 6-jack, separate surrounds (default) 3stack 3-stack, shared surrounds @@ -907,11 +952,18 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. ref Reference board 3stack D945 3stack 5stack D945 5stack + SPDIF - macmini Intel Mac Mini - macbook Intel Mac Book - macbook-pro-v1 Intel Mac Book Pro 1st generation - macbook-pro Intel Mac Book Pro 2nd generation - imac-intel Intel iMac + dell Dell XPS M1210 + intel-mac-v1 Intel Mac Type 1 + intel-mac-v2 Intel Mac Type 2 + intel-mac-v3 Intel Mac Type 3 + intel-mac-v4 Intel Mac Type 4 + intel-mac-v5 Intel Mac Type 5 + macmini Intel Mac Mini (equivalent with type 3) + macbook Intel Mac Book (eq. type 5) + macbook-pro-v1 Intel Mac Book Pro 1st generation (eq. type 3) + macbook-pro Intel Mac Book Pro 2nd generation (eq. type 3) + imac-intel Intel iMac (eq. type 2) + imac-intel-20 Intel iMac (newer version) (eq. type 3) STAC9202/9250/9251 ref Reference board, base config @@ -956,6 +1008,17 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. from the irq. Remember this is a last resort, and should be avoided as much as possible... + MORE NOTES ON "azx_get_response timeout" PROBLEMS: + On some hardwares, you may need to add a proper probe_mask option + to avoid the "azx_get_response timeout" problem above, instead. + This occurs when the access to non-existing or non-working codec slot + (likely a modem one) causes a stall of the communication via HD-audio + bus. You can see which codec slots are probed by enabling + CONFIG_SND_DEBUG_DETECT, or simply from the file name of the codec + proc files. Then limit the slots to probe by probe_mask option. + For example, probe_mask=1 means to probe only the first slot, and + probe_mask=4 means only the third slot. + The power-management is supported. Module snd-hdsp diff --git a/Documentation/sound/alsa/Audiophile-Usb.txt b/Documentation/sound/alsa/Audiophile-Usb.txt index e40cce83327c..2ad5e6306c44 100644 --- a/Documentation/sound/alsa/Audiophile-Usb.txt +++ b/Documentation/sound/alsa/Audiophile-Usb.txt @@ -1,4 +1,4 @@ - Guide to using M-Audio Audiophile USB with ALSA and Jack v1.3 + Guide to using M-Audio Audiophile USB with ALSA and Jack v1.5 ======================================================== Thibault Le Meur @@ -6,8 +6,19 @@ This document is a guide to using the M-Audio Audiophile USB (tm) device with ALSA and JACK. +History +======= +* v1.4 - Thibault Le Meur (2007-07-11) + - Added Low Endianness nature of 16bits-modes + found by Hakan Lennestal + - Modifying document structure +* v1.5 - Thibault Le Meur (2007-07-12) + - Added AC3/DTS passthru info + + 1 - Audiophile USB Specs and correct usage ========================================== + This part is a reminder of important facts about the functions and limitations of the device. @@ -25,18 +36,18 @@ The device has 4 audio interfaces, and 2 MIDI ports: The internal DAC/ADC has the following characteristics: * sample depth of 16 or 24 bits * sample rate from 8kHz to 96kHz -* Two ports can't use different sample depths at the same time. Moreover, the -Audiophile USB documentation gives the following Warning: "Please exit any -audio application running before switching between bit depths" +* Two interfaces can't use different sample depths at the same time. +Moreover, the Audiophile USB documentation gives the following Warning: +"Please exit any audio application running before switching between bit depths" Due to the USB 1.1 bandwidth limitation, a limited number of interfaces can be activated at the same time depending on the audio mode selected: - * 16-bit/48kHz ==> 4 channels in/4 channels out + * 16-bit/48kHz ==> 4 channels in + 4 channels out - Ai+Ao+Di+Do - * 24-bit/48kHz ==> 4 channels in/2 channels out, - or 2 channels in/4 channels out + * 24-bit/48kHz ==> 4 channels in + 2 channels out, + or 2 channels in + 4 channels out - Ai+Ao+Do or Ai+Di+Ao or Ai+Di+Do or Di+Ao+Do - * 24-bit/96kHz ==> 2 channels in, or 2 channels out (half duplex only) + * 24-bit/96kHz ==> 2 channels in _or_ 2 channels out (half duplex only) - Ai or Ao or Di or Do Important facts about the Digital interface: @@ -52,44 +63,56 @@ source is connected synchronization error (for instance sound played at an odd sample rate) -2 - Audiophile USB support in ALSA -================================== +2 - Audiophile USB MIDI support in ALSA +======================================= -2.1 - MIDI ports ----------------- -The Audiophile USB MIDI ports will be automatically supported once the +The Audiophile USB MIDI ports will be automatically supported once the following modules have been loaded: * snd-usb-audio * snd-seq-midi No additional setting is required. -2.2 - Audio ports ------------------ + +3 - Audiophile USB Audio support in ALSA +======================================== Audio functions of the Audiophile USB device are handled by the snd-usb-audio module. This module can work in a default mode (without any device-specific parameter), or in an "advanced" mode with the device-specific parameter called "device_setup". -2.2.1 - Default Alsa driver mode +3.1 - Default Alsa driver mode +------------------------------ -The default behavior of the snd-usb-audio driver is to parse the device -capabilities at startup and enable all functions inside the device (including -all ports at any supported sample rates and sample depths). This approach -has the advantage to let the driver easily switch from sample rates/depths -automatically according to the need of the application claiming the device. +The default behavior of the snd-usb-audio driver is to list the device +capabilities at startup and activate the required mode when required +by the applications: for instance if the user is recording in a +24bit-depth-mode and immediately after wants to switch to a 16bit-depth mode, +the snd-usb-audio module will reconfigure the device on the fly. -In this case the Audiophile ports are mapped to alsa pcm devices in the -following way (I suppose the device's index is 1): +This approach has the advantage to let the driver automatically switch from sample +rates/depths automatically according to the user's needs. However, those who +are using the device under windows know that this is not how the device is meant to +work: under windows applications must be closed before using the m-audio control +panel to switch the device working mode. Thus as we'll see in next section, this +Default Alsa driver mode can lead to device misconfigurations. + +Let's get back to the Default Alsa driver mode for now. In this case the +Audiophile interfaces are mapped to alsa pcm devices in the following +way (I suppose the device's index is 1): * hw:1,0 is Ao in playback and Di in capture * hw:1,1 is Do in playback and Ai in capture * hw:1,2 is Do in AC3/DTS passthrough mode -You must note as well that the device uses Big Endian byte encoding so that -supported audio format are S16_BE for 16-bit depth modes and S24_3BE for -24-bits depth mode. One exception is the hw:1,2 port which is Little Endian -compliant and thus uses S16_LE. +In this mode, the device uses Big Endian byte-encoding so that +supported audio format are S16_BE for 16-bit depth modes and S24_3BE for +24-bits depth mode. + +One exception is the hw:1,2 port which was reported to be Little Endian +compliant (supposedly supporting S16_LE) but processes in fact only S16_BE streams. +This has been fixed in kernel 2.6.23 and above and now the hw:1,2 interface +is reported to be big endian in this default driver mode. Examples: * playing a S24_3BE encoded raw file to the Ao port @@ -98,22 +121,26 @@ Examples: % arecord -D hw:1,1 -c2 -t raw -r48000 -fS24_3BE test.raw * playing a S16_BE encoded raw file to the Do port % aplay -D hw:1,1 -c2 -t raw -r48000 -fS16_BE test.raw + * playing an ac3 sample file to the Do port + % aplay -D hw:1,2 --channels=6 ac3_S16_BE_encoded_file.raw -If you're happy with the default Alsa driver setup and don't experience any +If you're happy with the default Alsa driver mode and don't experience any issue with this mode, then you can skip the following chapter. -2.2.2 - Advanced module setup +3.2 - Advanced module setup +--------------------------- Due to the hardware constraints described above, the device initialization made by the Alsa driver in default mode may result in a corrupted state of the device. For instance, a particularly annoying issue is that the sound captured -from the Ai port sounds distorted (as if boosted with an excessive high volume -gain). +from the Ai interface sounds distorted (as if boosted with an excessive high +volume gain). For people having this problem, the snd-usb-audio module has a new module -parameter called "device_setup". +parameter called "device_setup" (this parameter was introduced in kernel +release 2.6.17) -2.2.2.1 - Initializing the working mode of the Audiophile USB +3.2.1 - Initializing the working mode of the Audiophile USB As far as the Audiophile USB device is concerned, this value let the user specify: @@ -121,33 +148,57 @@ specify: * the sample rate * whether the Di port is used or not -Here is a list of supported device_setup values for this device: - * device_setup=0x00 (or omitted) - - Alsa driver default mode - - maintains backward compatibility with setups that do not use this - parameter by not introducing any change - - results sometimes in corrupted sound as described earlier +When initialized with "device_setup=0x00", the snd-usb-audio module has +the same behaviour as when the parameter is omitted (see paragraph "Default +Alsa driver mode" above) + +Others modes are described in the following subsections. + +3.2.1.1 - 16-bit modes + +The two supported modes are: + * device_setup=0x01 - 16bits 48kHz mode with Di disabled - Ai,Ao,Do can be used at the same time - hw:1,0 is not available in capture mode - hw:1,2 is not available + * device_setup=0x11 - 16bits 48kHz mode with Di enabled - Ai,Ao,Di,Do can be used at the same time - hw:1,0 is available in capture mode - hw:1,2 is not available + +In this modes the device operates only at 16bits-modes. Before kernel 2.6.23, +the devices where reported to be Big-Endian when in fact they were Little-Endian +so that playing a file was a matter of using: + % aplay -D hw:1,1 -c2 -t raw -r48000 -fS16_BE test_S16_LE.raw +where "test_S16_LE.raw" was in fact a little-endian sample file. + +Thanks to Hakan Lennestal (who discovered the Little-Endiannes of the device in +these modes) a fix has been committed (expected in kernel 2.6.23) and +Alsa now reports Little-Endian interfaces. Thus playing a file now is as simple as +using: + % aplay -D hw:1,1 -c2 -t raw -r48000 -fS16_LE test_S16_LE.raw + +3.2.1.2 - 24-bit modes + +The three supported modes are: + * device_setup=0x09 - 24bits 48kHz mode with Di disabled - Ai,Ao,Do can be used at the same time - hw:1,0 is not available in capture mode - hw:1,2 is not available + * device_setup=0x19 - 24bits 48kHz mode with Di enabled - 3 ports from {Ai,Ao,Di,Do} can be used at the same time - hw:1,0 is available in capture mode and an active digital source must be connected to Di - hw:1,2 is not available + * device_setup=0x0D or 0x10 - 24bits 96kHz mode - Di is enabled by default for this mode but does not need to be connected @@ -155,34 +206,64 @@ Here is a list of supported device_setup values for this device: - Only 1 port from {Ai,Ao,Di,Do} can be used at the same time - hw:1,0 is available in captured mode - hw:1,2 is not available + +In these modes the device is only Big-Endian compliant (see "Default Alsa driver +mode" above for an aplay command example) + +3.2.1.3 - AC3 w/ DTS passthru mode + +Thanks to Hakan Lennestal, I now have a report saying that this mode works. + * device_setup=0x03 - 16bits 48kHz mode with only the Do port enabled - - AC3 with DTS passthru (not tested) + - AC3 with DTS passthru - Caution with this setup the Do port is mapped to the pcm device hw:1,0 -2.2.2.2 - Setting and switching configurations with the device_setup parameter +The command line used to playback the AC3/DTS encoded .wav-files in this mode: + % aplay -D hw:1,0 --channels=6 ac3_S16_LE_encoded_file.raw + +3.2.2 - How to use the device_setup parameter +---------------------------------------------- The parameter can be given: + * By manually probing the device (as root): # modprobe -r snd-usb-audio # modprobe snd-usb-audio index=1 device_setup=0x09 + * Or while configuring the modules options in your modules configuration file - For Fedora distributions, edit the /etc/modprobe.conf file: alias snd-card-1 snd-usb-audio options snd-usb-audio index=1 device_setup=0x09 -IMPORTANT NOTE WHEN SWITCHING CONFIGURATION: -------------------------------------------- - * You may need to _first_ initialize the module with the correct device_setup - parameter and _only_after_ turn on the Audiophile USB device - * This is especially true when switching the sample depth: +CAUTION when initializaing the device +------------------------------------- + + * Correct initialization on the device requires that device_setup is given to + the module BEFORE the device is turned on. So, if you use the "manual probing" + method described above, take care to power-on the device AFTER this initialization. + + * Failing to respect this will lead in a misconfiguration of the device. In this case + turn off the device, unproble the snd-usb-audio module, then probe it again with + correct device_setup parameter and then (and only then) turn on the device again. + + * If you've correctly initialized the device in a valid mode and then want to switch + to another mode (possibly with another sample-depth), please use also the following + procedure: - first turn off the device - de-register the snd-usb-audio module (modprobe -r) - change the device_setup parameter by changing the device_setup option in /etc/modprobe.conf - turn on the device + * A workaround for this last issue has been applied to kernel 2.6.23, but it may not + be enough to ensure the 'stability' of the device initialization. -2.2.2.3 - Audiophile USB's device_setup structure +3.2.3 - Technical details for hackers +------------------------------------- +This section is for hackers, wanting to understand details about the device +internals and how Alsa supports it. + +3.2.3.1 - Audiophile USB's device_setup structure If you want to understand the device_setup magic numbers for the Audiophile USB, you need some very basic understanding of binary computation. However, @@ -228,12 +309,12 @@ Caution: - choosing b2 will prepare all interfaces for 24bits/96kHz but you'll only be able to use one at the same time -2.2.3 - USB implementation details for this device +3.2.3.2 - USB implementation details for this device You may safely skip this section if you're not interested in driver -development. +hacking. -This section describes some internal aspects of the device and summarize the +This section describes some internal aspects of the device and summarizes the data I got by usb-snooping the windows and Linux drivers. The M-Audio Audiophile USB has 7 USB Interfaces: @@ -293,43 +374,45 @@ parse_audio_endpoints function uses a quirk called "audiophile_skip_setting_quirk" in order to prevent AltSettings not corresponding to device_setup from being registered in the driver. -3 - Audiophile USB and Jack support +4 - Audiophile USB and Jack support =================================== This section deals with support of the Audiophile USB device in Jack. -The main issue regarding this support is that the device is Big Endian -compliant. -3.1 - Using the plug alsa plugin --------------------------------- +There are 2 main potential issues when using Jackd with the device: +* support for Big-Endian devices in 24-bit modes +* support for 4-in / 4-out channels -Jack doesn't directly support big endian devices. Thus, one way to have support -for this device with Alsa is to use the Alsa "plug" converter. +4.1 - Direct support in Jackd +----------------------------- + +Jack supports big endian devices only in recent versions (thanks to +Andreas Steinmetz for his first big-endian patch). I can't remember +extacly when this support was released into jackd, let's just say that +with jackd version 0.103.0 it's almost ok (just a small bug is affecting +16bits Big-Endian devices, but since you've read carefully the above +paragraphs, you're now using kernel >= 2.6.23 and your 16bits devices +are now Little Endians ;-) ). + +You can run jackd with the following command for playback with Ao and +record with Ai: + % jackd -R -dalsa -Phw:1,0 -r48000 -p128 -n2 -D -Chw:1,1 + +4.2 - Using Alsa plughw +----------------------- +If you don't have a recent Jackd installed, you can downgrade to using +the Alsa "plug" converter. For instance here is one way to run Jack with 2 playback channels on Ao and 2 capture channels from Ai: % jackd -R -dalsa -dplughw:1 -r48000 -p256 -n2 -D -Cplughw:1,1 - However you may see the following warning message: "You appear to be using the ALSA software "plug" layer, probably a result of using the "default" ALSA device. This is less efficient than it could be. Consider using a hardware device instead rather than using the plug layer." -3.2 - Patching alsa to use direct pcm device --------------------------------------------- -A patch for Jack by Andreas Steinmetz adds support for Big Endian devices. -However it has not been included in the CVS tree. - -You can find it at the following URL: -http://sourceforge.net/tracker/index.php?func=detail&aid=1289682&group_id=39687& -atid=425939 - -After having applied the patch you can run jackd with the following command -line: - % jackd -R -dalsa -Phw:1,0 -r48000 -p128 -n2 -D -Chw:1,1 - -3.2 - Getting 2 input and/or output interfaces in Jack +4.3 - Getting 2 input and/or output interfaces in Jack ------------------------------------------------------ As you can see, starting the Jack server this way will only enable 1 stereo @@ -339,6 +422,7 @@ This is due to the following restrictions: * Jack can only open one capture device and one playback device at a time * The Audiophile USB is seen as 2 (or three) Alsa devices: hw:1,0, hw:1,1 (and optionally hw:1,2) + If you want to get Ai+Di and/or Ao+Do support with Jack, you would need to combine the Alsa devices into one logical "complex" device. @@ -348,13 +432,11 @@ It is related to another device (ice1712) but can be adapted to suit the Audiophile USB. Enabling multiple Audiophile USB interfaces for Jackd will certainly require: -* patching Jack with the previously mentioned "Big Endian" patch -* patching Jackd with the MMAP_COMPLEX patch (see the ice1712 page) -* patching the alsa-lib/src/pcm/pcm_multi.c file (see the ice1712 page) +* Making sure your Jackd version has the MMAP_COMPLEX patch (see the ice1712 page) +* (maybe) patching the alsa-lib/src/pcm/pcm_multi.c file (see the ice1712 page) * define a multi device (combination of hw:1,0 and hw:1,1) in your .asoundrc file * start jackd with this device -I had no success in testing this for now, but this may be due to my OS -configuration. If you have any success with this kind of setup, please -drop me an email. +I had no success in testing this for now, if you have any success with this kind +of setup, please drop me an email. diff --git a/Documentation/sound/alsa/OSS-Emulation.txt b/Documentation/sound/alsa/OSS-Emulation.txt index ec2a02541d5b..bfa0c9aacb4b 100644 --- a/Documentation/sound/alsa/OSS-Emulation.txt +++ b/Documentation/sound/alsa/OSS-Emulation.txt @@ -278,6 +278,21 @@ current mixer configuration by reading and writing the whole file image. +Duplex Streams +============== + +Note that when attempting to use a single device file for playback and +capture, the OSS API provides no way to set the format, sample rate or +number of channels different in each direction. Thus + io_handle = open("device", O_RDWR) +will only function correctly if the values are the same in each direction. + +To use different values in the two directions, use both + input_handle = open("device", O_RDONLY) + output_handle = open("device", O_WRONLY) +and set the values for the corresponding handle. + + Unsupported Features ==================== diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index aa83d4163096..b69014865714 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -115,9 +115,10 @@ #define I2C_DRIVERID_KS0127 86 /* Samsung ks0127 video decoder */ #define I2C_DRIVERID_TLV320AIC23B 87 /* TI TLV320AIC23B audio codec */ #define I2C_DRIVERID_ISL1208 88 /* Intersil ISL1208 RTC */ -#define I2C_DRIVERID_WM8731 89 /* Wolfson WM8731 audio codec */ -#define I2C_DRIVERID_WM8750 90 /* Wolfson WM8750 audio codec */ -#define I2C_DRIVERID_WM8753 91 /* Wolfson WM8753 audio codec */ +#define I2C_DRIVERID_WM8731 89 /* Wolfson WM8731 audio codec */ +#define I2C_DRIVERID_WM8750 90 /* Wolfson WM8750 audio codec */ +#define I2C_DRIVERID_WM8753 91 /* Wolfson WM8753 audio codec */ +#define I2C_DRIVERID_LM4857 92 /* LM4857 Audio Amplifier */ #define I2C_DRIVERID_I2CDEV 900 #define I2C_DRIVERID_ARP 902 /* SMBus ARP Client */ diff --git a/include/sound/ak4xxx-adda.h b/include/sound/ak4xxx-adda.h index aa49dda4f410..fd0a6c46f497 100644 --- a/include/sound/ak4xxx-adda.h +++ b/include/sound/ak4xxx-adda.h @@ -43,6 +43,7 @@ struct snd_ak4xxx_ops { struct snd_akm4xxx_dac_channel { char *name; /* mixer volume name */ unsigned int num_channels; + char *switch_name; /* mixer switch*/ }; /* ADC labels and channels */ diff --git a/include/sound/cs46xx.h b/include/sound/cs46xx.h index 685928e6f65a..353910ce9755 100644 --- a/include/sound/cs46xx.h +++ b/include/sound/cs46xx.h @@ -1723,6 +1723,10 @@ struct snd_cs46xx { struct snd_cs46xx_pcm *playback_pcm; unsigned int play_ctl; #endif + +#ifdef CONFIG_PM + u32 *saved_regs; +#endif }; int snd_cs46xx_create(struct snd_card *card, diff --git a/include/sound/cs46xx_dsp_spos.h b/include/sound/cs46xx_dsp_spos.h index da934def31e9..d9da9e59cf37 100644 --- a/include/sound/cs46xx_dsp_spos.h +++ b/include/sound/cs46xx_dsp_spos.h @@ -107,6 +107,7 @@ 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; @@ -127,6 +128,7 @@ struct dsp_task_descriptor { int size; u32 address; int index; + u32 *data; }; struct dsp_pcm_channel_descriptor { diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 23e45a4cf0e4..529d0a564367 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1120,6 +1120,16 @@ /************************************************************************************************/ /* EMU1010m HANA Destinations */ /************************************************************************************************/ +/* 32-bit destinations of signal in the Hana FPGA. Destinations are either + * physical outputs of Hana, or outputs going to Alice2 (audigy) for capture + * - 16 x EMU_DST_ALICE2_EMU32_X. + */ +/* EMU32 = 32-bit serial channel between Alice2 (audigy) and Hana (FPGA) */ +/* EMU_DST_ALICE2_EMU32_X - data channels from Hana to Alice2 used for capture. + * Which data is fed into a EMU_DST_ALICE2_EMU32_X channel in Hana depends on + * setup of mixer control for each destination - see emumixer.c - + * snd_emu1010_output_enum_ctls[], snd_emu1010_input_enum_ctls[] + */ #define EMU_DST_ALICE2_EMU32_0 0x000f /* 16 EMU32 channels to Alice2 +0 to +0xf */ #define EMU_DST_ALICE2_EMU32_1 0x0000 /* 16 EMU32 channels to Alice2 +0 to +0xf */ #define EMU_DST_ALICE2_EMU32_2 0x0001 /* 16 EMU32 channels to Alice2 +0 to +0xf */ @@ -1199,6 +1209,12 @@ /************************************************************************************************/ /* EMU1010m HANA Sources */ /************************************************************************************************/ +/* 32-bit sources of signal in the Hana FPGA. The sources are routed to + * destinations using mixer control for each destination - see emumixer.c + * Sources are either physical inputs of FPGA, + * or outputs from Alice (audigy) - 16 x EMU_SRC_ALICE_EMU32A + + * 16 x EMU_SRC_ALICE_EMU32B + */ #define EMU_SRC_SILENCE 0x0000 /* Silence */ #define EMU_SRC_DOCK_MIC_A1 0x0100 /* Audio Dock Mic A, 1st or 48kHz only */ #define EMU_SRC_DOCK_MIC_A2 0x0101 /* Audio Dock Mic A, 2nd or 96kHz */ diff --git a/include/sound/sb.h b/include/sound/sb.h index 2dd5c8e5b4fe..3ad854b397d2 100644 --- a/include/sound/sb.h +++ b/include/sound/sb.h @@ -38,6 +38,7 @@ enum sb_hw_type { SB_HW_ALS100, /* Avance Logic ALS100 chip */ SB_HW_ALS4000, /* Avance Logic ALS4000 chip */ SB_HW_DT019X, /* Diamond Tech. DT-019X / Avance Logic ALS-007 */ + SB_HW_CS5530, /* Cyrix/NatSemi 5530 VSA1 */ }; #define SB_OPEN_PCM 0x01 diff --git a/include/sound/version.h b/include/sound/version.h index 8e5b2f0f5946..6bbcfefd2c38 100644 --- a/include/sound/version.h +++ b/include/sound/version.h @@ -1,3 +1,3 @@ /* include/version.h. Generated by alsa/ksync script. */ #define CONFIG_SND_VERSION "1.0.14" -#define CONFIG_SND_DATE " (Thu May 31 09:03:25 2007 UTC)" +#define CONFIG_SND_DATE " (Fri Jul 20 09:12:58 2007 UTC)" diff --git a/include/sound/wavefront_fx.h b/include/sound/wavefront_fx.h deleted file mode 100644 index cec92b141796..000000000000 --- a/include/sound/wavefront_fx.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef __SOUND_WAVEFRONT_FX_H -#define __SOUND_WAVEFRONT_FX_H - -extern int snd_wavefront_fx_detect (snd_wavefront_t *); -extern void snd_wavefront_fx_ioctl (snd_synth_t *sdev, - unsigned int cmd, - unsigned long arg); - -#endif __SOUND_WAVEFRONT_FX_H diff --git a/sound/Kconfig b/sound/Kconfig index 9ea473823418..e48b9b37d228 100644 --- a/sound/Kconfig +++ b/sound/Kconfig @@ -65,6 +65,8 @@ source "sound/arm/Kconfig" source "sound/mips/Kconfig" +source "sound/sh/Kconfig" + # the following will depend on the order of config. # here assuming USB is defined before ALSA source "sound/usb/Kconfig" diff --git a/sound/Makefile b/sound/Makefile index b7c7fb7c24c8..3ead922bd9c6 100644 --- a/sound/Makefile +++ b/sound/Makefile @@ -5,7 +5,7 @@ obj-$(CONFIG_SOUND) += soundcore.o obj-$(CONFIG_SOUND_PRIME) += sound_firmware.o obj-$(CONFIG_SOUND_PRIME) += oss/ obj-$(CONFIG_DMASOUND) += oss/ -obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ synth/ usb/ sparc/ parisc/ pcmcia/ mips/ soc/ +obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ sparc/ parisc/ pcmcia/ mips/ soc/ obj-$(CONFIG_SND_AOA) += aoa/ # This one must be compilable even if sound is configured out diff --git a/sound/aoa/codecs/snd-aoa-codec-onyx.c b/sound/aoa/codecs/snd-aoa-codec-onyx.c index ded516717940..028852374f21 100644 --- a/sound/aoa/codecs/snd-aoa-codec-onyx.c +++ b/sound/aoa/codecs/snd-aoa-codec-onyx.c @@ -661,7 +661,7 @@ static struct transfer_info onyx_transfers[] = { .tag = 2, }, #ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE -Once alsa gets supports for this kind of thing we can add it... + /* Once alsa gets supports for this kind of thing we can add it... */ { /* digital compressed output */ .formats = SNDRV_PCM_FMTBIT_COMPRESSED_16BE, @@ -713,7 +713,7 @@ static int onyx_prepare(struct codec_info_item *cii, if (substream->runtime->format == SNDRV_PCM_FMTBIT_COMPRESSED_16BE) { /* mute and lock analog output */ onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v); - if (onyx_write_register(onyx + if (onyx_write_register(onyx, ONYX_REG_DAC_CONTROL, v | ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT)) goto out_unlock; diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index a96733a5beb8..59b29cd482ae 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1487,7 +1487,7 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream) snd_pcm_stream_lock_irq(substream); /* resume pause */ - if (runtime->status->state == SNDRV_PCM_STATE_PAUSED) + if (substream->runtime->status->state == SNDRV_PCM_STATE_PAUSED) snd_pcm_pause(substream, 0); /* pre-start/stop - all running streams are changed to DRAINING state */ diff --git a/sound/core/seq/seq_instr.c b/sound/core/seq/seq_instr.c index f30d171b6d96..5efe6523a589 100644 --- a/sound/core/seq/seq_instr.c +++ b/sound/core/seq/seq_instr.c @@ -109,7 +109,7 @@ void snd_seq_instr_list_free(struct snd_seq_kinstr_list **list_ptr) spin_lock_irqsave(&list->lock, flags); while (instr->use) { spin_unlock_irqrestore(&list->lock, flags); - schedule_timeout_interruptible(1); + schedule_timeout(1); spin_lock_irqsave(&list->lock, flags); } spin_unlock_irqrestore(&list->lock, flags); @@ -199,7 +199,7 @@ int snd_seq_instr_list_free_cond(struct snd_seq_kinstr_list *list, instr = flist; flist = instr->next; while (instr->use) - schedule_timeout_interruptible(1); + schedule_timeout(1); if (snd_seq_instr_free(instr, atomic)<0) snd_printk(KERN_WARNING "instrument free problem\n"); instr = next; @@ -555,7 +555,7 @@ static int instr_free(struct snd_seq_kinstr_ops *ops, SNDRV_SEQ_INSTR_NOTIFY_REMOVE); while (instr->use) { spin_unlock_irqrestore(&list->lock, flags); - schedule_timeout_interruptible(1); + schedule_timeout(1); spin_lock_irqsave(&list->lock, flags); } spin_unlock_irqrestore(&list->lock, flags); diff --git a/sound/core/timer.c b/sound/core/timer.c index 67520b3c0042..f2bbacedd567 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -1549,9 +1549,11 @@ static int snd_timer_user_info(struct file *file, int err = 0; tu = file->private_data; - snd_assert(tu->timeri != NULL, return -ENXIO); + if (!tu->timeri) + return -EBADFD; t = tu->timeri->timer; - snd_assert(t != NULL, return -ENXIO); + if (!t) + return -EBADFD; info = kzalloc(sizeof(*info), GFP_KERNEL); if (! info) @@ -1579,9 +1581,11 @@ static int snd_timer_user_params(struct file *file, int err; tu = file->private_data; - snd_assert(tu->timeri != NULL, return -ENXIO); + if (!tu->timeri) + return -EBADFD; t = tu->timeri->timer; - snd_assert(t != NULL, return -ENXIO); + if (!t) + return -EBADFD; if (copy_from_user(¶ms, _params, sizeof(params))) return -EFAULT; if (!(t->hw.flags & SNDRV_TIMER_HW_SLAVE) && params.ticks < 1) { @@ -1675,7 +1679,8 @@ static int snd_timer_user_status(struct file *file, struct snd_timer_status status; tu = file->private_data; - snd_assert(tu->timeri != NULL, return -ENXIO); + if (!tu->timeri) + return -EBADFD; memset(&status, 0, sizeof(status)); status.tstamp = tu->tstamp; status.resolution = snd_timer_resolution(tu->timeri); @@ -1695,7 +1700,8 @@ static int snd_timer_user_start(struct file *file) struct snd_timer_user *tu; tu = file->private_data; - snd_assert(tu->timeri != NULL, return -ENXIO); + if (!tu->timeri) + return -EBADFD; snd_timer_stop(tu->timeri); tu->timeri->lost = 0; tu->last_resolution = 0; @@ -1708,7 +1714,8 @@ static int snd_timer_user_stop(struct file *file) struct snd_timer_user *tu; tu = file->private_data; - snd_assert(tu->timeri != NULL, return -ENXIO); + if (!tu->timeri) + return -EBADFD; return (err = snd_timer_stop(tu->timeri)) < 0 ? err : 0; } @@ -1718,7 +1725,8 @@ static int snd_timer_user_continue(struct file *file) struct snd_timer_user *tu; tu = file->private_data; - snd_assert(tu->timeri != NULL, return -ENXIO); + if (!tu->timeri) + return -EBADFD; tu->timeri->lost = 0; return (err = snd_timer_continue(tu->timeri)) < 0 ? err : 0; } @@ -1729,7 +1737,8 @@ static int snd_timer_user_pause(struct file *file) struct snd_timer_user *tu; tu = file->private_data; - snd_assert(tu->timeri != NULL, return -ENXIO); + if (!tu->timeri) + return -EBADFD; return (err = snd_timer_pause(tu->timeri)) < 0 ? err : 0; } diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index a0f28f51fc7e..4360ae9de19c 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -659,7 +659,7 @@ static struct platform_driver snd_dummy_driver = { }, }; -static void __init_or_module snd_dummy_unregister_all(void) +static void snd_dummy_unregister_all(void) { int i; diff --git a/sound/drivers/mpu401/mpu401.c b/sound/drivers/mpu401/mpu401.c index 1d563e515c17..67c6e9745418 100644 --- a/sound/drivers/mpu401/mpu401.c +++ b/sound/drivers/mpu401/mpu401.c @@ -228,7 +228,7 @@ static struct pnp_driver snd_mpu401_pnp_driver = { static struct pnp_driver snd_mpu401_pnp_driver; #endif -static void __init_or_module snd_mpu401_unregister_all(void) +static void snd_mpu401_unregister_all(void) { int i; diff --git a/sound/drivers/portman2x4.c b/sound/drivers/portman2x4.c index 497cafb57d9b..0eb9b5cebfcd 100644 --- a/sound/drivers/portman2x4.c +++ b/sound/drivers/portman2x4.c @@ -833,7 +833,7 @@ static struct platform_driver snd_portman_driver = { /********************************************************************* * module init stuff *********************************************************************/ -static void __init_or_module snd_portman_unregister_all(void) +static void snd_portman_unregister_all(void) { int i; diff --git a/sound/drivers/serial-u16550.c b/sound/drivers/serial-u16550.c index 838a4277929d..d3e6a20edd38 100644 --- a/sound/drivers/serial-u16550.c +++ b/sound/drivers/serial-u16550.c @@ -998,7 +998,7 @@ static struct platform_driver snd_serial_driver = { }, }; -static void __init_or_module snd_serial_unregister_all(void) +static void snd_serial_unregister_all(void) { int i; diff --git a/sound/drivers/virmidi.c b/sound/drivers/virmidi.c index 46f3d3486067..915c86773c21 100644 --- a/sound/drivers/virmidi.c +++ b/sound/drivers/virmidi.c @@ -145,7 +145,7 @@ static struct platform_driver snd_virmidi_driver = { }, }; -static void __init_or_module snd_virmidi_unregister_all(void) +static void snd_virmidi_unregister_all(void) { int i; diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index 8805110017a7..fd335159f849 100644 --- a/sound/i2c/other/ak4xxx-adda.c +++ b/sound/i2c/other/ak4xxx-adda.c @@ -481,8 +481,8 @@ static int ak4xxx_switch_get(struct snd_kcontrol *kcontrol, int addr = AK_GET_ADDR(kcontrol->private_value); int shift = AK_GET_SHIFT(kcontrol->private_value); int invert = AK_GET_INVERT(kcontrol->private_value); - unsigned char val = snd_akm4xxx_get(ak, chip, addr); - + /* we observe the (1<value.integer.value[0] = (val & (1<num_dacs; ) { + /* mute control for Revolution 7.1 - AK4381 */ + if (ak->type == SND_AK4381 + && ak->dac_info[mixer_ch].switch_name) { + memset(&knew, 0, sizeof(knew)); + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.count = 1; + knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + knew.name = ak->dac_info[mixer_ch].switch_name; + knew.info = ak4xxx_switch_info; + knew.get = ak4xxx_switch_get; + knew.put = ak4xxx_switch_put; + knew.access = 0; + /* register 1, bit 0 (SMUTE): 0 = normal operation, + 1 = mute */ + knew.private_value = + AK_COMPOSE(idx/2, 1, 0, 0) | AK_INVERT; + err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak)); + if (err < 0) + return err; + } memset(&knew, 0, sizeof(knew)); if (! ak->dac_info || ! ak->dac_info[mixer_ch].name) { knew.name = "DAC Volume"; diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig index cf3803cd579c..ea5084abe60f 100644 --- a/sound/isa/Kconfig +++ b/sound/isa/Kconfig @@ -1,8 +1,5 @@ # ALSA ISA drivers -menu "ISA devices" - depends on SND!=n && ISA && ISA_DMA_API - config SND_AD1848_LIB tristate select SND_PCM @@ -11,6 +8,22 @@ config SND_CS4231_LIB tristate select SND_PCM +config SND_SB_COMMON + tristate + +config SND_SB8_DSP + tristate + select SND_PCM + select SND_SB_COMMON + +config SND_SB16_DSP + tristate + select SND_PCM + select SND_SB_COMMON + +menu "ISA devices" + depends on SND!=n && ISA && ISA_DMA_API + config SND_ADLIB tristate "AdLib FM card" depends on SND @@ -55,7 +68,7 @@ config SND_ALS100 select ISAPNP select SND_OPL3_LIB select SND_MPU401_UART - select SND_PCM + select SND_SB16_DSP help Say Y here to include support for soundcards based on Avance Logic ALS100, ALS110, ALS120 and ALS200 chips. @@ -81,6 +94,7 @@ config SND_CMI8330 tristate "C-Media CMI8330" depends on SND select SND_AD1848_LIB + select SND_SB16_DSP help Say Y here to include support for soundcards based on the C-Media CMI8330 chip. @@ -132,7 +146,7 @@ config SND_DT019X select ISAPNP select SND_OPL3_LIB select SND_MPU401_UART - select SND_PCM + select SND_SB16_DSP help Say Y here to include support for soundcards based on the Diamond Technologies DT-019X or Avance Logic ALS-007 chips. @@ -145,7 +159,7 @@ config SND_ES968 depends on SND && PNP && ISA select ISAPNP select SND_MPU401_UART - select SND_PCM + select SND_SB8_DSP help Say Y here to include support for ESS AudioDrive ES968 chips. @@ -321,7 +335,7 @@ config SND_SB8 depends on SND select SND_OPL3_LIB select SND_RAWMIDI - select SND_PCM + select SND_SB8_DSP help Say Y here to include support for Creative Sound Blaster 1.0/ 2.0/Pro (8-bit) or 100% compatible soundcards. @@ -334,7 +348,7 @@ config SND_SB16 depends on SND select SND_OPL3_LIB select SND_MPU401_UART - select SND_PCM + select SND_SB16_DSP help Say Y here to include support for Sound Blaster 16 soundcards (including the Plug and Play version). @@ -347,7 +361,7 @@ config SND_SBAWE depends on SND select SND_OPL3_LIB select SND_MPU401_UART - select SND_PCM + select SND_SB16_DSP help Say Y here to include support for Sound Blaster AWE soundcards (including the Plug and Play version). diff --git a/sound/isa/ad1848/ad1848_lib.c b/sound/isa/ad1848/ad1848_lib.c index 8094282c2ae1..1bc2e3fd5721 100644 --- a/sound/isa/ad1848/ad1848_lib.c +++ b/sound/isa/ad1848/ad1848_lib.c @@ -245,7 +245,7 @@ static void snd_ad1848_mce_down(struct snd_ad1848 *chip) snd_printk(KERN_ERR "mce_down - auto calibration time out (2)\n"); return; } - time = schedule_timeout_interruptible(time); + time = schedule_timeout(time); spin_lock_irqsave(&chip->reg_lock, flags); } #if 0 @@ -258,7 +258,7 @@ static void snd_ad1848_mce_down(struct snd_ad1848 *chip) snd_printk(KERN_ERR "mce_down - auto calibration time out (3)\n"); return; } - time = schedule_timeout_interruptible(time); + time = schedule_timeout(time); spin_lock_irqsave(&chip->reg_lock, flags); } spin_unlock_irqrestore(&chip->reg_lock, flags); diff --git a/sound/isa/opl3sa2.c b/sound/isa/opl3sa2.c index 4f6800b43b0e..e70db32991d9 100644 --- a/sound/isa/opl3sa2.c +++ b/sound/isa/opl3sa2.c @@ -164,6 +164,8 @@ static struct pnp_card_device_id snd_opl3sa2_pnpids[] = { { .id = "YMH0801", .devs = { { "YMH0021" } } }, /* NeoMagic MagicWave 3DX */ { .id = "NMX2200", .devs = { { "YMH2210" } } }, + /* NeoMagic MagicWave 3D */ + { .id = "NMX2200", .devs = { { "NMX2210" } } }, /* --- */ { .id = "" } /* end */ }; diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c index 60c120ffb9de..049d479ce2b3 100644 --- a/sound/isa/opti9xx/opti92x-ad1848.c +++ b/sound/isa/opti9xx/opti92x-ad1848.c @@ -1927,10 +1927,12 @@ static struct snd_card *snd_opti9xx_card_new(void) static int __devinit snd_opti9xx_isa_match(struct device *devptr, unsigned int dev) { +#ifdef CONFIG_PNP if (snd_opti9xx_pnp_is_probed) return 0; if (isapnp) return 0; +#endif return 1; } @@ -2096,6 +2098,7 @@ static int __init alsa_card_opti9xx_init(void) pnp_register_card_driver(&opti9xx_pnpc_driver); if (snd_opti9xx_pnp_is_probed) return 0; + pnp_unregister_card_driver(&opti9xx_pnpc_driver); #endif return isa_register_driver(&snd_opti9xx_driver, 1); } diff --git a/sound/isa/sb/Makefile b/sound/isa/sb/Makefile index fd9d9c5726fc..556e66928029 100644 --- a/sound/isa/sb/Makefile +++ b/sound/isa/sb/Makefile @@ -22,14 +22,13 @@ snd-es968-objs := es968.o sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) # Toplevel Module Dependency -obj-$(CONFIG_SND_ALS100) += snd-sb16-dsp.o snd-sb-common.o -obj-$(CONFIG_SND_CMI8330) += snd-sb16-dsp.o snd-sb-common.o -obj-$(CONFIG_SND_DT019X) += snd-sb16-dsp.o snd-sb-common.o -obj-$(CONFIG_SND_SB8) += snd-sb8.o snd-sb8-dsp.o snd-sb-common.o -obj-$(CONFIG_SND_SB16) += snd-sb16.o snd-sb16-dsp.o snd-sb-common.o -obj-$(CONFIG_SND_SBAWE) += snd-sbawe.o snd-sb16-dsp.o snd-sb-common.o -obj-$(CONFIG_SND_ES968) += snd-es968.o snd-sb8-dsp.o snd-sb-common.o -obj-$(CONFIG_SND_ALS4000) += snd-sb-common.o +obj-$(CONFIG_SND_SB_COMMON) += snd-sb-common.o +obj-$(CONFIG_SND_SB16_DSP) += snd-sb16-dsp.o +obj-$(CONFIG_SND_SB8_DSP) += snd-sb8-dsp.o +obj-$(CONFIG_SND_SB8) += snd-sb8.o +obj-$(CONFIG_SND_SB16) += snd-sb16.o +obj-$(CONFIG_SND_SBAWE) += snd-sbawe.o +obj-$(CONFIG_SND_ES968) += snd-es968.o ifeq ($(CONFIG_SND_SB16_CSP),y) obj-$(CONFIG_SND_SB16) += snd-sb16-csp.o obj-$(CONFIG_SND_SBAWE) += snd-sb16-csp.o diff --git a/sound/isa/sb/sb16_main.c b/sound/isa/sb/sb16_main.c index 383911b9e74d..5d4d3aafe2d5 100644 --- a/sound/isa/sb/sb16_main.c +++ b/sound/isa/sb/sb16_main.c @@ -563,6 +563,11 @@ static int snd_sb16_playback_open(struct snd_pcm_substream *substream) __open_ok: if (chip->hardware == SB_HW_ALS100) runtime->hw.rate_max = 48000; + if (chip->hardware == SB_HW_CS5530) { + runtime->hw.buffer_bytes_max = 32 * 1024; + runtime->hw.periods_min = 2; + runtime->hw.rate_min = 44100; + } if (chip->mode & SB_RATE_LOCK) runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate; chip->playback_substream = substream; @@ -633,6 +638,11 @@ static int snd_sb16_capture_open(struct snd_pcm_substream *substream) __open_ok: if (chip->hardware == SB_HW_ALS100) runtime->hw.rate_max = 48000; + if (chip->hardware == SB_HW_CS5530) { + runtime->hw.buffer_bytes_max = 32 * 1024; + runtime->hw.periods_min = 2; + runtime->hw.rate_min = 44100; + } if (chip->mode & SB_RATE_LOCK) runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate; chip->capture_substream = substream; diff --git a/sound/isa/sb/sb_common.c b/sound/isa/sb/sb_common.c index 3094f3852167..efa9d5c2558a 100644 --- a/sound/isa/sb/sb_common.c +++ b/sound/isa/sb/sb_common.c @@ -128,7 +128,7 @@ static int snd_sbdsp_probe(struct snd_sb * chip) minor = version & 0xff; snd_printdd("SB [0x%lx]: DSP chip found, version = %i.%i\n", chip->port, major, minor); - + switch (chip->hardware) { case SB_HW_AUTO: switch (major) { @@ -168,6 +168,9 @@ static int snd_sbdsp_probe(struct snd_sb * chip) case SB_HW_DT019X: str = "(DT019X/ALS007)"; break; + case SB_HW_CS5530: + str = "16 (CS5530)"; + break; default: return -ENODEV; } diff --git a/sound/isa/sb/sb_mixer.c b/sound/isa/sb/sb_mixer.c index 490b1ca5cf58..3d4befcff28e 100644 --- a/sound/isa/sb/sb_mixer.c +++ b/sound/isa/sb/sb_mixer.c @@ -821,6 +821,7 @@ int snd_sbmixer_new(struct snd_sb *chip) break; case SB_HW_16: case SB_HW_ALS100: + case SB_HW_CS5530: if ((err = snd_sbmixer_init(chip, snd_sb16_controls, ARRAY_SIZE(snd_sb16_controls), @@ -950,6 +951,7 @@ void snd_sbmixer_suspend(struct snd_sb *chip) break; case SB_HW_16: case SB_HW_ALS100: + case SB_HW_CS5530: save_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs)); break; case SB_HW_ALS4000: @@ -975,6 +977,7 @@ void snd_sbmixer_resume(struct snd_sb *chip) break; case SB_HW_16: case SB_HW_ALS100: + case SB_HW_CS5530: restore_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs)); break; case SB_HW_ALS4000: diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c index 9ea417bcf3e5..cbad2a51cbaa 100644 --- a/sound/isa/sscape.c +++ b/sound/isa/sscape.c @@ -382,7 +382,7 @@ static int obp_startup_ack(struct soundscape *s, unsigned timeout) unsigned long flags; unsigned char x; - schedule_timeout_interruptible(1); + schedule_timeout(1); spin_lock_irqsave(&s->lock, flags); x = inb(HOST_DATA_IO(s->io_base)); @@ -409,7 +409,7 @@ static int host_startup_ack(struct soundscape *s, unsigned timeout) unsigned long flags; unsigned char x; - schedule_timeout_interruptible(1); + schedule_timeout(1); spin_lock_irqsave(&s->lock, flags); x = inb(HOST_DATA_IO(s->io_base)); diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c index 78020d832e04..bacc51c86587 100644 --- a/sound/isa/wavefront/wavefront_synth.c +++ b/sound/isa/wavefront/wavefront_synth.c @@ -1780,7 +1780,7 @@ wavefront_should_cause_interrupt (snd_wavefront_t *dev, outb (val,port); spin_unlock_irq(&dev->irq_lock); while (1) { - if ((timeout = schedule_timeout_interruptible(timeout)) == 0) + if ((timeout = schedule_timeout(timeout)) == 0) return; if (dev->irq_ok) return; diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 61e35ecc57b8..c6b44102aa5b 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -33,6 +33,7 @@ config SND_ALS4000 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. @@ -215,6 +216,16 @@ config SND_CS46XX_NEW_DSP This works better than the old code, so say Y. +config SND_CS5530 + tristate "CS5530 Audio" + depends on SND && ISA_DMA_API + 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 SND && X86 && !X86_64 diff --git a/sound/pci/Makefile b/sound/pci/Makefile index e06736da9ef1..cd76e0293d06 100644 --- a/sound/pci/Makefile +++ b/sound/pci/Makefile @@ -12,6 +12,7 @@ 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 snd-ens1371-objs := ens1371.o snd-es1938-objs := es1938.o @@ -36,6 +37,7 @@ 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 diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c index 41543a4933e7..05b4c8696941 100644 --- a/sound/pci/ali5451/ali5451.c +++ b/sound/pci/ali5451/ali5451.c @@ -239,7 +239,7 @@ struct snd_ali_image { struct snd_ali { - unsigned long irq; + int irq; unsigned long port; unsigned char revision; @@ -731,8 +731,7 @@ static void snd_ali_detect_spdif_rate(struct snd_ali *codec) return; } - count = 0; - while (count++ <= 50000) { + for (count = 0; count <= 50000; count++) { snd_ali_delay(codec, 6); bval = inb(ALI_REG(codec,ALI_SPDIF_CTRL + 1)); R2 = bval & 0x1F; @@ -2343,7 +2342,7 @@ static int __devinit snd_ali_probe(struct pci_dev *pci, strcpy(card->driver, "ALI5451"); strcpy(card->shortname, "ALI 5451"); - sprintf(card->longname, "%s at 0x%lx, irq %li", + sprintf(card->longname, "%s at 0x%lx, irq %i", card->shortname, codec->port, codec->irq); snd_ali_printk("register card.\n"); diff --git a/sound/pci/als300.c b/sound/pci/als300.c index 8afcb98ca7bb..48cc39b771d9 100644 --- a/sound/pci/als300.c +++ b/sound/pci/als300.c @@ -88,8 +88,8 @@ #define PLAYBACK_BLOCK_COUNTER 0x9A #define RECORD_BLOCK_COUNTER 0x9B -#define DEBUG_CALLS 1 -#define DEBUG_PLAY_REC 1 +#define DEBUG_CALLS 0 +#define DEBUG_PLAY_REC 0 #if DEBUG_CALLS #define snd_als300_dbgcalls(format, args...) printk(format, ##args) @@ -733,7 +733,8 @@ static int __devinit snd_als300_create(struct snd_card *card, snd_als300_init(chip); - if (snd_als300_ac97(chip) < 0) { + err = snd_als300_ac97(chip); + if (err < 0) { snd_printk(KERN_WARNING "Could not create ac97\n"); snd_als300_free(chip); return err; diff --git a/sound/pci/ca0106/ca0106_main.c b/sound/pci/ca0106/ca0106_main.c index 9fd7b8a5b75e..fcab8fb97e38 100644 --- a/sound/pci/ca0106/ca0106_main.c +++ b/sound/pci/ca0106/ca0106_main.c @@ -168,6 +168,25 @@ MODULE_PARM_DESC(subsystem, "Force card subsystem model."); #include "ca0106.h" static struct snd_ca0106_details ca0106_chip_details[] = { + /* Sound Blaster X-Fi Extreme Audio. This does not have an AC97. 53SB079000000 */ + /* It is really just a normal SB Live 24bit. */ + /* + * CTRL:CA0111-WTLF + * ADC: WM8775SEDS + * DAC: CS4382-KQZ + */ + /* Tested: + * Playback on front, rear, center/lfe speakers + * Capture from Mic in. + * Not-Tested: + * Capture from Line in. + * Playback to digital out. + */ + { .serial = 0x10121102, + .name = "X-Fi Extreme Audio [SB0790]", + .gpio_type = 1, + .i2c_adc = 1 } , + /* New Dell Sound Blaster Live! 7.1 24bit. This does not have an AC97. */ /* AudigyLS[SB0310] */ { .serial = 0x10021102, .name = "AudigyLS [SB0310]", diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c index bef1f6d1859c..71d7aab9d869 100644 --- a/sound/pci/cs46xx/cs46xx_lib.c +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -2897,6 +2897,10 @@ static int snd_cs46xx_free(struct snd_cs46xx *chip) } #endif +#ifdef CONFIG_PM + kfree(chip->saved_regs); +#endif + pci_disable_device(chip->pci); kfree(chip); return 0; @@ -3140,6 +3144,23 @@ static int snd_cs46xx_chip_init(struct snd_cs46xx *chip) /* * start and load DSP */ + +static void cs46xx_enable_stream_irqs(struct snd_cs46xx *chip) +{ + unsigned int tmp; + + snd_cs46xx_pokeBA0(chip, BA0_HICR, HICR_IEV | HICR_CHGM); + + tmp = snd_cs46xx_peek(chip, BA1_PFIE); + tmp &= ~0x0000f03f; + snd_cs46xx_poke(chip, BA1_PFIE, tmp); /* playback interrupt enable */ + + tmp = snd_cs46xx_peek(chip, BA1_CIE); + tmp &= ~0x0000003f; + tmp |= 0x00000001; + snd_cs46xx_poke(chip, BA1_CIE, tmp); /* capture interrupt enable */ +} + int __devinit snd_cs46xx_start_dsp(struct snd_cs46xx *chip) { unsigned int tmp; @@ -3214,19 +3235,7 @@ int __devinit snd_cs46xx_start_dsp(struct snd_cs46xx *chip) snd_cs46xx_proc_start(chip); - /* - * Enable interrupts on the part. - */ - snd_cs46xx_pokeBA0(chip, BA0_HICR, HICR_IEV | HICR_CHGM); - - tmp = snd_cs46xx_peek(chip, BA1_PFIE); - tmp &= ~0x0000f03f; - snd_cs46xx_poke(chip, BA1_PFIE, tmp); /* playback interrupt enable */ - - tmp = snd_cs46xx_peek(chip, BA1_CIE); - tmp &= ~0x0000003f; - tmp |= 0x00000001; - snd_cs46xx_poke(chip, BA1_CIE, tmp); /* capture interrupt enable */ + cs46xx_enable_stream_irqs(chip); #ifndef CONFIG_SND_CS46XX_NEW_DSP /* set the attenuation to 0dB */ @@ -3665,11 +3674,19 @@ static struct cs_card_type __devinitdata cards[] = { * APM support */ #ifdef CONFIG_PM +static unsigned int saved_regs[] = { + BA0_ACOSV, + BA0_ASER_FADDR, + BA0_ASER_MASTER, + BA1_PVOL, + BA1_CVOL, +}; + int snd_cs46xx_suspend(struct pci_dev *pci, pm_message_t state) { struct snd_card *card = pci_get_drvdata(pci); struct snd_cs46xx *chip = card->private_data; - int amp_saved; + int i, amp_saved; snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); chip->in_suspend = 1; @@ -3680,6 +3697,10 @@ int snd_cs46xx_suspend(struct pci_dev *pci, pm_message_t state) snd_ac97_suspend(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]); snd_ac97_suspend(chip->ac97[CS46XX_SECONDARY_CODEC_INDEX]); + /* save some registers */ + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + chip->saved_regs[i] = snd_cs46xx_peekBA0(chip, saved_regs[i]); + amp_saved = chip->amplifier; /* turn off amp */ chip->amplifier_ctrl(chip, -chip->amplifier); @@ -3698,7 +3719,7 @@ int snd_cs46xx_resume(struct pci_dev *pci) { struct snd_card *card = pci_get_drvdata(pci); struct snd_cs46xx *chip = card->private_data; - int amp_saved; + int i, amp_saved; pci_set_power_state(pci, PCI_D0); pci_restore_state(pci); @@ -3716,6 +3737,16 @@ int snd_cs46xx_resume(struct pci_dev *pci) snd_cs46xx_chip_init(chip); + snd_cs46xx_reset(chip); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + cs46xx_dsp_resume(chip); + /* restore some registers */ + for (i = 0; i < ARRAY_SIZE(saved_regs); i++) + snd_cs46xx_pokeBA0(chip, saved_regs[i], chip->saved_regs[i]); +#else + snd_cs46xx_download_image(chip); +#endif + #if 0 snd_cs46xx_codec_write(chip, BA0_AC97_GENERAL_PURPOSE, chip->ac97_general_purpose); @@ -3730,6 +3761,13 @@ int snd_cs46xx_resume(struct pci_dev *pci) snd_ac97_resume(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]); snd_ac97_resume(chip->ac97[CS46XX_SECONDARY_CODEC_INDEX]); + /* reset playback/capture */ + snd_cs46xx_set_play_sample_rate(chip, 8000); + snd_cs46xx_set_capture_sample_rate(chip, 8000); + snd_cs46xx_proc_start(chip); + + cs46xx_enable_stream_irqs(chip); + if (amp_saved) chip->amplifier_ctrl(chip, 1); /* turn amp on */ else @@ -3896,6 +3934,15 @@ int __devinit snd_cs46xx_create(struct snd_card *card, snd_cs46xx_proc_init(card, chip); +#ifdef CONFIG_PM + chip->saved_regs = kmalloc(sizeof(*chip->saved_regs) * + ARRAY_SIZE(saved_regs), GFP_KERNEL); + if (!chip->saved_regs) { + snd_cs46xx_free(chip); + return -ENOMEM; + } +#endif + chip->active_ctrl(chip, -1); /* disable CLKRUN */ snd_card_set_dev(card, &pci->dev); diff --git a/sound/pci/cs46xx/cs46xx_lib.h b/sound/pci/cs46xx/cs46xx_lib.h index f75750c2bd24..20dcd72f06c1 100644 --- a/sound/pci/cs46xx/cs46xx_lib.h +++ b/sound/pci/cs46xx/cs46xx_lib.h @@ -86,6 +86,9 @@ static inline unsigned int snd_cs46xx_peekBA0(struct snd_cs46xx *chip, unsigned 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 +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 diff --git a/sound/pci/cs46xx/dsp_spos.c b/sound/pci/cs46xx/dsp_spos.c index 336e77e2600c..590b35d91df2 100644 --- a/sound/pci/cs46xx/dsp_spos.c +++ b/sound/pci/cs46xx/dsp_spos.c @@ -306,13 +306,59 @@ void cs46xx_dsp_spos_destroy (struct snd_cs46xx * chip) mutex_unlock(&chip->spos_mutex); } +static int dsp_load_parameter(struct snd_cs46xx *chip, + struct dsp_segment_desc *parameter) +{ + u32 doffset, dsize; + + if (!parameter) { + snd_printdd("dsp_spos: module got no parameter segment\n"); + return 0; + } + + doffset = (parameter->offset * 4 + DSP_PARAMETER_BYTE_OFFSET); + dsize = parameter->size * 4; + + snd_printdd("dsp_spos: " + "downloading parameter data to chip (%08x-%08x)\n", + doffset,doffset + dsize); + if (snd_cs46xx_download (chip, parameter->data, doffset, dsize)) { + snd_printk(KERN_ERR "dsp_spos: " + "failed to download parameter data to DSP\n"); + return -EINVAL; + } + return 0; +} + +static int dsp_load_sample(struct snd_cs46xx *chip, + struct dsp_segment_desc *sample) +{ + u32 doffset, dsize; + + if (!sample) { + snd_printdd("dsp_spos: module got no sample segment\n"); + return 0; + } + + doffset = (sample->offset * 4 + DSP_SAMPLE_BYTE_OFFSET); + dsize = sample->size * 4; + + snd_printdd("dsp_spos: downloading sample data to chip (%08x-%08x)\n", + doffset,doffset + dsize); + + if (snd_cs46xx_download (chip,sample->data,doffset,dsize)) { + snd_printk(KERN_ERR "dsp_spos: failed to sample data to DSP\n"); + return -EINVAL; + } + return 0; +} + int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * module) { struct dsp_spos_instance * ins = chip->dsp_spos_instance; struct dsp_segment_desc * code = get_segment_desc (module,SEGTYPE_SP_PROGRAM); - struct dsp_segment_desc * parameter = get_segment_desc (module,SEGTYPE_SP_PARAMETER); - struct dsp_segment_desc * sample = get_segment_desc (module,SEGTYPE_SP_SAMPLE); u32 doffset, dsize; + int err; if (ins->nmodules == DSP_MAX_MODULES - 1) { snd_printk(KERN_ERR "dsp_spos: to many modules loaded into DSP\n"); @@ -326,49 +372,20 @@ int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * m snd_cs46xx_clear_BA1(chip, DSP_PARAMETER_BYTE_OFFSET, DSP_PARAMETER_BYTE_SIZE); } - if (parameter == NULL) { - snd_printdd("dsp_spos: module got no parameter segment\n"); - } else { - if (ins->nmodules > 0) { - snd_printk(KERN_WARNING "dsp_spos: WARNING current parameter data may be overwriten!\n"); - } - - doffset = (parameter->offset * 4 + DSP_PARAMETER_BYTE_OFFSET); - dsize = parameter->size * 4; - - snd_printdd("dsp_spos: downloading parameter data to chip (%08x-%08x)\n", - doffset,doffset + dsize); - - if (snd_cs46xx_download (chip, parameter->data, doffset, dsize)) { - snd_printk(KERN_ERR "dsp_spos: failed to download parameter data to DSP\n"); - return -EINVAL; - } - } + err = dsp_load_parameter(chip, get_segment_desc(module, + SEGTYPE_SP_PARAMETER)); + if (err < 0) + return err; if (ins->nmodules == 0) { snd_printdd("dsp_spos: clearing sample area\n"); snd_cs46xx_clear_BA1(chip, DSP_SAMPLE_BYTE_OFFSET, DSP_SAMPLE_BYTE_SIZE); } - if (sample == NULL) { - snd_printdd("dsp_spos: module got no sample segment\n"); - } else { - if (ins->nmodules > 0) { - snd_printk(KERN_WARNING "dsp_spos: WARNING current sample data may be overwriten\n"); - } - - doffset = (sample->offset * 4 + DSP_SAMPLE_BYTE_OFFSET); - dsize = sample->size * 4; - - snd_printdd("dsp_spos: downloading sample data to chip (%08x-%08x)\n", - doffset,doffset + dsize); - - if (snd_cs46xx_download (chip,sample->data,doffset,dsize)) { - snd_printk(KERN_ERR "dsp_spos: failed to sample data to DSP\n"); - return -EINVAL; - } - } - + err = dsp_load_sample(chip, get_segment_desc(module, + SEGTYPE_SP_SAMPLE)); + if (err < 0) + return err; if (ins->nmodules == 0) { snd_printdd("dsp_spos: clearing code area\n"); @@ -986,7 +1003,10 @@ _map_task_tree (struct snd_cs46xx *chip, char * name, u32 dest, u32 size) return NULL; } - strcpy(ins->tasks[ins->ntask].task_name,name); + if (name) + strcpy(ins->tasks[ins->ntask].task_name, name); + else + strcpy(ins->tasks[ins->ntask].task_name, "(NULL)"); ins->tasks[ins->ntask].address = dest; ins->tasks[ins->ntask].size = size; @@ -995,7 +1015,8 @@ _map_task_tree (struct snd_cs46xx *chip, char * name, u32 dest, u32 size) desc = (ins->tasks + ins->ntask); ins->ntask++; - add_symbol (chip,name,dest,SYMBOL_PARAMETER); + if (name) + add_symbol (chip,name,dest,SYMBOL_PARAMETER); return desc; } @@ -1006,6 +1027,7 @@ cs46xx_dsp_create_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data, u32 desc = _map_scb (chip,name,dest); if (desc) { + desc->data = scb_data; _dsp_create_scb(chip,scb_data,dest); } else { snd_printk(KERN_ERR "dsp_spos: failed to map SCB\n"); @@ -1023,6 +1045,7 @@ cs46xx_dsp_create_task_tree (struct snd_cs46xx *chip, char * name, u32 * task_da desc = _map_task_tree (chip,name,dest,size); if (desc) { + desc->data = task_data; _dsp_create_task_tree(chip,task_data,dest,size); } else { snd_printk(KERN_ERR "dsp_spos: failed to map TASK\n"); @@ -1320,8 +1343,10 @@ int cs46xx_dsp_scb_and_task_init (struct snd_cs46xx *chip) 0x0000ffff }; - /* dirty hack ... */ - _dsp_create_task_tree (chip,(u32 *)&mix2_ostream_spb,WRITE_BACK_SPB,2); + if (!cs46xx_dsp_create_task_tree(chip, NULL, + (u32 *)&mix2_ostream_spb, + WRITE_BACK_SPB, 2)) + goto _fail_end; } /* input sample converter */ @@ -1622,7 +1647,6 @@ static int cs46xx_dsp_async_init (struct snd_cs46xx *chip, return 0; } - static void cs46xx_dsp_disable_spdif_hw (struct snd_cs46xx *chip) { struct dsp_spos_instance * ins = chip->dsp_spos_instance; @@ -1894,3 +1918,61 @@ int cs46xx_dsp_set_iec958_volume (struct snd_cs46xx * chip, u16 left, u16 right) return 0; } + +#ifdef CONFIG_PM +int cs46xx_dsp_resume(struct snd_cs46xx * chip) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int i, err; + + /* clear parameter, sample and code areas */ + snd_cs46xx_clear_BA1(chip, DSP_PARAMETER_BYTE_OFFSET, + DSP_PARAMETER_BYTE_SIZE); + snd_cs46xx_clear_BA1(chip, DSP_SAMPLE_BYTE_OFFSET, + DSP_SAMPLE_BYTE_SIZE); + snd_cs46xx_clear_BA1(chip, DSP_CODE_BYTE_OFFSET, DSP_CODE_BYTE_SIZE); + + for (i = 0; i < ins->nmodules; i++) { + struct dsp_module_desc *module = &ins->modules[i]; + struct dsp_segment_desc *seg; + u32 doffset, dsize; + + seg = get_segment_desc(module, SEGTYPE_SP_PARAMETER); + err = dsp_load_parameter(chip, seg); + if (err < 0) + return err; + + seg = get_segment_desc(module, SEGTYPE_SP_SAMPLE); + err = dsp_load_sample(chip, seg); + if (err < 0) + return err; + + seg = get_segment_desc(module, SEGTYPE_SP_PROGRAM); + if (!seg) + continue; + + doffset = seg->offset * 4 + module->load_address * 4 + + DSP_CODE_BYTE_OFFSET; + dsize = seg->size * 4; + err = snd_cs46xx_download(chip, + ins->code.data + module->load_address, + doffset, dsize); + if (err < 0) + return err; + } + + for (i = 0; i < ins->ntask; i++) { + struct dsp_task_descriptor *t = &ins->tasks[i]; + _dsp_create_task_tree(chip, t->data, t->address, t->size); + } + + for (i = 0; i < ins->nscb; i++) { + struct dsp_scb_descriptor *s = &ins->scbs[i]; + if (s->deleted) + continue; + _dsp_create_scb(chip, s->data, s->address); + } + + return 0; +} +#endif diff --git a/sound/pci/cs5530.c b/sound/pci/cs5530.c new file mode 100644 index 000000000000..240a0a462209 --- /dev/null +++ b/sound/pci/cs5530.c @@ -0,0 +1,306 @@ +/* + * cs5530.c - Initialisation code for Cyrix/NatSemi VSA1 softaudio + * + * (C) Copyright 2007 Ash Willis + * (C) Copyright 2003 Red Hat Inc + * + * 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 +#include +#include +#include +#include +#include +#include + +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 int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +struct snd_cs5530 { + struct snd_card *card; + struct pci_dev *pci; + struct snd_sb *sb; + unsigned long pci_base; +}; + +static 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 __devexit snd_cs5530_remove(struct pci_dev *pci) +{ + snd_card_free(pci_get_drvdata(pci)); + pci_set_drvdata(pci, NULL); +} + +static u8 __devinit 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 __devinit 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 = ioremap_nocache(chip->pci_base, pci_resource_len(pci, 0)); + if (mem == NULL) { + kfree(chip); + pci_disable_device(pci); + 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)) + printk(KERN_INFO "CS5530: XpressAudio at 0x%lx\n", sb_base); + else { + printk(KERN_ERR "Could not find XpressAudio!\n"); + snd_cs5530_free(chip); + return -ENODEV; + } + + if (map & (1<<5)) + printk(KERN_INFO "CS5530: MPU at 0x300\n"); + else if (map & (1<<6)) + printk(KERN_INFO "CS5530: 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 { + printk(KERN_ERR "CS5530: 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 { + printk(KERN_ERR "CS5530: 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 { + printk(KERN_ERR "CS5530: SoundBlaster IRQ not set\n"); + snd_cs5530_free(chip); + return -ENODEV; + } + + printk(KERN_INFO "CS5530: 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) { + printk(KERN_ERR "CS5530: Could not create SoundBlaster\n"); + snd_cs5530_free(chip); + return err; + } + + err = snd_sb16dsp_pcm(chip->sb, 0, &chip->sb->pcm); + if (err < 0) { + printk(KERN_ERR "CS5530: Could not create PCM\n"); + snd_cs5530_free(chip); + return err; + } + + err = snd_sbmixer_new(chip->sb); + if (err < 0) { + printk(KERN_ERR "CS5530: 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; + } + + snd_card_set_dev(card, &pci->dev); + *rchip = chip; + return 0; +} + +static int __devinit 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; + } + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + + if (card == NULL) + return -ENOMEM; + + 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 driver = { + .name = "CS5530_Audio", + .id_table = snd_cs5530_ids, + .probe = snd_cs5530_probe, + .remove = __devexit_p(snd_cs5530_remove), +}; + +static int __init alsa_card_cs5530_init(void) +{ + return pci_register_driver(&driver); +} + +static void __exit alsa_card_cs5530_exit(void) +{ + pci_unregister_driver(&driver); +} + +module_init(alsa_card_cs5530_init) +module_exit(alsa_card_cs5530_exit) + diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 4a9b59ad8ab1..404ae1be0a4b 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -51,9 +51,15 @@ #define HANA_FILENAME "emu/hana.fw" #define DOCK_FILENAME "emu/audio_dock.fw" +#define EMU1010B_FILENAME "emu/emu1010b.fw" +#define MICRO_DOCK_FILENAME "emu/micro_dock.fw" +#define EMU1010_NOTEBOOK_FILENAME "emu/emu1010_notebook.fw" MODULE_FIRMWARE(HANA_FILENAME); MODULE_FIRMWARE(DOCK_FILENAME); +MODULE_FIRMWARE(EMU1010B_FILENAME); +MODULE_FIRMWARE(MICRO_DOCK_FILENAME); +MODULE_FIRMWARE(EMU1010_NOTEBOOK_FILENAME); /************************************************************************* @@ -660,10 +666,12 @@ static int snd_emu1010_load_firmware(struct snd_emu10k1 * emu, const char * file return err; } snd_printk(KERN_INFO "firmware size=0x%zx\n", fw_entry->size); +#if 0 if (fw_entry->size != 0x133a4) { snd_printk(KERN_ERR "firmware: %s wrong size.\n",filename); return -EINVAL; } +#endif /* The FPGA is a Xilinx Spartan IIE XC2S50E */ /* GPIO7 -> FPGA PGMN @@ -694,6 +702,37 @@ static int snd_emu1010_load_firmware(struct snd_emu10k1 * emu, const char * file return 0; } +/* + * EMU-1010 - details found out from this driver, official MS Win drivers, + * testing the card: + * + * Audigy2 (aka Alice2): + * --------------------- + * * communication over PCI + * * conversion of 32-bit data coming over EMU32 links from HANA FPGA + * to 2 x 16-bit, using internal DSP instructions + * * slave mode, clock supplied by HANA + * * linked to HANA using: + * 32 x 32-bit serial EMU32 output channels + * 16 x EMU32 input channels + * (?) x I2S I/O channels (?) + * + * FPGA (aka HANA): + * --------------- + * * provides all (?) physical inputs and outputs of the card + * (ADC, DAC, SPDIF I/O, ADAT I/O, etc.) + * * provides clock signal for the card and Alice2 + * * two crystals - for 44.1kHz and 48kHz multiples + * * provides internal routing of signal sources to signal destinations + * * inputs/outputs to Alice2 - see above + * + * Current status of the driver: + * ---------------------------- + * * only 44.1/48kHz supported (the MS Win driver supports up to 192 kHz) + * * PCM device nb. 2: + * 16 x 16-bit playback - snd_emu10k1_fx8010_playback_ops + * 16 x 32-bit capture - snd_emu10k1_capture_efx_ops + */ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 * emu) { unsigned int i; @@ -727,7 +766,7 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 * emu) /* ID, should read & 0x7f = 0x55. (Bit 7 is the IRQ bit) */ snd_emu1010_fpga_read(emu, EMU_HANA_ID, ® ); snd_printdd("reg1=0x%x\n",reg); - if (reg == 0x55) { + if ((reg & 0x3f) == 0x15) { /* FPGA netlist already present so clear it */ /* Return to programming mode */ @@ -735,19 +774,32 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 * emu) } snd_emu1010_fpga_read(emu, EMU_HANA_ID, ® ); snd_printdd("reg2=0x%x\n",reg); - if (reg == 0x55) { + if ((reg & 0x3f) == 0x15) { /* FPGA failed to return to programming mode */ + snd_printk(KERN_INFO "emu1010: FPGA failed to return to programming mode\n"); return -ENODEV; } snd_printk(KERN_INFO "emu1010: EMU_HANA_ID=0x%x\n",reg); - if ((err = snd_emu1010_load_firmware(emu, HANA_FILENAME)) != 0) { - snd_printk(KERN_INFO "emu1010: Loading Hana Firmware file %s failed\n", HANA_FILENAME); - return err; + if (emu->card_capabilities->emu1010 == 1) { + if ((err = snd_emu1010_load_firmware(emu, HANA_FILENAME)) != 0) { + snd_printk(KERN_INFO "emu1010: Loading Hana Firmware file %s failed\n", HANA_FILENAME); + return err; + } + } else if (emu->card_capabilities->emu1010 == 2) { + if ((err = snd_emu1010_load_firmware(emu, EMU1010B_FILENAME)) != 0) { + snd_printk(KERN_INFO "emu1010: Loading Firmware file %s failed\n", EMU1010B_FILENAME); + return err; + } + } else if (emu->card_capabilities->emu1010 == 3) { + if ((err = snd_emu1010_load_firmware(emu, EMU1010_NOTEBOOK_FILENAME)) != 0) { + snd_printk(KERN_INFO "emu1010: Loading Firmware file %s failed\n", EMU1010_NOTEBOOK_FILENAME); + return err; + } } /* ID, should read & 0x7f = 0x55 when FPGA programmed. */ snd_emu1010_fpga_read(emu, EMU_HANA_ID, ® ); - if (reg != 0x55) { + if ((reg & 0x3f) != 0x15) { /* FPGA failed to be programmed */ snd_printk(KERN_INFO "emu1010: Loading Hana Firmware file failed, reg=0x%x\n", reg); return -ENODEV; @@ -850,6 +902,27 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 * emu) EMU_DST_ALICE2_EMU32_6, EMU_SRC_DOCK_ADC2_LEFT1); snd_emu1010_fpga_link_dst_src_write(emu, EMU_DST_ALICE2_EMU32_7, EMU_SRC_DOCK_ADC2_RIGHT1); + /* Pavel Hofman - setting defaults for 8 more capture channels + * Defaults only, users will set their own values anyways, let's + * just copy/paste. + */ + + snd_emu1010_fpga_link_dst_src_write(emu, + EMU_DST_ALICE2_EMU32_8, EMU_SRC_DOCK_MIC_A1); + snd_emu1010_fpga_link_dst_src_write(emu, + EMU_DST_ALICE2_EMU32_9, EMU_SRC_DOCK_MIC_B1); + snd_emu1010_fpga_link_dst_src_write(emu, + EMU_DST_ALICE2_EMU32_A, EMU_SRC_HAMOA_ADC_LEFT2); + snd_emu1010_fpga_link_dst_src_write(emu, + EMU_DST_ALICE2_EMU32_B, EMU_SRC_HAMOA_ADC_LEFT2); + snd_emu1010_fpga_link_dst_src_write(emu, + EMU_DST_ALICE2_EMU32_C, EMU_SRC_DOCK_ADC1_LEFT1); + snd_emu1010_fpga_link_dst_src_write(emu, + EMU_DST_ALICE2_EMU32_D, EMU_SRC_DOCK_ADC1_RIGHT1); + snd_emu1010_fpga_link_dst_src_write(emu, + EMU_DST_ALICE2_EMU32_E, EMU_SRC_DOCK_ADC2_LEFT1); + snd_emu1010_fpga_link_dst_src_write(emu, + EMU_DST_ALICE2_EMU32_F, EMU_SRC_DOCK_ADC2_RIGHT1); #endif #if 0 /* Original */ @@ -943,16 +1016,27 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 * emu) /* Return to Audio Dock programming mode */ snd_printk(KERN_INFO "emu1010: Loading Audio Dock Firmware\n"); snd_emu1010_fpga_write(emu, EMU_HANA_FPGA_CONFIG, EMU_HANA_FPGA_CONFIG_AUDIODOCK ); - if ((err = snd_emu1010_load_firmware(emu, DOCK_FILENAME)) != 0) { - return err; + if (emu->card_capabilities->emu1010 == 1) { + if ((err = snd_emu1010_load_firmware(emu, DOCK_FILENAME)) != 0) { + return err; + } + } else if (emu->card_capabilities->emu1010 == 2) { + if ((err = snd_emu1010_load_firmware(emu, MICRO_DOCK_FILENAME)) != 0) { + return err; + } + } else if (emu->card_capabilities->emu1010 == 3) { + if ((err = snd_emu1010_load_firmware(emu, MICRO_DOCK_FILENAME)) != 0) { + return err; + } } + snd_emu1010_fpga_write(emu, EMU_HANA_FPGA_CONFIG, 0 ); snd_emu1010_fpga_read(emu, EMU_HANA_IRQ_STATUS, ® ); snd_printk(KERN_INFO "emu1010: EMU_HANA+DOCK_IRQ_STATUS=0x%x\n",reg); /* ID, should read & 0x7f = 0x55 when FPGA programmed. */ snd_emu1010_fpga_read(emu, EMU_HANA_ID, ® ); snd_printk(KERN_INFO "emu1010: EMU_HANA+DOCK_ID=0x%x\n",reg); - if (reg != 0x55) { + if ((reg & 0x3f) != 0x15) { /* FPGA failed to be programmed */ snd_printk(KERN_INFO "emu1010: Loading Audio Dock Firmware file failed, reg=0x%x\n", reg); return 0; @@ -1227,9 +1311,15 @@ static struct snd_emu_chip_details emu_chip_details[] = { .emu10k2_chip = 1, .ca0108_chip = 1, .ca_cardbus_chip = 1, - .spi_dac = 1, - .i2c_adc = 1, - .spk71 = 1} , + .spk71 = 1 , + .emu1010 = 3} , + {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40041102, + .driver = "Audigy2", .name = "E-mu 1010b PCI [MAEM????]", + .id = "EMU1010", + .emu10k2_chip = 1, + .ca0108_chip = 1, + .spk71 = 1 , + .emu1010 = 2} , {.vendor = 0x1102, .device = 0x0008, .driver = "Audigy2", .name = "Audigy 2 Value [Unknown]", .id = "Audigy2", @@ -1663,12 +1753,13 @@ int __devinit snd_emu10k1_create(struct snd_card *card, emu->fx8010.extout_mask = extout_mask; emu->enable_ir = enable_ir; + if (emu->card_capabilities->ca_cardbus_chip) { + if ((err = snd_emu10k1_cardbus_init(emu)) < 0) + goto error; + } if (emu->card_capabilities->ecard) { if ((err = snd_emu10k1_ecard_init(emu)) < 0) goto error; - } else if (emu->card_capabilities->ca_cardbus_chip) { - if ((err = snd_emu10k1_cardbus_init(emu)) < 0) - goto error; } else if (emu->card_capabilities->emu1010) { if ((err = snd_emu10k1_emu1010_init(emu)) < 0) { snd_emu10k1_free(emu); @@ -1814,10 +1905,10 @@ void snd_emu10k1_suspend_regs(struct snd_emu10k1 *emu) void snd_emu10k1_resume_init(struct snd_emu10k1 *emu) { + if (emu->card_capabilities->ca_cardbus_chip) + snd_emu10k1_cardbus_init(emu); if (emu->card_capabilities->ecard) snd_emu10k1_ecard_init(emu); - else if (emu->card_capabilities->ca_cardbus_chip) - snd_emu10k1_cardbus_init(emu); else if (emu->card_capabilities->emu1010) snd_emu10k1_emu1010_init(emu); else diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index c02012cccd8e..7206c0fa06f2 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1123,6 +1123,11 @@ snd_emu10k1_init_stereo_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl ctl->translation = EMU10K1_GPR_TRANSLATION_ONOFF; } +/* + * Used for emu1010 - conversion from 32-bit capture inputs from HANA + * to 2 x 16-bit registers in audigy - their values are read via DMA. + * Conversion is performed by Audigy DSP instructions of FX8010. + */ static int snd_emu10k1_audigy_dsp_convert_32_to_2x16( struct snd_emu10k1_fx8010_code *icode, u32 *ptr, int tmp, int bit_shifter16, @@ -1193,7 +1198,11 @@ static int __devinit _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) snd_emu10k1_ptr_write(emu, A_DBG, 0, (emu->fx8010.dbg = 0) | A_DBG_SINGLE_STEP); #if 1 - /* PCM front Playback Volume (independent from stereo mix) */ + /* PCM front Playback Volume (independent from stereo mix) + * playback = 0 + ( gpr * FXBUS_PCM_LEFT_FRONT >> 31) + * where gpr contains attenuation from corresponding mixer control + * (snd_emu10k1_init_stereo_control) + */ A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_FRONT)); A_OP(icode, &ptr, iMAC0, A_GPR(playback+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_FRONT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Front Playback Volume", gpr, 100); @@ -1549,7 +1558,7 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) if (emu->card_capabilities->emu1010) { snd_printk("EMU inputs on\n"); - /* Capture 8 channels of S32_LE sound */ + /* Capture 16 (originally 8) channels of S32_LE sound */ /* printk("emufx.c: gpr=0x%x, tmp=0x%x\n",gpr, tmp); */ /* For the EMU1010: How to get 32bit values from the DSP. High 16bits into L, low 16bits into R. */ @@ -1560,6 +1569,11 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_P16VIN(0x0), A_FXBUS2(0) ); /* Right ADC in 1 of 2 */ gpr_map[gpr++] = 0x00000000; + /* Delaying by one sample: instead of copying the input + * value A_P16VIN to output A_FXBUS2 as in the first channel, + * we use an auxiliary register, delaying the value by one + * sample + */ snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(2) ); A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x1), A_C_00000000, A_C_00000000); gpr_map[gpr++] = 0x00000000; @@ -1583,6 +1597,66 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) gpr_map[gpr++] = 0x00000000; snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xe) ); A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x7), A_C_00000000, A_C_00000000); + /* Pavel Hofman - we still have voices, A_FXBUS2s, and + * A_P16VINs available - + * let's add 8 more capture channels - total of 16 + */ + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x10)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x8), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x12)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x9), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x14)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xa), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x16)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xb), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x18)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xc), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x1a)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xd), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x1c)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xe), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x1e)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xf), + A_C_00000000, A_C_00000000); #if 0 for (z = 4; z < 8; z++) { diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 4db6e1ca1665..7b2c1dcc5337 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -77,6 +77,10 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol, return 0; } +/* + * Items labels in enum mixer controls assigning source data to + * each destination + */ static char *emu1010_src_texts[] = { "Silence", "Dock Mic A", @@ -133,6 +137,9 @@ static char *emu1010_src_texts[] = { "DSP 31", }; +/* + * List of data sources available for each destination + */ static unsigned int emu1010_src_regs[] = { EMU_SRC_SILENCE,/* 0 */ EMU_SRC_DOCK_MIC_A1, /* 1 */ @@ -189,6 +196,10 @@ static unsigned int emu1010_src_regs[] = { EMU_SRC_ALICE_EMU32B+0xf, /* 52 */ }; +/* + * Data destinations - physical EMU outputs. + * Each destination has an enum mixer control to choose a data source + */ static unsigned int emu1010_output_dst[] = { EMU_DST_DOCK_DAC1_LEFT1, /* 0 */ EMU_DST_DOCK_DAC1_RIGHT1, /* 1 */ @@ -216,6 +227,11 @@ static unsigned int emu1010_output_dst[] = { EMU_DST_HANA_ADAT+7, /* 23 */ }; +/* + * Data destinations - HANA outputs going to Alice2 (audigy) for + * capture (EMU32 + I2S links) + * Each destination has an enum mixer control to choose a data source + */ static unsigned int emu1010_input_dst[] = { EMU_DST_ALICE2_EMU32_0, EMU_DST_ALICE2_EMU32_1, diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index ab4f5df5241b..eda5cb373ded 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1233,24 +1233,26 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) runtime->hw.rate_min = runtime->hw.rate_max = 48000; spin_lock_irq(&emu->reg_lock); if (emu->card_capabilities->emu1010) { - /* TODO + /* Nb. of channels has been increased to 16 */ + /* TODO * SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE * SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | * SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | * SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000 * rate_min = 44100, * rate_max = 192000, - * channels_min = 8, - * channels_max = 8, + * channels_min = 16, + * channels_max = 16, * Need to add mixer control to fix sample rate * - * There are 16 mono channels of 16bits each. + * There are 32 mono channels of 16bits each. * 24bit Audio uses 2x channels over 16bit * 96kHz uses 2x channels over 48kHz * 192kHz uses 4x channels over 48kHz - * So, for 48kHz 24bit, one has 8 channels - * for 96kHz 24bit, one has 4 channels - * for 192kHz 24bit, one has 2 channels + * So, for 48kHz 24bit, one has 16 channels + * for 96kHz 24bit, one has 8 channels + * for 192kHz 24bit, one has 4 channels + * */ #if 1 switch (emu->emu1010.internal_clock) { @@ -1258,13 +1260,15 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) /* For 44.1kHz */ runtime->hw.rates = SNDRV_PCM_RATE_44100; runtime->hw.rate_min = runtime->hw.rate_max = 44100; - runtime->hw.channels_min = runtime->hw.channels_max = 8; + runtime->hw.channels_min = + runtime->hw.channels_max = 16; break; case 1: /* For 48kHz */ runtime->hw.rates = SNDRV_PCM_RATE_48000; runtime->hw.rate_min = runtime->hw.rate_max = 48000; - runtime->hw.channels_min = runtime->hw.channels_max = 8; + runtime->hw.channels_min = + runtime->hw.channels_max = 16; break; }; #endif @@ -1282,7 +1286,7 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) #endif runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; /* efx_voices_mask[0] is expected to be zero - * efx_voices_mask[1] is expected to have 16bits set + * efx_voices_mask[1] is expected to have 32bits set */ } else { runtime->hw.channels_min = runtime->hw.channels_max = 0; @@ -1787,11 +1791,24 @@ int __devinit snd_emu10k1_pcm_efx(struct snd_emu10k1 * emu, int device, struct s /* emu->efx_voices_mask[0] = FXWC_DEFAULTROUTE_C | FXWC_DEFAULTROUTE_A; */ if (emu->audigy) { emu->efx_voices_mask[0] = 0; - emu->efx_voices_mask[1] = 0xffff; + if (emu->card_capabilities->emu1010) + /* Pavel Hofman - 32 voices will be used for + * capture (write mode) - + * each bit = corresponding voice + */ + emu->efx_voices_mask[1] = 0xffffffff; + else + emu->efx_voices_mask[1] = 0xffff; } else { emu->efx_voices_mask[0] = 0xffff0000; emu->efx_voices_mask[1] = 0; } + /* For emu1010, the control has to set 32 upper bits (voices) + * out of the 64 bits (voices) to true for the 16-channels capture + * to work correctly. Correct A_FXWC2 initial value (0xffffffff) + * is already defined but the snd_emu10k1_pcm_efx_voices_mask + * control can override this register's value. + */ kctl = snd_ctl_new1(&snd_emu10k1_pcm_efx_voices_mask, emu); if (!kctl) return -ENOMEM; diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c index 7c403965153b..21cb4268a59b 100644 --- a/sound/pci/ens1370.c +++ b/sound/pci/ens1370.c @@ -1607,8 +1607,8 @@ struct es1371_quirk { unsigned char rev; /* revision */ }; -static int __devinit es1371_quirk_lookup(struct ensoniq *ensoniq, - struct es1371_quirk *list) +static int es1371_quirk_lookup(struct ensoniq *ensoniq, + struct es1371_quirk *list) { while (list->vid != (unsigned short)PCI_ANY_ID) { if (ensoniq->pci->vendor == list->vid && diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 2fa281cbef91..92bc8b3fa2a0 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -341,6 +341,9 @@ struct azx { unsigned int single_cmd :1; unsigned int polling_mode :1; unsigned int msi :1; + + /* for debugging */ + unsigned int last_cmd; /* last issued command (to sync) */ }; /* driver types */ @@ -466,18 +469,10 @@ static void azx_free_cmd_io(struct azx *chip) } /* send a command */ -static int azx_corb_send_cmd(struct hda_codec *codec, hda_nid_t nid, int direct, - unsigned int verb, unsigned int para) +static int azx_corb_send_cmd(struct hda_codec *codec, u32 val) { struct azx *chip = codec->bus->private_data; unsigned int wp; - u32 val; - - val = (u32)(codec->addr & 0x0f) << 28; - val |= (u32)direct << 27; - val |= (u32)nid << 20; - val |= verb << 8; - val |= para; /* add command to corb */ wp = azx_readb(chip, CORBWP); @@ -538,12 +533,12 @@ static unsigned int azx_rirb_get_response(struct hda_codec *codec) } if (! chip->rirb.cmds) return chip->rirb.res; /* the last value */ - schedule_timeout_interruptible(1); + schedule_timeout(1); } while (time_after_eq(timeout, jiffies)); if (chip->msi) { snd_printk(KERN_WARNING "hda_intel: No response from codec, " - "disabling MSI...\n"); + "disabling MSI: last cmd=0x%08x\n", chip->last_cmd); free_irq(chip->irq, chip); chip->irq = -1; pci_disable_msi(chip->pci); @@ -555,13 +550,15 @@ static unsigned int azx_rirb_get_response(struct hda_codec *codec) if (!chip->polling_mode) { snd_printk(KERN_WARNING "hda_intel: azx_get_response timeout, " - "switching to polling mode...\n"); + "switching to polling mode: last cmd=0x%08x\n", + chip->last_cmd); chip->polling_mode = 1; goto again; } snd_printk(KERN_ERR "hda_intel: azx_get_response timeout, " - "switching to single_cmd mode...\n"); + "switching to single_cmd mode: last cmd=0x%08x\n", + chip->last_cmd); chip->rirb.rp = azx_readb(chip, RIRBWP); chip->rirb.cmds = 0; /* switch to single_cmd mode */ @@ -581,20 +578,11 @@ static unsigned int azx_rirb_get_response(struct hda_codec *codec) */ /* send a command */ -static int azx_single_send_cmd(struct hda_codec *codec, hda_nid_t nid, - int direct, unsigned int verb, - unsigned int para) +static int azx_single_send_cmd(struct hda_codec *codec, u32 val) { struct azx *chip = codec->bus->private_data; - u32 val; int timeout = 50; - val = (u32)(codec->addr & 0x0f) << 28; - val |= (u32)direct << 27; - val |= (u32)nid << 20; - val |= verb << 8; - val |= para; - while (timeout--) { /* check ICB busy bit */ if (! (azx_readw(chip, IRS) & ICH6_IRS_BUSY)) { @@ -639,10 +627,19 @@ static int azx_send_cmd(struct hda_codec *codec, hda_nid_t nid, unsigned int para) { struct azx *chip = codec->bus->private_data; + u32 val; + + val = (u32)(codec->addr & 0x0f) << 28; + val |= (u32)direct << 27; + val |= (u32)nid << 20; + val |= verb << 8; + val |= para; + chip->last_cmd = val; + if (chip->single_cmd) - return azx_single_send_cmd(codec, nid, direct, verb, para); + return azx_single_send_cmd(codec, val); else - return azx_corb_send_cmd(codec, nid, direct, verb, para); + return azx_corb_send_cmd(codec, val); } /* get a response */ @@ -1788,6 +1785,12 @@ static struct pci_device_id azx_ids[] = { { 0x10de, 0x044b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_NVIDIA }, /* NVIDIA MCP65 */ { 0x10de, 0x055c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_NVIDIA }, /* NVIDIA MCP67 */ { 0x10de, 0x055d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_NVIDIA }, /* NVIDIA MCP67 */ + { 0x10de, 0x07fc, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_NVIDIA }, /* NVIDIA MCP73 */ + { 0x10de, 0x07fd, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_NVIDIA }, /* NVIDIA MCP73 */ + { 0x10de, 0x0774, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_NVIDIA }, /* NVIDIA MCP77 */ + { 0x10de, 0x0775, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_NVIDIA }, /* NVIDIA MCP77 */ + { 0x10de, 0x0776, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_NVIDIA }, /* NVIDIA MCP77 */ + { 0x10de, 0x0777, PCI_ANY_ID, PCI_ANY_ID, 0, 0, AZX_DRIVER_NVIDIA }, /* NVIDIA MCP77 */ { 0, } }; MODULE_DEVICE_TABLE(pci, azx_ids); diff --git a/sound/pci/hda/hda_proc.c b/sound/pci/hda/hda_proc.c index e313e685f161..ac15066fd300 100644 --- a/sound/pci/hda/hda_proc.c +++ b/sound/pci/hda/hda_proc.c @@ -250,6 +250,12 @@ static void print_codec_info(struct snd_info_entry *entry, struct snd_info_buffe snd_iprintf(buffer, "Vendor Id: 0x%x\n", codec->vendor_id); snd_iprintf(buffer, "Subsystem Id: 0x%x\n", codec->subsystem_id); snd_iprintf(buffer, "Revision Id: 0x%x\n", codec->revision_id); + + if (codec->mfg) + snd_iprintf(buffer, "Modem Function Group: 0x%x\n", codec->mfg); + else + snd_iprintf(buffer, "No Modem Function Group found\n"); + if (! codec->afg) return; snd_iprintf(buffer, "Default PCM:\n"); diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c index 0e1a879663fa..4d7f8d11ad75 100644 --- a/sound/pci/hda/patch_analog.c +++ b/sound/pci/hda/patch_analog.c @@ -1,7 +1,8 @@ /* - * HD audio interface patch for AD1981HD, AD1983, AD1986A, AD1988 + * HD audio interface patch for AD1882, AD1884, AD1981HD, AD1983, AD1984, + * AD1986A, AD1988 * - * Copyright (c) 2005 Takashi Iwai + * Copyright (c) 2005-2007 Takashi Iwai * * 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 @@ -61,7 +62,7 @@ struct ad198x_spec { int num_channel_mode; /* PCM information */ - struct hda_pcm pcm_rec[2]; /* used in alc_build_pcms() */ + struct hda_pcm pcm_rec[3]; /* used in alc_build_pcms() */ struct mutex amp_mutex; /* PCM volume/mute control mutex */ unsigned int spdif_route; @@ -2774,12 +2775,635 @@ static int patch_ad1988(struct hda_codec *codec) } +/* + * AD1884 / AD1984 + * + * port-B - front line/mic-in + * port-E - aux in/out + * port-F - aux in/out + * port-C - rear line/mic-in + * port-D - rear line/hp-out + * port-A - front line/hp-out + * + * AD1984 = AD1884 + two digital mic-ins + * + * FIXME: + * For simplicity, we share the single DAC for both HP and line-outs + * right now. The inidividual playbacks could be easily implemented, + * but no build-up framework is given, so far. + */ + +static hda_nid_t ad1884_dac_nids[1] = { + 0x04, +}; + +static hda_nid_t ad1884_adc_nids[2] = { + 0x08, 0x09, +}; + +static hda_nid_t ad1884_capsrc_nids[2] = { + 0x0c, 0x0d, +}; + +#define AD1884_SPDIF_OUT 0x02 + +static struct hda_input_mux ad1884_capture_source = { + .num_items = 4, + .items = { + { "Front Mic", 0x0 }, + { "Mic", 0x1 }, + { "CD", 0x2 }, + { "Mix", 0x3 }, + }, +}; + +static struct snd_kcontrol_new ad1884_base_mixers[] = { + HDA_CODEC_VOLUME("PCM Playback Volume", 0x04, 0x0, HDA_OUTPUT), + /* HDA_CODEC_VOLUME_IDX("PCM Playback Volume", 1, 0x03, 0x0, HDA_OUTPUT), */ + HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x13, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x13, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x02, HDA_INPUT), + /* + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("Digital Beep Playback Volume", 0x10, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Digital Beep Playback Switch", 0x10, 0x0, HDA_OUTPUT), + */ + HDA_CODEC_VOLUME("Mic Boost", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x14, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + * FIXME: the controls appear in the "playback" view! + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + /* SPDIF controls */ + HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + /* identical with ad1983 */ + .info = ad1983_spdif_route_info, + .get = ad1983_spdif_route_get, + .put = ad1983_spdif_route_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new ad1984_dmic_mixers[] = { + HDA_CODEC_VOLUME("Digital Mic Capture Volume", 0x05, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Digital Mic Capture Switch", 0x05, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Digital Mic Capture Volume", 1, 0x06, 0x0, + HDA_INPUT), + HDA_CODEC_MUTE_IDX("Digital Mic Capture Switch", 1, 0x06, 0x0, + HDA_INPUT), + { } /* end */ +}; + +/* + * initialization verbs + */ +static struct hda_verb ad1884_init_verbs[] = { + /* DACs; mute as default */ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* Port-A (HP) mixer */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Port-A pin */ + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* HP selector - select DAC2 */ + {0x22, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* Port-D (Line-out) mixer */ + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Port-D pin */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Mono-out mixer */ + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Mono-out pin */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Mono selector */ + {0x0e, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* Port-B (front mic) pin */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Port-C (rear mic) pin */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Analog mixer; mute as default */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + /* Analog Mix output amp */ + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */ + /* SPDIF output selector */ + {0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, /* PCM */ + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */ + { } /* end */ +}; + +static int patch_ad1884(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + mutex_init(&spec->amp_mutex); + codec->spec = spec; + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(ad1884_dac_nids); + spec->multiout.dac_nids = ad1884_dac_nids; + spec->multiout.dig_out_nid = AD1884_SPDIF_OUT; + spec->num_adc_nids = ARRAY_SIZE(ad1884_adc_nids); + spec->adc_nids = ad1884_adc_nids; + spec->capsrc_nids = ad1884_capsrc_nids; + spec->input_mux = &ad1884_capture_source; + spec->num_mixers = 1; + spec->mixers[0] = ad1884_base_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = ad1884_init_verbs; + spec->spdif_route = 0; + + codec->patch_ops = ad198x_patch_ops; + + return 0; +} + +/* + * Lenovo Thinkpad T61/X61 + */ +static struct hda_input_mux ad1984_thinkpad_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x0 }, + { "Internal Mic", 0x1 }, + { "Mix", 0x3 }, + }, +}; + +static struct snd_kcontrol_new ad1984_thinkpad_mixers[] = { + HDA_CODEC_VOLUME("PCM Playback Volume", 0x04, 0x0, HDA_OUTPUT), + /* HDA_CODEC_VOLUME_IDX("PCM Playback Volume", 1, 0x03, 0x0, HDA_OUTPUT), */ + HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Docking Mic Playback Volume", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("Docking Mic Playback Switch", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x14, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Boost", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Docking Mic Boost", 0x25, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + * FIXME: the controls appear in the "playback" view! + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { } /* end */ +}; + +/* additional verbs */ +static struct hda_verb ad1984_thinkpad_init_verbs[] = { + /* Port-E (docking station mic) pin */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* docking mic boost */ + {0x25, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Analog mixer - docking mic; mute as default */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* enable EAPD bit */ + {0x12, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, + { } /* end */ +}; + +/* Digial MIC ADC NID 0x05 + 0x06 */ +static int ad1984_pcm_dmic_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_setup_stream(codec, 0x05 + substream->number, + stream_tag, 0, format); + return 0; +} + +static int ad1984_pcm_dmic_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_setup_stream(codec, 0x05 + substream->number, + 0, 0, 0); + return 0; +} + +static struct hda_pcm_stream ad1984_pcm_dmic_capture = { + .substreams = 2, + .channels_min = 2, + .channels_max = 2, + .nid = 0x05, + .ops = { + .prepare = ad1984_pcm_dmic_prepare, + .cleanup = ad1984_pcm_dmic_cleanup + }, +}; + +static int ad1984_build_pcms(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + struct hda_pcm *info; + int err; + + err = ad198x_build_pcms(codec); + if (err < 0) + return err; + + info = spec->pcm_rec + codec->num_pcms; + codec->num_pcms++; + info->name = "AD1984 Digital Mic"; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = ad1984_pcm_dmic_capture; + return 0; +} + +/* models */ +enum { + AD1984_BASIC, + AD1984_THINKPAD, + AD1984_MODELS +}; + +static const char *ad1984_models[AD1984_MODELS] = { + [AD1984_BASIC] = "basic", + [AD1984_THINKPAD] = "thinkpad", +}; + +static struct snd_pci_quirk ad1984_cfg_tbl[] = { + /* Lenovo Thinkpad T61/X61 */ + SND_PCI_QUIRK(0x17aa, 0, "Lenovo Thinkpad", AD1984_THINKPAD), + {} +}; + +static int patch_ad1984(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int board_config, err; + + err = patch_ad1884(codec); + if (err < 0) + return err; + spec = codec->spec; + board_config = snd_hda_check_board_config(codec, AD1984_MODELS, + ad1984_models, ad1984_cfg_tbl); + switch (board_config) { + case AD1984_BASIC: + /* additional digital mics */ + spec->mixers[spec->num_mixers++] = ad1984_dmic_mixers; + codec->patch_ops.build_pcms = ad1984_build_pcms; + break; + case AD1984_THINKPAD: + spec->multiout.dig_out_nid = 0; + spec->input_mux = &ad1984_thinkpad_capture_source; + spec->mixers[0] = ad1984_thinkpad_mixers; + spec->init_verbs[spec->num_init_verbs++] = ad1984_thinkpad_init_verbs; + break; + } + return 0; +} + + +/* + * AD1882 + * + * port-A - front hp-out + * port-B - front mic-in + * port-C - rear line-in, shared surr-out (3stack) + * port-D - rear line-out + * port-E - rear mic-in, shared clfe-out (3stack) + * port-F - rear surr-out (6stack) + * port-G - rear clfe-out (6stack) + */ + +static hda_nid_t ad1882_dac_nids[3] = { + 0x04, 0x03, 0x05 +}; + +static hda_nid_t ad1882_adc_nids[2] = { + 0x08, 0x09, +}; + +static hda_nid_t ad1882_capsrc_nids[2] = { + 0x0c, 0x0d, +}; + +#define AD1882_SPDIF_OUT 0x02 + +/* list: 0x11, 0x39, 0x3a, 0x18, 0x3c, 0x3b, 0x12, 0x20 */ +static struct hda_input_mux ad1882_capture_source = { + .num_items = 5, + .items = { + { "Front Mic", 0x1 }, + { "Mic", 0x4 }, + { "Line", 0x2 }, + { "CD", 0x3 }, + { "Mix", 0x7 }, + }, +}; + +static struct snd_kcontrol_new ad1882_base_mixers[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x04, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x05, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x05, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x13, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x13, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x06, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x06, HDA_INPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x20, 0x07, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x20, 0x07, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x3c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x39, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line-In Boost", 0x3a, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + * FIXME: the controls appear in the "playback" view! + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + /* SPDIF controls */ + HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + /* identical with ad1983 */ + .info = ad1983_spdif_route_info, + .get = ad1983_spdif_route_get, + .put = ad1983_spdif_route_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new ad1882_3stack_mixers[] = { + HDA_CODEC_MUTE("Surround Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x17, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x17, 2, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = ad198x_ch_mode_info, + .get = ad198x_ch_mode_get, + .put = ad198x_ch_mode_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new ad1882_6stack_mixers[] = { + HDA_CODEC_MUTE("Surround Playback Switch", 0x16, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x24, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x24, 2, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct hda_verb ad1882_ch2_init[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + { } /* end */ +}; + +static struct hda_verb ad1882_ch4_init[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + { } /* end */ +}; + +static struct hda_verb ad1882_ch6_init[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + { } /* end */ +}; + +static struct hda_channel_mode ad1882_modes[3] = { + { 2, ad1882_ch2_init }, + { 4, ad1882_ch4_init }, + { 6, ad1882_ch6_init }, +}; + +/* + * initialization verbs + */ +static struct hda_verb ad1882_init_verbs[] = { + /* DACs; mute as default */ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* Port-A (HP) mixer */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Port-A pin */ + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* HP selector - select DAC2 */ + {0x37, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* Port-D (Line-out) mixer */ + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Port-D pin */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Mono-out mixer */ + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Mono-out pin */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Port-B (front mic) pin */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x39, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, /* boost */ + /* Port-C (line-in) pin */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x3a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, /* boost */ + /* Port-C mixer - mute as input */ + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Port-E (mic-in) pin */ + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x3c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, /* boost */ + /* Port-E mixer - mute as input */ + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Port-F (surround) */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Port-G (CLFE) */ + {0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Analog mixer; mute as default */ + /* list: 0x39, 0x3a, 0x11, 0x12, 0x3c, 0x3b, 0x18, 0x1a */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, + /* Analog Mix output amp */ + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */ + /* SPDIF output selector */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */ + {0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, /* PCM */ + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */ + { } /* end */ +}; + +/* models */ +enum { + AD1882_3STACK, + AD1882_6STACK, + AD1882_MODELS +}; + +static const char *ad1882_models[AD1986A_MODELS] = { + [AD1882_3STACK] = "3stack", + [AD1882_6STACK] = "6stack", +}; + + +static int patch_ad1882(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + mutex_init(&spec->amp_mutex); + codec->spec = spec; + + spec->multiout.max_channels = 6; + spec->multiout.num_dacs = 3; + spec->multiout.dac_nids = ad1882_dac_nids; + spec->multiout.dig_out_nid = AD1882_SPDIF_OUT; + spec->num_adc_nids = ARRAY_SIZE(ad1882_adc_nids); + spec->adc_nids = ad1882_adc_nids; + spec->capsrc_nids = ad1882_capsrc_nids; + spec->input_mux = &ad1882_capture_source; + spec->num_mixers = 1; + spec->mixers[0] = ad1882_base_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = ad1882_init_verbs; + spec->spdif_route = 0; + + codec->patch_ops = ad198x_patch_ops; + + /* override some parameters */ + board_config = snd_hda_check_board_config(codec, AD1882_MODELS, + ad1882_models, NULL); + switch (board_config) { + default: + case AD1882_3STACK: + spec->num_mixers = 2; + spec->mixers[1] = ad1882_3stack_mixers; + spec->channel_mode = ad1882_modes; + spec->num_channel_mode = ARRAY_SIZE(ad1882_modes); + spec->need_dac_fix = 1; + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; + break; + case AD1882_6STACK: + spec->num_mixers = 2; + spec->mixers[1] = ad1882_6stack_mixers; + break; + } + return 0; +} + + /* * patch entries */ struct hda_codec_preset snd_hda_preset_analog[] = { + { .id = 0x11d41882, .name = "AD1882", .patch = patch_ad1882 }, + { .id = 0x11d41884, .name = "AD1884", .patch = patch_ad1884 }, { .id = 0x11d41981, .name = "AD1981", .patch = patch_ad1981 }, { .id = 0x11d41983, .name = "AD1983", .patch = patch_ad1983 }, + { .id = 0x11d41984, .name = "AD1984", .patch = patch_ad1984 }, { .id = 0x11d41986, .name = "AD1986A", .patch = patch_ad1986a }, { .id = 0x11d41988, .name = "AD1988", .patch = patch_ad1988 }, { .id = 0x11d4198b, .name = "AD1988B", .patch = patch_ad1988 }, diff --git a/sound/pci/hda/patch_atihdmi.c b/sound/pci/hda/patch_atihdmi.c index f8eb4c90f801..72d3ab9751ac 100644 --- a/sound/pci/hda/patch_atihdmi.c +++ b/sound/pci/hda/patch_atihdmi.c @@ -172,6 +172,7 @@ static int patch_atihdmi(struct hda_codec *codec) */ struct hda_codec_preset snd_hda_preset_atihdmi[] = { { .id = 0x1002793c, .name = "ATI RS600 HDMI", .patch = patch_atihdmi }, + { .id = 0x10027919, .name = "ATI RS600 HDMI", .patch = patch_atihdmi }, { .id = 0x1002791a, .name = "ATI RS690/780 HDMI", .patch = patch_atihdmi }, { .id = 0x1002aa01, .name = "ATI R600 HDMI", .patch = patch_atihdmi }, {} /* terminator */ diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index bef214bcdddf..4d8e8af5c819 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -801,7 +801,9 @@ static struct snd_pci_quirk cxt5045_cfg_tbl[] = { SND_PCI_QUIRK(0x103c, 0x30b2, "HP DV Series", CXT5045_LAPTOP), SND_PCI_QUIRK(0x103c, 0x30b5, "HP DV2120", CXT5045_LAPTOP), SND_PCI_QUIRK(0x103c, 0x30cd, "HP DV Series", CXT5045_LAPTOP), + SND_PCI_QUIRK(0x103c, 0x30d9, "HP Spartan", CXT5045_LAPTOP), SND_PCI_QUIRK(0x1734, 0x10ad, "Fujitsu Si1520", CXT5045_FUJITSU), + SND_PCI_QUIRK(0x1734, 0x10cb, "Fujitsu Si3515", CXT5045_LAPTOP), SND_PCI_QUIRK(0x8086, 0x2111, "Conexant Reference board", CXT5045_LAPTOP), {} }; diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 4776de93928b..9a47eec5a27b 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -94,10 +94,18 @@ enum { ALC262_HP_BPC_D7000_WF, ALC262_BENQ_ED8, ALC262_SONY_ASSAMD, + ALC262_BENQ_T31, ALC262_AUTO, ALC262_MODEL_LAST /* last tag */ }; +/* ALC268 models */ +enum { + ALC268_3ST, + ALC268_AUTO, + ALC268_MODEL_LAST /* last tag */ +}; + /* ALC861 models */ enum { ALC861_3ST, @@ -115,6 +123,7 @@ enum { /* ALC861-VD models */ enum { ALC660VD_3ST, + ALC660VD_3ST_DIG, ALC861VD_3ST, ALC861VD_3ST_DIG, ALC861VD_6ST_DIG, @@ -144,6 +153,7 @@ enum { ALC882_TARGA, ALC882_ASUS_A7J, ALC885_MACPRO, + ALC885_IMAC24, ALC882_AUTO, ALC882_MODEL_LAST, }; @@ -163,6 +173,8 @@ enum { ALC883_LENOVO_101E_2ch, ALC883_LENOVO_NB0763, ALC888_LENOVO_MS7195_DIG, + ALC888_6ST_HP, + ALC888_3ST_HP, ALC883_AUTO, ALC883_MODEL_LAST, }; @@ -712,6 +724,38 @@ static void alc_subsystem_id(struct hda_codec *codec, } } +/* + * Fix-up pin default configurations + */ + +struct alc_pincfg { + hda_nid_t nid; + u32 val; +}; + +static void alc_fix_pincfg(struct hda_codec *codec, + const struct snd_pci_quirk *quirk, + const struct alc_pincfg **pinfix) +{ + const struct alc_pincfg *cfg; + + quirk = snd_pci_quirk_lookup(codec->bus->pci, quirk); + if (!quirk) + return; + + cfg = pinfix[quirk->value]; + for (; cfg->nid; cfg++) { + int i; + u32 val = cfg->val; + for (i = 0; i < 4; i++) { + snd_hda_codec_write(codec, cfg->nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_0 + i, + val & 0xff); + val >>= 8; + } + } +} + /* * ALC880 3-stack model * @@ -1878,31 +1922,53 @@ static void alc880_lg_unsol_event(struct hda_codec *codec, unsigned int res) * Pin assignment: * Speaker-out: 0x14 * Mic-In: 0x18 - * Built-in Mic-In: 0x19 (?) - * HP-Out: 0x1b + * Built-in Mic-In: 0x19 + * Line-In: 0x1b + * HP-Out: 0x1a * SPDIF-Out: 0x1e */ -/* seems analog CD is not working */ static struct hda_input_mux alc880_lg_lw_capture_source = { - .num_items = 2, + .num_items = 3, .items = { { "Mic", 0x0 }, { "Internal Mic", 0x1 }, + { "Line In", 0x2 }, }, }; +#define alc880_lg_lw_modes alc880_threestack_modes + static struct snd_kcontrol_new alc880_lg_lw_mixer[] = { - HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT), - HDA_BIND_MUTE("Master Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0f, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0f, 2, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + }, { } /* end */ }; static struct hda_verb alc880_lg_lw_init_verbs[] = { + {0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + {0x10, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */ + {0x12, AC_VERB_SET_CONNECT_SEL, 0x03}, /* line/surround */ + /* set capture source to mic-in */ {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, @@ -1912,7 +1978,6 @@ static struct hda_verb alc880_lg_lw_init_verbs[] = { {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* HP-out */ - {0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* mic-in to input */ @@ -2856,11 +2921,11 @@ static struct alc_config_preset alc880_presets[] = { .mixers = { alc880_lg_lw_mixer }, .init_verbs = { alc880_volume_init_verbs, alc880_lg_lw_init_verbs }, - .num_dacs = 1, + .num_dacs = ARRAY_SIZE(alc880_dac_nids), .dac_nids = alc880_dac_nids, .dig_out_nid = ALC880_DIGOUT_NID, - .num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes), - .channel_mode = alc880_2_jack_modes, + .num_channel_mode = ARRAY_SIZE(alc880_lg_lw_modes), + .channel_mode = alc880_lg_lw_modes, .input_mux = &alc880_lg_lw_capture_source, .unsol_event = alc880_lg_lw_unsol_event, .init_hook = alc880_lg_lw_automute, @@ -5054,6 +5119,60 @@ static struct hda_verb alc882_macpro_init_verbs[] = { { } }; +/* iMac 24 mixer. */ +static struct snd_kcontrol_new alc885_imac24_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("Master Playback Switch", 0x0c, 0x00, HDA_INPUT), + { } /* end */ +}; + +/* iMac 24 init verbs. */ +static struct hda_verb alc885_imac24_init_verbs[] = { + /* Internal speakers: output 0 (0x0c) */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x18, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Internal speakers: output 0 (0x0c) */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Headphone: output 0 (0x0c) */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + /* Front Mic: input vref at 80% */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + { } +}; + +/* Toggle speaker-output according to the hp-jack state */ +static void alc885_imac24_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_update(codec, 0x18, 0, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x18, 1, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x1a, 0, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x1a, 1, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); +} + +/* Processes unsolicited events. */ +static void alc885_imac24_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + /* Headphone insertion or removal. */ + if ((res >> 26) == ALC880_HP_EVENT) + alc885_imac24_automute(codec); +} + static struct hda_verb alc882_targa_verbs[] = { {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, @@ -5274,6 +5393,7 @@ static const char *alc882_models[ALC882_MODEL_LAST] = { [ALC882_ARIMA] = "arima", [ALC882_W2JC] = "w2jc", [ALC885_MACPRO] = "macpro", + [ALC885_IMAC24] = "imac24", [ALC882_AUTO] = "auto", }; @@ -5284,6 +5404,7 @@ static struct snd_pci_quirk alc882_cfg_tbl[] = { SND_PCI_QUIRK(0x1462, 0x28fb, "Targa T8", ALC882_TARGA), /* MSI-1049 T8 */ SND_PCI_QUIRK(0x161f, 0x2054, "Arima W820", ALC882_ARIMA), SND_PCI_QUIRK(0x1043, 0x060d, "Asus A7J", ALC882_ASUS_A7J), + SND_PCI_QUIRK(0x1043, 0x817f, "Asus P5LD2", ALC882_6ST_DIG), SND_PCI_QUIRK(0x1043, 0x81d8, "Asus P5WD", ALC882_6ST_DIG), SND_PCI_QUIRK(0x1043, 0x1971, "Asus W2JC", ALC882_W2JC), {} @@ -5345,6 +5466,19 @@ static struct alc_config_preset alc882_presets[] = { .channel_mode = alc882_ch_modes, .input_mux = &alc882_capture_source, }, + [ALC885_IMAC24] = { + .mixers = { alc885_imac24_mixer }, + .init_verbs = { alc885_imac24_init_verbs }, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .dig_out_nid = ALC882_DIGOUT_NID, + .dig_in_nid = ALC882_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc882_ch_modes), + .channel_mode = alc882_ch_modes, + .input_mux = &alc882_capture_source, + .unsol_event = alc885_imac24_unsol_event, + .init_hook = alc885_imac24_automute, + }, [ALC882_TARGA] = { .mixers = { alc882_targa_mixer, alc882_chmode_mixer, alc882_capture_mixer }, @@ -5378,6 +5512,29 @@ static struct alc_config_preset alc882_presets[] = { }; +/* + * Pin config fixes + */ +enum { + PINFIX_ABIT_AW9D_MAX +}; + +static struct alc_pincfg alc882_abit_aw9d_pinfix[] = { + { 0x15, 0x01080104 }, /* side */ + { 0x16, 0x01011012 }, /* rear */ + { 0x17, 0x01016011 }, /* clfe */ + { } +}; + +static const struct alc_pincfg *alc882_pin_fixes[] = { + [PINFIX_ABIT_AW9D_MAX] = alc882_abit_aw9d_pinfix, +}; + +static struct snd_pci_quirk alc882_pinfix_tbl[] = { + SND_PCI_QUIRK(0x147b, 0x107a, "Abit AW9D-MAX", PINFIX_ABIT_AW9D_MAX), + {} +}; + /* * BIOS auto configuration */ @@ -5494,6 +5651,9 @@ static int patch_alc882(struct hda_codec *codec) case 0x106b0c00: /* Mac Pro */ board_config = ALC885_MACPRO; break; + case 0x106b1000: /* iMac 24 */ + board_config = ALC885_IMAC24; + break; default: printk(KERN_INFO "hda_codec: Unknown model for ALC882, " "trying auto-probe from BIOS...\n"); @@ -5501,6 +5661,8 @@ static int patch_alc882(struct hda_codec *codec) } } + alc_fix_pincfg(codec, alc882_pinfix_tbl, alc882_pin_fixes); + if (board_config == ALC882_AUTO) { /* automatic parse from the BIOS config */ err = alc882_parse_auto_config(codec); @@ -5518,7 +5680,7 @@ static int patch_alc882(struct hda_codec *codec) if (board_config != ALC882_AUTO) setup_preset(spec, &alc882_presets[board_config]); - if (board_config == ALC885_MACPRO) { + if (board_config == ALC885_MACPRO || board_config == ALC885_IMAC24) { alc882_gpio_mute(codec, 0, 0); alc882_gpio_mute(codec, 1, 0); } @@ -5995,6 +6157,84 @@ static struct snd_kcontrol_new alc883_medion_md2_mixer[] = { { } /* end */ }; +static struct snd_kcontrol_new alc888_6st_hp_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0e, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0e, 2, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0d, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0d, 2, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0d, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0d, 2, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc888_3st_hp_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0e, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0e, 2, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0d, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0d, 2, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0d, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0d, 2, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + static struct snd_kcontrol_new alc883_chmode_mixer[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -6126,6 +6366,42 @@ static struct hda_verb alc888_lenovo_ms7195_verbs[] = { { } /* end */ }; +static struct hda_verb alc888_6st_hp_verbs[] = { + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Front: output 0 (0x0c) */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x02}, /* Rear : output 2 (0x0e) */ + {0x16, AC_VERB_SET_CONNECT_SEL, 0x01}, /* CLFE : output 1 (0x0d) */ + {0x17, AC_VERB_SET_CONNECT_SEL, 0x03}, /* Side : output 3 (0x0f) */ + { } +}; + +static struct hda_verb alc888_3st_hp_verbs[] = { + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Front: output 0 (0x0c) */ + {0x18, AC_VERB_SET_CONNECT_SEL, 0x01}, /* Rear : output 1 (0x0d) */ + {0x16, AC_VERB_SET_CONNECT_SEL, 0x02}, /* CLFE : output 2 (0x0e) */ + { } +}; + +static struct hda_verb alc888_3st_hp_2ch_init[] = { + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + { 0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { } +}; + +static struct hda_verb alc888_3st_hp_6ch_init[] = { + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { } +}; + +static struct hda_channel_mode alc888_3st_hp_modes[2] = { + { 2, alc888_3st_hp_2ch_init }, + { 6, alc888_3st_hp_6ch_init }, +}; + /* toggle front-jack and RCA according to the hp-jack state */ static void alc888_lenovo_ms7195_front_automute(struct hda_codec *codec) { @@ -6368,11 +6644,14 @@ static const char *alc883_models[ALC883_MODEL_LAST] = { [ALC883_LENOVO_101E_2ch] = "lenovo-101e", [ALC883_LENOVO_NB0763] = "lenovo-nb0763", [ALC888_LENOVO_MS7195_DIG] = "lenovo-ms7195-dig", + [ALC888_6ST_HP] = "6stack-hp", + [ALC888_3ST_HP] = "3stack-hp", [ALC883_AUTO] = "auto", }; static struct snd_pci_quirk alc883_cfg_tbl[] = { SND_PCI_QUIRK(0x1019, 0x6668, "ECS", ALC883_3ST_6ch_DIG), + SND_PCI_QUIRK(0x103c, 0x2a3d, "HP Pavillion", ALC883_6ST_DIG), SND_PCI_QUIRK(0x108e, 0x534d, NULL, ALC883_3ST_6ch), SND_PCI_QUIRK(0x1558, 0, "Clevo laptop", ALC883_LAPTOP_EAPD), SND_PCI_QUIRK(0x105b, 0x6668, "Foxconn", ALC883_6ST_DIG), @@ -6381,6 +6660,8 @@ static struct snd_pci_quirk alc883_cfg_tbl[] = { SND_PCI_QUIRK(0x1462, 0x7187, "MSI", ALC883_6ST_DIG), SND_PCI_QUIRK(0x1462, 0x7250, "MSI", ALC883_6ST_DIG), SND_PCI_QUIRK(0x1462, 0x7280, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x7327, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x0349, "MSI", ALC883_TARGA_2ch_DIG), SND_PCI_QUIRK(0x1462, 0x0579, "MSI", ALC883_TARGA_2ch_DIG), SND_PCI_QUIRK(0x1462, 0x3729, "MSI S420", ALC883_TARGA_DIG), SND_PCI_QUIRK(0x1462, 0x3ef9, "MSI", ALC883_TARGA_DIG), @@ -6400,6 +6681,9 @@ static struct snd_pci_quirk alc883_cfg_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x101e, "Lenovo 101e", ALC883_LENOVO_101E_2ch), SND_PCI_QUIRK(0x17aa, 0x3bfd, "Lenovo NB0763", ALC883_LENOVO_NB0763), SND_PCI_QUIRK(0x17aa, 0x2085, "Lenovo NB0763", ALC883_LENOVO_NB0763), + SND_PCI_QUIRK(0x103c, 0x2a61, "HP Nettle", ALC888_6ST_HP), + SND_PCI_QUIRK(0x103c, 0x2a60, "HP Lucknow", ALC888_3ST_HP), + SND_PCI_QUIRK(0x103c, 0x2a4f, "HP Samba", ALC888_3ST_HP), SND_PCI_QUIRK(0x17c0, 0x4071, "MEDION MD2", ALC883_MEDION_MD2), {} }; @@ -6584,6 +6868,31 @@ static struct alc_config_preset alc883_presets[] = { .unsol_event = alc883_lenovo_ms7195_unsol_event, .init_hook = alc888_lenovo_ms7195_front_automute, }, + [ALC888_6ST_HP] = { + .mixers = { alc888_6st_hp_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc888_6st_hp_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_adc_nids = ARRAY_SIZE(alc883_adc_nids), + .adc_nids = alc883_adc_nids, + .dig_in_nid = ALC883_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc883_sixstack_modes), + .channel_mode = alc883_sixstack_modes, + .input_mux = &alc883_capture_source, + }, + [ALC888_3ST_HP] = { + .mixers = { alc888_3st_hp_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc888_3st_hp_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc883_adc_nids), + .adc_nids = alc883_adc_nids, + .num_channel_mode = ARRAY_SIZE(alc888_3st_hp_modes), + .channel_mode = alc888_3st_hp_modes, + .need_dac_fix = 1, + .input_mux = &alc883_capture_source, + }, }; @@ -6857,7 +7166,16 @@ static struct snd_kcontrol_new alc262_sony_mixer[] = { { } /* end */ }; - +static struct snd_kcontrol_new alc262_benq_t31_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("ATAPI Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("ATAPI Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + { } /* end */ +}; #define alc262_capture_mixer alc882_capture_mixer #define alc262_capture_alt_mixer alc882_capture_alt_mixer @@ -7189,6 +7507,15 @@ static struct hda_verb alc262_EAPD_verbs[] = { {} }; +static struct hda_verb alc262_benq_t31_EAPD_verbs[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + + {0x20, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x20, AC_VERB_SET_PROC_COEF, 0x3050}, + {} +}; + /* add playback controls from the parsed DAC table */ static int alc262_auto_create_multi_out_ctls(struct alc_spec *spec, const struct auto_pin_cfg *cfg) @@ -7584,7 +7911,8 @@ static const char *alc262_models[ALC262_MODEL_LAST] = { [ALC262_HP_BPC] = "hp-bpc", [ALC262_HP_BPC_D7000_WL]= "hp-bpc-d7000", [ALC262_BENQ_ED8] = "benq", - [ALC262_BENQ_ED8] = "sony-assamd", + [ALC262_BENQ_T31] = "benq-t31", + [ALC262_SONY_ASSAMD] = "sony-assamd", [ALC262_AUTO] = "auto", }; @@ -7592,8 +7920,12 @@ static struct snd_pci_quirk alc262_cfg_tbl[] = { SND_PCI_QUIRK(0x1002, 0x437b, "Hippo", ALC262_HIPPO), SND_PCI_QUIRK(0x103c, 0x12fe, "HP xw9400", ALC262_HP_BPC), SND_PCI_QUIRK(0x103c, 0x280c, "HP xw4400", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x12ff, "HP xw4550", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x1308, "HP xw4600", ALC262_HP_BPC), SND_PCI_QUIRK(0x103c, 0x3014, "HP xw6400", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x1307, "HP xw6600", ALC262_HP_BPC), SND_PCI_QUIRK(0x103c, 0x3015, "HP xw8400", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x1306, "HP xw8600", ALC262_HP_BPC), SND_PCI_QUIRK(0x103c, 0x2800, "HP D7000", ALC262_HP_BPC_D7000_WL), SND_PCI_QUIRK(0x103c, 0x2802, "HP D7000", ALC262_HP_BPC_D7000_WL), SND_PCI_QUIRK(0x103c, 0x2804, "HP D7000", ALC262_HP_BPC_D7000_WL), @@ -7606,6 +7938,7 @@ static struct snd_pci_quirk alc262_cfg_tbl[] = { SND_PCI_QUIRK(0x10cf, 0x1397, "Fujitsu", ALC262_FUJITSU), SND_PCI_QUIRK(0x17ff, 0x058f, "Benq Hippo", ALC262_HIPPO_1), SND_PCI_QUIRK(0x17ff, 0x0560, "Benq ED8", ALC262_BENQ_ED8), + SND_PCI_QUIRK(0x17ff, 0x058d, "Benq T31-16", ALC262_BENQ_T31), SND_PCI_QUIRK(0x104d, 0x9015, "Sony 0x9015", ALC262_SONY_ASSAMD), SND_PCI_QUIRK(0x104d, 0x900e, "Sony ASSAMD", ALC262_SONY_ASSAMD), SND_PCI_QUIRK(0x104d, 0x1f00, "Sony ASSAMD", ALC262_SONY_ASSAMD), @@ -7710,6 +8043,17 @@ static struct alc_config_preset alc262_presets[] = { .channel_mode = alc262_modes, .input_mux = &alc262_capture_source, .unsol_event = alc262_hippo_unsol_event, + }, + [ALC262_BENQ_T31] = { + .mixers = { alc262_benq_t31_mixer }, + .init_verbs = { alc262_init_verbs, alc262_benq_t31_EAPD_verbs, alc262_hippo_unsol_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_capture_source, + .unsol_event = alc262_hippo_unsol_event, }, }; @@ -7799,6 +8143,515 @@ static int patch_alc262(struct hda_codec *codec) return 0; } +/* + * ALC268 channel source setting (2 channel) + */ +#define ALC268_DIGOUT_NID ALC880_DIGOUT_NID +#define alc268_modes alc260_modes + +static hda_nid_t alc268_dac_nids[2] = { + /* front, hp */ + 0x02, 0x03 +}; + +static hda_nid_t alc268_adc_nids[2] = { + /* ADC0-1 */ + 0x08, 0x07 +}; + +static hda_nid_t alc268_adc_nids_alt[1] = { + /* ADC0 */ + 0x08 +}; + +static struct snd_kcontrol_new alc268_base_mixer[] = { + /* output mixer control */ + HDA_CODEC_VOLUME("Front Playback Volume", 0x2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x3, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + { } +}; + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc268_base_init_verbs[] = { + /* Unmute DAC0-1 and set vol = 0 */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + /* + * Set up output mixers (0x0c - 0x0e) + */ + /* set vol=0 to output mixers */ + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_CONNECT_SEL, 0x00}, + + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + + /* FIXME: use matrix-type input source selection */ + /* Mixer elements: 0x18, 19, 1a, 1c, 14, 15, 0b */ + /* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */ + /* Input mixer2 */ + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))}, + + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))}, + { } +}; + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc268_volume_init_verbs[] = { + /* set output DAC */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + + /* set PCBEEP vol = 0 */ + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, (0xb000 | (0x00 << 8))}, + + { } +}; + +#define alc268_mux_enum_info alc_mux_enum_info +#define alc268_mux_enum_get alc_mux_enum_get + +static int alc268_mux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + const struct hda_input_mux *imux = spec->input_mux; + unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + static hda_nid_t capture_mixers[3] = { 0x23, 0x24 }; + hda_nid_t nid = capture_mixers[adc_idx]; + unsigned int *cur_val = &spec->cur_mux[adc_idx]; + unsigned int i, idx; + + idx = ucontrol->value.enumerated.item[0]; + if (idx >= imux->num_items) + idx = imux->num_items - 1; + if (*cur_val == idx && !codec->in_resume) + return 0; + for (i = 0; i < imux->num_items; i++) { + unsigned int v = (i == idx) ? 0x7000 : 0x7080; + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, + v | (imux->items[i].index << 8)); + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, + idx ); + } + *cur_val = idx; + return 1; +} + +static struct snd_kcontrol_new alc268_capture_alt_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x23, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x23, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + * FIXME: the controls appear in the "playback" view! + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc268_mux_enum_info, + .get = alc268_mux_enum_get, + .put = alc268_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc268_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x23, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x23, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x24, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x24, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + * FIXME: the controls appear in the "playback" view! + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc268_mux_enum_info, + .get = alc268_mux_enum_get, + .put = alc268_mux_enum_put, + }, + { } /* end */ +}; + +static struct hda_input_mux alc268_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x1 }, + { "Line", 0x2 }, + { "CD", 0x3 }, + }, +}; + +/* create input playback/capture controls for the given pin */ +static int alc268_new_analog_output(struct alc_spec *spec, hda_nid_t nid, + const char *ctlname, int idx) +{ + char name[32]; + int err; + + sprintf(name, "%s Playback Volume", ctlname); + if (nid == 0x14) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(0x02, 3, idx, + HDA_OUTPUT)); + if (err < 0) + return err; + } else if (nid == 0x15) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(0x03, 3, idx, + HDA_OUTPUT)); + if (err < 0) + return err; + } else + return -1; + sprintf(name, "%s Playback Switch", ctlname); + err = add_control(spec, ALC_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, 3, idx, HDA_OUTPUT)); + if (err < 0) + return err; + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int alc268_auto_create_multi_out_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + hda_nid_t nid; + int err; + + spec->multiout.num_dacs = 2; /* only use one dac */ + spec->multiout.dac_nids = spec->private_dac_nids; + spec->multiout.dac_nids[0] = 2; + spec->multiout.dac_nids[1] = 3; + + nid = cfg->line_out_pins[0]; + if (nid) + alc268_new_analog_output(spec, nid, "Front", 0); + + nid = cfg->speaker_pins[0]; + if (nid == 0x1d) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Speaker Playback Volume", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT)); + if (err < 0) + return err; + } + nid = cfg->hp_pins[0]; + if (nid) + alc268_new_analog_output(spec, nid, "Headphone", 0); + + nid = cfg->line_out_pins[1] | cfg->line_out_pins[2]; + if (nid == 0x16) { + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Mono Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 2, 0, HDA_INPUT)); + if (err < 0) + return err; + } + return 0; +} + +/* create playback/capture controls for input pins */ +static int alc268_auto_create_analog_input_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + struct hda_input_mux *imux = &spec->private_imux; + int i, idx1; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + switch(cfg->input_pins[i]) { + case 0x18: + idx1 = 0; /* Mic 1 */ + break; + case 0x19: + idx1 = 1; /* Mic 2 */ + break; + case 0x1a: + idx1 = 2; /* Line In */ + break; + case 0x1c: + idx1 = 3; /* CD */ + break; + default: + continue; + } + imux->items[imux->num_items].label = auto_pin_cfg_labels[i]; + imux->items[imux->num_items].index = idx1; + imux->num_items++; + } + return 0; +} + +static void alc268_auto_init_mono_speaker_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t speaker_nid = spec->autocfg.speaker_pins[0]; + hda_nid_t hp_nid = spec->autocfg.hp_pins[0]; + hda_nid_t line_nid = spec->autocfg.line_out_pins[0]; + unsigned int dac_vol1, dac_vol2; + + if (speaker_nid) { + snd_hda_codec_write(codec, speaker_nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + snd_hda_codec_write(codec, 0x0f, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(1)); + snd_hda_codec_write(codec, 0x10, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(1)); + } else { + snd_hda_codec_write(codec, 0x0f, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)); + snd_hda_codec_write(codec, 0x10, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)); + } + + dac_vol1 = dac_vol2 = 0xb000 | 0x40; /* set max volume */ + if (line_nid == 0x14) + dac_vol2 = AMP_OUT_ZERO; + else if (line_nid == 0x15) + dac_vol1 = AMP_OUT_ZERO; + if (hp_nid == 0x14) + dac_vol2 = AMP_OUT_ZERO; + else if (hp_nid == 0x15) + dac_vol1 = AMP_OUT_ZERO; + if (line_nid != 0x16 || hp_nid != 0x16 || + spec->autocfg.line_out_pins[1] != 0x16 || + spec->autocfg.line_out_pins[2] != 0x16) + dac_vol1 = dac_vol2 = AMP_OUT_ZERO; + + snd_hda_codec_write(codec, 0x02, 0, + AC_VERB_SET_AMP_GAIN_MUTE, dac_vol1); + snd_hda_codec_write(codec, 0x03, 0, + AC_VERB_SET_AMP_GAIN_MUTE, dac_vol2); +} + +/* pcm configuration: identiacal with ALC880 */ +#define alc268_pcm_analog_playback alc880_pcm_analog_playback +#define alc268_pcm_analog_capture alc880_pcm_analog_capture +#define alc268_pcm_digital_playback alc880_pcm_digital_playback + +/* + * BIOS auto configuration + */ +static int alc268_parse_auto_config(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int err; + static hda_nid_t alc268_ignore[] = { 0 }; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + alc268_ignore); + if (err < 0) + return err; + if (!spec->autocfg.line_outs) + return 0; /* can't find valid BIOS pin config */ + + err = alc268_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc268_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = 2; + + /* digital only support output */ + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = ALC268_DIGOUT_NID; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->init_verbs[spec->num_init_verbs++] = alc268_volume_init_verbs; + spec->num_mux_defs = 1; + spec->input_mux = &spec->private_imux; + + return 1; +} + +#define alc268_auto_init_multi_out alc882_auto_init_multi_out +#define alc268_auto_init_hp_out alc882_auto_init_hp_out +#define alc268_auto_init_analog_input alc882_auto_init_analog_input + +/* init callback for auto-configuration model -- overriding the default init */ +static void alc268_auto_init(struct hda_codec *codec) +{ + alc268_auto_init_multi_out(codec); + alc268_auto_init_hp_out(codec); + alc268_auto_init_mono_speaker_out(codec); + alc268_auto_init_analog_input(codec); +} + +/* + * configuration and preset + */ +static const char *alc268_models[ALC268_MODEL_LAST] = { + [ALC268_3ST] = "3stack", + [ALC268_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc268_cfg_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1205, "ASUS W7J", ALC268_3ST), + {} +}; + +static struct alc_config_preset alc268_presets[] = { + [ALC268_3ST] = { + .mixers = { alc268_base_mixer, alc268_capture_alt_mixer }, + .init_verbs = { alc268_base_init_verbs }, + .num_dacs = ARRAY_SIZE(alc268_dac_nids), + .dac_nids = alc268_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt), + .adc_nids = alc268_adc_nids_alt, + .hp_nid = 0x03, + .dig_out_nid = ALC268_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc268_modes), + .channel_mode = alc268_modes, + .input_mux = &alc268_capture_source, + }, +}; + +static int patch_alc268(struct hda_codec *codec) +{ + struct alc_spec *spec; + int board_config; + int err; + + spec = kcalloc(1, sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + board_config = snd_hda_check_board_config(codec, ALC268_MODEL_LAST, + alc268_models, + alc268_cfg_tbl); + + if (board_config < 0 || board_config >= ALC268_MODEL_LAST) { + printk(KERN_INFO "hda_codec: Unknown model for ALC268, " + "trying auto-probe from BIOS...\n"); + board_config = ALC268_AUTO; + } + + if (board_config == ALC268_AUTO) { + /* automatic parse from the BIOS config */ + err = alc268_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO + "hda_codec: Cannot set up configuration " + "from BIOS. Using base mode...\n"); + board_config = ALC268_3ST; + } + } + + if (board_config != ALC268_AUTO) + setup_preset(spec, &alc268_presets[board_config]); + + spec->stream_name_analog = "ALC268 Analog"; + spec->stream_analog_playback = &alc268_pcm_analog_playback; + spec->stream_analog_capture = &alc268_pcm_analog_capture; + + spec->stream_name_digital = "ALC268 Digital"; + spec->stream_digital_playback = &alc268_pcm_digital_playback; + + if (board_config == ALC268_AUTO) { + if (!spec->adc_nids && spec->input_mux) { + /* check whether NID 0x07 is valid */ + unsigned int wcap = get_wcaps(codec, 0x07); + + /* get type */ + wcap = (wcap & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; + if (wcap != AC_WID_AUD_IN) { + spec->adc_nids = alc268_adc_nids_alt; + spec->num_adc_nids = + ARRAY_SIZE(alc268_adc_nids_alt); + spec->mixers[spec->num_mixers] = + alc268_capture_alt_mixer; + spec->num_mixers++; + } else { + spec->adc_nids = alc268_adc_nids; + spec->num_adc_nids = + ARRAY_SIZE(alc268_adc_nids); + spec->mixers[spec->num_mixers] = + alc268_capture_mixer; + spec->num_mixers++; + } + } + } + codec->patch_ops = alc_patch_ops; + if (board_config == ALC268_AUTO) + spec->init_hook = alc268_auto_init; + + return 0; +} + /* * ALC861 channel source setting (2/6 channel selection for 3-stack) */ @@ -8767,13 +9620,21 @@ static struct snd_pci_quirk alc861_cfg_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1335, "ASUS F2/3", ALC861_ASUS_LAPTOP), SND_PCI_QUIRK(0x1043, 0x1338, "ASUS F2/3", ALC861_ASUS_LAPTOP), SND_PCI_QUIRK(0x1043, 0x13d7, "ASUS A9rp", ALC861_ASUS_LAPTOP), + SND_PCI_QUIRK(0x1584, 0x9075, "Airis Praxis N1212", ALC861_ASUS_LAPTOP), SND_PCI_QUIRK(0x1043, 0x1393, "ASUS", ALC861_ASUS), + SND_PCI_QUIRK(0x1043, 0x81cb, "ASUS P1-AH2", ALC861_3ST_DIG), SND_PCI_QUIRK(0x1179, 0xff00, "Toshiba", ALC861_TOSHIBA), - SND_PCI_QUIRK(0x1179, 0xff10, "Toshiba", ALC861_TOSHIBA), + /* FIXME: the entry below breaks Toshiba A100 (model=auto works!) + * Any other models that need this preset? + */ + /* SND_PCI_QUIRK(0x1179, 0xff10, "Toshiba", ALC861_TOSHIBA), */ SND_PCI_QUIRK(0x1584, 0x9072, "Uniwill m31", ALC861_UNIWILL_M31), + SND_PCI_QUIRK(0x1584, 0x9075, "Uniwill", ALC861_UNIWILL_M31), SND_PCI_QUIRK(0x1584, 0x2b01, "Uniwill X40AIx", ALC861_UNIWILL_M31), SND_PCI_QUIRK(0x1849, 0x0660, "Asrock 939SLI32", ALC660_3ST), SND_PCI_QUIRK(0x8086, 0xd600, "Intel", ALC861_3ST), + SND_PCI_QUIRK(0x1462, 0x7254, "HP dx2200 (MSI MS-7254)", ALC861_3ST), + SND_PCI_QUIRK(0x1462, 0x7297, "HP dx2250 (MSI MS-7297)", ALC861_3ST), {} }; @@ -9464,6 +10325,7 @@ static void alc861vd_dallas_unsol_event(struct hda_codec *codec, unsigned int re */ static const char *alc861vd_models[ALC861VD_MODEL_LAST] = { [ALC660VD_3ST] = "3stack-660", + [ALC660VD_3ST_DIG]= "3stack-660-digout", [ALC861VD_3ST] = "3stack", [ALC861VD_3ST_DIG] = "3stack-digout", [ALC861VD_6ST_DIG] = "6stack-digout", @@ -9475,7 +10337,7 @@ static const char *alc861vd_models[ALC861VD_MODEL_LAST] = { static struct snd_pci_quirk alc861vd_cfg_tbl[] = { SND_PCI_QUIRK(0x1043, 0x12e2, "Asus z35m", ALC660VD_3ST), SND_PCI_QUIRK(0x1043, 0x1339, "Asus G1", ALC660VD_3ST), - SND_PCI_QUIRK(0x1043, 0x81e7, "ASUS", ALC660VD_3ST), + SND_PCI_QUIRK(0x1043, 0x81e7, "ASUS", ALC660VD_3ST_DIG), SND_PCI_QUIRK(0x10de, 0x03f0, "Realtek ALC660 demo", ALC660VD_3ST), SND_PCI_QUIRK(0x1019, 0xa88d, "Realtek ALC660 demo", ALC660VD_3ST), @@ -9483,6 +10345,7 @@ static struct snd_pci_quirk alc861vd_cfg_tbl[] = { SND_PCI_QUIRK(0x1179, 0xff01, "DALLAS", ALC861VD_DALLAS), SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo 3000 C200", ALC861VD_LENOVO), SND_PCI_QUIRK(0x17aa, 0x2066, "Lenovo", ALC861VD_LENOVO), + SND_PCI_QUIRK(0x1179, 0xff00, "Toshiba A135", ALC861VD_LENOVO), {} }; @@ -9499,6 +10362,19 @@ static struct alc_config_preset alc861vd_presets[] = { .channel_mode = alc861vd_3stack_2ch_modes, .input_mux = &alc861vd_capture_source, }, + [ALC660VD_3ST_DIG] = { + .mixers = { alc861vd_3st_mixer }, + .init_verbs = { alc861vd_volume_init_verbs, + alc861vd_3stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc660vd_dac_nids), + .dac_nids = alc660vd_dac_nids, + .dig_out_nid = ALC861VD_DIGOUT_NID, + .num_adc_nids = ARRAY_SIZE(alc861vd_adc_nids), + .adc_nids = alc861vd_adc_nids, + .num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes), + .channel_mode = alc861vd_3stack_2ch_modes, + .input_mux = &alc861vd_capture_source, + }, [ALC861VD_3ST] = { .mixers = { alc861vd_3st_mixer }, .init_verbs = { alc861vd_volume_init_verbs, @@ -10420,7 +11296,7 @@ static int alc662_auto_create_multi_out_ctls(struct alc_spec *spec, for (i = 0; i < cfg->line_outs; i++) { if (!spec->multiout.dac_nids[i]) continue; - nid = alc880_idx_to_dac(i); + nid = alc880_idx_to_mixer(i); if (i == 2) { /* Center/LFE */ err = add_control(spec, ALC_CTL_WIDGET_VOL, @@ -10643,14 +11519,10 @@ static int alc662_parse_auto_config(struct hda_codec *codec) spec->num_mux_defs = 1; spec->input_mux = &spec->private_imux; - if (err < 0) - return err; - else if (err > 0) - /* hack - override the init verbs */ - spec->init_verbs[0] = alc662_auto_init_verbs; + spec->init_verbs[spec->num_init_verbs++] = alc662_auto_init_verbs; spec->mixers[spec->num_mixers] = alc662_capture_mixer; spec->num_mixers++; - return err; + return 1; } /* additional initialization for auto-configuration model */ @@ -10687,7 +11559,7 @@ static int patch_alc662(struct hda_codec *codec) if (err < 0) { alc_free(codec); return err; - } else if (err) { + } else if (!err) { printk(KERN_INFO "hda_codec: Cannot set up configuration " "from BIOS. Using base mode...\n"); @@ -10724,6 +11596,7 @@ static int patch_alc662(struct hda_codec *codec) struct hda_codec_preset snd_hda_preset_realtek[] = { { .id = 0x10ec0260, .name = "ALC260", .patch = patch_alc260 }, { .id = 0x10ec0262, .name = "ALC262", .patch = patch_alc262 }, + { .id = 0x10ec0268, .name = "ALC268", .patch = patch_alc268 }, { .id = 0x10ec0861, .rev = 0x100340, .name = "ALC660", .patch = patch_alc861 }, { .id = 0x10ec0660, .name = "ALC660-VD", .patch = patch_alc861vd }, diff --git a/sound/pci/hda/patch_si3054.c b/sound/pci/hda/patch_si3054.c index 43f537ef40bf..6d2ecc38905c 100644 --- a/sound/pci/hda/patch_si3054.c +++ b/sound/pci/hda/patch_si3054.c @@ -304,8 +304,12 @@ struct hda_codec_preset snd_hda_preset_si3054[] = { { .id = 0x10573055, .name = "Si3054", .patch = patch_si3054 }, { .id = 0x10573057, .name = "Si3054", .patch = patch_si3054 }, { .id = 0x10573155, .name = "Si3054", .patch = patch_si3054 }, + /* VIA HDA on Clevo m540 */ + { .id = 0x11063288, .name = "Si3054", .patch = patch_si3054 }, /* Asus A8J Modem (SM56) */ { .id = 0x15433155, .name = "Si3054", .patch = patch_si3054 }, + /* LG LW20 modem */ + { .id = 0x18540018, .name = "Si3054", .patch = patch_si3054 }, {} }; diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index e3964fc4c405..3f25de72966b 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -44,6 +44,7 @@ enum { enum { STAC_9205_REF, + STAC_M43xx, STAC_9205_MODELS }; @@ -59,11 +60,19 @@ enum { STAC_D945_REF, STAC_D945GTP3, STAC_D945GTP5, + STAC_922X_DELL, + STAC_INTEL_MAC_V1, + STAC_INTEL_MAC_V2, + STAC_INTEL_MAC_V3, + STAC_INTEL_MAC_V4, + STAC_INTEL_MAC_V5, + /* for backward compitability */ STAC_MACMINI, STAC_MACBOOK, STAC_MACBOOK_PRO_V1, STAC_MACBOOK_PRO_V2, STAC_IMAC_INTEL, + STAC_IMAC_INTEL_20, STAC_922X_MODELS }; @@ -210,7 +219,6 @@ static hda_nid_t stac9205_pin_nids[12] = { 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x14, 0x16, 0x17, 0x18, 0x21, 0x22, - }; static int stac92xx_dmux_enum_info(struct snd_kcontrol *kcontrol, @@ -326,8 +334,6 @@ static struct snd_kcontrol_new stac9200_mixer[] = { }; static struct snd_kcontrol_new stac925x_mixer[] = { - HDA_CODEC_VOLUME("Master Playback Volume", 0xe, 0, HDA_OUTPUT), - HDA_CODEC_MUTE("Master Playback Switch", 0xe, 0, HDA_OUTPUT), { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Input Source", @@ -549,44 +555,78 @@ static unsigned int d945gtp5_pin_configs[10] = { 0x02a19320, 0x40000100, }; -static unsigned int macbook_pro_v1_pin_configs[10] = { - 0x0321e230, 0x03a1e020, 0x9017e110, 0x01014010, - 0x01a19021, 0x0381e021, 0x1345e240, 0x13c5e22e, - 0x02a19320, 0x400000fb -}; - -static unsigned int macbook_pro_v2_pin_configs[10] = { - 0x0221401f, 0x90a70120, 0x01813024, 0x01014010, - 0x400000fd, 0x01016011, 0x1345e240, 0x13c5e22e, +static unsigned int intel_mac_v1_pin_configs[10] = { + 0x0121e21f, 0x400000ff, 0x9017e110, 0x400000fd, + 0x400000fe, 0x0181e020, 0x1145e030, 0x11c5e240, 0x400000fc, 0x400000fb, }; -static unsigned int imac_intel_pin_configs[10] = { - 0x0121e230, 0x90a70120, 0x9017e110, 0x400000fe, - 0x400000fd, 0x0181e021, 0x1145e040, 0x400000fa, +static unsigned int intel_mac_v2_pin_configs[10] = { + 0x0121e21f, 0x90a7012e, 0x9017e110, 0x400000fd, + 0x400000fe, 0x0181e020, 0x1145e230, 0x500000fa, 0x400000fc, 0x400000fb, }; +static unsigned int intel_mac_v3_pin_configs[10] = { + 0x0121e21f, 0x90a7012e, 0x9017e110, 0x400000fd, + 0x400000fe, 0x0181e020, 0x1145e230, 0x11c5e240, + 0x400000fc, 0x400000fb, +}; + +static unsigned int intel_mac_v4_pin_configs[10] = { + 0x0321e21f, 0x03a1e02e, 0x9017e110, 0x9017e11f, + 0x400000fe, 0x0381e020, 0x1345e230, 0x13c5e240, + 0x400000fc, 0x400000fb, +}; + +static unsigned int intel_mac_v5_pin_configs[10] = { + 0x0321e21f, 0x03a1e02e, 0x9017e110, 0x9017e11f, + 0x400000fe, 0x0381e020, 0x1345e230, 0x13c5e240, + 0x400000fc, 0x400000fb, +}; + +static unsigned int stac922x_dell_pin_configs[10] = { + 0x0221121e, 0x408103ff, 0x02a1123e, 0x90100310, + 0x408003f1, 0x0221122f, 0x03451340, 0x40c003f2, + 0x50a003f3, 0x405003f4 +}; + static unsigned int *stac922x_brd_tbl[STAC_922X_MODELS] = { [STAC_D945_REF] = ref922x_pin_configs, [STAC_D945GTP3] = d945gtp3_pin_configs, [STAC_D945GTP5] = d945gtp5_pin_configs, - [STAC_MACMINI] = macbook_pro_v1_pin_configs, - [STAC_MACBOOK] = macbook_pro_v1_pin_configs, - [STAC_MACBOOK_PRO_V1] = macbook_pro_v1_pin_configs, - [STAC_MACBOOK_PRO_V2] = macbook_pro_v2_pin_configs, - [STAC_IMAC_INTEL] = imac_intel_pin_configs, + [STAC_922X_DELL] = stac922x_dell_pin_configs, + [STAC_INTEL_MAC_V1] = intel_mac_v1_pin_configs, + [STAC_INTEL_MAC_V2] = intel_mac_v2_pin_configs, + [STAC_INTEL_MAC_V3] = intel_mac_v3_pin_configs, + [STAC_INTEL_MAC_V4] = intel_mac_v4_pin_configs, + [STAC_INTEL_MAC_V5] = intel_mac_v5_pin_configs, + /* for backward compitability */ + [STAC_MACMINI] = intel_mac_v3_pin_configs, + [STAC_MACBOOK] = intel_mac_v5_pin_configs, + [STAC_MACBOOK_PRO_V1] = intel_mac_v3_pin_configs, + [STAC_MACBOOK_PRO_V2] = intel_mac_v3_pin_configs, + [STAC_IMAC_INTEL] = intel_mac_v2_pin_configs, + [STAC_IMAC_INTEL_20] = intel_mac_v3_pin_configs, }; static const char *stac922x_models[STAC_922X_MODELS] = { [STAC_D945_REF] = "ref", [STAC_D945GTP5] = "5stack", [STAC_D945GTP3] = "3stack", + [STAC_922X_DELL] = "dell", + [STAC_INTEL_MAC_V1] = "intel-mac-v1", + [STAC_INTEL_MAC_V2] = "intel-mac-v2", + [STAC_INTEL_MAC_V3] = "intel-mac-v3", + [STAC_INTEL_MAC_V4] = "intel-mac-v4", + [STAC_INTEL_MAC_V5] = "intel-mac-v5", + /* for backward compitability */ [STAC_MACMINI] = "macmini", [STAC_MACBOOK] = "macbook", [STAC_MACBOOK_PRO_V1] = "macbook-pro-v1", [STAC_MACBOOK_PRO_V2] = "macbook-pro", [STAC_IMAC_INTEL] = "imac-intel", + [STAC_IMAC_INTEL_20] = "imac-intel-20", }; static struct snd_pci_quirk stac922x_cfg_tbl[] = { @@ -649,7 +689,10 @@ static struct snd_pci_quirk stac922x_cfg_tbl[] = { /* other systems */ /* Apple Mac Mini (early 2006) */ SND_PCI_QUIRK(0x8384, 0x7680, - "Mac Mini", STAC_MACMINI), + "Mac Mini", STAC_INTEL_MAC_V3), + /* Dell */ + SND_PCI_QUIRK(0x1028, 0x01d7, "Dell XPS M1210", STAC_922X_DELL), + {} /* terminator */ }; @@ -730,7 +773,8 @@ static unsigned int ref9205_pin_configs[12] = { }; static unsigned int *stac9205_brd_tbl[STAC_9205_MODELS] = { - ref9205_pin_configs, + [STAC_REF] = ref9205_pin_configs, + [STAC_M43xx] = NULL, }; static const char *stac9205_models[STAC_9205_MODELS] = { @@ -741,6 +785,10 @@ static struct snd_pci_quirk stac9205_cfg_tbl[] = { /* SigmaTel reference board */ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, "DFI LanParty", STAC_9205_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x01f8, + "Dell Precision", STAC_M43xx), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x01ff, + "Dell Precision", STAC_M43xx), {} /* terminator */ }; @@ -770,33 +818,56 @@ static int stac92xx_save_bios_config_regs(struct hda_codec *codec) return 0; } +static void stac92xx_set_config_reg(struct hda_codec *codec, + hda_nid_t pin_nid, unsigned int pin_config) +{ + int i; + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_0, + pin_config & 0x000000ff); + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_1, + (pin_config & 0x0000ff00) >> 8); + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_2, + (pin_config & 0x00ff0000) >> 16); + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, + pin_config >> 24); + i = snd_hda_codec_read(codec, pin_nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, + 0x00); + snd_printdd(KERN_INFO "hda_codec: pin nid %2.2x pin config %8.8x\n", + pin_nid, i); +} + static void stac92xx_set_config_regs(struct hda_codec *codec) { int i; struct sigmatel_spec *spec = codec->spec; - unsigned int pin_cfg; - if (! spec->pin_nids || ! spec->pin_configs) - return; + if (!spec->pin_configs) + return; - for (i = 0; i < spec->num_pins; i++) { - snd_hda_codec_write(codec, spec->pin_nids[i], 0, - AC_VERB_SET_CONFIG_DEFAULT_BYTES_0, - spec->pin_configs[i] & 0x000000ff); - snd_hda_codec_write(codec, spec->pin_nids[i], 0, - AC_VERB_SET_CONFIG_DEFAULT_BYTES_1, - (spec->pin_configs[i] & 0x0000ff00) >> 8); - snd_hda_codec_write(codec, spec->pin_nids[i], 0, - AC_VERB_SET_CONFIG_DEFAULT_BYTES_2, - (spec->pin_configs[i] & 0x00ff0000) >> 16); - snd_hda_codec_write(codec, spec->pin_nids[i], 0, - AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, - spec->pin_configs[i] >> 24); - pin_cfg = snd_hda_codec_read(codec, spec->pin_nids[i], 0, - AC_VERB_GET_CONFIG_DEFAULT, - 0x00); - snd_printdd(KERN_INFO "hda_codec: pin nid %2.2x pin config %8.8x\n", spec->pin_nids[i], pin_cfg); - } + for (i = 0; i < spec->num_pins; i++) + stac92xx_set_config_reg(codec, spec->pin_nids[i], + spec->pin_configs[i]); +} + +static void stac92xx_enable_gpio_mask(struct hda_codec *codec, + int gpio_mask, int gpio_data) +{ + /* Configure GPIOx as output */ + snd_hda_codec_write(codec, codec->afg, 0, + AC_VERB_SET_GPIO_DIRECTION, gpio_mask); + /* Configure GPIOx as CMOS */ + snd_hda_codec_write(codec, codec->afg, 0, 0x7e7, 0x00000000); + /* Assert GPIOx */ + snd_hda_codec_write(codec, codec->afg, 0, + AC_VERB_SET_GPIO_DATA, gpio_data); + /* Enable GPIOx */ + snd_hda_codec_write(codec, codec->afg, 0, + AC_VERB_SET_GPIO_MASK, gpio_mask); } /* @@ -1168,7 +1239,7 @@ static int is_in_dac_nids(struct sigmatel_spec *spec, hda_nid_t nid) * and 9202/925x. For those, dac_nids[] must be hard-coded. */ static int stac92xx_auto_fill_dac_nids(struct hda_codec *codec, - const struct auto_pin_cfg *cfg) + struct auto_pin_cfg *cfg) { struct sigmatel_spec *spec = codec->spec; int i, j, conn_len = 0; @@ -1193,6 +1264,13 @@ static int stac92xx_auto_fill_dac_nids(struct hda_codec *codec, } if (j == conn_len) { + if (spec->multiout.num_dacs > 0) { + /* we have already working output pins, + * so let's drop the broken ones again + */ + cfg->line_outs = spec->multiout.num_dacs; + break; + } /* error out, no available DAC found */ snd_printk(KERN_ERR "%s: No available DAC for pin 0x%x\n", @@ -1334,7 +1412,15 @@ static int stac92xx_auto_create_hp_ctls(struct hda_codec *codec, continue; add_spec_dacs(spec, nid); } - + for (i = 0; i < cfg->line_outs; i++) { + nid = snd_hda_codec_read(codec, cfg->line_out_pins[i], 0, + AC_VERB_GET_CONNECT_LIST, 0) & 0xff; + if (check_in_dac_nids(spec, nid)) + nid = 0; + if (! nid) + continue; + add_spec_dacs(spec, nid); + } for (i = old_num_dacs; i < spec->multiout.num_dacs; i++) { static const char *pfxs[] = { "Speaker", "External Speaker", "Speaker2", @@ -1891,7 +1977,7 @@ static int patch_stac9200(struct hda_codec *codec) return -ENOMEM; codec->spec = spec; - spec->num_pins = 8; + spec->num_pins = ARRAY_SIZE(stac9200_pin_nids); spec->pin_nids = stac9200_pin_nids; spec->board_config = snd_hda_check_board_config(codec, STAC_9200_MODELS, stac9200_models, @@ -1941,7 +2027,7 @@ static int patch_stac925x(struct hda_codec *codec) return -ENOMEM; codec->spec = spec; - spec->num_pins = 8; + spec->num_pins = ARRAY_SIZE(stac925x_pin_nids); spec->pin_nids = stac925x_pin_nids; spec->board_config = snd_hda_check_board_config(codec, STAC_925x_MODELS, stac925x_models, @@ -2013,29 +2099,41 @@ static int patch_stac922x(struct hda_codec *codec) return -ENOMEM; codec->spec = spec; - spec->num_pins = 10; + spec->num_pins = ARRAY_SIZE(stac922x_pin_nids); spec->pin_nids = stac922x_pin_nids; spec->board_config = snd_hda_check_board_config(codec, STAC_922X_MODELS, stac922x_models, stac922x_cfg_tbl); - if (spec->board_config == STAC_MACMINI) { + if (spec->board_config == STAC_INTEL_MAC_V3) { spec->gpio_mute = 1; /* Intel Macs have all same PCI SSID, so we need to check * codec SSID to distinguish the exact models */ printk(KERN_INFO "hda_codec: STAC922x, Apple subsys_id=%x\n", codec->subsystem_id); switch (codec->subsystem_id) { - case 0x106b0a00: /* MacBook First generatoin */ - spec->board_config = STAC_MACBOOK; + + case 0x106b0800: + spec->board_config = STAC_INTEL_MAC_V1; break; - case 0x106b0200: /* MacBook Pro first generation */ - spec->board_config = STAC_MACBOOK_PRO_V1; + case 0x106b0600: + case 0x106b0700: + spec->board_config = STAC_INTEL_MAC_V2; break; - case 0x106b1e00: /* MacBook Pro second generation */ - spec->board_config = STAC_MACBOOK_PRO_V2; + case 0x106b0e00: + case 0x106b0f00: + case 0x106b1600: + case 0x106b1700: + case 0x106b0200: + case 0x106b1e00: + spec->board_config = STAC_INTEL_MAC_V3; break; - case 0x106b0700: /* Intel-based iMac */ - spec->board_config = STAC_IMAC_INTEL; + case 0x106b1a00: + case 0x00000100: + spec->board_config = STAC_INTEL_MAC_V4; + break; + case 0x106b0a00: + case 0x106b2200: + spec->board_config = STAC_INTEL_MAC_V5; break; } } @@ -2082,6 +2180,13 @@ static int patch_stac922x(struct hda_codec *codec) codec->patch_ops = stac92xx_patch_ops; + /* Fix Mux capture level; max to 2 */ + snd_hda_override_amp_caps(codec, 0x12, HDA_OUTPUT, + (0 << AC_AMPCAP_OFFSET_SHIFT) | + (2 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x27 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); + return 0; } @@ -2095,7 +2200,7 @@ static int patch_stac927x(struct hda_codec *codec) return -ENOMEM; codec->spec = spec; - spec->num_pins = 14; + spec->num_pins = ARRAY_SIZE(stac927x_pin_nids); spec->pin_nids = stac927x_pin_nids; spec->board_config = snd_hda_check_board_config(codec, STAC_927X_MODELS, stac927x_models, @@ -2141,7 +2246,9 @@ static int patch_stac927x(struct hda_codec *codec) } spec->multiout.dac_nids = spec->dac_nids; - + /* GPIO0 High = Enable EAPD */ + stac92xx_enable_gpio_mask(codec, 0x00000001, 0x00000001); + err = stac92xx_parse_auto_config(codec, 0x1e, 0x20); if (!err) { if (spec->board_config < 0) { @@ -2159,27 +2266,20 @@ static int patch_stac927x(struct hda_codec *codec) codec->patch_ops = stac92xx_patch_ops; - /* Fix Mux capture level; max to 2 */ - snd_hda_override_amp_caps(codec, 0x12, HDA_OUTPUT, - (0 << AC_AMPCAP_OFFSET_SHIFT) | - (2 << AC_AMPCAP_NUM_STEPS_SHIFT) | - (0x27 << AC_AMPCAP_STEP_SIZE_SHIFT) | - (0 << AC_AMPCAP_MUTE_SHIFT)); - return 0; } static int patch_stac9205(struct hda_codec *codec) { struct sigmatel_spec *spec; - int err; + int err, gpio_mask, gpio_data; spec = kzalloc(sizeof(*spec), GFP_KERNEL); if (spec == NULL) return -ENOMEM; codec->spec = spec; - spec->num_pins = 14; + spec->num_pins = ARRAY_SIZE(stac9205_pin_nids); spec->pin_nids = stac9205_pin_nids; spec->board_config = snd_hda_check_board_config(codec, STAC_9205_MODELS, stac9205_models, @@ -2209,19 +2309,21 @@ static int patch_stac9205(struct hda_codec *codec) spec->mixer = stac9205_mixer; spec->multiout.dac_nids = spec->dac_nids; + + if (spec->board_config == STAC_M43xx) { + /* Enable SPDIF in/out */ + stac92xx_set_config_reg(codec, 0x1f, 0x01441030); + stac92xx_set_config_reg(codec, 0x20, 0x1c410030); - /* Configure GPIO0 as EAPD output */ - snd_hda_codec_write(codec, codec->afg, 0, - AC_VERB_SET_GPIO_DIRECTION, 0x00000001); - /* Configure GPIO0 as CMOS */ - snd_hda_codec_write(codec, codec->afg, 0, 0x7e7, 0x00000000); - /* Assert GPIO0 high */ - snd_hda_codec_write(codec, codec->afg, 0, - AC_VERB_SET_GPIO_DATA, 0x00000001); - /* Enable GPIO0 */ - snd_hda_codec_write(codec, codec->afg, 0, - AC_VERB_SET_GPIO_MASK, 0x00000001); + gpio_mask = 0x00000007; /* GPIO0-2 */ + /* GPIO0 High = EAPD, GPIO1 Low = DRM, + * GPIO2 High = Headphone Mute + */ + gpio_data = 0x00000005; + } else + gpio_mask = gpio_data = 0x00000001; /* GPIO0 High = EAPD */ + stac92xx_enable_gpio_mask(codec, gpio_mask, gpio_data); err = stac92xx_parse_auto_config(codec, 0x1f, 0x20); if (!err) { if (spec->board_config < 0) { @@ -2256,8 +2358,8 @@ static struct hda_input_mux vaio_mux = { .num_items = 2, .items = { /* { "HP", 0x0 }, */ - { "Line", 0x1 }, - { "Mic", 0x2 }, + { "Mic Jack", 0x1 }, + { "Internal Mic", 0x2 }, { "PCM", 0x3 }, } }; @@ -2268,7 +2370,7 @@ static struct hda_verb vaio_init[] = { {0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Mic? (<- 0x2) */ {0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* CD */ {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Mic? */ - {0x15, AC_VERB_SET_CONNECT_SEL, 0x2}, /* mic-sel: 0a,0d,14,02 */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x1}, /* mic-sel: 0a,0d,14,02 */ {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* HP */ {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* Speaker */ {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* capture sw/vol -> 0x8 */ @@ -2284,7 +2386,7 @@ static struct hda_verb vaio_ar_init[] = { {0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* CD */ /* {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },*/ /* Optical Out */ {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Mic? */ - {0x15, AC_VERB_SET_CONNECT_SEL, 0x2}, /* mic-sel: 0a,0d,14,02 */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x1}, /* mic-sel: 0a,0d,14,02 */ {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* HP */ {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* Speaker */ /* {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},*/ /* Optical Out */ diff --git a/sound/pci/ice1712/revo.c b/sound/pci/ice1712/revo.c index 690ceb340644..d18a31e188a9 100644 --- a/sound/pci/ice1712/revo.c +++ b/sound/pci/ice1712/revo.c @@ -186,7 +186,12 @@ static int revo51_i2c_init(struct snd_ice1712 *ice, #define AK_DAC(xname,xch) { .name = xname, .num_channels = xch } static const struct snd_akm4xxx_dac_channel revo71_front[] = { - AK_DAC("PCM Playback Volume", 2) + { + .name = "PCM Playback Volume", + .num_channels = 2, + /* front channels DAC supports muting */ + .switch_name = "PCM Playback Switch", + }, }; static const struct snd_akm4xxx_dac_channel revo71_surround[] = { diff --git a/sound/pci/nm256/nm256.c b/sound/pci/nm256/nm256.c index 03b3a4792f73..c7621bd770a6 100644 --- a/sound/pci/nm256/nm256.c +++ b/sound/pci/nm256/nm256.c @@ -1533,7 +1533,8 @@ snd_nm256_create(struct snd_card *card, struct pci_dev *pci, printk(KERN_ERR " force the driver to load by " "passing in the module parameter\n"); printk(KERN_ERR " force_ac97=1\n"); - printk(KERN_ERR " or try sb16 or cs423x drivers instead.\n"); + printk(KERN_ERR " or try sb16, opl3sa2, or " + "cs423x drivers instead.\n"); err = -ENXIO; goto __error; } diff --git a/sound/pci/rme9652/rme9652.c b/sound/pci/rme9652/rme9652.c index bd7dbd267ed1..2de27405a0bd 100644 --- a/sound/pci/rme9652/rme9652.c +++ b/sound/pci/rme9652/rme9652.c @@ -406,7 +406,7 @@ static snd_pcm_uframes_t rme9652_hw_pointer(struct snd_rme9652 *rme9652) } else if (!frag) return 0; offset -= rme9652->max_jitter; - if (offset < 0) + if ((int)offset < 0) offset += period_size * 2; } else { if (offset > period_size + rme9652->max_jitter) { diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 50c9f92cfd1b..6ea09df0c73a 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -2098,7 +2098,7 @@ static int snd_via82xx_chip_init(struct via82xx *chip) pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval); if (pval & VIA_ACLINK_C00_READY) /* primary codec ready */ break; - schedule_timeout_uninterruptible(1); + schedule_timeout(1); } while (time_before(jiffies, end_time)); if ((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_BUSY) @@ -2117,7 +2117,7 @@ static int snd_via82xx_chip_init(struct via82xx *chip) chip->ac97_secondary = 1; goto __ac97_ok2; } - schedule_timeout_interruptible(1); + schedule_timeout(1); } while (time_before(jiffies, end_time)); /* This is ok, the most of motherboards have only one codec */ diff --git a/sound/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c index 8cbf8eba4ae9..72425e73abae 100644 --- a/sound/pci/via82xx_modem.c +++ b/sound/pci/via82xx_modem.c @@ -983,7 +983,7 @@ static int snd_via82xx_chip_init(struct via82xx_modem *chip) pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval); if (pval & VIA_ACLINK_C00_READY) /* primary codec ready */ break; - schedule_timeout_uninterruptible(1); + schedule_timeout(1); } while (time_before(jiffies, end_time)); if ((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_BUSY) @@ -1001,7 +1001,7 @@ static int snd_via82xx_chip_init(struct via82xx_modem *chip) chip->ac97_secondary = 1; goto __ac97_ok2; } - schedule_timeout_interruptible(1); + schedule_timeout(1); } while (time_before(jiffies, end_time)); /* This is ok, the most of motherboards have only one codec */ diff --git a/sound/ppc/Kconfig b/sound/ppc/Kconfig index a3fb1496e4dc..cacb0b136883 100644 --- a/sound/ppc/Kconfig +++ b/sound/ppc/Kconfig @@ -33,3 +33,23 @@ config SND_POWERMAC_AUTO_DRC option. endmenu + +menu "ALSA PowerPC devices" + depends on SND!=n && ( PPC64 || PPC32 ) + +config SND_PS3 + tristate "PS3 Audio support" + depends on SND && PS3_PS3AV + select SND_PCM + default m + help + Say Y here to include support for audio on the PS3 + + To compile this driver as a module, choose M here: the module + will be called snd_ps3. + +config SND_PS3_DEFAULT_START_DELAY + int "Startup delay time in ms" + depends on SND_PS3 + default "2000" +endmenu diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile index 4d95c652c8ca..eacee2d0675c 100644 --- a/sound/ppc/Makefile +++ b/sound/ppc/Makefile @@ -6,4 +6,5 @@ snd-powermac-objs := powermac.o pmac.o awacs.o burgundy.o daca.o tumbler.o keywest.o beep.o # Toplevel Module Dependency -obj-$(CONFIG_SND_POWERMAC) += snd-powermac.o +obj-$(CONFIG_SND_POWERMAC) += snd-powermac.o +obj-$(CONFIG_SND_PS3) += snd_ps3.o diff --git a/sound/ppc/snd_ps3.c b/sound/ppc/snd_ps3.c new file mode 100644 index 000000000000..1aa0b467599f --- /dev/null +++ b/sound/ppc/snd_ps3.c @@ -0,0 +1,1125 @@ +/* + * Audio support for PS3 + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * All rights reserved. + * Copyright 2006, 2007 Sony Corporation + * + * 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; version 2 of the Licence. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "snd_ps3_reg.h" +#include "snd_ps3.h" + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PS3 sound driver"); +MODULE_AUTHOR("Sony Computer Entertainment Inc."); + +/* module entries */ +static int __init snd_ps3_init(void); +static void __exit snd_ps3_exit(void); + +/* ALSA snd driver ops */ +static int snd_ps3_pcm_open(struct snd_pcm_substream *substream); +static int snd_ps3_pcm_close(struct snd_pcm_substream *substream); +static int snd_ps3_pcm_prepare(struct snd_pcm_substream *substream); +static int snd_ps3_pcm_trigger(struct snd_pcm_substream *substream, + int cmd); +static snd_pcm_uframes_t snd_ps3_pcm_pointer(struct snd_pcm_substream + *substream); +static int snd_ps3_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params); +static int snd_ps3_pcm_hw_free(struct snd_pcm_substream *substream); + + +/* ps3_system_bus_driver entries */ +static int __init snd_ps3_driver_probe(struct ps3_system_bus_device *dev); +static int snd_ps3_driver_remove(struct ps3_system_bus_device *dev); + +/* address setup */ +static int snd_ps3_map_mmio(void); +static void snd_ps3_unmap_mmio(void); +static int snd_ps3_allocate_irq(void); +static void snd_ps3_free_irq(void); +static void snd_ps3_audio_set_base_addr(uint64_t ioaddr_start); + +/* interrupt handler */ +static irqreturn_t snd_ps3_interrupt(int irq, void *dev_id); + + +/* set sampling rate/format */ +static int snd_ps3_set_avsetting(struct snd_pcm_substream *substream); +/* take effect parameter change */ +static int snd_ps3_change_avsetting(struct snd_ps3_card_info *card); +/* initialize avsetting and take it effect */ +static int snd_ps3_init_avsetting(struct snd_ps3_card_info *card); +/* setup dma */ +static int snd_ps3_program_dma(struct snd_ps3_card_info *card, + enum snd_ps3_dma_filltype filltype); +static void snd_ps3_wait_for_dma_stop(struct snd_ps3_card_info *card); + +static dma_addr_t v_to_bus(struct snd_ps3_card_info *, void *vaddr, int ch); + + +module_init(snd_ps3_init); +module_exit(snd_ps3_exit); + +/* + * global + */ +static struct snd_ps3_card_info the_card; + +static int snd_ps3_start_delay = CONFIG_SND_PS3_DEFAULT_START_DELAY; + +module_param_named(start_delay, snd_ps3_start_delay, uint, 0644); +MODULE_PARM_DESC(start_delay, "time to insert silent data in milisec"); + +static int index = SNDRV_DEFAULT_IDX1; +static char *id = SNDRV_DEFAULT_STR1; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for PS3 soundchip."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for PS3 soundchip."); + + +/* + * PS3 audio register access + */ +static inline u32 read_reg(unsigned int reg) +{ + return in_be32(the_card.mapped_mmio_vaddr + reg); +} +static inline void write_reg(unsigned int reg, u32 val) +{ + out_be32(the_card.mapped_mmio_vaddr + reg, val); +} +static inline void update_reg(unsigned int reg, u32 or_val) +{ + u32 newval = read_reg(reg) | or_val; + write_reg(reg, newval); +} +static inline void update_mask_reg(unsigned int reg, u32 mask, u32 or_val) +{ + u32 newval = (read_reg(reg) & mask) | or_val; + write_reg(reg, newval); +} + +/* + * ALSA defs + */ +const static struct snd_pcm_hardware snd_ps3_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE), + .rates = (SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000), + .rate_min = 44100, + .rate_max = 96000, + + .channels_min = 2, /* stereo only */ + .channels_max = 2, + + .buffer_bytes_max = PS3_AUDIO_FIFO_SIZE * 64, + + /* interrupt by four stages */ + .period_bytes_min = PS3_AUDIO_FIFO_STAGE_SIZE * 4, + .period_bytes_max = PS3_AUDIO_FIFO_STAGE_SIZE * 4, + + .periods_min = 16, + .periods_max = 32, /* buffer_size_max/ period_bytes_max */ + + .fifo_size = PS3_AUDIO_FIFO_SIZE +}; + +static struct snd_pcm_ops snd_ps3_pcm_spdif_ops = +{ + .open = snd_ps3_pcm_open, + .close = snd_ps3_pcm_close, + .prepare = snd_ps3_pcm_prepare, + .ioctl = snd_pcm_lib_ioctl, + .trigger = snd_ps3_pcm_trigger, + .pointer = snd_ps3_pcm_pointer, + .hw_params = snd_ps3_pcm_hw_params, + .hw_free = snd_ps3_pcm_hw_free +}; + +static int snd_ps3_verify_dma_stop(struct snd_ps3_card_info *card, + int count, int force_stop) +{ + int dma_ch, done, retries, stop_forced = 0; + uint32_t status; + + for (dma_ch = 0; dma_ch < 8; dma_ch ++) { + retries = count; + do { + status = read_reg(PS3_AUDIO_KICK(dma_ch)) & + PS3_AUDIO_KICK_STATUS_MASK; + switch (status) { + case PS3_AUDIO_KICK_STATUS_DONE: + case PS3_AUDIO_KICK_STATUS_NOTIFY: + case PS3_AUDIO_KICK_STATUS_CLEAR: + case PS3_AUDIO_KICK_STATUS_ERROR: + done = 1; + break; + default: + done = 0; + udelay(10); + } + } while (!done && --retries); + if (!retries && force_stop) { + pr_info("%s: DMA ch %d is not stopped.", + __func__, dma_ch); + /* last resort. force to stop dma. + * NOTE: this cause DMA done interrupts + */ + update_reg(PS3_AUDIO_CONFIG, PS3_AUDIO_CONFIG_CLEAR); + stop_forced = 1; + } + } + return stop_forced; +} + +/* + * wait for all dma is done. + * NOTE: caller should reset card->running before call. + * If not, the interrupt handler will re-start DMA, + * then DMA is never stopped. + */ +static void snd_ps3_wait_for_dma_stop(struct snd_ps3_card_info *card) +{ + int stop_forced; + /* + * wait for the last dma is done + */ + + /* + * expected maximum DMA done time is 5.7ms + something (DMA itself). + * 5.7ms is from 16bit/sample 2ch 44.1Khz; the time next + * DMA kick event would occur. + */ + stop_forced = snd_ps3_verify_dma_stop(card, 700, 1); + + /* + * clear outstanding interrupts. + */ + update_reg(PS3_AUDIO_INTR_0, 0); + update_reg(PS3_AUDIO_AX_IS, 0); + + /* + *revert CLEAR bit since it will not reset automatically after DMA stop + */ + if (stop_forced) + update_mask_reg(PS3_AUDIO_CONFIG, ~PS3_AUDIO_CONFIG_CLEAR, 0); + /* ensure the hardware sees changes */ + wmb(); +} + +static void snd_ps3_kick_dma(struct snd_ps3_card_info *card) +{ + + update_reg(PS3_AUDIO_KICK(0), PS3_AUDIO_KICK_REQUEST); + /* ensure the hardware sees the change */ + wmb(); +} + +/* + * convert virtual addr to ioif bus addr. + */ +static dma_addr_t v_to_bus(struct snd_ps3_card_info *card, + void * paddr, + int ch) +{ + return card->dma_start_bus_addr[ch] + + (paddr - card->dma_start_vaddr[ch]); +}; + + +/* + * increment ring buffer pointer. + * NOTE: caller must hold write spinlock + */ +static void snd_ps3_bump_buffer(struct snd_ps3_card_info *card, + enum snd_ps3_ch ch, size_t byte_count, + int stage) +{ + if (!stage) + card->dma_last_transfer_vaddr[ch] = + card->dma_next_transfer_vaddr[ch]; + card->dma_next_transfer_vaddr[ch] += byte_count; + if ((card->dma_start_vaddr[ch] + (card->dma_buffer_size / 2)) <= + card->dma_next_transfer_vaddr[ch]) { + card->dma_next_transfer_vaddr[ch] = card->dma_start_vaddr[ch]; + } +} +/* + * setup dmac to send data to audio and attenuate samples on the ring buffer + */ +static int snd_ps3_program_dma(struct snd_ps3_card_info *card, + enum snd_ps3_dma_filltype filltype) +{ + /* this dmac does not support over 4G */ + uint32_t dma_addr; + int fill_stages, dma_ch, stage; + enum snd_ps3_ch ch; + uint32_t ch0_kick_event = 0; /* initialize to mute gcc */ + void *start_vaddr; + unsigned long irqsave; + int silent = 0; + + switch (filltype) { + case SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL: + silent = 1; + /* intentionally fall thru */ + case SND_PS3_DMA_FILLTYPE_FIRSTFILL: + ch0_kick_event = PS3_AUDIO_KICK_EVENT_ALWAYS; + break; + + case SND_PS3_DMA_FILLTYPE_SILENT_RUNNING: + silent = 1; + /* intentionally fall thru */ + case SND_PS3_DMA_FILLTYPE_RUNNING: + ch0_kick_event = PS3_AUDIO_KICK_EVENT_SERIALOUT0_EMPTY; + break; + } + + snd_ps3_verify_dma_stop(card, 700, 0); + fill_stages = 4; + spin_lock_irqsave(&card->dma_lock, irqsave); + for (ch = 0; ch < 2; ch++) { + start_vaddr = card->dma_next_transfer_vaddr[0]; + for (stage = 0; stage < fill_stages; stage ++) { + dma_ch = stage * 2 + ch; + if (silent) + dma_addr = card->null_buffer_start_dma_addr; + else + dma_addr = + v_to_bus(card, + card->dma_next_transfer_vaddr[ch], + ch); + + write_reg(PS3_AUDIO_SOURCE(dma_ch), + (PS3_AUDIO_SOURCE_TARGET_SYSTEM_MEMORY | + dma_addr)); + + /* dst: fixed to 3wire#0 */ + if (ch == 0) + write_reg(PS3_AUDIO_DEST(dma_ch), + (PS3_AUDIO_DEST_TARGET_AUDIOFIFO | + PS3_AUDIO_AO_3W_LDATA(0))); + else + write_reg(PS3_AUDIO_DEST(dma_ch), + (PS3_AUDIO_DEST_TARGET_AUDIOFIFO | + PS3_AUDIO_AO_3W_RDATA(0))); + + /* count always 1 DMA block (1/2 stage = 128 bytes) */ + write_reg(PS3_AUDIO_DMASIZE(dma_ch), 0); + /* bump pointer if needed */ + if (!silent) + snd_ps3_bump_buffer(card, ch, + PS3_AUDIO_DMAC_BLOCK_SIZE, + stage); + + /* kick event */ + if (dma_ch == 0) + write_reg(PS3_AUDIO_KICK(dma_ch), + ch0_kick_event); + else + write_reg(PS3_AUDIO_KICK(dma_ch), + PS3_AUDIO_KICK_EVENT_AUDIO_DMA(dma_ch + - 1) | + PS3_AUDIO_KICK_REQUEST); + } + } + /* ensure the hardware sees the change */ + wmb(); + spin_unlock_irqrestore(&card->dma_lock, irqsave); + + return 0; +} + +/* + * audio mute on/off + * mute_on : 0 output enabled + * 1 mute + */ +static int snd_ps3_mute(int mute_on) +{ + return ps3av_audio_mute(mute_on); +} + +/* + * PCM operators + */ +static int snd_ps3_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + int pcm_index; + + pcm_index = substream->pcm->device; + /* to retrieve substream/runtime in interrupt handler */ + card->substream = substream; + + runtime->hw = snd_ps3_pcm_hw; + + card->start_delay = snd_ps3_start_delay; + + /* mute off */ + snd_ps3_mute(0); /* this function sleep */ + + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + PS3_AUDIO_FIFO_STAGE_SIZE * 4 * 2); + return 0; +}; + +static int snd_ps3_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + size_t size; + + /* alloc transport buffer */ + size = params_buffer_bytes(hw_params); + snd_pcm_lib_malloc_pages(substream, size); + return 0; +}; + +static int snd_ps3_delay_to_bytes(struct snd_pcm_substream *substream, + unsigned int delay_ms) +{ + int ret; + int rate ; + + rate = substream->runtime->rate; + ret = snd_pcm_format_size(substream->runtime->format, + rate * delay_ms / 1000) + * substream->runtime->channels; + + pr_debug(KERN_ERR "%s: time=%d rate=%d bytes=%ld, frames=%d, ret=%d\n", + __func__, + delay_ms, + rate, + snd_pcm_format_size(substream->runtime->format, rate), + rate * delay_ms / 1000, + ret); + + return ret; +}; + +static int snd_ps3_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + unsigned long irqsave; + + if (!snd_ps3_set_avsetting(substream)) { + /* some parameter changed */ + write_reg(PS3_AUDIO_AX_IE, + PS3_AUDIO_AX_IE_ASOBEIE(0) | + PS3_AUDIO_AX_IE_ASOBUIE(0)); + /* + * let SPDIF device re-lock with SPDIF signal, + * start with some silence + */ + card->silent = snd_ps3_delay_to_bytes(substream, + card->start_delay) / + (PS3_AUDIO_FIFO_STAGE_SIZE * 4); /* every 4 times */ + } + + /* restart ring buffer pointer */ + spin_lock_irqsave(&card->dma_lock, irqsave); + { + card->dma_buffer_size = runtime->dma_bytes; + + card->dma_last_transfer_vaddr[SND_PS3_CH_L] = + card->dma_next_transfer_vaddr[SND_PS3_CH_L] = + card->dma_start_vaddr[SND_PS3_CH_L] = + runtime->dma_area; + card->dma_start_bus_addr[SND_PS3_CH_L] = runtime->dma_addr; + + card->dma_last_transfer_vaddr[SND_PS3_CH_R] = + card->dma_next_transfer_vaddr[SND_PS3_CH_R] = + card->dma_start_vaddr[SND_PS3_CH_R] = + runtime->dma_area + (runtime->dma_bytes / 2); + card->dma_start_bus_addr[SND_PS3_CH_R] = + runtime->dma_addr + (runtime->dma_bytes / 2); + + pr_debug("%s: vaddr=%p bus=%#lx\n", __func__, + card->dma_start_vaddr[SND_PS3_CH_L], + card->dma_start_bus_addr[SND_PS3_CH_L]); + + } + spin_unlock_irqrestore(&card->dma_lock, irqsave); + + /* ensure the hardware sees the change */ + mb(); + + return 0; +}; + +static int snd_ps3_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* clear outstanding interrupts */ + update_reg(PS3_AUDIO_AX_IS, 0); + + spin_lock(&card->dma_lock); + { + card->running = 1; + } + spin_unlock(&card->dma_lock); + + snd_ps3_program_dma(card, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + while (read_reg(PS3_AUDIO_KICK(7)) & + PS3_AUDIO_KICK_STATUS_MASK) { + udelay(1); + } + snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_RUNNING); + snd_ps3_kick_dma(card); + break; + + case SNDRV_PCM_TRIGGER_STOP: + spin_lock(&card->dma_lock); + { + card->running = 0; + } + spin_unlock(&card->dma_lock); + snd_ps3_wait_for_dma_stop(card); + break; + default: + break; + + } + + return ret; +}; + +/* + * report current pointer + */ +static snd_pcm_uframes_t snd_ps3_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + size_t bytes; + snd_pcm_uframes_t ret; + + spin_lock(&card->dma_lock); + { + bytes = (size_t)(card->dma_last_transfer_vaddr[SND_PS3_CH_L] - + card->dma_start_vaddr[SND_PS3_CH_L]); + } + spin_unlock(&card->dma_lock); + + ret = bytes_to_frames(substream->runtime, bytes * 2); + + return ret; +}; + +static int snd_ps3_pcm_hw_free(struct snd_pcm_substream *substream) +{ + int ret; + ret = snd_pcm_lib_free_pages(substream); + return ret; +}; + +static int snd_ps3_pcm_close(struct snd_pcm_substream *substream) +{ + /* mute on */ + snd_ps3_mute(1); + return 0; +}; + +static void snd_ps3_audio_fixup(struct snd_ps3_card_info *card) +{ + /* + * avsetting driver seems to never change the followings + * so, init them here once + */ + + /* no dma interrupt needed */ + write_reg(PS3_AUDIO_INTR_EN_0, 0); + + /* use every 4 buffer empty interrupt */ + update_mask_reg(PS3_AUDIO_AX_IC, + PS3_AUDIO_AX_IC_AASOIMD_MASK, + PS3_AUDIO_AX_IC_AASOIMD_EVERY4); + + /* enable 3wire clocks */ + update_mask_reg(PS3_AUDIO_AO_3WMCTRL, + ~(PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_DISABLED | + PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_DISABLED), + 0); + update_reg(PS3_AUDIO_AO_3WMCTRL, + PS3_AUDIO_AO_3WMCTRL_ASOPLRCK_DEFAULT); +} + +/* + * av setting + * NOTE: calling this function may generate audio interrupt. + */ +static int snd_ps3_change_avsetting(struct snd_ps3_card_info *card) +{ + int ret, retries, i; + pr_debug("%s: start\n", __func__); + + ret = ps3av_set_audio_mode(card->avs.avs_audio_ch, + card->avs.avs_audio_rate, + card->avs.avs_audio_width, + card->avs.avs_audio_format, + card->avs.avs_audio_source); + /* + * Reset the following unwanted settings: + */ + + /* disable all 3wire buffers */ + update_mask_reg(PS3_AUDIO_AO_3WMCTRL, + ~(PS3_AUDIO_AO_3WMCTRL_ASOEN(0) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(1) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(2) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(3)), + 0); + wmb(); /* ensure the hardware sees the change */ + /* wait for actually stopped */ + retries = 1000; + while ((read_reg(PS3_AUDIO_AO_3WMCTRL) & + (PS3_AUDIO_AO_3WMCTRL_ASORUN(0) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(1) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(2) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(3))) && + --retries) { + udelay(1); + } + + /* reset buffer pointer */ + for (i = 0; i < 4; i++) { + update_reg(PS3_AUDIO_AO_3WCTRL(i), + PS3_AUDIO_AO_3WCTRL_ASOBRST_RESET); + udelay(10); + } + wmb(); /* ensure the hardware actually start resetting */ + + /* enable 3wire#0 buffer */ + update_reg(PS3_AUDIO_AO_3WMCTRL, PS3_AUDIO_AO_3WMCTRL_ASOEN(0)); + + + /* In 24bit mode,ALSA inserts a zero byte at first byte of per sample */ + update_mask_reg(PS3_AUDIO_AO_3WCTRL(0), + ~PS3_AUDIO_AO_3WCTRL_ASODF, + PS3_AUDIO_AO_3WCTRL_ASODF_LSB); + update_mask_reg(PS3_AUDIO_AO_SPDCTRL(0), + ~PS3_AUDIO_AO_SPDCTRL_SPODF, + PS3_AUDIO_AO_SPDCTRL_SPODF_LSB); + /* ensure all the setting above is written back to register */ + wmb(); + /* avsetting driver altered AX_IE, caller must reset it if you want */ + pr_debug("%s: end\n", __func__); + return ret; +} + +static int snd_ps3_init_avsetting(struct snd_ps3_card_info *card) +{ + int ret; + pr_debug("%s: start\n", __func__); + card->avs.avs_audio_ch = PS3AV_CMD_AUDIO_NUM_OF_CH_2; + card->avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K; + card->avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16; + card->avs.avs_audio_format = PS3AV_CMD_AUDIO_FORMAT_PCM; + card->avs.avs_audio_source = PS3AV_CMD_AUDIO_SOURCE_SERIAL; + + ret = snd_ps3_change_avsetting(card); + + snd_ps3_audio_fixup(card); + + /* to start to generate SPDIF signal, fill data */ + snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + pr_debug("%s: end\n", __func__); + return ret; +} + +/* + * set sampling rate according to the substream + */ +static int snd_ps3_set_avsetting(struct snd_pcm_substream *substream) +{ + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + struct snd_ps3_avsetting_info avs; + + avs = card->avs; + + pr_debug("%s: called freq=%d width=%d\n", __func__, + substream->runtime->rate, + snd_pcm_format_width(substream->runtime->format)); + + pr_debug("%s: before freq=%d width=%d\n", __func__, + card->avs.avs_audio_rate, card->avs.avs_audio_width); + + /* sample rate */ + switch (substream->runtime->rate) { + case 44100: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_44K; + break; + case 48000: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K; + break; + case 88200: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_88K; + break; + case 96000: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_96K; + break; + default: + pr_info("%s: invalid rate %d\n", __func__, + substream->runtime->rate); + return 1; + } + + /* width */ + switch (snd_pcm_format_width(substream->runtime->format)) { + case 16: + avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16; + break; + case 24: + avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_24; + break; + default: + pr_info("%s: invalid width %d\n", __func__, + snd_pcm_format_width(substream->runtime->format)); + return 1; + } + + if ((card->avs.avs_audio_width != avs.avs_audio_width) || + (card->avs.avs_audio_rate != avs.avs_audio_rate)) { + card->avs = avs; + snd_ps3_change_avsetting(card); + + pr_debug("%s: after freq=%d width=%d\n", __func__, + card->avs.avs_audio_rate, card->avs.avs_audio_width); + + return 0; + } else + return 1; +} + + + +static int snd_ps3_map_mmio(void) +{ + the_card.mapped_mmio_vaddr = + ioremap(the_card.ps3_dev->m_region->bus_addr, + the_card.ps3_dev->m_region->len); + + if (!the_card.mapped_mmio_vaddr) { + pr_info("%s: ioremap 0 failed p=%#lx l=%#lx \n", + __func__, the_card.ps3_dev->m_region->lpar_addr, + the_card.ps3_dev->m_region->len); + return -ENXIO; + } + + return 0; +}; + +static void snd_ps3_unmap_mmio(void) +{ + iounmap(the_card.mapped_mmio_vaddr); + the_card.mapped_mmio_vaddr = NULL; +} + +static int snd_ps3_allocate_irq(void) +{ + int ret; + u64 lpar_addr, lpar_size; + u64 __iomem *mapped; + + /* FIXME: move this to device_init (H/W probe) */ + + /* get irq outlet */ + ret = lv1_gpu_device_map(1, &lpar_addr, &lpar_size); + if (ret) { + pr_info("%s: device map 1 failed %d\n", __func__, + ret); + return -ENXIO; + } + + mapped = ioremap(lpar_addr, lpar_size); + if (!mapped) { + pr_info("%s: ioremap 1 failed \n", __func__); + return -ENXIO; + } + + the_card.audio_irq_outlet = in_be64(mapped); + + iounmap(mapped); + ret = lv1_gpu_device_unmap(1); + if (ret) + pr_info("%s: unmap 1 failed\n", __func__); + + /* irq */ + ret = ps3_irq_plug_setup(PS3_BINDING_CPU_ANY, + the_card.audio_irq_outlet, + &the_card.irq_no); + if (ret) { + pr_info("%s:ps3_alloc_irq failed (%d)\n", __func__, ret); + return ret; + } + + ret = request_irq(the_card.irq_no, snd_ps3_interrupt, IRQF_DISABLED, + SND_PS3_DRIVER_NAME, &the_card); + if (ret) { + pr_info("%s: request_irq failed (%d)\n", __func__, ret); + goto cleanup_irq; + } + + return 0; + + cleanup_irq: + ps3_irq_plug_destroy(the_card.irq_no); + return ret; +}; + +static void snd_ps3_free_irq(void) +{ + free_irq(the_card.irq_no, &the_card); + ps3_irq_plug_destroy(the_card.irq_no); +} + +static void snd_ps3_audio_set_base_addr(uint64_t ioaddr_start) +{ + uint64_t val; + int ret; + + val = (ioaddr_start & (0x0fUL << 32)) >> (32 - 20) | + (0x03UL << 24) | + (0x0fUL << 12) | + (PS3_AUDIO_IOID); + + ret = lv1_gpu_attribute(0x100, 0x007, val, 0, 0); + if (ret) + pr_info("%s: gpu_attribute failed %d\n", __func__, + ret); +} + +static int __init snd_ps3_driver_probe(struct ps3_system_bus_device *dev) +{ + int ret; + u64 lpar_addr, lpar_size; + + BUG_ON(!firmware_has_feature(FW_FEATURE_PS3_LV1)); + BUG_ON(dev->match_id != PS3_MATCH_ID_SOUND); + + the_card.ps3_dev = dev; + + ret = ps3_open_hv_device(dev); + + if (ret) + return -ENXIO; + + /* setup MMIO */ + ret = lv1_gpu_device_map(2, &lpar_addr, &lpar_size); + if (ret) { + pr_info("%s: device map 2 failed %d\n", __func__, ret); + goto clean_open; + } + ps3_mmio_region_init(dev, dev->m_region, lpar_addr, lpar_size, + PAGE_SHIFT); + + ret = snd_ps3_map_mmio(); + if (ret) + goto clean_dev_map; + + /* setup DMA area */ + ps3_dma_region_init(dev, dev->d_region, + PAGE_SHIFT, /* use system page size */ + 0, /* dma type; not used */ + NULL, + _ALIGN_UP(SND_PS3_DMA_REGION_SIZE, PAGE_SIZE)); + dev->d_region->ioid = PS3_AUDIO_IOID; + + ret = ps3_dma_region_create(dev->d_region); + if (ret) { + pr_info("%s: region_create\n", __func__); + goto clean_mmio; + } + + snd_ps3_audio_set_base_addr(dev->d_region->bus_addr); + + /* CONFIG_SND_PS3_DEFAULT_START_DELAY */ + the_card.start_delay = snd_ps3_start_delay; + + /* irq */ + if (snd_ps3_allocate_irq()) { + ret = -ENXIO; + goto clean_dma_region; + } + + /* create card instance */ + the_card.card = snd_card_new(index, id, THIS_MODULE, 0); + if (!the_card.card) { + ret = -ENXIO; + goto clean_irq; + } + + strcpy(the_card.card->driver, "PS3"); + strcpy(the_card.card->shortname, "PS3"); + strcpy(the_card.card->longname, "PS3 sound"); + /* create PCM devices instance */ + /* NOTE:this driver works assuming pcm:substream = 1:1 */ + ret = snd_pcm_new(the_card.card, + "SPDIF", + 0, /* instance index, will be stored pcm.device*/ + 1, /* output substream */ + 0, /* input substream */ + &(the_card.pcm)); + if (ret) + goto clean_card; + + the_card.pcm->private_data = &the_card; + strcpy(the_card.pcm->name, "SPDIF"); + + /* set pcm ops */ + snd_pcm_set_ops(the_card.pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_ps3_pcm_spdif_ops); + + the_card.pcm->info_flags = SNDRV_PCM_INFO_NONINTERLEAVED; + /* pre-alloc PCM DMA buffer*/ + ret = snd_pcm_lib_preallocate_pages_for_all(the_card.pcm, + SNDRV_DMA_TYPE_DEV, + &dev->core, + SND_PS3_PCM_PREALLOC_SIZE, + SND_PS3_PCM_PREALLOC_SIZE); + if (ret < 0) { + pr_info("%s: prealloc failed\n", __func__); + goto clean_card; + } + + /* + * allocate null buffer + * its size should be lager than PS3_AUDIO_FIFO_STAGE_SIZE * 2 + * PAGE_SIZE is enogh + */ + if (!(the_card.null_buffer_start_vaddr = + dma_alloc_coherent(&the_card.ps3_dev->core, + PAGE_SIZE, + &the_card.null_buffer_start_dma_addr, + GFP_KERNEL))) { + pr_info("%s: nullbuffer alloc failed\n", __func__); + goto clean_preallocate; + } + pr_debug("%s: null vaddr=%p dma=%#lx\n", __func__, + the_card.null_buffer_start_vaddr, + the_card.null_buffer_start_dma_addr); + /* set default sample rate/word width */ + snd_ps3_init_avsetting(&the_card); + + /* register the card */ + ret = snd_card_register(the_card.card); + if (ret < 0) + goto clean_dma_map; + + pr_info("%s started. start_delay=%dms\n", + the_card.card->longname, the_card.start_delay); + return 0; + +clean_dma_map: + dma_free_coherent(&the_card.ps3_dev->core, + PAGE_SIZE, + the_card.null_buffer_start_vaddr, + the_card.null_buffer_start_dma_addr); +clean_preallocate: + snd_pcm_lib_preallocate_free_for_all(the_card.pcm); +clean_card: + snd_card_free(the_card.card); +clean_irq: + snd_ps3_free_irq(); +clean_dma_region: + ps3_dma_region_free(dev->d_region); +clean_mmio: + snd_ps3_unmap_mmio(); +clean_dev_map: + lv1_gpu_device_unmap(2); +clean_open: + ps3_close_hv_device(dev); + /* + * there is no destructor function to pcm. + * midlayer automatically releases if the card removed + */ + return ret; +}; /* snd_ps3_probe */ + +/* called when module removal */ +static int snd_ps3_driver_remove(struct ps3_system_bus_device *dev) +{ + int ret; + pr_info("%s:start id=%d\n", __func__, dev->match_id); + if (dev->match_id != PS3_MATCH_ID_SOUND) + return -ENXIO; + + /* + * ctl and preallocate buffer will be freed in + * snd_card_free + */ + ret = snd_card_free(the_card.card); + if (ret) + pr_info("%s: ctl freecard=%d\n", __func__, ret); + + dma_free_coherent(&dev->core, + PAGE_SIZE, + the_card.null_buffer_start_vaddr, + the_card.null_buffer_start_dma_addr); + + ps3_dma_region_free(dev->d_region); + + snd_ps3_free_irq(); + snd_ps3_unmap_mmio(); + + lv1_gpu_device_unmap(2); + ps3_close_hv_device(dev); + pr_info("%s:end id=%d\n", __func__, dev->match_id); + return 0; +} /* snd_ps3_remove */ + +static struct ps3_system_bus_driver snd_ps3_bus_driver_info = { + .match_id = PS3_MATCH_ID_SOUND, + .probe = snd_ps3_driver_probe, + .remove = snd_ps3_driver_remove, + .shutdown = snd_ps3_driver_remove, + .core = { + .name = SND_PS3_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + + +/* + * Interrupt handler + */ +static irqreturn_t snd_ps3_interrupt(int irq, void *dev_id) +{ + + uint32_t port_intr; + int underflow_occured = 0; + struct snd_ps3_card_info *card = dev_id; + + if (!card->running) { + update_reg(PS3_AUDIO_AX_IS, 0); + update_reg(PS3_AUDIO_INTR_0, 0); + return IRQ_HANDLED; + } + + port_intr = read_reg(PS3_AUDIO_AX_IS); + /* + *serial buffer empty detected (every 4 times), + *program next dma and kick it + */ + if (port_intr & PS3_AUDIO_AX_IE_ASOBEIE(0)) { + write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBEIE(0)); + if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) { + write_reg(PS3_AUDIO_AX_IS, port_intr); + underflow_occured = 1; + } + if (card->silent) { + /* we are still in silent time */ + snd_ps3_program_dma(card, + (underflow_occured) ? + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL : + SND_PS3_DMA_FILLTYPE_SILENT_RUNNING); + snd_ps3_kick_dma(card); + card->silent --; + } else { + snd_ps3_program_dma(card, + (underflow_occured) ? + SND_PS3_DMA_FILLTYPE_FIRSTFILL : + SND_PS3_DMA_FILLTYPE_RUNNING); + snd_ps3_kick_dma(card); + snd_pcm_period_elapsed(card->substream); + } + } else if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) { + write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBUIE(0)); + /* + * serial out underflow, but buffer empty not detected. + * in this case, fill fifo with 0 to recover. After + * filling dummy data, serial automatically start to + * consume them and then will generate normal buffer + * empty interrupts. + * If both buffer underflow and buffer empty are occured, + * it is better to do nomal data transfer than empty one + */ + snd_ps3_program_dma(card, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + snd_ps3_program_dma(card, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + } + /* clear interrupt cause */ + return IRQ_HANDLED; +}; + +/* + * module/subsystem initialize/terminate + */ +static int __init snd_ps3_init(void) +{ + int ret; + + if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) + return -ENXIO; + + memset(&the_card, 0, sizeof(the_card)); + spin_lock_init(&the_card.dma_lock); + + /* register systembus DRIVER, this calls our probe() func */ + ret = ps3_system_bus_driver_register(&snd_ps3_bus_driver_info); + + return ret; +} + +static void __exit snd_ps3_exit(void) +{ + ps3_system_bus_driver_unregister(&snd_ps3_bus_driver_info); +} + +MODULE_ALIAS(PS3_MODULE_ALIAS_SOUND); diff --git a/sound/ppc/snd_ps3.h b/sound/ppc/snd_ps3.h new file mode 100644 index 000000000000..4b7e6fbbe500 --- /dev/null +++ b/sound/ppc/snd_ps3.h @@ -0,0 +1,135 @@ +/* + * Audio support for PS3 + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * All rights reserved. + * Copyright 2006, 2007 Sony Corporation + * + * 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; version 2 of the Licence. + * + * 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 + */ + +#if !defined(_SND_PS3_H_) +#define _SND_PS3_H_ + +#include + +#define SND_PS3_DRIVER_NAME "snd_ps3" + +enum snd_ps3_out_channel { + SND_PS3_OUT_SPDIF_0, + SND_PS3_OUT_SPDIF_1, + SND_PS3_OUT_SERIAL_0, + SND_PS3_OUT_DEVS +}; + +enum snd_ps3_dma_filltype { + SND_PS3_DMA_FILLTYPE_FIRSTFILL, + SND_PS3_DMA_FILLTYPE_RUNNING, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL, + SND_PS3_DMA_FILLTYPE_SILENT_RUNNING +}; + +enum snd_ps3_ch { + SND_PS3_CH_L = 0, + SND_PS3_CH_R = 1, + SND_PS3_CH_MAX = 2 +}; + +struct snd_ps3_avsetting_info { + uint32_t avs_audio_ch; /* fixed */ + uint32_t avs_audio_rate; + uint32_t avs_audio_width; + uint32_t avs_audio_format; /* fixed */ + uint32_t avs_audio_source; /* fixed */ +}; +/* + * PS3 audio 'card' instance + * there should be only ONE hardware. + */ +struct snd_ps3_card_info { + struct ps3_system_bus_device *ps3_dev; + struct snd_card *card; + + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + + /* hvc info */ + u64 audio_lpar_addr; + u64 audio_lpar_size; + + /* registers */ + void __iomem *mapped_mmio_vaddr; + + /* irq */ + u64 audio_irq_outlet; + unsigned int irq_no; + + /* remember avsetting */ + struct snd_ps3_avsetting_info avs; + + /* dma buffer management */ + spinlock_t dma_lock; + /* dma_lock start */ + void * dma_start_vaddr[2]; /* 0 for L, 1 for R */ + dma_addr_t dma_start_bus_addr[2]; + size_t dma_buffer_size; + void * dma_last_transfer_vaddr[2]; + void * dma_next_transfer_vaddr[2]; + int silent; + /* dma_lock end */ + + int running; + + /* null buffer */ + void *null_buffer_start_vaddr; + dma_addr_t null_buffer_start_dma_addr; + + /* start delay */ + unsigned int start_delay; + +}; + + +/* PS3 audio DMAC block size in bytes */ +#define PS3_AUDIO_DMAC_BLOCK_SIZE (128) +/* one stage (stereo) of audio FIFO in bytes */ +#define PS3_AUDIO_FIFO_STAGE_SIZE (256) +/* how many stages the fifo have */ +#define PS3_AUDIO_FIFO_STAGE_COUNT (8) +/* fifo size 128 bytes * 8 stages * stereo (2ch) */ +#define PS3_AUDIO_FIFO_SIZE \ + (PS3_AUDIO_FIFO_STAGE_SIZE * PS3_AUDIO_FIFO_STAGE_COUNT) + +/* PS3 audio DMAC max block count in one dma shot = 128 (0x80) blocks*/ +#define PS3_AUDIO_DMAC_MAX_BLOCKS (PS3_AUDIO_DMASIZE_BLOCKS_MASK + 1) + +#define PS3_AUDIO_NORMAL_DMA_START_CH (0) +#define PS3_AUDIO_NORMAL_DMA_COUNT (8) +#define PS3_AUDIO_NULL_DMA_START_CH \ + (PS3_AUDIO_NORMAL_DMA_START_CH + PS3_AUDIO_NORMAL_DMA_COUNT) +#define PS3_AUDIO_NULL_DMA_COUNT (2) + +#define SND_PS3_MAX_VOL (0x0F) +#define SND_PS3_MIN_VOL (0x00) +#define SND_PS3_MIN_ATT SND_PS3_MIN_VOL +#define SND_PS3_MAX_ATT SND_PS3_MAX_VOL + +#define SND_PS3_PCM_PREALLOC_SIZE \ + (PS3_AUDIO_DMAC_BLOCK_SIZE * PS3_AUDIO_DMAC_MAX_BLOCKS * 4) + +#define SND_PS3_DMA_REGION_SIZE \ + (SND_PS3_PCM_PREALLOC_SIZE + PAGE_SIZE) + +#define PS3_AUDIO_IOID (1UL) + +#endif /* _SND_PS3_H_ */ diff --git a/sound/ppc/snd_ps3_reg.h b/sound/ppc/snd_ps3_reg.h new file mode 100644 index 000000000000..03fdee4aaaf2 --- /dev/null +++ b/sound/ppc/snd_ps3_reg.h @@ -0,0 +1,891 @@ +/* + * Audio support for PS3 + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * Copyright 2006, 2007 Sony Corporation + * All rights reserved. + * + * 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; version 2 of the License. + * + * 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 + */ + +/* + * interrupt / configure registers + */ + +#define PS3_AUDIO_INTR_0 (0x00000100) +#define PS3_AUDIO_INTR_EN_0 (0x00000140) +#define PS3_AUDIO_CONFIG (0x00000200) + +/* + * DMAC registers + * n:0..9 + */ +#define PS3_AUDIO_DMAC_REGBASE(x) (0x0000210 + 0x20 * (x)) + +#define PS3_AUDIO_KICK(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x00) +#define PS3_AUDIO_SOURCE(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x04) +#define PS3_AUDIO_DEST(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x08) +#define PS3_AUDIO_DMASIZE(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x0C) + +/* + * mute control + */ +#define PS3_AUDIO_AX_MCTRL (0x00004000) +#define PS3_AUDIO_AX_ISBP (0x00004004) +#define PS3_AUDIO_AX_AOBP (0x00004008) +#define PS3_AUDIO_AX_IC (0x00004010) +#define PS3_AUDIO_AX_IE (0x00004014) +#define PS3_AUDIO_AX_IS (0x00004018) + +/* + * three wire serial + * n:0..3 + */ +#define PS3_AUDIO_AO_MCTRL (0x00006000) +#define PS3_AUDIO_AO_3WMCTRL (0x00006004) + +#define PS3_AUDIO_AO_3WCTRL(n) (0x00006200 + 0x200 * (n)) + +/* + * S/PDIF + * n:0..1 + * x:0..11 + * y:0..5 + */ +#define PS3_AUDIO_AO_SPD_REGBASE(n) (0x00007200 + 0x200 * (n)) + +#define PS3_AUDIO_AO_SPDCTRL(n) \ + (PS3_AUDIO_AO_SPD_REGBASE(n) + 0x00) +#define PS3_AUDIO_AO_SPDUB(n, x) \ + (PS3_AUDIO_AO_SPD_REGBASE(n) + 0x04 + 0x04 * (x)) +#define PS3_AUDIO_AO_SPDCS(n, y) \ + (PS3_AUDIO_AO_SPD_REGBASE(n) + 0x34 + 0x04 * (y)) + + +/* + PS3_AUDIO_INTR_0 register tells an interrupt handler which audio + DMA channel triggered the interrupt. The interrupt status for a channel + can be cleared by writing a '1' to the corresponding bit. A new interrupt + cannot be generated until the previous interrupt has been cleared. + + Note that the status reported by PS3_AUDIO_INTR_0 is independent of the + value of PS3_AUDIO_INTR_EN_0. + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C| INTR_0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +#define PS3_AUDIO_INTR_0_CHAN(n) (1 << ((n) * 2)) +#define PS3_AUDIO_INTR_0_CHAN9 PS3_AUDIO_INTR_0_CHAN(9) +#define PS3_AUDIO_INTR_0_CHAN8 PS3_AUDIO_INTR_0_CHAN(8) +#define PS3_AUDIO_INTR_0_CHAN7 PS3_AUDIO_INTR_0_CHAN(7) +#define PS3_AUDIO_INTR_0_CHAN6 PS3_AUDIO_INTR_0_CHAN(6) +#define PS3_AUDIO_INTR_0_CHAN5 PS3_AUDIO_INTR_0_CHAN(5) +#define PS3_AUDIO_INTR_0_CHAN4 PS3_AUDIO_INTR_0_CHAN(4) +#define PS3_AUDIO_INTR_0_CHAN3 PS3_AUDIO_INTR_0_CHAN(3) +#define PS3_AUDIO_INTR_0_CHAN2 PS3_AUDIO_INTR_0_CHAN(2) +#define PS3_AUDIO_INTR_0_CHAN1 PS3_AUDIO_INTR_0_CHAN(1) +#define PS3_AUDIO_INTR_0_CHAN0 PS3_AUDIO_INTR_0_CHAN(0) + +/* + The PS3_AUDIO_INTR_EN_0 register specifies which DMA channels can generate + an interrupt to the PU. Each bit of PS3_AUDIO_INTR_EN_0 is ANDed with the + corresponding bit in PS3_AUDIO_INTR_0. The resulting bits are OR'd together + to generate the Audio interrupt. + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C| INTR_EN_0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + + Bit assignments are same as PS3_AUDIO_INTR_0 +*/ + +/* + PS3_AUDIO_CONFIG + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 C|0 0 0 0 0 0 0 0| CONFIG + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ + +/* The CLEAR field cancels all pending transfers, and stops any running DMA + transfers. Any interrupts associated with the canceled transfers + will occur as if the transfer had finished. + Since this bit is designed to recover from DMA related issues + which are caused by unpredictable situations, it is prefered to wait + for normal DMA transfer end without using this bit. +*/ +#define PS3_AUDIO_CONFIG_CLEAR (1 << 8) /* RWIVF */ + +/* + PS3_AUDIO_AX_MCTRL: Audio Port Mute Control Register + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|A|A|A|0 0 0 0 0 0 0|S|S|A|A|A|A| AX_MCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* 3 Wire Audio Serial Output Channel Mutes (0..3) */ +#define PS3_AUDIO_AX_MCTRL_ASOMT(n) (1 << (3 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO3MT (1 << 0) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO2MT (1 << 1) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO1MT (1 << 2) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO0MT (1 << 3) /* RWIVF */ + +/* S/PDIF mutes (0,1)*/ +#define PS3_AUDIO_AX_MCTRL_SPOMT(n) (1 << (5 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_SPO1MT (1 << 4) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_SPO0MT (1 << 5) /* RWIVF */ + +/* All 3 Wire Serial Outputs Mute */ +#define PS3_AUDIO_AX_MCTRL_AASOMT (1 << 13) /* RWIVF */ + +/* All S/PDIF Mute */ +#define PS3_AUDIO_AX_MCTRL_ASPOMT (1 << 14) /* RWIVF */ + +/* All Audio Outputs Mute */ +#define PS3_AUDIO_AX_MCTRL_AAOMT (1 << 15) /* RWIVF */ + +/* + S/PDIF Outputs Buffer Read/Write Pointer Register + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|0|SPO0B|0|SPO1B|0 0 0 0 0 0 0 0|0|SPO0B|0|SPO1B| AX_ISBP + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ +/* + S/PDIF Output Channel Read Buffer Numbers + Buffer number is value of field. + Indicates current read access buffer ID from Audio Data + Transfer controller of S/PDIF Output +*/ + +#define PS3_AUDIO_AX_ISBP_SPOBRN_MASK(n) (0x7 << 4 * (1 - (n))) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO1BRN_MASK (0x7 << 0) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO0BRN_MASK (0x7 << 4) /* R-IUF */ + +/* +S/PDIF Output Channel Buffer Write Numbers +Indicates current write access buffer ID from bus master. +*/ +#define PS3_AUDIO_AX_ISBP_SPOBWN_MASK(n) (0x7 << 4 * (5 - (n))) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO1BWN_MASK (0x7 << 16) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO0BWN_MASK (0x7 << 20) /* R-IUF */ + +/* + 3 Wire Audio Serial Outputs Buffer Read/Write + Pointer Register + Buffer number is value of field + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0|ASO0B|0|ASO1B|0|ASO2B|0|ASO3B|0|ASO0B|0|ASO1B|0|ASO2B|0|ASO3B| AX_AOBP + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +3 Wire Audio Serial Output Channel Buffer Read Numbers +Indicates current read access buffer Id from Audio Data Transfer +Controller of 3 Wire Audio Serial Output Channels +*/ +#define PS3_AUDIO_AX_AOBP_ASOBRN_MASK(n) (0x7 << 4 * (3 - (n))) /* R-IUF */ + +#define PS3_AUDIO_AX_AOBP_ASO3BRN_MASK (0x7 << 0) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO2BRN_MASK (0x7 << 4) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO1BRN_MASK (0x7 << 8) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO0BRN_MASK (0x7 << 12) /* R-IUF */ + +/* +3 Wire Audio Serial Output Channel Buffer Write Numbers +Indicates current write access buffer ID from bus master. +*/ +#define PS3_AUDIO_AX_AOBP_ASOBWN_MASK(n) (0x7 << 4 * (7 - (n))) /* R-IUF */ + +#define PS3_AUDIO_AX_AOBP_ASO3BWN_MASK (0x7 << 16) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO2BWN_MASK (0x7 << 20) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO1BWN_MASK (0x7 << 24) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO0BWN_MASK (0x7 << 28) /* R-IUF */ + + + +/* +Audio Port Interrupt Condition Register +For the fields in this register, the following values apply: +0 = Interrupt is generated every interrupt event. +1 = Interrupt is generated every 2 interrupt events. +2 = Interrupt is generated every 4 interrupt events. +3 = Reserved + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|0 0|SPO|0 0|SPO|0 0|AAS|0 0 0 0 0 0 0 0 0 0 0 0| AX_IC + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/* +All 3-Wire Audio Serial Outputs Interrupt Mode +Configures the Interrupt and Signal Notification +condition of all 3-wire Audio Serial Outputs. +*/ +#define PS3_AUDIO_AX_IC_AASOIMD_MASK (0x3 << 12) /* RWIVF */ +#define PS3_AUDIO_AX_IC_AASOIMD_EVERY1 (0x0 << 12) /* RWI-V */ +#define PS3_AUDIO_AX_IC_AASOIMD_EVERY2 (0x1 << 12) /* RW--V */ +#define PS3_AUDIO_AX_IC_AASOIMD_EVERY4 (0x2 << 12) /* RW--V */ + +/* +S/PDIF Output Channel Interrupt Modes +Configures the Interrupt and signal Notification +conditions of S/PDIF output channels. +*/ +#define PS3_AUDIO_AX_IC_SPO1IMD_MASK (0x3 << 16) /* RWIVF */ +#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY1 (0x0 << 16) /* RWI-V */ +#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY2 (0x1 << 16) /* RW--V */ +#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY4 (0x2 << 16) /* RW--V */ + +#define PS3_AUDIO_AX_IC_SPO0IMD_MASK (0x3 << 20) /* RWIVF */ +#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY1 (0x0 << 20) /* RWI-V */ +#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY2 (0x1 << 20) /* RW--V */ +#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY4 (0x2 << 20) /* RW--V */ + +/* +Audio Port interrupt Enable Register +Configures whether to enable or disable each Interrupt Generation. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|S|S|0 0|A|A|A|A|0 0 0 0|S|S|0 0|S|S|0 0|A|A|A|A| AX_IE + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ + +/* +3 Wire Audio Serial Output Channel Buffer Underflow +Interrupt Enables +Select enable/disable of Buffer Underflow Interrupts for +3-Wire Audio Serial Output Channels +DISABLED=Interrupt generation disabled. +*/ +#define PS3_AUDIO_AX_IE_ASOBUIE(n) (1 << (3 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO3BUIE (1 << 0) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO2BUIE (1 << 1) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO1BUIE (1 << 2) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO0BUIE (1 << 3) /* RWIVF */ + +/* S/PDIF Output Channel Buffer Underflow Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_SPOBUIE(n) (1 << (7 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO1BUIE (1 << 6) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO0BUIE (1 << 7) /* RWIVF */ + +/* S/PDIF Output Channel One Block Transfer Completion Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_SPOBTCIE(n) (1 << (11 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO1BTCIE (1 << 10) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO0BTCIE (1 << 11) /* RWIVF */ + +/* 3-Wire Audio Serial Output Channel Buffer Empty Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_ASOBEIE(n) (1 << (19 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO3BEIE (1 << 16) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO2BEIE (1 << 17) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO1BEIE (1 << 18) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO0BEIE (1 << 19) /* RWIVF */ + +/* S/PDIF Output Channel Buffer Empty Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_SPOBEIE(n) (1 << (23 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO1BEIE (1 << 22) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO0BEIE (1 << 23) /* RWIVF */ + +/* +Audio Port Interrupt Status Register +Indicates Interrupt status, which interrupt has occured, and can clear +each interrupt in this register. +Writing 1b to a field containing 1b clears field and de-asserts interrupt. +Writing 0b to a field has no effect. +Field vaules are the following: +0 - Interrupt hasn't occured. +1 - Interrupt has occured. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|S|S|0 0|A|A|A|A|0 0 0 0|S|S|0 0|S|S|0 0|A|A|A|A| AX_IS + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + + Bit assignment are same as AX_IE +*/ + +/* +Audio Output Master Control Register +Configures Master Clock and other master Audio Output Settings + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0|SCKSE|0|SCKSE| MR0 | MR1 |MCL|MCL|0 0 0 0|0 0 0 0 0 0 0 0| AO_MCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +MCLK Output Control +Controls mclko[1] output. +0 - Disable output (fixed at High) +1 - Output clock produced by clock selected +with scksel1 by mr1 +2 - Reserved +3 - Reserved +*/ + +#define PS3_AUDIO_AO_MCTRL_MCLKC1_MASK (0x3 << 12) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_DISABLED (0x0 << 12) /* RWI-V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_ENABLED (0x1 << 12) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_RESVD2 (0x2 << 12) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_RESVD3 (0x3 << 12) /* RW--V */ + +/* +MCLK Output Control +Controls mclko[0] output. +0 - Disable output (fixed at High) +1 - Output clock produced by clock selected +with SCKSEL0 by MR0 +2 - Reserved +3 - Reserved +*/ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_MASK (0x3 << 14) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_DISABLED (0x0 << 14) /* RWI-V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_ENABLED (0x1 << 14) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_RESVD2 (0x2 << 14) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_RESVD3 (0x3 << 14) /* RW--V */ +/* +Master Clock Rate 1 +Sets the divide ration of Master Clock1 (clock output from +mclko[1] for the input clock selected by scksel1. +*/ +#define PS3_AUDIO_AO_MCTRL_MR1_MASK (0xf << 16) +#define PS3_AUDIO_AO_MCTRL_MR1_DEFAULT (0x0 << 16) /* RWI-V */ +/* +Master Clock Rate 0 +Sets the divide ratio of Master Clock0 (clock output from +mclko[0] for the input clock selected by scksel0). +*/ +#define PS3_AUDIO_AO_MCTRL_MR0_MASK (0xf << 20) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_MR0_DEFAULT (0x0 << 20) /* RWI-V */ +/* +System Clock Select 0/1 +Selects the system clock to be used as Master Clock 0/1 +Input the system clock that is appropriate for the sampling +rate. +*/ +#define PS3_AUDIO_AO_MCTRL_SCKSEL1_MASK (0x7 << 24) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_SCKSEL1_DEFAULT (0x2 << 24) /* RWI-V */ + +#define PS3_AUDIO_AO_MCTRL_SCKSEL0_MASK (0x7 << 28) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_SCKSEL0_DEFAULT (0x2 << 28) /* RWI-V */ + + +/* +3-Wire Audio Output Master Control Register +Configures clock, 3-Wire Audio Serial Output Enable, and +other 3-Wire Audio Serial Output Master Settings + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |A|A|A|A|0 0 0|A| ASOSR |0 0 0 0|A|A|A|A|A|A|0|1|0 0 0 0 0 0 0 0| AO_3WMCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + + +/* +LRCKO Polarity +0 - Reserved +1 - default +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOPLRCK (1 << 8) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOPLRCK_DEFAULT (1 << 8) /* RW--V */ + +/* LRCK Output Disable */ + +#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD (1 << 10) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_ENABLED (0 << 10) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_DISABLED (1 << 10) /* RWI-V */ + +/* Bit Clock Output Disable */ + +#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD (1 << 11) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_ENABLED (0 << 11) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_DISABLED (1 << 11) /* RWI-V */ + +/* +3-Wire Audio Serial Output Channel 0-3 Operational +Status. Each bit becomes 1 after each 3-Wire Audio +Serial Output Channel N is in action by setting 1 to +asoen. +Each bit becomes 0 after each 3-Wire Audio Serial Output +Channel N is out of action by setting 0 to asoen. +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN(n) (1 << (15 - (n))) /* R-IVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(n) (0 << (15 - (n))) /* R-I-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(n) (1 << (15 - (n))) /* R---V */ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN0 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(0) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN0_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(0) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN0_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(0) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN1 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(1) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN1_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(1) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN1_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(1) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN2 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(2) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN2_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(2) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN2_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(2) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN3 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(3) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN3_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(3) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN3_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(3) + +/* +Sampling Rate +Specifies the divide ratio of the bit clock (clock output +from bclko) used by the 3-wire Audio Output Clock, whcih +is applied to the master clock selected by mcksel. +Data output is synchronized with this clock. +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_MASK (0xf << 20) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV2 (0x1 << 20) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV4 (0x2 << 20) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV8 (0x4 << 20) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV12 (0x6 << 20) /* RW--V */ + +/* +Master Clock Select +0 - Master Clock 0 +1 - Master Clock 1 +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL (1 << 24) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL_CLK0 (0 << 24) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL_CLK1 (1 << 24) /* RW--V */ + +/* +Enables and disables 4ch 3-Wire Audio Serial Output +operation. Each Bit from 0 to 3 corresponds to an +output channel, which means that each output channel +can be enabled or disabled individually. When +multiple channels are enabled at the same time, output +operations are performed in synchronization. +Bit 0 - Output Channel 0 (SDOUT[0]) +Bit 1 - Output Channel 1 (SDOUT[1]) +Bit 2 - Output Channel 2 (SDOUT[2]) +Bit 3 - Output Channel 3 (SDOUT[3]) +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN(n) (1 << (31 - (n))) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(n) (0 << (31 - (n))) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(n) (1 << (31 - (n))) /* RW--V */ + +#define PS3_AUDIO_AO_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(0) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(0) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(0) /* RW--V */ +#define PS3_AUDIO_A1_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(1) /* RWIVF */ +#define PS3_AUDIO_A1_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(1) /* RWI-V */ +#define PS3_AUDIO_A1_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(1) /* RW--V */ +#define PS3_AUDIO_A2_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(2) /* RWIVF */ +#define PS3_AUDIO_A2_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(2) /* RWI-V */ +#define PS3_AUDIO_A2_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(2) /* RW--V */ +#define PS3_AUDIO_A3_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(3) /* RWIVF */ +#define PS3_AUDIO_A3_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(3) /* RWI-V */ +#define PS3_AUDIO_A3_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(3) /* RW--V */ + +/* +3-Wire Audio Serial output Channel 0-3 Control Register +Configures settings for 3-Wire Serial Audio Output Channel 0-3 + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|A|0 0 0 0|A|0|ASO|0 0 0|0|0|0|0|0| AO_3WCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ +/* +Data Bit Mode +Specifies the number of data bits +0 - 16 bits +1 - reserved +2 - 20 bits +3 - 24 bits +*/ +#define PS3_AUDIO_AO_3WCTRL_ASODB_MASK (0x3 << 8) /* RWIVF */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_16BIT (0x0 << 8) /* RWI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_RESVD (0x1 << 8) /* RWI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_20BIT (0x2 << 8) /* RW--V */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_24BIT (0x3 << 8) /* RW--V */ +/* +Data Format Mode +Specifies the data format where (LSB side or MSB) the data(in 20 bit +or 24 bit resolution mode) is put in a 32 bit field. +0 - Data put on LSB side +1 - Data put on MSB side +*/ +#define PS3_AUDIO_AO_3WCTRL_ASODF (1 << 11) /* RWIVF */ +#define PS3_AUDIO_AO_3WCTRL_ASODF_LSB (0 << 11) /* RWI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASODF_MSB (1 << 11) /* RW--V */ +/* +Buffer Reset +Performs buffer reset. Writing 1 to this bit initializes the +corresponding 3-Wire Audio Output buffers(both L and R). +*/ +#define PS3_AUDIO_AO_3WCTRL_ASOBRST (1 << 16) /* CWIVF */ +#define PS3_AUDIO_AO_3WCTRL_ASOBRST_IDLE (0 << 16) /* -WI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASOBRST_RESET (1 << 16) /* -W--T */ + +/* +S/PDIF Audio Output Channel 0/1 Control Register +Configures settings for S/PDIF Audio Output Channel 0/1. + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |S|0 0 0|S|0 0|S| SPOSR |0 0|SPO|0 0 0 0|S|0|SPO|0 0 0 0 0 0 0|S| AO_SPDCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/* +Buffer reset. Writing 1 to this bit initializes the +corresponding S/PDIF output buffer pointer. +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOBRST (1 << 0) /* CWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOBRST_IDLE (0 << 0) /* -WI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOBRST_RESET (1 << 0) /* -W--T */ + +/* +Data Bit Mode +Specifies number of data bits +0 - 16 bits +1 - Reserved +2 - 20 bits +3 - 24 bits +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_MASK (0x3 << 8) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_16BIT (0x0 << 8) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_RESVD (0x1 << 8) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_20BIT (0x2 << 8) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_24BIT (0x3 << 8) /* RW--V */ +/* +Data format Mode +Specifies the data format, where (LSB side or MSB) +the data(in 20 or 24 bit resolution) is put in the +32 bit field. +0 - LSB Side +1 - MSB Side +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPODF (1 << 11) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPODF_LSB (0 << 11) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODF_MSB (1 << 11) /* RW--V */ +/* +Source Select +Specifies the source of the S/PDIF output. When 0, output +operation is controlled by 3wen[0] of AO_3WMCTRL register. +The SR must have the same setting as the a0_3wmctrl reg. +0 - 3-Wire Audio OUT Ch0 Buffer +1 - S/PDIF buffer +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOSS_MASK (0x3 << 16) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSS_3WEN (0x0 << 16) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSS_SPDIF (0x1 << 16) /* RW--V */ +/* +Sampling Rate +Specifies the divide ratio of the bit clock (clock output +from bclko) used by the S/PDIF Output Clock, which +is applied to the master clock selected by mcksel. +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR (0xf << 20) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV2 (0x1 << 20) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV4 (0x2 << 20) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV8 (0x4 << 20) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV12 (0x6 << 20) /* RW--V */ +/* +Master Clock Select +0 - Master Clock 0 +1 - Master Clock 1 +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL (1 << 24) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL_CLK0 (0 << 24) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL_CLK1 (1 << 24) /* RW--V */ + +/* +S/PDIF Output Channel Operational Status +This bit becomes 1 after S/PDIF Output Channel is in +action by setting 1 to spoen. This bit becomes 0 +after S/PDIF Output Channel is out of action by setting +0 to spoen. +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPORUN (1 << 27) /* R-IVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPORUN_STOPPED (0 << 27) /* R-I-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPORUN_RUNNING (1 << 27) /* R---V */ + +/* +S/PDIF Audio Output Channel Output Enable +Enables and disables output operation. This bit is used +only when sposs = 1 +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOEN (1 << 31) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOEN_DISABLED (0 << 31) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOEN_ENABLED (1 << 31) /* RW--V */ + +/* +S/PDIF Audio Output Channel Channel Status +Setting Registers. +Configures channel status bit settings for each block +(192 bits). +Output is performed from the MSB(AO_SPDCS0 register bit 31). +The same value is added for subframes within the same frame. + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | SPOCS | AO_SPDCS + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +S/PDIF Audio Output Channel User Bit Setting +Configures user bit settings for each block (384 bits). +Output is performed from the MSB(ao_spdub0 register bit 31). + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | SPOUB | AO_SPDUB + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/***************************************************************************** + * + * DMAC register + * + *****************************************************************************/ +/* +The PS3_AUDIO_KICK register is used to initiate a DMA transfer and monitor +its status + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0|STATU|0 0 0| EVENT |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|R| KICK + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/* +The REQUEST field is written to ACTIVE to initiate a DMA request when EVENT +occurs. +It will return to the DONE state when the request is completed. +The registers for a DMA channel should only be written if REQUEST is IDLE. +*/ + +#define PS3_AUDIO_KICK_REQUEST (1 << 0) /* RWIVF */ +#define PS3_AUDIO_KICK_REQUEST_IDLE (0 << 0) /* RWI-V */ +#define PS3_AUDIO_KICK_REQUEST_ACTIVE (1 << 0) /* -W--T */ + +/* + *The EVENT field is used to set the event in which + *the DMA request becomes active. + */ +#define PS3_AUDIO_KICK_EVENT_MASK (0x1f << 16) /* RWIVF */ +#define PS3_AUDIO_KICK_EVENT_ALWAYS (0x00 << 16) /* RWI-V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT0_EMPTY (0x01 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT0_UNDERFLOW (0x02 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT1_EMPTY (0x03 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT1_UNDERFLOW (0x04 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT2_EMPTY (0x05 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT2_UNDERFLOW (0x06 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT3_EMPTY (0x07 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT3_UNDERFLOW (0x08 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF0_BLOCKTRANSFERCOMPLETE \ + (0x09 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF0_UNDERFLOW (0x0A << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF0_EMPTY (0x0B << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF1_BLOCKTRANSFERCOMPLETE \ + (0x0C << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF1_UNDERFLOW (0x0D << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF1_EMPTY (0x0E << 16) /* RW--V */ + +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA(n) \ + ((0x13 + (n)) << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA0 (0x13 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA1 (0x14 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA2 (0x15 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA3 (0x16 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA4 (0x17 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA5 (0x18 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA6 (0x19 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA7 (0x1A << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA8 (0x1B << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA9 (0x1C << 16) /* RW--V */ + +/* +The STATUS field can be used to monitor the progress of a DMA request. +DONE indicates the previous request has completed. +EVENT indicates that the DMA engine is waiting for the EVENT to occur. +PENDING indicates that the DMA engine has not started processing this +request, but the EVENT has occured. +DMA indicates that the data transfer is in progress. +NOTIFY indicates that the notifier signalling end of transfer is being written. +CLEAR indicated that the previous transfer was cleared. +ERROR indicates the previous transfer requested an unsupported +source/destination combination. +*/ + +#define PS3_AUDIO_KICK_STATUS_MASK (0x7 << 24) /* R-IVF */ +#define PS3_AUDIO_KICK_STATUS_DONE (0x0 << 24) /* R-I-V */ +#define PS3_AUDIO_KICK_STATUS_EVENT (0x1 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_PENDING (0x2 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_DMA (0x3 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_NOTIFY (0x4 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_CLEAR (0x5 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_ERROR (0x6 << 24) /* R---V */ + +/* +The PS3_AUDIO_SOURCE register specifies the source address for transfers. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | START |0 0 0 0 0|TAR| SOURCE + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +The Audio DMA engine uses 128-byte transfers, thus the address must be aligned +to a 128 byte boundary. The low seven bits are assumed to be 0. +*/ + +#define PS3_AUDIO_SOURCE_START_MASK (0x01FFFFFF << 7) /* RWIUF */ + +/* +The TARGET field specifies the memory space containing the source address. +*/ + +#define PS3_AUDIO_SOURCE_TARGET_MASK (3 << 0) /* RWIVF */ +#define PS3_AUDIO_SOURCE_TARGET_SYSTEM_MEMORY (2 << 0) /* RW--V */ + +/* +The PS3_AUDIO_DEST register specifies the destination address for transfers. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | START |0 0 0 0 0|TAR| DEST + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +The Audio DMA engine uses 128-byte transfers, thus the address must be aligned +to a 128 byte boundary. The low seven bits are assumed to be 0. +*/ + +#define PS3_AUDIO_DEST_START_MASK (0x01FFFFFF << 7) /* RWIUF */ + +/* +The TARGET field specifies the memory space containing the destination address +AUDIOFIFO = Audio WriteData FIFO, +*/ + +#define PS3_AUDIO_DEST_TARGET_MASK (3 << 0) /* RWIVF */ +#define PS3_AUDIO_DEST_TARGET_AUDIOFIFO (1 << 0) /* RW--V */ + +/* +PS3_AUDIO_DMASIZE specifies the number of 128-byte blocks + 1 to transfer. +So a value of 0 means 128-bytes will get transfered. + + + 31 24 23 16 15 8 7 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| BLOCKS | DMASIZE + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + + +#define PS3_AUDIO_DMASIZE_BLOCKS_MASK (0x7f << 0) /* RWIUF */ + +/* + * source/destination address for internal fifos + */ +#define PS3_AUDIO_AO_3W_LDATA(n) (0x1000 + (0x100 * (n))) +#define PS3_AUDIO_AO_3W_RDATA(n) (0x1080 + (0x100 * (n))) + +#define PS3_AUDIO_AO_SPD_DATA(n) (0x2000 + (0x400 * (n))) + + +/* + * field attiribute + * + * Read + * ' ' = Other Information + * '-' = Field is part of a write-only register + * 'C' = Value read is always the same, constant value line follows (C) + * 'R' = Value is read + * + * Write + * ' ' = Other Information + * '-' = Must not be written (D), value ignored when written (R,A,F) + * 'W' = Can be written + * + * Internal State + * ' ' = Other Information + * '-' = No internal state + * 'X' = Internal state, initial value is unknown + * 'I' = Internal state, initial value is known and follows (I) + * + * Declaration/Size + * ' ' = Other Information + * '-' = Does Not Apply + * 'V' = Type is void + * 'U' = Type is unsigned integer + * 'S' = Type is signed integer + * 'F' = Type is IEEE floating point + * '1' = Byte size (008) + * '2' = Short size (016) + * '3' = Three byte size (024) + * '4' = Word size (032) + * '8' = Double size (064) + * + * Define Indicator + * ' ' = Other Information + * 'D' = Device + * 'M' = Memory + * 'R' = Register + * 'A' = Array of Registers + * 'F' = Field + * 'V' = Value + * 'T' = Task + */ + diff --git a/sound/sh/Kconfig b/sound/sh/Kconfig new file mode 100644 index 000000000000..b7e08ef22a94 --- /dev/null +++ b/sound/sh/Kconfig @@ -0,0 +1,14 @@ +# ALSA SH drivers + +menu "SUPERH devices" + depends on SND!=n && SUPERH + +config SND_AICA + tristate "Dreamcast Yamaha AICA sound" + depends on SH_DREAMCAST && SND + select SND_PCM + help + ALSA Sound driver for the SEGA Dreamcast console. + +endmenu + diff --git a/sound/sh/Makefile b/sound/sh/Makefile new file mode 100644 index 000000000000..8fdcb6e26f00 --- /dev/null +++ b/sound/sh/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for ALSA +# + +snd-aica-objs := aica.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_AICA) += snd-aica.o diff --git a/sound/sh/aica.c b/sound/sh/aica.c new file mode 100644 index 000000000000..739786529ca5 --- /dev/null +++ b/sound/sh/aica.c @@ -0,0 +1,665 @@ +/* +* This code is licenced under +* the General Public Licence +* version 2 +* +* Copyright Adrian McMenamin 2005, 2006, 2007 +* +* Requires firmware (BSD licenced) available from: +* http://linuxdc.cvs.sourceforge.net/linuxdc/linux-sh-dc/sound/oss/aica/firmware/ +* or the maintainer +* +* 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 +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "aica.h" + +MODULE_AUTHOR("Adrian McMenamin "); +MODULE_DESCRIPTION("Dreamcast AICA sound (pcm) driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Yamaha/SEGA, AICA}}"); + +/* module parameters */ +#define CARD_NAME "AICA" +static int index = -1; +static char *id; +static int enable = 1; +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); +module_param(enable, bool, 0644); +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); + +/* Use workqueue */ +static struct workqueue_struct *aica_queue; + +/* Simple platform device */ +static struct platform_device *pd; +static struct resource aica_memory_space[2] = { + { + .name = "AICA ARM CONTROL", + .start = ARM_RESET_REGISTER, + .flags = IORESOURCE_MEM, + .end = ARM_RESET_REGISTER + 3, + }, + { + .name = "AICA Sound RAM", + .start = SPU_MEMORY_BASE, + .flags = IORESOURCE_MEM, + .end = SPU_MEMORY_BASE + 0x200000 - 1, + }, +}; + +/* SPU specific functions */ +/* spu_write_wait - wait for G2-SH FIFO to clear */ +static void spu_write_wait(void) +{ + int time_count; + time_count = 0; + while (1) { + if (!(readl(G2_FIFO) & 0x11)) + break; + /* To ensure hardware failure doesn't wedge kernel */ + time_count++; + if (time_count > 0x10000) { + snd_printk + ("WARNING: G2 FIFO appears to be blocked.\n"); + break; + } + } +} + +/* spu_memset - write to memory in SPU address space */ +static void spu_memset(u32 toi, u32 what, int length) +{ + int i; + snd_assert(length % 4 == 0, return); + for (i = 0; i < length; i++) { + if (!(i % 8)) + spu_write_wait(); + writel(what, toi + SPU_MEMORY_BASE); + toi++; + } +} + +/* spu_memload - write to SPU address space */ +static void spu_memload(u32 toi, void *from, int length) +{ + u32 *froml = from; + u32 __iomem *to = (u32 __iomem *) (SPU_MEMORY_BASE + toi); + int i; + u32 val; + length = DIV_ROUND_UP(length, 4); + spu_write_wait(); + for (i = 0; i < length; i++) { + if (!(i % 8)) + spu_write_wait(); + val = *froml; + writel(val, to); + froml++; + to++; + } +} + +/* spu_disable - set spu registers to stop sound output */ +static void spu_disable(void) +{ + int i; + u32 regval; + spu_write_wait(); + regval = readl(ARM_RESET_REGISTER); + regval |= 1; + spu_write_wait(); + writel(regval, ARM_RESET_REGISTER); + for (i = 0; i < 64; i++) { + spu_write_wait(); + regval = readl(SPU_REGISTER_BASE + (i * 0x80)); + regval = (regval & ~0x4000) | 0x8000; + spu_write_wait(); + writel(regval, SPU_REGISTER_BASE + (i * 0x80)); + } +} + +/* spu_enable - set spu registers to enable sound output */ +static void spu_enable(void) +{ + u32 regval = readl(ARM_RESET_REGISTER); + regval &= ~1; + spu_write_wait(); + writel(regval, ARM_RESET_REGISTER); +} + +/* + * Halt the sound processor, clear the memory, + * load some default ARM7 code, and then restart ARM7 +*/ +static void spu_reset(void) +{ + spu_disable(); + spu_memset(0, 0, 0x200000 / 4); + /* Put ARM7 in endless loop */ + ctrl_outl(0xea000002, SPU_MEMORY_BASE); + spu_enable(); +} + +/* aica_chn_start - write to spu to start playback */ +static void aica_chn_start(void) +{ + spu_write_wait(); + writel(AICA_CMD_KICK | AICA_CMD_START, (u32 *) AICA_CONTROL_POINT); +} + +/* aica_chn_halt - write to spu to halt playback */ +static void aica_chn_halt(void) +{ + spu_write_wait(); + writel(AICA_CMD_KICK | AICA_CMD_STOP, (u32 *) AICA_CONTROL_POINT); +} + +/* ALSA code below */ +static struct snd_pcm_hardware snd_pcm_aica_playback_hw = { + .info = (SNDRV_PCM_INFO_NONINTERLEAVED), + .formats = + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_IMA_ADPCM), + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = AICA_BUFFER_SIZE, + .period_bytes_min = AICA_PERIOD_SIZE, + .period_bytes_max = AICA_PERIOD_SIZE, + .periods_min = AICA_PERIOD_NUMBER, + .periods_max = AICA_PERIOD_NUMBER, +}; + +static int aica_dma_transfer(int channels, int buffer_size, + struct snd_pcm_substream *substream) +{ + int q, err, period_offset; + struct snd_card_aica *dreamcastcard; + struct snd_pcm_runtime *runtime; + err = 0; + dreamcastcard = substream->pcm->private_data; + period_offset = dreamcastcard->clicks; + period_offset %= (AICA_PERIOD_NUMBER / channels); + runtime = substream->runtime; + for (q = 0; q < channels; q++) { + err = dma_xfer(AICA_DMA_CHANNEL, + (unsigned long) (runtime->dma_area + + (AICA_BUFFER_SIZE * q) / + channels + + AICA_PERIOD_SIZE * + period_offset), + AICA_CHANNEL0_OFFSET + q * CHANNEL_OFFSET + + AICA_PERIOD_SIZE * period_offset, + buffer_size / channels, AICA_DMA_MODE); + if (unlikely(err < 0)) + break; + dma_wait_for_completion(AICA_DMA_CHANNEL); + } + return err; +} + +static void startup_aica(struct snd_card_aica *dreamcastcard) +{ + spu_memload(AICA_CHANNEL0_CONTROL_OFFSET, + dreamcastcard->channel, sizeof(struct aica_channel)); + aica_chn_start(); +} + +static void run_spu_dma(struct work_struct *work) +{ + int buffer_size; + struct snd_pcm_runtime *runtime; + struct snd_card_aica *dreamcastcard; + dreamcastcard = + container_of(work, struct snd_card_aica, spu_dma_work); + runtime = dreamcastcard->substream->runtime; + if (unlikely(dreamcastcard->dma_check == 0)) { + buffer_size = + frames_to_bytes(runtime, runtime->buffer_size); + if (runtime->channels > 1) + dreamcastcard->channel->flags |= 0x01; + aica_dma_transfer(runtime->channels, buffer_size, + dreamcastcard->substream); + startup_aica(dreamcastcard); + dreamcastcard->clicks = + buffer_size / (AICA_PERIOD_SIZE * runtime->channels); + return; + } else { + aica_dma_transfer(runtime->channels, + AICA_PERIOD_SIZE * runtime->channels, + dreamcastcard->substream); + snd_pcm_period_elapsed(dreamcastcard->substream); + dreamcastcard->clicks++; + if (unlikely(dreamcastcard->clicks >= AICA_PERIOD_NUMBER)) + dreamcastcard->clicks %= AICA_PERIOD_NUMBER; + mod_timer(&dreamcastcard->timer, jiffies + 1); + } +} + +static void aica_period_elapsed(unsigned long timer_var) +{ + /*timer function - so cannot sleep */ + int play_period; + struct snd_pcm_runtime *runtime; + struct snd_pcm_substream *substream; + struct snd_card_aica *dreamcastcard; + substream = (struct snd_pcm_substream *) timer_var; + runtime = substream->runtime; + dreamcastcard = substream->pcm->private_data; + /* Have we played out an additional period? */ + play_period = + frames_to_bytes(runtime, + readl + (AICA_CONTROL_CHANNEL_SAMPLE_NUMBER)) / + AICA_PERIOD_SIZE; + if (play_period == dreamcastcard->current_period) { + /* reschedule the timer */ + mod_timer(&(dreamcastcard->timer), jiffies + 1); + return; + } + if (runtime->channels > 1) + dreamcastcard->current_period = play_period; + if (unlikely(dreamcastcard->dma_check == 0)) + dreamcastcard->dma_check = 1; + queue_work(aica_queue, &(dreamcastcard->spu_dma_work)); +} + +static void spu_begin_dma(struct snd_pcm_substream *substream) +{ + struct snd_card_aica *dreamcastcard; + struct snd_pcm_runtime *runtime; + runtime = substream->runtime; + dreamcastcard = substream->pcm->private_data; + /*get the queue to do the work */ + queue_work(aica_queue, &(dreamcastcard->spu_dma_work)); + /* Timer may already be running */ + if (unlikely(dreamcastcard->timer.data)) { + mod_timer(&dreamcastcard->timer, jiffies + 4); + return; + } + init_timer(&(dreamcastcard->timer)); + dreamcastcard->timer.data = (unsigned long) substream; + dreamcastcard->timer.function = aica_period_elapsed; + dreamcastcard->timer.expires = jiffies + 4; + add_timer(&(dreamcastcard->timer)); +} + +static int snd_aicapcm_pcm_open(struct snd_pcm_substream + *substream) +{ + struct snd_pcm_runtime *runtime; + struct aica_channel *channel; + struct snd_card_aica *dreamcastcard; + if (!enable) + return -ENOENT; + dreamcastcard = substream->pcm->private_data; + channel = kmalloc(sizeof(struct aica_channel), GFP_KERNEL); + if (!channel) + return -ENOMEM; + /* set defaults for channel */ + channel->sfmt = SM_8BIT; + channel->cmd = AICA_CMD_START; + channel->vol = dreamcastcard->master_volume; + channel->pan = 0x80; + channel->pos = 0; + channel->flags = 0; /* default to mono */ + dreamcastcard->channel = channel; + runtime = substream->runtime; + runtime->hw = snd_pcm_aica_playback_hw; + spu_enable(); + dreamcastcard->clicks = 0; + dreamcastcard->current_period = 0; + dreamcastcard->dma_check = 0; + return 0; +} + +static int snd_aicapcm_pcm_close(struct snd_pcm_substream + *substream) +{ + struct snd_card_aica *dreamcastcard = substream->pcm->private_data; + flush_workqueue(aica_queue); + if (dreamcastcard->timer.data) + del_timer(&dreamcastcard->timer); + kfree(dreamcastcard->channel); + spu_disable(); + return 0; +} + +static int snd_aicapcm_pcm_hw_free(struct snd_pcm_substream + *substream) +{ + /* Free the DMA buffer */ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_aicapcm_pcm_hw_params(struct snd_pcm_substream + *substream, struct snd_pcm_hw_params + *hw_params) +{ + /* Allocate a DMA buffer using ALSA built-ins */ + return + snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_aicapcm_pcm_prepare(struct snd_pcm_substream + *substream) +{ + struct snd_card_aica *dreamcastcard = substream->pcm->private_data; + if ((substream->runtime)->format == SNDRV_PCM_FORMAT_S16_LE) + dreamcastcard->channel->sfmt = SM_16BIT; + dreamcastcard->channel->freq = substream->runtime->rate; + dreamcastcard->substream = substream; + return 0; +} + +static int snd_aicapcm_pcm_trigger(struct snd_pcm_substream + *substream, int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + spu_begin_dma(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + aica_chn_halt(); + break; + default: + return -EINVAL; + } + return 0; +} + +static unsigned long snd_aicapcm_pcm_pointer(struct snd_pcm_substream + *substream) +{ + return readl(AICA_CONTROL_CHANNEL_SAMPLE_NUMBER); +} + +static struct snd_pcm_ops snd_aicapcm_playback_ops = { + .open = snd_aicapcm_pcm_open, + .close = snd_aicapcm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_aicapcm_pcm_hw_params, + .hw_free = snd_aicapcm_pcm_hw_free, + .prepare = snd_aicapcm_pcm_prepare, + .trigger = snd_aicapcm_pcm_trigger, + .pointer = snd_aicapcm_pcm_pointer, +}; + +/* TO DO: set up to handle more than one pcm instance */ +static int __init snd_aicapcmchip(struct snd_card_aica + *dreamcastcard, int pcm_index) +{ + struct snd_pcm *pcm; + int err; + /* AICA has no capture ability */ + err = + snd_pcm_new(dreamcastcard->card, "AICA PCM", pcm_index, 1, 0, + &pcm); + if (unlikely(err < 0)) + return err; + pcm->private_data = dreamcastcard; + strcpy(pcm->name, "AICA PCM"); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_aicapcm_playback_ops); + /* Allocate the DMA buffers */ + err = + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), + AICA_BUFFER_SIZE, + AICA_BUFFER_SIZE); + return err; +} + +/* Mixer controls */ +static int aica_pcmswitch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int aica_pcmswitch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 1; /* TO DO: Fix me */ + return 0; +} + +static int aica_pcmswitch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (ucontrol->value.integer.value[0] == 1) + return 0; /* TO DO: Fix me */ + else + aica_chn_halt(); + return 0; +} + +static int aica_pcmvolume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xFF; + return 0; +} + +static int aica_pcmvolume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_card_aica *dreamcastcard; + dreamcastcard = kcontrol->private_data; + if (unlikely(!dreamcastcard->channel)) + return -ETXTBSY; /* we've not yet been set up */ + ucontrol->value.integer.value[0] = dreamcastcard->channel->vol; + return 0; +} + +static int aica_pcmvolume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_card_aica *dreamcastcard; + dreamcastcard = kcontrol->private_data; + if (unlikely(!dreamcastcard->channel)) + return -ETXTBSY; + if (unlikely(dreamcastcard->channel->vol == + ucontrol->value.integer.value[0])) + return 0; + dreamcastcard->channel->vol = ucontrol->value.integer.value[0]; + dreamcastcard->master_volume = ucontrol->value.integer.value[0]; + spu_memload(AICA_CHANNEL0_CONTROL_OFFSET, + dreamcastcard->channel, sizeof(struct aica_channel)); + return 1; +} + +static struct snd_kcontrol_new snd_aica_pcmswitch_control __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .index = 0, + .info = aica_pcmswitch_info, + .get = aica_pcmswitch_get, + .put = aica_pcmswitch_put +}; + +static struct snd_kcontrol_new snd_aica_pcmvolume_control __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .index = 0, + .info = aica_pcmvolume_info, + .get = aica_pcmvolume_get, + .put = aica_pcmvolume_put +}; + +static int load_aica_firmware(void) +{ + int err; + const struct firmware *fw_entry; + spu_reset(); + err = request_firmware(&fw_entry, "aica_firmware.bin", &pd->dev); + if (unlikely(err)) + return err; + /* write firware into memory */ + spu_disable(); + spu_memload(0, fw_entry->data, fw_entry->size); + spu_enable(); + release_firmware(fw_entry); + return err; +} + +static int __devinit add_aicamixer_controls(struct snd_card_aica + *dreamcastcard) +{ + int err; + err = snd_ctl_add + (dreamcastcard->card, + snd_ctl_new1(&snd_aica_pcmvolume_control, dreamcastcard)); + if (unlikely(err < 0)) + return err; + err = snd_ctl_add + (dreamcastcard->card, + snd_ctl_new1(&snd_aica_pcmswitch_control, dreamcastcard)); + if (unlikely(err < 0)) + return err; + return 0; +} + +static int snd_aica_remove(struct platform_device *devptr) +{ + struct snd_card_aica *dreamcastcard; + dreamcastcard = platform_get_drvdata(devptr); + if (unlikely(!dreamcastcard)) + return -ENODEV; + snd_card_free(dreamcastcard->card); + kfree(dreamcastcard); + platform_set_drvdata(devptr, NULL); + return 0; +} + +static int __init snd_aica_probe(struct platform_device *devptr) +{ + int err; + struct snd_card_aica *dreamcastcard; + dreamcastcard = kmalloc(sizeof(struct snd_card_aica), GFP_KERNEL); + if (unlikely(!dreamcastcard)) + return -ENOMEM; + dreamcastcard->card = + snd_card_new(index, SND_AICA_DRIVER, THIS_MODULE, 0); + if (unlikely(!dreamcastcard->card)) { + kfree(dreamcastcard); + return -ENODEV; + } + strcpy(dreamcastcard->card->driver, "snd_aica"); + strcpy(dreamcastcard->card->shortname, SND_AICA_DRIVER); + strcpy(dreamcastcard->card->longname, + "Yamaha AICA Super Intelligent Sound Processor for SEGA Dreamcast"); + /* Prepare to use the queue */ + INIT_WORK(&(dreamcastcard->spu_dma_work), run_spu_dma); + /* Load the PCM 'chip' */ + err = snd_aicapcmchip(dreamcastcard, 0); + if (unlikely(err < 0)) + goto freedreamcast; + snd_card_set_dev(dreamcastcard->card, &devptr->dev); + dreamcastcard->timer.data = 0; + dreamcastcard->channel = NULL; + /* Add basic controls */ + err = add_aicamixer_controls(dreamcastcard); + if (unlikely(err < 0)) + goto freedreamcast; + /* Register the card with ALSA subsystem */ + err = snd_card_register(dreamcastcard->card); + if (unlikely(err < 0)) + goto freedreamcast; + platform_set_drvdata(devptr, dreamcastcard); + aica_queue = create_workqueue(CARD_NAME); + if (unlikely(!aica_queue)) + goto freedreamcast; + snd_printk + ("ALSA Driver for Yamaha AICA Super Intelligent Sound Processor\n"); + return 0; + freedreamcast: + snd_card_free(dreamcastcard->card); + kfree(dreamcastcard); + return err; +} + +static struct platform_driver snd_aica_driver = { + .probe = snd_aica_probe, + .remove = snd_aica_remove, + .driver = { + .name = SND_AICA_DRIVER}, +}; + +static int __init aica_init(void) +{ + int err; + err = platform_driver_register(&snd_aica_driver); + if (unlikely(err < 0)) + return err; + pd = platform_device_register_simple(SND_AICA_DRIVER, -1, + aica_memory_space, 2); + if (unlikely(IS_ERR(pd))) { + platform_driver_unregister(&snd_aica_driver); + return PTR_ERR(pd); + } + /* Load the firmware */ + return load_aica_firmware(); +} + +static void __exit aica_exit(void) +{ + /* Destroy the aica kernel thread * + * being extra cautious to check if it exists*/ + if (likely(aica_queue)) + destroy_workqueue(aica_queue); + platform_device_unregister(pd); + platform_driver_unregister(&snd_aica_driver); + /* Kill any sound still playing and reset ARM7 to safe state */ + spu_reset(); +} + +module_init(aica_init); +module_exit(aica_exit); diff --git a/sound/sh/aica.h b/sound/sh/aica.h new file mode 100644 index 000000000000..8c11e3d10a50 --- /dev/null +++ b/sound/sh/aica.h @@ -0,0 +1,81 @@ +/* aica.h + * Header file for ALSA driver for + * Sega Dreamcast Yamaha AICA sound + * Copyright Adrian McMenamin + * + * 2006 + * + * 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 + * + */ + +/* SPU memory and register constants etc */ +#define G2_FIFO 0xa05f688c +#define SPU_MEMORY_BASE 0xA0800000 +#define ARM_RESET_REGISTER 0xA0702C00 +#define SPU_REGISTER_BASE 0xA0700000 + +/* AICA channels stuff */ +#define AICA_CONTROL_POINT 0xA0810000 +#define AICA_CONTROL_CHANNEL_SAMPLE_NUMBER 0xA0810008 +#define AICA_CHANNEL0_CONTROL_OFFSET 0x10004 + +/* Command values */ +#define AICA_CMD_KICK 0x80000000 +#define AICA_CMD_NONE 0 +#define AICA_CMD_START 1 +#define AICA_CMD_STOP 2 +#define AICA_CMD_VOL 3 + +/* Sound modes */ +#define SM_8BIT 1 +#define SM_16BIT 0 +#define SM_ADPCM 2 + +/* Buffer and period size */ +#define AICA_BUFFER_SIZE 0x8000 +#define AICA_PERIOD_SIZE 0x800 +#define AICA_PERIOD_NUMBER 16 + +#define AICA_CHANNEL0_OFFSET 0x11000 +#define AICA_CHANNEL1_OFFSET 0x21000 +#define CHANNEL_OFFSET 0x10000 + +#define AICA_DMA_CHANNEL 0 +#define AICA_DMA_MODE 5 + +#define SND_AICA_DRIVER "AICA" + +struct aica_channel { + uint32_t cmd; /* Command ID */ + uint32_t pos; /* Sample position */ + uint32_t length; /* Sample length */ + uint32_t freq; /* Frequency */ + uint32_t vol; /* Volume 0-255 */ + uint32_t pan; /* Pan 0-255 */ + uint32_t sfmt; /* Sound format */ + uint32_t flags; /* Bit flags */ +}; + +struct snd_card_aica { + struct work_struct spu_dma_work; + struct snd_card *card; + struct aica_channel *channel; + struct snd_pcm_substream *substream; + int clicks; + int current_period; + struct timer_list timer; + int master_volume; + int dma_check; +}; diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 10cffc087181..97b255233175 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -27,6 +27,7 @@ config SND_SOC source "sound/soc/at91/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig" +source "sound/soc/sh/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 0ae2e49036f9..304140377632 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,4 +1,4 @@ snd-soc-core-objs := soc-core.o soc-dapm.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o -obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ s3c24xx/ +obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ s3c24xx/ sh/ diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 044a3712077a..e97c68306a9a 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -1,6 +1,7 @@ config SND_S3C24XX_SOC tristate "SoC Audio for the Samsung S3C24XX chips" depends on ARCH_S3C2410 && SND_SOC + select SND_PCM help Say Y or M if you want to add support for codecs attached to the S3C24XX AC97, I2S or SSP interface. You will also need @@ -8,3 +9,29 @@ config SND_S3C24XX_SOC config SND_S3C24XX_SOC_I2S tristate + +config SND_S3C2443_SOC_AC97 + tristate + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + +config SND_S3C24XX_SOC_NEO1973_WM8753 + tristate "SoC I2S Audio support for NEO1973 - WM8753" + depends on SND_S3C24XX_SOC && MACH_GTA01 + select SND_S3C24XX_SOC_I2S + select SND_SOC_WM8753 + help + Say Y if you want to add support for SoC audio on smdk2440 + with the WM8753. + +config SND_S3C24XX_SOC_SMDK2443_WM9710 + tristate "SoC AC97 Audio support for SMDK2443 - WM9710" + depends on SND_S3C24XX_SOC && MACH_SMDK2443 + select SND_S3C2443_SOC_AC97 + select SND_SOC_AC97_CODEC + help + Say Y if you want to add support for SoC audio on smdk2443 + with the WM9710. + + diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index 6f0fffcb30f5..13c92f0fa1e4 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -1,6 +1,15 @@ # S3c24XX Platform Support snd-soc-s3c24xx-objs := s3c24xx-pcm.o snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o +snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o +obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o + +# S3C24XX Machine Support +snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o +snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o + +obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o +obj-$(CONFIG_SND_S3C24XX_SOC_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o diff --git a/sound/soc/s3c24xx/lm4857.h b/sound/soc/s3c24xx/lm4857.h new file mode 100644 index 000000000000..0cf5b7011d6f --- /dev/null +++ b/sound/soc/s3c24xx/lm4857.h @@ -0,0 +1,32 @@ +/* + * lm4857.h -- ALSA Soc Audio Layer + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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. + * + * Revision history + * 18th Jun 2007 Initial version. + */ + +#ifndef LM4857_H_ +#define LM4857_H_ + +/* The register offsets in the cache array */ +#define LM4857_MVOL 0 +#define LM4857_LVOL 1 +#define LM4857_RVOL 2 +#define LM4857_CTRL 3 + +/* the shifts required to set these bits */ +#define LM4857_3D 5 +#define LM4857_WAKEUP 5 +#define LM4857_EPGAIN 4 + +#endif /*LM4857_H_*/ + diff --git a/sound/soc/s3c24xx/neo1973_wm8753.c b/sound/soc/s3c24xx/neo1973_wm8753.c new file mode 100644 index 000000000000..d5a8fc2cf8d6 --- /dev/null +++ b/sound/soc/s3c24xx/neo1973_wm8753.c @@ -0,0 +1,670 @@ +/* + * neo1973_wm8753.c -- SoC audio for Neo1973 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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. + * + * Revision history + * 20th Jan 2007 Initial version. + * 05th Feb 2007 Rename all to Neo1973 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../codecs/wm8753.h" +#include "lm4857.h" +#include "s3c24xx-pcm.h" +#include "s3c24xx-i2s.h" + +/* define the scenarios */ +#define NEO_AUDIO_OFF 0 +#define NEO_GSM_CALL_AUDIO_HANDSET 1 +#define NEO_GSM_CALL_AUDIO_HEADSET 2 +#define NEO_GSM_CALL_AUDIO_BLUETOOTH 3 +#define NEO_STEREO_TO_SPEAKERS 4 +#define NEO_STEREO_TO_HEADPHONES 5 +#define NEO_CAPTURE_HANDSET 6 +#define NEO_CAPTURE_HEADSET 7 +#define NEO_CAPTURE_BLUETOOTH 8 + +static struct snd_soc_machine neo1973; +static struct i2c_client *i2c; + +static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int pll_out = 0, bclk = 0; + int ret = 0; + unsigned long iis_clkrate; + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + switch (params_rate(params)) { + case 8000: + case 16000: + pll_out = 12288000; + break; + case 48000: + bclk = WM8753_BCLK_DIV_4; + pll_out = 12288000; + break; + case 96000: + bclk = WM8753_BCLK_DIV_2; + pll_out = 12288000; + break; + case 11025: + bclk = WM8753_BCLK_DIV_16; + pll_out = 11289600; + break; + case 22050: + bclk = WM8753_BCLK_DIV_8; + pll_out = 11289600; + break; + case 44100: + bclk = WM8753_BCLK_DIV_4; + pll_out = 11289600; + break; + case 88200: + bclk = WM8753_BCLK_DIV_2; + pll_out = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8753_MCLK, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + S3C2410_IISMOD_32FS ); + if (ret < 0) + return ret; + + /* set codec BCLK division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(4,4)); + if (ret < 0) + return ret; + + /* codec PLL input is PCLK/4 */ + ret = codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL1, + iis_clkrate / 4, pll_out); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + + /* disable the PLL */ + return codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL1, 0, 0); +} + +/* + * Neo1973 WM8753 HiFi DAI opserations. + */ +static struct snd_soc_ops neo1973_hifi_ops = { + .hw_params = neo1973_hifi_hw_params, + .hw_free = neo1973_hifi_hw_free, +}; + +static int neo1973_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + unsigned int pcmdiv = 0; + int ret = 0; + unsigned long iis_clkrate; + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + if (params_rate(params) != 8000) + return -EINVAL; + if (params_channels(params) != 1) + return -EINVAL; + + pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */ + + /* todo: gg check mode (DSP_B) against CSR datasheet */ + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8753_PCMCLK, 12288000, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set codec PCM division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv); + if (ret < 0) + return ret; + + /* configue and enable PLL for 12.288MHz output */ + ret = codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL2, + iis_clkrate / 4, 12288000); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_voice_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + + /* disable the PLL */ + return codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL2, 0, 0); +} + +static struct snd_soc_ops neo1973_voice_ops = { + .hw_params = neo1973_voice_hw_params, + .hw_free = neo1973_voice_hw_free, +}; + +static int neo1973_scenario = 0; + +static int neo1973_get_scenario(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = neo1973_scenario; + return 0; +} + +static int set_scenario_endpoints(struct snd_soc_codec *codec, int scenario) +{ + switch(neo1973_scenario) { + case NEO_AUDIO_OFF: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_GSM_CALL_AUDIO_HANDSET: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 1); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 1); + break; + case NEO_GSM_CALL_AUDIO_HEADSET: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 1); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 1); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_GSM_CALL_AUDIO_BLUETOOTH: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 1); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_STEREO_TO_SPEAKERS: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_STEREO_TO_HEADPHONES: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_CAPTURE_HANDSET: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 1); + break; + case NEO_CAPTURE_HEADSET: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 1); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_CAPTURE_BLUETOOTH: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + default: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + } + + snd_soc_dapm_sync_endpoints(codec); + + return 0; +} + +static int neo1973_set_scenario(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (neo1973_scenario == ucontrol->value.integer.value[0]) + return 0; + + neo1973_scenario = ucontrol->value.integer.value[0]; + set_scenario_endpoints(codec, neo1973_scenario); + return 1; +} + +static u8 lm4857_regs[4] = {0x00, 0x40, 0x80, 0xC0}; + +static void lm4857_write_regs(void) +{ + if (i2c_master_send(i2c, lm4857_regs, 4) != 4) + printk(KERN_ERR "lm4857: i2c write failed\n"); +} + +static int lm4857_get_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int reg=kcontrol->private_value & 0xFF; + int shift = (kcontrol->private_value >> 8) & 0x0F; + int mask = (kcontrol->private_value >> 16) & 0xFF; + + ucontrol->value.integer.value[0] = (lm4857_regs[reg] >> shift) & mask; + return 0; +} + +static int lm4857_set_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int reg = kcontrol->private_value & 0xFF; + int shift = (kcontrol->private_value >> 8) & 0x0F; + int mask = (kcontrol->private_value >> 16) & 0xFF; + + if (((lm4857_regs[reg] >> shift ) & mask) == + ucontrol->value.integer.value[0]) + return 0; + + lm4857_regs[reg] &= ~ (mask << shift); + lm4857_regs[reg] |= ucontrol->value.integer.value[0] << shift; + lm4857_write_regs(); + return 1; +} + +static int lm4857_get_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 value = lm4857_regs[LM4857_CTRL] & 0x0F; + + if (value) + value -= 5; + + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int lm4857_set_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 value = ucontrol->value.integer.value[0]; + + if (value) + value += 5; + + if ((lm4857_regs[LM4857_CTRL] & 0x0F) == value) + return 0; + + lm4857_regs[LM4857_CTRL] &= 0xF0; + lm4857_regs[LM4857_CTRL] |= value; + lm4857_write_regs(); + return 1; +} + +static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Audio Out", NULL), + SND_SOC_DAPM_LINE("GSM Line Out", NULL), + SND_SOC_DAPM_LINE("GSM Line In", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Call Mic", NULL), +}; + + +/* example machine audio_mapnections */ +static const char* audio_map[][3] = { + + /* Connections to the lm4857 amp */ + {"Audio Out", NULL, "LOUT1"}, + {"Audio Out", NULL, "ROUT1"}, + + /* Connections to the GSM Module */ + {"GSM Line Out", NULL, "MONO1"}, + {"GSM Line Out", NULL, "MONO2"}, + {"RXP", NULL, "GSM Line In"}, + {"RXN", NULL, "GSM Line In"}, + + /* Connections to Headset */ + {"MIC1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headset Mic"}, + + /* Call Mic */ + {"MIC2", NULL, "Mic Bias"}, + {"MIC2N", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Call Mic"}, + + /* Connect the ALC pins */ + {"ACIN", NULL, "ACOP"}, + + {NULL, NULL, NULL}, +}; + +static const char *lm4857_mode[] = { + "Off", + "Call Speaker", + "Stereo Speakers", + "Stereo Speakers + Headphones", + "Headphones" +}; + +static const struct soc_enum lm4857_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode), +}; + +static const char *neo_scenarios[] = { + "Off", + "GSM Handset", + "GSM Headset", + "GSM Bluetooth", + "Speakers", + "Headphones", + "Capture Handset", + "Capture Headset", + "Capture Bluetooth" +}; + +static const struct soc_enum neo_scenario_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(neo_scenarios),neo_scenarios), +}; + +static const struct snd_kcontrol_new wm8753_neo1973_controls[] = { + SOC_SINGLE_EXT("Amp Left Playback Volume", LM4857_LVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp Right Playback Volume", LM4857_RVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp Mono Playback Volume", LM4857_MVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_ENUM_EXT("Amp Mode", lm4857_mode_enum[0], + lm4857_get_mode, lm4857_set_mode), + SOC_ENUM_EXT("Neo Mode", neo_scenario_enum[0], + neo1973_get_scenario, neo1973_set_scenario), + SOC_SINGLE_EXT("Amp Spk 3D Playback Switch", LM4857_LVOL, 5, 1, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp HP 3d Playback Switch", LM4857_RVOL, 5, 1, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp Fast Wakeup Playback Switch", LM4857_CTRL, 5, 1, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp Earpiece 6dB Playback Switch", LM4857_CTRL, 4, 1, 0, + lm4857_get_reg, lm4857_set_reg), +}; + +/* + * This is an example machine initialisation for a wm8753 connected to a + * neo1973 II. It is missing logic to detect hp/mic insertions and logic + * to re-route the audio in such an event. + */ +static int neo1973_wm8753_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* set up NC codec pins */ + snd_soc_dapm_set_endpoint(codec, "LOUT2", 0); + snd_soc_dapm_set_endpoint(codec, "ROUT2", 0); + snd_soc_dapm_set_endpoint(codec, "OUT3", 0); + snd_soc_dapm_set_endpoint(codec, "OUT4", 0); + snd_soc_dapm_set_endpoint(codec, "LINE1", 0); + snd_soc_dapm_set_endpoint(codec, "LINE2", 0); + + + /* set endpoints to default mode */ + set_scenario_endpoints(codec, NEO_AUDIO_OFF); + + /* Add neo1973 specific widgets */ + for (i = 0; i < ARRAY_SIZE(wm8753_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &wm8753_dapm_widgets[i]); + + /* add neo1973 specific controls */ + for (i = 0; i < ARRAY_SIZE(wm8753_neo1973_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8753_neo1973_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + /* set up neo1973 specific audio path audio_mapnects */ + for (i = 0; audio_map[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, audio_map[i][0], + audio_map[i][1], audio_map[i][2]); + } + + snd_soc_dapm_sync_endpoints(codec); + return 0; +} + +/* + * BT Codec DAI + */ +static struct snd_soc_cpu_dai bt_dai = +{ .name = "Bluetooth", + .id = 0, + .type = SND_SOC_DAI_PCM, + .playback = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, +}; + +static struct snd_soc_dai_link neo1973_dai[] = { +{ /* Hifi Playback - for similatious use with voice below */ + .name = "WM8753", + .stream_name = "WM8753 HiFi", + .cpu_dai = &s3c24xx_i2s_dai, + .codec_dai = &wm8753_dai[WM8753_DAI_HIFI], + .init = neo1973_wm8753_init, + .ops = &neo1973_hifi_ops, +}, +{ /* Voice via BT */ + .name = "Bluetooth", + .stream_name = "Voice", + .cpu_dai = &bt_dai, + .codec_dai = &wm8753_dai[WM8753_DAI_VOICE], + .ops = &neo1973_voice_ops, +}, +}; + +static struct snd_soc_machine neo1973 = { + .name = "neo1973", + .dai_link = neo1973_dai, + .num_links = ARRAY_SIZE(neo1973_dai), +}; + +static struct wm8753_setup_data neo1973_wm8753_setup = { + .i2c_address = 0x1a, +}; + +static struct snd_soc_device neo1973_snd_devdata = { + .machine = &neo1973, + .platform = &s3c24xx_soc_platform, + .codec_dev = &soc_codec_dev_wm8753, + .codec_data = &neo1973_wm8753_setup, +}; + +static struct i2c_client client_template; + +static unsigned short normal_i2c[] = { 0x7C, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static int lm4857_amp_probe(struct i2c_adapter *adap, int addr, int kind) +{ + int ret; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) + return -ENOMEM; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + printk(KERN_ERR "LM4857 failed to attach at addr %x\n", addr); + goto exit_err; + } + + lm4857_write_regs(); + return ret; + +exit_err: + kfree(i2c); + return ret; +} + +static int lm4857_i2c_detach(struct i2c_client *client) +{ + i2c_detach_client(client); + kfree(client); + return 0; +} + +static int lm4857_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, lm4857_amp_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver lm4857_i2c_driver = { + .driver = { + .name = "LM4857 I2C Amp", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_LM4857, + .attach_adapter = lm4857_i2c_attach, + .detach_client = lm4857_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "LM4857", + .driver = &lm4857_i2c_driver, +}; + +static struct platform_device *neo1973_snd_device; + +static int __init neo1973_init(void) +{ + int ret; + + neo1973_snd_device = platform_device_alloc("soc-audio", -1); + if (!neo1973_snd_device) + return -ENOMEM; + + platform_set_drvdata(neo1973_snd_device, &neo1973_snd_devdata); + neo1973_snd_devdata.dev = &neo1973_snd_device->dev; + ret = platform_device_add(neo1973_snd_device); + + if (ret) + platform_device_put(neo1973_snd_device); + + ret = i2c_add_driver(&lm4857_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + + return ret; +} + +static void __exit neo1973_exit(void) +{ + platform_device_unregister(neo1973_snd_device); +} + +module_init(neo1973_init); +module_exit(neo1973_exit); + +/* Module information */ +MODULE_AUTHOR("Graeme Gregory, graeme.gregory@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c2443-ac97.c b/sound/soc/s3c24xx/s3c2443-ac97.c new file mode 100644 index 000000000000..75acf7ef5528 --- /dev/null +++ b/sound/soc/s3c24xx/s3c2443-ac97.c @@ -0,0 +1,401 @@ +/* + * s3c2443-ac97.c -- ALSA Soc Audio Layer + * + * (c) 2007 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Copyright (C) 2005, Sean Choi + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Revision history + * 21st Mar 2007 Initial Version + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "s3c24xx-pcm.h" +#include "s3c24xx-ac97.h" + +struct s3c24xx_ac97_info { + void __iomem *regs; + struct clk *ac97_clk; +}; +static struct s3c24xx_ac97_info s3c24xx_ac97; + +DECLARE_COMPLETION(ac97_completion); +static u32 codec_ready; +static DECLARE_MUTEX(ac97_mutex); + +static unsigned short s3c2443_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + u32 ac_glbctrl; + u32 ac_codec_cmd; + u32 stat, addr, data; + + down(&ac97_mutex); + + codec_ready = S3C_AC97_GLBSTAT_CODECREADY; + ac_codec_cmd = readl(s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD); + ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg); + writel(ac_codec_cmd, s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD); + + udelay(50); + + ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + + wait_for_completion(&ac97_completion); + + stat = readl(s3c24xx_ac97.regs + S3C_AC97_STAT); + addr = (stat >> 16) & 0x7f; + data = (stat & 0xffff); + + if (addr != reg) + printk(KERN_ERR "s3c24xx-ac97: req addr = %02x," + " rep addr = %02x\n", reg, addr); + + up(&ac97_mutex); + + return (unsigned short)data; +} + +static void s3c2443_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + u32 ac_glbctrl; + u32 ac_codec_cmd; + + down(&ac97_mutex); + + codec_ready = S3C_AC97_GLBSTAT_CODECREADY; + ac_codec_cmd = readl(s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD); + ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val); + writel(ac_codec_cmd, s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD); + + udelay(50); + + ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + + wait_for_completion(&ac97_completion); + + ac_codec_cmd = readl(s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD); + ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ; + writel(ac_codec_cmd, s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD); + + up(&ac97_mutex); + +} + +static void s3c2443_ac97_warm_reset(struct snd_ac97 *ac97) +{ + u32 ac_glbctrl; + + ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl = S3C_AC97_GLBCTRL_WARMRESET; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + ac_glbctrl = 0; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); +} + +static void s3c2443_ac97_cold_reset(struct snd_ac97 *ac97) +{ + u32 ac_glbctrl; + + ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl = S3C_AC97_GLBCTRL_COLDRESET; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + ac_glbctrl = 0; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA | + S3C_AC97_GLBCTRL_PCMINTM_DMA | S3C_AC97_GLBCTRL_MICINTM_DMA; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); +} + +static irqreturn_t s3c2443_ac97_irq(int irq, void *dev_id) +{ + int status; + u32 ac_glbctrl; + + status = readl(s3c24xx_ac97.regs + S3C_AC97_GLBSTAT) & codec_ready; + + if (status) { + ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + complete(&ac97_completion); + } + return IRQ_HANDLED; +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = s3c2443_ac97_read, + .write = s3c2443_ac97_write, + .warm_reset = s3c2443_ac97_warm_reset, + .reset = s3c2443_ac97_cold_reset, +}; + +static struct s3c2410_dma_client s3c2443_dma_client_out = { + .name = "AC97 PCM Stereo out" +}; + +static struct s3c2410_dma_client s3c2443_dma_client_in = { + .name = "AC97 PCM Stereo in" +}; + +static struct s3c2410_dma_client s3c2443_dma_client_micin = { + .name = "AC97 Mic Mono in" +}; + +static struct s3c24xx_pcm_dma_params s3c2443_ac97_pcm_stereo_out = { + .client = &s3c2443_dma_client_out, + .channel = DMACH_PCM_OUT, + .dma_addr = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA, + .dma_size = 4, +}; + +static struct s3c24xx_pcm_dma_params s3c2443_ac97_pcm_stereo_in = { + .client = &s3c2443_dma_client_in, + .channel = DMACH_PCM_IN, + .dma_addr = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA, + .dma_size = 4, +}; + +static struct s3c24xx_pcm_dma_params s3c2443_ac97_mic_mono_in = { + .client = &s3c2443_dma_client_micin, + .channel = DMACH_MIC_IN, + .dma_addr = S3C2440_PA_AC97 + S3C_AC97_MIC_DATA, + .dma_size = 4, +}; + +static int s3c2443_ac97_probe(struct platform_device *pdev) +{ + int ret; + u32 ac_glbctrl; + + s3c24xx_ac97.regs = ioremap(S3C2440_PA_AC97, 0x100); + if (s3c24xx_ac97.regs == NULL) + return -ENXIO; + + s3c24xx_ac97.ac97_clk = clk_get(&pdev->dev, "ac97"); + if (s3c24xx_ac97.ac97_clk == NULL) { + printk(KERN_ERR "s3c2443-ac97 failed to get ac97_clock\n"); + iounmap(s3c24xx_ac97.regs); + return -ENODEV; + } + clk_enable(s3c24xx_ac97.ac97_clk); + + s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2443_GPE0_AC_nRESET); + s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2443_GPE1_AC_SYNC); + s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2443_GPE2_AC_BITCLK); + s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2443_GPE3_AC_SDI); + s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2443_GPE4_AC_SDO); + + ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl = S3C_AC97_GLBCTRL_COLDRESET; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + ac_glbctrl = 0; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE; + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + + ret = request_irq(IRQ_S3C2443_AC97, s3c2443_ac97_irq, + IRQF_DISABLED, "AC97", NULL); + if (ret < 0) { + printk(KERN_ERR "s3c24xx-ac97: interrupt request failed.\n"); + clk_disable(s3c24xx_ac97.ac97_clk); + clk_put(s3c24xx_ac97.ac97_clk); + iounmap(s3c24xx_ac97.regs); + } + return ret; +} + +static void s3c2443_ac97_remove(struct platform_device *pdev) +{ + free_irq(IRQ_S3C2443_AC97, NULL); + clk_disable(s3c24xx_ac97.ac97_clk); + clk_put(s3c24xx_ac97.ac97_clk); + iounmap(s3c24xx_ac97.regs); +} + +static int s3c2443_ac97_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cpu_dai->dma_data = &s3c2443_ac97_pcm_stereo_out; + else + cpu_dai->dma_data = &s3c2443_ac97_pcm_stereo_in; + + return 0; +} + +static int s3c2443_ac97_trigger(struct snd_pcm_substream *substream, int cmd) +{ + u32 ac_glbctrl; + + ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + switch(cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA; + else + ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK; + else + ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK; + break; + } + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + + return 0; +} + +static int s3c2443_ac97_hw_mic_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return -ENODEV; + else + cpu_dai->dma_data = &s3c2443_ac97_mic_mono_in; + + return 0; +} + +static int s3c2443_ac97_mic_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + u32 ac_glbctrl; + + ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + switch(cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK; + } + writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); + + return 0; +} + +#define s3c2443_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +struct snd_soc_cpu_dai s3c2443_ac97_dai[] = { +{ + .name = "s3c2443-ac97", + .id = 0, + .type = SND_SOC_DAI_AC97, + .probe = s3c2443_ac97_probe, + .remove = s3c2443_ac97_remove, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = s3c2443_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = s3c2443_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = s3c2443_ac97_hw_params, + .trigger = s3c2443_ac97_trigger}, +}, +{ + .name = "pxa2xx-ac97-mic", + .id = 1, + .type = SND_SOC_DAI_AC97, + .capture = { + .stream_name = "AC97 Mic Capture", + .channels_min = 1, + .channels_max = 1, + .rates = s3c2443_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = s3c2443_ac97_hw_mic_params, + .trigger = s3c2443_ac97_mic_trigger,}, +}, +}; + +EXPORT_SYMBOL_GPL(s3c2443_ac97_dai); +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +MODULE_AUTHOR("Graeme Gregory"); +MODULE_DESCRIPTION("AC97 driver for the Samsung s3c2443 chip"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c24xx-ac97.h b/sound/soc/s3c24xx/s3c24xx-ac97.h new file mode 100644 index 000000000000..2b835e8260fa --- /dev/null +++ b/sound/soc/s3c24xx/s3c24xx-ac97.h @@ -0,0 +1,25 @@ +/* + * s3c24xx-ac97.c -- ALSA Soc Audio Layer + * + * (c) 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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. + * + * Revision history + * 10th Nov 2006 Initial version. + */ + +#ifndef S3C24XXAC97_H_ +#define S3C24XXAC97_H_ + +#define AC_CMD_ADDR(x) (x << 16) +#define AC_CMD_DATA(x) (x & 0xffff) + +extern struct snd_soc_cpu_dai s3c2443_ac97_dai[]; + +#endif /*S3C24XXAC97_H_*/ diff --git a/sound/soc/s3c24xx/s3c24xx-i2s.c b/sound/soc/s3c24xx/s3c24xx-i2s.c index 8ca314dc8891..39f02462e07d 100644 --- a/sound/soc/s3c24xx/s3c24xx-i2s.c +++ b/sound/soc/s3c24xx/s3c24xx-i2s.c @@ -344,11 +344,11 @@ static int s3c24xx_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai, DBG("Entered %s\n", __FUNCTION__); switch (div_id) { - case S3C24XX_DIV_MCLK: + case S3C24XX_DIV_BCLK: reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK; writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); break; - case S3C24XX_DIV_BCLK: + case S3C24XX_DIV_MCLK: reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS); writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); break; diff --git a/sound/soc/s3c24xx/smdk2443_wm9710.c b/sound/soc/s3c24xx/smdk2443_wm9710.c new file mode 100644 index 000000000000..d46cd811ceb3 --- /dev/null +++ b/sound/soc/s3c24xx/smdk2443_wm9710.c @@ -0,0 +1,85 @@ +/* + * smdk2443_wm9710.c -- SoC audio for smdk2443 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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. + * + * Revision history + * 8th Mar 2007 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/ac97.h" +#include "s3c24xx-pcm.h" +#include "s3c24xx-ac97.h" + +static struct snd_soc_machine smdk2443; + +static struct snd_soc_dai_link smdk2443_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &s3c2443_ac97_dai[0], + .codec_dai = &ac97_dai, +}, +}; + +static struct snd_soc_machine smdk2443 = { + .name = "SMDK2443", + .dai_link = smdk2443_dai, + .num_links = ARRAY_SIZE(smdk2443_dai), +}; + +static struct snd_soc_device smdk2443_snd_ac97_devdata = { + .machine = &smdk2443, + .platform = &s3c24xx_soc_platform, + .codec_dev = &soc_codec_dev_ac97, +}; + +static struct platform_device *smdk2443_snd_ac97_device; + +static int __init smdk2443_init(void) +{ + int ret; + + smdk2443_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!smdk2443_snd_ac97_device) + return -ENOMEM; + + platform_set_drvdata(smdk2443_snd_ac97_device, + &smdk2443_snd_ac97_devdata); + smdk2443_snd_ac97_devdata.dev = &smdk2443_snd_ac97_device->dev; + ret = platform_device_add(smdk2443_snd_ac97_device); + + if (ret) + platform_device_put(smdk2443_snd_ac97_device); + + return ret; +} + +static void __exit smdk2443_exit(void) +{ + platform_device_unregister(smdk2443_snd_ac97_device); +} + +module_init(smdk2443_init); +module_exit(smdk2443_exit); + +/* Module information */ +MODULE_AUTHOR("Graeme Gregory, graeme.gregory@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC WM9710 SMDK2443"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig new file mode 100644 index 000000000000..f03220d23e73 --- /dev/null +++ b/sound/soc/sh/Kconfig @@ -0,0 +1,38 @@ +menu "SoC Audio support for SuperH" + +config SND_SOC_PCM_SH7760 + tristate "SoC Audio support for Renesas SH7760" + depends on CPU_SUBTYPE_SH7760 && SND_SOC && SH_DMABRG + help + Enable this option for SH7760 AC97/I2S audio support. + + +## +## Audio unit modules +## + +config SND_SOC_SH4_HAC + select AC97_BUS + select SND_SOC_AC97_BUS + select SND_AC97_CODEC + tristate + +config SND_SOC_SH4_SSI + tristate + + + +## +## Boards +## + +config SND_SH7760_AC97 + tristate "SH7760 AC97 sound support" + depends on CPU_SUBTYPE_SH7760 && SND_SOC_PCM_SH7760 + select SND_SOC_SH4_HAC + select SND_SOC_AC97_CODEC + help + This option enables generic sound support for the first + AC97 unit of the SH7760. + +endmenu diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile new file mode 100644 index 000000000000..a8e8ab81cc6a --- /dev/null +++ b/sound/soc/sh/Makefile @@ -0,0 +1,14 @@ +## DMA engines +snd-soc-dma-sh7760-objs := dma-sh7760.o +obj-$(CONFIG_SND_SOC_PCM_SH7760) += snd-soc-dma-sh7760.o + +## audio units found on some SH-4 +snd-soc-hac-objs := hac.o +snd-soc-ssi-objs := ssi.o +obj-$(CONFIG_SND_SOC_SH4_HAC) += snd-soc-hac.o +obj-$(CONFIG_SND_SOC_SH4_SSI) += snd-soc-ssi.o + +## boards +snd-soc-sh7760-ac97-objs := sh7760-ac97.o + +obj-$(CONFIG_SND_SH7760_AC97) += snd-soc-sh7760-ac97.o diff --git a/sound/soc/sh/dma-sh7760.c b/sound/soc/sh/dma-sh7760.c new file mode 100644 index 000000000000..cdee374b843e --- /dev/null +++ b/sound/soc/sh/dma-sh7760.c @@ -0,0 +1,354 @@ +/* + * SH7760 ("camelot") DMABRG audio DMA unit support + * + * Copyright (C) 2007 Manuel Lauss + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * The SH7760 DMABRG provides 4 dma channels (2x rec, 2x play), which + * trigger an interrupt when one half of the programmed transfer size + * has been xmitted. + * + * FIXME: little-endian only for now + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* registers and bits */ +#define BRGATXSAR 0x00 +#define BRGARXDAR 0x04 +#define BRGATXTCR 0x08 +#define BRGARXTCR 0x0C +#define BRGACR 0x10 +#define BRGATXTCNT 0x14 +#define BRGARXTCNT 0x18 + +#define ACR_RAR (1 << 18) +#define ACR_RDS (1 << 17) +#define ACR_RDE (1 << 16) +#define ACR_TAR (1 << 2) +#define ACR_TDS (1 << 1) +#define ACR_TDE (1 << 0) + +/* receiver/transmitter data alignment */ +#define ACR_RAM_NONE (0 << 24) +#define ACR_RAM_4BYTE (1 << 24) +#define ACR_RAM_2WORD (2 << 24) +#define ACR_TAM_NONE (0 << 8) +#define ACR_TAM_4BYTE (1 << 8) +#define ACR_TAM_2WORD (2 << 8) + + +struct camelot_pcm { + unsigned long mmio; /* DMABRG audio channel control reg MMIO */ + unsigned int txid; /* ID of first DMABRG IRQ for this unit */ + + struct snd_pcm_substream *tx_ss; + unsigned long tx_period_size; + unsigned int tx_period; + + struct snd_pcm_substream *rx_ss; + unsigned long rx_period_size; + unsigned int rx_period; + +} cam_pcm_data[2] = { + { + .mmio = 0xFE3C0040, + .txid = DMABRGIRQ_A0TXF, + }, + { + .mmio = 0xFE3C0060, + .txid = DMABRGIRQ_A1TXF, + }, +}; + +#define BRGREG(x) (*(unsigned long *)(cam->mmio + (x))) + +/* + * set a minimum of 16kb per period, to avoid interrupt-"storm" and + * resulting skipping. In general, the bigger the minimum size, the + * better for overall system performance. (The SH7760 is a puny CPU + * with a slow SDRAM interface and poor internal bus bandwidth, + * *especially* when the LCDC is active). The minimum for the DMAC + * is 8 bytes; 16kbytes are enough to get skip-free playback of a + * 44kHz/16bit/stereo MP3 on a lightly loaded system, and maintain + * reasonable responsiveness in MPlayer. + */ +#define DMABRG_PERIOD_MIN 16 * 1024 +#define DMABRG_PERIOD_MAX 0x03fffffc +#define DMABRG_PREALLOC_BUFFER 32 * 1024 +#define DMABRG_PREALLOC_BUFFER_MAX 32 * 1024 + +/* support everything the SSI supports */ +#define DMABRG_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define DMABRG_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) + +static struct snd_pcm_hardware camelot_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = DMABRG_FMTS, + .rates = DMABRG_RATES, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 8, /* max of the SSI */ + .buffer_bytes_max = DMABRG_PERIOD_MAX, + .period_bytes_min = DMABRG_PERIOD_MIN, + .period_bytes_max = DMABRG_PERIOD_MAX / 2, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 128, +}; + +static void camelot_txdma(void *data) +{ + struct camelot_pcm *cam = data; + cam->tx_period ^= 1; + snd_pcm_period_elapsed(cam->tx_ss); +} + +static void camelot_rxdma(void *data) +{ + struct camelot_pcm *cam = data; + cam->rx_period ^= 1; + snd_pcm_period_elapsed(cam->rx_ss); +} + +static int camelot_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int ret, dmairq; + + snd_soc_set_runtime_hwparams(substream, &camelot_pcm_hardware); + + /* DMABRG buffer half/full events */ + dmairq = (recv) ? cam->txid + 2 : cam->txid; + if (recv) { + cam->rx_ss = substream; + ret = dmabrg_request_irq(dmairq, camelot_rxdma, cam); + if (unlikely(ret)) { + pr_debug("audio unit %d irqs already taken!\n", + rtd->dai->cpu_dai->id); + return -EBUSY; + } + (void)dmabrg_request_irq(dmairq + 1,camelot_rxdma, cam); + } else { + cam->tx_ss = substream; + ret = dmabrg_request_irq(dmairq, camelot_txdma, cam); + if (unlikely(ret)) { + pr_debug("audio unit %d irqs already taken!\n", + rtd->dai->cpu_dai->id); + return -EBUSY; + } + (void)dmabrg_request_irq(dmairq + 1, camelot_txdma, cam); + } + return 0; +} + +static int camelot_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int dmairq; + + dmairq = (recv) ? cam->txid + 2 : cam->txid; + + if (recv) + cam->rx_ss = NULL; + else + cam->tx_ss = NULL; + + dmabrg_free_irq(dmairq + 1); + dmabrg_free_irq(dmairq); + + return 0; +} + +static int camelot_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + + if (recv) { + cam->rx_period_size = params_period_bytes(hw_params); + cam->rx_period = 0; + } else { + cam->tx_period_size = params_period_bytes(hw_params); + cam->tx_period = 0; + } + return 0; +} + +static int camelot_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int camelot_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + + pr_debug("PCM data: addr 0x%08ulx len %d\n", + (u32)runtime->dma_addr, runtime->dma_bytes); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + BRGREG(BRGATXSAR) = (unsigned long)runtime->dma_area; + BRGREG(BRGATXTCR) = runtime->dma_bytes; + } else { + BRGREG(BRGARXDAR) = (unsigned long)runtime->dma_area; + BRGREG(BRGARXTCR) = runtime->dma_bytes; + } + + return 0; +} + +static inline void dmabrg_play_dma_start(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* start DMABRG engine: XFER start, auto-addr-reload */ + BRGREG(BRGACR) = acr | ACR_TDE | ACR_TAR | ACR_TAM_2WORD; +} + +static inline void dmabrg_play_dma_stop(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* forcibly terminate data transmission */ + BRGREG(BRGACR) = acr | ACR_TDS; +} + +static inline void dmabrg_rec_dma_start(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* start DMABRG engine: recv start, auto-reload */ + BRGREG(BRGACR) = acr | ACR_RDE | ACR_RAR | ACR_RAM_2WORD; +} + +static inline void dmabrg_rec_dma_stop(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* forcibly terminate data receiver */ + BRGREG(BRGACR) = acr | ACR_RDS; +} + +static int camelot_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (recv) + dmabrg_rec_dma_start(cam); + else + dmabrg_play_dma_start(cam); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (recv) + dmabrg_rec_dma_stop(cam); + else + dmabrg_play_dma_stop(cam); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t camelot_pos(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + unsigned long pos; + + /* cannot use the DMABRG pointer register: under load, by the + * time ALSA comes around to read the register, it is already + * far ahead (or worse, already done with the fragment) of the + * position at the time the IRQ was triggered, which results in + * fast-playback sound in my test application (ScummVM) + */ + if (recv) + pos = cam->rx_period ? cam->rx_period_size : 0; + else + pos = cam->tx_period ? cam->tx_period_size : 0; + + return bytes_to_frames(runtime, pos); +} + +static struct snd_pcm_ops camelot_pcm_ops = { + .open = camelot_pcm_open, + .close = camelot_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = camelot_hw_params, + .hw_free = camelot_hw_free, + .prepare = camelot_prepare, + .trigger = camelot_trigger, + .pointer = camelot_pos, +}; + +static void camelot_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int camelot_pcm_new(struct snd_card *card, + struct snd_soc_codec_dai *dai, + struct snd_pcm *pcm) +{ + /* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel + * in MMAP mode (i.e. aplay -M) + */ + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + DMABRG_PREALLOC_BUFFER, DMABRG_PREALLOC_BUFFER_MAX); + + return 0; +} + +struct snd_soc_platform sh7760_soc_platform = { + .name = "sh7760-pcm", + .pcm_ops = &camelot_pcm_ops, + .pcm_new = camelot_pcm_new, + .pcm_free = camelot_pcm_free, +}; +EXPORT_SYMBOL_GPL(sh7760_soc_platform); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SH7760 Audio DMA (DMABRG) driver"); +MODULE_AUTHOR("Manuel Lauss "); diff --git a/sound/soc/sh/hac.c b/sound/soc/sh/hac.c new file mode 100644 index 000000000000..8e3f03908cdb --- /dev/null +++ b/sound/soc/sh/hac.c @@ -0,0 +1,322 @@ +/* + * Hitachi Audio Controller (AC97) support for SH7760/SH7780 + * + * Copyright (c) 2007 Manuel Lauss + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * dont forget to set IPSEL/OMSEL register bits (in your board code) to + * enable HAC output pins! + */ + +/* BIG FAT FIXME: although the SH7760 has 2 independent AC97 units, only + * the FIRST can be used since ASoC does not pass any information to the + * ac97_read/write() functions regarding WHICH unit to use. You'll have + * to edit the code a bit to use the other AC97 unit. --mlau + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* regs and bits */ +#define HACCR 0x08 +#define HACCSAR 0x20 +#define HACCSDR 0x24 +#define HACPCML 0x28 +#define HACPCMR 0x2C +#define HACTIER 0x50 +#define HACTSR 0x54 +#define HACRIER 0x58 +#define HACRSR 0x5C +#define HACACR 0x60 + +#define CR_CR (1 << 15) /* "codec-ready" indicator */ +#define CR_CDRT (1 << 11) /* cold reset */ +#define CR_WMRT (1 << 10) /* warm reset */ +#define CR_B9 (1 << 9) /* the mysterious "bit 9" */ +#define CR_ST (1 << 5) /* AC97 link start bit */ + +#define CSAR_RD (1 << 19) /* AC97 data read bit */ +#define CSAR_WR (0) + +#define TSR_CMDAMT (1 << 31) +#define TSR_CMDDMT (1 << 30) + +#define RSR_STARY (1 << 22) +#define RSR_STDRY (1 << 21) + +#define ACR_DMARX16 (1 << 30) +#define ACR_DMATX16 (1 << 29) +#define ACR_TX12ATOM (1 << 26) +#define ACR_DMARX20 ((1 << 24) | (1 << 22)) +#define ACR_DMATX20 ((1 << 23) | (1 << 21)) + +#define CSDR_SHIFT 4 +#define CSDR_MASK (0xffff << CSDR_SHIFT) +#define CSAR_SHIFT 12 +#define CSAR_MASK (0x7f << CSAR_SHIFT) + +#define AC97_WRITE_RETRY 1 +#define AC97_READ_RETRY 5 + +/* manual-suggested AC97 codec access timeouts (us) */ +#define TMO_E1 500 /* 21 < E1 < 1000 */ +#define TMO_E2 13 /* 13 < E2 */ +#define TMO_E3 21 /* 21 < E3 */ +#define TMO_E4 500 /* 21 < E4 < 1000 */ + +struct hac_priv { + unsigned long mmio; /* HAC base address */ +} hac_cpu_data[] = { +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + { + .mmio = 0xFE240000, + }, + { + .mmio = 0xFE250000, + }, +#elif defined(CONFIG_CPU_SUBTYPE_SH7780) + { + .mmio = 0xFFE40000, + }, +#else +#error "Unsupported SuperH SoC" +#endif +}; + +#define HACREG(reg) (*(unsigned long *)(hac->mmio + (reg))) + +/* + * AC97 read/write flow as outlined in the SH7760 manual (pages 903-906) + */ +static int hac_get_codec_data(struct hac_priv *hac, unsigned short r, + unsigned short *v) +{ + unsigned int to1, to2, i; + unsigned short adr; + + for (i = 0; i < AC97_READ_RETRY; ++i) { + *v = 0; + /* wait for HAC to receive something from the codec */ + for (to1 = TMO_E4; + to1 && !(HACREG(HACRSR) & RSR_STARY); + --to1) + udelay(1); + for (to2 = TMO_E4; + to2 && !(HACREG(HACRSR) & RSR_STDRY); + --to2) + udelay(1); + + if (!to1 && !to2) + return 0; /* codec comm is down */ + + adr = ((HACREG(HACCSAR) & CSAR_MASK) >> CSAR_SHIFT); + *v = ((HACREG(HACCSDR) & CSDR_MASK) >> CSDR_SHIFT); + + HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY); + + if (r == adr) + break; + + /* manual says: wait at least 21 usec before retrying */ + udelay(21); + } + HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY); + return (i < AC97_READ_RETRY); +} + +static unsigned short hac_read_codec_aux(struct hac_priv *hac, + unsigned short reg) +{ + unsigned short val; + unsigned int i, to; + + for (i = 0; i < AC97_READ_RETRY; i++) { + /* send_read_request */ + local_irq_disable(); + HACREG(HACTSR) &= ~(TSR_CMDAMT); + HACREG(HACCSAR) = (reg << CSAR_SHIFT) | CSAR_RD; + local_irq_enable(); + + for (to = TMO_E3; + to && !(HACREG(HACTSR) & TSR_CMDAMT); + --to) + udelay(1); + + HACREG(HACTSR) &= ~TSR_CMDAMT; + val = 0; + if (hac_get_codec_data(hac, reg, &val) != 0) + break; + } + + if (i == AC97_READ_RETRY) + return ~0; + + return val; +} + +static void hac_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + unsigned int i, to; + /* write_codec_aux */ + for (i = 0; i < AC97_WRITE_RETRY; i++) { + /* send_write_request */ + local_irq_disable(); + HACREG(HACTSR) &= ~(TSR_CMDDMT | TSR_CMDAMT); + HACREG(HACCSDR) = (val << CSDR_SHIFT); + HACREG(HACCSAR) = (reg << CSAR_SHIFT) & (~CSAR_RD); + local_irq_enable(); + + /* poll-wait for CMDAMT and CMDDMT */ + for (to = TMO_E1; + to && !(HACREG(HACTSR) & (TSR_CMDAMT|TSR_CMDDMT)); + --to) + udelay(1); + + HACREG(HACTSR) &= ~(TSR_CMDAMT | TSR_CMDDMT); + if (to) + break; + /* timeout, try again */ + } +} + +static unsigned short hac_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + return hac_read_codec_aux(hac, reg); +} + +static void hac_ac97_warmrst(struct snd_ac97 *ac97) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + unsigned int tmo; + + HACREG(HACCR) = CR_WMRT | CR_ST | CR_B9; + msleep(10); + HACREG(HACCR) = CR_ST | CR_B9; + for (tmo = 1000; (tmo > 0) && !(HACREG(HACCR) & CR_CR); tmo--) + udelay(1); + + if (!tmo) + printk(KERN_INFO "hac: reset: AC97 link down!\n"); + /* settings this bit lets us have a conversation with codec */ + HACREG(HACACR) |= ACR_TX12ATOM; +} + +static void hac_ac97_coldrst(struct snd_ac97 *ac97) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac; + hac = &hac_cpu_data[unit_id]; + + HACREG(HACCR) = 0; + HACREG(HACCR) = CR_CDRT | CR_ST | CR_B9; + msleep(10); + hac_ac97_warmrst(ac97); +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = hac_ac97_read, + .write = hac_ac97_write, + .reset = hac_ac97_coldrst, + .warm_reset = hac_ac97_warmrst, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static int hac_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hac_priv *hac = &hac_cpu_data[rtd->dai->cpu_dai->id]; + int d = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + + switch (params->msbits) { + case 16: + HACREG(HACACR) |= d ? ACR_DMARX16 : ACR_DMATX16; + HACREG(HACACR) &= d ? ~ACR_DMARX20 : ~ACR_DMATX20; + break; + case 20: + HACREG(HACACR) &= d ? ~ACR_DMARX16 : ~ACR_DMATX16; + HACREG(HACACR) |= d ? ACR_DMARX20 : ACR_DMATX20; + break; + default: + pr_debug("hac: invalid depth %d bit\n", params->msbits); + return -EINVAL; + break; + } + + return 0; +} + +#define AC97_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define AC97_FMTS \ + SNDRV_PCM_FMTBIT_S16_LE + +struct snd_soc_cpu_dai sh4_hac_dai[] = { +{ + .name = "HAC0", + .id = 0, + .type = SND_SOC_DAI_AC97, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = { + .hw_params = hac_hw_params, + }, +}, +#ifdef CONFIG_CPU_SUBTYPE_SH7760 +{ + .name = "HAC1", + .id = 1, + .type = SND_SOC_DAI_AC97, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = { + .hw_params = hac_hw_params, + }, + +}, +#endif +}; +EXPORT_SYMBOL_GPL(sh4_hac_dai); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SuperH onchip HAC (AC97) audio driver"); +MODULE_AUTHOR("Manuel Lauss "); diff --git a/sound/soc/sh/sh7760-ac97.c b/sound/soc/sh/sh7760-ac97.c new file mode 100644 index 000000000000..5563f14511fa --- /dev/null +++ b/sound/soc/sh/sh7760-ac97.c @@ -0,0 +1,92 @@ +/* + * Generic AC97 sound support for SH7760 + * + * (c) 2007 Manuel Lauss + * + * Licensed under the GPLv2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/ac97.h" + +#define IPSEL 0xFE400034 + +/* platform specific structs can be declared here */ +extern struct snd_soc_cpu_dai sh4_hac_dai[2]; +extern struct snd_soc_platform sh7760_soc_platform; + +static int machine_init(struct snd_soc_codec *codec) +{ + snd_soc_dapm_sync_endpoints(codec); + return 0; +} + +static struct snd_soc_dai_link sh7760_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &sh4_hac_dai[0], /* HAC0 */ + .codec_dai = &ac97_dai, + .init = machine_init, + .ops = NULL, +}; + +static struct snd_soc_machine sh7760_ac97_soc_machine = { + .name = "SH7760 AC97", + .dai_link = &sh7760_ac97_dai, + .num_links = 1, +}; + +static struct snd_soc_device sh7760_ac97_snd_devdata = { + .machine = &sh7760_ac97_soc_machine, + .platform = &sh7760_soc_platform, + .codec_dev = &soc_codec_dev_ac97, +}; + +static struct platform_device *sh7760_ac97_snd_device; + +static int __init sh7760_ac97_init(void) +{ + int ret; + unsigned short ipsel; + + /* enable both AC97 controllers in pinmux reg */ + ipsel = ctrl_inw(IPSEL); + ctrl_outw(ipsel | (3 << 10), IPSEL); + + ret = -ENOMEM; + sh7760_ac97_snd_device = platform_device_alloc("soc-audio", -1); + if (!sh7760_ac97_snd_device) + goto out; + + platform_set_drvdata(sh7760_ac97_snd_device, + &sh7760_ac97_snd_devdata); + sh7760_ac97_snd_devdata.dev = &sh7760_ac97_snd_device->dev; + ret = platform_device_add(sh7760_ac97_snd_device); + + if (ret) + platform_device_put(sh7760_ac97_snd_device); + +out: + return ret; +} + +static void __exit sh7760_ac97_exit(void) +{ + platform_device_unregister(sh7760_ac97_snd_device); +} + +module_init(sh7760_ac97_init); +module_exit(sh7760_ac97_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Generic SH7760 AC97 sound machine"); +MODULE_AUTHOR("Manuel Lauss "); diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c new file mode 100644 index 000000000000..b72bc316cb8e --- /dev/null +++ b/sound/soc/sh/ssi.c @@ -0,0 +1,400 @@ +/* + * Serial Sound Interface (I2S) support for SH7760/SH7780 + * + * Copyright (c) 2007 Manuel Lauss + * + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * dont forget to set IPSEL/OMSEL register bits (in your board code) to + * enable SSI output pins! + */ + +/* + * LIMITATIONS: + * The SSI unit has only one physical data line, so full duplex is + * impossible. This can be remedied on the SH7760 by using the + * other SSI unit for recording; however the SH7780 has only 1 SSI + * unit, and its pins are shared with the AC97 unit, among others. + * + * FEATURES: + * The SSI features "compressed mode": in this mode it continuously + * streams PCM data over the I2S lines and uses LRCK as a handshake + * signal. Can be used to send compressed data (AC3/DTS) to a DSP. + * The number of bits sent over the wire in a frame can be adjusted + * and can be independent from the actual sample bit depth. This is + * useful to support TDM mode codecs like the AD1939 which have a + * fixed TDM slot size, regardless of sample resolution. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SSICR 0x00 +#define SSISR 0x04 + +#define CR_DMAEN (1 << 28) +#define CR_CHNL_SHIFT 22 +#define CR_CHNL_MASK (3 << CR_CHNL_SHIFT) +#define CR_DWL_SHIFT 19 +#define CR_DWL_MASK (7 << CR_DWL_SHIFT) +#define CR_SWL_SHIFT 16 +#define CR_SWL_MASK (7 << CR_SWL_SHIFT) +#define CR_SCK_MASTER (1 << 15) /* bitclock master bit */ +#define CR_SWS_MASTER (1 << 14) /* wordselect master bit */ +#define CR_SCKP (1 << 13) /* I2Sclock polarity */ +#define CR_SWSP (1 << 12) /* LRCK polarity */ +#define CR_SPDP (1 << 11) +#define CR_SDTA (1 << 10) /* i2s alignment (msb/lsb) */ +#define CR_PDTA (1 << 9) /* fifo data alignment */ +#define CR_DEL (1 << 8) /* delay data by 1 i2sclk */ +#define CR_BREN (1 << 7) /* clock gating in burst mode */ +#define CR_CKDIV_SHIFT 4 +#define CR_CKDIV_MASK (7 << CR_CKDIV_SHIFT) /* bitclock divider */ +#define CR_MUTE (1 << 3) /* SSI mute */ +#define CR_CPEN (1 << 2) /* compressed mode */ +#define CR_TRMD (1 << 1) /* transmit/receive select */ +#define CR_EN (1 << 0) /* enable SSI */ + +#define SSIREG(reg) (*(unsigned long *)(ssi->mmio + (reg))) + +struct ssi_priv { + unsigned long mmio; + unsigned long sysclk; + int inuse; +} ssi_cpu_data[] = { +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + { + .mmio = 0xFE680000, + }, + { + .mmio = 0xFE690000, + }, +#elif defined(CONFIG_CPU_SUBTYPE_SH7780) + { + .mmio = 0xFFE70000, + }, +#else +#error "Unsupported SuperH SoC" +#endif +}; + +/* + * track usage of the SSI; it is simplex-only so prevent attempts of + * concurrent playback + capture. FIXME: any locking required? + */ +static int ssi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + if (ssi->inuse) { + pr_debug("ssi: already in use!\n"); + return -EBUSY; + } else + ssi->inuse = 1; + return 0; +} + +static void ssi_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + + ssi->inuse = 0; +} + +static int ssi_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + SSIREG(SSICR) |= CR_DMAEN | CR_EN; + break; + case SNDRV_PCM_TRIGGER_STOP: + SSIREG(SSICR) &= ~(CR_DMAEN | CR_EN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ssi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + unsigned long ssicr = SSIREG(SSICR); + unsigned int bits, channels, swl, recv, i; + + channels = params_channels(params); + bits = params->msbits; + recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1; + + pr_debug("ssi_hw_params() enter\nssicr was %08lx\n", ssicr); + pr_debug("bits: %d channels: %d\n", bits, channels); + + ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA | + CR_SWL_MASK); + + /* direction (send/receive) */ + if (!recv) + ssicr |= CR_TRMD; /* transmit */ + + /* channels */ + if ((channels < 2) || (channels > 8) || (channels & 1)) { + pr_debug("ssi: invalid number of channels\n"); + return -EINVAL; + } + ssicr |= ((channels >> 1) - 1) << CR_CHNL_SHIFT; + + /* DATA WORD LENGTH (DWL): databits in audio sample */ + i = 0; + switch (bits) { + case 32: ++i; + case 24: ++i; + case 22: ++i; + case 20: ++i; + case 18: ++i; + case 16: ++i; + ssicr |= i << CR_DWL_SHIFT; + case 8: break; + default: + pr_debug("ssi: invalid sample width\n"); + return -EINVAL; + } + + /* + * SYSTEM WORD LENGTH: size in bits of half a frame over the I2S + * wires. This is usually bits_per_sample x channels/2; i.e. in + * Stereo mode the SWL equals DWL. SWL can be bigger than the + * product of (channels_per_slot x samplebits), e.g. for codecs + * like the AD1939 which only accept 32bit wide TDM slots. For + * "standard" I2S operation we set SWL = chans / 2 * DWL here. + * Waiting for ASoC to get TDM support ;-) + */ + if ((bits > 16) && (bits <= 24)) { + bits = 24; /* these are padded by the SSI */ + /*ssicr |= CR_PDTA;*/ /* cpu/data endianness ? */ + } + i = 0; + swl = (bits * channels) / 2; + switch (swl) { + case 256: ++i; + case 128: ++i; + case 64: ++i; + case 48: ++i; + case 32: ++i; + case 16: ++i; + ssicr |= i << CR_SWL_SHIFT; + case 8: break; + default: + pr_debug("ssi: invalid system word length computed\n"); + return -EINVAL; + } + + SSIREG(SSICR) = ssicr; + + pr_debug("ssi_hw_params() leave\nssicr is now %08lx\n", ssicr); + return 0; +} + +static int ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct ssi_priv *ssi = &ssi_cpu_data[cpu_dai->id]; + + ssi->sysclk = freq; + + return 0; +} + +/* + * This divider is used to generate the SSI_SCK (I2S bitclock) from the + * clock at the HAC_BIT_CLK ("oversampling clock") pin. + */ +static int ssi_set_clkdiv(struct snd_soc_cpu_dai *dai, int did, int div) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + unsigned long ssicr; + int i; + + i = 0; + ssicr = SSIREG(SSICR) & ~CR_CKDIV_MASK; + switch (div) { + case 16: ++i; + case 8: ++i; + case 4: ++i; + case 2: ++i; + SSIREG(SSICR) = ssicr | (i << CR_CKDIV_SHIFT); + case 1: break; + default: + pr_debug("ssi: invalid sck divider %d\n", div); + return -EINVAL; + } + + return 0; +} + +static int ssi_set_fmt(struct snd_soc_cpu_dai *dai, unsigned int fmt) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + unsigned long ssicr = SSIREG(SSICR); + + pr_debug("ssi_set_fmt()\nssicr was 0x%08lx\n", ssicr); + + ssicr &= ~(CR_DEL | CR_PDTA | CR_BREN | CR_SWSP | CR_SCKP | + CR_SWS_MASTER | CR_SCK_MASTER); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_RIGHT_J: + ssicr |= CR_DEL | CR_PDTA; + break; + case SND_SOC_DAIFMT_LEFT_J: + ssicr |= CR_DEL; + break; + default: + pr_debug("ssi: unsupported format\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CONT: + break; + case SND_SOC_DAIFMT_GATED: + ssicr |= CR_BREN; + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + ssicr |= CR_SCKP; /* sample data at low clkedge */ + break; + case SND_SOC_DAIFMT_NB_IF: + ssicr |= CR_SCKP | CR_SWSP; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + ssicr |= CR_SWSP; /* word select starts low */ + break; + default: + pr_debug("ssi: invalid inversion\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFM: + ssicr |= CR_SCK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: + ssicr |= CR_SWS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ssicr |= CR_SWS_MASTER | CR_SCK_MASTER; + break; + default: + pr_debug("ssi: invalid master/slave configuration\n"); + return -EINVAL; + } + + SSIREG(SSICR) = ssicr; + pr_debug("ssi_set_fmt() leave\nssicr is now 0x%08lx\n", ssicr); + + return 0; +} + +/* the SSI depends on an external clocksource (at HAC_BIT_CLK) even in + * Master mode, so really this is board specific; the SSI can do any + * rate with the right bitclk and divider settings. + */ +#define SSI_RATES \ + SNDRV_PCM_RATE_8000_192000 + +/* the SSI can do 8-32 bit samples, with 8 possible channels */ +#define SSI_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) + +struct snd_soc_cpu_dai sh4_ssi_dai[] = { +{ + .name = "SSI0", + .id = 0, + .type = SND_SOC_DAI_I2S, + .playback = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .capture = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .ops = { + .startup = ssi_startup, + .shutdown = ssi_shutdown, + .trigger = ssi_trigger, + .hw_params = ssi_hw_params, + }, + .dai_ops = { + .set_sysclk = ssi_set_sysclk, + .set_clkdiv = ssi_set_clkdiv, + .set_fmt = ssi_set_fmt, + }, +}, +#ifdef CONFIG_CPU_SUBTYPE_SH7760 +{ + .name = "SSI1", + .id = 1, + .type = SND_SOC_DAI_I2S, + .playback = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .capture = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .ops = { + .startup = ssi_startup, + .shutdown = ssi_shutdown, + .trigger = ssi_trigger, + .hw_params = ssi_hw_params, + }, + .dai_ops = { + .set_sysclk = ssi_set_sysclk, + .set_clkdiv = ssi_set_clkdiv, + .set_fmt = ssi_set_fmt, + }, +}, +#endif +}; +EXPORT_SYMBOL_GPL(sh4_ssi_dai); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SuperH onchip SSI (I2S) audio driver"); +MODULE_AUTHOR("Manuel Lauss "); diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c index 8ebc1adb5ed9..7bd5852fcc0d 100644 --- a/sound/usb/usbaudio.c +++ b/sound/usb/usbaudio.c @@ -2350,7 +2350,9 @@ static int is_big_endian_format(struct snd_usb_audio *chip, struct audioformat * return 1; break; case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ - return 1; + if (device_setup[chip->index] == 0x00 || + fp->altsetting==1 || fp->altsetting==2 || fp->altsetting==3) + return 1; } return 0; } @@ -2530,7 +2532,18 @@ static int parse_audio_format_i(struct snd_usb_audio *chip, struct audioformat * * but we give normal PCM format to get the existing * apps working... */ - pcm_format = SNDRV_PCM_FORMAT_S16_LE; + switch (chip->usb_id) { + + case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ + if (device_setup[chip->index] == 0x00 && + fp->altsetting == 6) + pcm_format = SNDRV_PCM_FORMAT_S16_BE; + else + pcm_format = SNDRV_PCM_FORMAT_S16_LE; + break; + default: + pcm_format = SNDRV_PCM_FORMAT_S16_LE; + } } else { pcm_format = parse_audio_format_i_type(chip, fp, format, fmt); if (pcm_format < 0) @@ -3251,6 +3264,11 @@ static int snd_usb_cm106_boot_quirk(struct usb_device *dev) static int audiophile_skip_setting_quirk(struct snd_usb_audio *chip, int iface, int altno) { + /* Reset ALL ifaces to 0 altsetting. + * Call it for every possible altsetting of every interface. + */ + usb_set_interface(chip->dev, iface, 0); + if (device_setup[chip->index] & AUDIOPHILE_SET) { if ((device_setup[chip->index] & AUDIOPHILE_SET_DTS) && altno != 6) diff --git a/sound/usb/usbquirks.h b/sound/usb/usbquirks.h index 374fbf657a2d..5a2f518c6629 100644 --- a/sound/usb/usbquirks.h +++ b/sound/usb/usbquirks.h @@ -52,6 +52,24 @@ .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL }, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08ae, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08c6, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL +}, { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_CLASS | @@ -1051,7 +1069,15 @@ YAMAHA_DEVICE(0x7010, "UB99"), .type = QUIRK_MIDI_STANDARD_INTERFACE } }, - /* TODO: add Roland EXR support */ +{ + USB_DEVICE(0x0582, 0x0060), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "EXR Series", + .ifnum = 0, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, { /* has ID 0x0067 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0065), @@ -1094,6 +1120,19 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, +{ + USB_DEVICE(0x582, 0x00a6), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "Juno-G", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, { /* * This quirk is for the "Advanced" modes of the Edirol UA-25. * If the switch is not in an advanced setting, the UA-25 has @@ -1230,6 +1269,37 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, /* TODO: add Edirol MD-P1 support */ +{ + /* Roland SH-201 */ + USB_DEVICE(0x0582, 0x00ad), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SH-201", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, /* Guillemot devices */ { diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c index 0a352e46862f..48e9aa3f18c9 100644 --- a/sound/usb/usx2y/usbusx2yaudio.c +++ b/sound/usb/usx2y/usbusx2yaudio.c @@ -935,10 +935,9 @@ static struct snd_pcm_ops snd_usX2Y_pcm_ops = */ static void usX2Y_audio_stream_free(struct snd_usX2Y_substream **usX2Y_substream) { - if (NULL != usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]) { - kfree(usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]); - usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK] = NULL; - } + kfree(usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]); + usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK] = NULL; + kfree(usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE]); usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE] = NULL; }