GIT 549681c3f62dc0a75be0291056c6193c7c2a7295 git+ssh://master.kernel.org/pub/scm/linux/kernel/git/perex/alsa.git#mm commit Author: Takashi Iwai Date: Wed May 30 12:46:21 2007 +0200 [ALSA] rme9652 - Fix the hw_pointer check The negative check in hw_pointer callback doesn't work because the value is unsigned. Cast to int in the comparison to fix this. Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 62234bf86340b0e694afcbebe8e30dcff6621075 Author: Takashi Iwai Date: Wed May 30 12:42:31 2007 +0200 [ALSA] ali5451 - Fix invalid type of codec->irq field The irq field of struct snd_ali shouldn't be unsigned since it's uninitialized value is -1. Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 2c5fe2a1cb6da51d5ef19cb52322ff2d7d6e2d49 Author: Takashi Iwai Date: Tue May 29 19:01:37 2007 +0200 [ALSA] hda-codec - Fix STAC922x capture boost level STAC922x provides the capture boost level up to 4, but actually it works only up to 2. Since the range of the mixer is automatically defined from amp-capability bits, we need to override the value beforehand. snd_hda_override_amp_caps() is introduced for this purpose. The function patch_stac922x() calls this for NID 0x12 (Mux Capture Volume). This should fix another recording problem on Intel Macs. Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit fd5a2a6ddbc0fed0c08f295074505f6084de483b Author: Takashi Iwai Date: Tue May 29 18:01:06 2007 +0200 [ALSA] hda-codec - Fix input with STAC92xx The recent fix for STAC92xx surround outputs broke the input pin setting for shared line-in and mic jacks. This patch fixes the breakage. Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 7619ebfc4ab84e7fe1e977499f3e33173bc7e9f5 Author: Ivan N. Zlatev Date: Tue May 29 16:03:00 2007 +0200 [ALSA] hda-codec - Fix pin configs for Intel Macs * adds the pinconfigs for all 5 Apple boards and 14 Subsystem IDs (support for possibly all iMac, Mac, MacMini etc etc) * adds 'intel-mac-v1' to v5 models which replace the current * reflects changes in Alsa-Configuration.txt Signed-off-by: Ivan N. Zlatev Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit df211e0c2539a242f8d31385a38674f44a7a4980 Author: Steve Longerbeam Date: Tue May 29 14:36:17 2007 +0200 [ALSA] hda-codec - Fix pin configs for Gateway MX6453 Fix pin default configs for speaker associations and sequence for Gateway MX6453 machine with STAC925x codecs. Signed-off-by: Steve Longerbeam Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 26edea5973d02fe8d3d35bff7533afd9c2f24113 Author: Ash Willis Date: Tue May 29 14:34:17 2007 +0200 [ALSA] Disable debugging output for the ALS300 driver Disables debugging output in the ALS300 driver. Also contains a whitespace cleanup and a fix for a potential bug. Signed-off-by: Ash Willis Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit a9d768b1eb3dafdf302998b98d89649e9a002b2a Author: Tobin Davis Date: Tue May 29 14:29:08 2007 +0200 [ALSA] hda-codec - Add support for MSI K9N Ultra This patch adds the MSI K9N Ultra system to the realtek patch. Signed-off-by: Tobin Davis Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 15c44cdc9e28d2b111488cbff14e0f616bf8223f Author: Daniel T Chen Date: Tue May 29 03:41:55 2007 -0400 [ALSA] hda-codec - Add quirk for Supermicro PDSBA to alc883_cfg_tbl[] Tested and verified in #alsa/Freenode on Tuesday, May 29, 2007. Signed-off-by: Daniel T Chen Signed-off-by: crimsun@Box.ncat.edu Signed-off-by: Jaroslav Kysela commit 9701fe9bc2c8b1c8d77087805b8ffc25d9fa8a29 Author: Baruch Even Date: Fri May 25 12:18:49 2007 +0200 [ALSA] hda-codec - Add quirk for MSI S420 Add a quirk for MSI S420 (based on a guess work). From: Baruch Even Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit f5a040ac9a0395bd3497a80db8790d8450be93a9 Author: Takashi Iwai Date: Fri May 25 11:50:33 2007 +0200 [ALSA] Fix SB-module dependency with PCI drivers A few PCI drivers like ALS4000 and CS5530 require the SB16-codes. This patch fixes / improves the dependency between SB modules and PCI drivers. Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 4efd7aaaff9403be8e2cd966254a09f080add75a Author: Takashi Iwai Date: Thu May 24 18:46:54 2007 +0200 [ALSA] Add support for Cyrix/NatSemi Geode CS5530 (VSA1) Add support for Cyrix/NatSemi Geode SC5530 (VSA1). The driver is snd-cs5530. Signed-off-by Ash Willis Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 334be813276ddce703d9eb71edf0a9e23f58c7cd Author: Takashi Iwai Date: Wed May 23 16:27:32 2007 +0200 [ALSA] hda-codec - Fix wrong mixer controls for AD1984 thinkpad model Fixed the wrong mixer controls for AD1984 thinkpad model. Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 8b056d008cae2397ac00e66d39477a4c730f9d51 Author: Zoltan Devai Date: Tue May 22 16:17:05 2007 +0200 [ALSA] Fix ASoC s3c24xx-pcm spinlock bug This should fix a spinlock lockup bug on the s3c24xx arch. From: Zoltan Devai Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit af63f3dde975477b3560c7956dffcf302b17fe8a Author: Takashi Iwai Date: Mon May 21 12:41:29 2007 +0200 [ALSA] hda-codec - Add support of newer version of Intel iMac Added the pin configs for newer version of Intel iMac. The information provided by Ivan N. Zlatev . Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit aeff95f35af1ac9882bde6299bc3321f580e886e Author: Daniel Drake Date: Mon May 21 12:27:57 2007 +0200 [ALSA] hda-intel: fix ASUS M2V detection Commit f32610edab47f36946d23b883aeae91e15986121 added ALC660VD support, but this caused a 2.6.21 regression for some users. The ASUS M2V device is now detected as ALC660VD rather than ALC660/861 but the PCI quirk was not carried over. This patch allows affected users to use audio again. http://bugzilla.kernel.org/show_bug.cgi?id=8273 https://bugs.gentoo.org/show_bug.cgi?id=178243 Signed-off-by: Daniel Drake Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 37caff727a7d04aae25deac8f4470f6001f38ca1 Author: Pavel Hofman Date: Sat May 19 17:21:04 2007 +0200 [ALSA] ice1724 - Add PCM Playback Switch to Revo 7.1 This patch adds the support of mute for front channels of M-Audio Revolution 7.1 (the DAC AK4381 features a mute bit). Signed-off-by: Pavel Hofman Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 43982a6889335f3addc05657a2a9b6a310f1ee88 Author: Takashi Iwai Date: Sat May 19 17:06:42 2007 +0200 [ALSA] Add description about probe_mask option for snd-hda-intel Added a brief description about probe_mask option for snd-hda-intel. Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit fcf8284cd43656d2711d811ef85a62fb4a3a8f38 Author: Takashi Iwai Date: Sat May 19 16:30:35 2007 +0200 [ALSA] ali5451 - Fix possible NULL dereference Reported by Eric Sesterhenn. Fix the wrong checks of extra voice pointer, which may cause NULL dereferences. Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 3a04122bcfcb54b0f53e3919f0e940372093c2ac Author: Takashi Iwai Date: Fri May 18 18:21:41 2007 +0200 [ALSA] hda-codec - Add AD1884 / AD1984 codec support Added the support of AD1884 and AD1984 codec chips. Also experimental quirks for Thinkpad T61/X61 laptops with AD1984. Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 21f564d0ecea0580da77ef7235d1602fb8829185 Author: Christian Rothlaender Date: Fri May 18 16:55:26 2007 +0200 [ALSA] hda-codec - Add support for ASUS A8J modem This patch adds support for the ASUS A8J Series 56k Modem (Motorola SM56) Signed-off-by: Christian Rothlaender Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 78d40bd8ca70ec375aef5daa8f0265031c8e52d5 Author: Adrian McMenamin Date: Fri May 18 14:26:59 2007 +0200 [ALSA] Add ALSA support for the SEGA Dreamcast PCM device ALSA support for the SEGA Dreamcast Yamaha AICA sound device (pcm) This patch adds ALSA sound support for pcm playback on two channels on the SEGA Dreamcast built-in sound device (the Yamaha AICA) Add driver for the AICA sound device built into the SEGA Dreamcast Hook it all up with the build system. Signed-off-by: Adrian McMenamin Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 27e437cc1cb7edee27765726f4db815520bccb79 Author: Tobin Davis Date: Thu May 17 09:40:47 2007 +0200 [ALSA] HDA: Fix headphone mute issue on non-eapd Conexant systems This patch fixes an automute code issue for systems that do not rely on eapd support. Signed-off-by: Tobin Davis Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 7031521219be28854eb2cd1c36806b361d2446ec Author: Tobin Davis Date: Thu May 17 09:38:24 2007 +0200 [ALSA] HDA: Add more systems to Sigmatel codec This patch adds more Dell systems and a Panasonic laptop with STAC9200 codecs. Signed-off-by: Tobin Davis Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 36b912b3fcf9383e99ca64e503b93697135e3a1f Author: Tobin Davis Date: Thu May 17 09:36:34 2007 +0200 [ALSA] HDA: Add support for Gateway NX860 This patch adds support for the Gateway NX860 system. Signed-off-by: Tobin Davis Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 422eac90cda58489fc4dcbb7d0a6ee2004652b8f Author: Thomas Reitmayr Date: Tue May 15 11:47:48 2007 +0200 [ALSA] usbaudio - Coping with short replies in usbmixer This patch makes sure that short USB replies are treated as an error when requesting the value of a certain mixer control. Signed-off-by: Thomas Reitmayr Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 743365981b9bb6769f346e1331a906a0b5cbf308 Author: Daniel T Chen Date: Tue May 15 11:46:23 2007 +0200 [ALSA] Include quirks from Ubuntu Dapper/Edgy/Feisty Included is a patch against current alsa-kernel hg tip adding quirks that Ubuntu Dapper/Edgy/Feisty contains. Signed-off-by: Daniel T Chen Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 27f03676ee94b0473bc067a7eee6575b9517a1af Author: Rene Herman Date: Tue May 15 11:42:56 2007 +0200 [ALSA] Fix probe of non-PnP ISA devices isa_register_driver() returns an error if no device is found and it's no fatal error for the drivers with pnp support. Signed-off-by: Rene Herman Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 4d8643dd6e294e4e931102887fb97c2a855e0345 Author: Manuel Lauss Date: Mon May 14 18:40:07 2007 +0200 [ALSA] SH7760 ASoC support ALSA ASoC support for SH7760 This patch adds ALSA ASoC drivers for the Audio interfaces of the SH7760 SoC: Add driver for the SH7760 DMA engine (dmabrg) Add AC97 driver for HAC unit(s) found on SH7760/SH7780 Add I2S driver for SSI unit(s) found on SH7760/SH7780 Add a generic SH7760-AC97 machine driver. Hook it all up with the build system. Signed-off-by: Manuel Lauss Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit d184de2b7f082f7f47ebe2671489f88702ccd417 Author: Graeme Gregory Date: Mon May 14 11:06:11 2007 +0200 [ALSA] ASoC S3C24xx machine drivers - Kconfig This patch adds Kconfig and build support for the Neo1973, SMDK2443 and S3C2443 AC97 ALSA audio drivers. Signed-off-by: Graeme Gregory Signed-off-by: Liam Girdwood Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 1b7dd1329b9db3d4d83a29f12dc7933a7cb0bc09 Author: Liam Girdwood Date: Mon May 14 11:05:09 2007 +0200 [ALSA] ASoC S3C24xx machine drivers - I2C ID for LM4857 This patch adds I2C ID for the LM4857 audio amp and corrects the spacing of the WM8731, WM8750 and WM8753 ID's. Signed-off-by: Liam Girdwood Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 0addc0e1aa5724be84161d41368fe399fcdd76d5 Author: Graeme Gregory Date: Mon May 14 11:04:34 2007 +0200 [ALSA] ASoC S3C24xx machine drivers - SMDK 2443 This patch adds ALSA support for the SMDK2443 reference board. Signed-off-by: Graeme Gregory Signed-off-by: Liam Girdwood Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit e4a3658c3fc9645980fc4b510b49b14e7846bfc7 Author: Graeme Gregory Date: Mon May 14 11:03:52 2007 +0200 [ALSA] ASoC S3C24xx machine drivers - Openmoko Neo1973 This patch adds ALSA support for the Openmoko Neo1973 phone. Features:- * HiFi Playback and capture. * Phone calls supported. * Support for BT PCM in WM8753 voice interface. * Support for LM4857 audio amp. Signed-off-by: Graeme Gregory Signed-off-by: Liam Girdwood Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 7010db4ebfe475a4d8bc524f9897592deb7e4ad1 Author: Graeme Gregory Date: Mon May 14 11:02:51 2007 +0200 [ALSA] ASoC S3C24xx machine drivers - s3c2443-AC97 This patch adds AC97 support to the Samsung S3C2443 CPU. Signed-off-by: Graeme Gregory Signed-off-by: Liam Girdwood Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 93bca5bd00177874dafab40b516a34f0467dd168 Author: Kailang Yang Date: Mon May 14 11:00:38 2007 +0200 [ALSA] hda-codec - Fix ALC882/861VD codec support on some laptops Fixed some platform no sound and Add JD Function for below platform list: Sony(0x104d) 0x9015, 0x900e, 0x1f00 Toshiba(0x1179) DALLAS 0xff00, 0xff01 Targa(0x1462) 0x28fb Asus(0x1043) A7J 0x060d Lenovo(0x17aa) 0x3bfd, 0x2085 MEDION(0x17c0) MD2 0x4071 Signed-off-by: Kailang Yang Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit c5f2af3258a1d11361b6c1eda94bc7ce0b44a584 Author: Richard Knutsson Date: Mon May 14 10:38:57 2007 +0200 [ALSA] usbusx2yaudio: kfree(NULL) is valid if (!x) kfree(x); is not needed since kfree(NULL) is valid. Signed-off-by: Richard Knutsson Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 28afa1185f4b621a1b9cc83569668468558a2cdb Author: Liam Girdwood Date: Thu May 10 19:27:27 2007 +0200 [ALSA] ASoC AC97 device reg bugfix This patch fixes a bug whereby AC97 bus device data was being clobbered when AC97 codecs using the generic ac97_codec.c driver were being registered. Codecs that didn't use the generic driver were unaffected (e.g. WM9712, WM9713). Changes:- o Add new AC97 codec class for custom (or need bus dev registration) AC97 codecs. o Only register/deregister this custom codec device with the AC97 bus. The generic AC97 driver already does this for generic codec devices. This may be related to bug #3038 :- https://bugtrack.alsa-project.org/alsa-bug/view.php?id=3038 Signed-off-by: Liam Girdwood Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit daa0d36e8a010cc0437e1ccca2eb61fc3106eb28 Author: Liam Girdwood Date: Thu May 10 17:12:31 2007 +0200 [ALSA] ASoC AC97 static GPL symbol fix This patch fixes a bug whereby the GPL'ed symbol ac97_dai was being declared as static. Signed-off-by: Liam Girdwood Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 4937eb1a073410eb43617a39db19b642f6783218 Author: Takashi Iwai Date: Thu May 10 16:56:09 2007 +0200 [ALSA] hda-codec - Make the mixer capability check more robust In some cases, mixer elements return -EINVAL because it couldn't obtain proper amp_cap bits. The patch improves the robustness, trying the amp_cap query again in such a case. Signed-off-by: Takashi Iwai Signed-off-by: Jaroslav Kysela commit 9bfad60b25aa27a1db19cbc179682c56ce678cc5 Author: Daniel Drake Date: Thu May 10 08:52:19 2007 +0200 [ALSA] usb-audio: another Logitech QuickCam ID This patch adds the ID for another quickcam microphone, reported by freqmod on ALSA ticket #0003040 I'm going to submit a USB patch separately to provide a macro to simplify these entries, as suggested by Alan Stern. We could switch to using that in future. Signed-off-by: Daniel Drake Signed-off-by: Clemens Ladisch Signed-off-by: Jaroslav Kysela Documentation/sound/alsa/ALSA-Configuration.txt | 46 +- include/linux/i2c-id.h | 7 include/sound/ak4xxx-adda.h | 1 include/sound/sb.h | 1 sound/Kconfig | 2 sound/Makefile | 2 sound/i2c/other/ak4xxx-adda.c | 24 + sound/isa/Kconfig | 26 + sound/isa/sb/Makefile | 15 - sound/isa/sb/sb16_main.c | 10 sound/isa/sb/sb_common.c | 5 sound/isa/sb/sb_mixer.c | 3 sound/pci/Kconfig | 11 sound/pci/Makefile | 2 sound/pci/ali5451/ali5451.c | 10 sound/pci/als300.c | 7 sound/pci/cs5530.c | 306 ++++++++++ sound/pci/hda/hda_codec.c | 13 sound/pci/hda/hda_local.h | 2 sound/pci/hda/patch_analog.c | 343 ++++++++++++ sound/pci/hda/patch_conexant.c | 48 ++ sound/pci/hda/patch_realtek.c | 5 sound/pci/hda/patch_si3054.c | 2 sound/pci/hda/patch_sigmatel.c | 164 +++++- sound/pci/ice1712/revo.c | 7 sound/pci/rme9652/rme9652.c | 2 sound/sh/Kconfig | 14 sound/sh/Makefile | 8 sound/sh/aica.c | 675 +++++++++++++++++++++++ sound/sh/aica.h | 80 +++ sound/soc/Kconfig | 1 sound/soc/Makefile | 2 sound/soc/s3c24xx/Kconfig | 27 + sound/soc/s3c24xx/Makefile | 9 sound/soc/s3c24xx/neo1973_wm8753.c | 670 +++++++++++++++++++++++ sound/soc/s3c24xx/s3c2443-ac97.c | 401 ++++++++++++++ sound/soc/s3c24xx/s3c24xx-ac97.h | 25 + sound/soc/s3c24xx/s3c24xx-pcm.c | 2 sound/soc/s3c24xx/smdk2443_wm9710.c | 85 +++ sound/soc/sh/Kconfig | 39 + sound/soc/sh/Makefile | 14 sound/soc/sh/dma-sh7760.c | 354 ++++++++++++ sound/soc/sh/hac.c | 322 +++++++++++ sound/soc/sh/sh7760-ac97.c | 92 +++ sound/soc/sh/ssi.c | 400 ++++++++++++++ sound/usb/usx2y/usbusx2yaudio.c | 7 46 files changed, 4215 insertions(+), 76 deletions(-) diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 57b878c..ca3e6d5 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 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 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) @@ -864,12 +870,22 @@ Prior to version 0.9.0rc4 options had a allout 5-jack in back, 2-jack in front, SPDIF out auto auto-config reading BIOS (default) + 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,16 +923,23 @@ Prior to version 0.9.0rc4 options had a 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 + 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 m2-2 Some Gateway MX series laptops m6 Some Gateway NX series laptops + pa6 Gateway NX860 series STAC9227/9228/9229/927x ref Reference board @@ -955,6 +978,17 @@ Prior to version 0.9.0rc4 options had a 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/include/linux/i2c-id.h b/include/linux/i2c-id.h index aa83d41..b690148 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -115,9 +115,10 @@ #define I2C_DRIVERID_BT866 85 /* Conexan #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 aa49dda..fd0a6c4 100644 --- a/include/sound/ak4xxx-adda.h +++ b/include/sound/ak4xxx-adda.h @@ -43,6 +43,7 @@ #define AK4XXX_IMAGE_SIZE (AK4XXX_MAX_CH 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/sb.h b/include/sound/sb.h index 2dd5c8e..3ad854b 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/sound/Kconfig b/sound/Kconfig index 9ea4738..e48b9b3 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 b7c7fb7..3ead922 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/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index 8805110..fd33515 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_ 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 cf3803c..b90ddab 100644 --- a/sound/isa/Kconfig +++ b/sound/isa/Kconfig @@ -11,6 +11,19 @@ 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 + 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/sb/Makefile b/sound/isa/sb/Makefile index fd9d9c5..556e669 100644 --- a/sound/isa/sb/Makefile +++ b/sound/isa/sb/Makefile @@ -22,14 +22,13 @@ # 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 383911b..5d4d3aa 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 __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 __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 3094f38..efa9d5c 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 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 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 490b1ca..3d4befc 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 * 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 *c 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/pci/Kconfig b/sound/pci/Kconfig index 61e35ec..c6b4410 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 e06736d..cd76e02 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 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 e1ed595..5a84221 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; @@ -1250,7 +1250,7 @@ static int snd_ali_playback_hw_params(st evoice->substream = substream; } } else { - if (!evoice) { + if (evoice) { snd_ali_free_voice(codec, evoice); pvoice->extra = evoice = NULL; } @@ -1267,7 +1267,7 @@ static int snd_ali_playback_hw_free(stru struct snd_ali_voice *evoice = pvoice ? pvoice->extra : NULL; snd_pcm_lib_free_pages(substream); - if (!evoice) { + if (evoice) { snd_ali_free_voice(codec, evoice); pvoice->extra = NULL; } @@ -1356,7 +1356,7 @@ static int snd_ali_playback_prepare(stru VOL, CTRL, EC); - if (!evoice) { + if (evoice) { evoice->count = pvoice->count; evoice->eso = pvoice->count << 1; ESO = evoice->eso - 1; @@ -2343,7 +2343,7 @@ static int __devinit snd_ali_probe(struc 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 8afcb98..48cc39b 100644 --- a/sound/pci/als300.c +++ b/sound/pci/als300.c @@ -88,8 +88,8 @@ #define MUS_VOC_VOL 0x8E #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(s 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/cs5530.c b/sound/pci/cs5530.c new file mode 100644 index 0000000..240a0a4 --- /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/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 8e89d56..f87f8f0 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -713,6 +713,19 @@ static u32 query_amp_caps(struct hda_cod return info->amp_caps; } +int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, + unsigned int caps) +{ + struct hda_amp_info *info; + + info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, dir, 0)); + if (!info) + return -EINVAL; + info->amp_caps = caps; + info->status |= INFO_AMP_CAPS; + return 0; +} + /* * read the current volume to info * if the cache exists, read the cache value. diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index be12b88..f91ea5e 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -277,5 +277,7 @@ static inline u32 get_wcaps(struct hda_c return codec->wcaps[nid - codec->start_nid]; } +int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, + unsigned int caps); #endif /* __SOUND_HDA_LOCAL_H */ diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c index 0e1a879..4659fde 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 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; @@ -2775,11 +2776,347 @@ static int patch_ad1988(struct hda_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_OUTPUT), + HDA_CODEC_MUTE_IDX("Digital Mic Capture Switch", 1, 0x06, 0x0, + HDA_OUTPUT), + { } /* end */ +}; + +/* + * initialization verbs + */ +static struct hda_verb ad1884_init_verbs[] = { + /* DACs; mute as default */ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* 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 = 4, + .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("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)}, + { } /* 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; +} + + +/* * patch entries */ struct hda_codec_preset snd_hda_preset_analog[] = { + { .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_conexant.c b/sound/pci/hda/patch_conexant.c index a5a4b2b..bef214b 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -705,6 +705,17 @@ static struct snd_kcontrol_new cxt5045_t .get = conexant_mux_enum_get, .put = conexant_mux_enum_put, }, + /* Audio input controls */ + HDA_CODEC_VOLUME("Input-1 Volume", 0x1a, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Input-1 Switch", 0x1a, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Input-2 Volume", 0x1a, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Input-2 Switch", 0x1a, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Input-3 Volume", 0x1a, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("Input-3 Switch", 0x1a, 0x2, HDA_INPUT), + HDA_CODEC_VOLUME("Input-4 Volume", 0x1a, 0x3, HDA_INPUT), + HDA_CODEC_MUTE("Input-4 Switch", 0x1a, 0x3, HDA_INPUT), + HDA_CODEC_VOLUME("Input-5 Volume", 0x1a, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("Input-5 Switch", 0x1a, 0x4, HDA_INPUT), { } /* end */ }; @@ -947,6 +958,23 @@ static void cxt5047_hp_automute(struct h snd_hda_codec_amp_update(codec, 0x1c, 1, HDA_OUTPUT, 0, 0x80, bits); } +/* mute internal speaker if HP is plugged */ +static void cxt5047_hp2_automute(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + unsigned int bits; + + spec->hp_present = snd_hda_codec_read(codec, 0x13, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + + bits = spec->hp_present ? 0x80 : 0; + snd_hda_codec_amp_update(codec, 0x1d, 0, HDA_OUTPUT, 0, 0x80, bits); + snd_hda_codec_amp_update(codec, 0x1d, 1, HDA_OUTPUT, 0, 0x80, bits); + /* Mute/Unmute PCM 2 for good measure - some systems need this */ + snd_hda_codec_amp_update(codec, 0x1c, 0, HDA_OUTPUT, 0, 0x80, bits); + snd_hda_codec_amp_update(codec, 0x1c, 1, HDA_OUTPUT, 0, 0x80, bits); +} + /* toggle input of built-in and mic jack appropriately */ static void cxt5047_hp_automic(struct hda_codec *codec) { @@ -985,6 +1013,21 @@ static void cxt5047_hp_unsol_event(struc } } +/* unsolicited event for HP jack sensing - non-EAPD systems */ +static void cxt5047_hp2_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + res >>= 26; + switch (res) { + case CONEXANT_HP_EVENT: + cxt5047_hp2_automute(codec); + break; + case CONEXANT_MIC_EVENT: + cxt5047_hp_automic(codec); + break; + } +} + static struct snd_kcontrol_new cxt5047_mixers[] = { HDA_CODEC_VOLUME("Mic Bypass Capture Volume", 0x19, 0x02, HDA_INPUT), HDA_CODEC_MUTE("Mic Bypass Capture Switch", 0x19, 0x02, HDA_INPUT), @@ -1300,19 +1343,20 @@ static int patch_cxt5047(struct hda_code spec->channel_mode = cxt5047_modes, codec->patch_ops = conexant_patch_ops; - codec->patch_ops.unsol_event = cxt5047_hp_unsol_event; board_config = snd_hda_check_board_config(codec, CXT5047_MODELS, cxt5047_models, cxt5047_cfg_tbl); switch (board_config) { case CXT5047_LAPTOP: + codec->patch_ops.unsol_event = cxt5047_hp2_unsol_event; break; case CXT5047_LAPTOP_HP: spec->input_mux = &cxt5047_hp_capture_source; spec->num_init_verbs = 2; spec->init_verbs[1] = cxt5047_hp_init_verbs; spec->mixers[0] = cxt5047_hp_mixers; + codec->patch_ops.unsol_event = cxt5047_hp_unsol_event; codec->patch_ops.init = cxt5047_hp_init; break; case CXT5047_LAPTOP_EAPD: @@ -1320,12 +1364,14 @@ static int patch_cxt5047(struct hda_code spec->num_init_verbs = 2; spec->init_verbs[1] = cxt5047_toshiba_init_verbs; spec->mixers[0] = cxt5047_toshiba_mixers; + codec->patch_ops.unsol_event = cxt5047_hp_unsol_event; break; #ifdef CONFIG_SND_DEBUG case CXT5047_TEST: spec->input_mux = &cxt5047_test_capture_source; spec->mixers[0] = cxt5047_test_mixer; spec->init_verbs[0] = cxt5047_test_init_verbs; + codec->patch_ops.unsol_event = cxt5047_hp_unsol_event; #endif } return 0; diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 34ac634..4776de9 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -6379,8 +6379,10 @@ static struct snd_pci_quirk alc883_cfg_t SND_PCI_QUIRK(0x1458, 0xa002, "MSI", ALC883_6ST_DIG), SND_PCI_QUIRK(0x1462, 0x6668, "MSI", ALC883_6ST_DIG), 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, 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), SND_PCI_QUIRK(0x1462, 0x3b7f, "MSI", ALC883_TARGA_2ch_DIG), SND_PCI_QUIRK(0x1462, 0x3fcc, "MSI", ALC883_TARGA_DIG), @@ -6391,6 +6393,7 @@ static struct snd_pci_quirk alc883_cfg_t SND_PCI_QUIRK(0x1462, 0x4324, "MSI", ALC883_TARGA_DIG), SND_PCI_QUIRK(0x1462, 0xa422, "MSI", ALC883_TARGA_2ch_DIG), SND_PCI_QUIRK(0x1025, 0, "Acer laptop", ALC883_ACER), + SND_PCI_QUIRK(0x15d9, 0x8780, "Supermicro PDSBA", ALC883_3ST_6ch), SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_MEDION), SND_PCI_QUIRK(0x1071, 0x8258, "Evesham Voyaeger", ALC883_LAPTOP_EAPD), SND_PCI_QUIRK(0x8086, 0xd601, "D102GGC", ALC883_3ST_6ch), @@ -8765,7 +8768,6 @@ static struct snd_pci_quirk alc861_cfg_t SND_PCI_QUIRK(0x1043, 0x1338, "ASUS F2/3", ALC861_ASUS_LAPTOP), SND_PCI_QUIRK(0x1043, 0x13d7, "ASUS A9rp", ALC861_ASUS_LAPTOP), SND_PCI_QUIRK(0x1043, 0x1393, "ASUS", ALC861_ASUS), - SND_PCI_QUIRK(0x1043, 0x81e7, "ASUS", ALC660_3ST), SND_PCI_QUIRK(0x1179, 0xff00, "Toshiba", ALC861_TOSHIBA), SND_PCI_QUIRK(0x1179, 0xff10, "Toshiba", ALC861_TOSHIBA), SND_PCI_QUIRK(0x1584, 0x9072, "Uniwill m31", ALC861_UNIWILL_M31), @@ -9473,6 +9475,7 @@ static const char *alc861vd_models[ALC86 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(0x10de, 0x03f0, "Realtek ALC660 demo", ALC660VD_3ST), SND_PCI_QUIRK(0x1019, 0xa88d, "Realtek ALC660 demo", ALC660VD_3ST), diff --git a/sound/pci/hda/patch_si3054.c b/sound/pci/hda/patch_si3054.c index 6fcda9b..43f537e 100644 --- a/sound/pci/hda/patch_si3054.c +++ b/sound/pci/hda/patch_si3054.c @@ -304,6 +304,8 @@ struct hda_codec_preset snd_hda_preset_s { .id = 0x10573055, .name = "Si3054", .patch = patch_si3054 }, { .id = 0x10573057, .name = "Si3054", .patch = patch_si3054 }, { .id = 0x10573155, .name = "Si3054", .patch = patch_si3054 }, + /* Asus A8J Modem (SM56) */ + { .id = 0x15433155, .name = "Si3054", .patch = patch_si3054 }, {} }; diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index a6a0a80..2f91353 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -51,6 +51,7 @@ enum { STAC_925x_REF, STAC_M2_2, STAC_MA6, + STAC_PA6, STAC_925x_MODELS }; @@ -58,11 +59,18 @@ enum { STAC_D945_REF, STAC_D945GTP3, STAC_D945GTP5, + 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 }; @@ -152,6 +160,10 @@ static hda_nid_t stac925x_dac_nids[1] = 0x02, }; +static hda_nid_t stac925x_dmic_nids[1] = { + 0x15, +}; + static hda_nid_t stac922x_adc_nids[2] = { 0x06, 0x07, }; @@ -467,8 +479,21 @@ static struct snd_pci_quirk stac9200_cfg "Dell XPS M1710", STAC_REF), SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cf, "Dell Precision M90", STAC_REF), +<<<<<<< HEAD/sound/pci/hda/patch_sigmatel.c + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d6, + "unknown Dell", STAC_REF), +======= SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d6, "unknown Dell", STAC_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d8, + "Dell Inspiron 640m", STAC_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f5, + "Dell Inspiron 1501", STAC_REF), + + /* Panasonic */ + SND_PCI_QUIRK(0x10f7, 0x8338, "Panasonic CF-74", STAC_REF), + +>>>>>>> /sound/pci/hda/patch_sigmatel.c {} /* terminator */ }; @@ -482,29 +507,38 @@ static unsigned int stac925x_MA6_pin_con 0x90a70320, 0x90100211, 0x400003f1, 0x9033032e, }; +static unsigned int stac925x_PA6_pin_configs[8] = { + 0x40c003f0, 0x424503f2, 0x01813022, 0x02a19021, + 0x50a103f0, 0x90100211, 0x400003f1, 0x9033032e, +}; + static unsigned int stac925xM2_2_pin_configs[8] = { - 0x40c003f3, 0x424503f2, 0x041800f4, 0x02a19020, - 0x50a103F0, 0x90100210, 0x400003f1, 0x9033032e, + 0x40c003f3, 0x424503f2, 0x04180011, 0x02a19020, + 0x50a103f0, 0x90100212, 0x400003f1, 0x9033032e, }; static unsigned int *stac925x_brd_tbl[STAC_925x_MODELS] = { [STAC_REF] = ref925x_pin_configs, [STAC_M2_2] = stac925xM2_2_pin_configs, [STAC_MA6] = stac925x_MA6_pin_configs, + [STAC_PA6] = stac925x_PA6_pin_configs, }; static const char *stac925x_models[STAC_925x_MODELS] = { [STAC_REF] = "ref", [STAC_M2_2] = "m2-2", [STAC_MA6] = "m6", + [STAC_PA6] = "pa6", }; static struct snd_pci_quirk stac925x_cfg_tbl[] = { /* SigmaTel reference board */ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, "DFI LanParty", STAC_REF), + SND_PCI_QUIRK(0x8384, 0x7632, "Stac9202 Reference Board", STAC_REF), SND_PCI_QUIRK(0x107b, 0x0316, "Gateway M255", STAC_REF), SND_PCI_QUIRK(0x107b, 0x0366, "Gateway MP6954", STAC_REF), SND_PCI_QUIRK(0x107b, 0x0461, "Gateway NX560XL", STAC_MA6), + SND_PCI_QUIRK(0x107b, 0x0681, "Gateway NX860", STAC_PA6), SND_PCI_QUIRK(0x1002, 0x437b, "Gateway MX6453", STAC_M2_2), {} /* terminator */ }; @@ -527,21 +561,33 @@ static unsigned int d945gtp5_pin_configs 0x02a19320, 0x40000100, }; -static unsigned int macbook_pro_v1_pin_configs[10] = { - 0x0321e230, 0x03a1e020, 0x9017e110, 0x01014010, - 0x01a19021, 0x0381e021, 0x1345e240, 0x13c5e22e, - 0x02a19320, 0x400000fb +static unsigned int intel_mac_v1_pin_configs[10] = { + 0x0121e21f, 0x400000ff, 0x9017e110, 0x400000fd, + 0x400000fe, 0x0181e020, 0x1145e030, 0x11c5e240, + 0x400000fc, 0x400000fb, }; -static unsigned int macbook_pro_v2_pin_configs[10] = { - 0x0221401f, 0x90a70120, 0x01813024, 0x01014010, - 0x400000fd, 0x01016011, 0x1345e240, 0x13c5e22e, +static unsigned int intel_mac_v2_pin_configs[10] = { + 0x0121e21f, 0x90a7012e, 0x9017e110, 0x400000fd, + 0x400000fe, 0x0181e020, 0x1145e230, 0x500000fa, 0x400000fc, 0x400000fb, }; -static unsigned int imac_intel_pin_configs[10] = { - 0x0121e230, 0x90a70120, 0x9017e110, 0x400000fe, - 0x400000fd, 0x0181e021, 0x1145e040, 0x400000fa, +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, }; @@ -549,22 +595,36 @@ static unsigned int *stac922x_brd_tbl[ST [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_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_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[] = { @@ -627,7 +687,7 @@ static struct snd_pci_quirk stac922x_cfg /* other systems */ /* Apple Mac Mini (early 2006) */ SND_PCI_QUIRK(0x8384, 0x7680, - "Mac Mini", STAC_MACMINI), + "Mac Mini", STAC_INTEL_MAC_V3), {} /* terminator */ }; @@ -1742,6 +1802,21 @@ static void stac92xx_set_pinctl(struct h unsigned int pin_ctl = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0x00); + if (pin_ctl & AC_PINCTL_IN_EN) { + /* + * we need to check the current set-up direction of + * shared input pins since they can be switched via + * "xxx as Output" mixer switch + */ + struct sigmatel_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + if ((nid == cfg->input_pins[AUTO_PIN_LINE] && + spec->line_switch) || + (nid == cfg->input_pins[AUTO_PIN_MIC] && + spec->mic_switch)) + return; + } + /* if setting pin direction bits, clear the current direction bits first */ if (flag & (AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN)) @@ -1911,7 +1986,8 @@ static int patch_stac925x(struct hda_cod stac925x_cfg_tbl); again: if (spec->board_config < 0) { - snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC925x, using BIOS defaults\n"); + snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC925x," + "using BIOS defaults\n"); err = stac92xx_save_bios_config_regs(codec); if (err < 0) { stac92xx_free(codec); @@ -1929,7 +2005,18 @@ static int patch_stac925x(struct hda_cod spec->adc_nids = stac925x_adc_nids; spec->mux_nids = stac925x_mux_nids; spec->num_muxes = 1; - spec->num_dmics = 0; + switch (codec->vendor_id) { + case 0x83847632: /* STAC9202 */ + case 0x83847633: /* STAC9202D */ + case 0x83847636: /* STAC9251 */ + case 0x83847637: /* STAC9251D */ + spec->num_dmics = 1; + spec->dmic_nids = stac925x_dmic_nids; + break; + default: + spec->num_dmics = 0; + break; + } spec->init = stac925x_core_init; spec->mixer = stac925x_mixer; @@ -1969,24 +2056,36 @@ static int patch_stac922x(struct hda_cod 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 0x106b0600: + case 0x106b0700: + spec->board_config = STAC_INTEL_MAC_V2; break; - case 0x106b0200: /* MacBook Pro first generation */ - spec->board_config = STAC_MACBOOK_PRO_V1; + case 0x106b0e00: + case 0x106b0f00: + case 0x106b1600: + case 0x106b1700: + case 0x106b0200: + case 0x106b1e00: + spec->board_config = STAC_INTEL_MAC_V3; break; - case 0x106b1e00: /* MacBook Pro second generation */ - spec->board_config = STAC_MACBOOK_PRO_V2; + case 0x106b1a00: + case 0x00000100: + spec->board_config = STAC_INTEL_MAC_V4; break; - case 0x106b0700: /* Intel-based iMac */ - spec->board_config = STAC_IMAC_INTEL; + case 0x106b0a00: + case 0x106b2200: + spec->board_config = STAC_INTEL_MAC_V5; break; } } @@ -2033,6 +2132,13 @@ static int patch_stac922x(struct hda_cod 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; } diff --git a/sound/pci/ice1712/revo.c b/sound/pci/ice1712/revo.c index 690ceb3..d18a31e 100644 --- a/sound/pci/ice1712/revo.c +++ b/sound/pci/ice1712/revo.c @@ -186,7 +186,12 @@ static int revo51_i2c_init(struct snd_ic #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/rme9652/rme9652.c b/sound/pci/rme9652/rme9652.c index bd7dbd2..2de2740 100644 --- a/sound/pci/rme9652/rme9652.c +++ b/sound/pci/rme9652/rme9652.c @@ -406,7 +406,7 @@ static snd_pcm_uframes_t rme9652_hw_poin } 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/sh/Kconfig b/sound/sh/Kconfig new file mode 100644 index 0000000..b7e08ef --- /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 0000000..8fdcb6e --- /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 0000000..97bb86a --- /dev/null +++ b/sound/sh/aica.c @@ -0,0 +1,675 @@ +/* +* 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 spu_work_holder { + struct work_struct spu_dma_work; + void *sspointer; +} spu_working; + +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_substream *substream; + struct snd_pcm_runtime *runtime; + struct snd_card_aica *dreamcastcard; + struct spu_work_holder *holder = container_of(work, struct spu_work_holder, spu_dma_work); + substream = holder-> sspointer; + dreamcastcard = substream->pcm->private_data; + runtime = 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, 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, + 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, &(spu_working.spu_dma_work)); +} + +static void spu_begin_dma(struct snd_pcm_substream *substream) +{ + /* Must be atomic */ + struct snd_card_aica *dreamcastcard; + struct snd_pcm_runtime *runtime; + runtime = substream->runtime; + dreamcastcard = substream->pcm->private_data; + /* Use queue to do the heavy lifting */ + spu_working.sspointer = substream; + INIT_WORK(&(spu_working.spu_dma_work), run_spu_dma); + queue_work(aica_queue, &(spu_working.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; + 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) +{ + struct snd_card_aica *dreamcastcard; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + spu_begin_dma(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + dreamcastcard = substream->pcm->private_data; + if (dreamcastcard->timer.data) + del_timer(&dreamcastcard->timer); + 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"); + /* 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 */ + 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 0000000..0603b5b --- /dev/null +++ b/sound/sh/aica.h @@ -0,0 +1,80 @@ +/* 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 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 10cffc0..97b2552 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -27,6 +27,7 @@ # All the supported Soc's 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 0ae2e49..3041403 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 044a371..e97c683 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 6f0fffc..13c92f0 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/neo1973_wm8753.c b/sound/soc/s3c24xx/neo1973_wm8753.c new file mode 100644 index 0000000..d5a8fc2 --- /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 0000000..75acf7e --- /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 0000000..2b835e8 --- /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-pcm.c b/sound/soc/s3c24xx/s3c24xx-pcm.c index 21dc697..bfbdc3c 100644 --- a/sound/soc/s3c24xx/s3c24xx-pcm.c +++ b/sound/soc/s3c24xx/s3c24xx-pcm.c @@ -337,6 +337,8 @@ static int s3c24xx_pcm_open(struct snd_p if (prtd == NULL) return -ENOMEM; + spin_lock_init(&prtd->lock); + runtime->private_data = prtd; return 0; } diff --git a/sound/soc/s3c24xx/smdk2443_wm9710.c b/sound/soc/s3c24xx/smdk2443_wm9710.c new file mode 100644 index 0000000..d46cd81 --- /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 0000000..a332e51 --- /dev/null +++ b/sound/soc/sh/Kconfig @@ -0,0 +1,39 @@ +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 + select 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 0000000..a8e8ab8 --- /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 0000000..cdee374 --- /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 0000000..8e3f039 --- /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 0000000..5563f14 --- /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 0000000..b72bc31 --- /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/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c index 0a352e4..48e9aa3 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_ */ 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; }