aboutsummaryrefslogtreecommitdiffstats
path: root/sound/oss
diff options
context:
space:
mode:
authorroot <root@artemis.panaceas.org>2015-12-25 04:40:36 +0000
committerroot <root@artemis.panaceas.org>2015-12-25 04:40:36 +0000
commit849369d6c66d3054688672f97d31fceb8e8230fb (patch)
tree6135abc790ca67dedbe07c39806591e70eda81ce /sound/oss
downloadlinux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.gz
linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.bz2
linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.zip
initial_commit
Diffstat (limited to 'sound/oss')
-rw-r--r--sound/oss/.gitignore4
-rw-r--r--sound/oss/CHANGELOG369
-rw-r--r--sound/oss/Kconfig543
-rw-r--r--sound/oss/Makefile108
-rw-r--r--sound/oss/README.FIRST6
-rw-r--r--sound/oss/ad1848.c3069
-rw-r--r--sound/oss/ad1848.h24
-rw-r--r--sound/oss/ad1848_mixer.h253
-rw-r--r--sound/oss/aedsp16.c1373
-rw-r--r--sound/oss/audio.c985
-rw-r--r--sound/oss/bin2hex.c39
-rw-r--r--sound/oss/coproc.h12
-rw-r--r--sound/oss/dev_table.c256
-rw-r--r--sound/oss/dev_table.h390
-rw-r--r--sound/oss/dmabuf.c1268
-rw-r--r--sound/oss/dmasound/Kconfig45
-rw-r--r--sound/oss/dmasound/Makefile7
-rw-r--r--sound/oss/dmasound/dmasound.h262
-rw-r--r--sound/oss/dmasound/dmasound_atari.c1620
-rw-r--r--sound/oss/dmasound/dmasound_core.c1589
-rw-r--r--sound/oss/dmasound/dmasound_paula.c751
-rw-r--r--sound/oss/dmasound/dmasound_q40.c638
-rw-r--r--sound/oss/hex2hex.c101
-rw-r--r--sound/oss/kahlua.c231
-rw-r--r--sound/oss/midi_ctrl.h22
-rw-r--r--sound/oss/midi_synth.c712
-rw-r--r--sound/oss/midi_synth.h47
-rw-r--r--sound/oss/midibuf.c425
-rw-r--r--sound/oss/mpu401.c1806
-rw-r--r--sound/oss/mpu401.h11
-rw-r--r--sound/oss/msnd.c413
-rw-r--r--sound/oss/msnd.h278
-rw-r--r--sound/oss/msnd_classic.c3
-rw-r--r--sound/oss/msnd_classic.h185
-rw-r--r--sound/oss/msnd_pinnacle.c1931
-rw-r--r--sound/oss/msnd_pinnacle.h246
-rw-r--r--sound/oss/opl3.c1258
-rw-r--r--sound/oss/opl3_hw.h246
-rw-r--r--sound/oss/os.h46
-rw-r--r--sound/oss/pas2.h17
-rw-r--r--sound/oss/pas2_card.c455
-rw-r--r--sound/oss/pas2_midi.c262
-rw-r--r--sound/oss/pas2_mixer.c336
-rw-r--r--sound/oss/pas2_pcm.c437
-rw-r--r--sound/oss/pss.c1266
-rw-r--r--sound/oss/sb.h185
-rw-r--r--sound/oss/sb_audio.c1098
-rw-r--r--sound/oss/sb_card.c354
-rw-r--r--sound/oss/sb_card.h149
-rw-r--r--sound/oss/sb_common.c1292
-rw-r--r--sound/oss/sb_ess.c1831
-rw-r--r--sound/oss/sb_ess.h34
-rw-r--r--sound/oss/sb_midi.c206
-rw-r--r--sound/oss/sb_mixer.c770
-rw-r--r--sound/oss/sb_mixer.h105
-rw-r--r--sound/oss/sequencer.c1671
-rw-r--r--sound/oss/sound_calls.h87
-rw-r--r--sound/oss/sound_config.h147
-rw-r--r--sound/oss/sound_firmware.h2
-rw-r--r--sound/oss/sound_timer.c327
-rw-r--r--sound/oss/soundcard.c739
-rw-r--r--sound/oss/soundvers.h2
-rw-r--r--sound/oss/swarm_cs4297a.c2768
-rw-r--r--sound/oss/sys_timer.c285
-rw-r--r--sound/oss/trix.c525
-rw-r--r--sound/oss/tuning.h23
-rw-r--r--sound/oss/uart401.c482
-rw-r--r--sound/oss/uart6850.c361
-rw-r--r--sound/oss/ulaw.h69
-rw-r--r--sound/oss/v_midi.c290
-rw-r--r--sound/oss/v_midi.h14
-rw-r--r--sound/oss/vidc.c558
-rw-r--r--sound/oss/vidc.h63
-rw-r--r--sound/oss/vidc_fill.S218
-rw-r--r--sound/oss/vwsnd.c3498
-rw-r--r--sound/oss/waveartist.c2025
-rw-r--r--sound/oss/waveartist.h92
77 files changed, 44615 insertions, 0 deletions
diff --git a/sound/oss/.gitignore b/sound/oss/.gitignore
new file mode 100644
index 00000000..7efb12b4
--- /dev/null
+++ b/sound/oss/.gitignore
@@ -0,0 +1,4 @@
+#Ignore generated files
+maui_boot.h
+pss_boot.h
+trix_boot.h
diff --git a/sound/oss/CHANGELOG b/sound/oss/CHANGELOG
new file mode 100644
index 00000000..8706cd66
--- /dev/null
+++ b/sound/oss/CHANGELOG
@@ -0,0 +1,369 @@
+Note these changes relate to Hannu's code and don't include the changes
+made outside of this for modularising the sound
+
+Changelog for version 3.8o
+--------------------------
+
+Since 3.8h
+- Included support for OPL3-SA1 and SoftOSS
+
+Since 3.8
+- Fixed SNDCTL_DSP_GETOSPACE
+- Compatibility fixes for Linux 2.1.47
+
+Since 3.8-beta21
+- Fixed all known bugs (I think).
+
+Since 3.8-beta8
+- Lot of fixes to audio playback code in dmabuf.c
+
+Since 3.8-beta6
+- Fixed the famous Quake delay bug.
+
+Since 3.8-beta5
+- Fixed many bugs in audio playback.
+
+Since 3.8-beta4
+- Just minor changes.
+
+Since 3.8-beta1
+- Major rewrite of audio playback handling.
+- Added AWE32 support by Takashi Iwai (in ./lowlevel/).
+
+Since 3.7-beta#
+- Passing of ioctl() parameters between soundcard.c and other modules has been
+changed so that arg always points to kernel space.
+- Some bugfixes.
+
+Since 3.7-beta5
+- Disabled MIDI input with GUS PnP (Interwave). There seems to be constant
+stream of received 0x00 bytes when the MIDI receiver is enabled.
+
+Since 3.5
+- Changes almost everywhere.
+- Support for OPTi 82C924-based sound cards.
+
+Since 3.5.4-beta8
+- Fixed a bug in handling of non-fragment sized writes in 16 bit/stereo mode
+ with GUS.
+- Limited minimum fragment size with some audio devices (GUS=512 and
+ SB=32). These devices require more time to "recover" from processing
+ of each fragment.
+
+Since 3.5.4-beta6/7
+- There seems to be problems in the OPTi 82C930 so cards based on this
+ chip don't necessarily work yet. There are problems in detecting the
+ MIDI interface. Also mixer volumes may be seriously wrong on some systems.
+ You can safely use this driver version with C930 if it looks to work.
+ However please don't complain if you have problems with it. C930 support
+ should be fixed in future releases.
+- Got initialization of GUS PnP to work. With this version GUS PnP should
+ work in GUS compatible mode after initialization using isapnptools.
+- Fixed a bug in handling of full duplex cards in write only mode. This has
+ been causing "audio device opening" errors with RealAudio player.
+
+Since 3.5.4.beta5
+- Changes to OPTi 82C930 driver.
+- Major changes to the Soundscape driver. The driver requires now just one
+ DMA channel. The extra audio/dsp device (the "Not functional" one) used
+ for code download in the earlier versions has been eliminated. There is now
+ just one /dev/dsp# device which is used both for code download and audio.
+
+Since 3.5.4.beta4
+- Minor changes.
+
+Since 3.5.4-beta2
+- Fixed silent playback with ESS 688/1688.
+- Got SB16 to work without the 16 bit DMA channel (only the 8 bit one
+ is required for 8 and 16 bit modes).
+- Added the "lowlevel" subdirectory for additional low level drivers that
+ are not part of USS core. See lowlevel/README for more info.
+- Included support for ACI mixer (by Markus Kuhn). ACI is a mixer used in
+ miroPCM sound cards. See lowlevel/aci.readme for more info.
+- Support for Aztech Washington chipset (AZT2316 ASIC).
+
+Since 3.5.4-beta1
+- Reduced clicking with AD1848.
+- Support for OPTi 82C930. Only half duplex at this time. 16 bit playback
+ is sometimes just white noise (occurs randomly).
+
+Since 3.5.2
+- Major changes to the SB/Jazz16/ESS driver (most parts rewritten).
+ The most noticeable new feature is support for multiple SB cards at the same
+ time.
+- Renamed sb16_midi.c to uart401.c. Also modified it to work also with
+ other MPU401 UART compatible cards than SB16/ESS/Jazz.
+- Some changes which reduce clicking in audio playback.
+- Copying policy is now GPL.
+
+Since 3.5.1
+- TB Maui initialization support
+Since 3.5
+- Improved handling of playback underrun situations.
+
+Since 3.5-beta10
+- Bug fixing
+
+Since 3.5-beta9
+- Fixed for compatibility with Linux 1.3.70 and later.
+- Changed boot time passing of 16 bit DMA channel number to SB driver.
+
+Since 3.5-beta8
+- Minor changes
+
+Since 3.5-beta7
+- enhancements to configure program (by Jeff Tranter):
+ - prompts are in same format as 1.3.x Linux kernel config program
+ - on-line help for each question
+ - fixed some compile warnings detected by gcc/g++ -Wall
+ - minor grammatical changes to prompts
+
+Since 3.5-beta6
+- Fixed bugs in mmap() support.
+- Minor changes to Maui driver.
+
+Since 3.5-beta5
+- Fixed crash after recording with ESS688. It's generally a good
+ idea to stop inbound DMA transfers before freeing the memory
+ buffer.
+- Fixed handling of AD1845 codec (for example Shuttle Sound System).
+- Few other fixes.
+
+Since 3.5-beta4
+- Fixed bug in handling of uninitialized instruments with GUS.
+
+Since 3.5-beta3
+- Few changes which decrease popping at end/beginning of audio playback.
+
+Since 3.5-beta2
+- Removed MAD16+CS4231 hack made in previous version since it didn't
+ help.
+- Fixed the above bug in proper way and in proper place. Many thanks
+ to James Hightower.
+
+Since 3.5-beta1
+- Bug fixes.
+- Full duplex audio with MAD16+CS4231 may work now. The driver configures
+ SB DMA of MAD16 so that it doesn't conflict with codec's DMA channels.
+ The side effect is that all 8 bit DMA channels (0,1,3) are populated in
+ duplex mode.
+
+Since 3.5-alpha9
+- Bug fixes (mostly in Jazz16 and ESS1688/688 supports).
+- Temporarily disabled recording with ESS1688/688 since it causes crash.
+- Changed audio buffer partitioning algorithm so that it selects
+ smaller fragment size than earlier. This improves real time capabilities
+ of the driver and makes recording to disk to work better. Unfortunately
+ this change breaks some programs which assume that fragments cannot be
+ shorter than 4096 bytes.
+
+Since 3.5-alpha8
+- Bug fixes
+
+Since 3.5-alpha7
+- Linux kernel compatible configuration (_EXPERIMENTAL_). Enable
+ using command "cd /linux/drivers/sound;make script" and then
+ just run kernel's make config normally.
+- Minor fixes to the SB support. Hopefully the driver works with
+ all SB models now.
+- Added support for ESS ES1688 "AudioDrive" based cards.
+
+Since 3.5-alpha6
+- SB Pro and SB16 supports are no longer separately selectable options.
+ Enabling SB enables them too.
+- Changed all #ifndef EXCLUDE_xx stuff to #ifdef CONFIG_xx. Modified
+configure to handle this.
+- Removed initialization messages from the
+modularized version. They can be enabled by using init_trace=1 in
+the insmod command line (insmod sound init_trace=1).
+- More AIX stuff.
+- Added support for synchronizing dsp/audio devices with /dev/sequencer.
+- mmap() support for dsp/audio devices.
+
+Since 3.5-alpha5
+- AIX port.
+- Changed some xxx_PATCH macros in soundcard.h to work with
+ big endian machines.
+
+Since 3.5-alpha4
+- Removed the 'setfx' stuff from the version distributed with kernel
+ sources. Running 'setfx' is required again.
+
+Since 3.5-alpha3
+- Moved stuff from the 'setfx' program to the AudioTrix Pro driver.
+
+Since 3.5-alpha2
+- Modifications to makefile and configure.c. Unnecessary sources
+ are no longer compiled. Newly created local.h is also copied to
+ /etc/soundconf. "make oldconfig" reads /etc/soundconf and produces
+ new local.h which is compatible with current version of the driver.
+- Some fixes to the SB16 support.
+- Fixed random protection fault in gus_wave.c
+
+Since 3.5-alpha1
+- Modified to work with Linux-1.3.33 and later
+- Some minor changes
+
+Since 3.0.2
+- Support for CS4232 based PnP cards (AcerMagic S23 etc).
+- Full duplex support for some CS4231, CS4232 and AD1845 based cards
+(GUS MAX, AudioTrix Pro, AcerMagic S23 and many MAD16/Mozart cards
+having a codec mentioned above).
+- Almost fully rewritten loadable modules support.
+- Fixed some bugs.
+- Huge amount of testing (more testing is still required).
+- mmap() support (works with some cards). Requires much more testing.
+- Sample/patch/program loading for TB Maui/Tropez. No initialization
+since TB doesn't allow me to release that code.
+- Using CS4231 compatible codecs as timer for /dev/music.
+
+Since 3.0.1
+- Added allocation of I/O ports, DMA channels and interrupts
+to the initialization code. This may break modules support since
+the driver may not free some resources on unload. Should be fixed soon.
+
+Since 3.0
+- Some important bug fixes.
+- select() for /dev/dsp and /dev/audio (Linux only).
+(To use select() with read, you have to call read() to start
+the recording. Calling write() kills recording immediately so
+use select() carefully when you are writing a half duplex app.
+Full duplex mode is not implemented yet.) Select works also with
+/dev/sequencer and /dev/music. Maybe with /dev/midi## too.
+
+Since 3.0-beta2
+- Minor fixes.
+- Added Readme.cards
+
+Since 3.0-beta1
+- Minor fixes to the modules support.
+- Eliminated call to sb_free_irq() in ad1848.c
+- Rewritten MAD16&Mozart support (not tested with MAD16 Pro).
+- Fix to DMA initialization of PSS cards.
+- Some fixes to ad1848/cs42xx mixer support (GUS MAX, MSS, etc.)
+- Fixed some bugs in the PSS driver which caused I/O errors with
+ the MSS mode (/dev/dsp).
+
+Since 3.0-950506
+- Recording with GUS MAX fixed. It works when the driver is configured
+ to use two DMA channels with GUS MAX (16 bit ones recommended).
+
+Since 3.0-94xxxx
+- Too many changes
+
+Since 3.0-940818
+- Fixes for Linux 1.1.4x.
+- Disables Disney Sound System with SG NX Pro 16 (less noise).
+
+Since 2.90-2
+- Fixes to soundcard.h
+- Non blocking mode to /dev/sequencer
+- Experimental detection code for Ensoniq Soundscape.
+
+Since 2.90
+- Minor and major bug fixes
+
+Since pre-3.0-940712
+- GUS MAX support
+- Partially working MSS/WSS support (could work with some cards).
+- Hardware u-Law and A-Law support with AD1848/CS4248 and CS4231 codecs
+ (GUS MAX, GUS16, WSS etc). Hardware ADPCM is possible with GUS16 and
+ GUS MAX, but it doesn't work yet.
+Since pre-3.0-940426
+- AD1848/CS4248/CS4231 codec support (MSS, GUS MAX, Aztec, Orchid etc).
+This codec chip is used in various sound cards. This version is developed
+for the 16 bit daughtercard of GUS. It should work with other cards also
+if the following requirements are met:
+ - The I/O, IRQ and DMA settings are jumper selectable or
+ the card is initialized by booting DOS before booting Linux (etc.).
+ - You add the IO, IRQ and DMA settings manually to the local.h.
+ (Just define GUS16_BASE, GUS16_IRQ and GUS16_DMA). Note that
+ the base address bust be the base address of the codec chip not the
+ card itself. For the GUS16 these are the same but most MSS compatible
+ cards have the codec located at card_base+4.
+- Some minor changes
+
+Since 2.5 (******* MAJOR REWRITE ***********)
+
+This version is based on v2.3. I have tried to maintain two versions
+together so that this one should have the same features than v2.5.
+Something may still be missing. If you notice such things, please let me
+know.
+
+The Readme.v30 contains more details.
+
+- /dev/midi## devices.
+- /dev/sequencer2
+
+Since 2.5-beta2
+- Some fine tuning to the GUS v3.7 mixer code.
+- Fixed speed limits for the plain SB (1.0 to 2.0).
+
+Since 2.5-beta
+- Fixed OPL-3 detection with SB. Caused problems with PAS16.
+- GUS v3.7 mixer support.
+
+Since 2.4
+- Mixer support for Sound Galaxy NX Pro (define __SGNXPRO__ on your local.h).
+- Fixed truncated sound on /dev/dsp when the device is closed.
+- Linear volume mode for GUS
+- Pitch bends larger than +/- 2 octaves.
+- MIDI recording for SB and SB Pro. (Untested).
+- Some other fixes.
+- SB16 MIDI and DSP drivers only initialized if SB16 actually installed.
+- Implemented better detection for OPL-3. This should be useful if you
+ have an old SB Pro (the non-OPL-3 one) or a SB 2.0 clone which has a OPL-3.
+- SVR4.2 support by Ian Hartas. Initial ALPHA TEST version (untested).
+
+Since 2.3b
+- Fixed bug which made it impossible to make long recordings to disk.
+ Recording was not restarted after a buffer overflow situation.
+- Limited mixer support for GUS.
+- Numerous improvements to the GUS driver by Andrew Robinson. Including
+ some click removal etc.
+
+Since 2.3
+- Fixed some minor bugs in the SB16 driver.
+
+Since 2.2b
+- Full SB16 DSP support. 8/16 bit, mono/stereo
+- The SCO and FreeBSD versions should be in sync now. There are some
+ problems with SB16 and GUS in the FreeBSD versions.
+ The DMA buffer allocation of the SCO version has been polished but
+ there could still be some problems. At least it hogs memory.
+ The DMA channel
+ configuration method used in the SCO/System is a hack.
+- Support for the MPU emulation of the SB16.
+- Some big arrays are now allocated boot time. This makes the BSS segment
+ smaller which makes it possible to use the full driver with
+ NetBSD. These arrays are not allocated if no suitable sound card is available.
+- Fixed a bug in the compute_and_set_volume in gus_wave.c
+- Fixed the too fast mono playback problem of SB Pro and PAS16.
+
+Since 2.2
+- Stereo recording for SB Pro. Somehow it was missing and nobody
+ had noticed it earlier.
+- Minor polishing.
+- Interpreting of boot time arguments (sound=) for Linux.
+- Breakup of sb_dsp.c. Parts of the code has been moved to
+ sb_mixer.c and sb_midi.c
+
+Since 2.1
+- Preliminary support for SB16.
+ - The SB16 mixer is supported in its native mode.
+ - Digitized voice capability up to 44.1 kHz/8 bit/mono
+ (16 bit and stereo support coming in the next release).
+- Fixed some bugs in the digitized voice driver for PAS16.
+- Proper initialization of the SB emulation of latest PAS16 models.
+
+- Significantly improved /dev/dsp and /dev/audio support.
+ - Now supports half duplex mode. It's now possible to record and
+ playback without closing and reopening the device.
+ - It's possible to use smaller buffers than earlier. There is a new
+ ioctl(fd, SNDCTL_DSP_SUBDIVIDE, &n) where n should be 1, 2 or 4.
+ This call instructs the driver to use smaller buffers. The default
+ buffer size (0.5 to 1.0 seconds) is divided by n. Should be called
+ immediately after opening the device.
+
+Since 2.0
+Just cosmetic changes.
diff --git a/sound/oss/Kconfig b/sound/oss/Kconfig
new file mode 100644
index 00000000..6c93e051
--- /dev/null
+++ b/sound/oss/Kconfig
@@ -0,0 +1,543 @@
+# 18 Apr 1998, Michael Elizabeth Chastain, <mailto:mec@shout.net>
+# More hacking for modularisation.
+#
+# Prompt user for primary drivers.
+
+config SOUND_BCM_CS4297A
+ tristate "Crystal Sound CS4297a (for Swarm)"
+ depends on SIBYTE_SWARM
+ help
+ The BCM91250A has a Crystal CS4297a on synchronous serial
+ port B (in addition to the DB-9 serial port). Say Y or M
+ here to enable the sound chip instead of the UART. Also
+ note that CONFIG_KGDB should not be enabled at the same
+ time, since it also attempts to use this UART port.
+
+config SOUND_VWSND
+ tristate "SGI Visual Workstation Sound"
+ depends on X86_VISWS
+ help
+ Say Y or M if you have an SGI Visual Workstation and you want to be
+ able to use its on-board audio. Read
+ <file:Documentation/sound/oss/vwsnd> for more info on this driver's
+ capabilities.
+
+config SOUND_MSNDCLAS
+ tristate "Support for Turtle Beach MultiSound Classic, Tahiti, Monterey"
+ depends on (m || !STANDALONE) && ISA
+ help
+ Say M here if you have a Turtle Beach MultiSound Classic, Tahiti or
+ Monterey (not for the Pinnacle or Fiji).
+
+ See <file:Documentation/sound/oss/MultiSound> for important information
+ about this driver. Note that it has been discontinued, but the
+ Voyetra Turtle Beach knowledge base entry for it is still available
+ at <http://www.turtlebeach.com/site/kb_ftp/790.asp>.
+
+comment "Compiled-in MSND Classic support requires firmware during compilation."
+ depends on SOUND_PRIME && SOUND_MSNDCLAS=y
+
+config MSNDCLAS_HAVE_BOOT
+ bool
+ depends on SOUND_MSNDCLAS=y && !STANDALONE
+ default y
+
+config MSNDCLAS_INIT_FILE
+ string "Full pathname of MSNDINIT.BIN firmware file"
+ depends on SOUND_MSNDCLAS
+ default "/etc/sound/msndinit.bin"
+ help
+ The MultiSound cards have two firmware files which are required for
+ operation, and are not currently included. These files can be
+ obtained from Turtle Beach. See
+ <file:Documentation/sound/oss/MultiSound> for information on how to
+ obtain this.
+
+config MSNDCLAS_PERM_FILE
+ string "Full pathname of MSNDPERM.BIN firmware file"
+ depends on SOUND_MSNDCLAS
+ default "/etc/sound/msndperm.bin"
+ help
+ The MultiSound cards have two firmware files which are required for
+ operation, and are not currently included. These files can be
+ obtained from Turtle Beach. See
+ <file:Documentation/sound/oss/MultiSound> for information on how to
+ obtain this.
+
+config MSNDCLAS_IRQ
+ int "MSND Classic IRQ 5, 7, 9, 10, 11, 12"
+ depends on SOUND_MSNDCLAS=y
+ default "5"
+ help
+ Interrupt Request line for the MultiSound Classic and related cards.
+
+config MSNDCLAS_MEM
+ hex "MSND Classic memory B0000, C8000, D0000, D8000, E0000, E8000"
+ depends on SOUND_MSNDCLAS=y
+ default "D0000"
+ help
+ Memory-mapped I/O base address for the MultiSound Classic and
+ related cards.
+
+config MSNDCLAS_IO
+ hex "MSND Classic I/O 210, 220, 230, 240, 250, 260, 290, 3E0"
+ depends on SOUND_MSNDCLAS=y
+ default "290"
+ help
+ I/O port address for the MultiSound Classic and related cards.
+
+config SOUND_MSNDPIN
+ tristate "Support for Turtle Beach MultiSound Pinnacle, Fiji"
+ depends on (m || !STANDALONE) && ISA
+ help
+ Say M here if you have a Turtle Beach MultiSound Pinnacle or Fiji.
+ See <file:Documentation/sound/oss/MultiSound> for important information
+ about this driver. Note that it has been discontinued, but the
+ Voyetra Turtle Beach knowledge base entry for it is still available
+ at <http://www.turtlebeach.com/site/kb_ftp/600.asp>.
+
+comment "Compiled-in MSND Pinnacle support requires firmware during compilation."
+ depends on SOUND_PRIME && SOUND_MSNDPIN=y
+
+config MSNDPIN_HAVE_BOOT
+ bool
+ depends on SOUND_MSNDPIN=y
+ default y
+
+config MSNDPIN_INIT_FILE
+ string "Full pathname of PNDSPINI.BIN firmware file"
+ depends on SOUND_MSNDPIN
+ default "/etc/sound/pndspini.bin"
+ help
+ The MultiSound cards have two firmware files which are required
+ for operation, and are not currently included. These files can be
+ obtained from Turtle Beach. See
+ <file:Documentation/sound/oss/MultiSound> for information on how to
+ obtain this.
+
+config MSNDPIN_PERM_FILE
+ string "Full pathname of PNDSPERM.BIN firmware file"
+ depends on SOUND_MSNDPIN
+ default "/etc/sound/pndsperm.bin"
+ help
+ The MultiSound cards have two firmware files which are required for
+ operation, and are not currently included. These files can be
+ obtained from Turtle Beach. See
+ <file:Documentation/sound/oss/MultiSound> for information on how to
+ obtain this.
+
+config MSNDPIN_IRQ
+ int "MSND Pinnacle IRQ 5, 7, 9, 10, 11, 12"
+ depends on SOUND_MSNDPIN=y
+ default "5"
+ help
+ Interrupt request line for the primary synthesizer on MultiSound
+ Pinnacle and Fiji sound cards.
+
+config MSNDPIN_MEM
+ hex "MSND Pinnacle memory B0000, C8000, D0000, D8000, E0000, E8000"
+ depends on SOUND_MSNDPIN=y
+ default "D0000"
+ help
+ Memory-mapped I/O base address for the primary synthesizer on
+ MultiSound Pinnacle and Fiji sound cards.
+
+config MSNDPIN_IO
+ hex "MSND Pinnacle I/O 210, 220, 230, 240, 250, 260, 290, 3E0"
+ depends on SOUND_MSNDPIN=y
+ default "290"
+ help
+ Memory-mapped I/O base address for the primary synthesizer on
+ MultiSound Pinnacle and Fiji sound cards.
+
+config MSNDPIN_DIGITAL
+ bool "MSND Pinnacle has S/PDIF I/O"
+ depends on SOUND_MSNDPIN=y
+ help
+ If you have the S/PDIF daughter board for the Pinnacle or Fiji,
+ answer Y here; otherwise, say N. If you have this, you will be able
+ to play and record from the S/PDIF port (digital signal). See
+ <file:Documentation/sound/oss/MultiSound> for information on how to make
+ use of this capability.
+
+config MSNDPIN_NONPNP
+ bool "MSND Pinnacle non-PnP Mode"
+ depends on SOUND_MSNDPIN=y
+ help
+ The Pinnacle and Fiji card resources can be configured either with
+ PnP, or through a configuration port. Say Y here if your card is NOT
+ in PnP mode. For the Pinnacle, configuration in non-PnP mode allows
+ use of the IDE and joystick peripherals on the card as well; these
+ do not show up when the card is in PnP mode. Specifying zero for any
+ resource of a device will disable the device. If you are running the
+ card in PnP mode, you must say N here and use isapnptools to
+ configure the card's resources.
+
+comment "MSND Pinnacle DSP section will be configured to above parameters."
+ depends on SOUND_MSNDPIN=y && MSNDPIN_NONPNP
+
+config MSNDPIN_CFG
+ hex "MSND Pinnacle config port 250,260,270"
+ depends on MSNDPIN_NONPNP
+ default "250"
+ help
+ This is the port which the Pinnacle and Fiji uses to configure the
+ card's resources when not in PnP mode. If your card is in PnP mode,
+ then be sure to say N to the previous option, "MSND Pinnacle Non-PnP
+ Mode".
+
+comment "Pinnacle-specific Device Configuration (0 disables)"
+ depends on SOUND_MSNDPIN=y && MSNDPIN_NONPNP
+
+config MSNDPIN_MPU_IO
+ hex "MSND Pinnacle MPU I/O (e.g. 330)"
+ depends on MSNDPIN_NONPNP
+ default "0"
+ help
+ Memory-mapped I/O base address for the Kurzweil daughterboard
+ synthesizer on MultiSound Pinnacle and Fiji sound cards.
+
+config MSNDPIN_MPU_IRQ
+ int "MSND Pinnacle MPU IRQ (e.g. 9)"
+ depends on MSNDPIN_NONPNP
+ default "0"
+ help
+ Interrupt request number for the Kurzweil daughterboard
+ synthesizer on MultiSound Pinnacle and Fiji sound cards.
+
+config MSNDPIN_IDE_IO0
+ hex "MSND Pinnacle IDE I/O 0 (e.g. 170)"
+ depends on MSNDPIN_NONPNP
+ default "0"
+ help
+ CD-ROM drive 0 memory-mapped I/O base address for the MultiSound
+ Pinnacle and Fiji sound cards.
+
+config MSNDPIN_IDE_IO1
+ hex "MSND Pinnacle IDE I/O 1 (e.g. 376)"
+ depends on MSNDPIN_NONPNP
+ default "0"
+ help
+ CD-ROM drive 1 memory-mapped I/O base address for the MultiSound
+ Pinnacle and Fiji sound cards.
+
+config MSNDPIN_IDE_IRQ
+ int "MSND Pinnacle IDE IRQ (e.g. 15)"
+ depends on MSNDPIN_NONPNP
+ default "0"
+ help
+ Interrupt request number for the IDE CD-ROM interface on the
+ MultiSound Pinnacle and Fiji sound cards.
+
+config MSNDPIN_JOYSTICK_IO
+ hex "MSND Pinnacle joystick I/O (e.g. 200)"
+ depends on MSNDPIN_NONPNP
+ default "0"
+ help
+ Memory-mapped I/O base address for the joystick port on MultiSound
+ Pinnacle and Fiji sound cards.
+
+config MSND_FIFOSIZE
+ int "MSND buffer size (kB)"
+ depends on SOUND_MSNDPIN=y || SOUND_MSNDCLAS=y
+ default "128"
+ help
+ Configures the size of each audio buffer, in kilobytes, for
+ recording and playing in the MultiSound drivers (both the Classic
+ and Pinnacle). Larger values reduce the chance of data overruns at
+ the expense of overall latency. If unsure, use the default.
+
+menuconfig SOUND_OSS
+ tristate "OSS sound modules"
+ depends on ISA_DMA_API && VIRT_TO_BUS
+ help
+ OSS is the Open Sound System suite of sound card drivers. They make
+ sound programming easier since they provide a common API. Say Y or
+ M here (the module will be called sound) if you haven't found a
+ driver for your sound card above, then pick your driver from the
+ list below.
+
+if SOUND_OSS
+
+config SOUND_TRACEINIT
+ bool "Verbose initialisation"
+ help
+ Verbose soundcard initialization -- affects the format of autoprobe
+ and initialization messages at boot time.
+
+config SOUND_DMAP
+ bool "Persistent DMA buffers"
+ ---help---
+ Linux can often have problems allocating DMA buffers for ISA sound
+ cards on machines with more than 16MB of RAM. This is because ISA
+ DMA buffers must exist below the 16MB boundary and it is quite
+ possible that a large enough free block in this region cannot be
+ found after the machine has been running for a while. If you say Y
+ here the DMA buffers (64Kb) will be allocated at boot time and kept
+ until the shutdown. This option is only useful if you said Y to
+ "OSS sound modules", above. If you said M to "OSS sound modules"
+ then you can get the persistent DMA buffer functionality by passing
+ the command-line argument "dmabuf=1" to the sound module.
+
+ Say Y unless you have 16MB or more RAM or a PCI sound card.
+
+config SOUND_VMIDI
+ tristate "Loopback MIDI device support"
+ help
+ Support for MIDI loopback on port 1 or 2.
+
+config SOUND_TRIX
+ tristate "MediaTrix AudioTrix Pro support"
+ help
+ Answer Y if you have the AudioTriX Pro sound card manufactured
+ by MediaTrix.
+
+config TRIX_HAVE_BOOT
+ bool "Have TRXPRO.HEX firmware file"
+ depends on SOUND_TRIX=y && !STANDALONE
+ help
+ The MediaTrix AudioTrix Pro has an on-board microcontroller which
+ needs to be initialized by downloading the code from the file
+ TRXPRO.HEX in the DOS driver directory. If you don't have the
+ TRXPRO.HEX file handy you may skip this step. However, the SB and
+ MPU-401 modes of AudioTrix Pro will not work without this file!
+
+config TRIX_BOOT_FILE
+ string "Full pathname of TRXPRO.HEX firmware file"
+ depends on TRIX_HAVE_BOOT
+ default "/etc/sound/trxpro.hex"
+ help
+ Enter the full pathname of your TRXPRO.HEX file, starting from /.
+
+config SOUND_MSS
+ tristate "Microsoft Sound System support"
+ ---help---
+ Again think carefully before answering Y to this question. It's
+ safe to answer Y if you have the original Windows Sound System card
+ made by Microsoft or Aztech SG 16 Pro (or NX16 Pro). Also you may
+ say Y in case your card is NOT among these:
+
+ ATI Stereo F/X, AdLib, Audio Excell DSP16, Cardinal DSP16,
+ Ensoniq SoundScape (and compatibles made by Reveal and Spea),
+ Gravis Ultrasound, Gravis Ultrasound ACE, Gravis Ultrasound Max,
+ Gravis Ultrasound with 16 bit option, Logitech Sound Man 16,
+ Logitech SoundMan Games, Logitech SoundMan Wave, MAD16 Pro (OPTi
+ 82C929), Media Vision Jazz16, MediaTriX AudioTriX Pro, Microsoft
+ Windows Sound System (MSS/WSS), Mozart (OAK OTI-601), Orchid
+ SW32, Personal Sound System (PSS), Pro Audio Spectrum 16, Pro
+ Audio Studio 16, Pro Sonic 16, Roland MPU-401 MIDI interface,
+ Sound Blaster 1.0, Sound Blaster 16, Sound Blaster 16ASP, Sound
+ Blaster 2.0, Sound Blaster AWE32, Sound Blaster Pro, TI TM4000M
+ notebook, ThunderBoard, Turtle Beach Tropez, Yamaha FM
+ synthesizers (OPL2, OPL3 and OPL4), 6850 UART MIDI Interface.
+
+ For cards having native support in VoxWare, consult the card
+ specific instructions in <file:Documentation/sound/oss/README.OSS>.
+ Some drivers have their own MSS support and saying Y to this option
+ will cause a conflict.
+
+ If you compile the driver into the kernel, you have to add
+ "ad1848=<io>,<irq>,<dma>,<dma2>[,<type>]" to the kernel command
+ line.
+
+config SOUND_MPU401
+ tristate "MPU-401 support (NOT for SB16)"
+ ---help---
+ Be careful with this question. The MPU401 interface is supported by
+ all sound cards. However, some natively supported cards have their
+ own driver for MPU401. Enabling this MPU401 option with these cards
+ will cause a conflict. Also, enabling MPU401 on a system that
+ doesn't really have a MPU401 could cause some trouble. If your card
+ was in the list of supported cards, look at the card specific
+ instructions in the <file:Documentation/sound/oss/README.OSS> file. It
+ is safe to answer Y if you have a true MPU401 MIDI interface card.
+
+ If you compile the driver into the kernel, you have to add
+ "mpu401=<io>,<irq>" to the kernel command line.
+
+config SOUND_PAS
+ tristate "ProAudioSpectrum 16 support"
+ ---help---
+ Answer Y only if you have a Pro Audio Spectrum 16, ProAudio Studio
+ 16 or Logitech SoundMan 16 sound card. Answer N if you have some
+ other card made by Media Vision or Logitech since those are not
+ PAS16 compatible. Please read <file:Documentation/sound/oss/PAS16>.
+ It is not necessary to add Sound Blaster support separately; it
+ is included in PAS support.
+
+ If you compile the driver into the kernel, you have to add
+ "pas2=<io>,<irq>,<dma>,<dma2>,<sbio>,<sbirq>,<sbdma>,<sbdma2>
+ to the kernel command line.
+
+config PAS_JOYSTICK
+ bool "Enable PAS16 joystick port"
+ depends on SOUND_PAS=y
+ help
+ Say Y here to enable the Pro Audio Spectrum 16's auxiliary joystick
+ port.
+
+config SOUND_PSS
+ tristate "PSS (AD1848, ADSP-2115, ESC614) support"
+ help
+ Answer Y or M if you have an Orchid SW32, Cardinal DSP16, Beethoven
+ ADSP-16 or some other card based on the PSS chipset (AD1848 codec +
+ ADSP-2115 DSP chip + Echo ESC614 ASIC CHIP). For more information on
+ how to compile it into the kernel or as a module see the file
+ <file:Documentation/sound/oss/PSS>.
+
+ If you compile the driver into the kernel, you have to add
+ "pss=<io>,<mssio>,<mssirq>,<mssdma>,<mpuio>,<mpuirq>" to the kernel
+ command line.
+
+config PSS_MIXER
+ bool "Enable PSS mixer (Beethoven ADSP-16 and other compatible)"
+ depends on SOUND_PSS
+ help
+ Answer Y for Beethoven ADSP-16. You may try to say Y also for other
+ cards if they have master volume, bass, treble, and you can't
+ control it under Linux. If you answer N for Beethoven ADSP-16, you
+ can't control master volume, bass, treble and synth volume.
+
+ If you said M to "PSS support" above, you may enable or disable this
+ PSS mixer with the module parameter pss_mixer. For more information
+ see the file <file:Documentation/sound/oss/PSS>.
+
+config PSS_HAVE_BOOT
+ bool "Have DSPxxx.LD firmware file"
+ depends on SOUND_PSS && !STANDALONE
+ help
+ If you have the DSPxxx.LD file or SYNTH.LD file for you card, say Y
+ to include this file. Without this file the synth device (OPL) may
+ not work.
+
+config PSS_BOOT_FILE
+ string "Full pathname of DSPxxx.LD firmware file"
+ depends on PSS_HAVE_BOOT
+ default "/etc/sound/dsp001.ld"
+ help
+ Enter the full pathname of your DSPxxx.LD file or SYNTH.LD file,
+ starting from /.
+
+config SOUND_SB
+ tristate "100% Sound Blaster compatibles (SB16/32/64, ESS, Jazz16) support"
+ ---help---
+ Answer Y if you have an original Sound Blaster card made by Creative
+ Labs or a 100% hardware compatible clone (like the Thunderboard or
+ SM Games). For an unknown card you may answer Y if the card claims
+ to be Sound Blaster-compatible.
+
+ Please read the file <file:Documentation/sound/oss/Soundblaster>.
+
+ You should also say Y here for cards based on the Avance Logic
+ ALS-007 and ALS-1X0 chips (read <file:Documentation/sound/oss/ALS>) and
+ for cards based on ESS chips (read
+ <file:Documentation/sound/oss/ESS1868> and
+ <file:Documentation/sound/oss/ESS>). If you have an SB AWE 32 or SB AWE
+ 64, say Y here and also to "AWE32 synth" below and read
+ <file:Documentation/sound/oss/INSTALL.awe>. If you have an IBM Mwave
+ card, say Y here and read <file:Documentation/sound/oss/mwave>.
+
+ If you compile the driver into the kernel and don't want to use
+ isapnp, you have to add "sb=<io>,<irq>,<dma>,<dma2>" to the kernel
+ command line.
+
+ You can say M here to compile this driver as a module; the module is
+ called sb.
+
+config SOUND_YM3812
+ tristate "Yamaha FM synthesizer (YM3812/OPL-3) support"
+ ---help---
+ Answer Y if your card has a FM chip made by Yamaha (OPL2/OPL3/OPL4).
+ Answering Y is usually a safe and recommended choice, however some
+ cards may have software (TSR) FM emulation. Enabling FM support with
+ these cards may cause trouble (I don't currently know of any such
+ cards, however). Please read the file
+ <file:Documentation/sound/oss/OPL3> if your card has an OPL3 chip.
+
+ If you compile the driver into the kernel, you have to add
+ "opl3=<io>" to the kernel command line.
+
+ If unsure, say Y.
+
+config SOUND_UART6850
+ tristate "6850 UART support"
+ help
+ This option enables support for MIDI interfaces based on the 6850
+ UART chip. This interface is rarely found on sound cards. It's safe
+ to answer N to this question.
+
+ If you compile the driver into the kernel, you have to add
+ "uart6850=<io>,<irq>" to the kernel command line.
+
+config SOUND_AEDSP16
+ tristate "Gallant Audio Cards (SC-6000 and SC-6600 based)"
+ ---help---
+ Answer Y if you have a Gallant's Audio Excel DSP 16 card. This
+ driver supports Audio Excel DSP 16 but not the III nor PnP versions
+ of this card.
+
+ The Gallant's Audio Excel DSP 16 card can emulate either an SBPro or
+ a Microsoft Sound System card, so you should have said Y to either
+ "100% Sound Blaster compatibles (SB16/32/64, ESS, Jazz16) support"
+ or "Microsoft Sound System support", above, and you need to answer
+ the "MSS emulation" and "SBPro emulation" questions below
+ accordingly. You should say Y to one and only one of these two
+ questions.
+
+ Read the <file:Documentation/sound/oss/README.OSS> file and the head of
+ <file:sound/oss/aedsp16.c> as well as
+ <file:Documentation/sound/oss/AudioExcelDSP16> to get more information
+ about this driver and its configuration.
+
+config SC6600
+ bool "SC-6600 based audio cards (new Audio Excel DSP 16)"
+ depends on SOUND_AEDSP16
+ help
+ The SC6600 is the new version of DSP mounted on the Audio Excel DSP
+ 16 cards. Find in the manual the FCC ID of your audio card and
+ answer Y if you have an SC6600 DSP.
+
+config SC6600_JOY
+ bool "Activate SC-6600 Joystick Interface"
+ depends on SC6600
+ help
+ Say Y here in order to use the joystick interface of the Audio Excel
+ DSP 16 card.
+
+config SC6600_CDROM
+ int "SC-6600 CDROM Interface (4=None, 3=IDE, 1=Panasonic, 0=?Sony?)"
+ depends on SC6600
+ default "4"
+ help
+ This is used to activate the CD-ROM interface of the Audio Excel
+ DSP 16 card. Enter: 0 for Sony, 1 for Panasonic, 2 for IDE, 4 for no
+ CD-ROM present.
+
+config SC6600_CDROMBASE
+ hex "SC-6600 CDROM Interface I/O Address"
+ depends on SC6600
+ default "0"
+ help
+ Base I/O port address for the CD-ROM interface of the Audio Excel
+ DSP 16 card.
+
+config SOUND_VIDC
+ tristate "VIDC 16-bit sound"
+ depends on ARM && (ARCH_ACORN || ARCH_CLPS7500)
+ help
+ 16-bit support for the VIDC onboard sound hardware found on Acorn
+ machines.
+
+config SOUND_WAVEARTIST
+ tristate "Netwinder WaveArtist"
+ depends on ARM && ARCH_NETWINDER
+ help
+ Say Y here to include support for the Rockwell WaveArtist sound
+ system. This driver is mainly for the NetWinder.
+
+config SOUND_KAHLUA
+ tristate "XpressAudio Sound Blaster emulation"
+ depends on SOUND_SB
+
+endif # SOUND_OSS
+
diff --git a/sound/oss/Makefile b/sound/oss/Makefile
new file mode 100644
index 00000000..77f21b68
--- /dev/null
+++ b/sound/oss/Makefile
@@ -0,0 +1,108 @@
+# Makefile for the Linux sound card driver
+#
+# 18 Apr 1998, Michael Elizabeth Chastain, <mailto:mec@shout.net>
+# Rewritten to use lists instead of if-statements.
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_SOUND_OSS) += sound.o
+
+# Please leave it as is, cause the link order is significant !
+
+obj-$(CONFIG_SOUND_AEDSP16) += aedsp16.o
+obj-$(CONFIG_SOUND_PSS) += pss.o ad1848.o mpu401.o
+obj-$(CONFIG_SOUND_TRIX) += trix.o ad1848.o sb_lib.o uart401.o
+obj-$(CONFIG_SOUND_MSS) += ad1848.o
+obj-$(CONFIG_SOUND_PAS) += pas2.o sb.o sb_lib.o uart401.o
+obj-$(CONFIG_SOUND_SB) += sb.o sb_lib.o uart401.o
+obj-$(CONFIG_SOUND_KAHLUA) += kahlua.o
+obj-$(CONFIG_SOUND_MPU401) += mpu401.o
+obj-$(CONFIG_SOUND_UART6850) += uart6850.o
+obj-$(CONFIG_SOUND_YM3812) += opl3.o
+obj-$(CONFIG_SOUND_VMIDI) += v_midi.o
+obj-$(CONFIG_SOUND_VIDC) += vidc_mod.o
+obj-$(CONFIG_SOUND_WAVEARTIST) += waveartist.o
+obj-$(CONFIG_SOUND_MSNDCLAS) += msnd.o msnd_classic.o
+obj-$(CONFIG_SOUND_MSNDPIN) += msnd.o msnd_pinnacle.o
+obj-$(CONFIG_SOUND_VWSND) += vwsnd.o
+obj-$(CONFIG_SOUND_BCM_CS4297A) += swarm_cs4297a.o
+
+obj-$(CONFIG_DMASOUND) += dmasound/
+
+# Declare multi-part drivers.
+
+sound-objs := \
+ dev_table.o soundcard.o \
+ audio.o dmabuf.o \
+ midi_synth.o midibuf.o \
+ sequencer.o sound_timer.o sys_timer.o
+
+pas2-objs := pas2_card.o pas2_midi.o pas2_mixer.o pas2_pcm.o
+sb-objs := sb_card.o
+sb_lib-objs := sb_common.o sb_audio.o sb_midi.o sb_mixer.o sb_ess.o
+vidc_mod-objs := vidc.o vidc_fill.o
+
+hostprogs-y := bin2hex hex2hex
+
+# Files generated that shall be removed upon make clean
+clean-files := msndperm.c msndinit.c pndsperm.c pndspini.c \
+ pss_boot.h trix_boot.h
+
+# Firmware files that need translation
+#
+# The translated files are protected by a file that keeps track
+# of what name was used to build them. If the name changes, they
+# will be forced to be remade.
+#
+
+# Turtle Beach MultiSound
+
+ifeq ($(CONFIG_MSNDCLAS_HAVE_BOOT),y)
+ $(obj)/msnd_classic.o: $(obj)/msndperm.c $(obj)/msndinit.c
+
+ $(obj)/msndperm.c: $(patsubst "%", %, $(CONFIG_MSNDCLAS_PERM_FILE)) $(obj)/bin2hex
+ $(obj)/bin2hex msndperm < $< > $@
+
+ $(obj)/msndinit.c: $(patsubst "%", %, $(CONFIG_MSNDCLAS_INIT_FILE)) $(obj)/bin2hex
+ $(obj)/bin2hex msndinit < $< > $@
+endif
+
+ifeq ($(CONFIG_MSNDPIN_HAVE_BOOT),y)
+ $(obj)/msnd_pinnacle.o: $(obj)/pndsperm.c $(obj)/pndspini.c
+
+ $(obj)/pndsperm.c: $(patsubst "%", %, $(CONFIG_MSNDPIN_PERM_FILE)) $(obj)/bin2hex
+ $(obj)/bin2hex pndsperm < $< > $@
+
+ $(obj)/pndspini.c: $(patsubst "%", %, $(CONFIG_MSNDPIN_INIT_FILE)) $(obj)/bin2hex
+ $(obj)/bin2hex pndspini < $< > $@
+endif
+
+# PSS (ECHO-ADI2111)
+
+$(obj)/pss.o: $(obj)/pss_boot.h
+
+ifeq ($(CONFIG_PSS_HAVE_BOOT),y)
+ $(obj)/pss_boot.h: $(patsubst "%", %, $(CONFIG_PSS_BOOT_FILE)) $(obj)/bin2hex
+ $(obj)/bin2hex pss_synth < $< > $@
+else
+ $(obj)/pss_boot.h:
+ $(Q)( \
+ echo 'static unsigned char * pss_synth = NULL;'; \
+ echo 'static int pss_synthLen = 0;'; \
+ ) > $@
+endif
+
+# MediaTrix AudioTrix Pro
+
+$(obj)/trix.o: $(obj)/trix_boot.h
+
+ifeq ($(CONFIG_TRIX_HAVE_BOOT),y)
+ $(obj)/trix_boot.h: $(patsubst "%", %, $(CONFIG_TRIX_BOOT_FILE)) $(obj)/hex2hex
+ $(obj)/hex2hex -i trix_boot < $< > $@
+else
+ $(obj)/trix_boot.h:
+ $(Q)( \
+ echo 'static unsigned char * trix_boot = NULL;'; \
+ echo 'static int trix_boot_len = 0;'; \
+ ) > $@
+endif
diff --git a/sound/oss/README.FIRST b/sound/oss/README.FIRST
new file mode 100644
index 00000000..90fdcf06
--- /dev/null
+++ b/sound/oss/README.FIRST
@@ -0,0 +1,6 @@
+The modular sound driver patches were funded by Red Hat Software
+(www.redhat.com). The sound driver here is thus a modified version of
+Hannu's code. Please bear that in mind when considering the appropriate
+forums for bug reporting.
+
+Alan Cox
diff --git a/sound/oss/ad1848.c b/sound/oss/ad1848.c
new file mode 100644
index 00000000..4d2a6ae9
--- /dev/null
+++ b/sound/oss/ad1848.c
@@ -0,0 +1,3069 @@
+/*
+ * sound/oss/ad1848.c
+ *
+ * The low level driver for the AD1848/CS4248 codec chip which
+ * is used for example in the MS Sound System.
+ *
+ * The CS4231 which is used in the GUS MAX and some other cards is
+ * upwards compatible with AD1848 and this driver is able to drive it.
+ *
+ * CS4231A and AD1845 are upward compatible with CS4231. However
+ * the new features of these chips are different.
+ *
+ * CS4232 is a PnP audio chip which contains a CS4231A (and SB, MPU).
+ * CS4232A is an improved version of CS4232.
+ *
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ * general sleep/wakeup clean up.
+ * Alan Cox : reformatted. Fixed SMP bugs. Moved to kernel alloc/free
+ * of irqs. Use dev_id.
+ * Christoph Hellwig : adapted to module_init/module_exit
+ * Aki Laukkanen : added power management support
+ * Arnaldo C. de Melo : added missing restore_flags in ad1848_resume
+ * Miguel Freitas : added ISA PnP support
+ * Alan Cox : Added CS4236->4239 identification
+ * Daniel T. Cobra : Alernate config/mixer for later chips
+ * Alan Cox : Merged chip idents and config code
+ *
+ * TODO
+ * APM save restore assist code on IBM thinkpad
+ *
+ * Status:
+ * Tested. Believed fully functional.
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/stddef.h>
+#include <linux/slab.h>
+#include <linux/isapnp.h>
+#include <linux/pnp.h>
+#include <linux/spinlock.h>
+
+#define DEB(x)
+#define DEB1(x)
+#include "sound_config.h"
+
+#include "ad1848.h"
+#include "ad1848_mixer.h"
+
+typedef struct
+{
+ spinlock_t lock;
+ int base;
+ int irq;
+ int dma1, dma2;
+ int dual_dma; /* 1, when two DMA channels allocated */
+ int subtype;
+ unsigned char MCE_bit;
+ unsigned char saved_regs[64]; /* Includes extended register space */
+ int debug_flag;
+
+ int audio_flags;
+ int record_dev, playback_dev;
+
+ int xfer_count;
+ int audio_mode;
+ int open_mode;
+ int intr_active;
+ char *chip_name, *name;
+ int model;
+#define MD_1848 1
+#define MD_4231 2
+#define MD_4231A 3
+#define MD_1845 4
+#define MD_4232 5
+#define MD_C930 6
+#define MD_IWAVE 7
+#define MD_4235 8 /* Crystal Audio CS4235 */
+#define MD_1845_SSCAPE 9 /* Ensoniq Soundscape PNP*/
+#define MD_4236 10 /* 4236 and higher */
+#define MD_42xB 11 /* CS 42xB */
+#define MD_4239 12 /* CS4239 */
+
+ /* Mixer parameters */
+ int recmask;
+ int supported_devices, orig_devices;
+ int supported_rec_devices, orig_rec_devices;
+ int *levels;
+ short mixer_reroute[32];
+ int dev_no;
+ volatile unsigned long timer_ticks;
+ int timer_running;
+ int irq_ok;
+ mixer_ents *mix_devices;
+ int mixer_output_port;
+} ad1848_info;
+
+typedef struct ad1848_port_info
+{
+ int open_mode;
+ int speed;
+ unsigned char speed_bits;
+ int channels;
+ int audio_format;
+ unsigned char format_bits;
+}
+ad1848_port_info;
+
+static struct address_info cfg;
+static int nr_ad1848_devs;
+
+static int deskpro_xl;
+static int deskpro_m;
+static int soundpro;
+
+static volatile signed char irq2dev[17] = {
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+#ifndef EXCLUDE_TIMERS
+static int timer_installed = -1;
+#endif
+
+static int loaded;
+
+static int ad_format_mask[13 /*devc->model */ ] =
+{
+ 0,
+ AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW,
+ AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+ AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+ AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW, /* AD1845 */
+ AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+ AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+ AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+ AFMT_U8 | AFMT_S16_LE /* CS4235 */,
+ AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW /* Ensoniq Soundscape*/,
+ AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+ AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+ AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM
+};
+
+static ad1848_info adev_info[MAX_AUDIO_DEV];
+
+#define io_Index_Addr(d) ((d)->base)
+#define io_Indexed_Data(d) ((d)->base+1)
+#define io_Status(d) ((d)->base+2)
+#define io_Polled_IO(d) ((d)->base+3)
+
+static struct {
+ unsigned char flags;
+#define CAP_F_TIMER 0x01
+} capabilities [10 /*devc->model */ ] = {
+ {0}
+ ,{0} /* MD_1848 */
+ ,{CAP_F_TIMER} /* MD_4231 */
+ ,{CAP_F_TIMER} /* MD_4231A */
+ ,{CAP_F_TIMER} /* MD_1845 */
+ ,{CAP_F_TIMER} /* MD_4232 */
+ ,{0} /* MD_C930 */
+ ,{CAP_F_TIMER} /* MD_IWAVE */
+ ,{0} /* MD_4235 */
+ ,{CAP_F_TIMER} /* MD_1845_SSCAPE */
+};
+
+#ifdef CONFIG_PNP
+static int isapnp = 1;
+static int isapnpjump;
+static int reverse;
+
+static int audio_activated;
+#else
+static int isapnp;
+#endif
+
+
+
+static int ad1848_open(int dev, int mode);
+static void ad1848_close(int dev);
+static void ad1848_output_block(int dev, unsigned long buf, int count, int intrflag);
+static void ad1848_start_input(int dev, unsigned long buf, int count, int intrflag);
+static int ad1848_prepare_for_output(int dev, int bsize, int bcount);
+static int ad1848_prepare_for_input(int dev, int bsize, int bcount);
+static void ad1848_halt(int dev);
+static void ad1848_halt_input(int dev);
+static void ad1848_halt_output(int dev);
+static void ad1848_trigger(int dev, int bits);
+static irqreturn_t adintr(int irq, void *dev_id);
+
+#ifndef EXCLUDE_TIMERS
+static int ad1848_tmr_install(int dev);
+static void ad1848_tmr_reprogram(int dev);
+#endif
+
+static int ad_read(ad1848_info * devc, int reg)
+{
+ int x;
+ int timeout = 900000;
+
+ while (timeout > 0 && inb(devc->base) == 0x80) /*Are we initializing */
+ timeout--;
+
+ if(reg < 32)
+ {
+ outb(((unsigned char) (reg & 0xff) | devc->MCE_bit), io_Index_Addr(devc));
+ x = inb(io_Indexed_Data(devc));
+ }
+ else
+ {
+ int xreg, xra;
+
+ xreg = (reg & 0xff) - 32;
+ xra = (((xreg & 0x0f) << 4) & 0xf0) | 0x08 | ((xreg & 0x10) >> 2);
+ outb(((unsigned char) (23 & 0xff) | devc->MCE_bit), io_Index_Addr(devc));
+ outb(((unsigned char) (xra & 0xff)), io_Indexed_Data(devc));
+ x = inb(io_Indexed_Data(devc));
+ }
+
+ return x;
+}
+
+static void ad_write(ad1848_info * devc, int reg, int data)
+{
+ int timeout = 900000;
+
+ while (timeout > 0 && inb(devc->base) == 0x80) /* Are we initializing */
+ timeout--;
+
+ if(reg < 32)
+ {
+ outb(((unsigned char) (reg & 0xff) | devc->MCE_bit), io_Index_Addr(devc));
+ outb(((unsigned char) (data & 0xff)), io_Indexed_Data(devc));
+ }
+ else
+ {
+ int xreg, xra;
+
+ xreg = (reg & 0xff) - 32;
+ xra = (((xreg & 0x0f) << 4) & 0xf0) | 0x08 | ((xreg & 0x10) >> 2);
+ outb(((unsigned char) (23 & 0xff) | devc->MCE_bit), io_Index_Addr(devc));
+ outb(((unsigned char) (xra & 0xff)), io_Indexed_Data(devc));
+ outb((unsigned char) (data & 0xff), io_Indexed_Data(devc));
+ }
+}
+
+static void wait_for_calibration(ad1848_info * devc)
+{
+ int timeout = 0;
+
+ /*
+ * Wait until the auto calibration process has finished.
+ *
+ * 1) Wait until the chip becomes ready (reads don't return 0x80).
+ * 2) Wait until the ACI bit of I11 gets on and then off.
+ */
+
+ timeout = 100000;
+ while (timeout > 0 && inb(devc->base) == 0x80)
+ timeout--;
+ if (inb(devc->base) & 0x80)
+ printk(KERN_WARNING "ad1848: Auto calibration timed out(1).\n");
+
+ timeout = 100;
+ while (timeout > 0 && !(ad_read(devc, 11) & 0x20))
+ timeout--;
+ if (!(ad_read(devc, 11) & 0x20))
+ return;
+
+ timeout = 80000;
+ while (timeout > 0 && (ad_read(devc, 11) & 0x20))
+ timeout--;
+ if (ad_read(devc, 11) & 0x20)
+ if ((devc->model != MD_1845) && (devc->model != MD_1845_SSCAPE))
+ printk(KERN_WARNING "ad1848: Auto calibration timed out(3).\n");
+}
+
+static void ad_mute(ad1848_info * devc)
+{
+ int i;
+ unsigned char prev;
+
+ /*
+ * Save old register settings and mute output channels
+ */
+
+ for (i = 6; i < 8; i++)
+ {
+ prev = devc->saved_regs[i] = ad_read(devc, i);
+ }
+
+}
+
+static void ad_unmute(ad1848_info * devc)
+{
+}
+
+static void ad_enter_MCE(ad1848_info * devc)
+{
+ int timeout = 1000;
+ unsigned short prev;
+
+ while (timeout > 0 && inb(devc->base) == 0x80) /*Are we initializing */
+ timeout--;
+
+ devc->MCE_bit = 0x40;
+ prev = inb(io_Index_Addr(devc));
+ if (prev & 0x40)
+ {
+ return;
+ }
+ outb((devc->MCE_bit), io_Index_Addr(devc));
+}
+
+static void ad_leave_MCE(ad1848_info * devc)
+{
+ unsigned char prev, acal;
+ int timeout = 1000;
+
+ while (timeout > 0 && inb(devc->base) == 0x80) /*Are we initializing */
+ timeout--;
+
+ acal = ad_read(devc, 9);
+
+ devc->MCE_bit = 0x00;
+ prev = inb(io_Index_Addr(devc));
+ outb((0x00), io_Index_Addr(devc)); /* Clear the MCE bit */
+
+ if ((prev & 0x40) == 0) /* Not in MCE mode */
+ {
+ return;
+ }
+ outb((0x00), io_Index_Addr(devc)); /* Clear the MCE bit */
+ if (acal & 0x08) /* Auto calibration is enabled */
+ wait_for_calibration(devc);
+}
+
+static int ad1848_set_recmask(ad1848_info * devc, int mask)
+{
+ unsigned char recdev;
+ int i, n;
+ unsigned long flags;
+
+ mask &= devc->supported_rec_devices;
+
+ /* Rename the mixer bits if necessary */
+ for (i = 0; i < 32; i++)
+ {
+ if (devc->mixer_reroute[i] != i)
+ {
+ if (mask & (1 << i))
+ {
+ mask &= ~(1 << i);
+ mask |= (1 << devc->mixer_reroute[i]);
+ }
+ }
+ }
+
+ n = 0;
+ for (i = 0; i < 32; i++) /* Count selected device bits */
+ if (mask & (1 << i))
+ n++;
+
+ spin_lock_irqsave(&devc->lock,flags);
+ if (!soundpro) {
+ if (n == 0)
+ mask = SOUND_MASK_MIC;
+ else if (n != 1) { /* Too many devices selected */
+ mask &= ~devc->recmask; /* Filter out active settings */
+
+ n = 0;
+ for (i = 0; i < 32; i++) /* Count selected device bits */
+ if (mask & (1 << i))
+ n++;
+
+ if (n != 1)
+ mask = SOUND_MASK_MIC;
+ }
+ switch (mask) {
+ case SOUND_MASK_MIC:
+ recdev = 2;
+ break;
+
+ case SOUND_MASK_LINE:
+ case SOUND_MASK_LINE3:
+ recdev = 0;
+ break;
+
+ case SOUND_MASK_CD:
+ case SOUND_MASK_LINE1:
+ recdev = 1;
+ break;
+
+ case SOUND_MASK_IMIX:
+ recdev = 3;
+ break;
+
+ default:
+ mask = SOUND_MASK_MIC;
+ recdev = 2;
+ }
+
+ recdev <<= 6;
+ ad_write(devc, 0, (ad_read(devc, 0) & 0x3f) | recdev);
+ ad_write(devc, 1, (ad_read(devc, 1) & 0x3f) | recdev);
+ } else { /* soundpro */
+ unsigned char val;
+ int set_rec_bit;
+ int j;
+
+ for (i = 0; i < 32; i++) { /* For each bit */
+ if ((devc->supported_rec_devices & (1 << i)) == 0)
+ continue; /* Device not supported */
+
+ for (j = LEFT_CHN; j <= RIGHT_CHN; j++) {
+ if (devc->mix_devices[i][j].nbits == 0) /* Inexistent channel */
+ continue;
+
+ /*
+ * This is tricky:
+ * set_rec_bit becomes 1 if the corresponding bit in mask is set
+ * then it gets flipped if the polarity is inverse
+ */
+ set_rec_bit = ((mask & (1 << i)) != 0) ^ devc->mix_devices[i][j].recpol;
+
+ val = ad_read(devc, devc->mix_devices[i][j].recreg);
+ val &= ~(1 << devc->mix_devices[i][j].recpos);
+ val |= (set_rec_bit << devc->mix_devices[i][j].recpos);
+ ad_write(devc, devc->mix_devices[i][j].recreg, val);
+ }
+ }
+ }
+ spin_unlock_irqrestore(&devc->lock,flags);
+
+ /* Rename the mixer bits back if necessary */
+ for (i = 0; i < 32; i++)
+ {
+ if (devc->mixer_reroute[i] != i)
+ {
+ if (mask & (1 << devc->mixer_reroute[i]))
+ {
+ mask &= ~(1 << devc->mixer_reroute[i]);
+ mask |= (1 << i);
+ }
+ }
+ }
+ devc->recmask = mask;
+ return mask;
+}
+
+static void change_bits(ad1848_info * devc, unsigned char *regval,
+ unsigned char *muteval, int dev, int chn, int newval)
+{
+ unsigned char mask;
+ int shift;
+ int mute;
+ int mutemask;
+ int set_mute_bit;
+
+ set_mute_bit = (newval == 0) ^ devc->mix_devices[dev][chn].mutepol;
+
+ if (devc->mix_devices[dev][chn].polarity == 1) /* Reverse */
+ newval = 100 - newval;
+
+ mask = (1 << devc->mix_devices[dev][chn].nbits) - 1;
+ shift = devc->mix_devices[dev][chn].bitpos;
+
+ if (devc->mix_devices[dev][chn].mutepos == 8)
+ { /* if there is no mute bit */
+ mute = 0; /* No mute bit; do nothing special */
+ mutemask = ~0; /* No mute bit; do nothing special */
+ }
+ else
+ {
+ mute = (set_mute_bit << devc->mix_devices[dev][chn].mutepos);
+ mutemask = ~(1 << devc->mix_devices[dev][chn].mutepos);
+ }
+
+ newval = (int) ((newval * mask) + 50) / 100; /* Scale it */
+ *regval &= ~(mask << shift); /* Clear bits */
+ *regval |= (newval & mask) << shift; /* Set new value */
+
+ *muteval &= mutemask;
+ *muteval |= mute;
+}
+
+static int ad1848_mixer_get(ad1848_info * devc, int dev)
+{
+ if (!((1 << dev) & devc->supported_devices))
+ return -EINVAL;
+
+ dev = devc->mixer_reroute[dev];
+
+ return devc->levels[dev];
+}
+
+static void ad1848_mixer_set_channel(ad1848_info *devc, int dev, int value, int channel)
+{
+ int regoffs, muteregoffs;
+ unsigned char val, muteval;
+ unsigned long flags;
+
+ regoffs = devc->mix_devices[dev][channel].regno;
+ muteregoffs = devc->mix_devices[dev][channel].mutereg;
+ val = ad_read(devc, regoffs);
+
+ if (muteregoffs != regoffs) {
+ muteval = ad_read(devc, muteregoffs);
+ change_bits(devc, &val, &muteval, dev, channel, value);
+ }
+ else
+ change_bits(devc, &val, &val, dev, channel, value);
+
+ spin_lock_irqsave(&devc->lock,flags);
+ ad_write(devc, regoffs, val);
+ devc->saved_regs[regoffs] = val;
+ if (muteregoffs != regoffs) {
+ ad_write(devc, muteregoffs, muteval);
+ devc->saved_regs[muteregoffs] = muteval;
+ }
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static int ad1848_mixer_set(ad1848_info * devc, int dev, int value)
+{
+ int left = value & 0x000000ff;
+ int right = (value & 0x0000ff00) >> 8;
+ int retvol;
+
+ if (dev > 31)
+ return -EINVAL;
+
+ if (!(devc->supported_devices & (1 << dev)))
+ return -EINVAL;
+
+ dev = devc->mixer_reroute[dev];
+
+ if (devc->mix_devices[dev][LEFT_CHN].nbits == 0)
+ return -EINVAL;
+
+ if (left > 100)
+ left = 100;
+ if (right > 100)
+ right = 100;
+
+ if (devc->mix_devices[dev][RIGHT_CHN].nbits == 0) /* Mono control */
+ right = left;
+
+ retvol = left | (right << 8);
+
+ /* Scale volumes */
+ left = mix_cvt[left];
+ right = mix_cvt[right];
+
+ devc->levels[dev] = retvol;
+
+ /*
+ * Set the left channel
+ */
+ ad1848_mixer_set_channel(devc, dev, left, LEFT_CHN);
+
+ /*
+ * Set the right channel
+ */
+ if (devc->mix_devices[dev][RIGHT_CHN].nbits == 0)
+ goto out;
+ ad1848_mixer_set_channel(devc, dev, right, RIGHT_CHN);
+
+ out:
+ return retvol;
+}
+
+static void ad1848_mixer_reset(ad1848_info * devc)
+{
+ int i;
+ char name[32];
+ unsigned long flags;
+
+ devc->mix_devices = &(ad1848_mix_devices[0]);
+
+ sprintf(name, "%s_%d", devc->chip_name, nr_ad1848_devs);
+
+ for (i = 0; i < 32; i++)
+ devc->mixer_reroute[i] = i;
+
+ devc->supported_rec_devices = MODE1_REC_DEVICES;
+
+ switch (devc->model)
+ {
+ case MD_4231:
+ case MD_4231A:
+ case MD_1845:
+ case MD_1845_SSCAPE:
+ devc->supported_devices = MODE2_MIXER_DEVICES;
+ break;
+
+ case MD_C930:
+ devc->supported_devices = C930_MIXER_DEVICES;
+ devc->mix_devices = &(c930_mix_devices[0]);
+ break;
+
+ case MD_IWAVE:
+ devc->supported_devices = MODE3_MIXER_DEVICES;
+ devc->mix_devices = &(iwave_mix_devices[0]);
+ break;
+
+ case MD_42xB:
+ case MD_4239:
+ devc->mix_devices = &(cs42xb_mix_devices[0]);
+ devc->supported_devices = MODE3_MIXER_DEVICES;
+ break;
+ case MD_4232:
+ case MD_4235:
+ case MD_4236:
+ devc->supported_devices = MODE3_MIXER_DEVICES;
+ break;
+
+ case MD_1848:
+ if (soundpro) {
+ devc->supported_devices = SPRO_MIXER_DEVICES;
+ devc->supported_rec_devices = SPRO_REC_DEVICES;
+ devc->mix_devices = &(spro_mix_devices[0]);
+ break;
+ }
+
+ default:
+ devc->supported_devices = MODE1_MIXER_DEVICES;
+ }
+
+ devc->orig_devices = devc->supported_devices;
+ devc->orig_rec_devices = devc->supported_rec_devices;
+
+ devc->levels = load_mixer_volumes(name, default_mixer_levels, 1);
+
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ {
+ if (devc->supported_devices & (1 << i))
+ ad1848_mixer_set(devc, i, devc->levels[i]);
+ }
+
+ ad1848_set_recmask(devc, SOUND_MASK_MIC);
+
+ devc->mixer_output_port = devc->levels[31] | AUDIO_HEADPHONE | AUDIO_LINE_OUT;
+
+ spin_lock_irqsave(&devc->lock,flags);
+ if (!soundpro) {
+ if (devc->mixer_output_port & AUDIO_SPEAKER)
+ ad_write(devc, 26, ad_read(devc, 26) & ~0x40); /* Unmute mono out */
+ else
+ ad_write(devc, 26, ad_read(devc, 26) | 0x40); /* Mute mono out */
+ } else {
+ /*
+ * From the "wouldn't it be nice if the mixer API had (better)
+ * support for custom stuff" category
+ */
+ /* Enable surround mode and SB16 mixer */
+ ad_write(devc, 16, 0x60);
+ }
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static int ad1848_mixer_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+ ad1848_info *devc = mixer_devs[dev]->devc;
+ int val;
+
+ if (cmd == SOUND_MIXER_PRIVATE1)
+ {
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+
+ if (val != 0xffff)
+ {
+ unsigned long flags;
+ val &= (AUDIO_SPEAKER | AUDIO_HEADPHONE | AUDIO_LINE_OUT);
+ devc->mixer_output_port = val;
+ val |= AUDIO_HEADPHONE | AUDIO_LINE_OUT; /* Always on */
+ devc->mixer_output_port = val;
+ spin_lock_irqsave(&devc->lock,flags);
+ if (val & AUDIO_SPEAKER)
+ ad_write(devc, 26, ad_read(devc, 26) & ~0x40); /* Unmute mono out */
+ else
+ ad_write(devc, 26, ad_read(devc, 26) | 0x40); /* Mute mono out */
+ spin_unlock_irqrestore(&devc->lock,flags);
+ }
+ val = devc->mixer_output_port;
+ return put_user(val, (int __user *)arg);
+ }
+ if (cmd == SOUND_MIXER_PRIVATE2)
+ {
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+ return(ad1848_control(AD1848_MIXER_REROUTE, val));
+ }
+ if (((cmd >> 8) & 0xff) == 'M')
+ {
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+ {
+ switch (cmd & 0xff)
+ {
+ case SOUND_MIXER_RECSRC:
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+ val = ad1848_set_recmask(devc, val);
+ break;
+
+ default:
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+ val = ad1848_mixer_set(devc, cmd & 0xff, val);
+ break;
+ }
+ return put_user(val, (int __user *)arg);
+ }
+ else
+ {
+ switch (cmd & 0xff)
+ {
+ /*
+ * Return parameters
+ */
+
+ case SOUND_MIXER_RECSRC:
+ val = devc->recmask;
+ break;
+
+ case SOUND_MIXER_DEVMASK:
+ val = devc->supported_devices;
+ break;
+
+ case SOUND_MIXER_STEREODEVS:
+ val = devc->supported_devices;
+ if (devc->model != MD_C930)
+ val &= ~(SOUND_MASK_SPEAKER | SOUND_MASK_IMIX);
+ break;
+
+ case SOUND_MIXER_RECMASK:
+ val = devc->supported_rec_devices;
+ break;
+
+ case SOUND_MIXER_CAPS:
+ val=SOUND_CAP_EXCL_INPUT;
+ break;
+
+ default:
+ val = ad1848_mixer_get(devc, cmd & 0xff);
+ break;
+ }
+ return put_user(val, (int __user *)arg);
+ }
+ }
+ else
+ return -EINVAL;
+}
+
+static int ad1848_set_speed(int dev, int arg)
+{
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+ /*
+ * The sampling speed is encoded in the least significant nibble of I8. The
+ * LSB selects the clock source (0=24.576 MHz, 1=16.9344 MHz) and other
+ * three bits select the divisor (indirectly):
+ *
+ * The available speeds are in the following table. Keep the speeds in
+ * the increasing order.
+ */
+ typedef struct
+ {
+ int speed;
+ unsigned char bits;
+ }
+ speed_struct;
+
+ static speed_struct speed_table[] =
+ {
+ {5510, (0 << 1) | 1},
+ {5510, (0 << 1) | 1},
+ {6620, (7 << 1) | 1},
+ {8000, (0 << 1) | 0},
+ {9600, (7 << 1) | 0},
+ {11025, (1 << 1) | 1},
+ {16000, (1 << 1) | 0},
+ {18900, (2 << 1) | 1},
+ {22050, (3 << 1) | 1},
+ {27420, (2 << 1) | 0},
+ {32000, (3 << 1) | 0},
+ {33075, (6 << 1) | 1},
+ {37800, (4 << 1) | 1},
+ {44100, (5 << 1) | 1},
+ {48000, (6 << 1) | 0}
+ };
+
+ int i, n, selected = -1;
+
+ n = sizeof(speed_table) / sizeof(speed_struct);
+
+ if (arg <= 0)
+ return portc->speed;
+
+ if (devc->model == MD_1845 || devc->model == MD_1845_SSCAPE) /* AD1845 has different timer than others */
+ {
+ if (arg < 4000)
+ arg = 4000;
+ if (arg > 50000)
+ arg = 50000;
+
+ portc->speed = arg;
+ portc->speed_bits = speed_table[3].bits;
+ return portc->speed;
+ }
+ if (arg < speed_table[0].speed)
+ selected = 0;
+ if (arg > speed_table[n - 1].speed)
+ selected = n - 1;
+
+ for (i = 1 /*really */ ; selected == -1 && i < n; i++)
+ {
+ if (speed_table[i].speed == arg)
+ selected = i;
+ else if (speed_table[i].speed > arg)
+ {
+ int diff1, diff2;
+
+ diff1 = arg - speed_table[i - 1].speed;
+ diff2 = speed_table[i].speed - arg;
+
+ if (diff1 < diff2)
+ selected = i - 1;
+ else
+ selected = i;
+ }
+ }
+ if (selected == -1)
+ {
+ printk(KERN_WARNING "ad1848: Can't find speed???\n");
+ selected = 3;
+ }
+ portc->speed = speed_table[selected].speed;
+ portc->speed_bits = speed_table[selected].bits;
+ return portc->speed;
+}
+
+static short ad1848_set_channels(int dev, short arg)
+{
+ ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+ if (arg != 1 && arg != 2)
+ return portc->channels;
+
+ portc->channels = arg;
+ return arg;
+}
+
+static unsigned int ad1848_set_bits(int dev, unsigned int arg)
+{
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+ static struct format_tbl
+ {
+ int format;
+ unsigned char bits;
+ }
+ format2bits[] =
+ {
+ {
+ 0, 0
+ }
+ ,
+ {
+ AFMT_MU_LAW, 1
+ }
+ ,
+ {
+ AFMT_A_LAW, 3
+ }
+ ,
+ {
+ AFMT_IMA_ADPCM, 5
+ }
+ ,
+ {
+ AFMT_U8, 0
+ }
+ ,
+ {
+ AFMT_S16_LE, 2
+ }
+ ,
+ {
+ AFMT_S16_BE, 6
+ }
+ ,
+ {
+ AFMT_S8, 0
+ }
+ ,
+ {
+ AFMT_U16_LE, 0
+ }
+ ,
+ {
+ AFMT_U16_BE, 0
+ }
+ };
+ int i, n = sizeof(format2bits) / sizeof(struct format_tbl);
+
+ if (arg == 0)
+ return portc->audio_format;
+
+ if (!(arg & ad_format_mask[devc->model]))
+ arg = AFMT_U8;
+
+ portc->audio_format = arg;
+
+ for (i = 0; i < n; i++)
+ if (format2bits[i].format == arg)
+ {
+ if ((portc->format_bits = format2bits[i].bits) == 0)
+ return portc->audio_format = AFMT_U8; /* Was not supported */
+
+ return arg;
+ }
+ /* Still hanging here. Something must be terribly wrong */
+ portc->format_bits = 0;
+ return portc->audio_format = AFMT_U8;
+}
+
+static struct audio_driver ad1848_audio_driver =
+{
+ .owner = THIS_MODULE,
+ .open = ad1848_open,
+ .close = ad1848_close,
+ .output_block = ad1848_output_block,
+ .start_input = ad1848_start_input,
+ .prepare_for_input = ad1848_prepare_for_input,
+ .prepare_for_output = ad1848_prepare_for_output,
+ .halt_io = ad1848_halt,
+ .halt_input = ad1848_halt_input,
+ .halt_output = ad1848_halt_output,
+ .trigger = ad1848_trigger,
+ .set_speed = ad1848_set_speed,
+ .set_bits = ad1848_set_bits,
+ .set_channels = ad1848_set_channels
+};
+
+static struct mixer_operations ad1848_mixer_operations =
+{
+ .owner = THIS_MODULE,
+ .id = "SOUNDPORT",
+ .name = "AD1848/CS4248/CS4231",
+ .ioctl = ad1848_mixer_ioctl
+};
+
+static int ad1848_open(int dev, int mode)
+{
+ ad1848_info *devc;
+ ad1848_port_info *portc;
+ unsigned long flags;
+
+ if (dev < 0 || dev >= num_audiodevs)
+ return -ENXIO;
+
+ devc = (ad1848_info *) audio_devs[dev]->devc;
+ portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+ /* here we don't have to protect against intr */
+ spin_lock(&devc->lock);
+ if (portc->open_mode || (devc->open_mode & mode))
+ {
+ spin_unlock(&devc->lock);
+ return -EBUSY;
+ }
+ devc->dual_dma = 0;
+
+ if (audio_devs[dev]->flags & DMA_DUPLEX)
+ {
+ devc->dual_dma = 1;
+ }
+ devc->intr_active = 0;
+ devc->audio_mode = 0;
+ devc->open_mode |= mode;
+ portc->open_mode = mode;
+ spin_unlock(&devc->lock);
+ ad1848_trigger(dev, 0);
+
+ if (mode & OPEN_READ)
+ devc->record_dev = dev;
+ if (mode & OPEN_WRITE)
+ devc->playback_dev = dev;
+/*
+ * Mute output until the playback really starts. This decreases clicking (hope so).
+ */
+ spin_lock_irqsave(&devc->lock,flags);
+ ad_mute(devc);
+ spin_unlock_irqrestore(&devc->lock,flags);
+
+ return 0;
+}
+
+static void ad1848_close(int dev)
+{
+ unsigned long flags;
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+ DEB(printk("ad1848_close(void)\n"));
+
+ devc->intr_active = 0;
+ ad1848_halt(dev);
+
+ spin_lock_irqsave(&devc->lock,flags);
+
+ devc->audio_mode = 0;
+ devc->open_mode &= ~portc->open_mode;
+ portc->open_mode = 0;
+
+ ad_unmute(devc);
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_output_block(int dev, unsigned long buf, int count, int intrflag)
+{
+ unsigned long flags, cnt;
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+ cnt = count;
+
+ if (portc->audio_format == AFMT_IMA_ADPCM)
+ {
+ cnt /= 4;
+ }
+ else
+ {
+ if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE)) /* 16 bit data */
+ cnt >>= 1;
+ }
+ if (portc->channels > 1)
+ cnt >>= 1;
+ cnt--;
+
+ if ((devc->audio_mode & PCM_ENABLE_OUTPUT) && (audio_devs[dev]->flags & DMA_AUTOMODE) &&
+ intrflag &&
+ cnt == devc->xfer_count)
+ {
+ devc->audio_mode |= PCM_ENABLE_OUTPUT;
+ devc->intr_active = 1;
+ return; /*
+ * Auto DMA mode on. No need to react
+ */
+ }
+ spin_lock_irqsave(&devc->lock,flags);
+
+ ad_write(devc, 15, (unsigned char) (cnt & 0xff));
+ ad_write(devc, 14, (unsigned char) ((cnt >> 8) & 0xff));
+
+ devc->xfer_count = cnt;
+ devc->audio_mode |= PCM_ENABLE_OUTPUT;
+ devc->intr_active = 1;
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_start_input(int dev, unsigned long buf, int count, int intrflag)
+{
+ unsigned long flags, cnt;
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+ cnt = count;
+ if (portc->audio_format == AFMT_IMA_ADPCM)
+ {
+ cnt /= 4;
+ }
+ else
+ {
+ if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE)) /* 16 bit data */
+ cnt >>= 1;
+ }
+ if (portc->channels > 1)
+ cnt >>= 1;
+ cnt--;
+
+ if ((devc->audio_mode & PCM_ENABLE_INPUT) && (audio_devs[dev]->flags & DMA_AUTOMODE) &&
+ intrflag &&
+ cnt == devc->xfer_count)
+ {
+ devc->audio_mode |= PCM_ENABLE_INPUT;
+ devc->intr_active = 1;
+ return; /*
+ * Auto DMA mode on. No need to react
+ */
+ }
+ spin_lock_irqsave(&devc->lock,flags);
+
+ if (devc->model == MD_1848)
+ {
+ ad_write(devc, 15, (unsigned char) (cnt & 0xff));
+ ad_write(devc, 14, (unsigned char) ((cnt >> 8) & 0xff));
+ }
+ else
+ {
+ ad_write(devc, 31, (unsigned char) (cnt & 0xff));
+ ad_write(devc, 30, (unsigned char) ((cnt >> 8) & 0xff));
+ }
+
+ ad_unmute(devc);
+
+ devc->xfer_count = cnt;
+ devc->audio_mode |= PCM_ENABLE_INPUT;
+ devc->intr_active = 1;
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static int ad1848_prepare_for_output(int dev, int bsize, int bcount)
+{
+ int timeout;
+ unsigned char fs, old_fs, tmp = 0;
+ unsigned long flags;
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+ ad_mute(devc);
+
+ spin_lock_irqsave(&devc->lock,flags);
+ fs = portc->speed_bits | (portc->format_bits << 5);
+
+ if (portc->channels > 1)
+ fs |= 0x10;
+
+ ad_enter_MCE(devc); /* Enables changes to the format select reg */
+
+ if (devc->model == MD_1845 || devc->model == MD_1845_SSCAPE) /* Use alternate speed select registers */
+ {
+ fs &= 0xf0; /* Mask off the rate select bits */
+
+ ad_write(devc, 22, (portc->speed >> 8) & 0xff); /* Speed MSB */
+ ad_write(devc, 23, portc->speed & 0xff); /* Speed LSB */
+ }
+ old_fs = ad_read(devc, 8);
+
+ if (devc->model == MD_4232 || devc->model >= MD_4236)
+ {
+ tmp = ad_read(devc, 16);
+ ad_write(devc, 16, tmp | 0x30);
+ }
+ if (devc->model == MD_IWAVE)
+ ad_write(devc, 17, 0xc2); /* Disable variable frequency select */
+
+ ad_write(devc, 8, fs);
+
+ /*
+ * Write to I8 starts resynchronization. Wait until it completes.
+ */
+
+ timeout = 0;
+ while (timeout < 100 && inb(devc->base) != 0x80)
+ timeout++;
+ timeout = 0;
+ while (timeout < 10000 && inb(devc->base) == 0x80)
+ timeout++;
+
+ if (devc->model >= MD_4232)
+ ad_write(devc, 16, tmp & ~0x30);
+
+ ad_leave_MCE(devc); /*
+ * Starts the calibration process.
+ */
+ spin_unlock_irqrestore(&devc->lock,flags);
+ devc->xfer_count = 0;
+
+#ifndef EXCLUDE_TIMERS
+ if (dev == timer_installed && devc->timer_running)
+ if ((fs & 0x01) != (old_fs & 0x01))
+ {
+ ad1848_tmr_reprogram(dev);
+ }
+#endif
+ ad1848_halt_output(dev);
+ return 0;
+}
+
+static int ad1848_prepare_for_input(int dev, int bsize, int bcount)
+{
+ int timeout;
+ unsigned char fs, old_fs, tmp = 0;
+ unsigned long flags;
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+ if (devc->audio_mode)
+ return 0;
+
+ spin_lock_irqsave(&devc->lock,flags);
+ fs = portc->speed_bits | (portc->format_bits << 5);
+
+ if (portc->channels > 1)
+ fs |= 0x10;
+
+ ad_enter_MCE(devc); /* Enables changes to the format select reg */
+
+ if ((devc->model == MD_1845) || (devc->model == MD_1845_SSCAPE)) /* Use alternate speed select registers */
+ {
+ fs &= 0xf0; /* Mask off the rate select bits */
+
+ ad_write(devc, 22, (portc->speed >> 8) & 0xff); /* Speed MSB */
+ ad_write(devc, 23, portc->speed & 0xff); /* Speed LSB */
+ }
+ if (devc->model == MD_4232)
+ {
+ tmp = ad_read(devc, 16);
+ ad_write(devc, 16, tmp | 0x30);
+ }
+ if (devc->model == MD_IWAVE)
+ ad_write(devc, 17, 0xc2); /* Disable variable frequency select */
+
+ /*
+ * If mode >= 2 (CS4231), set I28. It's the capture format register.
+ */
+
+ if (devc->model != MD_1848)
+ {
+ old_fs = ad_read(devc, 28);
+ ad_write(devc, 28, fs);
+
+ /*
+ * Write to I28 starts resynchronization. Wait until it completes.
+ */
+
+ timeout = 0;
+ while (timeout < 100 && inb(devc->base) != 0x80)
+ timeout++;
+
+ timeout = 0;
+ while (timeout < 10000 && inb(devc->base) == 0x80)
+ timeout++;
+
+ if (devc->model != MD_1848 && devc->model != MD_1845 && devc->model != MD_1845_SSCAPE)
+ {
+ /*
+ * CS4231 compatible devices don't have separate sampling rate selection
+ * register for recording an playback. The I8 register is shared so we have to
+ * set the speed encoding bits of it too.
+ */
+ unsigned char tmp = portc->speed_bits | (ad_read(devc, 8) & 0xf0);
+
+ ad_write(devc, 8, tmp);
+ /*
+ * Write to I8 starts resynchronization. Wait until it completes.
+ */
+ timeout = 0;
+ while (timeout < 100 && inb(devc->base) != 0x80)
+ timeout++;
+
+ timeout = 0;
+ while (timeout < 10000 && inb(devc->base) == 0x80)
+ timeout++;
+ }
+ }
+ else
+ { /* For AD1848 set I8. */
+
+ old_fs = ad_read(devc, 8);
+ ad_write(devc, 8, fs);
+ /*
+ * Write to I8 starts resynchronization. Wait until it completes.
+ */
+ timeout = 0;
+ while (timeout < 100 && inb(devc->base) != 0x80)
+ timeout++;
+ timeout = 0;
+ while (timeout < 10000 && inb(devc->base) == 0x80)
+ timeout++;
+ }
+
+ if (devc->model == MD_4232)
+ ad_write(devc, 16, tmp & ~0x30);
+
+ ad_leave_MCE(devc); /*
+ * Starts the calibration process.
+ */
+ spin_unlock_irqrestore(&devc->lock,flags);
+ devc->xfer_count = 0;
+
+#ifndef EXCLUDE_TIMERS
+ if (dev == timer_installed && devc->timer_running)
+ {
+ if ((fs & 0x01) != (old_fs & 0x01))
+ {
+ ad1848_tmr_reprogram(dev);
+ }
+ }
+#endif
+ ad1848_halt_input(dev);
+ return 0;
+}
+
+static void ad1848_halt(int dev)
+{
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+ unsigned char bits = ad_read(devc, 9);
+
+ if (bits & 0x01 && (portc->open_mode & OPEN_WRITE))
+ ad1848_halt_output(dev);
+
+ if (bits & 0x02 && (portc->open_mode & OPEN_READ))
+ ad1848_halt_input(dev);
+ devc->audio_mode = 0;
+}
+
+static void ad1848_halt_input(int dev)
+{
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ unsigned long flags;
+
+ if (!(ad_read(devc, 9) & 0x02))
+ return; /* Capture not enabled */
+
+ spin_lock_irqsave(&devc->lock,flags);
+
+ ad_mute(devc);
+
+ {
+ int tmout;
+
+ if(!isa_dma_bridge_buggy)
+ disable_dma(audio_devs[dev]->dmap_in->dma);
+
+ for (tmout = 0; tmout < 100000; tmout++)
+ if (ad_read(devc, 11) & 0x10)
+ break;
+ ad_write(devc, 9, ad_read(devc, 9) & ~0x02); /* Stop capture */
+
+ if(!isa_dma_bridge_buggy)
+ enable_dma(audio_devs[dev]->dmap_in->dma);
+ devc->audio_mode &= ~PCM_ENABLE_INPUT;
+ }
+
+ outb(0, io_Status(devc)); /* Clear interrupt status */
+ outb(0, io_Status(devc)); /* Clear interrupt status */
+
+ devc->audio_mode &= ~PCM_ENABLE_INPUT;
+
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_halt_output(int dev)
+{
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ unsigned long flags;
+
+ if (!(ad_read(devc, 9) & 0x01))
+ return; /* Playback not enabled */
+
+ spin_lock_irqsave(&devc->lock,flags);
+
+ ad_mute(devc);
+ {
+ int tmout;
+
+ if(!isa_dma_bridge_buggy)
+ disable_dma(audio_devs[dev]->dmap_out->dma);
+
+ for (tmout = 0; tmout < 100000; tmout++)
+ if (ad_read(devc, 11) & 0x10)
+ break;
+ ad_write(devc, 9, ad_read(devc, 9) & ~0x01); /* Stop playback */
+
+ if(!isa_dma_bridge_buggy)
+ enable_dma(audio_devs[dev]->dmap_out->dma);
+
+ devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
+ }
+
+ outb((0), io_Status(devc)); /* Clear interrupt status */
+ outb((0), io_Status(devc)); /* Clear interrupt status */
+
+ devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
+
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_trigger(int dev, int state)
+{
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+ unsigned long flags;
+ unsigned char tmp, old;
+
+ spin_lock_irqsave(&devc->lock,flags);
+ state &= devc->audio_mode;
+
+ tmp = old = ad_read(devc, 9);
+
+ if (portc->open_mode & OPEN_READ)
+ {
+ if (state & PCM_ENABLE_INPUT)
+ tmp |= 0x02;
+ else
+ tmp &= ~0x02;
+ }
+ if (portc->open_mode & OPEN_WRITE)
+ {
+ if (state & PCM_ENABLE_OUTPUT)
+ tmp |= 0x01;
+ else
+ tmp &= ~0x01;
+ }
+ /* ad_mute(devc); */
+ if (tmp != old)
+ {
+ ad_write(devc, 9, tmp);
+ ad_unmute(devc);
+ }
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_init_hw(ad1848_info * devc)
+{
+ int i;
+ int *init_values;
+
+ /*
+ * Initial values for the indirect registers of CS4248/AD1848.
+ */
+ static int init_values_a[] =
+ {
+ 0xa8, 0xa8, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00,
+ 0x00, 0x0c, 0x02, 0x00, 0x8a, 0x01, 0x00, 0x00,
+
+ /* Positions 16 to 31 just for CS4231/2 and ad1845 */
+ 0x80, 0x00, 0x10, 0x10, 0x00, 0x00, 0x1f, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ static int init_values_b[] =
+ {
+ /*
+ Values for the newer chips
+ Some of the register initialization values were changed. In
+ order to get rid of the click that preceded PCM playback,
+ calibration was disabled on the 10th byte. On that same byte,
+ dual DMA was enabled; on the 11th byte, ADC dithering was
+ enabled, since that is theoretically desirable; on the 13th
+ byte, Mode 3 was selected, to enable access to extended
+ registers.
+ */
+ 0xa8, 0xa8, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x06, 0x00, 0xe0, 0x01, 0x00, 0x00,
+ 0x80, 0x00, 0x10, 0x10, 0x00, 0x00, 0x1f, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ /*
+ * Select initialisation data
+ */
+
+ init_values = init_values_a;
+ if(devc->model >= MD_4236)
+ init_values = init_values_b;
+
+ for (i = 0; i < 16; i++)
+ ad_write(devc, i, init_values[i]);
+
+
+ ad_mute(devc); /* Initialize some variables */
+ ad_unmute(devc); /* Leave it unmuted now */
+
+ if (devc->model > MD_1848)
+ {
+ if (devc->model == MD_1845_SSCAPE)
+ ad_write(devc, 12, ad_read(devc, 12) | 0x50);
+ else
+ ad_write(devc, 12, ad_read(devc, 12) | 0x40); /* Mode2 = enabled */
+
+ if (devc->model == MD_IWAVE)
+ ad_write(devc, 12, 0x6c); /* Select codec mode 3 */
+
+ if (devc->model != MD_1845_SSCAPE)
+ for (i = 16; i < 32; i++)
+ ad_write(devc, i, init_values[i]);
+
+ if (devc->model == MD_IWAVE)
+ ad_write(devc, 16, 0x30); /* Playback and capture counters enabled */
+ }
+ if (devc->model > MD_1848)
+ {
+ if (devc->audio_flags & DMA_DUPLEX)
+ ad_write(devc, 9, ad_read(devc, 9) & ~0x04); /* Dual DMA mode */
+ else
+ ad_write(devc, 9, ad_read(devc, 9) | 0x04); /* Single DMA mode */
+
+ if (devc->model == MD_1845 || devc->model == MD_1845_SSCAPE)
+ ad_write(devc, 27, ad_read(devc, 27) | 0x08); /* Alternate freq select enabled */
+
+ if (devc->model == MD_IWAVE)
+ { /* Some magic Interwave specific initialization */
+ ad_write(devc, 12, 0x6c); /* Select codec mode 3 */
+ ad_write(devc, 16, 0x30); /* Playback and capture counters enabled */
+ ad_write(devc, 17, 0xc2); /* Alternate feature enable */
+ }
+ }
+ else
+ {
+ devc->audio_flags &= ~DMA_DUPLEX;
+ ad_write(devc, 9, ad_read(devc, 9) | 0x04); /* Single DMA mode */
+ if (soundpro)
+ ad_write(devc, 12, ad_read(devc, 12) | 0x40); /* Mode2 = enabled */
+ }
+
+ outb((0), io_Status(devc)); /* Clear pending interrupts */
+
+ /*
+ * Toggle the MCE bit. It completes the initialization phase.
+ */
+
+ ad_enter_MCE(devc); /* In case the bit was off */
+ ad_leave_MCE(devc);
+
+ ad1848_mixer_reset(devc);
+}
+
+int ad1848_detect(struct resource *ports, int *ad_flags, int *osp)
+{
+ unsigned char tmp;
+ ad1848_info *devc = &adev_info[nr_ad1848_devs];
+ unsigned char tmp1 = 0xff, tmp2 = 0xff;
+ int optiC930 = 0; /* OPTi 82C930 flag */
+ int interwave = 0;
+ int ad1847_flag = 0;
+ int cs4248_flag = 0;
+ int sscape_flag = 0;
+ int io_base = ports->start;
+
+ int i;
+
+ DDB(printk("ad1848_detect(%x)\n", io_base));
+
+ if (ad_flags)
+ {
+ if (*ad_flags == 0x12345678)
+ {
+ interwave = 1;
+ *ad_flags = 0;
+ }
+
+ if (*ad_flags == 0x87654321)
+ {
+ sscape_flag = 1;
+ *ad_flags = 0;
+ }
+
+ if (*ad_flags == 0x12345677)
+ {
+ cs4248_flag = 1;
+ *ad_flags = 0;
+ }
+ }
+ if (nr_ad1848_devs >= MAX_AUDIO_DEV)
+ {
+ printk(KERN_ERR "ad1848 - Too many audio devices\n");
+ return 0;
+ }
+ spin_lock_init(&devc->lock);
+ devc->base = io_base;
+ devc->irq_ok = 0;
+ devc->timer_running = 0;
+ devc->MCE_bit = 0x40;
+ devc->irq = 0;
+ devc->open_mode = 0;
+ devc->chip_name = devc->name = "AD1848";
+ devc->model = MD_1848; /* AD1848 or CS4248 */
+ devc->levels = NULL;
+ devc->debug_flag = 0;
+
+ /*
+ * Check that the I/O address is in use.
+ *
+ * The bit 0x80 of the base I/O port is known to be 0 after the
+ * chip has performed its power on initialization. Just assume
+ * this has happened before the OS is starting.
+ *
+ * If the I/O address is unused, it typically returns 0xff.
+ */
+
+ if (inb(devc->base) == 0xff)
+ {
+ DDB(printk("ad1848_detect: The base I/O address appears to be dead\n"));
+ }
+
+ /*
+ * Wait for the device to stop initialization
+ */
+
+ DDB(printk("ad1848_detect() - step 0\n"));
+
+ for (i = 0; i < 10000000; i++)
+ {
+ unsigned char x = inb(devc->base);
+
+ if (x == 0xff || !(x & 0x80))
+ break;
+ }
+
+ DDB(printk("ad1848_detect() - step A\n"));
+
+ if (inb(devc->base) == 0x80) /* Not ready. Let's wait */
+ ad_leave_MCE(devc);
+
+ if ((inb(devc->base) & 0x80) != 0x00) /* Not a AD1848 */
+ {
+ DDB(printk("ad1848 detect error - step A (%02x)\n", (int) inb(devc->base)));
+ return 0;
+ }
+
+ /*
+ * Test if it's possible to change contents of the indirect registers.
+ * Registers 0 and 1 are ADC volume registers. The bit 0x10 is read only
+ * so try to avoid using it.
+ */
+
+ DDB(printk("ad1848_detect() - step B\n"));
+ ad_write(devc, 0, 0xaa);
+ ad_write(devc, 1, 0x45); /* 0x55 with bit 0x10 clear */
+
+ if ((tmp1 = ad_read(devc, 0)) != 0xaa || (tmp2 = ad_read(devc, 1)) != 0x45)
+ {
+ if (tmp2 == 0x65) /* AD1847 has couple of bits hardcoded to 1 */
+ ad1847_flag = 1;
+ else
+ {
+ DDB(printk("ad1848 detect error - step B (%x/%x)\n", tmp1, tmp2));
+ return 0;
+ }
+ }
+ DDB(printk("ad1848_detect() - step C\n"));
+ ad_write(devc, 0, 0x45);
+ ad_write(devc, 1, 0xaa);
+
+ if ((tmp1 = ad_read(devc, 0)) != 0x45 || (tmp2 = ad_read(devc, 1)) != 0xaa)
+ {
+ if (tmp2 == 0x8a) /* AD1847 has few bits hardcoded to 1 */
+ ad1847_flag = 1;
+ else
+ {
+ DDB(printk("ad1848 detect error - step C (%x/%x)\n", tmp1, tmp2));
+ return 0;
+ }
+ }
+
+ /*
+ * The indirect register I12 has some read only bits. Let's
+ * try to change them.
+ */
+
+ DDB(printk("ad1848_detect() - step D\n"));
+ tmp = ad_read(devc, 12);
+ ad_write(devc, 12, (~tmp) & 0x0f);
+
+ if ((tmp & 0x0f) != ((tmp1 = ad_read(devc, 12)) & 0x0f))
+ {
+ DDB(printk("ad1848 detect error - step D (%x)\n", tmp1));
+ return 0;
+ }
+
+ /*
+ * NOTE! Last 4 bits of the reg I12 tell the chip revision.
+ * 0x01=RevB and 0x0A=RevC.
+ */
+
+ /*
+ * The original AD1848/CS4248 has just 15 indirect registers. This means
+ * that I0 and I16 should return the same value (etc.).
+ * However this doesn't work with CS4248. Actually it seems to be impossible
+ * to detect if the chip is a CS4231 or CS4248.
+ * Ensure that the Mode2 enable bit of I12 is 0. Otherwise this test fails
+ * with CS4231.
+ */
+
+ /*
+ * OPTi 82C930 has mode2 control bit in another place. This test will fail
+ * with it. Accept this situation as a possible indication of this chip.
+ */
+
+ DDB(printk("ad1848_detect() - step F\n"));
+ ad_write(devc, 12, 0); /* Mode2=disabled */
+
+ for (i = 0; i < 16; i++)
+ {
+ if ((tmp1 = ad_read(devc, i)) != (tmp2 = ad_read(devc, i + 16)))
+ {
+ DDB(printk("ad1848 detect step F(%d/%x/%x) - OPTi chip???\n", i, tmp1, tmp2));
+ if (!ad1847_flag)
+ optiC930 = 1;
+ break;
+ }
+ }
+
+ /*
+ * Try to switch the chip to mode2 (CS4231) by setting the MODE2 bit (0x40).
+ * The bit 0x80 is always 1 in CS4248 and CS4231.
+ */
+
+ DDB(printk("ad1848_detect() - step G\n"));
+
+ if (ad_flags && *ad_flags == 400)
+ *ad_flags = 0;
+ else
+ ad_write(devc, 12, 0x40); /* Set mode2, clear 0x80 */
+
+
+ if (ad_flags)
+ *ad_flags = 0;
+
+ tmp1 = ad_read(devc, 12);
+ if (tmp1 & 0x80)
+ {
+ if (ad_flags)
+ *ad_flags |= AD_F_CS4248;
+
+ devc->chip_name = "CS4248"; /* Our best knowledge just now */
+ }
+ if (optiC930 || (tmp1 & 0xc0) == (0x80 | 0x40))
+ {
+ /*
+ * CS4231 detected - is it?
+ *
+ * Verify that setting I0 doesn't change I16.
+ */
+
+ DDB(printk("ad1848_detect() - step H\n"));
+ ad_write(devc, 16, 0); /* Set I16 to known value */
+
+ ad_write(devc, 0, 0x45);
+ if ((tmp1 = ad_read(devc, 16)) != 0x45) /* No change -> CS4231? */
+ {
+ ad_write(devc, 0, 0xaa);
+ if ((tmp1 = ad_read(devc, 16)) == 0xaa) /* Rotten bits? */
+ {
+ DDB(printk("ad1848 detect error - step H(%x)\n", tmp1));
+ return 0;
+ }
+
+ /*
+ * Verify that some bits of I25 are read only.
+ */
+
+ DDB(printk("ad1848_detect() - step I\n"));
+ tmp1 = ad_read(devc, 25); /* Original bits */
+ ad_write(devc, 25, ~tmp1); /* Invert all bits */
+ if ((ad_read(devc, 25) & 0xe7) == (tmp1 & 0xe7))
+ {
+ int id;
+
+ /*
+ * It's at least CS4231
+ */
+
+ devc->chip_name = "CS4231";
+ devc->model = MD_4231;
+
+ /*
+ * It could be an AD1845 or CS4231A as well.
+ * CS4231 and AD1845 report the same revision info in I25
+ * while the CS4231A reports different.
+ */
+
+ id = ad_read(devc, 25);
+ if ((id & 0xe7) == 0x80) /* Device busy??? */
+ id = ad_read(devc, 25);
+ if ((id & 0xe7) == 0x80) /* Device still busy??? */
+ id = ad_read(devc, 25);
+ DDB(printk("ad1848_detect() - step J (%02x/%02x)\n", id, ad_read(devc, 25)));
+
+ if ((id & 0xe7) == 0x80) {
+ /*
+ * It must be a CS4231 or AD1845. The register I23 of
+ * CS4231 is undefined and it appears to be read only.
+ * AD1845 uses I23 for setting sample rate. Assume
+ * the chip is AD1845 if I23 is changeable.
+ */
+
+ unsigned char tmp = ad_read(devc, 23);
+ ad_write(devc, 23, ~tmp);
+
+ if (interwave)
+ {
+ devc->model = MD_IWAVE;
+ devc->chip_name = "IWave";
+ }
+ else if (ad_read(devc, 23) != tmp) /* AD1845 ? */
+ {
+ devc->chip_name = "AD1845";
+ devc->model = MD_1845;
+ }
+ else if (cs4248_flag)
+ {
+ if (ad_flags)
+ *ad_flags |= AD_F_CS4248;
+ devc->chip_name = "CS4248";
+ devc->model = MD_1848;
+ ad_write(devc, 12, ad_read(devc, 12) & ~0x40); /* Mode2 off */
+ }
+ ad_write(devc, 23, tmp); /* Restore */
+ }
+ else
+ {
+ switch (id & 0x1f) {
+ case 3: /* CS4236/CS4235/CS42xB/CS4239 */
+ {
+ int xid;
+ ad_write(devc, 12, ad_read(devc, 12) | 0x60); /* switch to mode 3 */
+ ad_write(devc, 23, 0x9c); /* select extended register 25 */
+ xid = inb(io_Indexed_Data(devc));
+ ad_write(devc, 12, ad_read(devc, 12) & ~0x60); /* back to mode 0 */
+ switch (xid & 0x1f)
+ {
+ case 0x00:
+ devc->chip_name = "CS4237B(B)";
+ devc->model = MD_42xB;
+ break;
+ case 0x08:
+ /* Seems to be a 4238 ?? */
+ devc->chip_name = "CS4238";
+ devc->model = MD_42xB;
+ break;
+ case 0x09:
+ devc->chip_name = "CS4238B";
+ devc->model = MD_42xB;
+ break;
+ case 0x0b:
+ devc->chip_name = "CS4236B";
+ devc->model = MD_4236;
+ break;
+ case 0x10:
+ devc->chip_name = "CS4237B";
+ devc->model = MD_42xB;
+ break;
+ case 0x1d:
+ devc->chip_name = "CS4235";
+ devc->model = MD_4235;
+ break;
+ case 0x1e:
+ devc->chip_name = "CS4239";
+ devc->model = MD_4239;
+ break;
+ default:
+ printk("Chip ident is %X.\n", xid&0x1F);
+ devc->chip_name = "CS42xx";
+ devc->model = MD_4232;
+ break;
+ }
+ }
+ break;
+
+ case 2: /* CS4232/CS4232A */
+ devc->chip_name = "CS4232";
+ devc->model = MD_4232;
+ break;
+
+ case 0:
+ if ((id & 0xe0) == 0xa0)
+ {
+ devc->chip_name = "CS4231A";
+ devc->model = MD_4231A;
+ }
+ else
+ {
+ devc->chip_name = "CS4321";
+ devc->model = MD_4231;
+ }
+ break;
+
+ default: /* maybe */
+ DDB(printk("ad1848: I25 = %02x/%02x\n", ad_read(devc, 25), ad_read(devc, 25) & 0xe7));
+ if (optiC930)
+ {
+ devc->chip_name = "82C930";
+ devc->model = MD_C930;
+ }
+ else
+ {
+ devc->chip_name = "CS4231";
+ devc->model = MD_4231;
+ }
+ }
+ }
+ }
+ ad_write(devc, 25, tmp1); /* Restore bits */
+
+ DDB(printk("ad1848_detect() - step K\n"));
+ }
+ } else if (tmp1 == 0x0a) {
+ /*
+ * Is it perhaps a SoundPro CMI8330?
+ * If so, then we should be able to change indirect registers
+ * greater than I15 after activating MODE2, even though reading
+ * back I12 does not show it.
+ */
+
+ /*
+ * Let's try comparing register values
+ */
+ for (i = 0; i < 16; i++) {
+ if ((tmp1 = ad_read(devc, i)) != (tmp2 = ad_read(devc, i + 16))) {
+ DDB(printk("ad1848 detect step H(%d/%x/%x) - SoundPro chip?\n", i, tmp1, tmp2));
+ soundpro = 1;
+ devc->chip_name = "SoundPro CMI 8330";
+ break;
+ }
+ }
+ }
+
+ DDB(printk("ad1848_detect() - step L\n"));
+ if (ad_flags)
+ {
+ if (devc->model != MD_1848)
+ *ad_flags |= AD_F_CS4231;
+ }
+ DDB(printk("ad1848_detect() - Detected OK\n"));
+
+ if (devc->model == MD_1848 && ad1847_flag)
+ devc->chip_name = "AD1847";
+
+
+ if (sscape_flag == 1)
+ devc->model = MD_1845_SSCAPE;
+
+ return 1;
+}
+
+int ad1848_init (char *name, struct resource *ports, int irq, int dma_playback,
+ int dma_capture, int share_dma, int *osp, struct module *owner)
+{
+ /*
+ * NOTE! If irq < 0, there is another driver which has allocated the IRQ
+ * so that this driver doesn't need to allocate/deallocate it.
+ * The actually used IRQ is ABS(irq).
+ */
+
+ int my_dev;
+ char dev_name[100];
+ int e;
+
+ ad1848_info *devc = &adev_info[nr_ad1848_devs];
+
+ ad1848_port_info *portc = NULL;
+
+ devc->irq = (irq > 0) ? irq : 0;
+ devc->open_mode = 0;
+ devc->timer_ticks = 0;
+ devc->dma1 = dma_playback;
+ devc->dma2 = dma_capture;
+ devc->subtype = cfg.card_subtype;
+ devc->audio_flags = DMA_AUTOMODE;
+ devc->playback_dev = devc->record_dev = 0;
+ if (name != NULL)
+ devc->name = name;
+
+ if (name != NULL && name[0] != 0)
+ sprintf(dev_name,
+ "%s (%s)", name, devc->chip_name);
+ else
+ sprintf(dev_name,
+ "Generic audio codec (%s)", devc->chip_name);
+
+ rename_region(ports, devc->name);
+
+ conf_printf2(dev_name, devc->base, devc->irq, dma_playback, dma_capture);
+
+ if (devc->model == MD_1848 || devc->model == MD_C930)
+ devc->audio_flags |= DMA_HARDSTOP;
+
+ if (devc->model > MD_1848)
+ {
+ if (devc->dma1 == devc->dma2 || devc->dma2 == -1 || devc->dma1 == -1)
+ devc->audio_flags &= ~DMA_DUPLEX;
+ else
+ devc->audio_flags |= DMA_DUPLEX;
+ }
+
+ portc = kmalloc(sizeof(ad1848_port_info), GFP_KERNEL);
+ if(portc==NULL) {
+ release_region(devc->base, 4);
+ return -1;
+ }
+
+ if ((my_dev = sound_install_audiodrv(AUDIO_DRIVER_VERSION,
+ dev_name,
+ &ad1848_audio_driver,
+ sizeof(struct audio_driver),
+ devc->audio_flags,
+ ad_format_mask[devc->model],
+ devc,
+ dma_playback,
+ dma_capture)) < 0)
+ {
+ release_region(devc->base, 4);
+ kfree(portc);
+ return -1;
+ }
+
+ audio_devs[my_dev]->portc = portc;
+ audio_devs[my_dev]->mixer_dev = -1;
+ if (owner)
+ audio_devs[my_dev]->d->owner = owner;
+ memset((char *) portc, 0, sizeof(*portc));
+
+ nr_ad1848_devs++;
+
+ ad1848_init_hw(devc);
+
+ if (irq > 0)
+ {
+ devc->dev_no = my_dev;
+ if (request_irq(devc->irq, adintr, 0, devc->name,
+ (void *)(long)my_dev) < 0)
+ {
+ printk(KERN_WARNING "ad1848: Unable to allocate IRQ\n");
+ /* Don't free it either then.. */
+ devc->irq = 0;
+ }
+ if (capabilities[devc->model].flags & CAP_F_TIMER)
+ {
+#ifndef CONFIG_SMP
+ int x;
+ unsigned char tmp = ad_read(devc, 16);
+#endif
+
+ devc->timer_ticks = 0;
+
+ ad_write(devc, 21, 0x00); /* Timer MSB */
+ ad_write(devc, 20, 0x10); /* Timer LSB */
+#ifndef CONFIG_SMP
+ ad_write(devc, 16, tmp | 0x40); /* Enable timer */
+ for (x = 0; x < 100000 && devc->timer_ticks == 0; x++);
+ ad_write(devc, 16, tmp & ~0x40); /* Disable timer */
+
+ if (devc->timer_ticks == 0)
+ printk(KERN_WARNING "ad1848: Interrupt test failed (IRQ%d)\n", irq);
+ else
+ {
+ DDB(printk("Interrupt test OK\n"));
+ devc->irq_ok = 1;
+ }
+#else
+ devc->irq_ok = 1;
+#endif
+ }
+ else
+ devc->irq_ok = 1; /* Couldn't test. assume it's OK */
+ } else if (irq < 0)
+ irq2dev[-irq] = devc->dev_no = my_dev;
+
+#ifndef EXCLUDE_TIMERS
+ if ((capabilities[devc->model].flags & CAP_F_TIMER) &&
+ devc->irq_ok)
+ ad1848_tmr_install(my_dev);
+#endif
+
+ if (!share_dma)
+ {
+ if (sound_alloc_dma(dma_playback, devc->name))
+ printk(KERN_WARNING "ad1848.c: Can't allocate DMA%d\n", dma_playback);
+
+ if (dma_capture != dma_playback)
+ if (sound_alloc_dma(dma_capture, devc->name))
+ printk(KERN_WARNING "ad1848.c: Can't allocate DMA%d\n", dma_capture);
+ }
+
+ if ((e = sound_install_mixer(MIXER_DRIVER_VERSION,
+ dev_name,
+ &ad1848_mixer_operations,
+ sizeof(struct mixer_operations),
+ devc)) >= 0)
+ {
+ audio_devs[my_dev]->mixer_dev = e;
+ if (owner)
+ mixer_devs[e]->owner = owner;
+ }
+ return my_dev;
+}
+
+int ad1848_control(int cmd, int arg)
+{
+ ad1848_info *devc;
+ unsigned long flags;
+
+ if (nr_ad1848_devs < 1)
+ return -ENODEV;
+
+ devc = &adev_info[nr_ad1848_devs - 1];
+
+ switch (cmd)
+ {
+ case AD1848_SET_XTAL: /* Change clock frequency of AD1845 (only ) */
+ if (devc->model != MD_1845 && devc->model != MD_1845_SSCAPE)
+ return -EINVAL;
+ spin_lock_irqsave(&devc->lock,flags);
+ ad_enter_MCE(devc);
+ ad_write(devc, 29, (ad_read(devc, 29) & 0x1f) | (arg << 5));
+ ad_leave_MCE(devc);
+ spin_unlock_irqrestore(&devc->lock,flags);
+ break;
+
+ case AD1848_MIXER_REROUTE:
+ {
+ int o = (arg >> 8) & 0xff;
+ int n = arg & 0xff;
+
+ if (o < 0 || o >= SOUND_MIXER_NRDEVICES)
+ return -EINVAL;
+
+ if (!(devc->supported_devices & (1 << o)) &&
+ !(devc->supported_rec_devices & (1 << o)))
+ return -EINVAL;
+
+ if (n == SOUND_MIXER_NONE)
+ { /* Just hide this control */
+ ad1848_mixer_set(devc, o, 0); /* Shut up it */
+ devc->supported_devices &= ~(1 << o);
+ devc->supported_rec_devices &= ~(1 << o);
+ break;
+ }
+
+ /* Make the mixer control identified by o to appear as n */
+ if (n < 0 || n >= SOUND_MIXER_NRDEVICES)
+ return -EINVAL;
+
+ devc->mixer_reroute[n] = o; /* Rename the control */
+ if (devc->supported_devices & (1 << o))
+ devc->supported_devices |= (1 << n);
+ if (devc->supported_rec_devices & (1 << o))
+ devc->supported_rec_devices |= (1 << n);
+
+ devc->supported_devices &= ~(1 << o);
+ devc->supported_rec_devices &= ~(1 << o);
+ }
+ break;
+ }
+ return 0;
+}
+
+void ad1848_unload(int io_base, int irq, int dma_playback, int dma_capture, int share_dma)
+{
+ int i, mixer, dev = 0;
+ ad1848_info *devc = NULL;
+
+ for (i = 0; devc == NULL && i < nr_ad1848_devs; i++)
+ {
+ if (adev_info[i].base == io_base)
+ {
+ devc = &adev_info[i];
+ dev = devc->dev_no;
+ }
+ }
+
+ if (devc != NULL)
+ {
+ kfree(audio_devs[dev]->portc);
+ release_region(devc->base, 4);
+
+ if (!share_dma)
+ {
+ if (devc->irq > 0) /* There is no point in freeing irq, if it wasn't allocated */
+ free_irq(devc->irq, (void *)(long)devc->dev_no);
+
+ sound_free_dma(dma_playback);
+
+ if (dma_playback != dma_capture)
+ sound_free_dma(dma_capture);
+
+ }
+ mixer = audio_devs[devc->dev_no]->mixer_dev;
+ if(mixer>=0)
+ sound_unload_mixerdev(mixer);
+
+ nr_ad1848_devs--;
+ for ( ; i < nr_ad1848_devs ; i++)
+ adev_info[i] = adev_info[i+1];
+ }
+ else
+ printk(KERN_ERR "ad1848: Can't find device to be unloaded. Base=%x\n", io_base);
+}
+
+static irqreturn_t adintr(int irq, void *dev_id)
+{
+ unsigned char status;
+ ad1848_info *devc;
+ int dev;
+ int alt_stat = 0xff;
+ unsigned char c930_stat = 0;
+ int cnt = 0;
+
+ dev = (long)dev_id;
+ devc = (ad1848_info *) audio_devs[dev]->devc;
+
+interrupt_again: /* Jump back here if int status doesn't reset */
+
+ status = inb(io_Status(devc));
+
+ if (status == 0x80)
+ printk(KERN_DEBUG "adintr: Why?\n");
+ if (devc->model == MD_1848)
+ outb((0), io_Status(devc)); /* Clear interrupt status */
+
+ if (status & 0x01)
+ {
+ if (devc->model == MD_C930)
+ { /* 82C930 has interrupt status register in MAD16 register MC11 */
+
+ spin_lock(&devc->lock);
+
+ /* 0xe0e is C930 address port
+ * 0xe0f is C930 data port
+ */
+ outb(11, 0xe0e);
+ c930_stat = inb(0xe0f);
+ outb((~c930_stat), 0xe0f);
+
+ spin_unlock(&devc->lock);
+
+ alt_stat = (c930_stat << 2) & 0x30;
+ }
+ else if (devc->model != MD_1848)
+ {
+ spin_lock(&devc->lock);
+ alt_stat = ad_read(devc, 24);
+ ad_write(devc, 24, ad_read(devc, 24) & ~alt_stat); /* Selective ack */
+ spin_unlock(&devc->lock);
+ }
+
+ if ((devc->open_mode & OPEN_READ) && (devc->audio_mode & PCM_ENABLE_INPUT) && (alt_stat & 0x20))
+ {
+ DMAbuf_inputintr(devc->record_dev);
+ }
+ if ((devc->open_mode & OPEN_WRITE) && (devc->audio_mode & PCM_ENABLE_OUTPUT) &&
+ (alt_stat & 0x10))
+ {
+ DMAbuf_outputintr(devc->playback_dev, 1);
+ }
+ if (devc->model != MD_1848 && (alt_stat & 0x40)) /* Timer interrupt */
+ {
+ devc->timer_ticks++;
+#ifndef EXCLUDE_TIMERS
+ if (timer_installed == dev && devc->timer_running)
+ sound_timer_interrupt();
+#endif
+ }
+ }
+/*
+ * Sometimes playback or capture interrupts occur while a timer interrupt
+ * is being handled. The interrupt will not be retriggered if we don't
+ * handle it now. Check if an interrupt is still pending and restart
+ * the handler in this case.
+ */
+ if (inb(io_Status(devc)) & 0x01 && cnt++ < 4)
+ {
+ goto interrupt_again;
+ }
+ return IRQ_HANDLED;
+}
+
+/*
+ * Experimental initialization sequence for the integrated sound system
+ * of the Compaq Deskpro M.
+ */
+
+static int init_deskpro_m(struct address_info *hw_config)
+{
+ unsigned char tmp;
+
+ if ((tmp = inb(0xc44)) == 0xff)
+ {
+ DDB(printk("init_deskpro_m: Dead port 0xc44\n"));
+ return 0;
+ }
+
+ outb(0x10, 0xc44);
+ outb(0x40, 0xc45);
+ outb(0x00, 0xc46);
+ outb(0xe8, 0xc47);
+ outb(0x14, 0xc44);
+ outb(0x40, 0xc45);
+ outb(0x00, 0xc46);
+ outb(0xe8, 0xc47);
+ outb(0x10, 0xc44);
+
+ return 1;
+}
+
+/*
+ * Experimental initialization sequence for the integrated sound system
+ * of Compaq Deskpro XL.
+ */
+
+static int init_deskpro(struct address_info *hw_config)
+{
+ unsigned char tmp;
+
+ if ((tmp = inb(0xc44)) == 0xff)
+ {
+ DDB(printk("init_deskpro: Dead port 0xc44\n"));
+ return 0;
+ }
+ outb((tmp | 0x04), 0xc44); /* Select bank 1 */
+ if (inb(0xc44) != 0x04)
+ {
+ DDB(printk("init_deskpro: Invalid bank1 signature in port 0xc44\n"));
+ return 0;
+ }
+ /*
+ * OK. It looks like a Deskpro so let's proceed.
+ */
+
+ /*
+ * I/O port 0xc44 Audio configuration register.
+ *
+ * bits 0xc0: Audio revision bits
+ * 0x00 = Compaq Business Audio
+ * 0x40 = MS Sound System Compatible (reset default)
+ * 0x80 = Reserved
+ * 0xc0 = Reserved
+ * bit 0x20: No Wait State Enable
+ * 0x00 = Disabled (reset default, DMA mode)
+ * 0x20 = Enabled (programmed I/O mode)
+ * bit 0x10: MS Sound System Decode Enable
+ * 0x00 = Decoding disabled (reset default)
+ * 0x10 = Decoding enabled
+ * bit 0x08: FM Synthesis Decode Enable
+ * 0x00 = Decoding Disabled (reset default)
+ * 0x08 = Decoding enabled
+ * bit 0x04 Bank select
+ * 0x00 = Bank 0
+ * 0x04 = Bank 1
+ * bits 0x03 MSS Base address
+ * 0x00 = 0x530 (reset default)
+ * 0x01 = 0x604
+ * 0x02 = 0xf40
+ * 0x03 = 0xe80
+ */
+
+#ifdef DEBUGXL
+ /* Debug printing */
+ printk("Port 0xc44 (before): ");
+ outb((tmp & ~0x04), 0xc44);
+ printk("%02x ", inb(0xc44));
+ outb((tmp | 0x04), 0xc44);
+ printk("%02x\n", inb(0xc44));
+#endif
+
+ /* Set bank 1 of the register */
+ tmp = 0x58; /* MSS Mode, MSS&FM decode enabled */
+
+ switch (hw_config->io_base)
+ {
+ case 0x530:
+ tmp |= 0x00;
+ break;
+ case 0x604:
+ tmp |= 0x01;
+ break;
+ case 0xf40:
+ tmp |= 0x02;
+ break;
+ case 0xe80:
+ tmp |= 0x03;
+ break;
+ default:
+ DDB(printk("init_deskpro: Invalid MSS port %x\n", hw_config->io_base));
+ return 0;
+ }
+ outb((tmp & ~0x04), 0xc44); /* Write to bank=0 */
+
+#ifdef DEBUGXL
+ /* Debug printing */
+ printk("Port 0xc44 (after): ");
+ outb((tmp & ~0x04), 0xc44); /* Select bank=0 */
+ printk("%02x ", inb(0xc44));
+ outb((tmp | 0x04), 0xc44); /* Select bank=1 */
+ printk("%02x\n", inb(0xc44));
+#endif
+
+ /*
+ * I/O port 0xc45 FM Address Decode/MSS ID Register.
+ *
+ * bank=0, bits 0xfe: FM synthesis Decode Compare bits 7:1 (default=0x88)
+ * bank=0, bit 0x01: SBIC Power Control Bit
+ * 0x00 = Powered up
+ * 0x01 = Powered down
+ * bank=1, bits 0xfc: MSS ID (default=0x40)
+ */
+
+#ifdef DEBUGXL
+ /* Debug printing */
+ printk("Port 0xc45 (before): ");
+ outb((tmp & ~0x04), 0xc44); /* Select bank=0 */
+ printk("%02x ", inb(0xc45));
+ outb((tmp | 0x04), 0xc44); /* Select bank=1 */
+ printk("%02x\n", inb(0xc45));
+#endif
+
+ outb((tmp & ~0x04), 0xc44); /* Select bank=0 */
+ outb((0x88), 0xc45); /* FM base 7:0 = 0x88 */
+ outb((tmp | 0x04), 0xc44); /* Select bank=1 */
+ outb((0x10), 0xc45); /* MSS ID = 0x10 (MSS port returns 0x04) */
+
+#ifdef DEBUGXL
+ /* Debug printing */
+ printk("Port 0xc45 (after): ");
+ outb((tmp & ~0x04), 0xc44); /* Select bank=0 */
+ printk("%02x ", inb(0xc45));
+ outb((tmp | 0x04), 0xc44); /* Select bank=1 */
+ printk("%02x\n", inb(0xc45));
+#endif
+
+
+ /*
+ * I/O port 0xc46 FM Address Decode/Address ASIC Revision Register.
+ *
+ * bank=0, bits 0xff: FM synthesis Decode Compare bits 15:8 (default=0x03)
+ * bank=1, bits 0xff: Audio addressing ASIC id
+ */
+
+#ifdef DEBUGXL
+ /* Debug printing */
+ printk("Port 0xc46 (before): ");
+ outb((tmp & ~0x04), 0xc44); /* Select bank=0 */
+ printk("%02x ", inb(0xc46));
+ outb((tmp | 0x04), 0xc44); /* Select bank=1 */
+ printk("%02x\n", inb(0xc46));
+#endif
+
+ outb((tmp & ~0x04), 0xc44); /* Select bank=0 */
+ outb((0x03), 0xc46); /* FM base 15:8 = 0x03 */
+ outb((tmp | 0x04), 0xc44); /* Select bank=1 */
+ outb((0x11), 0xc46); /* ASIC ID = 0x11 */
+
+#ifdef DEBUGXL
+ /* Debug printing */
+ printk("Port 0xc46 (after): ");
+ outb((tmp & ~0x04), 0xc44); /* Select bank=0 */
+ printk("%02x ", inb(0xc46));
+ outb((tmp | 0x04), 0xc44); /* Select bank=1 */
+ printk("%02x\n", inb(0xc46));
+#endif
+
+ /*
+ * I/O port 0xc47 FM Address Decode Register.
+ *
+ * bank=0, bits 0xff: Decode enable selection for various FM address bits
+ * bank=1, bits 0xff: Reserved
+ */
+
+#ifdef DEBUGXL
+ /* Debug printing */
+ printk("Port 0xc47 (before): ");
+ outb((tmp & ~0x04), 0xc44); /* Select bank=0 */
+ printk("%02x ", inb(0xc47));
+ outb((tmp | 0x04), 0xc44); /* Select bank=1 */
+ printk("%02x\n", inb(0xc47));
+#endif
+
+ outb((tmp & ~0x04), 0xc44); /* Select bank=0 */
+ outb((0x7c), 0xc47); /* FM decode enable bits = 0x7c */
+ outb((tmp | 0x04), 0xc44); /* Select bank=1 */
+ outb((0x00), 0xc47); /* Reserved bank1 = 0x00 */
+
+#ifdef DEBUGXL
+ /* Debug printing */
+ printk("Port 0xc47 (after): ");
+ outb((tmp & ~0x04), 0xc44); /* Select bank=0 */
+ printk("%02x ", inb(0xc47));
+ outb((tmp | 0x04), 0xc44); /* Select bank=1 */
+ printk("%02x\n", inb(0xc47));
+#endif
+
+ /*
+ * I/O port 0xc6f = Audio Disable Function Register
+ */
+
+#ifdef DEBUGXL
+ printk("Port 0xc6f (before) = %02x\n", inb(0xc6f));
+#endif
+
+ outb((0x80), 0xc6f);
+
+#ifdef DEBUGXL
+ printk("Port 0xc6f (after) = %02x\n", inb(0xc6f));
+#endif
+
+ return 1;
+}
+
+int probe_ms_sound(struct address_info *hw_config, struct resource *ports)
+{
+ unsigned char tmp;
+
+ DDB(printk("Entered probe_ms_sound(%x, %d)\n", hw_config->io_base, hw_config->card_subtype));
+
+ if (hw_config->card_subtype == 1) /* Has no IRQ/DMA registers */
+ {
+ /* check_opl3(0x388, hw_config); */
+ return ad1848_detect(ports, NULL, hw_config->osp);
+ }
+
+ if (deskpro_xl && hw_config->card_subtype == 2) /* Compaq Deskpro XL */
+ {
+ if (!init_deskpro(hw_config))
+ return 0;
+ }
+
+ if (deskpro_m) /* Compaq Deskpro M */
+ {
+ if (!init_deskpro_m(hw_config))
+ return 0;
+ }
+
+ /*
+ * Check if the IO port returns valid signature. The original MS Sound
+ * system returns 0x04 while some cards (AudioTrix Pro for example)
+ * return 0x00 or 0x0f.
+ */
+
+ if ((tmp = inb(hw_config->io_base + 3)) == 0xff) /* Bus float */
+ {
+ int ret;
+
+ DDB(printk("I/O address is inactive (%x)\n", tmp));
+ if (!(ret = ad1848_detect(ports, NULL, hw_config->osp)))
+ return 0;
+ return 1;
+ }
+ DDB(printk("MSS signature = %x\n", tmp & 0x3f));
+ if ((tmp & 0x3f) != 0x04 &&
+ (tmp & 0x3f) != 0x0f &&
+ (tmp & 0x3f) != 0x00)
+ {
+ int ret;
+
+ MDB(printk(KERN_ERR "No MSS signature detected on port 0x%x (0x%x)\n", hw_config->io_base, (int) inb(hw_config->io_base + 3)));
+ DDB(printk("Trying to detect codec anyway but IRQ/DMA may not work\n"));
+ if (!(ret = ad1848_detect(ports, NULL, hw_config->osp)))
+ return 0;
+
+ hw_config->card_subtype = 1;
+ return 1;
+ }
+ if ((hw_config->irq != 5) &&
+ (hw_config->irq != 7) &&
+ (hw_config->irq != 9) &&
+ (hw_config->irq != 10) &&
+ (hw_config->irq != 11) &&
+ (hw_config->irq != 12))
+ {
+ printk(KERN_ERR "MSS: Bad IRQ %d\n", hw_config->irq);
+ return 0;
+ }
+ if (hw_config->dma != 0 && hw_config->dma != 1 && hw_config->dma != 3)
+ {
+ printk(KERN_ERR "MSS: Bad DMA %d\n", hw_config->dma);
+ return 0;
+ }
+ /*
+ * Check that DMA0 is not in use with a 8 bit board.
+ */
+
+ if (hw_config->dma == 0 && inb(hw_config->io_base + 3) & 0x80)
+ {
+ printk(KERN_ERR "MSS: Can't use DMA0 with a 8 bit card/slot\n");
+ return 0;
+ }
+ if (hw_config->irq > 7 && hw_config->irq != 9 && inb(hw_config->io_base + 3) & 0x80)
+ {
+ printk(KERN_ERR "MSS: Can't use IRQ%d with a 8 bit card/slot\n", hw_config->irq);
+ return 0;
+ }
+ return ad1848_detect(ports, NULL, hw_config->osp);
+}
+
+void attach_ms_sound(struct address_info *hw_config, struct resource *ports, struct module *owner)
+{
+ static signed char interrupt_bits[12] =
+ {
+ -1, -1, -1, -1, -1, 0x00, -1, 0x08, -1, 0x10, 0x18, 0x20
+ };
+ signed char bits;
+ char dma2_bit = 0;
+
+ static char dma_bits[4] =
+ {
+ 1, 2, 0, 3
+ };
+
+ int config_port = hw_config->io_base + 0;
+ int version_port = hw_config->io_base + 3;
+ int dma = hw_config->dma;
+ int dma2 = hw_config->dma2;
+
+ if (hw_config->card_subtype == 1) /* Has no IRQ/DMA registers */
+ {
+ hw_config->slots[0] = ad1848_init("MS Sound System", ports,
+ hw_config->irq,
+ hw_config->dma,
+ hw_config->dma2, 0,
+ hw_config->osp,
+ owner);
+ return;
+ }
+ /*
+ * Set the IRQ and DMA addresses.
+ */
+
+ bits = interrupt_bits[hw_config->irq];
+ if (bits == -1)
+ {
+ printk(KERN_ERR "MSS: Bad IRQ %d\n", hw_config->irq);
+ release_region(ports->start, 4);
+ release_region(ports->start - 4, 4);
+ return;
+ }
+ outb((bits | 0x40), config_port);
+ if ((inb(version_port) & 0x40) == 0)
+ printk(KERN_ERR "[MSS: IRQ Conflict?]\n");
+
+/*
+ * Handle the capture DMA channel
+ */
+
+ if (dma2 != -1 && dma2 != dma)
+ {
+ if (!((dma == 0 && dma2 == 1) ||
+ (dma == 1 && dma2 == 0) ||
+ (dma == 3 && dma2 == 0)))
+ { /* Unsupported combination. Try to swap channels */
+ int tmp = dma;
+
+ dma = dma2;
+ dma2 = tmp;
+ }
+ if ((dma == 0 && dma2 == 1) ||
+ (dma == 1 && dma2 == 0) ||
+ (dma == 3 && dma2 == 0))
+ {
+ dma2_bit = 0x04; /* Enable capture DMA */
+ }
+ else
+ {
+ printk(KERN_WARNING "MSS: Invalid capture DMA\n");
+ dma2 = dma;
+ }
+ }
+ else
+ {
+ dma2 = dma;
+ }
+
+ hw_config->dma = dma;
+ hw_config->dma2 = dma2;
+
+ outb((bits | dma_bits[dma] | dma2_bit), config_port); /* Write IRQ+DMA setup */
+
+ hw_config->slots[0] = ad1848_init("MS Sound System", ports,
+ hw_config->irq,
+ dma, dma2, 0,
+ hw_config->osp,
+ THIS_MODULE);
+}
+
+void unload_ms_sound(struct address_info *hw_config)
+{
+ ad1848_unload(hw_config->io_base + 4,
+ hw_config->irq,
+ hw_config->dma,
+ hw_config->dma2, 0);
+ sound_unload_audiodev(hw_config->slots[0]);
+ release_region(hw_config->io_base, 4);
+}
+
+#ifndef EXCLUDE_TIMERS
+
+/*
+ * Timer stuff (for /dev/music).
+ */
+
+static unsigned int current_interval;
+
+static unsigned int ad1848_tmr_start(int dev, unsigned int usecs)
+{
+ unsigned long flags;
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+ unsigned long xtal_nsecs; /* nanoseconds per xtal oscillator tick */
+ unsigned long divider;
+
+ spin_lock_irqsave(&devc->lock,flags);
+
+ /*
+ * Length of the timer interval (in nanoseconds) depends on the
+ * selected crystal oscillator. Check this from bit 0x01 of I8.
+ *
+ * AD1845 has just one oscillator which has cycle time of 10.050 us
+ * (when a 24.576 MHz xtal oscillator is used).
+ *
+ * Convert requested interval to nanoseconds before computing
+ * the timer divider.
+ */
+
+ if (devc->model == MD_1845 || devc->model == MD_1845_SSCAPE)
+ xtal_nsecs = 10050;
+ else if (ad_read(devc, 8) & 0x01)
+ xtal_nsecs = 9920;
+ else
+ xtal_nsecs = 9969;
+
+ divider = (usecs * 1000 + xtal_nsecs / 2) / xtal_nsecs;
+
+ if (divider < 100) /* Don't allow shorter intervals than about 1ms */
+ divider = 100;
+
+ if (divider > 65535) /* Overflow check */
+ divider = 65535;
+
+ ad_write(devc, 21, (divider >> 8) & 0xff); /* Set upper bits */
+ ad_write(devc, 20, divider & 0xff); /* Set lower bits */
+ ad_write(devc, 16, ad_read(devc, 16) | 0x40); /* Start the timer */
+ devc->timer_running = 1;
+ spin_unlock_irqrestore(&devc->lock,flags);
+
+ return current_interval = (divider * xtal_nsecs + 500) / 1000;
+}
+
+static void ad1848_tmr_reprogram(int dev)
+{
+ /*
+ * Audio driver has changed sampling rate so that a different xtal
+ * oscillator was selected. We have to reprogram the timer rate.
+ */
+
+ ad1848_tmr_start(dev, current_interval);
+ sound_timer_syncinterval(current_interval);
+}
+
+static void ad1848_tmr_disable(int dev)
+{
+ unsigned long flags;
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+
+ spin_lock_irqsave(&devc->lock,flags);
+ ad_write(devc, 16, ad_read(devc, 16) & ~0x40);
+ devc->timer_running = 0;
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_tmr_restart(int dev)
+{
+ unsigned long flags;
+ ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+
+ if (current_interval == 0)
+ return;
+
+ spin_lock_irqsave(&devc->lock,flags);
+ ad_write(devc, 16, ad_read(devc, 16) | 0x40);
+ devc->timer_running = 1;
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static struct sound_lowlev_timer ad1848_tmr =
+{
+ 0,
+ 2,
+ ad1848_tmr_start,
+ ad1848_tmr_disable,
+ ad1848_tmr_restart
+};
+
+static int ad1848_tmr_install(int dev)
+{
+ if (timer_installed != -1)
+ return 0; /* Don't install another timer */
+
+ timer_installed = ad1848_tmr.dev = dev;
+ sound_timer_init(&ad1848_tmr, audio_devs[dev]->name);
+
+ return 1;
+}
+#endif /* EXCLUDE_TIMERS */
+
+EXPORT_SYMBOL(ad1848_detect);
+EXPORT_SYMBOL(ad1848_init);
+EXPORT_SYMBOL(ad1848_unload);
+EXPORT_SYMBOL(ad1848_control);
+EXPORT_SYMBOL(probe_ms_sound);
+EXPORT_SYMBOL(attach_ms_sound);
+EXPORT_SYMBOL(unload_ms_sound);
+
+static int __initdata io = -1;
+static int __initdata irq = -1;
+static int __initdata dma = -1;
+static int __initdata dma2 = -1;
+static int __initdata type = 0;
+
+module_param(io, int, 0); /* I/O for a raw AD1848 card */
+module_param(irq, int, 0); /* IRQ to use */
+module_param(dma, int, 0); /* First DMA channel */
+module_param(dma2, int, 0); /* Second DMA channel */
+module_param(type, int, 0); /* Card type */
+module_param(deskpro_xl, bool, 0); /* Special magic for Deskpro XL boxen */
+module_param(deskpro_m, bool, 0); /* Special magic for Deskpro M box */
+module_param(soundpro, bool, 0); /* More special magic for SoundPro chips */
+
+#ifdef CONFIG_PNP
+module_param(isapnp, int, 0);
+module_param(isapnpjump, int, 0);
+module_param(reverse, bool, 0);
+MODULE_PARM_DESC(isapnp, "When set to 0, Plug & Play support will be disabled");
+MODULE_PARM_DESC(isapnpjump, "Jumps to a specific slot in the driver's PnP table. Use the source, Luke.");
+MODULE_PARM_DESC(reverse, "When set to 1, will reverse ISAPnP search order");
+
+static struct pnp_dev *ad1848_dev = NULL;
+
+/* Please add new entries at the end of the table */
+static struct {
+ char *name;
+ unsigned short card_vendor, card_device,
+ vendor, function;
+ short mss_io, irq, dma, dma2; /* index into isapnp table */
+ int type;
+} ad1848_isapnp_list[] __initdata = {
+ {"CMI 8330 SoundPRO",
+ ISAPNP_VENDOR('C','M','I'), ISAPNP_DEVICE(0x0001),
+ ISAPNP_VENDOR('@','@','@'), ISAPNP_FUNCTION(0x0001),
+ 0, 0, 0,-1, 0},
+ {"CS4232 based card",
+ ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+ ISAPNP_VENDOR('C','S','C'), ISAPNP_FUNCTION(0x0000),
+ 0, 0, 0, 1, 0},
+ {"CS4232 based card",
+ ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+ ISAPNP_VENDOR('C','S','C'), ISAPNP_FUNCTION(0x0100),
+ 0, 0, 0, 1, 0},
+ {"OPL3-SA2 WSS mode",
+ ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+ ISAPNP_VENDOR('Y','M','H'), ISAPNP_FUNCTION(0x0021),
+ 1, 0, 0, 1, 1},
+ {"Advanced Gravis InterWave Audio",
+ ISAPNP_VENDOR('G','R','V'), ISAPNP_DEVICE(0x0001),
+ ISAPNP_VENDOR('G','R','V'), ISAPNP_FUNCTION(0x0000),
+ 0, 0, 0, 1, 0},
+ {NULL}
+};
+
+static struct isapnp_device_id id_table[] __devinitdata = {
+ { ISAPNP_VENDOR('C','M','I'), ISAPNP_DEVICE(0x0001),
+ ISAPNP_VENDOR('@','@','@'), ISAPNP_FUNCTION(0x0001), 0 },
+ { ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+ ISAPNP_VENDOR('C','S','C'), ISAPNP_FUNCTION(0x0000), 0 },
+ { ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+ ISAPNP_VENDOR('C','S','C'), ISAPNP_FUNCTION(0x0100), 0 },
+ /* The main driver for this card is opl3sa2
+ { ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+ ISAPNP_VENDOR('Y','M','H'), ISAPNP_FUNCTION(0x0021), 0 },
+ */
+ { ISAPNP_VENDOR('G','R','V'), ISAPNP_DEVICE(0x0001),
+ ISAPNP_VENDOR('G','R','V'), ISAPNP_FUNCTION(0x0000), 0 },
+ {0}
+};
+
+MODULE_DEVICE_TABLE(isapnp, id_table);
+
+static struct pnp_dev *activate_dev(char *devname, char *resname, struct pnp_dev *dev)
+{
+ int err;
+
+ err = pnp_device_attach(dev);
+ if (err < 0)
+ return(NULL);
+
+ if((err = pnp_activate_dev(dev)) < 0) {
+ printk(KERN_ERR "ad1848: %s %s config failed (out of resources?)[%d]\n", devname, resname, err);
+
+ pnp_device_detach(dev);
+
+ return(NULL);
+ }
+ audio_activated = 1;
+ return(dev);
+}
+
+static struct pnp_dev __init *ad1848_init_generic(struct pnp_card *bus,
+ struct address_info *hw_config, int slot)
+{
+
+ /* Configure Audio device */
+ if((ad1848_dev = pnp_find_dev(bus, ad1848_isapnp_list[slot].vendor, ad1848_isapnp_list[slot].function, NULL)))
+ {
+ if((ad1848_dev = activate_dev(ad1848_isapnp_list[slot].name, "ad1848", ad1848_dev)))
+ {
+ hw_config->io_base = pnp_port_start(ad1848_dev, ad1848_isapnp_list[slot].mss_io);
+ hw_config->irq = pnp_irq(ad1848_dev, ad1848_isapnp_list[slot].irq);
+ hw_config->dma = pnp_dma(ad1848_dev, ad1848_isapnp_list[slot].dma);
+ if(ad1848_isapnp_list[slot].dma2 != -1)
+ hw_config->dma2 = pnp_dma(ad1848_dev, ad1848_isapnp_list[slot].dma2);
+ else
+ hw_config->dma2 = -1;
+ hw_config->card_subtype = ad1848_isapnp_list[slot].type;
+ } else
+ return(NULL);
+ } else
+ return(NULL);
+
+ return(ad1848_dev);
+}
+
+static int __init ad1848_isapnp_init(struct address_info *hw_config, struct pnp_card *bus, int slot)
+{
+ char *busname = bus->name[0] ? bus->name : ad1848_isapnp_list[slot].name;
+
+ /* Initialize this baby. */
+
+ if(ad1848_init_generic(bus, hw_config, slot)) {
+ /* We got it. */
+
+ printk(KERN_NOTICE "ad1848: PnP reports '%s' at i/o %#x, irq %d, dma %d, %d\n",
+ busname,
+ hw_config->io_base, hw_config->irq, hw_config->dma,
+ hw_config->dma2);
+ return 1;
+ }
+ return 0;
+}
+
+static int __init ad1848_isapnp_probe(struct address_info *hw_config)
+{
+ static int first = 1;
+ int i;
+
+ /* Count entries in sb_isapnp_list */
+ for (i = 0; ad1848_isapnp_list[i].card_vendor != 0; i++);
+ i--;
+
+ /* Check and adjust isapnpjump */
+ if( isapnpjump < 0 || isapnpjump > i) {
+ isapnpjump = reverse ? i : 0;
+ printk(KERN_ERR "ad1848: Valid range for isapnpjump is 0-%d. Adjusted to %d.\n", i, isapnpjump);
+ }
+
+ if(!first || !reverse)
+ i = isapnpjump;
+ first = 0;
+ while(ad1848_isapnp_list[i].card_vendor != 0) {
+ static struct pnp_card *bus = NULL;
+
+ while ((bus = pnp_find_card(
+ ad1848_isapnp_list[i].card_vendor,
+ ad1848_isapnp_list[i].card_device,
+ bus))) {
+
+ if(ad1848_isapnp_init(hw_config, bus, i)) {
+ isapnpjump = i; /* start next search from here */
+ return 0;
+ }
+ }
+ i += reverse ? -1 : 1;
+ }
+
+ return -ENODEV;
+}
+#endif
+
+
+static int __init init_ad1848(void)
+{
+ printk(KERN_INFO "ad1848/cs4248 codec driver Copyright (C) by Hannu Savolainen 1993-1996\n");
+
+#ifdef CONFIG_PNP
+ if(isapnp && (ad1848_isapnp_probe(&cfg) < 0) ) {
+ printk(KERN_NOTICE "ad1848: No ISAPnP cards found, trying standard ones...\n");
+ isapnp = 0;
+ }
+#endif
+
+ if(io != -1) {
+ struct resource *ports;
+ if( isapnp == 0 )
+ {
+ if(irq == -1 || dma == -1) {
+ printk(KERN_WARNING "ad1848: must give I/O , IRQ and DMA.\n");
+ return -EINVAL;
+ }
+
+ cfg.irq = irq;
+ cfg.io_base = io;
+ cfg.dma = dma;
+ cfg.dma2 = dma2;
+ cfg.card_subtype = type;
+ }
+
+ ports = request_region(io + 4, 4, "ad1848");
+
+ if (!ports)
+ return -EBUSY;
+
+ if (!request_region(io, 4, "WSS config")) {
+ release_region(io + 4, 4);
+ return -EBUSY;
+ }
+
+ if (!probe_ms_sound(&cfg, ports)) {
+ release_region(io + 4, 4);
+ release_region(io, 4);
+ return -ENODEV;
+ }
+ attach_ms_sound(&cfg, ports, THIS_MODULE);
+ loaded = 1;
+ }
+ return 0;
+}
+
+static void __exit cleanup_ad1848(void)
+{
+ if(loaded)
+ unload_ms_sound(&cfg);
+
+#ifdef CONFIG_PNP
+ if(ad1848_dev){
+ if(audio_activated)
+ pnp_device_detach(ad1848_dev);
+ }
+#endif
+}
+
+module_init(init_ad1848);
+module_exit(cleanup_ad1848);
+
+#ifndef MODULE
+static int __init setup_ad1848(char *str)
+{
+ /* io, irq, dma, dma2, type */
+ int ints[6];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ io = ints[1];
+ irq = ints[2];
+ dma = ints[3];
+ dma2 = ints[4];
+ type = ints[5];
+
+ return 1;
+}
+
+__setup("ad1848=", setup_ad1848);
+#endif
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/ad1848.h b/sound/oss/ad1848.h
new file mode 100644
index 00000000..b95ebe28
--- /dev/null
+++ b/sound/oss/ad1848.h
@@ -0,0 +1,24 @@
+
+#include <linux/interrupt.h>
+
+#define AD_F_CS4231 0x0001 /* Returned if a CS4232 (or compatible) detected */
+#define AD_F_CS4248 0x0001 /* Returned if a CS4248 (or compatible) detected */
+
+#define AD1848_SET_XTAL 1
+#define AD1848_MIXER_REROUTE 2
+
+#define AD1848_REROUTE(oldctl, newctl) \
+ ad1848_control(AD1848_MIXER_REROUTE, ((oldctl)<<8)|(newctl))
+
+
+int ad1848_init(char *name, struct resource *ports, int irq, int dma_playback,
+ int dma_capture, int share_dma, int *osp, struct module *owner);
+void ad1848_unload (int io_base, int irq, int dma_playback, int dma_capture, int share_dma);
+
+int ad1848_detect (struct resource *ports, int *flags, int *osp);
+int ad1848_control(int cmd, int arg);
+
+void attach_ms_sound(struct address_info * hw_config, struct resource *ports, struct module * owner);
+
+int probe_ms_sound(struct address_info *hw_config, struct resource *ports);
+void unload_ms_sound(struct address_info *hw_info);
diff --git a/sound/oss/ad1848_mixer.h b/sound/oss/ad1848_mixer.h
new file mode 100644
index 00000000..2cf719b5
--- /dev/null
+++ b/sound/oss/ad1848_mixer.h
@@ -0,0 +1,253 @@
+/*
+ * sound/oss/ad1848_mixer.h
+ *
+ * Definitions for the mixer of AD1848 and compatible codecs.
+ */
+
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+
+/*
+ * The AD1848 codec has generic input lines called Line, Aux1 and Aux2.
+ * Sound card manufacturers have connected actual inputs (CD, synth, line,
+ * etc) to these inputs in different order. Therefore it's difficult
+ * to assign mixer channels to these inputs correctly. The following
+ * contains two alternative mappings. The first one is for GUS MAX and
+ * the second is just a generic one (line1, line2 and line3).
+ * (Actually this is not a mapping but rather some kind of interleaving
+ * solution).
+ */
+#define MODE1_REC_DEVICES (SOUND_MASK_LINE3 | SOUND_MASK_MIC | \
+ SOUND_MASK_LINE1 | SOUND_MASK_IMIX)
+
+#define SPRO_REC_DEVICES (SOUND_MASK_LINE | SOUND_MASK_MIC | \
+ SOUND_MASK_CD | SOUND_MASK_LINE1)
+
+#define MODE1_MIXER_DEVICES (SOUND_MASK_LINE1 | SOUND_MASK_MIC | \
+ SOUND_MASK_LINE2 | \
+ SOUND_MASK_IGAIN | \
+ SOUND_MASK_PCM | SOUND_MASK_IMIX)
+
+#define MODE2_MIXER_DEVICES (SOUND_MASK_LINE1 | SOUND_MASK_LINE2 | \
+ SOUND_MASK_MIC | \
+ SOUND_MASK_LINE3 | SOUND_MASK_SPEAKER | \
+ SOUND_MASK_IGAIN | \
+ SOUND_MASK_PCM | SOUND_MASK_IMIX)
+
+#define MODE3_MIXER_DEVICES (MODE2_MIXER_DEVICES | SOUND_MASK_VOLUME)
+
+/* OPTi 82C930 has no IMIX level control, but it can still be selected as an
+ * input
+ */
+#define C930_MIXER_DEVICES (SOUND_MASK_LINE1 | SOUND_MASK_LINE2 | \
+ SOUND_MASK_MIC | SOUND_MASK_VOLUME | \
+ SOUND_MASK_LINE3 | \
+ SOUND_MASK_IGAIN | SOUND_MASK_PCM)
+
+#define SPRO_MIXER_DEVICES (SOUND_MASK_VOLUME | SOUND_MASK_PCM | \
+ SOUND_MASK_LINE | SOUND_MASK_SYNTH | \
+ SOUND_MASK_CD | SOUND_MASK_MIC | \
+ SOUND_MASK_SPEAKER | SOUND_MASK_LINE1 | \
+ SOUND_MASK_OGAIN)
+
+struct mixer_def {
+ unsigned int regno:6; /* register number for volume */
+ unsigned int polarity:1; /* volume polarity: 0=normal, 1=reversed */
+ unsigned int bitpos:3; /* position of bits in register for volume */
+ unsigned int nbits:3; /* number of bits in register for volume */
+ unsigned int mutereg:6; /* register number for mute bit */
+ unsigned int mutepol:1; /* mute polarity: 0=normal, 1=reversed */
+ unsigned int mutepos:4; /* position of mute bit in register */
+ unsigned int recreg:6; /* register number for recording bit */
+ unsigned int recpol:1; /* recording polarity: 0=normal, 1=reversed */
+ unsigned int recpos:4; /* position of recording bit in register */
+};
+
+static char mix_cvt[101] = {
+ 0, 0, 3, 7,10,13,16,19,21,23,26,28,30,32,34,35,37,39,40,42,
+ 43,45,46,47,49,50,51,52,53,55,56,57,58,59,60,61,62,63,64,65,
+ 65,66,67,68,69,70,70,71,72,73,73,74,75,75,76,77,77,78,79,79,
+ 80,81,81,82,82,83,84,84,85,85,86,86,87,87,88,88,89,89,90,90,
+ 91,91,92,92,93,93,94,94,95,95,96,96,96,97,97,98,98,98,99,99,
+ 100
+};
+
+typedef struct mixer_def mixer_ent;
+typedef mixer_ent mixer_ents[2];
+
+/*
+ * Most of the mixer entries work in backwards. Setting the polarity field
+ * makes them to work correctly.
+ *
+ * The channel numbering used by individual sound cards is not fixed. Some
+ * cards have assigned different meanings for the AUX1, AUX2 and LINE inputs.
+ * The current version doesn't try to compensate this.
+ */
+
+#define MIX_ENT(name, reg_l, pola_l, pos_l, len_l, reg_r, pola_r, pos_r, len_r, mute_bit) \
+ [name] = {{reg_l, pola_l, pos_l, len_l, reg_l, 0, mute_bit, 0, 0, 8}, \
+ {reg_r, pola_r, pos_r, len_r, reg_r, 0, mute_bit, 0, 0, 8}}
+
+#define MIX_ENT2(name, reg_l, pola_l, pos_l, len_l, mute_reg_l, mute_pola_l, mute_pos_l, \
+ rec_reg_l, rec_pola_l, rec_pos_l, \
+ reg_r, pola_r, pos_r, len_r, mute_reg_r, mute_pola_r, mute_pos_r, \
+ rec_reg_r, rec_pola_r, rec_pos_r) \
+ [name] = {{reg_l, pola_l, pos_l, len_l, mute_reg_l, mute_pola_l, mute_pos_l, \
+ rec_reg_l, rec_pola_l, rec_pos_l}, \
+ {reg_r, pola_r, pos_r, len_r, mute_reg_r, mute_pola_r, mute_pos_r, \
+ rec_reg_r, rec_pola_r, rec_pos_r}}
+
+static mixer_ents ad1848_mix_devices[32] = {
+ MIX_ENT(SOUND_MIXER_VOLUME, 27, 1, 0, 4, 29, 1, 0, 4, 8),
+ MIX_ENT(SOUND_MIXER_BASS, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_TREBLE, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_SYNTH, 4, 1, 0, 5, 5, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_PCM, 6, 1, 0, 6, 7, 1, 0, 6, 7),
+ MIX_ENT(SOUND_MIXER_SPEAKER, 26, 1, 0, 4, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_LINE, 18, 1, 0, 5, 19, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_MIC, 0, 0, 5, 1, 1, 0, 5, 1, 8),
+ MIX_ENT(SOUND_MIXER_CD, 2, 1, 0, 5, 3, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_IMIX, 13, 1, 2, 6, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_ALTPCM, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_RECLEV, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 4, 1, 0, 0, 4, 8),
+ MIX_ENT(SOUND_MIXER_OGAIN, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_LINE1, 2, 1, 0, 5, 3, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_LINE2, 4, 1, 0, 5, 5, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_LINE3, 18, 1, 0, 5, 19, 1, 0, 5, 7)
+};
+
+static mixer_ents iwave_mix_devices[32] = {
+ MIX_ENT(SOUND_MIXER_VOLUME, 25, 1, 0, 5, 27, 1, 0, 5, 8),
+ MIX_ENT(SOUND_MIXER_BASS, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_TREBLE, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_SYNTH, 4, 1, 0, 5, 5, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_PCM, 6, 1, 0, 6, 7, 1, 0, 6, 7),
+ MIX_ENT(SOUND_MIXER_SPEAKER, 26, 1, 0, 4, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_LINE, 18, 1, 0, 5, 19, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_MIC, 0, 0, 5, 1, 1, 0, 5, 1, 8),
+ MIX_ENT(SOUND_MIXER_CD, 2, 1, 0, 5, 3, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_IMIX, 16, 1, 0, 5, 17, 1, 0, 5, 8),
+ MIX_ENT(SOUND_MIXER_ALTPCM, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_RECLEV, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 4, 1, 0, 0, 4, 8),
+ MIX_ENT(SOUND_MIXER_OGAIN, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_LINE1, 2, 1, 0, 5, 3, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_LINE2, 4, 1, 0, 5, 5, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_LINE3, 18, 1, 0, 5, 19, 1, 0, 5, 7)
+};
+
+static mixer_ents cs42xb_mix_devices[32] = {
+ /* Digital master volume actually has seven bits, but we only use
+ six to avoid the discontinuity when the analog gain kicks in. */
+ MIX_ENT(SOUND_MIXER_VOLUME, 46, 1, 0, 6, 47, 1, 0, 6, 7),
+ MIX_ENT(SOUND_MIXER_BASS, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_TREBLE, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_SYNTH, 4, 1, 0, 5, 5, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_PCM, 6, 1, 0, 6, 7, 1, 0, 6, 7),
+ MIX_ENT(SOUND_MIXER_SPEAKER, 26, 1, 0, 4, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_LINE, 18, 1, 0, 5, 19, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_MIC, 34, 1, 0, 5, 35, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_CD, 2, 1, 0, 5, 3, 1, 0, 5, 7),
+ /* For the IMIX entry, it was not possible to use the MIX_ENT macro
+ because the mute bit is in different positions for the two
+ channels and requires reverse polarity. */
+ [SOUND_MIXER_IMIX] = {{13, 1, 2, 6, 13, 1, 0, 0, 0, 8},
+ {42, 1, 0, 6, 42, 1, 7, 0, 0, 8}},
+ MIX_ENT(SOUND_MIXER_ALTPCM, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_RECLEV, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 4, 1, 0, 0, 4, 8),
+ MIX_ENT(SOUND_MIXER_OGAIN, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_LINE1, 2, 1, 0, 5, 3, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_LINE2, 4, 1, 0, 5, 5, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_LINE3, 38, 1, 0, 6, 39, 1, 0, 6, 7)
+};
+
+/* OPTi 82C930 has somewhat different port addresses.
+ * Note: VOLUME == SPEAKER, SYNTH == LINE2, LINE == LINE3, CD == LINE1
+ * VOLUME, SYNTH, LINE, CD are not enabled above.
+ * MIC is level of mic monitoring direct to output. Same for CD, LINE, etc.
+ */
+static mixer_ents c930_mix_devices[32] = {
+ MIX_ENT(SOUND_MIXER_VOLUME, 22, 1, 1, 5, 23, 1, 1, 5, 7),
+ MIX_ENT(SOUND_MIXER_BASS, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_TREBLE, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_SYNTH, 4, 1, 1, 4, 5, 1, 1, 4, 7),
+ MIX_ENT(SOUND_MIXER_PCM, 6, 1, 0, 5, 7, 1, 0, 5, 7),
+ MIX_ENT(SOUND_MIXER_SPEAKER, 22, 1, 1, 5, 23, 1, 1, 5, 7),
+ MIX_ENT(SOUND_MIXER_LINE, 18, 1, 1, 4, 19, 1, 1, 4, 7),
+ MIX_ENT(SOUND_MIXER_MIC, 20, 1, 1, 4, 21, 1, 1, 4, 7),
+ MIX_ENT(SOUND_MIXER_CD, 2, 1, 1, 4, 3, 1, 1, 4, 7),
+ MIX_ENT(SOUND_MIXER_IMIX, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_ALTPCM, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_RECLEV, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 4, 1, 0, 0, 4, 8),
+ MIX_ENT(SOUND_MIXER_OGAIN, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT(SOUND_MIXER_LINE1, 2, 1, 1, 4, 3, 1, 1, 4, 7),
+ MIX_ENT(SOUND_MIXER_LINE2, 4, 1, 1, 4, 5, 1, 1, 4, 7),
+ MIX_ENT(SOUND_MIXER_LINE3, 18, 1, 1, 4, 19, 1, 1, 4, 7)
+};
+
+static mixer_ents spro_mix_devices[32] = {
+ MIX_ENT (SOUND_MIXER_VOLUME, 19, 0, 4, 4, 19, 0, 0, 4, 8),
+ MIX_ENT (SOUND_MIXER_BASS, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT (SOUND_MIXER_TREBLE, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT2(SOUND_MIXER_SYNTH, 4, 1, 1, 4, 23, 0, 3, 0, 0, 8,
+ 5, 1, 1, 4, 23, 0, 3, 0, 0, 8),
+ MIX_ENT (SOUND_MIXER_PCM, 6, 1, 1, 4, 7, 1, 1, 4, 8),
+ MIX_ENT (SOUND_MIXER_SPEAKER, 18, 0, 3, 2, 0, 0, 0, 0, 8),
+ MIX_ENT2(SOUND_MIXER_LINE, 20, 0, 4, 4, 17, 1, 4, 16, 0, 2,
+ 20, 0, 0, 4, 17, 1, 3, 16, 0, 1),
+ MIX_ENT2(SOUND_MIXER_MIC, 18, 0, 0, 3, 17, 1, 0, 16, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ MIX_ENT2(SOUND_MIXER_CD, 21, 0, 4, 4, 17, 1, 2, 16, 0, 4,
+ 21, 0, 0, 4, 17, 1, 1, 16, 0, 3),
+ MIX_ENT (SOUND_MIXER_IMIX, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT (SOUND_MIXER_ALTPCM, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT (SOUND_MIXER_RECLEV, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT (SOUND_MIXER_IGAIN, 0, 0, 0, 0, 0, 0, 0, 0, 8),
+ MIX_ENT (SOUND_MIXER_OGAIN, 17, 1, 6, 1, 0, 0, 0, 0, 8),
+ /* This is external wavetable */
+ MIX_ENT2(SOUND_MIXER_LINE1, 22, 0, 4, 4, 23, 1, 1, 23, 0, 4,
+ 22, 0, 0, 4, 23, 1, 0, 23, 0, 5),
+};
+
+static int default_mixer_levels[32] =
+{
+ 0x3232, /* Master Volume */
+ 0x3232, /* Bass */
+ 0x3232, /* Treble */
+ 0x4b4b, /* FM */
+ 0x3232, /* PCM */
+ 0x1515, /* PC Speaker */
+ 0x2020, /* Ext Line */
+ 0x1010, /* Mic */
+ 0x4b4b, /* CD */
+ 0x0000, /* Recording monitor */
+ 0x4b4b, /* Second PCM */
+ 0x4b4b, /* Recording level */
+ 0x4b4b, /* Input gain */
+ 0x4b4b, /* Output gain */
+ 0x2020, /* Line1 */
+ 0x2020, /* Line2 */
+ 0x1515 /* Line3 (usually line in)*/
+};
+
+#define LEFT_CHN 0
+#define RIGHT_CHN 1
+
+/*
+ * Channel enable bits for ioctl(SOUND_MIXER_PRIVATE1)
+ */
+
+#ifndef AUDIO_SPEAKER
+#define AUDIO_SPEAKER 0x01 /* Enable mono output */
+#define AUDIO_HEADPHONE 0x02 /* Sparc only */
+#define AUDIO_LINE_OUT 0x04 /* Sparc only */
+#endif
diff --git a/sound/oss/aedsp16.c b/sound/oss/aedsp16.c
new file mode 100644
index 00000000..35b5912c
--- /dev/null
+++ b/sound/oss/aedsp16.c
@@ -0,0 +1,1373 @@
+/*
+ sound/oss/aedsp16.c
+
+ Audio Excel DSP 16 software configuration routines
+ Copyright (C) 1995,1996,1997,1998 Riccardo Facchetti (fizban@tin.it)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+/*
+ * Include the main OSS Lite header file. It include all the os, OSS Lite, etc
+ * headers needed by this source.
+ */
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include "sound_config.h"
+
+/*
+
+ READ THIS
+
+ This module started to configure the Audio Excel DSP 16 Sound Card.
+ Now works with the SC-6000 (old aedsp16) and new SC-6600 based cards.
+
+ NOTE: I have NO idea about Audio Excel DSP 16 III. If someone owns this
+ audio card and want to see the kernel support for it, please contact me.
+
+ Audio Excel DSP 16 is an SB pro II, Microsoft Sound System and MPU-401
+ compatible card.
+ It is software-only configurable (no jumpers to hard-set irq/dma/mpu-irq),
+ so before this module, the only way to configure the DSP under linux was
+ boot the MS-DOS loading the sound.sys device driver (this driver soft-
+ configure the sound board hardware by massaging someone of its registers),
+ and then ctrl-alt-del to boot linux with the DSP configured by the DOS
+ driver.
+
+ This module works configuring your Audio Excel DSP 16's irq, dma and
+ mpu-401-irq. The OSS Lite routines rely on the fact that if the
+ hardware is there, they can detect it. The problem with AEDSP16 is
+ that no hardware can be found by the probe routines if the sound card
+ is not configured properly. Sometimes the kernel probe routines can find
+ an SBPRO even when the card is not configured (this is the standard setup
+ of the card), but the SBPRO emulation don't work well if the card is not
+ properly initialized. For this reason
+
+ aedsp16_init_board()
+
+ routine is called before the OSS Lite probe routines try to detect the
+ hardware.
+
+ NOTE (READ THE NOTE TOO, IT CONTAIN USEFUL INFORMATIONS)
+
+ NOTE: Now it works with SC-6000 and SC-6600 based audio cards. The new cards
+ have no jumper switch at all. No more WSS or MPU-401 I/O port switches. They
+ have to be configured by software.
+
+ NOTE: The driver is merged with the new OSS Lite sound driver. It works
+ as a lowlevel driver.
+
+ The Audio Excel DSP 16 Sound Card emulates both SBPRO and MSS;
+ the OSS Lite sound driver can be configured for SBPRO and MSS cards
+ at the same time, but the aedsp16 can't be two cards!!
+ When we configure it, we have to choose the SBPRO or the MSS emulation
+ for AEDSP16. We also can install a *REAL* card of the other type (see [1]).
+
+ NOTE: If someone can test the combination AEDSP16+MSS or AEDSP16+SBPRO
+ please let me know if it works.
+
+ The MPU-401 support can be compiled in together with one of the other
+ two operating modes.
+
+ NOTE: This is something like plug-and-play: we have only to plug
+ the AEDSP16 board in the socket, and then configure and compile
+ a kernel that uses the AEDSP16 software configuration capability.
+ No jumper setting is needed!
+
+ For example, if you want AEDSP16 to be an SBPro, on irq 10, dma 3
+ you have just to make config the OSS Lite package, configuring
+ the AEDSP16 sound card, then activating the SBPro emulation mode
+ and at last configuring IRQ and DMA.
+ Compile the kernel and run it.
+
+ NOTE: This means for SC-6000 cards that you can choose irq and dma,
+ but not the I/O addresses. To change I/O addresses you have to set
+ them with jumpers. For SC-6600 cards you have no jumpers so you have
+ to set up your full card configuration in the make config.
+
+ You can change the irq/dma/mirq settings WITHOUT THE NEED to open
+ your computer and massage the jumpers (there are no irq/dma/mirq
+ jumpers to be configured anyway, only I/O BASE values have to be
+ configured with jumpers)
+
+ For some ununderstandable reason, the card default of irq 7, dma 1,
+ don't work for me. Seems to be an IRQ or DMA conflict. Under heavy
+ HDD work, the kernel start to erupt out a lot of messages like:
+
+ 'Sound: DMA timed out - IRQ/DRQ config error?'
+
+ For what I can say, I have NOT any conflict at irq 7 (under linux I'm
+ using the lp polling driver), and dma line 1 is unused as stated by
+ /proc/dma. I can suppose this is a bug of AEDSP16. I know my hardware so
+ I'm pretty sure I have not any conflict, but may be I'm wrong. Who knows!
+ Anyway a setting of irq 10, dma 3 works really fine.
+
+ NOTE: if someone can use AEDSP16 with irq 7, dma 1, please let me know
+ the emulation mode, all the installed hardware and the hardware
+ configuration (irq and dma settings of all the hardware).
+
+ This init module should work with SBPRO+MSS, when one of the two is
+ the AEDSP16 emulation and the other the real card. (see [1])
+ For example:
+
+ AEDSP16 (0x220) in SBPRO emu (0x220) + real MSS + other
+ AEDSP16 (0x220) in MSS emu + real SBPRO (0x240) + other
+
+ MPU401 should work. (see [2])
+
+ [1]
+ ---
+ Date: Mon, 29 Jul 1997 08:35:40 +0100
+ From: Mr S J Greenaway <sjg95@unixfe.rl.ac.uk>
+
+ [...]
+ Just to let you know got my Audio Excel (emulating a MSS) working
+ with my original SB16, thanks for the driver!
+ [...]
+ ---
+
+ [2] Not tested by me for lack of hardware.
+
+ TODO, WISHES AND TECH
+
+ - About I/O ports allocation -
+
+ Request the 2x0h region (port base) in any case if we are using this card.
+
+ NOTE: the "aedsp16 (base)" string with which we are requesting the aedsp16
+ port base region (see code) does not mean necessarily that we are emulating
+ sbpro. Even if this region is the sbpro I/O ports region, we use this
+ region to access the control registers of the card, and if emulating
+ sbpro, I/O sbpro registers too. If we are emulating MSS, the sbpro
+ registers are not used, in no way, to emulate an sbpro: they are
+ used only for configuration purposes.
+
+ Started Fri Mar 17 16:13:18 MET 1995
+
+ v0.1 (ALPHA, was a user-level program called AudioExcelDSP16.c)
+ - Initial code.
+ v0.2 (ALPHA)
+ - Cleanups.
+ - Integrated with Linux voxware v 2.90-2 kernel sound driver.
+ - SoundBlaster Pro mode configuration.
+ - Microsoft Sound System mode configuration.
+ - MPU-401 mode configuration.
+ v0.3 (ALPHA)
+ - Cleanups.
+ - Rearranged the code to let aedsp16_init_board be more general.
+ - Erased the REALLY_SLOW_IO. We don't need it. Erased the linux/io.h
+ inclusion too. We rely on os.h
+ - Used the to get a variable
+ len string (we are not sure about the len of Copyright string).
+ This works with any SB and compatible.
+ - Added the code to request_region at device init (should go in
+ the main body of voxware).
+ v0.4 (BETA)
+ - Better configure.c patch for aedsp16 configuration (better
+ logic of inclusion of AEDSP16 support)
+ - Modified the conditional compilation to better support more than
+ one sound card of the emulated type (read the NOTES above)
+ - Moved the sb init routine from the attach to the very first
+ probe in sb_card.c
+ - Rearrangements and cleanups
+ - Wiped out some unnecessary code and variables: this is kernel
+ code so it is better save some TEXT and DATA
+ - Fixed the request_region code. We must allocate the aedsp16 (sbpro)
+ I/O ports in any case because they are used to access the DSP
+ configuration registers and we can not allow anyone to get them.
+ v0.5
+ - cleanups on comments
+ - prep for diffs against v3.0-proto-950402
+ v0.6
+ - removed the request_region()s when compiling the MODULE sound.o
+ because we are not allowed (by the actual voxware structure) to
+ release_region()
+ v0.7 (pre ALPHA, not distributed)
+ - started porting this module to kernel 1.3.84. Dummy probe/attach
+ routines.
+ v0.8 (ALPHA)
+ - attached all the init routines.
+ v0.9 (BETA)
+ - Integrated with linux-pre2.0.7
+ - Integrated with configuration scripts.
+ - Cleaned up and beautyfied the code.
+ v0.9.9 (BETA)
+ - Thanks to Piercarlo Grandi: corrected the conditonal compilation code.
+ Now only the code configured is compiled in, with some memory saving.
+ v0.9.10
+ - Integration into the sound/lowlevel/ section of the sound driver.
+ - Re-organized the code.
+ v0.9.11 (not distributed)
+ - Rewritten the init interface-routines to initialize the AEDSP16 in
+ one shot.
+ - More cosmetics.
+ - SC-6600 support.
+ - More soft/hard configuration.
+ v0.9.12
+ - Refined the v0.9.11 code with conditional compilation to distinguish
+ between SC-6000 and SC-6600 code.
+ v1.0.0
+ - Prep for merging with OSS Lite and Linux kernel 2.1.13
+ - Corrected a bug in request/check/release region calls (thanks to the
+ new kernel exception handling).
+ v1.1
+ - Revamped for integration with new modularized sound drivers: to enhance
+ the flexibility of modular version, I have removed all the conditional
+ compilation for SBPRO, MPU and MSS code. Now it is all managed with
+ the ae_config structure.
+ v1.2
+ - Module informations added.
+ - Removed aedsp16_delay_10msec(), now using mdelay(10)
+ - All data and funcs moved to .*.init section.
+ v1.3
+ Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 2000/09/27
+ - got rid of check_region
+
+ Known Problems:
+ - Audio Excel DSP 16 III don't work with this driver.
+
+ Credits:
+ Many thanks to Gerald Britton <gbritton@CapAccess.org>. He helped me a
+ lot in testing the 0.9.11 and 0.9.12 versions of this driver.
+
+ */
+
+
+#define VERSION "1.3" /* Version of Audio Excel DSP 16 driver */
+
+#undef AEDSP16_DEBUG /* Define this to 1 to enable debug code */
+#undef AEDSP16_DEBUG_MORE /* Define this to 1 to enable more debug */
+#undef AEDSP16_INFO /* Define this to 1 to enable info code */
+
+#if defined(AEDSP16_DEBUG)
+# define DBG(x) printk x
+# if defined(AEDSP16_DEBUG_MORE)
+# define DBG1(x) printk x
+# else
+# define DBG1(x)
+# endif
+#else
+# define DBG(x)
+# define DBG1(x)
+#endif
+
+/*
+ * Misc definitions
+ */
+#define TRUE 1
+#define FALSE 0
+
+/*
+ * Region Size for request/check/release region.
+ */
+#define IOBASE_REGION_SIZE 0x10
+
+/*
+ * Hardware related defaults
+ */
+#define DEF_AEDSP16_IOB 0x220 /* 0x220(default) 0x240 */
+#define DEF_AEDSP16_IRQ 7 /* 5 7(default) 9 10 11 */
+#define DEF_AEDSP16_MRQ 0 /* 5 7 9 10 0(default), 0 means disable */
+#define DEF_AEDSP16_DMA 1 /* 0 1(default) 3 */
+
+/*
+ * Commands of AEDSP16's DSP (SBPRO+special).
+ * Some of them are COMMAND_xx, in the future they may change.
+ */
+#define WRITE_MDIRQ_CFG 0x50 /* Set M&I&DRQ mask (the real config) */
+#define COMMAND_52 0x52 /* */
+#define READ_HARD_CFG 0x58 /* Read Hardware Config (I/O base etc) */
+#define COMMAND_5C 0x5c /* */
+#define COMMAND_60 0x60 /* */
+#define COMMAND_66 0x66 /* */
+#define COMMAND_6C 0x6c /* */
+#define COMMAND_6E 0x6e /* */
+#define COMMAND_88 0x88 /* */
+#define DSP_INIT_MSS 0x8c /* Enable Microsoft Sound System mode */
+#define COMMAND_C5 0xc5 /* */
+#define GET_DSP_VERSION 0xe1 /* Get DSP Version */
+#define GET_DSP_COPYRIGHT 0xe3 /* Get DSP Copyright */
+
+/*
+ * Offsets of AEDSP16 DSP I/O ports. The offset is added to base I/O port
+ * to have the actual I/O port.
+ * Register permissions are:
+ * (wo) == Write Only
+ * (ro) == Read Only
+ * (w-) == Write
+ * (r-) == Read
+ */
+#define DSP_RESET 0x06 /* offset of DSP RESET (wo) */
+#define DSP_READ 0x0a /* offset of DSP READ (ro) */
+#define DSP_WRITE 0x0c /* offset of DSP WRITE (w-) */
+#define DSP_COMMAND 0x0c /* offset of DSP COMMAND (w-) */
+#define DSP_STATUS 0x0c /* offset of DSP STATUS (r-) */
+#define DSP_DATAVAIL 0x0e /* offset of DSP DATA AVAILABLE (ro) */
+
+
+#define RETRY 10 /* Various retry values on I/O opera- */
+#define STATUSRETRY 1000 /* tions. Sometimes we have to */
+#define HARDRETRY 500000 /* wait for previous cmd to complete */
+
+/*
+ * Size of character arrays that store name and version of sound card
+ */
+#define CARDNAMELEN 15 /* Size of the card's name in chars */
+#define CARDVERLEN 10 /* Size of the card's version in chars */
+#define CARDVERDIGITS 2 /* Number of digits in the version */
+
+#if defined(CONFIG_SC6600)
+/*
+ * Bitmapped flags of hard configuration
+ */
+/*
+ * Decode macros (xl == low byte, xh = high byte)
+ */
+#define IOBASE(xl) ((xl & 0x01)?0x240:0x220)
+#define JOY(xl) (xl & 0x02)
+#define MPUADDR(xl) ( \
+ (xl & 0x0C)?0x330: \
+ (xl & 0x08)?0x320: \
+ (xl & 0x04)?0x310: \
+ 0x300)
+#define WSSADDR(xl) ((xl & 0x10)?0xE80:0x530)
+#define CDROM(xh) (xh & 0x20)
+#define CDROMADDR(xh) (((xh & 0x1F) << 4) + 0x200)
+/*
+ * Encode macros
+ */
+#define BLDIOBASE(xl, val) { \
+ xl &= ~0x01; \
+ if (val == 0x240) \
+ xl |= 0x01; \
+ }
+#define BLDJOY(xl, val) { \
+ xl &= ~0x02; \
+ if (val == 1) \
+ xl |= 0x02; \
+ }
+#define BLDMPUADDR(xl, val) { \
+ xl &= ~0x0C; \
+ switch (val) { \
+ case 0x330: \
+ xl |= 0x0C; \
+ break; \
+ case 0x320: \
+ xl |= 0x08; \
+ break; \
+ case 0x310: \
+ xl |= 0x04; \
+ break; \
+ case 0x300: \
+ xl |= 0x00; \
+ break; \
+ default: \
+ xl |= 0x00; \
+ break; \
+ } \
+ }
+#define BLDWSSADDR(xl, val) { \
+ xl &= ~0x10; \
+ if (val == 0xE80) \
+ xl |= 0x10; \
+ }
+#define BLDCDROM(xh, val) { \
+ xh &= ~0x20; \
+ if (val == 1) \
+ xh |= 0x20; \
+ }
+#define BLDCDROMADDR(xh, val) { \
+ int tmp = val; \
+ tmp -= 0x200; \
+ tmp >>= 4; \
+ tmp &= 0x1F; \
+ xh |= tmp; \
+ xh &= 0x7F; \
+ xh |= 0x40; \
+ }
+#endif /* CONFIG_SC6600 */
+
+/*
+ * Bit mapped flags for calling aedsp16_init_board(), and saving the current
+ * emulation mode.
+ */
+#define INIT_NONE (0 )
+#define INIT_SBPRO (1<<0)
+#define INIT_MSS (1<<1)
+#define INIT_MPU401 (1<<2)
+
+static int soft_cfg __initdata = 0; /* bitmapped config */
+static int soft_cfg_mss __initdata = 0; /* bitmapped mss config */
+static int ver[CARDVERDIGITS] __initdata = {0, 0}; /* DSP Ver:
+ hi->ver[0] lo->ver[1] */
+
+#if defined(CONFIG_SC6600)
+static int hard_cfg[2] /* lo<-hard_cfg[0] hi<-hard_cfg[1] */
+ __initdata = { 0, 0};
+#endif /* CONFIG_SC6600 */
+
+#if defined(CONFIG_SC6600)
+/* Decoded hard configuration */
+struct d_hcfg {
+ int iobase;
+ int joystick;
+ int mpubase;
+ int wssbase;
+ int cdrom;
+ int cdrombase;
+};
+
+static struct d_hcfg decoded_hcfg __initdata = {0, };
+
+#endif /* CONFIG_SC6600 */
+
+/* orVals contain the values to be or'ed */
+struct orVals {
+ int val; /* irq|mirq|dma */
+ int or; /* soft_cfg |= TheStruct.or */
+};
+
+/* aedsp16_info contain the audio card configuration */
+struct aedsp16_info {
+ int base_io; /* base I/O address for accessing card */
+ int irq; /* irq value for DSP I/O */
+ int mpu_irq; /* irq for mpu401 interface I/O */
+ int dma; /* dma value for DSP I/O */
+ int mss_base; /* base I/O for Microsoft Sound System */
+ int mpu_base; /* base I/O for MPU-401 emulation */
+ int init; /* Initialization status of the card */
+};
+
+/*
+ * Magic values that the DSP will eat when configuring irq/mirq/dma
+ */
+/* DSP IRQ conversion array */
+static struct orVals orIRQ[] __initdata = {
+ {0x05, 0x28},
+ {0x07, 0x08},
+ {0x09, 0x10},
+ {0x0a, 0x18},
+ {0x0b, 0x20},
+ {0x00, 0x00}
+};
+
+/* MPU-401 IRQ conversion array */
+static struct orVals orMIRQ[] __initdata = {
+ {0x05, 0x04},
+ {0x07, 0x44},
+ {0x09, 0x84},
+ {0x0a, 0xc4},
+ {0x00, 0x00}
+};
+
+/* DMA Channels conversion array */
+static struct orVals orDMA[] __initdata = {
+ {0x00, 0x01},
+ {0x01, 0x02},
+ {0x03, 0x03},
+ {0x00, 0x00}
+};
+
+static struct aedsp16_info ae_config = {
+ DEF_AEDSP16_IOB,
+ DEF_AEDSP16_IRQ,
+ DEF_AEDSP16_MRQ,
+ DEF_AEDSP16_DMA,
+ -1,
+ -1,
+ INIT_NONE
+};
+
+/*
+ * Buffers to store audio card informations
+ */
+static char DSPCopyright[CARDNAMELEN + 1] __initdata = {0, };
+static char DSPVersion[CARDVERLEN + 1] __initdata = {0, };
+
+static int __init aedsp16_wait_data(int port)
+{
+ int loop = STATUSRETRY;
+ unsigned char ret = 0;
+
+ DBG1(("aedsp16_wait_data (0x%x): ", port));
+
+ do {
+ ret = inb(port + DSP_DATAVAIL);
+ /*
+ * Wait for data available (bit 7 of ret == 1)
+ */
+ } while (!(ret & 0x80) && loop--);
+
+ if (ret & 0x80) {
+ DBG1(("success.\n"));
+ return TRUE;
+ }
+
+ DBG1(("failure.\n"));
+ return FALSE;
+}
+
+static int __init aedsp16_read(int port)
+{
+ int inbyte;
+
+ DBG((" Read DSP Byte (0x%x): ", port));
+
+ if (aedsp16_wait_data(port) == FALSE) {
+ DBG(("failure.\n"));
+ return -1;
+ }
+
+ inbyte = inb(port + DSP_READ);
+
+ DBG(("read [0x%x]/{%c}.\n", inbyte, inbyte));
+
+ return inbyte;
+}
+
+static int __init aedsp16_test_dsp(int port)
+{
+ return ((aedsp16_read(port) == 0xaa) ? TRUE : FALSE);
+}
+
+static int __init aedsp16_dsp_reset(int port)
+{
+ /*
+ * Reset DSP
+ */
+
+ DBG(("Reset DSP:\n"));
+
+ outb(1, (port + DSP_RESET));
+ udelay(10);
+ outb(0, (port + DSP_RESET));
+ udelay(10);
+ udelay(10);
+ if (aedsp16_test_dsp(port) == TRUE) {
+ DBG(("success.\n"));
+ return TRUE;
+ } else
+ DBG(("failure.\n"));
+ return FALSE;
+}
+
+static int __init aedsp16_write(int port, int cmd)
+{
+ unsigned char ret;
+ int loop = HARDRETRY;
+
+ DBG((" Write DSP Byte (0x%x) [0x%x]: ", port, cmd));
+
+ do {
+ ret = inb(port + DSP_STATUS);
+ /*
+ * DSP ready to receive data if bit 7 of ret == 0
+ */
+ if (!(ret & 0x80)) {
+ outb(cmd, port + DSP_COMMAND);
+ DBG(("success.\n"));
+ return 0;
+ }
+ } while (loop--);
+
+ DBG(("timeout.\n"));
+ printk("[AEDSP16] DSP Command (0x%x) timeout.\n", cmd);
+
+ return -1;
+}
+
+#if defined(CONFIG_SC6600)
+
+#if defined(AEDSP16_INFO) || defined(AEDSP16_DEBUG)
+void __init aedsp16_pinfo(void) {
+ DBG(("\n Base address: %x\n", decoded_hcfg.iobase));
+ DBG((" Joystick : %s present\n", decoded_hcfg.joystick?"":" not"));
+ DBG((" WSS addr : %x\n", decoded_hcfg.wssbase));
+ DBG((" MPU-401 addr: %x\n", decoded_hcfg.mpubase));
+ DBG((" CDROM : %s present\n", (decoded_hcfg.cdrom!=4)?"":" not"));
+ DBG((" CDROMADDR : %x\n\n", decoded_hcfg.cdrombase));
+}
+#endif
+
+static void __init aedsp16_hard_decode(void) {
+
+ DBG((" aedsp16_hard_decode: 0x%x, 0x%x\n", hard_cfg[0], hard_cfg[1]));
+
+/*
+ * Decode Cfg Bytes.
+ */
+ decoded_hcfg.iobase = IOBASE(hard_cfg[0]);
+ decoded_hcfg.joystick = JOY(hard_cfg[0]);
+ decoded_hcfg.wssbase = WSSADDR(hard_cfg[0]);
+ decoded_hcfg.mpubase = MPUADDR(hard_cfg[0]);
+ decoded_hcfg.cdrom = CDROM(hard_cfg[1]);
+ decoded_hcfg.cdrombase = CDROMADDR(hard_cfg[1]);
+
+#if defined(AEDSP16_INFO) || defined(AEDSP16_DEBUG)
+ printk(" Original sound card configuration:\n");
+ aedsp16_pinfo();
+#endif
+
+/*
+ * Now set up the real kernel configuration.
+ */
+ decoded_hcfg.iobase = ae_config.base_io;
+ decoded_hcfg.wssbase = ae_config.mss_base;
+ decoded_hcfg.mpubase = ae_config.mpu_base;
+
+#if defined(CONFIG_SC6600_JOY)
+ decoded_hcfg.joystick = CONFIG_SC6600_JOY; /* Enable */
+#endif
+#if defined(CONFIG_SC6600_CDROM)
+ decoded_hcfg.cdrom = CONFIG_SC6600_CDROM; /* 4:N-3:I-2:G-1:P-0:S */
+#endif
+#if defined(CONFIG_SC6600_CDROMBASE)
+ decoded_hcfg.cdrombase = CONFIG_SC6600_CDROMBASE; /* 0 Disable */
+#endif
+
+#if defined(AEDSP16_DEBUG)
+ DBG((" New Values:\n"));
+ aedsp16_pinfo();
+#endif
+
+ DBG(("success.\n"));
+}
+
+static void __init aedsp16_hard_encode(void) {
+
+ DBG((" aedsp16_hard_encode: 0x%x, 0x%x\n", hard_cfg[0], hard_cfg[1]));
+
+ hard_cfg[0] = 0;
+ hard_cfg[1] = 0;
+
+ hard_cfg[0] |= 0x20;
+
+ BLDIOBASE (hard_cfg[0], decoded_hcfg.iobase);
+ BLDWSSADDR(hard_cfg[0], decoded_hcfg.wssbase);
+ BLDMPUADDR(hard_cfg[0], decoded_hcfg.mpubase);
+ BLDJOY(hard_cfg[0], decoded_hcfg.joystick);
+ BLDCDROM(hard_cfg[1], decoded_hcfg.cdrom);
+ BLDCDROMADDR(hard_cfg[1], decoded_hcfg.cdrombase);
+
+#if defined(AEDSP16_DEBUG)
+ aedsp16_pinfo();
+#endif
+
+ DBG((" aedsp16_hard_encode: 0x%x, 0x%x\n", hard_cfg[0], hard_cfg[1]));
+ DBG(("success.\n"));
+
+}
+
+static int __init aedsp16_hard_write(int port) {
+
+ DBG(("aedsp16_hard_write:\n"));
+
+ if (aedsp16_write(port, COMMAND_6C)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_6C);
+ DBG(("failure.\n"));
+ return FALSE;
+ }
+ if (aedsp16_write(port, COMMAND_5C)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_5C);
+ DBG(("failure.\n"));
+ return FALSE;
+ }
+ if (aedsp16_write(port, hard_cfg[0])) {
+ printk("[AEDSP16] DATA 0x%x: failed!\n", hard_cfg[0]);
+ DBG(("failure.\n"));
+ return FALSE;
+ }
+ if (aedsp16_write(port, hard_cfg[1])) {
+ printk("[AEDSP16] DATA 0x%x: failed!\n", hard_cfg[1]);
+ DBG(("failure.\n"));
+ return FALSE;
+ }
+ if (aedsp16_write(port, COMMAND_C5)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_C5);
+ DBG(("failure.\n"));
+ return FALSE;
+ }
+
+ DBG(("success.\n"));
+
+ return TRUE;
+}
+
+static int __init aedsp16_hard_read(int port) {
+
+ DBG(("aedsp16_hard_read:\n"));
+
+ if (aedsp16_write(port, READ_HARD_CFG)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", READ_HARD_CFG);
+ DBG(("failure.\n"));
+ return FALSE;
+ }
+
+ if ((hard_cfg[0] = aedsp16_read(port)) == -1) {
+ printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n",
+ READ_HARD_CFG);
+ DBG(("failure.\n"));
+ return FALSE;
+ }
+ if ((hard_cfg[1] = aedsp16_read(port)) == -1) {
+ printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n",
+ READ_HARD_CFG);
+ DBG(("failure.\n"));
+ return FALSE;
+ }
+ if (aedsp16_read(port) == -1) {
+ printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n",
+ READ_HARD_CFG);
+ DBG(("failure.\n"));
+ return FALSE;
+ }
+
+ DBG(("success.\n"));
+
+ return TRUE;
+}
+
+static int __init aedsp16_ext_cfg_write(int port) {
+
+ int extcfg, val;
+
+ if (aedsp16_write(port, COMMAND_66)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_66);
+ return FALSE;
+ }
+
+ extcfg = 7;
+ if (decoded_hcfg.cdrom != 2)
+ extcfg = 0x0F;
+ if ((decoded_hcfg.cdrom == 4) ||
+ (decoded_hcfg.cdrom == 3))
+ extcfg &= ~2;
+ if (decoded_hcfg.cdrombase == 0)
+ extcfg &= ~2;
+ if (decoded_hcfg.mpubase == 0)
+ extcfg &= ~1;
+
+ if (aedsp16_write(port, extcfg)) {
+ printk("[AEDSP16] Write extcfg: failed!\n");
+ return FALSE;
+ }
+ if (aedsp16_write(port, 0)) {
+ printk("[AEDSP16] Write extcfg: failed!\n");
+ return FALSE;
+ }
+ if (decoded_hcfg.cdrom == 3) {
+ if (aedsp16_write(port, COMMAND_52)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_52);
+ return FALSE;
+ }
+ if ((val = aedsp16_read(port)) == -1) {
+ printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n"
+ , COMMAND_52);
+ return FALSE;
+ }
+ val &= 0x7F;
+ if (aedsp16_write(port, COMMAND_60)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_60);
+ return FALSE;
+ }
+ if (aedsp16_write(port, val)) {
+ printk("[AEDSP16] Write val: failed!\n");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif /* CONFIG_SC6600 */
+
+static int __init aedsp16_cfg_write(int port) {
+ if (aedsp16_write(port, WRITE_MDIRQ_CFG)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG);
+ return FALSE;
+ }
+ if (aedsp16_write(port, soft_cfg)) {
+ printk("[AEDSP16] Initialization of (M)IRQ and DMA: failed!\n");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int __init aedsp16_init_mss(int port)
+{
+ DBG(("aedsp16_init_mss:\n"));
+
+ mdelay(10);
+
+ if (aedsp16_write(port, DSP_INIT_MSS)) {
+ printk("[AEDSP16] aedsp16_init_mss [0x%x]: failed!\n",
+ DSP_INIT_MSS);
+ DBG(("failure.\n"));
+ return FALSE;
+ }
+
+ mdelay(10);
+
+ if (aedsp16_cfg_write(port) == FALSE)
+ return FALSE;
+
+ outb(soft_cfg_mss, ae_config.mss_base);
+
+ DBG(("success.\n"));
+
+ return TRUE;
+}
+
+static int __init aedsp16_setup_board(int port) {
+ int loop = RETRY;
+
+#if defined(CONFIG_SC6600)
+ int val = 0;
+
+ if (aedsp16_hard_read(port) == FALSE) {
+ printk("[AEDSP16] aedsp16_hard_read: failed!\n");
+ return FALSE;
+ }
+
+ if (aedsp16_write(port, COMMAND_52)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_52);
+ return FALSE;
+ }
+
+ if ((val = aedsp16_read(port)) == -1) {
+ printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n",
+ COMMAND_52);
+ return FALSE;
+ }
+#endif
+
+ do {
+ if (aedsp16_write(port, COMMAND_88)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_88);
+ return FALSE;
+ }
+ mdelay(10);
+ } while ((aedsp16_wait_data(port) == FALSE) && loop--);
+
+ if (aedsp16_read(port) == -1) {
+ printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n",
+ COMMAND_88);
+ return FALSE;
+ }
+
+#if !defined(CONFIG_SC6600)
+ if (aedsp16_write(port, COMMAND_5C)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_5C);
+ return FALSE;
+ }
+#endif
+
+ if (aedsp16_cfg_write(port) == FALSE)
+ return FALSE;
+
+#if defined(CONFIG_SC6600)
+ if (aedsp16_write(port, COMMAND_60)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_60);
+ return FALSE;
+ }
+ if (aedsp16_write(port, val)) {
+ printk("[AEDSP16] DATA 0x%x: failed!\n", val);
+ return FALSE;
+ }
+ if (aedsp16_write(port, COMMAND_6E)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_6E);
+ return FALSE;
+ }
+ if (aedsp16_write(port, ver[0])) {
+ printk("[AEDSP16] DATA 0x%x: failed!\n", ver[0]);
+ return FALSE;
+ }
+ if (aedsp16_write(port, ver[1])) {
+ printk("[AEDSP16] DATA 0x%x: failed!\n", ver[1]);
+ return FALSE;
+ }
+
+ if (aedsp16_hard_write(port) == FALSE) {
+ printk("[AEDSP16] aedsp16_hard_write: failed!\n");
+ return FALSE;
+ }
+
+ if (aedsp16_write(port, COMMAND_5C)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_5C);
+ return FALSE;
+ }
+
+#if defined(THIS_IS_A_THING_I_HAVE_NOT_TESTED_YET)
+ if (aedsp16_cfg_write(port) == FALSE)
+ return FALSE;
+#endif
+
+#endif
+
+ return TRUE;
+}
+
+static int __init aedsp16_stdcfg(int port) {
+ if (aedsp16_write(port, WRITE_MDIRQ_CFG)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG);
+ return FALSE;
+ }
+ /*
+ * 0x0A == (IRQ 7, DMA 1, MIRQ 0)
+ */
+ if (aedsp16_write(port, 0x0A)) {
+ printk("[AEDSP16] aedsp16_stdcfg: failed!\n");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int __init aedsp16_dsp_version(int port)
+{
+ int len = 0;
+ int ret;
+
+ DBG(("Get DSP Version:\n"));
+
+ if (aedsp16_write(ae_config.base_io, GET_DSP_VERSION)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", GET_DSP_VERSION);
+ DBG(("failed.\n"));
+ return FALSE;
+ }
+
+ do {
+ if ((ret = aedsp16_read(port)) == -1) {
+ DBG(("failed.\n"));
+ return FALSE;
+ }
+ /*
+ * We already know how many int are stored (2), so we know when the
+ * string is finished.
+ */
+ ver[len++] = ret;
+ } while (len < CARDVERDIGITS);
+ sprintf(DSPVersion, "%d.%d", ver[0], ver[1]);
+
+ DBG(("success.\n"));
+
+ return TRUE;
+}
+
+static int __init aedsp16_dsp_copyright(int port)
+{
+ int len = 0;
+ int ret;
+
+ DBG(("Get DSP Copyright:\n"));
+
+ if (aedsp16_write(ae_config.base_io, GET_DSP_COPYRIGHT)) {
+ printk("[AEDSP16] CMD 0x%x: failed!\n", GET_DSP_COPYRIGHT);
+ DBG(("failed.\n"));
+ return FALSE;
+ }
+
+ do {
+ if ((ret = aedsp16_read(port)) == -1) {
+ /*
+ * If no more data available, return to the caller, no error if len>0.
+ * We have no other way to know when the string is finished.
+ */
+ if (len)
+ break;
+ else {
+ DBG(("failed.\n"));
+ return FALSE;
+ }
+ }
+
+ DSPCopyright[len++] = ret;
+
+ } while (len < CARDNAMELEN);
+
+ DBG(("success.\n"));
+
+ return TRUE;
+}
+
+static void __init aedsp16_init_tables(void)
+{
+ int i = 0;
+
+ memset(DSPCopyright, 0, CARDNAMELEN + 1);
+ memset(DSPVersion, 0, CARDVERLEN + 1);
+
+ for (i = 0; orIRQ[i].or; i++)
+ if (orIRQ[i].val == ae_config.irq) {
+ soft_cfg |= orIRQ[i].or;
+ soft_cfg_mss |= orIRQ[i].or;
+ }
+
+ for (i = 0; orMIRQ[i].or; i++)
+ if (orMIRQ[i].or == ae_config.mpu_irq)
+ soft_cfg |= orMIRQ[i].or;
+
+ for (i = 0; orDMA[i].or; i++)
+ if (orDMA[i].val == ae_config.dma) {
+ soft_cfg |= orDMA[i].or;
+ soft_cfg_mss |= orDMA[i].or;
+ }
+}
+
+static int __init aedsp16_init_board(void)
+{
+ aedsp16_init_tables();
+
+ if (aedsp16_dsp_reset(ae_config.base_io) == FALSE) {
+ printk("[AEDSP16] aedsp16_dsp_reset: failed!\n");
+ return FALSE;
+ }
+ if (aedsp16_dsp_copyright(ae_config.base_io) == FALSE) {
+ printk("[AEDSP16] aedsp16_dsp_copyright: failed!\n");
+ return FALSE;
+ }
+
+ /*
+ * My AEDSP16 card return SC-6000 in DSPCopyright, so
+ * if we have something different, we have to be warned.
+ */
+ if (strcmp("SC-6000", DSPCopyright))
+ printk("[AEDSP16] Warning: non SC-6000 audio card!\n");
+
+ if (aedsp16_dsp_version(ae_config.base_io) == FALSE) {
+ printk("[AEDSP16] aedsp16_dsp_version: failed!\n");
+ return FALSE;
+ }
+
+ if (aedsp16_stdcfg(ae_config.base_io) == FALSE) {
+ printk("[AEDSP16] aedsp16_stdcfg: failed!\n");
+ return FALSE;
+ }
+
+#if defined(CONFIG_SC6600)
+ if (aedsp16_hard_read(ae_config.base_io) == FALSE) {
+ printk("[AEDSP16] aedsp16_hard_read: failed!\n");
+ return FALSE;
+ }
+
+ aedsp16_hard_decode();
+
+ aedsp16_hard_encode();
+
+ if (aedsp16_hard_write(ae_config.base_io) == FALSE) {
+ printk("[AEDSP16] aedsp16_hard_write: failed!\n");
+ return FALSE;
+ }
+
+ if (aedsp16_ext_cfg_write(ae_config.base_io) == FALSE) {
+ printk("[AEDSP16] aedsp16_ext_cfg_write: failed!\n");
+ return FALSE;
+ }
+#endif /* CONFIG_SC6600 */
+
+ if (aedsp16_setup_board(ae_config.base_io) == FALSE) {
+ printk("[AEDSP16] aedsp16_setup_board: failed!\n");
+ return FALSE;
+ }
+
+ if (ae_config.mss_base != -1) {
+ if (ae_config.init & INIT_MSS) {
+ if (aedsp16_init_mss(ae_config.base_io) == FALSE) {
+ printk("[AEDSP16] Can not initialize"
+ "Microsoft Sound System mode.\n");
+ return FALSE;
+ }
+ }
+ }
+
+#if !defined(MODULE) || defined(AEDSP16_INFO) || defined(AEDSP16_DEBUG)
+
+ printk("Audio Excel DSP 16 init v%s (%s %s) [",
+ VERSION, DSPCopyright,
+ DSPVersion);
+
+ if (ae_config.mpu_base != -1) {
+ if (ae_config.init & INIT_MPU401) {
+ printk("MPU401");
+ if ((ae_config.init & INIT_MSS) ||
+ (ae_config.init & INIT_SBPRO))
+ printk(" ");
+ }
+ }
+
+ if (ae_config.mss_base == -1) {
+ if (ae_config.init & INIT_SBPRO) {
+ printk("SBPro");
+ if (ae_config.init & INIT_MSS)
+ printk(" ");
+ }
+ }
+
+ if (ae_config.mss_base != -1)
+ if (ae_config.init & INIT_MSS)
+ printk("MSS");
+
+ printk("]\n");
+#endif /* MODULE || AEDSP16_INFO || AEDSP16_DEBUG */
+
+ mdelay(10);
+
+ return TRUE;
+}
+
+static int __init init_aedsp16_sb(void)
+{
+ DBG(("init_aedsp16_sb: "));
+
+/*
+ * If the card is already init'ed MSS, we can not init it to SBPRO too
+ * because the board can not emulate simultaneously MSS and SBPRO.
+ */
+ if (ae_config.init & INIT_MSS)
+ return FALSE;
+ if (ae_config.init & INIT_SBPRO)
+ return FALSE;
+
+ ae_config.init |= INIT_SBPRO;
+
+ DBG(("done.\n"));
+
+ return TRUE;
+}
+
+static void uninit_aedsp16_sb(void)
+{
+ DBG(("uninit_aedsp16_sb: "));
+
+ ae_config.init &= ~INIT_SBPRO;
+
+ DBG(("done.\n"));
+}
+
+static int __init init_aedsp16_mss(void)
+{
+ DBG(("init_aedsp16_mss: "));
+
+/*
+ * If the card is already init'ed SBPRO, we can not init it to MSS too
+ * because the board can not emulate simultaneously MSS and SBPRO.
+ */
+ if (ae_config.init & INIT_SBPRO)
+ return FALSE;
+ if (ae_config.init & INIT_MSS)
+ return FALSE;
+/*
+ * We must allocate the CONFIG_AEDSP16_BASE region too because these are the
+ * I/O ports to access card's control registers.
+ */
+ if (!(ae_config.init & INIT_MPU401)) {
+ if (!request_region(ae_config.base_io, IOBASE_REGION_SIZE,
+ "aedsp16 (base)")) {
+ printk(
+ "AEDSP16 BASE I/O port region is already in use.\n");
+ return FALSE;
+ }
+ }
+
+ ae_config.init |= INIT_MSS;
+
+ DBG(("done.\n"));
+
+ return TRUE;
+}
+
+static void uninit_aedsp16_mss(void)
+{
+ DBG(("uninit_aedsp16_mss: "));
+
+ if ((!(ae_config.init & INIT_MPU401)) &&
+ (ae_config.init & INIT_MSS)) {
+ release_region(ae_config.base_io, IOBASE_REGION_SIZE);
+ DBG(("AEDSP16 base region released.\n"));
+ }
+
+ ae_config.init &= ~INIT_MSS;
+ DBG(("done.\n"));
+}
+
+static int __init init_aedsp16_mpu(void)
+{
+ DBG(("init_aedsp16_mpu: "));
+
+ if (ae_config.init & INIT_MPU401)
+ return FALSE;
+
+/*
+ * We must request the CONFIG_AEDSP16_BASE region too because these are the I/O
+ * ports to access card's control registers.
+ */
+ if (!(ae_config.init & (INIT_MSS | INIT_SBPRO))) {
+ if (!request_region(ae_config.base_io, IOBASE_REGION_SIZE,
+ "aedsp16 (base)")) {
+ printk(
+ "AEDSP16 BASE I/O port region is already in use.\n");
+ return FALSE;
+ }
+ }
+
+ ae_config.init |= INIT_MPU401;
+
+ DBG(("done.\n"));
+
+ return TRUE;
+}
+
+static void uninit_aedsp16_mpu(void)
+{
+ DBG(("uninit_aedsp16_mpu: "));
+
+ if ((!(ae_config.init & (INIT_MSS | INIT_SBPRO))) &&
+ (ae_config.init & INIT_MPU401)) {
+ release_region(ae_config.base_io, IOBASE_REGION_SIZE);
+ DBG(("AEDSP16 base region released.\n"));
+ }
+
+ ae_config.init &= ~INIT_MPU401;
+
+ DBG(("done.\n"));
+}
+
+static int __init init_aedsp16(void)
+{
+ int initialized = FALSE;
+
+ DBG(("Initializing BASE[0x%x] IRQ[%d] DMA[%d] MIRQ[%d]\n",
+ ae_config.base_io,ae_config.irq,ae_config.dma,ae_config.mpu_irq));
+
+ if (ae_config.mss_base == -1) {
+ if (init_aedsp16_sb() == FALSE) {
+ uninit_aedsp16_sb();
+ } else {
+ initialized = TRUE;
+ }
+ }
+
+ if (ae_config.mpu_base != -1) {
+ if (init_aedsp16_mpu() == FALSE) {
+ uninit_aedsp16_mpu();
+ } else {
+ initialized = TRUE;
+ }
+ }
+
+/*
+ * In the sequence of init routines, the MSS init MUST be the last!
+ * This because of the special register programming the MSS mode needs.
+ * A board reset would disable the MSS mode restoring the default SBPRO
+ * mode.
+ */
+ if (ae_config.mss_base != -1) {
+ if (init_aedsp16_mss() == FALSE) {
+ uninit_aedsp16_mss();
+ } else {
+ initialized = TRUE;
+ }
+ }
+
+ if (initialized)
+ initialized = aedsp16_init_board();
+ return initialized;
+}
+
+static void __exit uninit_aedsp16(void)
+{
+ if (ae_config.mss_base != -1)
+ uninit_aedsp16_mss();
+ else
+ uninit_aedsp16_sb();
+ if (ae_config.mpu_base != -1)
+ uninit_aedsp16_mpu();
+}
+
+static int __initdata io = -1;
+static int __initdata irq = -1;
+static int __initdata dma = -1;
+static int __initdata mpu_irq = -1;
+static int __initdata mss_base = -1;
+static int __initdata mpu_base = -1;
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O base address (0x220 0x240)");
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "IRQ line (5 7 9 10 11)");
+module_param(dma, int, 0);
+MODULE_PARM_DESC(dma, "dma line (0 1 3)");
+module_param(mpu_irq, int, 0);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ line (5 7 9 10 0)");
+module_param(mss_base, int, 0);
+MODULE_PARM_DESC(mss_base, "MSS emulation I/O base address (0x530 0xE80)");
+module_param(mpu_base, int, 0);
+MODULE_PARM_DESC(mpu_base,"MPU-401 I/O base address (0x300 0x310 0x320 0x330)");
+MODULE_AUTHOR("Riccardo Facchetti <fizban@tin.it>");
+MODULE_DESCRIPTION("Audio Excel DSP 16 Driver Version " VERSION);
+MODULE_LICENSE("GPL");
+
+static int __init do_init_aedsp16(void) {
+ printk("Audio Excel DSP 16 init driver Copyright (C) Riccardo Facchetti 1995-98\n");
+ if (io == -1 || dma == -1 || irq == -1) {
+ printk(KERN_INFO "aedsp16: I/O, IRQ and DMA are mandatory\n");
+ return -EINVAL;
+ }
+
+ ae_config.base_io = io;
+ ae_config.irq = irq;
+ ae_config.dma = dma;
+
+ ae_config.mss_base = mss_base;
+ ae_config.mpu_base = mpu_base;
+ ae_config.mpu_irq = mpu_irq;
+
+ if (init_aedsp16() == FALSE) {
+ printk(KERN_ERR "aedsp16: initialization failed\n");
+ /*
+ * XXX
+ * What error should we return here ?
+ */
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void __exit cleanup_aedsp16(void) {
+ uninit_aedsp16();
+}
+
+module_init(do_init_aedsp16);
+module_exit(cleanup_aedsp16);
+
+#ifndef MODULE
+static int __init setup_aedsp16(char *str)
+{
+ /* io, irq, dma, mss_io, mpu_io, mpu_irq */
+ int ints[7];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ io = ints[1];
+ irq = ints[2];
+ dma = ints[3];
+ mss_base = ints[4];
+ mpu_base = ints[5];
+ mpu_irq = ints[6];
+ return 1;
+}
+
+__setup("aedsp16=", setup_aedsp16);
+#endif
diff --git a/sound/oss/audio.c b/sound/oss/audio.c
new file mode 100644
index 00000000..4b958b1c
--- /dev/null
+++ b/sound/oss/audio.c
@@ -0,0 +1,985 @@
+/*
+ * sound/oss/audio.c
+ *
+ * Device file manager for /dev/audio
+ */
+
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ * Thomas Sailer : moved several static variables into struct audio_operations
+ * (which is grossly misnamed btw.) because they have the same
+ * lifetime as the rest in there and dynamic allocation saves
+ * 12k or so
+ * Thomas Sailer : use more logical O_NONBLOCK semantics
+ * Daniel Rodriksson: reworked the use of the device specific copy_user
+ * still generic
+ * Horst von Brand: Add missing #include <linux/string.h>
+ * Chris Rankin : Update the module-usage counter for the coprocessor,
+ * and decrement the counters again if we cannot open
+ * the audio device.
+ */
+
+#include <linux/stddef.h>
+#include <linux/string.h>
+#include <linux/kmod.h>
+
+#include "sound_config.h"
+#include "ulaw.h"
+#include "coproc.h"
+
+#define NEUTRAL8 0x80
+#define NEUTRAL16 0x00
+
+
+static int dma_ioctl(int dev, unsigned int cmd, void __user *arg);
+
+static int set_format(int dev, int fmt)
+{
+ if (fmt != AFMT_QUERY)
+ {
+ audio_devs[dev]->local_conversion = 0;
+
+ if (!(audio_devs[dev]->format_mask & fmt)) /* Not supported */
+ {
+ if (fmt == AFMT_MU_LAW)
+ {
+ fmt = AFMT_U8;
+ audio_devs[dev]->local_conversion = CNV_MU_LAW;
+ }
+ else
+ fmt = AFMT_U8; /* This is always supported */
+ }
+ audio_devs[dev]->audio_format = audio_devs[dev]->d->set_bits(dev, fmt);
+ audio_devs[dev]->local_format = fmt;
+ }
+ else
+ return audio_devs[dev]->local_format;
+
+ if (audio_devs[dev]->local_conversion)
+ return audio_devs[dev]->local_conversion;
+ else
+ return audio_devs[dev]->local_format;
+}
+
+int audio_open(int dev, struct file *file)
+{
+ int ret;
+ int bits;
+ int dev_type = dev & 0x0f;
+ int mode = translate_mode(file);
+ const struct audio_driver *driver;
+ const struct coproc_operations *coprocessor;
+
+ dev = dev >> 4;
+
+ if (dev_type == SND_DEV_DSP16)
+ bits = 16;
+ else
+ bits = 8;
+
+ if (dev < 0 || dev >= num_audiodevs)
+ return -ENXIO;
+
+ driver = audio_devs[dev]->d;
+
+ if (!try_module_get(driver->owner))
+ return -ENODEV;
+
+ if ((ret = DMAbuf_open(dev, mode)) < 0)
+ goto error_1;
+
+ if ( (coprocessor = audio_devs[dev]->coproc) != NULL ) {
+ if (!try_module_get(coprocessor->owner))
+ goto error_2;
+
+ if ((ret = coprocessor->open(coprocessor->devc, COPR_PCM)) < 0) {
+ printk(KERN_WARNING "Sound: Can't access coprocessor device\n");
+ goto error_3;
+ }
+ }
+
+ audio_devs[dev]->local_conversion = 0;
+
+ if (dev_type == SND_DEV_AUDIO)
+ set_format(dev, AFMT_MU_LAW);
+ else
+ set_format(dev, bits);
+
+ audio_devs[dev]->audio_mode = AM_NONE;
+
+ return 0;
+
+ /*
+ * Clean-up stack: this is what needs (un)doing if
+ * we can't open the audio device ...
+ */
+ error_3:
+ module_put(coprocessor->owner);
+
+ error_2:
+ DMAbuf_release(dev, mode);
+
+ error_1:
+ module_put(driver->owner);
+
+ return ret;
+}
+
+static void sync_output(int dev)
+{
+ int p, i;
+ int l;
+ struct dma_buffparms *dmap = audio_devs[dev]->dmap_out;
+
+ if (dmap->fragment_size <= 0)
+ return;
+ dmap->flags |= DMA_POST;
+
+ /* Align the write pointer with fragment boundaries */
+
+ if ((l = dmap->user_counter % dmap->fragment_size) > 0)
+ {
+ int len;
+ unsigned long offs = dmap->user_counter % dmap->bytes_in_use;
+
+ len = dmap->fragment_size - l;
+ memset(dmap->raw_buf + offs, dmap->neutral_byte, len);
+ DMAbuf_move_wrpointer(dev, len);
+ }
+
+ /*
+ * Clean all unused buffer fragments.
+ */
+
+ p = dmap->qtail;
+ dmap->flags |= DMA_POST;
+
+ for (i = dmap->qlen + 1; i < dmap->nbufs; i++)
+ {
+ p = (p + 1) % dmap->nbufs;
+ if (((dmap->raw_buf + p * dmap->fragment_size) + dmap->fragment_size) >
+ (dmap->raw_buf + dmap->buffsize))
+ printk(KERN_ERR "audio: Buffer error 2\n");
+
+ memset(dmap->raw_buf + p * dmap->fragment_size,
+ dmap->neutral_byte,
+ dmap->fragment_size);
+ }
+
+ dmap->flags |= DMA_DIRTY;
+}
+
+void audio_release(int dev, struct file *file)
+{
+ const struct coproc_operations *coprocessor;
+ int mode = translate_mode(file);
+
+ dev = dev >> 4;
+
+ /*
+ * We do this in DMAbuf_release(). Why are we doing it
+ * here? Why don't we test the file mode before setting
+ * both flags? DMAbuf_release() does.
+ * ...pester...pester...pester...
+ */
+ audio_devs[dev]->dmap_out->closing = 1;
+ audio_devs[dev]->dmap_in->closing = 1;
+
+ /*
+ * We need to make sure we allocated the dmap_out buffer
+ * before we go mucking around with it in sync_output().
+ */
+ if (mode & OPEN_WRITE)
+ sync_output(dev);
+
+ if ( (coprocessor = audio_devs[dev]->coproc) != NULL ) {
+ coprocessor->close(coprocessor->devc, COPR_PCM);
+ module_put(coprocessor->owner);
+ }
+ DMAbuf_release(dev, mode);
+
+ module_put(audio_devs[dev]->d->owner);
+}
+
+static void translate_bytes(const unsigned char *table, unsigned char *buff, int n)
+{
+ unsigned long i;
+
+ if (n <= 0)
+ return;
+
+ for (i = 0; i < n; ++i)
+ buff[i] = table[buff[i]];
+}
+
+int audio_write(int dev, struct file *file, const char __user *buf, int count)
+{
+ int c, p, l, buf_size, used, returned;
+ int err;
+ char *dma_buf;
+
+ dev = dev >> 4;
+
+ p = 0;
+ c = count;
+
+ if(count < 0)
+ return -EINVAL;
+
+ if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+ return -EPERM;
+
+ if (audio_devs[dev]->flags & DMA_DUPLEX)
+ audio_devs[dev]->audio_mode |= AM_WRITE;
+ else
+ audio_devs[dev]->audio_mode = AM_WRITE;
+
+ if (!count) /* Flush output */
+ {
+ sync_output(dev);
+ return 0;
+ }
+
+ while (c)
+ {
+ if ((err = DMAbuf_getwrbuffer(dev, &dma_buf, &buf_size, !!(file->f_flags & O_NONBLOCK))) < 0)
+ {
+ /* Handle nonblocking mode */
+ if ((file->f_flags & O_NONBLOCK) && err == -EAGAIN)
+ return p? p : -EAGAIN; /* No more space. Return # of accepted bytes */
+ return err;
+ }
+ l = c;
+
+ if (l > buf_size)
+ l = buf_size;
+
+ returned = l;
+ used = l;
+ if (!audio_devs[dev]->d->copy_user)
+ {
+ if ((dma_buf + l) >
+ (audio_devs[dev]->dmap_out->raw_buf + audio_devs[dev]->dmap_out->buffsize))
+ {
+ printk(KERN_ERR "audio: Buffer error 3 (%lx,%d), (%lx, %d)\n", (long) dma_buf, l, (long) audio_devs[dev]->dmap_out->raw_buf, (int) audio_devs[dev]->dmap_out->buffsize);
+ return -EDOM;
+ }
+ if (dma_buf < audio_devs[dev]->dmap_out->raw_buf)
+ {
+ printk(KERN_ERR "audio: Buffer error 13 (%lx<%lx)\n", (long) dma_buf, (long) audio_devs[dev]->dmap_out->raw_buf);
+ return -EDOM;
+ }
+ if(copy_from_user(dma_buf, &(buf)[p], l))
+ return -EFAULT;
+ }
+ else audio_devs[dev]->d->copy_user (dev,
+ dma_buf, 0,
+ buf, p,
+ c, buf_size,
+ &used, &returned,
+ l);
+ l = returned;
+
+ if (audio_devs[dev]->local_conversion & CNV_MU_LAW)
+ {
+ translate_bytes(ulaw_dsp, (unsigned char *) dma_buf, l);
+ }
+ c -= used;
+ p += used;
+ DMAbuf_move_wrpointer(dev, l);
+
+ }
+
+ return count;
+}
+
+int audio_read(int dev, struct file *file, char __user *buf, int count)
+{
+ int c, p, l;
+ char *dmabuf;
+ int buf_no;
+
+ dev = dev >> 4;
+ p = 0;
+ c = count;
+
+ if (!(audio_devs[dev]->open_mode & OPEN_READ))
+ return -EPERM;
+
+ if ((audio_devs[dev]->audio_mode & AM_WRITE) && !(audio_devs[dev]->flags & DMA_DUPLEX))
+ sync_output(dev);
+
+ if (audio_devs[dev]->flags & DMA_DUPLEX)
+ audio_devs[dev]->audio_mode |= AM_READ;
+ else
+ audio_devs[dev]->audio_mode = AM_READ;
+
+ while(c)
+ {
+ if ((buf_no = DMAbuf_getrdbuffer(dev, &dmabuf, &l, !!(file->f_flags & O_NONBLOCK))) < 0)
+ {
+ /*
+ * Nonblocking mode handling. Return current # of bytes
+ */
+
+ if (p > 0) /* Avoid throwing away data */
+ return p; /* Return it instead */
+
+ if ((file->f_flags & O_NONBLOCK) && buf_no == -EAGAIN)
+ return -EAGAIN;
+
+ return buf_no;
+ }
+ if (l > c)
+ l = c;
+
+ /*
+ * Insert any local processing here.
+ */
+
+ if (audio_devs[dev]->local_conversion & CNV_MU_LAW)
+ {
+ translate_bytes(dsp_ulaw, (unsigned char *) dmabuf, l);
+ }
+
+ {
+ char *fixit = dmabuf;
+
+ if(copy_to_user(&(buf)[p], fixit, l))
+ return -EFAULT;
+ };
+
+ DMAbuf_rmchars(dev, buf_no, l);
+
+ p += l;
+ c -= l;
+ }
+
+ return count - c;
+}
+
+int audio_ioctl(int dev, struct file *file, unsigned int cmd, void __user *arg)
+{
+ int val, count;
+ unsigned long flags;
+ struct dma_buffparms *dmap;
+ int __user *p = arg;
+
+ dev = dev >> 4;
+
+ if (_IOC_TYPE(cmd) == 'C') {
+ if (audio_devs[dev]->coproc) /* Coprocessor ioctl */
+ return audio_devs[dev]->coproc->ioctl(audio_devs[dev]->coproc->devc, cmd, arg, 0);
+ /* else
+ printk(KERN_DEBUG"/dev/dsp%d: No coprocessor for this device\n", dev); */
+ return -ENXIO;
+ }
+ else switch (cmd)
+ {
+ case SNDCTL_DSP_SYNC:
+ if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+ return 0;
+ if (audio_devs[dev]->dmap_out->fragment_size == 0)
+ return 0;
+ sync_output(dev);
+ DMAbuf_sync(dev);
+ DMAbuf_reset(dev);
+ return 0;
+
+ case SNDCTL_DSP_POST:
+ if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+ return 0;
+ if (audio_devs[dev]->dmap_out->fragment_size == 0)
+ return 0;
+ audio_devs[dev]->dmap_out->flags |= DMA_POST | DMA_DIRTY;
+ sync_output(dev);
+ dma_ioctl(dev, SNDCTL_DSP_POST, NULL);
+ return 0;
+
+ case SNDCTL_DSP_RESET:
+ audio_devs[dev]->audio_mode = AM_NONE;
+ DMAbuf_reset(dev);
+ return 0;
+
+ case SNDCTL_DSP_GETFMTS:
+ val = audio_devs[dev]->format_mask | AFMT_MU_LAW;
+ break;
+
+ case SNDCTL_DSP_SETFMT:
+ if (get_user(val, p))
+ return -EFAULT;
+ val = set_format(dev, val);
+ break;
+
+ case SNDCTL_DSP_GETISPACE:
+ if (!(audio_devs[dev]->open_mode & OPEN_READ))
+ return 0;
+ if ((audio_devs[dev]->audio_mode & AM_WRITE) && !(audio_devs[dev]->flags & DMA_DUPLEX))
+ return -EBUSY;
+ return dma_ioctl(dev, cmd, arg);
+
+ case SNDCTL_DSP_GETOSPACE:
+ if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+ return -EPERM;
+ if ((audio_devs[dev]->audio_mode & AM_READ) && !(audio_devs[dev]->flags & DMA_DUPLEX))
+ return -EBUSY;
+ return dma_ioctl(dev, cmd, arg);
+
+ case SNDCTL_DSP_NONBLOCK:
+ spin_lock(&file->f_lock);
+ file->f_flags |= O_NONBLOCK;
+ spin_unlock(&file->f_lock);
+ return 0;
+
+ case SNDCTL_DSP_GETCAPS:
+ val = 1 | DSP_CAP_MMAP; /* Revision level of this ioctl() */
+ if (audio_devs[dev]->flags & DMA_DUPLEX &&
+ audio_devs[dev]->open_mode == OPEN_READWRITE)
+ val |= DSP_CAP_DUPLEX;
+ if (audio_devs[dev]->coproc)
+ val |= DSP_CAP_COPROC;
+ if (audio_devs[dev]->d->local_qlen) /* Device has hidden buffers */
+ val |= DSP_CAP_BATCH;
+ if (audio_devs[dev]->d->trigger) /* Supports SETTRIGGER */
+ val |= DSP_CAP_TRIGGER;
+ break;
+
+ case SOUND_PCM_WRITE_RATE:
+ if (get_user(val, p))
+ return -EFAULT;
+ val = audio_devs[dev]->d->set_speed(dev, val);
+ break;
+
+ case SOUND_PCM_READ_RATE:
+ val = audio_devs[dev]->d->set_speed(dev, 0);
+ break;
+
+ case SNDCTL_DSP_STEREO:
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val > 1 || val < 0)
+ return -EINVAL;
+ val = audio_devs[dev]->d->set_channels(dev, val + 1) - 1;
+ break;
+
+ case SOUND_PCM_WRITE_CHANNELS:
+ if (get_user(val, p))
+ return -EFAULT;
+ val = audio_devs[dev]->d->set_channels(dev, val);
+ break;
+
+ case SOUND_PCM_READ_CHANNELS:
+ val = audio_devs[dev]->d->set_channels(dev, 0);
+ break;
+
+ case SOUND_PCM_READ_BITS:
+ val = audio_devs[dev]->d->set_bits(dev, 0);
+ break;
+
+ case SNDCTL_DSP_SETDUPLEX:
+ if (audio_devs[dev]->open_mode != OPEN_READWRITE)
+ return -EPERM;
+ return (audio_devs[dev]->flags & DMA_DUPLEX) ? 0 : -EIO;
+
+ case SNDCTL_DSP_PROFILE:
+ if (get_user(val, p))
+ return -EFAULT;
+ if (audio_devs[dev]->open_mode & OPEN_WRITE)
+ audio_devs[dev]->dmap_out->applic_profile = val;
+ if (audio_devs[dev]->open_mode & OPEN_READ)
+ audio_devs[dev]->dmap_in->applic_profile = val;
+ return 0;
+
+ case SNDCTL_DSP_GETODELAY:
+ dmap = audio_devs[dev]->dmap_out;
+ if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+ return -EINVAL;
+ if (!(dmap->flags & DMA_ALLOC_DONE))
+ {
+ val=0;
+ break;
+ }
+
+ spin_lock_irqsave(&dmap->lock,flags);
+ /* Compute number of bytes that have been played */
+ count = DMAbuf_get_buffer_pointer (dev, dmap, DMODE_OUTPUT);
+ if (count < dmap->fragment_size && dmap->qhead != 0)
+ count += dmap->bytes_in_use; /* Pointer wrap not handled yet */
+ count += dmap->byte_counter;
+
+ /* Subtract current count from the number of bytes written by app */
+ count = dmap->user_counter - count;
+ if (count < 0)
+ count = 0;
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ val = count;
+ break;
+
+ default:
+ return dma_ioctl(dev, cmd, arg);
+ }
+ return put_user(val, p);
+}
+
+void audio_init_devices(void)
+{
+ /*
+ * NOTE! This routine could be called several times during boot.
+ */
+}
+
+void reorganize_buffers(int dev, struct dma_buffparms *dmap, int recording)
+{
+ /*
+ * This routine breaks the physical device buffers to logical ones.
+ */
+
+ struct audio_operations *dsp_dev = audio_devs[dev];
+
+ unsigned i, n;
+ unsigned sr, nc, sz, bsz;
+
+ sr = dsp_dev->d->set_speed(dev, 0);
+ nc = dsp_dev->d->set_channels(dev, 0);
+ sz = dsp_dev->d->set_bits(dev, 0);
+
+ if (sz == 8)
+ dmap->neutral_byte = NEUTRAL8;
+ else
+ dmap->neutral_byte = NEUTRAL16;
+
+ if (sr < 1 || nc < 1 || sz < 1)
+ {
+/* printk(KERN_DEBUG "Warning: Invalid PCM parameters[%d] sr=%d, nc=%d, sz=%d\n", dev, sr, nc, sz);*/
+ sr = DSP_DEFAULT_SPEED;
+ nc = 1;
+ sz = 8;
+ }
+
+ sz = sr * nc * sz;
+
+ sz /= 8; /* #bits -> #bytes */
+ dmap->data_rate = sz;
+
+ if (!dmap->needs_reorg)
+ return;
+ dmap->needs_reorg = 0;
+
+ if (dmap->fragment_size == 0)
+ {
+ /* Compute the fragment size using the default algorithm */
+
+ /*
+ * Compute a buffer size for time not exceeding 1 second.
+ * Usually this algorithm gives a buffer size for 0.5 to 1.0 seconds
+ * of sound (using the current speed, sample size and #channels).
+ */
+
+ bsz = dmap->buffsize;
+ while (bsz > sz)
+ bsz /= 2;
+
+ if (bsz == dmap->buffsize)
+ bsz /= 2; /* Needs at least 2 buffers */
+
+ /*
+ * Split the computed fragment to smaller parts. After 3.5a9
+ * the default subdivision is 4 which should give better
+ * results when recording.
+ */
+
+ if (dmap->subdivision == 0) /* Not already set */
+ {
+ dmap->subdivision = 4; /* Init to the default value */
+
+ if ((bsz / dmap->subdivision) > 4096)
+ dmap->subdivision *= 2;
+ if ((bsz / dmap->subdivision) < 4096)
+ dmap->subdivision = 1;
+ }
+ bsz /= dmap->subdivision;
+
+ if (bsz < 16)
+ bsz = 16; /* Just a sanity check */
+
+ dmap->fragment_size = bsz;
+ }
+ else
+ {
+ /*
+ * The process has specified the buffer size with SNDCTL_DSP_SETFRAGMENT or
+ * the buffer size computation has already been done.
+ */
+ if (dmap->fragment_size > (dmap->buffsize / 2))
+ dmap->fragment_size = (dmap->buffsize / 2);
+ bsz = dmap->fragment_size;
+ }
+
+ if (audio_devs[dev]->min_fragment)
+ if (bsz < (1 << audio_devs[dev]->min_fragment))
+ bsz = 1 << audio_devs[dev]->min_fragment;
+ if (audio_devs[dev]->max_fragment)
+ if (bsz > (1 << audio_devs[dev]->max_fragment))
+ bsz = 1 << audio_devs[dev]->max_fragment;
+ bsz &= ~0x07; /* Force size which is multiple of 8 bytes */
+#ifdef OS_DMA_ALIGN_CHECK
+ OS_DMA_ALIGN_CHECK(bsz);
+#endif
+
+ n = dmap->buffsize / bsz;
+ if (n > MAX_SUB_BUFFERS)
+ n = MAX_SUB_BUFFERS;
+ if (n > dmap->max_fragments)
+ n = dmap->max_fragments;
+
+ if (n < 2)
+ {
+ n = 2;
+ bsz /= 2;
+ }
+ dmap->nbufs = n;
+ dmap->bytes_in_use = n * bsz;
+ dmap->fragment_size = bsz;
+ dmap->max_byte_counter = (dmap->data_rate * 60 * 60) +
+ dmap->bytes_in_use; /* Approximately one hour */
+
+ if (dmap->raw_buf)
+ {
+ memset(dmap->raw_buf, dmap->neutral_byte, dmap->bytes_in_use);
+ }
+
+ for (i = 0; i < dmap->nbufs; i++)
+ {
+ dmap->counts[i] = 0;
+ }
+
+ dmap->flags |= DMA_ALLOC_DONE | DMA_EMPTY;
+}
+
+static int dma_subdivide(int dev, struct dma_buffparms *dmap, int fact)
+{
+ if (fact == 0)
+ {
+ fact = dmap->subdivision;
+ if (fact == 0)
+ fact = 1;
+ return fact;
+ }
+ if (dmap->subdivision != 0 || dmap->fragment_size) /* Too late to change */
+ return -EINVAL;
+
+ if (fact > MAX_REALTIME_FACTOR)
+ return -EINVAL;
+
+ if (fact != 1 && fact != 2 && fact != 4 && fact != 8 && fact != 16)
+ return -EINVAL;
+
+ dmap->subdivision = fact;
+ return fact;
+}
+
+static int dma_set_fragment(int dev, struct dma_buffparms *dmap, int fact)
+{
+ int bytes, count;
+
+ if (fact == 0)
+ return -EIO;
+
+ if (dmap->subdivision != 0 ||
+ dmap->fragment_size) /* Too late to change */
+ return -EINVAL;
+
+ bytes = fact & 0xffff;
+ count = (fact >> 16) & 0x7fff;
+
+ if (count == 0)
+ count = MAX_SUB_BUFFERS;
+ else if (count < MAX_SUB_BUFFERS)
+ count++;
+
+ if (bytes < 4 || bytes > 17) /* <16 || > 512k */
+ return -EINVAL;
+
+ if (count < 2)
+ return -EINVAL;
+
+ if (audio_devs[dev]->min_fragment > 0)
+ if (bytes < audio_devs[dev]->min_fragment)
+ bytes = audio_devs[dev]->min_fragment;
+
+ if (audio_devs[dev]->max_fragment > 0)
+ if (bytes > audio_devs[dev]->max_fragment)
+ bytes = audio_devs[dev]->max_fragment;
+
+#ifdef OS_DMA_MINBITS
+ if (bytes < OS_DMA_MINBITS)
+ bytes = OS_DMA_MINBITS;
+#endif
+
+ dmap->fragment_size = (1 << bytes);
+ dmap->max_fragments = count;
+
+ if (dmap->fragment_size > dmap->buffsize)
+ dmap->fragment_size = dmap->buffsize;
+
+ if (dmap->fragment_size == dmap->buffsize &&
+ audio_devs[dev]->flags & DMA_AUTOMODE)
+ dmap->fragment_size /= 2; /* Needs at least 2 buffers */
+
+ dmap->subdivision = 1; /* Disable SNDCTL_DSP_SUBDIVIDE */
+ return bytes | ((count - 1) << 16);
+}
+
+static int dma_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+ struct dma_buffparms *dmap_out = audio_devs[dev]->dmap_out;
+ struct dma_buffparms *dmap_in = audio_devs[dev]->dmap_in;
+ struct dma_buffparms *dmap;
+ audio_buf_info info;
+ count_info cinfo;
+ int fact, ret, changed, bits, count, err;
+ unsigned long flags;
+
+ switch (cmd)
+ {
+ case SNDCTL_DSP_SUBDIVIDE:
+ ret = 0;
+ if (get_user(fact, (int __user *)arg))
+ return -EFAULT;
+ if (audio_devs[dev]->open_mode & OPEN_WRITE)
+ ret = dma_subdivide(dev, dmap_out, fact);
+ if (ret < 0)
+ return ret;
+ if (audio_devs[dev]->open_mode != OPEN_WRITE ||
+ (audio_devs[dev]->flags & DMA_DUPLEX &&
+ audio_devs[dev]->open_mode & OPEN_READ))
+ ret = dma_subdivide(dev, dmap_in, fact);
+ if (ret < 0)
+ return ret;
+ break;
+
+ case SNDCTL_DSP_GETISPACE:
+ case SNDCTL_DSP_GETOSPACE:
+ dmap = dmap_out;
+ if (cmd == SNDCTL_DSP_GETISPACE && !(audio_devs[dev]->open_mode & OPEN_READ))
+ return -EINVAL;
+ if (cmd == SNDCTL_DSP_GETOSPACE && !(audio_devs[dev]->open_mode & OPEN_WRITE))
+ return -EINVAL;
+ if (cmd == SNDCTL_DSP_GETISPACE && audio_devs[dev]->flags & DMA_DUPLEX)
+ dmap = dmap_in;
+ if (dmap->mapping_flags & DMA_MAP_MAPPED)
+ return -EINVAL;
+ if (!(dmap->flags & DMA_ALLOC_DONE))
+ reorganize_buffers(dev, dmap, (cmd == SNDCTL_DSP_GETISPACE));
+ info.fragstotal = dmap->nbufs;
+ if (cmd == SNDCTL_DSP_GETISPACE)
+ info.fragments = dmap->qlen;
+ else
+ {
+ if (!DMAbuf_space_in_queue(dev))
+ info.fragments = 0;
+ else
+ {
+ info.fragments = DMAbuf_space_in_queue(dev);
+ if (audio_devs[dev]->d->local_qlen)
+ {
+ int tmp = audio_devs[dev]->d->local_qlen(dev);
+ if (tmp && info.fragments)
+ tmp--; /*
+ * This buffer has been counted twice
+ */
+ info.fragments -= tmp;
+ }
+ }
+ }
+ if (info.fragments < 0)
+ info.fragments = 0;
+ else if (info.fragments > dmap->nbufs)
+ info.fragments = dmap->nbufs;
+
+ info.fragsize = dmap->fragment_size;
+ info.bytes = info.fragments * dmap->fragment_size;
+
+ if (cmd == SNDCTL_DSP_GETISPACE && dmap->qlen)
+ info.bytes -= dmap->counts[dmap->qhead];
+ else
+ {
+ info.fragments = info.bytes / dmap->fragment_size;
+ info.bytes -= dmap->user_counter % dmap->fragment_size;
+ }
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_SETTRIGGER:
+ if (get_user(bits, (int __user *)arg))
+ return -EFAULT;
+ bits &= audio_devs[dev]->open_mode;
+ if (audio_devs[dev]->d->trigger == NULL)
+ return -EINVAL;
+ if (!(audio_devs[dev]->flags & DMA_DUPLEX) && (bits & PCM_ENABLE_INPUT) &&
+ (bits & PCM_ENABLE_OUTPUT))
+ return -EINVAL;
+
+ if (bits & PCM_ENABLE_INPUT)
+ {
+ spin_lock_irqsave(&dmap_in->lock,flags);
+ changed = (audio_devs[dev]->enable_bits ^ bits) & PCM_ENABLE_INPUT;
+ if (changed && audio_devs[dev]->go)
+ {
+ reorganize_buffers(dev, dmap_in, 1);
+ if ((err = audio_devs[dev]->d->prepare_for_input(dev,
+ dmap_in->fragment_size, dmap_in->nbufs)) < 0) {
+ spin_unlock_irqrestore(&dmap_in->lock,flags);
+ return err;
+ }
+ dmap_in->dma_mode = DMODE_INPUT;
+ audio_devs[dev]->enable_bits |= PCM_ENABLE_INPUT;
+ DMAbuf_activate_recording(dev, dmap_in);
+ } else
+ audio_devs[dev]->enable_bits &= ~PCM_ENABLE_INPUT;
+ spin_unlock_irqrestore(&dmap_in->lock,flags);
+ }
+ if (bits & PCM_ENABLE_OUTPUT)
+ {
+ spin_lock_irqsave(&dmap_out->lock,flags);
+ changed = (audio_devs[dev]->enable_bits ^ bits) & PCM_ENABLE_OUTPUT;
+ if (changed &&
+ (dmap_out->mapping_flags & DMA_MAP_MAPPED || dmap_out->qlen > 0) &&
+ audio_devs[dev]->go)
+ {
+ if (!(dmap_out->flags & DMA_ALLOC_DONE))
+ reorganize_buffers(dev, dmap_out, 0);
+ dmap_out->dma_mode = DMODE_OUTPUT;
+ audio_devs[dev]->enable_bits |= PCM_ENABLE_OUTPUT;
+ dmap_out->counts[dmap_out->qhead] = dmap_out->fragment_size;
+ DMAbuf_launch_output(dev, dmap_out);
+ } else
+ audio_devs[dev]->enable_bits &= ~PCM_ENABLE_OUTPUT;
+ spin_unlock_irqrestore(&dmap_out->lock,flags);
+ }
+#if 0
+ if (changed && audio_devs[dev]->d->trigger)
+ audio_devs[dev]->d->trigger(dev, bits * audio_devs[dev]->go);
+#endif
+ /* Falls through... */
+
+ case SNDCTL_DSP_GETTRIGGER:
+ ret = audio_devs[dev]->enable_bits;
+ break;
+
+ case SNDCTL_DSP_SETSYNCRO:
+ if (!audio_devs[dev]->d->trigger)
+ return -EINVAL;
+ audio_devs[dev]->d->trigger(dev, 0);
+ audio_devs[dev]->go = 0;
+ return 0;
+
+ case SNDCTL_DSP_GETIPTR:
+ if (!(audio_devs[dev]->open_mode & OPEN_READ))
+ return -EINVAL;
+ spin_lock_irqsave(&dmap_in->lock,flags);
+ cinfo.bytes = dmap_in->byte_counter;
+ cinfo.ptr = DMAbuf_get_buffer_pointer(dev, dmap_in, DMODE_INPUT) & ~3;
+ if (cinfo.ptr < dmap_in->fragment_size && dmap_in->qtail != 0)
+ cinfo.bytes += dmap_in->bytes_in_use; /* Pointer wrap not handled yet */
+ cinfo.blocks = dmap_in->qlen;
+ cinfo.bytes += cinfo.ptr;
+ if (dmap_in->mapping_flags & DMA_MAP_MAPPED)
+ dmap_in->qlen = 0; /* Reset interrupt counter */
+ spin_unlock_irqrestore(&dmap_in->lock,flags);
+ if (copy_to_user(arg, &cinfo, sizeof(cinfo)))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_GETOPTR:
+ if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+ return -EINVAL;
+
+ spin_lock_irqsave(&dmap_out->lock,flags);
+ cinfo.bytes = dmap_out->byte_counter;
+ cinfo.ptr = DMAbuf_get_buffer_pointer(dev, dmap_out, DMODE_OUTPUT) & ~3;
+ if (cinfo.ptr < dmap_out->fragment_size && dmap_out->qhead != 0)
+ cinfo.bytes += dmap_out->bytes_in_use; /* Pointer wrap not handled yet */
+ cinfo.blocks = dmap_out->qlen;
+ cinfo.bytes += cinfo.ptr;
+ if (dmap_out->mapping_flags & DMA_MAP_MAPPED)
+ dmap_out->qlen = 0; /* Reset interrupt counter */
+ spin_unlock_irqrestore(&dmap_out->lock,flags);
+ if (copy_to_user(arg, &cinfo, sizeof(cinfo)))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_GETODELAY:
+ if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+ return -EINVAL;
+ if (!(dmap_out->flags & DMA_ALLOC_DONE))
+ {
+ ret=0;
+ break;
+ }
+ spin_lock_irqsave(&dmap_out->lock,flags);
+ /* Compute number of bytes that have been played */
+ count = DMAbuf_get_buffer_pointer (dev, dmap_out, DMODE_OUTPUT);
+ if (count < dmap_out->fragment_size && dmap_out->qhead != 0)
+ count += dmap_out->bytes_in_use; /* Pointer wrap not handled yet */
+ count += dmap_out->byte_counter;
+ /* Subtract current count from the number of bytes written by app */
+ count = dmap_out->user_counter - count;
+ if (count < 0)
+ count = 0;
+ spin_unlock_irqrestore(&dmap_out->lock,flags);
+ ret = count;
+ break;
+
+ case SNDCTL_DSP_POST:
+ if (audio_devs[dev]->dmap_out->qlen > 0)
+ if (!(audio_devs[dev]->dmap_out->flags & DMA_ACTIVE))
+ DMAbuf_launch_output(dev, audio_devs[dev]->dmap_out);
+ return 0;
+
+ case SNDCTL_DSP_GETBLKSIZE:
+ dmap = dmap_out;
+ if (audio_devs[dev]->open_mode & OPEN_WRITE)
+ reorganize_buffers(dev, dmap_out, (audio_devs[dev]->open_mode == OPEN_READ));
+ if (audio_devs[dev]->open_mode == OPEN_READ ||
+ (audio_devs[dev]->flags & DMA_DUPLEX &&
+ audio_devs[dev]->open_mode & OPEN_READ))
+ reorganize_buffers(dev, dmap_in, (audio_devs[dev]->open_mode == OPEN_READ));
+ if (audio_devs[dev]->open_mode == OPEN_READ)
+ dmap = dmap_in;
+ ret = dmap->fragment_size;
+ break;
+
+ case SNDCTL_DSP_SETFRAGMENT:
+ ret = 0;
+ if (get_user(fact, (int __user *)arg))
+ return -EFAULT;
+ if (audio_devs[dev]->open_mode & OPEN_WRITE)
+ ret = dma_set_fragment(dev, dmap_out, fact);
+ if (ret < 0)
+ return ret;
+ if (audio_devs[dev]->open_mode == OPEN_READ ||
+ (audio_devs[dev]->flags & DMA_DUPLEX &&
+ audio_devs[dev]->open_mode & OPEN_READ))
+ ret = dma_set_fragment(dev, dmap_in, fact);
+ if (ret < 0)
+ return ret;
+ if (!arg) /* don't know what this is good for, but preserve old semantics */
+ return 0;
+ break;
+
+ default:
+ if (!audio_devs[dev]->d->ioctl)
+ return -EINVAL;
+ return audio_devs[dev]->d->ioctl(dev, cmd, arg);
+ }
+ return put_user(ret, (int __user *)arg);
+}
diff --git a/sound/oss/bin2hex.c b/sound/oss/bin2hex.c
new file mode 100644
index 00000000..b59109eb
--- /dev/null
+++ b/sound/oss/bin2hex.c
@@ -0,0 +1,39 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+int main( int argc, const char * argv [] )
+{
+ const char * varname;
+ int i = 0;
+ int c;
+ int id = 0;
+
+ if(argv[1] && strcmp(argv[1],"-i")==0)
+ {
+ argv++;
+ argc--;
+ id=1;
+ }
+
+ if(argc==1)
+ {
+ fprintf(stderr, "bin2hex: [-i] firmware\n");
+ exit(1);
+ }
+
+ varname = argv[1];
+ printf( "/* automatically generated by bin2hex */\n" );
+ printf( "static unsigned char %s [] %s =\n{\n", varname , id?"__initdata":"");
+
+ while ( ( c = getchar( ) ) != EOF )
+ {
+ if ( i != 0 && i % 10 == 0 )
+ printf( "\n" );
+ printf( "0x%02lx,", c & 0xFFl );
+ i++;
+ }
+
+ printf( "};\nstatic int %sLen = %d;\n", varname, i );
+ return 0;
+}
diff --git a/sound/oss/coproc.h b/sound/oss/coproc.h
new file mode 100644
index 00000000..7bec21bb
--- /dev/null
+++ b/sound/oss/coproc.h
@@ -0,0 +1,12 @@
+/*
+ * Definitions for various on board processors on the sound cards. For
+ * example DSP processors.
+ */
+
+/*
+ * Coprocessor access types
+ */
+#define COPR_CUSTOM 0x0001 /* Custom applications */
+#define COPR_MIDI 0x0002 /* MIDI (MPU-401) emulation */
+#define COPR_PCM 0x0004 /* Digitized voice applications */
+#define COPR_SYNTH 0x0008 /* Music synthesis */
diff --git a/sound/oss/dev_table.c b/sound/oss/dev_table.c
new file mode 100644
index 00000000..d8cf3e58
--- /dev/null
+++ b/sound/oss/dev_table.c
@@ -0,0 +1,256 @@
+/*
+ * sound/oss/dev_table.c
+ *
+ * Device call tables.
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+#include <linux/init.h>
+
+#include "sound_config.h"
+
+struct audio_operations *audio_devs[MAX_AUDIO_DEV];
+EXPORT_SYMBOL(audio_devs);
+
+int num_audiodevs;
+EXPORT_SYMBOL(num_audiodevs);
+
+struct mixer_operations *mixer_devs[MAX_MIXER_DEV];
+EXPORT_SYMBOL(mixer_devs);
+
+int num_mixers;
+EXPORT_SYMBOL(num_mixers);
+
+struct synth_operations *synth_devs[MAX_SYNTH_DEV+MAX_MIDI_DEV];
+EXPORT_SYMBOL(synth_devs);
+
+int num_synths;
+
+struct midi_operations *midi_devs[MAX_MIDI_DEV];
+EXPORT_SYMBOL(midi_devs);
+
+int num_midis;
+EXPORT_SYMBOL(num_midis);
+
+struct sound_timer_operations *sound_timer_devs[MAX_TIMER_DEV] = {
+ &default_sound_timer, NULL
+};
+EXPORT_SYMBOL(sound_timer_devs);
+
+int num_sound_timers = 1;
+
+
+static int sound_alloc_audiodev(void);
+
+int sound_install_audiodrv(int vers, char *name, struct audio_driver *driver,
+ int driver_size, int flags, unsigned int format_mask,
+ void *devc, int dma1, int dma2)
+{
+ struct audio_driver *d;
+ struct audio_operations *op;
+ int num;
+
+ if (vers != AUDIO_DRIVER_VERSION || driver_size > sizeof(struct audio_driver)) {
+ printk(KERN_ERR "Sound: Incompatible audio driver for %s\n", name);
+ return -(EINVAL);
+ }
+ num = sound_alloc_audiodev();
+
+ if (num == -1) {
+ printk(KERN_ERR "sound: Too many audio drivers\n");
+ return -(EBUSY);
+ }
+ d = (struct audio_driver *) (sound_mem_blocks[sound_nblocks] = vmalloc(sizeof(struct audio_driver)));
+ sound_nblocks++;
+ if (sound_nblocks >= MAX_MEM_BLOCKS)
+ sound_nblocks = MAX_MEM_BLOCKS - 1;
+
+ op = (struct audio_operations *) (sound_mem_blocks[sound_nblocks] = vzalloc(sizeof(struct audio_operations)));
+ sound_nblocks++;
+ if (sound_nblocks >= MAX_MEM_BLOCKS)
+ sound_nblocks = MAX_MEM_BLOCKS - 1;
+
+ if (d == NULL || op == NULL) {
+ printk(KERN_ERR "Sound: Can't allocate driver for (%s)\n", name);
+ sound_unload_audiodev(num);
+ return -(ENOMEM);
+ }
+ init_waitqueue_head(&op->in_sleeper);
+ init_waitqueue_head(&op->out_sleeper);
+ init_waitqueue_head(&op->poll_sleeper);
+ if (driver_size < sizeof(struct audio_driver))
+ memset((char *) d, 0, sizeof(struct audio_driver));
+
+ memcpy((char *) d, (char *) driver, driver_size);
+
+ op->d = d;
+ strlcpy(op->name, name, sizeof(op->name));
+ op->flags = flags;
+ op->format_mask = format_mask;
+ op->devc = devc;
+
+ /*
+ * Hardcoded defaults
+ */
+ audio_devs[num] = op;
+
+ DMAbuf_init(num, dma1, dma2);
+
+ audio_init_devices();
+ return num;
+}
+EXPORT_SYMBOL(sound_install_audiodrv);
+
+int sound_install_mixer(int vers, char *name, struct mixer_operations *driver,
+ int driver_size, void *devc)
+{
+ struct mixer_operations *op;
+
+ int n = sound_alloc_mixerdev();
+
+ if (n == -1) {
+ printk(KERN_ERR "Sound: Too many mixer drivers\n");
+ return -EBUSY;
+ }
+ if (vers != MIXER_DRIVER_VERSION ||
+ driver_size > sizeof(struct mixer_operations)) {
+ printk(KERN_ERR "Sound: Incompatible mixer driver for %s\n", name);
+ return -EINVAL;
+ }
+
+ /* FIXME: This leaks a mixer_operations struct every time its called
+ until you unload sound! */
+
+ op = (struct mixer_operations *) (sound_mem_blocks[sound_nblocks] = vzalloc(sizeof(struct mixer_operations)));
+ sound_nblocks++;
+ if (sound_nblocks >= MAX_MEM_BLOCKS)
+ sound_nblocks = MAX_MEM_BLOCKS - 1;
+
+ if (op == NULL) {
+ printk(KERN_ERR "Sound: Can't allocate mixer driver for (%s)\n", name);
+ return -ENOMEM;
+ }
+ memcpy((char *) op, (char *) driver, driver_size);
+
+ strlcpy(op->name, name, sizeof(op->name));
+ op->devc = devc;
+
+ mixer_devs[n] = op;
+ return n;
+}
+EXPORT_SYMBOL(sound_install_mixer);
+
+void sound_unload_audiodev(int dev)
+{
+ if (dev != -1) {
+ DMAbuf_deinit(dev);
+ audio_devs[dev] = NULL;
+ unregister_sound_dsp((dev<<4)+3);
+ }
+}
+EXPORT_SYMBOL(sound_unload_audiodev);
+
+static int sound_alloc_audiodev(void)
+{
+ int i = register_sound_dsp(&oss_sound_fops, -1);
+ if(i==-1)
+ return i;
+ i>>=4;
+ if(i>=num_audiodevs)
+ num_audiodevs = i + 1;
+ return i;
+}
+
+int sound_alloc_mididev(void)
+{
+ int i = register_sound_midi(&oss_sound_fops, -1);
+ if(i==-1)
+ return i;
+ i>>=4;
+ if(i>=num_midis)
+ num_midis = i + 1;
+ return i;
+}
+EXPORT_SYMBOL(sound_alloc_mididev);
+
+int sound_alloc_synthdev(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_SYNTH_DEV; i++) {
+ if (synth_devs[i] == NULL) {
+ if (i >= num_synths)
+ num_synths++;
+ return i;
+ }
+ }
+ return -1;
+}
+EXPORT_SYMBOL(sound_alloc_synthdev);
+
+int sound_alloc_mixerdev(void)
+{
+ int i = register_sound_mixer(&oss_sound_fops, -1);
+ if(i==-1)
+ return -1;
+ i>>=4;
+ if(i>=num_mixers)
+ num_mixers = i + 1;
+ return i;
+}
+EXPORT_SYMBOL(sound_alloc_mixerdev);
+
+int sound_alloc_timerdev(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_TIMER_DEV; i++) {
+ if (sound_timer_devs[i] == NULL) {
+ if (i >= num_sound_timers)
+ num_sound_timers++;
+ return i;
+ }
+ }
+ return -1;
+}
+EXPORT_SYMBOL(sound_alloc_timerdev);
+
+void sound_unload_mixerdev(int dev)
+{
+ if (dev != -1) {
+ mixer_devs[dev] = NULL;
+ unregister_sound_mixer(dev<<4);
+ num_mixers--;
+ }
+}
+EXPORT_SYMBOL(sound_unload_mixerdev);
+
+void sound_unload_mididev(int dev)
+{
+ if (dev != -1) {
+ midi_devs[dev] = NULL;
+ unregister_sound_midi((dev<<4)+2);
+ }
+}
+EXPORT_SYMBOL(sound_unload_mididev);
+
+void sound_unload_synthdev(int dev)
+{
+ if (dev != -1)
+ synth_devs[dev] = NULL;
+}
+EXPORT_SYMBOL(sound_unload_synthdev);
+
+void sound_unload_timerdev(int dev)
+{
+ if (dev != -1)
+ sound_timer_devs[dev] = NULL;
+}
+EXPORT_SYMBOL(sound_unload_timerdev);
+
diff --git a/sound/oss/dev_table.h b/sound/oss/dev_table.h
new file mode 100644
index 00000000..0199a317
--- /dev/null
+++ b/sound/oss/dev_table.h
@@ -0,0 +1,390 @@
+/*
+ * dev_table.h
+ *
+ * Global definitions for device call tables
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+
+#ifndef _DEV_TABLE_H_
+#define _DEV_TABLE_H_
+
+#include <linux/spinlock.h>
+/*
+ * Sound card numbers 27 to 999. (1 to 26 are defined in soundcard.h)
+ * Numbers 1000 to N are reserved for driver's internal use.
+ */
+
+#define SNDCARD_DESKPROXL 27 /* Compaq Deskpro XL */
+#define SNDCARD_VIDC 28 /* ARMs VIDC */
+#define SNDCARD_SBPNP 29
+#define SNDCARD_SOFTOSS 36
+#define SNDCARD_VMIDI 37
+#define SNDCARD_OPL3SA1 38 /* Note: clash in msnd.h */
+#define SNDCARD_OPL3SA1_SB 39
+#define SNDCARD_OPL3SA1_MPU 40
+#define SNDCARD_WAVEFRONT 41
+#define SNDCARD_OPL3SA2 42
+#define SNDCARD_OPL3SA2_MPU 43
+#define SNDCARD_WAVEARTIST 44 /* Waveartist */
+#define SNDCARD_OPL3SA2_MSS 45 /* Originally missed */
+#define SNDCARD_AD1816 88
+
+/*
+ * NOTE! NOTE! NOTE! NOTE!
+ *
+ * If you modify this file, please check the dev_table.c also.
+ *
+ * NOTE! NOTE! NOTE! NOTE!
+ */
+
+struct driver_info
+{
+ char *driver_id;
+ int card_subtype; /* Driver specific. Usually 0 */
+ int card_type; /* From soundcard.h */
+ char *name;
+ void (*attach) (struct address_info *hw_config);
+ int (*probe) (struct address_info *hw_config);
+ void (*unload) (struct address_info *hw_config);
+};
+
+struct card_info
+{
+ int card_type; /* Link (search key) to the driver list */
+ struct address_info config;
+ int enabled;
+ void *for_driver_use;
+};
+
+
+/*
+ * Device specific parameters (used only by dmabuf.c)
+ */
+#define MAX_SUB_BUFFERS (32*MAX_REALTIME_FACTOR)
+
+#define DMODE_NONE 0
+#define DMODE_OUTPUT PCM_ENABLE_OUTPUT
+#define DMODE_INPUT PCM_ENABLE_INPUT
+
+struct dma_buffparms
+{
+ int dma_mode; /* DMODE_INPUT, DMODE_OUTPUT or DMODE_NONE */
+ int closing;
+
+ /*
+ * Pointers to raw buffers
+ */
+
+ char *raw_buf;
+ unsigned long raw_buf_phys;
+ int buffsize;
+
+ /*
+ * Device state tables
+ */
+
+ unsigned long flags;
+#define DMA_BUSY 0x00000001
+#define DMA_RESTART 0x00000002
+#define DMA_ACTIVE 0x00000004
+#define DMA_STARTED 0x00000008
+#define DMA_EMPTY 0x00000010
+#define DMA_ALLOC_DONE 0x00000020
+#define DMA_SYNCING 0x00000040
+#define DMA_DIRTY 0x00000080
+#define DMA_POST 0x00000100
+#define DMA_NODMA 0x00000200
+#define DMA_NOTIMEOUT 0x00000400
+
+ int open_mode;
+
+ /*
+ * Queue parameters.
+ */
+ int qlen;
+ int qhead;
+ int qtail;
+ spinlock_t lock;
+
+ int cfrag; /* Current incomplete fragment (write) */
+
+ int nbufs;
+ int counts[MAX_SUB_BUFFERS];
+ int subdivision;
+
+ int fragment_size;
+ int needs_reorg;
+ int max_fragments;
+
+ int bytes_in_use;
+
+ int underrun_count;
+ unsigned long byte_counter;
+ unsigned long user_counter;
+ unsigned long max_byte_counter;
+ int data_rate; /* Bytes/second */
+
+ int mapping_flags;
+#define DMA_MAP_MAPPED 0x00000001
+ char neutral_byte;
+ int dma; /* DMA channel */
+
+ int applic_profile; /* Application profile (APF_*) */
+ /* Interrupt callback stuff */
+ void (*audio_callback) (int dev, int parm);
+ int callback_parm;
+
+ int buf_flags[MAX_SUB_BUFFERS];
+#define BUFF_EOF 0x00000001 /* Increment eof count */
+#define BUFF_DIRTY 0x00000002 /* Buffer written */
+};
+
+/*
+ * Structure for use with various microcontrollers and DSP processors
+ * in the recent sound cards.
+ */
+typedef struct coproc_operations
+{
+ char name[64];
+ struct module *owner;
+ int (*open) (void *devc, int sub_device);
+ void (*close) (void *devc, int sub_device);
+ int (*ioctl) (void *devc, unsigned int cmd, void __user * arg, int local);
+ void (*reset) (void *devc);
+
+ void *devc; /* Driver specific info */
+} coproc_operations;
+
+struct audio_driver
+{
+ struct module *owner;
+ int (*open) (int dev, int mode);
+ void (*close) (int dev);
+ void (*output_block) (int dev, unsigned long buf,
+ int count, int intrflag);
+ void (*start_input) (int dev, unsigned long buf,
+ int count, int intrflag);
+ int (*ioctl) (int dev, unsigned int cmd, void __user * arg);
+ int (*prepare_for_input) (int dev, int bufsize, int nbufs);
+ int (*prepare_for_output) (int dev, int bufsize, int nbufs);
+ void (*halt_io) (int dev);
+ int (*local_qlen)(int dev);
+ void (*copy_user) (int dev,
+ char *localbuf, int localoffs,
+ const char __user *userbuf, int useroffs,
+ int max_in, int max_out,
+ int *used, int *returned,
+ int len);
+ void (*halt_input) (int dev);
+ void (*halt_output) (int dev);
+ void (*trigger) (int dev, int bits);
+ int (*set_speed)(int dev, int speed);
+ unsigned int (*set_bits)(int dev, unsigned int bits);
+ short (*set_channels)(int dev, short channels);
+ void (*postprocess_write)(int dev); /* Device spesific postprocessing for written data */
+ void (*preprocess_read)(int dev); /* Device spesific preprocessing for read data */
+ void (*mmap)(int dev);
+};
+
+struct audio_operations
+{
+ char name[128];
+ int flags;
+#define NOTHING_SPECIAL 0x00
+#define NEEDS_RESTART 0x01
+#define DMA_AUTOMODE 0x02
+#define DMA_DUPLEX 0x04
+#define DMA_PSEUDO_AUTOMODE 0x08
+#define DMA_HARDSTOP 0x10
+#define DMA_EXACT 0x40
+#define DMA_NORESET 0x80
+ int format_mask; /* Bitmask for supported audio formats */
+ void *devc; /* Driver specific info */
+ struct audio_driver *d;
+ void *portc; /* Driver specific info */
+ struct dma_buffparms *dmap_in, *dmap_out;
+ struct coproc_operations *coproc;
+ int mixer_dev;
+ int enable_bits;
+ int open_mode;
+ int go;
+ int min_fragment; /* 0 == unlimited */
+ int max_fragment; /* 0 == unlimited */
+ int parent_dev; /* 0 -> no parent, 1 to n -> parent=parent_dev+1 */
+
+ /* fields formerly in dmabuf.c */
+ wait_queue_head_t in_sleeper;
+ wait_queue_head_t out_sleeper;
+ wait_queue_head_t poll_sleeper;
+
+ /* fields formerly in audio.c */
+ int audio_mode;
+
+#define AM_NONE 0
+#define AM_WRITE OPEN_WRITE
+#define AM_READ OPEN_READ
+
+ int local_format;
+ int audio_format;
+ int local_conversion;
+#define CNV_MU_LAW 0x00000001
+
+ /* large structures at the end to keep offsets small */
+ struct dma_buffparms dmaps[2];
+};
+
+int *load_mixer_volumes(char *name, int *levels, int present);
+
+struct mixer_operations
+{
+ struct module *owner;
+ char id[16];
+ char name[64];
+ int (*ioctl) (int dev, unsigned int cmd, void __user * arg);
+
+ void *devc;
+ int modify_counter;
+};
+
+struct synth_operations
+{
+ struct module *owner;
+ char *id; /* Unique identifier (ASCII) max 29 char */
+ struct synth_info *info;
+ int midi_dev;
+ int synth_type;
+ int synth_subtype;
+
+ int (*open) (int dev, int mode);
+ void (*close) (int dev);
+ int (*ioctl) (int dev, unsigned int cmd, void __user * arg);
+ int (*kill_note) (int dev, int voice, int note, int velocity);
+ int (*start_note) (int dev, int voice, int note, int velocity);
+ int (*set_instr) (int dev, int voice, int instr);
+ void (*reset) (int dev);
+ void (*hw_control) (int dev, unsigned char *event);
+ int (*load_patch) (int dev, int format, const char __user *addr,
+ int count, int pmgr_flag);
+ void (*aftertouch) (int dev, int voice, int pressure);
+ void (*controller) (int dev, int voice, int ctrl_num, int value);
+ void (*panning) (int dev, int voice, int value);
+ void (*volume_method) (int dev, int mode);
+ void (*bender) (int dev, int chn, int value);
+ int (*alloc_voice) (int dev, int chn, int note, struct voice_alloc_info *alloc);
+ void (*setup_voice) (int dev, int voice, int chn);
+ int (*send_sysex)(int dev, unsigned char *bytes, int len);
+
+ struct voice_alloc_info alloc;
+ struct channel_info chn_info[16];
+ int emulation;
+#define EMU_GM 1 /* General MIDI */
+#define EMU_XG 2 /* Yamaha XG */
+#define MAX_SYSEX_BUF 64
+ unsigned char sysex_buf[MAX_SYSEX_BUF];
+ int sysex_ptr;
+};
+
+struct midi_input_info
+{
+ /* MIDI input scanner variables */
+#define MI_MAX 10
+ volatile int m_busy;
+ unsigned char m_buf[MI_MAX];
+ unsigned char m_prev_status; /* For running status */
+ int m_ptr;
+#define MST_INIT 0
+#define MST_DATA 1
+#define MST_SYSEX 2
+ int m_state;
+ int m_left;
+};
+
+struct midi_operations
+{
+ struct module *owner;
+ struct midi_info info;
+ struct synth_operations *converter;
+ struct midi_input_info in_info;
+ int (*open) (int dev, int mode,
+ void (*inputintr)(int dev, unsigned char data),
+ void (*outputintr)(int dev)
+ );
+ void (*close) (int dev);
+ int (*ioctl) (int dev, unsigned int cmd, void __user * arg);
+ int (*outputc) (int dev, unsigned char data);
+ int (*start_read) (int dev);
+ int (*end_read) (int dev);
+ void (*kick)(int dev);
+ int (*command) (int dev, unsigned char *data);
+ int (*buffer_status) (int dev);
+ int (*prefix_cmd) (int dev, unsigned char status);
+ struct coproc_operations *coproc;
+ void *devc;
+};
+
+struct sound_lowlev_timer
+{
+ int dev;
+ int priority;
+ unsigned int (*tmr_start)(int dev, unsigned int usecs);
+ void (*tmr_disable)(int dev);
+ void (*tmr_restart)(int dev);
+};
+
+struct sound_timer_operations
+{
+ struct module *owner;
+ struct sound_timer_info info;
+ int priority;
+ int devlink;
+ int (*open)(int dev, int mode);
+ void (*close)(int dev);
+ int (*event)(int dev, unsigned char *ev);
+ unsigned long (*get_time)(int dev);
+ int (*ioctl) (int dev, unsigned int cmd, void __user * arg);
+ void (*arm_timer)(int dev, long time);
+};
+
+extern struct sound_timer_operations default_sound_timer;
+
+extern struct audio_operations *audio_devs[MAX_AUDIO_DEV];
+extern int num_audiodevs;
+extern struct mixer_operations *mixer_devs[MAX_MIXER_DEV];
+extern int num_mixers;
+extern struct synth_operations *synth_devs[MAX_SYNTH_DEV+MAX_MIDI_DEV];
+extern int num_synths;
+extern struct midi_operations *midi_devs[MAX_MIDI_DEV];
+extern int num_midis;
+extern struct sound_timer_operations * sound_timer_devs[MAX_TIMER_DEV];
+extern int num_sound_timers;
+
+extern int sound_map_buffer (int dev, struct dma_buffparms *dmap, buffmem_desc *info);
+void sound_timer_init (struct sound_lowlev_timer *t, char *name);
+void sound_dma_intr (int dev, struct dma_buffparms *dmap, int chan);
+
+#define AUDIO_DRIVER_VERSION 2
+#define MIXER_DRIVER_VERSION 2
+int sound_install_audiodrv(int vers, char *name, struct audio_driver *driver,
+ int driver_size, int flags, unsigned int format_mask,
+ void *devc, int dma1, int dma2);
+int sound_install_mixer(int vers, char *name, struct mixer_operations *driver,
+ int driver_size, void *devc);
+
+void sound_unload_audiodev(int dev);
+void sound_unload_mixerdev(int dev);
+void sound_unload_mididev(int dev);
+void sound_unload_synthdev(int dev);
+void sound_unload_timerdev(int dev);
+int sound_alloc_mixerdev(void);
+int sound_alloc_timerdev(void);
+int sound_alloc_synthdev(void);
+int sound_alloc_mididev(void);
+#endif /* _DEV_TABLE_H_ */
+
diff --git a/sound/oss/dmabuf.c b/sound/oss/dmabuf.c
new file mode 100644
index 00000000..bcc3e8e0
--- /dev/null
+++ b/sound/oss/dmabuf.c
@@ -0,0 +1,1268 @@
+/*
+ * sound/oss/dmabuf.c
+ *
+ * The DMA buffer manager for digitized voice applications
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Thomas Sailer : moved several static variables into struct audio_operations
+ * (which is grossly misnamed btw.) because they have the same
+ * lifetime as the rest in there and dynamic allocation saves
+ * 12k or so
+ * Thomas Sailer : remove {in,out}_sleep_flag. It was used for the sleeper to
+ * determine if it was woken up by the expiring timeout or by
+ * an explicit wake_up. The return value from schedule_timeout
+ * can be used instead; if 0, the wakeup was due to the timeout.
+ *
+ * Rob Riggs Added persistent DMA buffers (1998/10/17)
+ */
+
+#define BE_CONSERVATIVE
+#define SAMPLE_ROUNDUP 0
+
+#include <linux/mm.h>
+#include <linux/gfp.h>
+#include "sound_config.h"
+
+#define DMAP_FREE_ON_CLOSE 0
+#define DMAP_KEEP_ON_CLOSE 1
+extern int sound_dmap_flag;
+
+static void dma_reset_output(int dev);
+static void dma_reset_input(int dev);
+static int local_start_dma(struct audio_operations *adev, unsigned long physaddr, int count, int dma_mode);
+
+
+
+static int debugmem; /* switched off by default */
+static int dma_buffsize = DSP_BUFFSIZE;
+
+static long dmabuf_timeout(struct dma_buffparms *dmap)
+{
+ long tmout;
+
+ tmout = (dmap->fragment_size * HZ) / dmap->data_rate;
+ tmout += HZ / 5; /* Some safety distance */
+ if (tmout < (HZ / 2))
+ tmout = HZ / 2;
+ if (tmout > 20 * HZ)
+ tmout = 20 * HZ;
+ return tmout;
+}
+
+static int sound_alloc_dmap(struct dma_buffparms *dmap)
+{
+ char *start_addr, *end_addr;
+ int dma_pagesize;
+ int sz, size;
+ struct page *page;
+
+ dmap->mapping_flags &= ~DMA_MAP_MAPPED;
+
+ if (dmap->raw_buf != NULL)
+ return 0; /* Already done */
+ if (dma_buffsize < 4096)
+ dma_buffsize = 4096;
+ dma_pagesize = (dmap->dma < 4) ? (64 * 1024) : (128 * 1024);
+
+ /*
+ * Now check for the Cyrix problem.
+ */
+
+ if(isa_dma_bridge_buggy==2)
+ dma_pagesize=32768;
+
+ dmap->raw_buf = NULL;
+ dmap->buffsize = dma_buffsize;
+ if (dmap->buffsize > dma_pagesize)
+ dmap->buffsize = dma_pagesize;
+ start_addr = NULL;
+ /*
+ * Now loop until we get a free buffer. Try to get smaller buffer if
+ * it fails. Don't accept smaller than 8k buffer for performance
+ * reasons.
+ */
+ while (start_addr == NULL && dmap->buffsize > PAGE_SIZE) {
+ for (sz = 0, size = PAGE_SIZE; size < dmap->buffsize; sz++, size <<= 1);
+ dmap->buffsize = PAGE_SIZE * (1 << sz);
+ start_addr = (char *) __get_free_pages(GFP_ATOMIC|GFP_DMA|__GFP_NOWARN, sz);
+ if (start_addr == NULL)
+ dmap->buffsize /= 2;
+ }
+
+ if (start_addr == NULL) {
+ printk(KERN_WARNING "Sound error: Couldn't allocate DMA buffer\n");
+ return -ENOMEM;
+ } else {
+ /* make some checks */
+ end_addr = start_addr + dmap->buffsize - 1;
+
+ if (debugmem)
+ printk(KERN_DEBUG "sound: start 0x%lx, end 0x%lx\n", (long) start_addr, (long) end_addr);
+
+ /* now check if it fits into the same dma-pagesize */
+
+ if (((long) start_addr & ~(dma_pagesize - 1)) != ((long) end_addr & ~(dma_pagesize - 1))
+ || end_addr >= (char *) (MAX_DMA_ADDRESS)) {
+ printk(KERN_ERR "sound: Got invalid address 0x%lx for %db DMA-buffer\n", (long) start_addr, dmap->buffsize);
+ return -EFAULT;
+ }
+ }
+ dmap->raw_buf = start_addr;
+ dmap->raw_buf_phys = virt_to_bus(start_addr);
+
+ for (page = virt_to_page(start_addr); page <= virt_to_page(end_addr); page++)
+ SetPageReserved(page);
+ return 0;
+}
+
+static void sound_free_dmap(struct dma_buffparms *dmap)
+{
+ int sz, size;
+ struct page *page;
+ unsigned long start_addr, end_addr;
+
+ if (dmap->raw_buf == NULL)
+ return;
+ if (dmap->mapping_flags & DMA_MAP_MAPPED)
+ return; /* Don't free mmapped buffer. Will use it next time */
+ for (sz = 0, size = PAGE_SIZE; size < dmap->buffsize; sz++, size <<= 1);
+
+ start_addr = (unsigned long) dmap->raw_buf;
+ end_addr = start_addr + dmap->buffsize;
+
+ for (page = virt_to_page(start_addr); page <= virt_to_page(end_addr); page++)
+ ClearPageReserved(page);
+
+ free_pages((unsigned long) dmap->raw_buf, sz);
+ dmap->raw_buf = NULL;
+}
+
+
+/* Intel version !!!!!!!!! */
+
+static int sound_start_dma(struct dma_buffparms *dmap, unsigned long physaddr, int count, int dma_mode)
+{
+ unsigned long flags;
+ int chan = dmap->dma;
+
+ /* printk( "Start DMA%d %d, %d\n", chan, (int)(physaddr-dmap->raw_buf_phys), count); */
+
+ flags = claim_dma_lock();
+ disable_dma(chan);
+ clear_dma_ff(chan);
+ set_dma_mode(chan, dma_mode);
+ set_dma_addr(chan, physaddr);
+ set_dma_count(chan, count);
+ enable_dma(chan);
+ release_dma_lock(flags);
+
+ return 0;
+}
+
+static void dma_init_buffers(struct dma_buffparms *dmap)
+{
+ dmap->qlen = dmap->qhead = dmap->qtail = dmap->user_counter = 0;
+ dmap->byte_counter = 0;
+ dmap->max_byte_counter = 8000 * 60 * 60;
+ dmap->bytes_in_use = dmap->buffsize;
+
+ dmap->dma_mode = DMODE_NONE;
+ dmap->mapping_flags = 0;
+ dmap->neutral_byte = 0x80;
+ dmap->data_rate = 8000;
+ dmap->cfrag = -1;
+ dmap->closing = 0;
+ dmap->nbufs = 1;
+ dmap->flags = DMA_BUSY; /* Other flags off */
+}
+
+static int open_dmap(struct audio_operations *adev, int mode, struct dma_buffparms *dmap)
+{
+ int err;
+
+ if (dmap->flags & DMA_BUSY)
+ return -EBUSY;
+ if ((err = sound_alloc_dmap(dmap)) < 0)
+ return err;
+
+ if (dmap->raw_buf == NULL) {
+ printk(KERN_WARNING "Sound: DMA buffers not available\n");
+ return -ENOSPC; /* Memory allocation failed during boot */
+ }
+ if (dmap->dma >= 0 && sound_open_dma(dmap->dma, adev->name)) {
+ printk(KERN_WARNING "Unable to grab(2) DMA%d for the audio driver\n", dmap->dma);
+ return -EBUSY;
+ }
+ dma_init_buffers(dmap);
+ spin_lock_init(&dmap->lock);
+ dmap->open_mode = mode;
+ dmap->subdivision = dmap->underrun_count = 0;
+ dmap->fragment_size = 0;
+ dmap->max_fragments = 65536; /* Just a large value */
+ dmap->byte_counter = 0;
+ dmap->max_byte_counter = 8000 * 60 * 60;
+ dmap->applic_profile = APF_NORMAL;
+ dmap->needs_reorg = 1;
+ dmap->audio_callback = NULL;
+ dmap->callback_parm = 0;
+ return 0;
+}
+
+static void close_dmap(struct audio_operations *adev, struct dma_buffparms *dmap)
+{
+ unsigned long flags;
+
+ if (dmap->dma >= 0) {
+ sound_close_dma(dmap->dma);
+ flags=claim_dma_lock();
+ disable_dma(dmap->dma);
+ release_dma_lock(flags);
+ }
+ if (dmap->flags & DMA_BUSY)
+ dmap->dma_mode = DMODE_NONE;
+ dmap->flags &= ~DMA_BUSY;
+
+ if (sound_dmap_flag == DMAP_FREE_ON_CLOSE)
+ sound_free_dmap(dmap);
+}
+
+
+static unsigned int default_set_bits(int dev, unsigned int bits)
+{
+ mm_segment_t fs = get_fs();
+
+ set_fs(get_ds());
+ audio_devs[dev]->d->ioctl(dev, SNDCTL_DSP_SETFMT, (void __user *)&bits);
+ set_fs(fs);
+ return bits;
+}
+
+static int default_set_speed(int dev, int speed)
+{
+ mm_segment_t fs = get_fs();
+
+ set_fs(get_ds());
+ audio_devs[dev]->d->ioctl(dev, SNDCTL_DSP_SPEED, (void __user *)&speed);
+ set_fs(fs);
+ return speed;
+}
+
+static short default_set_channels(int dev, short channels)
+{
+ int c = channels;
+ mm_segment_t fs = get_fs();
+
+ set_fs(get_ds());
+ audio_devs[dev]->d->ioctl(dev, SNDCTL_DSP_CHANNELS, (void __user *)&c);
+ set_fs(fs);
+ return c;
+}
+
+static void check_driver(struct audio_driver *d)
+{
+ if (d->set_speed == NULL)
+ d->set_speed = default_set_speed;
+ if (d->set_bits == NULL)
+ d->set_bits = default_set_bits;
+ if (d->set_channels == NULL)
+ d->set_channels = default_set_channels;
+}
+
+int DMAbuf_open(int dev, int mode)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ int retval;
+ struct dma_buffparms *dmap_in = NULL;
+ struct dma_buffparms *dmap_out = NULL;
+
+ if (!adev)
+ return -ENXIO;
+ if (!(adev->flags & DMA_DUPLEX))
+ adev->dmap_in = adev->dmap_out;
+ check_driver(adev->d);
+
+ if ((retval = adev->d->open(dev, mode)) < 0)
+ return retval;
+ dmap_out = adev->dmap_out;
+ dmap_in = adev->dmap_in;
+ if (dmap_in == dmap_out)
+ adev->flags &= ~DMA_DUPLEX;
+
+ if (mode & OPEN_WRITE) {
+ if ((retval = open_dmap(adev, mode, dmap_out)) < 0) {
+ adev->d->close(dev);
+ return retval;
+ }
+ }
+ adev->enable_bits = mode;
+
+ if (mode == OPEN_READ || (mode != OPEN_WRITE && (adev->flags & DMA_DUPLEX))) {
+ if ((retval = open_dmap(adev, mode, dmap_in)) < 0) {
+ adev->d->close(dev);
+ if (mode & OPEN_WRITE)
+ close_dmap(adev, dmap_out);
+ return retval;
+ }
+ }
+ adev->open_mode = mode;
+ adev->go = 1;
+
+ adev->d->set_bits(dev, 8);
+ adev->d->set_channels(dev, 1);
+ adev->d->set_speed(dev, DSP_DEFAULT_SPEED);
+ if (adev->dmap_out->dma_mode == DMODE_OUTPUT)
+ memset(adev->dmap_out->raw_buf, adev->dmap_out->neutral_byte,
+ adev->dmap_out->bytes_in_use);
+ return 0;
+}
+/* MUST not hold the spinlock */
+void DMAbuf_reset(int dev)
+{
+ if (audio_devs[dev]->open_mode & OPEN_WRITE)
+ dma_reset_output(dev);
+
+ if (audio_devs[dev]->open_mode & OPEN_READ)
+ dma_reset_input(dev);
+}
+
+static void dma_reset_output(int dev)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ unsigned long flags,f ;
+ struct dma_buffparms *dmap = adev->dmap_out;
+
+ if (!(dmap->flags & DMA_STARTED)) /* DMA is not active */
+ return;
+
+ /*
+ * First wait until the current fragment has been played completely
+ */
+ spin_lock_irqsave(&dmap->lock,flags);
+ adev->dmap_out->flags |= DMA_SYNCING;
+
+ adev->dmap_out->underrun_count = 0;
+ if (!signal_pending(current) && adev->dmap_out->qlen &&
+ adev->dmap_out->underrun_count == 0){
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ interruptible_sleep_on_timeout(&adev->out_sleeper,
+ dmabuf_timeout(dmap));
+ spin_lock_irqsave(&dmap->lock,flags);
+ }
+ adev->dmap_out->flags &= ~(DMA_SYNCING | DMA_ACTIVE);
+
+ /*
+ * Finally shut the device off
+ */
+ if (!(adev->flags & DMA_DUPLEX) || !adev->d->halt_output)
+ adev->d->halt_io(dev);
+ else
+ adev->d->halt_output(dev);
+ adev->dmap_out->flags &= ~DMA_STARTED;
+
+ f=claim_dma_lock();
+ clear_dma_ff(dmap->dma);
+ disable_dma(dmap->dma);
+ release_dma_lock(f);
+
+ dmap->byte_counter = 0;
+ reorganize_buffers(dev, adev->dmap_out, 0);
+ dmap->qlen = dmap->qhead = dmap->qtail = dmap->user_counter = 0;
+ spin_unlock_irqrestore(&dmap->lock,flags);
+}
+
+static void dma_reset_input(int dev)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ unsigned long flags;
+ struct dma_buffparms *dmap = adev->dmap_in;
+
+ spin_lock_irqsave(&dmap->lock,flags);
+ if (!(adev->flags & DMA_DUPLEX) || !adev->d->halt_input)
+ adev->d->halt_io(dev);
+ else
+ adev->d->halt_input(dev);
+ adev->dmap_in->flags &= ~DMA_STARTED;
+
+ dmap->qlen = dmap->qhead = dmap->qtail = dmap->user_counter = 0;
+ dmap->byte_counter = 0;
+ reorganize_buffers(dev, adev->dmap_in, 1);
+ spin_unlock_irqrestore(&dmap->lock,flags);
+}
+/* MUST be called with holding the dmap->lock */
+void DMAbuf_launch_output(int dev, struct dma_buffparms *dmap)
+{
+ struct audio_operations *adev = audio_devs[dev];
+
+ if (!((adev->enable_bits * adev->go) & PCM_ENABLE_OUTPUT))
+ return; /* Don't start DMA yet */
+ dmap->dma_mode = DMODE_OUTPUT;
+
+ if (!(dmap->flags & DMA_ACTIVE) || !(adev->flags & DMA_AUTOMODE) || (dmap->flags & DMA_NODMA)) {
+ if (!(dmap->flags & DMA_STARTED)) {
+ reorganize_buffers(dev, dmap, 0);
+ if (adev->d->prepare_for_output(dev, dmap->fragment_size, dmap->nbufs))
+ return;
+ if (!(dmap->flags & DMA_NODMA))
+ local_start_dma(adev, dmap->raw_buf_phys, dmap->bytes_in_use,DMA_MODE_WRITE);
+ dmap->flags |= DMA_STARTED;
+ }
+ if (dmap->counts[dmap->qhead] == 0)
+ dmap->counts[dmap->qhead] = dmap->fragment_size;
+ dmap->dma_mode = DMODE_OUTPUT;
+ adev->d->output_block(dev, dmap->raw_buf_phys + dmap->qhead * dmap->fragment_size,
+ dmap->counts[dmap->qhead], 1);
+ if (adev->d->trigger)
+ adev->d->trigger(dev,adev->enable_bits * adev->go);
+ }
+ dmap->flags |= DMA_ACTIVE;
+}
+
+int DMAbuf_sync(int dev)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ unsigned long flags;
+ int n = 0;
+ struct dma_buffparms *dmap;
+
+ if (!adev->go && !(adev->enable_bits & PCM_ENABLE_OUTPUT))
+ return 0;
+
+ if (adev->dmap_out->dma_mode == DMODE_OUTPUT) {
+ dmap = adev->dmap_out;
+ spin_lock_irqsave(&dmap->lock,flags);
+ if (dmap->qlen > 0 && !(dmap->flags & DMA_ACTIVE))
+ DMAbuf_launch_output(dev, dmap);
+ adev->dmap_out->flags |= DMA_SYNCING;
+ adev->dmap_out->underrun_count = 0;
+ while (!signal_pending(current) && n++ < adev->dmap_out->nbufs &&
+ adev->dmap_out->qlen && adev->dmap_out->underrun_count == 0) {
+ long t = dmabuf_timeout(dmap);
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ /* FIXME: not safe may miss events */
+ t = interruptible_sleep_on_timeout(&adev->out_sleeper, t);
+ spin_lock_irqsave(&dmap->lock,flags);
+ if (!t) {
+ adev->dmap_out->flags &= ~DMA_SYNCING;
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ return adev->dmap_out->qlen;
+ }
+ }
+ adev->dmap_out->flags &= ~(DMA_SYNCING | DMA_ACTIVE);
+
+ /*
+ * Some devices such as GUS have huge amount of on board RAM for the
+ * audio data. We have to wait until the device has finished playing.
+ */
+
+ /* still holding the lock */
+ if (adev->d->local_qlen) { /* Device has hidden buffers */
+ while (!signal_pending(current) &&
+ adev->d->local_qlen(dev)){
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ interruptible_sleep_on_timeout(&adev->out_sleeper,
+ dmabuf_timeout(dmap));
+ spin_lock_irqsave(&dmap->lock,flags);
+ }
+ }
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ }
+ adev->dmap_out->dma_mode = DMODE_NONE;
+ return adev->dmap_out->qlen;
+}
+
+int DMAbuf_release(int dev, int mode)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ struct dma_buffparms *dmap;
+ unsigned long flags;
+
+ dmap = adev->dmap_out;
+ if (adev->open_mode & OPEN_WRITE)
+ adev->dmap_out->closing = 1;
+
+ if (adev->open_mode & OPEN_READ){
+ adev->dmap_in->closing = 1;
+ dmap = adev->dmap_in;
+ }
+ if (adev->open_mode & OPEN_WRITE)
+ if (!(adev->dmap_out->mapping_flags & DMA_MAP_MAPPED))
+ if (!signal_pending(current) && (adev->dmap_out->dma_mode == DMODE_OUTPUT))
+ DMAbuf_sync(dev);
+ if (adev->dmap_out->dma_mode == DMODE_OUTPUT)
+ memset(adev->dmap_out->raw_buf, adev->dmap_out->neutral_byte, adev->dmap_out->bytes_in_use);
+
+ DMAbuf_reset(dev);
+ spin_lock_irqsave(&dmap->lock,flags);
+ adev->d->close(dev);
+
+ if (adev->open_mode & OPEN_WRITE)
+ close_dmap(adev, adev->dmap_out);
+
+ if (adev->open_mode == OPEN_READ ||
+ (adev->open_mode != OPEN_WRITE &&
+ (adev->flags & DMA_DUPLEX)))
+ close_dmap(adev, adev->dmap_in);
+ adev->open_mode = 0;
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ return 0;
+}
+/* called with dmap->lock dold */
+int DMAbuf_activate_recording(int dev, struct dma_buffparms *dmap)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ int err;
+
+ if (!(adev->open_mode & OPEN_READ))
+ return 0;
+ if (!(adev->enable_bits & PCM_ENABLE_INPUT))
+ return 0;
+ if (dmap->dma_mode == DMODE_OUTPUT) { /* Direction change */
+ /* release lock - it's not recursive */
+ spin_unlock_irq(&dmap->lock);
+ DMAbuf_sync(dev);
+ DMAbuf_reset(dev);
+ spin_lock_irq(&dmap->lock);
+ dmap->dma_mode = DMODE_NONE;
+ }
+ if (!dmap->dma_mode) {
+ reorganize_buffers(dev, dmap, 1);
+ if ((err = adev->d->prepare_for_input(dev,
+ dmap->fragment_size, dmap->nbufs)) < 0)
+ return err;
+ dmap->dma_mode = DMODE_INPUT;
+ }
+ if (!(dmap->flags & DMA_ACTIVE)) {
+ if (dmap->needs_reorg)
+ reorganize_buffers(dev, dmap, 0);
+ local_start_dma(adev, dmap->raw_buf_phys, dmap->bytes_in_use, DMA_MODE_READ);
+ adev->d->start_input(dev, dmap->raw_buf_phys + dmap->qtail * dmap->fragment_size,
+ dmap->fragment_size, 0);
+ dmap->flags |= DMA_ACTIVE;
+ if (adev->d->trigger)
+ adev->d->trigger(dev, adev->enable_bits * adev->go);
+ }
+ return 0;
+}
+/* acquires lock */
+int DMAbuf_getrdbuffer(int dev, char **buf, int *len, int dontblock)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ unsigned long flags;
+ int err = 0, n = 0;
+ struct dma_buffparms *dmap = adev->dmap_in;
+ int go;
+
+ if (!(adev->open_mode & OPEN_READ))
+ return -EIO;
+ spin_lock_irqsave(&dmap->lock,flags);
+ if (dmap->needs_reorg)
+ reorganize_buffers(dev, dmap, 0);
+ if (adev->dmap_in->mapping_flags & DMA_MAP_MAPPED) {
+/* printk(KERN_WARNING "Sound: Can't read from mmapped device (1)\n");*/
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ return -EINVAL;
+ } else while (dmap->qlen <= 0 && n++ < 10) {
+ long timeout = MAX_SCHEDULE_TIMEOUT;
+ if (!(adev->enable_bits & PCM_ENABLE_INPUT) || !adev->go) {
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ return -EAGAIN;
+ }
+ if ((err = DMAbuf_activate_recording(dev, dmap)) < 0) {
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ return err;
+ }
+ /* Wait for the next block */
+
+ if (dontblock) {
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ return -EAGAIN;
+ }
+ if ((go = adev->go))
+ timeout = dmabuf_timeout(dmap);
+
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ timeout = interruptible_sleep_on_timeout(&adev->in_sleeper,
+ timeout);
+ if (!timeout) {
+ /* FIXME: include device name */
+ err = -EIO;
+ printk(KERN_WARNING "Sound: DMA (input) timed out - IRQ/DRQ config error?\n");
+ dma_reset_input(dev);
+ } else
+ err = -EINTR;
+ spin_lock_irqsave(&dmap->lock,flags);
+ }
+ spin_unlock_irqrestore(&dmap->lock,flags);
+
+ if (dmap->qlen <= 0)
+ return err ? err : -EINTR;
+ *buf = &dmap->raw_buf[dmap->qhead * dmap->fragment_size + dmap->counts[dmap->qhead]];
+ *len = dmap->fragment_size - dmap->counts[dmap->qhead];
+
+ return dmap->qhead;
+}
+
+int DMAbuf_rmchars(int dev, int buff_no, int c)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ struct dma_buffparms *dmap = adev->dmap_in;
+ int p = dmap->counts[dmap->qhead] + c;
+
+ if (dmap->mapping_flags & DMA_MAP_MAPPED)
+ {
+/* printk("Sound: Can't read from mmapped device (2)\n");*/
+ return -EINVAL;
+ }
+ else if (dmap->qlen <= 0)
+ return -EIO;
+ else if (p >= dmap->fragment_size) { /* This buffer is completely empty */
+ dmap->counts[dmap->qhead] = 0;
+ dmap->qlen--;
+ dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
+ }
+ else dmap->counts[dmap->qhead] = p;
+
+ return 0;
+}
+/* MUST be called with dmap->lock hold */
+int DMAbuf_get_buffer_pointer(int dev, struct dma_buffparms *dmap, int direction)
+{
+ /*
+ * Try to approximate the active byte position of the DMA pointer within the
+ * buffer area as well as possible.
+ */
+
+ int pos;
+ unsigned long f;
+
+ if (!(dmap->flags & DMA_ACTIVE))
+ pos = 0;
+ else {
+ int chan = dmap->dma;
+
+ f=claim_dma_lock();
+ clear_dma_ff(chan);
+
+ if(!isa_dma_bridge_buggy)
+ disable_dma(dmap->dma);
+
+ pos = get_dma_residue(chan);
+
+ pos = dmap->bytes_in_use - pos;
+
+ if (!(dmap->mapping_flags & DMA_MAP_MAPPED)) {
+ if (direction == DMODE_OUTPUT) {
+ if (dmap->qhead == 0)
+ if (pos > dmap->fragment_size)
+ pos = 0;
+ } else {
+ if (dmap->qtail == 0)
+ if (pos > dmap->fragment_size)
+ pos = 0;
+ }
+ }
+ if (pos < 0)
+ pos = 0;
+ if (pos >= dmap->bytes_in_use)
+ pos = 0;
+
+ if(!isa_dma_bridge_buggy)
+ enable_dma(dmap->dma);
+
+ release_dma_lock(f);
+ }
+ /* printk( "%04x ", pos); */
+
+ return pos;
+}
+
+/*
+ * DMAbuf_start_devices() is called by the /dev/music driver to start
+ * one or more audio devices at desired moment.
+ */
+
+void DMAbuf_start_devices(unsigned int devmask)
+{
+ struct audio_operations *adev;
+ int dev;
+
+ for (dev = 0; dev < num_audiodevs; dev++) {
+ if (!(devmask & (1 << dev)))
+ continue;
+ if (!(adev = audio_devs[dev]))
+ continue;
+ if (adev->open_mode == 0)
+ continue;
+ if (adev->go)
+ continue;
+ /* OK to start the device */
+ adev->go = 1;
+ if (adev->d->trigger)
+ adev->d->trigger(dev,adev->enable_bits * adev->go);
+ }
+}
+/* via poll called without a lock ?*/
+int DMAbuf_space_in_queue(int dev)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ int len, max, tmp;
+ struct dma_buffparms *dmap = adev->dmap_out;
+ int lim = dmap->nbufs;
+
+ if (lim < 2)
+ lim = 2;
+
+ if (dmap->qlen >= lim) /* No space at all */
+ return 0;
+
+ /*
+ * Verify that there are no more pending buffers than the limit
+ * defined by the process.
+ */
+
+ max = dmap->max_fragments;
+ if (max > lim)
+ max = lim;
+ len = dmap->qlen;
+
+ if (adev->d->local_qlen) {
+ tmp = adev->d->local_qlen(dev);
+ if (tmp && len)
+ tmp--; /* This buffer has been counted twice */
+ len += tmp;
+ }
+ if (dmap->byte_counter % dmap->fragment_size) /* There is a partial fragment */
+ len = len + 1;
+
+ if (len >= max)
+ return 0;
+ return max - len;
+}
+/* MUST not hold the spinlock - this function may sleep */
+static int output_sleep(int dev, int dontblock)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ int err = 0;
+ struct dma_buffparms *dmap = adev->dmap_out;
+ long timeout;
+ long timeout_value;
+
+ if (dontblock)
+ return -EAGAIN;
+ if (!(adev->enable_bits & PCM_ENABLE_OUTPUT))
+ return -EAGAIN;
+
+ /*
+ * Wait for free space
+ */
+ if (signal_pending(current))
+ return -EINTR;
+ timeout = (adev->go && !(dmap->flags & DMA_NOTIMEOUT));
+ if (timeout)
+ timeout_value = dmabuf_timeout(dmap);
+ else
+ timeout_value = MAX_SCHEDULE_TIMEOUT;
+ timeout_value = interruptible_sleep_on_timeout(&adev->out_sleeper,
+ timeout_value);
+ if (timeout != MAX_SCHEDULE_TIMEOUT && !timeout_value) {
+ printk(KERN_WARNING "Sound: DMA (output) timed out - IRQ/DRQ config error?\n");
+ dma_reset_output(dev);
+ } else {
+ if (signal_pending(current))
+ err = -EINTR;
+ }
+ return err;
+}
+/* called with the lock held */
+static int find_output_space(int dev, char **buf, int *size)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ struct dma_buffparms *dmap = adev->dmap_out;
+ unsigned long active_offs;
+ long len, offs;
+ int maxfrags;
+ int occupied_bytes = (dmap->user_counter % dmap->fragment_size);
+
+ *buf = dmap->raw_buf;
+ if (!(maxfrags = DMAbuf_space_in_queue(dev)) && !occupied_bytes)
+ return 0;
+
+#ifdef BE_CONSERVATIVE
+ active_offs = dmap->byte_counter + dmap->qhead * dmap->fragment_size;
+#else
+ active_offs = max(DMAbuf_get_buffer_pointer(dev, dmap, DMODE_OUTPUT), 0);
+ /* Check for pointer wrapping situation */
+ if (active_offs >= dmap->bytes_in_use)
+ active_offs = 0;
+ active_offs += dmap->byte_counter;
+#endif
+
+ offs = (dmap->user_counter % dmap->bytes_in_use) & ~SAMPLE_ROUNDUP;
+ if (offs < 0 || offs >= dmap->bytes_in_use) {
+ printk(KERN_ERR "Sound: Got unexpected offs %ld. Giving up.\n", offs);
+ printk("Counter = %ld, bytes=%d\n", dmap->user_counter, dmap->bytes_in_use);
+ return 0;
+ }
+ *buf = dmap->raw_buf + offs;
+
+ len = active_offs + dmap->bytes_in_use - dmap->user_counter; /* Number of unused bytes in buffer */
+
+ if ((offs + len) > dmap->bytes_in_use)
+ len = dmap->bytes_in_use - offs;
+ if (len < 0) {
+ return 0;
+ }
+ if (len > ((maxfrags * dmap->fragment_size) - occupied_bytes))
+ len = (maxfrags * dmap->fragment_size) - occupied_bytes;
+ *size = len & ~SAMPLE_ROUNDUP;
+ return (*size > 0);
+}
+/* acquires lock */
+int DMAbuf_getwrbuffer(int dev, char **buf, int *size, int dontblock)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ unsigned long flags;
+ int err = -EIO;
+ struct dma_buffparms *dmap = adev->dmap_out;
+
+ if (dmap->mapping_flags & DMA_MAP_MAPPED) {
+/* printk(KERN_DEBUG "Sound: Can't write to mmapped device (3)\n");*/
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&dmap->lock,flags);
+ if (dmap->needs_reorg)
+ reorganize_buffers(dev, dmap, 0);
+
+ if (dmap->dma_mode == DMODE_INPUT) { /* Direction change */
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ DMAbuf_reset(dev);
+ spin_lock_irqsave(&dmap->lock,flags);
+ }
+ dmap->dma_mode = DMODE_OUTPUT;
+
+ while (find_output_space(dev, buf, size) <= 0) {
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ if ((err = output_sleep(dev, dontblock)) < 0) {
+ return err;
+ }
+ spin_lock_irqsave(&dmap->lock,flags);
+ }
+
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ return 0;
+}
+/* has to acquire dmap->lock */
+int DMAbuf_move_wrpointer(int dev, int l)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ struct dma_buffparms *dmap = adev->dmap_out;
+ unsigned long ptr;
+ unsigned long end_ptr, p;
+ int post;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dmap->lock,flags);
+ post= (dmap->flags & DMA_POST);
+ ptr = (dmap->user_counter / dmap->fragment_size) * dmap->fragment_size;
+
+ dmap->flags &= ~DMA_POST;
+ dmap->cfrag = -1;
+ dmap->user_counter += l;
+ dmap->flags |= DMA_DIRTY;
+
+ if (dmap->byte_counter >= dmap->max_byte_counter) {
+ /* Wrap the byte counters */
+ long decr = dmap->byte_counter;
+ dmap->byte_counter = (dmap->byte_counter % dmap->bytes_in_use);
+ decr -= dmap->byte_counter;
+ dmap->user_counter -= decr;
+ }
+ end_ptr = (dmap->user_counter / dmap->fragment_size) * dmap->fragment_size;
+
+ p = (dmap->user_counter - 1) % dmap->bytes_in_use;
+ dmap->neutral_byte = dmap->raw_buf[p];
+
+ /* Update the fragment based bookkeeping too */
+ while (ptr < end_ptr) {
+ dmap->counts[dmap->qtail] = dmap->fragment_size;
+ dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+ dmap->qlen++;
+ ptr += dmap->fragment_size;
+ }
+
+ dmap->counts[dmap->qtail] = dmap->user_counter - ptr;
+
+ /*
+ * Let the low level driver perform some postprocessing to
+ * the written data.
+ */
+ if (adev->d->postprocess_write)
+ adev->d->postprocess_write(dev);
+
+ if (!(dmap->flags & DMA_ACTIVE))
+ if (dmap->qlen > 1 || (dmap->qlen > 0 && (post || dmap->qlen >= dmap->nbufs - 1)))
+ DMAbuf_launch_output(dev, dmap);
+
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ return 0;
+}
+
+int DMAbuf_start_dma(int dev, unsigned long physaddr, int count, int dma_mode)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ struct dma_buffparms *dmap = (dma_mode == DMA_MODE_WRITE) ? adev->dmap_out : adev->dmap_in;
+
+ if (dmap->raw_buf == NULL) {
+ printk(KERN_ERR "sound: DMA buffer(1) == NULL\n");
+ printk("Device %d, chn=%s\n", dev, (dmap == adev->dmap_out) ? "out" : "in");
+ return 0;
+ }
+ if (dmap->dma < 0)
+ return 0;
+ sound_start_dma(dmap, physaddr, count, dma_mode);
+ return count;
+}
+EXPORT_SYMBOL(DMAbuf_start_dma);
+
+static int local_start_dma(struct audio_operations *adev, unsigned long physaddr, int count, int dma_mode)
+{
+ struct dma_buffparms *dmap = (dma_mode == DMA_MODE_WRITE) ? adev->dmap_out : adev->dmap_in;
+
+ if (dmap->raw_buf == NULL) {
+ printk(KERN_ERR "sound: DMA buffer(2) == NULL\n");
+ printk(KERN_ERR "Device %s, chn=%s\n", adev->name, (dmap == adev->dmap_out) ? "out" : "in");
+ return 0;
+ }
+ if (dmap->flags & DMA_NODMA)
+ return 1;
+ if (dmap->dma < 0)
+ return 0;
+ sound_start_dma(dmap, dmap->raw_buf_phys, dmap->bytes_in_use, dma_mode | DMA_AUTOINIT);
+ dmap->flags |= DMA_STARTED;
+ return count;
+}
+
+static void finish_output_interrupt(int dev, struct dma_buffparms *dmap)
+{
+ struct audio_operations *adev = audio_devs[dev];
+
+ if (dmap->audio_callback != NULL)
+ dmap->audio_callback(dev, dmap->callback_parm);
+ wake_up(&adev->out_sleeper);
+ wake_up(&adev->poll_sleeper);
+}
+/* called with dmap->lock held in irq context*/
+static void do_outputintr(int dev, int dummy)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ struct dma_buffparms *dmap = adev->dmap_out;
+ int this_fragment;
+
+ if (dmap->raw_buf == NULL) {
+ printk(KERN_ERR "Sound: Error. Audio interrupt (%d) after freeing buffers.\n", dev);
+ return;
+ }
+ if (dmap->mapping_flags & DMA_MAP_MAPPED) { /* Virtual memory mapped access */
+ /* mmapped access */
+ dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
+ if (dmap->qhead == 0) { /* Wrapped */
+ dmap->byte_counter += dmap->bytes_in_use;
+ if (dmap->byte_counter >= dmap->max_byte_counter) { /* Overflow */
+ long decr = dmap->byte_counter;
+ dmap->byte_counter = (dmap->byte_counter % dmap->bytes_in_use);
+ decr -= dmap->byte_counter;
+ dmap->user_counter -= decr;
+ }
+ }
+ dmap->qlen++; /* Yes increment it (don't decrement) */
+ if (!(adev->flags & DMA_AUTOMODE))
+ dmap->flags &= ~DMA_ACTIVE;
+ dmap->counts[dmap->qhead] = dmap->fragment_size;
+ DMAbuf_launch_output(dev, dmap);
+ finish_output_interrupt(dev, dmap);
+ return;
+ }
+
+ dmap->qlen--;
+ this_fragment = dmap->qhead;
+ dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
+
+ if (dmap->qhead == 0) { /* Wrapped */
+ dmap->byte_counter += dmap->bytes_in_use;
+ if (dmap->byte_counter >= dmap->max_byte_counter) { /* Overflow */
+ long decr = dmap->byte_counter;
+ dmap->byte_counter = (dmap->byte_counter % dmap->bytes_in_use);
+ decr -= dmap->byte_counter;
+ dmap->user_counter -= decr;
+ }
+ }
+ if (!(adev->flags & DMA_AUTOMODE))
+ dmap->flags &= ~DMA_ACTIVE;
+
+ /*
+ * This is dmap->qlen <= 0 except when closing when
+ * dmap->qlen < 0
+ */
+
+ while (dmap->qlen <= -dmap->closing) {
+ dmap->underrun_count++;
+ dmap->qlen++;
+ if ((dmap->flags & DMA_DIRTY) && dmap->applic_profile != APF_CPUINTENS) {
+ dmap->flags &= ~DMA_DIRTY;
+ memset(adev->dmap_out->raw_buf, adev->dmap_out->neutral_byte,
+ adev->dmap_out->buffsize);
+ }
+ dmap->user_counter += dmap->fragment_size;
+ dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+ }
+ if (dmap->qlen > 0)
+ DMAbuf_launch_output(dev, dmap);
+ finish_output_interrupt(dev, dmap);
+}
+/* called in irq context */
+void DMAbuf_outputintr(int dev, int notify_only)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ unsigned long flags;
+ struct dma_buffparms *dmap = adev->dmap_out;
+
+ spin_lock_irqsave(&dmap->lock,flags);
+ if (!(dmap->flags & DMA_NODMA)) {
+ int chan = dmap->dma, pos, n;
+ unsigned long f;
+
+ f=claim_dma_lock();
+
+ if(!isa_dma_bridge_buggy)
+ disable_dma(dmap->dma);
+ clear_dma_ff(chan);
+ pos = dmap->bytes_in_use - get_dma_residue(chan);
+ if(!isa_dma_bridge_buggy)
+ enable_dma(dmap->dma);
+ release_dma_lock(f);
+
+ pos = pos / dmap->fragment_size; /* Actual qhead */
+ if (pos < 0 || pos >= dmap->nbufs)
+ pos = 0;
+ n = 0;
+ while (dmap->qhead != pos && n++ < dmap->nbufs)
+ do_outputintr(dev, notify_only);
+ }
+ else
+ do_outputintr(dev, notify_only);
+ spin_unlock_irqrestore(&dmap->lock,flags);
+}
+EXPORT_SYMBOL(DMAbuf_outputintr);
+
+/* called with dmap->lock held in irq context */
+static void do_inputintr(int dev)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ struct dma_buffparms *dmap = adev->dmap_in;
+
+ if (dmap->raw_buf == NULL) {
+ printk(KERN_ERR "Sound: Fatal error. Audio interrupt after freeing buffers.\n");
+ return;
+ }
+ if (dmap->mapping_flags & DMA_MAP_MAPPED) {
+ dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+ if (dmap->qtail == 0) { /* Wrapped */
+ dmap->byte_counter += dmap->bytes_in_use;
+ if (dmap->byte_counter >= dmap->max_byte_counter) { /* Overflow */
+ long decr = dmap->byte_counter;
+ dmap->byte_counter = (dmap->byte_counter % dmap->bytes_in_use) + dmap->bytes_in_use;
+ decr -= dmap->byte_counter;
+ dmap->user_counter -= decr;
+ }
+ }
+ dmap->qlen++;
+
+ if (!(adev->flags & DMA_AUTOMODE)) {
+ if (dmap->needs_reorg)
+ reorganize_buffers(dev, dmap, 0);
+ local_start_dma(adev, dmap->raw_buf_phys, dmap->bytes_in_use,DMA_MODE_READ);
+ adev->d->start_input(dev, dmap->raw_buf_phys + dmap->qtail * dmap->fragment_size,
+ dmap->fragment_size, 1);
+ if (adev->d->trigger)
+ adev->d->trigger(dev, adev->enable_bits * adev->go);
+ }
+ dmap->flags |= DMA_ACTIVE;
+ } else if (dmap->qlen >= (dmap->nbufs - 1)) {
+ printk(KERN_WARNING "Sound: Recording overrun\n");
+ dmap->underrun_count++;
+
+ /* Just throw away the oldest fragment but keep the engine running */
+ dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
+ dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+ } else if (dmap->qlen >= 0 && dmap->qlen < dmap->nbufs) {
+ dmap->qlen++;
+ dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+ if (dmap->qtail == 0) { /* Wrapped */
+ dmap->byte_counter += dmap->bytes_in_use;
+ if (dmap->byte_counter >= dmap->max_byte_counter) { /* Overflow */
+ long decr = dmap->byte_counter;
+ dmap->byte_counter = (dmap->byte_counter % dmap->bytes_in_use) + dmap->bytes_in_use;
+ decr -= dmap->byte_counter;
+ dmap->user_counter -= decr;
+ }
+ }
+ }
+ if (!(adev->flags & DMA_AUTOMODE) || (dmap->flags & DMA_NODMA)) {
+ local_start_dma(adev, dmap->raw_buf_phys, dmap->bytes_in_use, DMA_MODE_READ);
+ adev->d->start_input(dev, dmap->raw_buf_phys + dmap->qtail * dmap->fragment_size, dmap->fragment_size, 1);
+ if (adev->d->trigger)
+ adev->d->trigger(dev,adev->enable_bits * adev->go);
+ }
+ dmap->flags |= DMA_ACTIVE;
+ if (dmap->qlen > 0)
+ {
+ wake_up(&adev->in_sleeper);
+ wake_up(&adev->poll_sleeper);
+ }
+}
+/* called in irq context */
+void DMAbuf_inputintr(int dev)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ struct dma_buffparms *dmap = adev->dmap_in;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dmap->lock,flags);
+
+ if (!(dmap->flags & DMA_NODMA)) {
+ int chan = dmap->dma, pos, n;
+ unsigned long f;
+
+ f=claim_dma_lock();
+ if(!isa_dma_bridge_buggy)
+ disable_dma(dmap->dma);
+ clear_dma_ff(chan);
+ pos = dmap->bytes_in_use - get_dma_residue(chan);
+ if(!isa_dma_bridge_buggy)
+ enable_dma(dmap->dma);
+ release_dma_lock(f);
+
+ pos = pos / dmap->fragment_size; /* Actual qhead */
+ if (pos < 0 || pos >= dmap->nbufs)
+ pos = 0;
+
+ n = 0;
+ while (dmap->qtail != pos && ++n < dmap->nbufs)
+ do_inputintr(dev);
+ } else
+ do_inputintr(dev);
+ spin_unlock_irqrestore(&dmap->lock,flags);
+}
+EXPORT_SYMBOL(DMAbuf_inputintr);
+
+void DMAbuf_init(int dev, int dma1, int dma2)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ /*
+ * NOTE! This routine could be called several times.
+ */
+
+ if (adev && adev->dmap_out == NULL) {
+ if (adev->d == NULL)
+ panic("OSS: audio_devs[%d]->d == NULL\n", dev);
+
+ if (adev->parent_dev) { /* Use DMA map of the parent dev */
+ int parent = adev->parent_dev - 1;
+ adev->dmap_out = audio_devs[parent]->dmap_out;
+ adev->dmap_in = audio_devs[parent]->dmap_in;
+ } else {
+ adev->dmap_out = adev->dmap_in = &adev->dmaps[0];
+ adev->dmap_out->dma = dma1;
+ if (adev->flags & DMA_DUPLEX) {
+ adev->dmap_in = &adev->dmaps[1];
+ adev->dmap_in->dma = dma2;
+ }
+ }
+ /* Persistent DMA buffers allocated here */
+ if (sound_dmap_flag == DMAP_KEEP_ON_CLOSE) {
+ if (adev->dmap_in->raw_buf == NULL)
+ sound_alloc_dmap(adev->dmap_in);
+ if (adev->dmap_out->raw_buf == NULL)
+ sound_alloc_dmap(adev->dmap_out);
+ }
+ }
+}
+
+/* No kernel lock - DMAbuf_activate_recording protected by global cli/sti */
+static unsigned int poll_input(struct file * file, int dev, poll_table *wait)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ struct dma_buffparms *dmap = adev->dmap_in;
+
+ if (!(adev->open_mode & OPEN_READ))
+ return 0;
+ if (dmap->mapping_flags & DMA_MAP_MAPPED) {
+ if (dmap->qlen)
+ return POLLIN | POLLRDNORM;
+ return 0;
+ }
+ if (dmap->dma_mode != DMODE_INPUT) {
+ if (dmap->dma_mode == DMODE_NONE &&
+ adev->enable_bits & PCM_ENABLE_INPUT &&
+ !dmap->qlen && adev->go) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&dmap->lock,flags);
+ DMAbuf_activate_recording(dev, dmap);
+ spin_unlock_irqrestore(&dmap->lock,flags);
+ }
+ return 0;
+ }
+ if (!dmap->qlen)
+ return 0;
+ return POLLIN | POLLRDNORM;
+}
+
+static unsigned int poll_output(struct file * file, int dev, poll_table *wait)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ struct dma_buffparms *dmap = adev->dmap_out;
+
+ if (!(adev->open_mode & OPEN_WRITE))
+ return 0;
+ if (dmap->mapping_flags & DMA_MAP_MAPPED) {
+ if (dmap->qlen)
+ return POLLOUT | POLLWRNORM;
+ return 0;
+ }
+ if (dmap->dma_mode == DMODE_INPUT)
+ return 0;
+ if (dmap->dma_mode == DMODE_NONE)
+ return POLLOUT | POLLWRNORM;
+ if (!DMAbuf_space_in_queue(dev))
+ return 0;
+ return POLLOUT | POLLWRNORM;
+}
+
+unsigned int DMAbuf_poll(struct file * file, int dev, poll_table *wait)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ poll_wait(file, &adev->poll_sleeper, wait);
+ return poll_input(file, dev, wait) | poll_output(file, dev, wait);
+}
+
+void DMAbuf_deinit(int dev)
+{
+ struct audio_operations *adev = audio_devs[dev];
+ /* This routine is called when driver is being unloaded */
+ if (!adev)
+ return;
+
+ /* Persistent DMA buffers deallocated here */
+ if (sound_dmap_flag == DMAP_KEEP_ON_CLOSE) {
+ sound_free_dmap(adev->dmap_out);
+ if (adev->flags & DMA_DUPLEX)
+ sound_free_dmap(adev->dmap_in);
+ }
+}
diff --git a/sound/oss/dmasound/Kconfig b/sound/oss/dmasound/Kconfig
new file mode 100644
index 00000000..f456574a
--- /dev/null
+++ b/sound/oss/dmasound/Kconfig
@@ -0,0 +1,45 @@
+config DMASOUND_ATARI
+ tristate "Atari DMA sound support"
+ depends on ATARI && SOUND
+ select DMASOUND
+ help
+ If you want to use the internal audio of your Atari in Linux, answer
+ Y to this question. This will provide a Sun-like /dev/audio,
+ compatible with the Linux/i386 sound system. Otherwise, say N.
+
+ This driver is also available as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you
+ want). If you want to compile it as a module, say M here and read
+ <file:Documentation/kbuild/modules.txt>.
+
+config DMASOUND_PAULA
+ tristate "Amiga DMA sound support"
+ depends on AMIGA && SOUND
+ select DMASOUND
+ help
+ If you want to use the internal audio of your Amiga in Linux, answer
+ Y to this question. This will provide a Sun-like /dev/audio,
+ compatible with the Linux/i386 sound system. Otherwise, say N.
+
+ This driver is also available as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you
+ want). If you want to compile it as a module, say M here and read
+ <file:Documentation/kbuild/modules.txt>.
+
+config DMASOUND_Q40
+ tristate "Q40 sound support"
+ depends on Q40 && SOUND
+ select DMASOUND
+ help
+ If you want to use the internal audio of your Q40 in Linux, answer
+ Y to this question. This will provide a Sun-like /dev/audio,
+ compatible with the Linux/i386 sound system. Otherwise, say N.
+
+ This driver is also available as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you
+ want). If you want to compile it as a module, say M here and read
+ <file:Documentation/kbuild/modules.txt>.
+
+config DMASOUND
+ tristate
+ select SOUND_OSS_CORE
diff --git a/sound/oss/dmasound/Makefile b/sound/oss/dmasound/Makefile
new file mode 100644
index 00000000..3c153165
--- /dev/null
+++ b/sound/oss/dmasound/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the DMA sound driver
+#
+
+obj-$(CONFIG_DMASOUND_ATARI) += dmasound_core.o dmasound_atari.o
+obj-$(CONFIG_DMASOUND_PAULA) += dmasound_core.o dmasound_paula.o
+obj-$(CONFIG_DMASOUND_Q40) += dmasound_core.o dmasound_q40.o
diff --git a/sound/oss/dmasound/dmasound.h b/sound/oss/dmasound/dmasound.h
new file mode 100644
index 00000000..1308d8d3
--- /dev/null
+++ b/sound/oss/dmasound/dmasound.h
@@ -0,0 +1,262 @@
+#ifndef _dmasound_h_
+/*
+ * linux/sound/oss/dmasound/dmasound.h
+ *
+ *
+ * Minor numbers for the sound driver.
+ *
+ * Unfortunately Creative called the codec chip of SB as a DSP. For this
+ * reason the /dev/dsp is reserved for digitized audio use. There is a
+ * device for true DSP processors but it will be called something else.
+ * In v3.0 it's /dev/sndproc but this could be a temporary solution.
+ */
+#define _dmasound_h_
+
+#include <linux/types.h>
+
+#define SND_NDEVS 256 /* Number of supported devices */
+#define SND_DEV_CTL 0 /* Control port /dev/mixer */
+#define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM
+ synthesizer and MIDI output) */
+#define SND_DEV_MIDIN 2 /* Raw midi access */
+#define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */
+#define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */
+#define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */
+#define SND_DEV_STATUS 6 /* /dev/sndstat */
+/* #7 not in use now. Was in 2.4. Free for use after v3.0. */
+#define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */
+#define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */
+#define SND_DEV_PSS SND_DEV_SNDPROC
+
+/* switch on various prinks */
+#define DEBUG_DMASOUND 1
+
+#define MAX_AUDIO_DEV 5
+#define MAX_MIXER_DEV 4
+#define MAX_SYNTH_DEV 3
+#define MAX_MIDI_DEV 6
+#define MAX_TIMER_DEV 3
+
+#define MAX_CATCH_RADIUS 10
+
+#define le2be16(x) (((x)<<8 & 0xff00) | ((x)>>8 & 0x00ff))
+#define le2be16dbl(x) (((x)<<8 & 0xff00ff00) | ((x)>>8 & 0x00ff00ff))
+
+#define IOCTL_IN(arg, ret) \
+ do { int error = get_user(ret, (int __user *)(arg)); \
+ if (error) return error; \
+ } while (0)
+#define IOCTL_OUT(arg, ret) ioctl_return((int __user *)(arg), ret)
+
+static inline int ioctl_return(int __user *addr, int value)
+{
+ return value < 0 ? value : put_user(value, addr);
+}
+
+
+ /*
+ * Configuration
+ */
+
+#undef HAS_8BIT_TABLES
+
+#if defined(CONFIG_DMASOUND_ATARI) || defined(CONFIG_DMASOUND_ATARI_MODULE) ||\
+ defined(CONFIG_DMASOUND_PAULA) || defined(CONFIG_DMASOUND_PAULA_MODULE) ||\
+ defined(CONFIG_DMASOUND_Q40) || defined(CONFIG_DMASOUND_Q40_MODULE)
+#define HAS_8BIT_TABLES
+#define MIN_BUFFERS 4
+#define MIN_BUFSIZE (1<<12) /* in bytes (- where does this come from ?) */
+#define MIN_FRAG_SIZE 8 /* not 100% sure about this */
+#define MAX_BUFSIZE (1<<17) /* Limit for Amiga is 128 kb */
+#define MAX_FRAG_SIZE 15 /* allow *4 for mono-8 => stereo-16 (for multi) */
+
+#else /* is pmac and multi is off */
+
+#define MIN_BUFFERS 2
+#define MIN_BUFSIZE (1<<8) /* in bytes */
+#define MIN_FRAG_SIZE 8
+#define MAX_BUFSIZE (1<<18) /* this is somewhat arbitrary for pmac */
+#define MAX_FRAG_SIZE 16 /* need to allow *4 for mono-8 => stereo-16 */
+#endif
+
+#define DEFAULT_N_BUFFERS 4
+#define DEFAULT_BUFF_SIZE (1<<15)
+
+ /*
+ * Initialization
+ */
+
+extern int dmasound_init(void);
+#ifdef MODULE
+extern void dmasound_deinit(void);
+#else
+#define dmasound_deinit() do { } while (0)
+#endif
+
+/* description of the set-up applies to either hard or soft settings */
+
+typedef struct {
+ int format; /* AFMT_* */
+ int stereo; /* 0 = mono, 1 = stereo */
+ int size; /* 8/16 bit*/
+ int speed; /* speed */
+} SETTINGS;
+
+ /*
+ * Machine definitions
+ */
+
+typedef struct {
+ const char *name;
+ const char *name2;
+ struct module *owner;
+ void *(*dma_alloc)(unsigned int, gfp_t);
+ void (*dma_free)(void *, unsigned int);
+ int (*irqinit)(void);
+#ifdef MODULE
+ void (*irqcleanup)(void);
+#endif
+ void (*init)(void);
+ void (*silence)(void);
+ int (*setFormat)(int);
+ int (*setVolume)(int);
+ int (*setBass)(int);
+ int (*setTreble)(int);
+ int (*setGain)(int);
+ void (*play)(void);
+ void (*record)(void); /* optional */
+ void (*mixer_init)(void); /* optional */
+ int (*mixer_ioctl)(u_int, u_long); /* optional */
+ int (*write_sq_setup)(void); /* optional */
+ int (*read_sq_setup)(void); /* optional */
+ int (*sq_open)(fmode_t); /* optional */
+ int (*state_info)(char *, size_t); /* optional */
+ void (*abort_read)(void); /* optional */
+ int min_dsp_speed;
+ int max_dsp_speed;
+ int version ;
+ int hardware_afmts ; /* OSS says we only return h'ware info */
+ /* when queried via SNDCTL_DSP_GETFMTS */
+ int capabilities ; /* low-level reply to SNDCTL_DSP_GETCAPS */
+ SETTINGS default_hard ; /* open() or init() should set something valid */
+ SETTINGS default_soft ; /* you can make it look like old OSS, if you want to */
+} MACHINE;
+
+ /*
+ * Low level stuff
+ */
+
+typedef struct {
+ ssize_t (*ct_ulaw)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+ ssize_t (*ct_alaw)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+ ssize_t (*ct_s8)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+ ssize_t (*ct_u8)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+ ssize_t (*ct_s16be)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+ ssize_t (*ct_u16be)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+ ssize_t (*ct_s16le)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+ ssize_t (*ct_u16le)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+} TRANS;
+
+struct sound_settings {
+ MACHINE mach; /* machine dependent things */
+ SETTINGS hard; /* hardware settings */
+ SETTINGS soft; /* software settings */
+ SETTINGS dsp; /* /dev/dsp default settings */
+ TRANS *trans_write; /* supported translations */
+ int volume_left; /* volume (range is machine dependent) */
+ int volume_right;
+ int bass; /* tone (range is machine dependent) */
+ int treble;
+ int gain;
+ int minDev; /* minor device number currently open */
+ spinlock_t lock;
+};
+
+extern struct sound_settings dmasound;
+
+#ifdef HAS_8BIT_TABLES
+extern char dmasound_ulaw2dma8[];
+extern char dmasound_alaw2dma8[];
+#endif
+
+ /*
+ * Mid level stuff
+ */
+
+static inline int dmasound_set_volume(int volume)
+{
+ return dmasound.mach.setVolume(volume);
+}
+
+static inline int dmasound_set_bass(int bass)
+{
+ return dmasound.mach.setBass ? dmasound.mach.setBass(bass) : 50;
+}
+
+static inline int dmasound_set_treble(int treble)
+{
+ return dmasound.mach.setTreble ? dmasound.mach.setTreble(treble) : 50;
+}
+
+static inline int dmasound_set_gain(int gain)
+{
+ return dmasound.mach.setGain ? dmasound.mach.setGain(gain) : 100;
+}
+
+
+ /*
+ * Sound queue stuff, the heart of the driver
+ */
+
+struct sound_queue {
+ /* buffers allocated for this queue */
+ int numBufs; /* real limits on what the user can have */
+ int bufSize; /* in bytes */
+ char **buffers;
+
+ /* current parameters */
+ int locked ; /* params cannot be modified when != 0 */
+ int user_frags ; /* user requests this many */
+ int user_frag_size ; /* of this size */
+ int max_count; /* actual # fragments <= numBufs */
+ int block_size; /* internal block size in bytes */
+ int max_active; /* in-use fragments <= max_count */
+
+ /* it shouldn't be necessary to declare any of these volatile */
+ int front, rear, count;
+ int rear_size;
+ /*
+ * The use of the playing field depends on the hardware
+ *
+ * Atari, PMac: The number of frames that are loaded/playing
+ *
+ * Amiga: Bit 0 is set: a frame is loaded
+ * Bit 1 is set: a frame is playing
+ */
+ int active;
+ wait_queue_head_t action_queue, open_queue, sync_queue;
+ int non_blocking;
+ int busy, syncing, xruns, died;
+};
+
+#define SLEEP(queue) interruptible_sleep_on_timeout(&queue, HZ)
+#define WAKE_UP(queue) (wake_up_interruptible(&queue))
+
+extern struct sound_queue dmasound_write_sq;
+#define write_sq dmasound_write_sq
+
+extern int dmasound_catchRadius;
+#define catchRadius dmasound_catchRadius
+
+/* define the value to be put in the byte-swap reg in mac-io
+ when we want it to swap for us.
+*/
+#define BS_VAL 1
+
+#define SW_INPUT_VOLUME_SCALE 4
+#define SW_INPUT_VOLUME_DEFAULT (128 / SW_INPUT_VOLUME_SCALE)
+
+extern int expand_read_bal; /* Balance factor for reading */
+extern uint software_input_volume; /* software implemented recording volume! */
+
+#endif /* _dmasound_h_ */
diff --git a/sound/oss/dmasound/dmasound_atari.c b/sound/oss/dmasound/dmasound_atari.c
new file mode 100644
index 00000000..13c21446
--- /dev/null
+++ b/sound/oss/dmasound/dmasound_atari.c
@@ -0,0 +1,1620 @@
+/*
+ * linux/sound/oss/dmasound/dmasound_atari.c
+ *
+ * Atari TT and Falcon DMA Sound Driver
+ *
+ * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits
+ * prior to 28/01/2001
+ *
+ * 28/01/2001 [0.1] Iain Sandoe
+ * - added versioning
+ * - put in and populated the hardware_afmts field.
+ * [0.2] - put in SNDCTL_DSP_GETCAPS value.
+ * 01/02/2001 [0.3] - put in default hard/soft settings.
+ */
+
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/soundcard.h>
+#include <linux/mm.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+
+#include <asm/uaccess.h>
+#include <asm/atariints.h>
+#include <asm/atari_stram.h>
+
+#include "dmasound.h"
+
+#define DMASOUND_ATARI_REVISION 0
+#define DMASOUND_ATARI_EDITION 3
+
+extern void atari_microwire_cmd(int cmd);
+
+static int is_falcon;
+static int write_sq_ignore_int; /* ++TeSche: used for Falcon */
+
+static int expand_bal; /* Balance factor for expanding (not volume!) */
+static int expand_data; /* Data for expanding */
+
+
+/*** Translations ************************************************************/
+
+
+/* ++TeSche: radically changed for new expanding purposes...
+ *
+ * These two routines now deal with copying/expanding/translating the samples
+ * from user space into our buffer at the right frequency. They take care about
+ * how much data there's actually to read, how much buffer space there is and
+ * to convert samples into the right frequency/encoding. They will only work on
+ * complete samples so it may happen they leave some bytes in the input stream
+ * if the user didn't write a multiple of the current sample size. They both
+ * return the number of bytes they've used from both streams so you may detect
+ * such a situation. Luckily all programs should be able to cope with that.
+ *
+ * I think I've optimized anything as far as one can do in plain C, all
+ * variables should fit in registers and the loops are really short. There's
+ * one loop for every possible situation. Writing a more generalized and thus
+ * parameterized loop would only produce slower code. Feel free to optimize
+ * this in assembler if you like. :)
+ *
+ * I think these routines belong here because they're not yet really hardware
+ * independent, especially the fact that the Falcon can play 16bit samples
+ * only in stereo is hardcoded in both of them!
+ *
+ * ++geert: split in even more functions (one per format)
+ */
+
+static ssize_t ata_ct_law(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ct_s8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ct_u8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ct_s16be(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ct_u16be(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ct_s16le(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ct_u16le(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ctx_law(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ctx_s8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ctx_u8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ctx_s16be(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ctx_u16be(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ctx_s16le(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+static ssize_t ata_ctx_u16le(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft);
+
+
+/*** Low level stuff *********************************************************/
+
+
+static void *AtaAlloc(unsigned int size, gfp_t flags);
+static void AtaFree(void *, unsigned int size);
+static int AtaIrqInit(void);
+#ifdef MODULE
+static void AtaIrqCleanUp(void);
+#endif /* MODULE */
+static int AtaSetBass(int bass);
+static int AtaSetTreble(int treble);
+static void TTSilence(void);
+static void TTInit(void);
+static int TTSetFormat(int format);
+static int TTSetVolume(int volume);
+static int TTSetGain(int gain);
+static void FalconSilence(void);
+static void FalconInit(void);
+static int FalconSetFormat(int format);
+static int FalconSetVolume(int volume);
+static void AtaPlayNextFrame(int index);
+static void AtaPlay(void);
+static irqreturn_t AtaInterrupt(int irq, void *dummy);
+
+/*** Mid level stuff *********************************************************/
+
+static void TTMixerInit(void);
+static void FalconMixerInit(void);
+static int AtaMixerIoctl(u_int cmd, u_long arg);
+static int TTMixerIoctl(u_int cmd, u_long arg);
+static int FalconMixerIoctl(u_int cmd, u_long arg);
+static int AtaWriteSqSetup(void);
+static int AtaSqOpen(fmode_t mode);
+static int TTStateInfo(char *buffer, size_t space);
+static int FalconStateInfo(char *buffer, size_t space);
+
+
+/*** Translations ************************************************************/
+
+
+static ssize_t ata_ct_law(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8
+ : dmasound_alaw2dma8;
+ ssize_t count, used;
+ u_char *p = &frame[*frameUsed];
+
+ count = min_t(unsigned long, userCount, frameLeft);
+ if (dmasound.soft.stereo)
+ count &= ~1;
+ used = count;
+ while (count > 0) {
+ u_char data;
+ if (get_user(data, userPtr++))
+ return -EFAULT;
+ *p++ = table[data];
+ count--;
+ }
+ *frameUsed += used;
+ return used;
+}
+
+
+static ssize_t ata_ct_s8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ ssize_t count, used;
+ void *p = &frame[*frameUsed];
+
+ count = min_t(unsigned long, userCount, frameLeft);
+ if (dmasound.soft.stereo)
+ count &= ~1;
+ used = count;
+ if (copy_from_user(p, userPtr, count))
+ return -EFAULT;
+ *frameUsed += used;
+ return used;
+}
+
+
+static ssize_t ata_ct_u8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ ssize_t count, used;
+
+ if (!dmasound.soft.stereo) {
+ u_char *p = &frame[*frameUsed];
+ count = min_t(unsigned long, userCount, frameLeft);
+ used = count;
+ while (count > 0) {
+ u_char data;
+ if (get_user(data, userPtr++))
+ return -EFAULT;
+ *p++ = data ^ 0x80;
+ count--;
+ }
+ } else {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ count = min_t(unsigned long, userCount, frameLeft)>>1;
+ used = count*2;
+ while (count > 0) {
+ u_short data;
+ if (get_user(data, (u_short __user *)userPtr))
+ return -EFAULT;
+ userPtr += 2;
+ *p++ = data ^ 0x8080;
+ count--;
+ }
+ }
+ *frameUsed += used;
+ return used;
+}
+
+
+static ssize_t ata_ct_s16be(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ ssize_t count, used;
+
+ if (!dmasound.soft.stereo) {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ count = min_t(unsigned long, userCount, frameLeft)>>1;
+ used = count*2;
+ while (count > 0) {
+ u_short data;
+ if (get_user(data, (u_short __user *)userPtr))
+ return -EFAULT;
+ userPtr += 2;
+ *p++ = data;
+ *p++ = data;
+ count--;
+ }
+ *frameUsed += used*2;
+ } else {
+ void *p = (u_short *)&frame[*frameUsed];
+ count = min_t(unsigned long, userCount, frameLeft) & ~3;
+ used = count;
+ if (copy_from_user(p, userPtr, count))
+ return -EFAULT;
+ *frameUsed += used;
+ }
+ return used;
+}
+
+
+static ssize_t ata_ct_u16be(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ ssize_t count, used;
+
+ if (!dmasound.soft.stereo) {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ count = min_t(unsigned long, userCount, frameLeft)>>1;
+ used = count*2;
+ while (count > 0) {
+ u_short data;
+ if (get_user(data, (u_short __user *)userPtr))
+ return -EFAULT;
+ userPtr += 2;
+ data ^= 0x8000;
+ *p++ = data;
+ *p++ = data;
+ count--;
+ }
+ *frameUsed += used*2;
+ } else {
+ u_long *p = (u_long *)&frame[*frameUsed];
+ count = min_t(unsigned long, userCount, frameLeft)>>2;
+ used = count*4;
+ while (count > 0) {
+ u_int data;
+ if (get_user(data, (u_int __user *)userPtr))
+ return -EFAULT;
+ userPtr += 4;
+ *p++ = data ^ 0x80008000;
+ count--;
+ }
+ *frameUsed += used;
+ }
+ return used;
+}
+
+
+static ssize_t ata_ct_s16le(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ ssize_t count, used;
+
+ count = frameLeft;
+ if (!dmasound.soft.stereo) {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ count = min_t(unsigned long, userCount, frameLeft)>>1;
+ used = count*2;
+ while (count > 0) {
+ u_short data;
+ if (get_user(data, (u_short __user *)userPtr))
+ return -EFAULT;
+ userPtr += 2;
+ data = le2be16(data);
+ *p++ = data;
+ *p++ = data;
+ count--;
+ }
+ *frameUsed += used*2;
+ } else {
+ u_long *p = (u_long *)&frame[*frameUsed];
+ count = min_t(unsigned long, userCount, frameLeft)>>2;
+ used = count*4;
+ while (count > 0) {
+ u_long data;
+ if (get_user(data, (u_int __user *)userPtr))
+ return -EFAULT;
+ userPtr += 4;
+ data = le2be16dbl(data);
+ *p++ = data;
+ count--;
+ }
+ *frameUsed += used;
+ }
+ return used;
+}
+
+
+static ssize_t ata_ct_u16le(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ ssize_t count, used;
+
+ count = frameLeft;
+ if (!dmasound.soft.stereo) {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ count = min_t(unsigned long, userCount, frameLeft)>>1;
+ used = count*2;
+ while (count > 0) {
+ u_short data;
+ if (get_user(data, (u_short __user *)userPtr))
+ return -EFAULT;
+ userPtr += 2;
+ data = le2be16(data) ^ 0x8000;
+ *p++ = data;
+ *p++ = data;
+ }
+ *frameUsed += used*2;
+ } else {
+ u_long *p = (u_long *)&frame[*frameUsed];
+ count = min_t(unsigned long, userCount, frameLeft)>>2;
+ used = count;
+ while (count > 0) {
+ u_long data;
+ if (get_user(data, (u_int __user *)userPtr))
+ return -EFAULT;
+ userPtr += 4;
+ data = le2be16dbl(data) ^ 0x80008000;
+ *p++ = data;
+ count--;
+ }
+ *frameUsed += used;
+ }
+ return used;
+}
+
+
+static ssize_t ata_ctx_law(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8
+ : dmasound_alaw2dma8;
+ /* this should help gcc to stuff everything into registers */
+ long bal = expand_bal;
+ long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ ssize_t used, usedf;
+
+ used = userCount;
+ usedf = frameLeft;
+ if (!dmasound.soft.stereo) {
+ u_char *p = &frame[*frameUsed];
+ u_char data = expand_data;
+ while (frameLeft) {
+ u_char c;
+ if (bal < 0) {
+ if (!userCount)
+ break;
+ if (get_user(c, userPtr++))
+ return -EFAULT;
+ data = table[c];
+ userCount--;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft--;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ } else {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ u_short data = expand_data;
+ while (frameLeft >= 2) {
+ u_char c;
+ if (bal < 0) {
+ if (userCount < 2)
+ break;
+ if (get_user(c, userPtr++))
+ return -EFAULT;
+ data = table[c] << 8;
+ if (get_user(c, userPtr++))
+ return -EFAULT;
+ data |= table[c];
+ userCount -= 2;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft -= 2;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ }
+ expand_bal = bal;
+ used -= userCount;
+ *frameUsed += usedf-frameLeft;
+ return used;
+}
+
+
+static ssize_t ata_ctx_s8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ /* this should help gcc to stuff everything into registers */
+ long bal = expand_bal;
+ long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ ssize_t used, usedf;
+
+ used = userCount;
+ usedf = frameLeft;
+ if (!dmasound.soft.stereo) {
+ u_char *p = &frame[*frameUsed];
+ u_char data = expand_data;
+ while (frameLeft) {
+ if (bal < 0) {
+ if (!userCount)
+ break;
+ if (get_user(data, userPtr++))
+ return -EFAULT;
+ userCount--;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft--;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ } else {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ u_short data = expand_data;
+ while (frameLeft >= 2) {
+ if (bal < 0) {
+ if (userCount < 2)
+ break;
+ if (get_user(data, (u_short __user *)userPtr))
+ return -EFAULT;
+ userPtr += 2;
+ userCount -= 2;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft -= 2;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ }
+ expand_bal = bal;
+ used -= userCount;
+ *frameUsed += usedf-frameLeft;
+ return used;
+}
+
+
+static ssize_t ata_ctx_u8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ /* this should help gcc to stuff everything into registers */
+ long bal = expand_bal;
+ long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ ssize_t used, usedf;
+
+ used = userCount;
+ usedf = frameLeft;
+ if (!dmasound.soft.stereo) {
+ u_char *p = &frame[*frameUsed];
+ u_char data = expand_data;
+ while (frameLeft) {
+ if (bal < 0) {
+ if (!userCount)
+ break;
+ if (get_user(data, userPtr++))
+ return -EFAULT;
+ data ^= 0x80;
+ userCount--;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft--;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ } else {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ u_short data = expand_data;
+ while (frameLeft >= 2) {
+ if (bal < 0) {
+ if (userCount < 2)
+ break;
+ if (get_user(data, (u_short __user *)userPtr))
+ return -EFAULT;
+ userPtr += 2;
+ data ^= 0x8080;
+ userCount -= 2;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft -= 2;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ }
+ expand_bal = bal;
+ used -= userCount;
+ *frameUsed += usedf-frameLeft;
+ return used;
+}
+
+
+static ssize_t ata_ctx_s16be(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ /* this should help gcc to stuff everything into registers */
+ long bal = expand_bal;
+ long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ ssize_t used, usedf;
+
+ used = userCount;
+ usedf = frameLeft;
+ if (!dmasound.soft.stereo) {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ u_short data = expand_data;
+ while (frameLeft >= 4) {
+ if (bal < 0) {
+ if (userCount < 2)
+ break;
+ if (get_user(data, (u_short __user *)userPtr))
+ return -EFAULT;
+ userPtr += 2;
+ userCount -= 2;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ *p++ = data;
+ frameLeft -= 4;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ } else {
+ u_long *p = (u_long *)&frame[*frameUsed];
+ u_long data = expand_data;
+ while (frameLeft >= 4) {
+ if (bal < 0) {
+ if (userCount < 4)
+ break;
+ if (get_user(data, (u_int __user *)userPtr))
+ return -EFAULT;
+ userPtr += 4;
+ userCount -= 4;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft -= 4;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ }
+ expand_bal = bal;
+ used -= userCount;
+ *frameUsed += usedf-frameLeft;
+ return used;
+}
+
+
+static ssize_t ata_ctx_u16be(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ /* this should help gcc to stuff everything into registers */
+ long bal = expand_bal;
+ long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ ssize_t used, usedf;
+
+ used = userCount;
+ usedf = frameLeft;
+ if (!dmasound.soft.stereo) {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ u_short data = expand_data;
+ while (frameLeft >= 4) {
+ if (bal < 0) {
+ if (userCount < 2)
+ break;
+ if (get_user(data, (u_short __user *)userPtr))
+ return -EFAULT;
+ userPtr += 2;
+ data ^= 0x8000;
+ userCount -= 2;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ *p++ = data;
+ frameLeft -= 4;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ } else {
+ u_long *p = (u_long *)&frame[*frameUsed];
+ u_long data = expand_data;
+ while (frameLeft >= 4) {
+ if (bal < 0) {
+ if (userCount < 4)
+ break;
+ if (get_user(data, (u_int __user *)userPtr))
+ return -EFAULT;
+ userPtr += 4;
+ data ^= 0x80008000;
+ userCount -= 4;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft -= 4;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ }
+ expand_bal = bal;
+ used -= userCount;
+ *frameUsed += usedf-frameLeft;
+ return used;
+}
+
+
+static ssize_t ata_ctx_s16le(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ /* this should help gcc to stuff everything into registers */
+ long bal = expand_bal;
+ long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ ssize_t used, usedf;
+
+ used = userCount;
+ usedf = frameLeft;
+ if (!dmasound.soft.stereo) {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ u_short data = expand_data;
+ while (frameLeft >= 4) {
+ if (bal < 0) {
+ if (userCount < 2)
+ break;
+ if (get_user(data, (u_short __user *)userPtr))
+ return -EFAULT;
+ userPtr += 2;
+ data = le2be16(data);
+ userCount -= 2;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ *p++ = data;
+ frameLeft -= 4;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ } else {
+ u_long *p = (u_long *)&frame[*frameUsed];
+ u_long data = expand_data;
+ while (frameLeft >= 4) {
+ if (bal < 0) {
+ if (userCount < 4)
+ break;
+ if (get_user(data, (u_int __user *)userPtr))
+ return -EFAULT;
+ userPtr += 4;
+ data = le2be16dbl(data);
+ userCount -= 4;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft -= 4;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ }
+ expand_bal = bal;
+ used -= userCount;
+ *frameUsed += usedf-frameLeft;
+ return used;
+}
+
+
+static ssize_t ata_ctx_u16le(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ /* this should help gcc to stuff everything into registers */
+ long bal = expand_bal;
+ long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ ssize_t used, usedf;
+
+ used = userCount;
+ usedf = frameLeft;
+ if (!dmasound.soft.stereo) {
+ u_short *p = (u_short *)&frame[*frameUsed];
+ u_short data = expand_data;
+ while (frameLeft >= 4) {
+ if (bal < 0) {
+ if (userCount < 2)
+ break;
+ if (get_user(data, (u_short __user *)userPtr))
+ return -EFAULT;
+ userPtr += 2;
+ data = le2be16(data) ^ 0x8000;
+ userCount -= 2;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ *p++ = data;
+ frameLeft -= 4;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ } else {
+ u_long *p = (u_long *)&frame[*frameUsed];
+ u_long data = expand_data;
+ while (frameLeft >= 4) {
+ if (bal < 0) {
+ if (userCount < 4)
+ break;
+ if (get_user(data, (u_int __user *)userPtr))
+ return -EFAULT;
+ userPtr += 4;
+ data = le2be16dbl(data) ^ 0x80008000;
+ userCount -= 4;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft -= 4;
+ bal -= sSpeed;
+ }
+ expand_data = data;
+ }
+ expand_bal = bal;
+ used -= userCount;
+ *frameUsed += usedf-frameLeft;
+ return used;
+}
+
+
+static TRANS transTTNormal = {
+ .ct_ulaw = ata_ct_law,
+ .ct_alaw = ata_ct_law,
+ .ct_s8 = ata_ct_s8,
+ .ct_u8 = ata_ct_u8,
+};
+
+static TRANS transTTExpanding = {
+ .ct_ulaw = ata_ctx_law,
+ .ct_alaw = ata_ctx_law,
+ .ct_s8 = ata_ctx_s8,
+ .ct_u8 = ata_ctx_u8,
+};
+
+static TRANS transFalconNormal = {
+ .ct_ulaw = ata_ct_law,
+ .ct_alaw = ata_ct_law,
+ .ct_s8 = ata_ct_s8,
+ .ct_u8 = ata_ct_u8,
+ .ct_s16be = ata_ct_s16be,
+ .ct_u16be = ata_ct_u16be,
+ .ct_s16le = ata_ct_s16le,
+ .ct_u16le = ata_ct_u16le
+};
+
+static TRANS transFalconExpanding = {
+ .ct_ulaw = ata_ctx_law,
+ .ct_alaw = ata_ctx_law,
+ .ct_s8 = ata_ctx_s8,
+ .ct_u8 = ata_ctx_u8,
+ .ct_s16be = ata_ctx_s16be,
+ .ct_u16be = ata_ctx_u16be,
+ .ct_s16le = ata_ctx_s16le,
+ .ct_u16le = ata_ctx_u16le,
+};
+
+
+/*** Low level stuff *********************************************************/
+
+
+
+/*
+ * Atari (TT/Falcon)
+ */
+
+static void *AtaAlloc(unsigned int size, gfp_t flags)
+{
+ return atari_stram_alloc(size, "dmasound");
+}
+
+static void AtaFree(void *obj, unsigned int size)
+{
+ atari_stram_free( obj );
+}
+
+static int __init AtaIrqInit(void)
+{
+ /* Set up timer A. Timer A
+ will receive a signal upon end of playing from the sound
+ hardware. Furthermore Timer A is able to count events
+ and will cause an interrupt after a programmed number
+ of events. So all we need to keep the music playing is
+ to provide the sound hardware with new data upon
+ an interrupt from timer A. */
+ st_mfp.tim_ct_a = 0; /* ++roman: Stop timer before programming! */
+ st_mfp.tim_dt_a = 1; /* Cause interrupt after first event. */
+ st_mfp.tim_ct_a = 8; /* Turn on event counting. */
+ /* Register interrupt handler. */
+ if (request_irq(IRQ_MFP_TIMA, AtaInterrupt, IRQ_TYPE_SLOW, "DMA sound",
+ AtaInterrupt))
+ return 0;
+ st_mfp.int_en_a |= 0x20; /* Turn interrupt on. */
+ st_mfp.int_mk_a |= 0x20;
+ return 1;
+}
+
+#ifdef MODULE
+static void AtaIrqCleanUp(void)
+{
+ st_mfp.tim_ct_a = 0; /* stop timer */
+ st_mfp.int_en_a &= ~0x20; /* turn interrupt off */
+ free_irq(IRQ_MFP_TIMA, AtaInterrupt);
+}
+#endif /* MODULE */
+
+
+#define TONE_VOXWARE_TO_DB(v) \
+ (((v) < 0) ? -12 : ((v) > 100) ? 12 : ((v) - 50) * 6 / 25)
+#define TONE_DB_TO_VOXWARE(v) (((v) * 25 + ((v) > 0 ? 5 : -5)) / 6 + 50)
+
+
+static int AtaSetBass(int bass)
+{
+ dmasound.bass = TONE_VOXWARE_TO_DB(bass);
+ atari_microwire_cmd(MW_LM1992_BASS(dmasound.bass));
+ return TONE_DB_TO_VOXWARE(dmasound.bass);
+}
+
+
+static int AtaSetTreble(int treble)
+{
+ dmasound.treble = TONE_VOXWARE_TO_DB(treble);
+ atari_microwire_cmd(MW_LM1992_TREBLE(dmasound.treble));
+ return TONE_DB_TO_VOXWARE(dmasound.treble);
+}
+
+
+
+/*
+ * TT
+ */
+
+
+static void TTSilence(void)
+{
+ tt_dmasnd.ctrl = DMASND_CTRL_OFF;
+ atari_microwire_cmd(MW_LM1992_PSG_HIGH); /* mix in PSG signal 1:1 */
+}
+
+
+static void TTInit(void)
+{
+ int mode, i, idx;
+ const int freq[4] = {50066, 25033, 12517, 6258};
+
+ /* search a frequency that fits into the allowed error range */
+
+ idx = -1;
+ for (i = 0; i < ARRAY_SIZE(freq); i++)
+ /* this isn't as much useful for a TT than for a Falcon, but
+ * then it doesn't hurt very much to implement it for a TT too.
+ */
+ if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) < catchRadius)
+ idx = i;
+ if (idx > -1) {
+ dmasound.soft.speed = freq[idx];
+ dmasound.trans_write = &transTTNormal;
+ } else
+ dmasound.trans_write = &transTTExpanding;
+
+ TTSilence();
+ dmasound.hard = dmasound.soft;
+
+ if (dmasound.hard.speed > 50066) {
+ /* we would need to squeeze the sound, but we won't do that */
+ dmasound.hard.speed = 50066;
+ mode = DMASND_MODE_50KHZ;
+ dmasound.trans_write = &transTTNormal;
+ } else if (dmasound.hard.speed > 25033) {
+ dmasound.hard.speed = 50066;
+ mode = DMASND_MODE_50KHZ;
+ } else if (dmasound.hard.speed > 12517) {
+ dmasound.hard.speed = 25033;
+ mode = DMASND_MODE_25KHZ;
+ } else if (dmasound.hard.speed > 6258) {
+ dmasound.hard.speed = 12517;
+ mode = DMASND_MODE_12KHZ;
+ } else {
+ dmasound.hard.speed = 6258;
+ mode = DMASND_MODE_6KHZ;
+ }
+
+ tt_dmasnd.mode = (dmasound.hard.stereo ?
+ DMASND_MODE_STEREO : DMASND_MODE_MONO) |
+ DMASND_MODE_8BIT | mode;
+
+ expand_bal = -dmasound.soft.speed;
+}
+
+
+static int TTSetFormat(int format)
+{
+ /* TT sound DMA supports only 8bit modes */
+
+ switch (format) {
+ case AFMT_QUERY:
+ return dmasound.soft.format;
+ case AFMT_MU_LAW:
+ case AFMT_A_LAW:
+ case AFMT_S8:
+ case AFMT_U8:
+ break;
+ default:
+ format = AFMT_S8;
+ }
+
+ dmasound.soft.format = format;
+ dmasound.soft.size = 8;
+ if (dmasound.minDev == SND_DEV_DSP) {
+ dmasound.dsp.format = format;
+ dmasound.dsp.size = 8;
+ }
+ TTInit();
+
+ return format;
+}
+
+
+#define VOLUME_VOXWARE_TO_DB(v) \
+ (((v) < 0) ? -40 : ((v) > 100) ? 0 : ((v) * 2) / 5 - 40)
+#define VOLUME_DB_TO_VOXWARE(v) ((((v) + 40) * 5 + 1) / 2)
+
+
+static int TTSetVolume(int volume)
+{
+ dmasound.volume_left = VOLUME_VOXWARE_TO_DB(volume & 0xff);
+ atari_microwire_cmd(MW_LM1992_BALLEFT(dmasound.volume_left));
+ dmasound.volume_right = VOLUME_VOXWARE_TO_DB((volume & 0xff00) >> 8);
+ atari_microwire_cmd(MW_LM1992_BALRIGHT(dmasound.volume_right));
+ return VOLUME_DB_TO_VOXWARE(dmasound.volume_left) |
+ (VOLUME_DB_TO_VOXWARE(dmasound.volume_right) << 8);
+}
+
+
+#define GAIN_VOXWARE_TO_DB(v) \
+ (((v) < 0) ? -80 : ((v) > 100) ? 0 : ((v) * 4) / 5 - 80)
+#define GAIN_DB_TO_VOXWARE(v) ((((v) + 80) * 5 + 1) / 4)
+
+static int TTSetGain(int gain)
+{
+ dmasound.gain = GAIN_VOXWARE_TO_DB(gain);
+ atari_microwire_cmd(MW_LM1992_VOLUME(dmasound.gain));
+ return GAIN_DB_TO_VOXWARE(dmasound.gain);
+}
+
+
+
+/*
+ * Falcon
+ */
+
+
+static void FalconSilence(void)
+{
+ /* stop playback, set sample rate 50kHz for PSG sound */
+ tt_dmasnd.ctrl = DMASND_CTRL_OFF;
+ tt_dmasnd.mode = DMASND_MODE_50KHZ | DMASND_MODE_STEREO | DMASND_MODE_8BIT;
+ tt_dmasnd.int_div = 0; /* STE compatible divider */
+ tt_dmasnd.int_ctrl = 0x0;
+ tt_dmasnd.cbar_src = 0x0000; /* no matrix inputs */
+ tt_dmasnd.cbar_dst = 0x0000; /* no matrix outputs */
+ tt_dmasnd.dac_src = 1; /* connect ADC to DAC, disconnect matrix */
+ tt_dmasnd.adc_src = 3; /* ADC Input = PSG */
+}
+
+
+static void FalconInit(void)
+{
+ int divider, i, idx;
+ const int freq[8] = {49170, 32780, 24585, 19668, 16390, 12292, 9834, 8195};
+
+ /* search a frequency that fits into the allowed error range */
+
+ idx = -1;
+ for (i = 0; i < ARRAY_SIZE(freq); i++)
+ /* if we will tolerate 3% error 8000Hz->8195Hz (2.38%) would
+ * be playable without expanding, but that now a kernel runtime
+ * option
+ */
+ if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) < catchRadius)
+ idx = i;
+ if (idx > -1) {
+ dmasound.soft.speed = freq[idx];
+ dmasound.trans_write = &transFalconNormal;
+ } else
+ dmasound.trans_write = &transFalconExpanding;
+
+ FalconSilence();
+ dmasound.hard = dmasound.soft;
+
+ if (dmasound.hard.size == 16) {
+ /* the Falcon can play 16bit samples only in stereo */
+ dmasound.hard.stereo = 1;
+ }
+
+ if (dmasound.hard.speed > 49170) {
+ /* we would need to squeeze the sound, but we won't do that */
+ dmasound.hard.speed = 49170;
+ divider = 1;
+ dmasound.trans_write = &transFalconNormal;
+ } else if (dmasound.hard.speed > 32780) {
+ dmasound.hard.speed = 49170;
+ divider = 1;
+ } else if (dmasound.hard.speed > 24585) {
+ dmasound.hard.speed = 32780;
+ divider = 2;
+ } else if (dmasound.hard.speed > 19668) {
+ dmasound.hard.speed = 24585;
+ divider = 3;
+ } else if (dmasound.hard.speed > 16390) {
+ dmasound.hard.speed = 19668;
+ divider = 4;
+ } else if (dmasound.hard.speed > 12292) {
+ dmasound.hard.speed = 16390;
+ divider = 5;
+ } else if (dmasound.hard.speed > 9834) {
+ dmasound.hard.speed = 12292;
+ divider = 7;
+ } else if (dmasound.hard.speed > 8195) {
+ dmasound.hard.speed = 9834;
+ divider = 9;
+ } else {
+ dmasound.hard.speed = 8195;
+ divider = 11;
+ }
+ tt_dmasnd.int_div = divider;
+
+ /* Setup Falcon sound DMA for playback */
+ tt_dmasnd.int_ctrl = 0x4; /* Timer A int at play end */
+ tt_dmasnd.track_select = 0x0; /* play 1 track, track 1 */
+ tt_dmasnd.cbar_src = 0x0001; /* DMA(25MHz) --> DAC */
+ tt_dmasnd.cbar_dst = 0x0000;
+ tt_dmasnd.rec_track_select = 0;
+ tt_dmasnd.dac_src = 2; /* connect matrix to DAC */
+ tt_dmasnd.adc_src = 0; /* ADC Input = Mic */
+
+ tt_dmasnd.mode = (dmasound.hard.stereo ?
+ DMASND_MODE_STEREO : DMASND_MODE_MONO) |
+ ((dmasound.hard.size == 8) ?
+ DMASND_MODE_8BIT : DMASND_MODE_16BIT) |
+ DMASND_MODE_6KHZ;
+
+ expand_bal = -dmasound.soft.speed;
+}
+
+
+static int FalconSetFormat(int format)
+{
+ int size;
+ /* Falcon sound DMA supports 8bit and 16bit modes */
+
+ switch (format) {
+ case AFMT_QUERY:
+ return dmasound.soft.format;
+ case AFMT_MU_LAW:
+ case AFMT_A_LAW:
+ case AFMT_U8:
+ case AFMT_S8:
+ size = 8;
+ break;
+ case AFMT_S16_BE:
+ case AFMT_U16_BE:
+ case AFMT_S16_LE:
+ case AFMT_U16_LE:
+ size = 16;
+ break;
+ default: /* :-) */
+ size = 8;
+ format = AFMT_S8;
+ }
+
+ dmasound.soft.format = format;
+ dmasound.soft.size = size;
+ if (dmasound.minDev == SND_DEV_DSP) {
+ dmasound.dsp.format = format;
+ dmasound.dsp.size = dmasound.soft.size;
+ }
+
+ FalconInit();
+
+ return format;
+}
+
+
+/* This is for the Falcon output *attenuation* in 1.5dB steps,
+ * i.e. output level from 0 to -22.5dB in -1.5dB steps.
+ */
+#define VOLUME_VOXWARE_TO_ATT(v) \
+ ((v) < 0 ? 15 : (v) > 100 ? 0 : 15 - (v) * 3 / 20)
+#define VOLUME_ATT_TO_VOXWARE(v) (100 - (v) * 20 / 3)
+
+
+static int FalconSetVolume(int volume)
+{
+ dmasound.volume_left = VOLUME_VOXWARE_TO_ATT(volume & 0xff);
+ dmasound.volume_right = VOLUME_VOXWARE_TO_ATT((volume & 0xff00) >> 8);
+ tt_dmasnd.output_atten = dmasound.volume_left << 8 | dmasound.volume_right << 4;
+ return VOLUME_ATT_TO_VOXWARE(dmasound.volume_left) |
+ VOLUME_ATT_TO_VOXWARE(dmasound.volume_right) << 8;
+}
+
+
+static void AtaPlayNextFrame(int index)
+{
+ char *start, *end;
+
+ /* used by AtaPlay() if all doubts whether there really is something
+ * to be played are already wiped out.
+ */
+ start = write_sq.buffers[write_sq.front];
+ end = start+((write_sq.count == index) ? write_sq.rear_size
+ : write_sq.block_size);
+ /* end might not be a legal virtual address. */
+ DMASNDSetEnd(virt_to_phys(end - 1) + 1);
+ DMASNDSetBase(virt_to_phys(start));
+ /* Since only an even number of samples per frame can
+ be played, we might lose one byte here. (TO DO) */
+ write_sq.front = (write_sq.front+1) % write_sq.max_count;
+ write_sq.active++;
+ tt_dmasnd.ctrl = DMASND_CTRL_ON | DMASND_CTRL_REPEAT;
+}
+
+
+static void AtaPlay(void)
+{
+ /* ++TeSche: Note that write_sq.active is no longer just a flag but
+ * holds the number of frames the DMA is currently programmed for
+ * instead, may be 0, 1 (currently being played) or 2 (pre-programmed).
+ *
+ * Changes done to write_sq.count and write_sq.active are a bit more
+ * subtle again so now I must admit I also prefer disabling the irq
+ * here rather than considering all possible situations. But the point
+ * is that disabling the irq doesn't have any bad influence on this
+ * version of the driver as we benefit from having pre-programmed the
+ * DMA wherever possible: There's no need to reload the DMA at the
+ * exact time of an interrupt but only at some time while the
+ * pre-programmed frame is playing!
+ */
+ atari_disable_irq(IRQ_MFP_TIMA);
+
+ if (write_sq.active == 2 || /* DMA is 'full' */
+ write_sq.count <= 0) { /* nothing to do */
+ atari_enable_irq(IRQ_MFP_TIMA);
+ return;
+ }
+
+ if (write_sq.active == 0) {
+ /* looks like there's nothing 'in' the DMA yet, so try
+ * to put two frames into it (at least one is available).
+ */
+ if (write_sq.count == 1 &&
+ write_sq.rear_size < write_sq.block_size &&
+ !write_sq.syncing) {
+ /* hmmm, the only existing frame is not
+ * yet filled and we're not syncing?
+ */
+ atari_enable_irq(IRQ_MFP_TIMA);
+ return;
+ }
+ AtaPlayNextFrame(1);
+ if (write_sq.count == 1) {
+ /* no more frames */
+ atari_enable_irq(IRQ_MFP_TIMA);
+ return;
+ }
+ if (write_sq.count == 2 &&
+ write_sq.rear_size < write_sq.block_size &&
+ !write_sq.syncing) {
+ /* hmmm, there were two frames, but the second
+ * one is not yet filled and we're not syncing?
+ */
+ atari_enable_irq(IRQ_MFP_TIMA);
+ return;
+ }
+ AtaPlayNextFrame(2);
+ } else {
+ /* there's already a frame being played so we may only stuff
+ * one new into the DMA, but even if this may be the last
+ * frame existing the previous one is still on write_sq.count.
+ */
+ if (write_sq.count == 2 &&
+ write_sq.rear_size < write_sq.block_size &&
+ !write_sq.syncing) {
+ /* hmmm, the only existing frame is not
+ * yet filled and we're not syncing?
+ */
+ atari_enable_irq(IRQ_MFP_TIMA);
+ return;
+ }
+ AtaPlayNextFrame(2);
+ }
+ atari_enable_irq(IRQ_MFP_TIMA);
+}
+
+
+static irqreturn_t AtaInterrupt(int irq, void *dummy)
+{
+#if 0
+ /* ++TeSche: if you should want to test this... */
+ static int cnt;
+ if (write_sq.active == 2)
+ if (++cnt == 10) {
+ /* simulate losing an interrupt */
+ cnt = 0;
+ return IRQ_HANDLED;
+ }
+#endif
+ spin_lock(&dmasound.lock);
+ if (write_sq_ignore_int && is_falcon) {
+ /* ++TeSche: Falcon only: ignore first irq because it comes
+ * immediately after starting a frame. after that, irqs come
+ * (almost) like on the TT.
+ */
+ write_sq_ignore_int = 0;
+ goto out;
+ }
+
+ if (!write_sq.active) {
+ /* playing was interrupted and sq_reset() has already cleared
+ * the sq variables, so better don't do anything here.
+ */
+ WAKE_UP(write_sq.sync_queue);
+ goto out;
+ }
+
+ /* Probably ;) one frame is finished. Well, in fact it may be that a
+ * pre-programmed one is also finished because there has been a long
+ * delay in interrupt delivery and we've completely lost one, but
+ * there's no way to detect such a situation. In such a case the last
+ * frame will be played more than once and the situation will recover
+ * as soon as the irq gets through.
+ */
+ write_sq.count--;
+ write_sq.active--;
+
+ if (!write_sq.active) {
+ tt_dmasnd.ctrl = DMASND_CTRL_OFF;
+ write_sq_ignore_int = 1;
+ }
+
+ WAKE_UP(write_sq.action_queue);
+ /* At least one block of the queue is free now
+ so wake up a writing process blocked because
+ of a full queue. */
+
+ if ((write_sq.active != 1) || (write_sq.count != 1))
+ /* We must be a bit carefully here: write_sq.count indicates the
+ * number of buffers used and not the number of frames to be
+ * played. If write_sq.count==1 and write_sq.active==1 that
+ * means the only remaining frame was already programmed
+ * earlier (and is currently running) so we mustn't call
+ * AtaPlay() here, otherwise we'll play one frame too much.
+ */
+ AtaPlay();
+
+ if (!write_sq.active) WAKE_UP(write_sq.sync_queue);
+ /* We are not playing after AtaPlay(), so there
+ is nothing to play any more. Wake up a process
+ waiting for audio output to drain. */
+out:
+ spin_unlock(&dmasound.lock);
+ return IRQ_HANDLED;
+}
+
+
+/*** Mid level stuff *********************************************************/
+
+
+/*
+ * /dev/mixer abstraction
+ */
+
+#define RECLEVEL_VOXWARE_TO_GAIN(v) \
+ ((v) < 0 ? 0 : (v) > 100 ? 15 : (v) * 3 / 20)
+#define RECLEVEL_GAIN_TO_VOXWARE(v) (((v) * 20 + 2) / 3)
+
+
+static void __init TTMixerInit(void)
+{
+ atari_microwire_cmd(MW_LM1992_VOLUME(0));
+ dmasound.volume_left = 0;
+ atari_microwire_cmd(MW_LM1992_BALLEFT(0));
+ dmasound.volume_right = 0;
+ atari_microwire_cmd(MW_LM1992_BALRIGHT(0));
+ atari_microwire_cmd(MW_LM1992_TREBLE(0));
+ atari_microwire_cmd(MW_LM1992_BASS(0));
+}
+
+static void __init FalconMixerInit(void)
+{
+ dmasound.volume_left = (tt_dmasnd.output_atten & 0xf00) >> 8;
+ dmasound.volume_right = (tt_dmasnd.output_atten & 0xf0) >> 4;
+}
+
+static int AtaMixerIoctl(u_int cmd, u_long arg)
+{
+ int data;
+ unsigned long flags;
+ switch (cmd) {
+ case SOUND_MIXER_READ_SPEAKER:
+ if (is_falcon || MACH_IS_TT) {
+ int porta;
+ spin_lock_irqsave(&dmasound.lock, flags);
+ sound_ym.rd_data_reg_sel = 14;
+ porta = sound_ym.rd_data_reg_sel;
+ spin_unlock_irqrestore(&dmasound.lock, flags);
+ return IOCTL_OUT(arg, porta & 0x40 ? 0 : 100);
+ }
+ break;
+ case SOUND_MIXER_WRITE_VOLUME:
+ IOCTL_IN(arg, data);
+ return IOCTL_OUT(arg, dmasound_set_volume(data));
+ case SOUND_MIXER_WRITE_SPEAKER:
+ if (is_falcon || MACH_IS_TT) {
+ int porta;
+ IOCTL_IN(arg, data);
+ spin_lock_irqsave(&dmasound.lock, flags);
+ sound_ym.rd_data_reg_sel = 14;
+ porta = (sound_ym.rd_data_reg_sel & ~0x40) |
+ (data < 50 ? 0x40 : 0);
+ sound_ym.wd_data = porta;
+ spin_unlock_irqrestore(&dmasound.lock, flags);
+ return IOCTL_OUT(arg, porta & 0x40 ? 0 : 100);
+ }
+ }
+ return -EINVAL;
+}
+
+
+static int TTMixerIoctl(u_int cmd, u_long arg)
+{
+ int data;
+ switch (cmd) {
+ case SOUND_MIXER_READ_RECMASK:
+ return IOCTL_OUT(arg, 0);
+ case SOUND_MIXER_READ_DEVMASK:
+ return IOCTL_OUT(arg,
+ SOUND_MASK_VOLUME | SOUND_MASK_TREBLE | SOUND_MASK_BASS |
+ (MACH_IS_TT ? SOUND_MASK_SPEAKER : 0));
+ case SOUND_MIXER_READ_STEREODEVS:
+ return IOCTL_OUT(arg, SOUND_MASK_VOLUME);
+ case SOUND_MIXER_READ_VOLUME:
+ return IOCTL_OUT(arg,
+ VOLUME_DB_TO_VOXWARE(dmasound.volume_left) |
+ (VOLUME_DB_TO_VOXWARE(dmasound.volume_right) << 8));
+ case SOUND_MIXER_READ_BASS:
+ return IOCTL_OUT(arg, TONE_DB_TO_VOXWARE(dmasound.bass));
+ case SOUND_MIXER_READ_TREBLE:
+ return IOCTL_OUT(arg, TONE_DB_TO_VOXWARE(dmasound.treble));
+ case SOUND_MIXER_READ_OGAIN:
+ return IOCTL_OUT(arg, GAIN_DB_TO_VOXWARE(dmasound.gain));
+ case SOUND_MIXER_WRITE_BASS:
+ IOCTL_IN(arg, data);
+ return IOCTL_OUT(arg, dmasound_set_bass(data));
+ case SOUND_MIXER_WRITE_TREBLE:
+ IOCTL_IN(arg, data);
+ return IOCTL_OUT(arg, dmasound_set_treble(data));
+ case SOUND_MIXER_WRITE_OGAIN:
+ IOCTL_IN(arg, data);
+ return IOCTL_OUT(arg, dmasound_set_gain(data));
+ }
+ return AtaMixerIoctl(cmd, arg);
+}
+
+static int FalconMixerIoctl(u_int cmd, u_long arg)
+{
+ int data;
+ switch (cmd) {
+ case SOUND_MIXER_READ_RECMASK:
+ return IOCTL_OUT(arg, SOUND_MASK_MIC);
+ case SOUND_MIXER_READ_DEVMASK:
+ return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_MIC | SOUND_MASK_SPEAKER);
+ case SOUND_MIXER_READ_STEREODEVS:
+ return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_MIC);
+ case SOUND_MIXER_READ_VOLUME:
+ return IOCTL_OUT(arg,
+ VOLUME_ATT_TO_VOXWARE(dmasound.volume_left) |
+ VOLUME_ATT_TO_VOXWARE(dmasound.volume_right) << 8);
+ case SOUND_MIXER_READ_CAPS:
+ return IOCTL_OUT(arg, SOUND_CAP_EXCL_INPUT);
+ case SOUND_MIXER_WRITE_MIC:
+ IOCTL_IN(arg, data);
+ tt_dmasnd.input_gain =
+ RECLEVEL_VOXWARE_TO_GAIN(data & 0xff) << 4 |
+ RECLEVEL_VOXWARE_TO_GAIN(data >> 8 & 0xff);
+ /* fall thru, return set value */
+ case SOUND_MIXER_READ_MIC:
+ return IOCTL_OUT(arg,
+ RECLEVEL_GAIN_TO_VOXWARE(tt_dmasnd.input_gain >> 4 & 0xf) |
+ RECLEVEL_GAIN_TO_VOXWARE(tt_dmasnd.input_gain & 0xf) << 8);
+ }
+ return AtaMixerIoctl(cmd, arg);
+}
+
+static int AtaWriteSqSetup(void)
+{
+ write_sq_ignore_int = 0;
+ return 0 ;
+}
+
+static int AtaSqOpen(fmode_t mode)
+{
+ write_sq_ignore_int = 1;
+ return 0 ;
+}
+
+static int TTStateInfo(char *buffer, size_t space)
+{
+ int len = 0;
+ len += sprintf(buffer+len, "\tvol left %ddB [-40... 0]\n",
+ dmasound.volume_left);
+ len += sprintf(buffer+len, "\tvol right %ddB [-40... 0]\n",
+ dmasound.volume_right);
+ len += sprintf(buffer+len, "\tbass %ddB [-12...+12]\n",
+ dmasound.bass);
+ len += sprintf(buffer+len, "\ttreble %ddB [-12...+12]\n",
+ dmasound.treble);
+ if (len >= space) {
+ printk(KERN_ERR "dmasound_atari: overflowed state buffer alloc.\n") ;
+ len = space ;
+ }
+ return len;
+}
+
+static int FalconStateInfo(char *buffer, size_t space)
+{
+ int len = 0;
+ len += sprintf(buffer+len, "\tvol left %ddB [-22.5 ... 0]\n",
+ dmasound.volume_left);
+ len += sprintf(buffer+len, "\tvol right %ddB [-22.5 ... 0]\n",
+ dmasound.volume_right);
+ if (len >= space) {
+ printk(KERN_ERR "dmasound_atari: overflowed state buffer alloc.\n") ;
+ len = space ;
+ }
+ return len;
+}
+
+
+/*** Machine definitions *****************************************************/
+
+static SETTINGS def_hard_falcon = {
+ .format = AFMT_S8,
+ .stereo = 0,
+ .size = 8,
+ .speed = 8195
+} ;
+
+static SETTINGS def_hard_tt = {
+ .format = AFMT_S8,
+ .stereo = 0,
+ .size = 8,
+ .speed = 12517
+} ;
+
+static SETTINGS def_soft = {
+ .format = AFMT_U8,
+ .stereo = 0,
+ .size = 8,
+ .speed = 8000
+} ;
+
+static __initdata MACHINE machTT = {
+ .name = "Atari",
+ .name2 = "TT",
+ .owner = THIS_MODULE,
+ .dma_alloc = AtaAlloc,
+ .dma_free = AtaFree,
+ .irqinit = AtaIrqInit,
+#ifdef MODULE
+ .irqcleanup = AtaIrqCleanUp,
+#endif /* MODULE */
+ .init = TTInit,
+ .silence = TTSilence,
+ .setFormat = TTSetFormat,
+ .setVolume = TTSetVolume,
+ .setBass = AtaSetBass,
+ .setTreble = AtaSetTreble,
+ .setGain = TTSetGain,
+ .play = AtaPlay,
+ .mixer_init = TTMixerInit,
+ .mixer_ioctl = TTMixerIoctl,
+ .write_sq_setup = AtaWriteSqSetup,
+ .sq_open = AtaSqOpen,
+ .state_info = TTStateInfo,
+ .min_dsp_speed = 6258,
+ .version = ((DMASOUND_ATARI_REVISION<<8) | DMASOUND_ATARI_EDITION),
+ .hardware_afmts = AFMT_S8, /* h'ware-supported formats *only* here */
+ .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */
+};
+
+static __initdata MACHINE machFalcon = {
+ .name = "Atari",
+ .name2 = "FALCON",
+ .dma_alloc = AtaAlloc,
+ .dma_free = AtaFree,
+ .irqinit = AtaIrqInit,
+#ifdef MODULE
+ .irqcleanup = AtaIrqCleanUp,
+#endif /* MODULE */
+ .init = FalconInit,
+ .silence = FalconSilence,
+ .setFormat = FalconSetFormat,
+ .setVolume = FalconSetVolume,
+ .setBass = AtaSetBass,
+ .setTreble = AtaSetTreble,
+ .play = AtaPlay,
+ .mixer_init = FalconMixerInit,
+ .mixer_ioctl = FalconMixerIoctl,
+ .write_sq_setup = AtaWriteSqSetup,
+ .sq_open = AtaSqOpen,
+ .state_info = FalconStateInfo,
+ .min_dsp_speed = 8195,
+ .version = ((DMASOUND_ATARI_REVISION<<8) | DMASOUND_ATARI_EDITION),
+ .hardware_afmts = (AFMT_S8 | AFMT_S16_BE), /* h'ware-supported formats *only* here */
+ .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */
+};
+
+
+/*** Config & Setup **********************************************************/
+
+
+static int __init dmasound_atari_init(void)
+{
+ if (MACH_IS_ATARI && ATARIHW_PRESENT(PCM_8BIT)) {
+ if (ATARIHW_PRESENT(CODEC)) {
+ dmasound.mach = machFalcon;
+ dmasound.mach.default_soft = def_soft ;
+ dmasound.mach.default_hard = def_hard_falcon ;
+ is_falcon = 1;
+ } else if (ATARIHW_PRESENT(MICROWIRE)) {
+ dmasound.mach = machTT;
+ dmasound.mach.default_soft = def_soft ;
+ dmasound.mach.default_hard = def_hard_tt ;
+ is_falcon = 0;
+ } else
+ return -ENODEV;
+ if ((st_mfp.int_en_a & st_mfp.int_mk_a & 0x20) == 0)
+ return dmasound_init();
+ else {
+ printk("DMA sound driver: Timer A interrupt already in use\n");
+ return -EBUSY;
+ }
+ }
+ return -ENODEV;
+}
+
+static void __exit dmasound_atari_cleanup(void)
+{
+ dmasound_deinit();
+}
+
+module_init(dmasound_atari_init);
+module_exit(dmasound_atari_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/dmasound/dmasound_core.c b/sound/oss/dmasound/dmasound_core.c
new file mode 100644
index 00000000..c918313c
--- /dev/null
+++ b/sound/oss/dmasound/dmasound_core.c
@@ -0,0 +1,1589 @@
+/*
+ * linux/sound/oss/dmasound/dmasound_core.c
+ *
+ *
+ * OSS/Free compatible Atari TT/Falcon and Amiga DMA sound driver for
+ * Linux/m68k
+ * Extended to support Power Macintosh for Linux/ppc by Paul Mackerras
+ *
+ * (c) 1995 by Michael Schlueter & Michael Marte
+ *
+ * Michael Schlueter (michael@duck.syd.de) did the basic structure of the VFS
+ * interface and the u-law to signed byte conversion.
+ *
+ * Michael Marte (marte@informatik.uni-muenchen.de) did the sound queue,
+ * /dev/mixer, /dev/sndstat and complemented the VFS interface. He would like
+ * to thank:
+ * - Michael Schlueter for initial ideas and documentation on the MFP and
+ * the DMA sound hardware.
+ * - Therapy? for their CD 'Troublegum' which really made me rock.
+ *
+ * /dev/sndstat is based on code by Hannu Savolainen, the author of the
+ * VoxWare family of drivers.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ * History:
+ *
+ * 1995/8/25 First release
+ *
+ * 1995/9/02 Roman Hodek:
+ * - Fixed atari_stram_alloc() call, the timer
+ * programming and several race conditions
+ * 1995/9/14 Roman Hodek:
+ * - After some discussion with Michael Schlueter,
+ * revised the interrupt disabling
+ * - Slightly speeded up U8->S8 translation by using
+ * long operations where possible
+ * - Added 4:3 interpolation for /dev/audio
+ *
+ * 1995/9/20 Torsten Scherer:
+ * - Fixed a bug in sq_write and changed /dev/audio
+ * converting to play at 12517Hz instead of 6258Hz.
+ *
+ * 1995/9/23 Torsten Scherer:
+ * - Changed sq_interrupt() and sq_play() to pre-program
+ * the DMA for another frame while there's still one
+ * running. This allows the IRQ response to be
+ * arbitrarily delayed and playing will still continue.
+ *
+ * 1995/10/14 Guenther Kelleter, Torsten Scherer:
+ * - Better support for Falcon audio (the Falcon doesn't
+ * raise an IRQ at the end of a frame, but at the
+ * beginning instead!). uses 'if (codec_dma)' in lots
+ * of places to simply switch between Falcon and TT
+ * code.
+ *
+ * 1995/11/06 Torsten Scherer:
+ * - Started introducing a hardware abstraction scheme
+ * (may perhaps also serve for Amigas?)
+ * - Can now play samples at almost all frequencies by
+ * means of a more generalized expand routine
+ * - Takes a good deal of care to cut data only at
+ * sample sizes
+ * - Buffer size is now a kernel runtime option
+ * - Implemented fsync() & several minor improvements
+ * Guenther Kelleter:
+ * - Useful hints and bug fixes
+ * - Cross-checked it for Falcons
+ *
+ * 1996/3/9 Geert Uytterhoeven:
+ * - Support added for Amiga, A-law, 16-bit little
+ * endian.
+ * - Unification to drivers/sound/dmasound.c.
+ *
+ * 1996/4/6 Martin Mitchell:
+ * - Updated to 1.3 kernel.
+ *
+ * 1996/6/13 Topi Kanerva:
+ * - Fixed things that were broken (mainly the amiga
+ * 14-bit routines)
+ * - /dev/sndstat shows now the real hardware frequency
+ * - The lowpass filter is disabled by default now
+ *
+ * 1996/9/25 Geert Uytterhoeven:
+ * - Modularization
+ *
+ * 1998/6/10 Andreas Schwab:
+ * - Converted to use sound_core
+ *
+ * 1999/12/28 Richard Zidlicky:
+ * - Added support for Q40
+ *
+ * 2000/2/27 Geert Uytterhoeven:
+ * - Clean up and split the code into 4 parts:
+ * o dmasound_core: machine-independent code
+ * o dmasound_atari: Atari TT and Falcon support
+ * o dmasound_awacs: Apple PowerMac support
+ * o dmasound_paula: Amiga support
+ *
+ * 2000/3/25 Geert Uytterhoeven:
+ * - Integration of dmasound_q40
+ * - Small clean ups
+ *
+ * 2001/01/26 [1.0] Iain Sandoe
+ * - make /dev/sndstat show revision & edition info.
+ * - since dmasound.mach.sq_setup() can fail on pmac
+ * its type has been changed to int and the returns
+ * are checked.
+ * [1.1] - stop missing translations from being called.
+ * 2001/02/08 [1.2] - remove unused translation tables & move machine-
+ * specific tables to low-level.
+ * - return correct info. for SNDCTL_DSP_GETFMTS.
+ * [1.3] - implement SNDCTL_DSP_GETCAPS fully.
+ * [1.4] - make /dev/sndstat text length usage deterministic.
+ * - make /dev/sndstat call to low-level
+ * dmasound.mach.state_info() pass max space to ll driver.
+ * - tidy startup banners and output info.
+ * [1.5] - tidy up a little (removed some unused #defines in
+ * dmasound.h)
+ * - fix up HAS_RECORD conditionalisation.
+ * - add record code in places it is missing...
+ * - change buf-sizes to bytes to allow < 1kb for pmac
+ * if user param entry is < 256 the value is taken to
+ * be in kb > 256 is taken to be in bytes.
+ * - make default buff/frag params conditional on
+ * machine to allow smaller values for pmac.
+ * - made the ioctls, read & write comply with the OSS
+ * rules on setting params.
+ * - added parsing of _setup() params for record.
+ * 2001/04/04 [1.6] - fix bug where sample rates higher than maximum were
+ * being reported as OK.
+ * - fix open() to return -EBUSY as per OSS doc. when
+ * audio is in use - this is independent of O_NOBLOCK.
+ * - fix bug where SNDCTL_DSP_POST was blocking.
+ */
+
+ /* Record capability notes 30/01/2001:
+ * At present these observations apply only to pmac LL driver (the only one
+ * that can do record, at present). However, if other LL drivers for machines
+ * with record are added they may apply.
+ *
+ * The fragment parameters for the record and play channels are separate.
+ * However, if the driver is opened O_RDWR there is no way (in the current OSS
+ * API) to specify their values independently for the record and playback
+ * channels. Since the only common factor between the input & output is the
+ * sample rate (on pmac) it should be possible to open /dev/dspX O_WRONLY and
+ * /dev/dspY O_RDONLY. The input & output channels could then have different
+ * characteristics (other than the first that sets sample rate claiming the
+ * right to set it for ever). As it stands, the format, channels, number of
+ * bits & sample rate are assumed to be common. In the future perhaps these
+ * should be the responsibility of the LL driver - and then if a card really
+ * does not share items between record & playback they can be specified
+ * separately.
+*/
+
+/* Thread-safeness of shared_resources notes: 31/01/2001
+ * If the user opens O_RDWR and then splits record & play between two threads
+ * both of which inherit the fd - and then starts changing things from both
+ * - we will have difficulty telling.
+ *
+ * It's bad application coding - but ...
+ * TODO: think about how to sort this out... without bogging everything down in
+ * semaphores.
+ *
+ * Similarly, the OSS spec says "all changes to parameters must be between
+ * open() and the first read() or write(). - and a bit later on (by
+ * implication) "between SNDCTL_DSP_RESET and the first read() or write() after
+ * it". If the app is multi-threaded and this rule is broken between threads
+ * we will have trouble spotting it - and the fault will be rather obscure :-(
+ *
+ * We will try and put out at least a kmsg if we see it happen... but I think
+ * it will be quite hard to trap it with an -EXXX return... because we can't
+ * see the fault until after the damage is done.
+*/
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sound.h>
+#include <linux/init.h>
+#include <linux/soundcard.h>
+#include <linux/poll.h>
+#include <linux/mutex.h>
+
+#include <asm/uaccess.h>
+
+#include "dmasound.h"
+
+#define DMASOUND_CORE_REVISION 1
+#define DMASOUND_CORE_EDITION 6
+
+ /*
+ * Declarations
+ */
+
+static DEFINE_MUTEX(dmasound_core_mutex);
+int dmasound_catchRadius = 0;
+module_param(dmasound_catchRadius, int, 0);
+
+static unsigned int numWriteBufs = DEFAULT_N_BUFFERS;
+module_param(numWriteBufs, int, 0);
+static unsigned int writeBufSize = DEFAULT_BUFF_SIZE ; /* in bytes */
+module_param(writeBufSize, int, 0);
+
+MODULE_LICENSE("GPL");
+
+#ifdef MODULE
+static int sq_unit = -1;
+static int mixer_unit = -1;
+static int state_unit = -1;
+static int irq_installed;
+#endif /* MODULE */
+
+/* control over who can modify resources shared between play/record */
+static fmode_t shared_resource_owner;
+static int shared_resources_initialised;
+
+ /*
+ * Mid level stuff
+ */
+
+struct sound_settings dmasound = {
+ .lock = __SPIN_LOCK_UNLOCKED(dmasound.lock)
+};
+
+static inline void sound_silence(void)
+{
+ dmasound.mach.silence(); /* _MUST_ stop DMA */
+}
+
+static inline int sound_set_format(int format)
+{
+ return dmasound.mach.setFormat(format);
+}
+
+
+static int sound_set_speed(int speed)
+{
+ if (speed < 0)
+ return dmasound.soft.speed;
+
+ /* trap out-of-range speed settings.
+ at present we allow (arbitrarily) low rates - using soft
+ up-conversion - but we can't allow > max because there is
+ no soft down-conversion.
+ */
+ if (dmasound.mach.max_dsp_speed &&
+ (speed > dmasound.mach.max_dsp_speed))
+ speed = dmasound.mach.max_dsp_speed ;
+
+ dmasound.soft.speed = speed;
+
+ if (dmasound.minDev == SND_DEV_DSP)
+ dmasound.dsp.speed = dmasound.soft.speed;
+
+ return dmasound.soft.speed;
+}
+
+static int sound_set_stereo(int stereo)
+{
+ if (stereo < 0)
+ return dmasound.soft.stereo;
+
+ stereo = !!stereo; /* should be 0 or 1 now */
+
+ dmasound.soft.stereo = stereo;
+ if (dmasound.minDev == SND_DEV_DSP)
+ dmasound.dsp.stereo = stereo;
+
+ return stereo;
+}
+
+static ssize_t sound_copy_translate(TRANS *trans, const u_char __user *userPtr,
+ size_t userCount, u_char frame[],
+ ssize_t *frameUsed, ssize_t frameLeft)
+{
+ ssize_t (*ct_func)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+
+ switch (dmasound.soft.format) {
+ case AFMT_MU_LAW:
+ ct_func = trans->ct_ulaw;
+ break;
+ case AFMT_A_LAW:
+ ct_func = trans->ct_alaw;
+ break;
+ case AFMT_S8:
+ ct_func = trans->ct_s8;
+ break;
+ case AFMT_U8:
+ ct_func = trans->ct_u8;
+ break;
+ case AFMT_S16_BE:
+ ct_func = trans->ct_s16be;
+ break;
+ case AFMT_U16_BE:
+ ct_func = trans->ct_u16be;
+ break;
+ case AFMT_S16_LE:
+ ct_func = trans->ct_s16le;
+ break;
+ case AFMT_U16_LE:
+ ct_func = trans->ct_u16le;
+ break;
+ default:
+ return 0;
+ }
+ /* if the user has requested a non-existent translation don't try
+ to call it but just return 0 bytes moved
+ */
+ if (ct_func)
+ return ct_func(userPtr, userCount, frame, frameUsed, frameLeft);
+ return 0;
+}
+
+ /*
+ * /dev/mixer abstraction
+ */
+
+static struct {
+ int busy;
+ int modify_counter;
+} mixer;
+
+static int mixer_open(struct inode *inode, struct file *file)
+{
+ mutex_lock(&dmasound_core_mutex);
+ if (!try_module_get(dmasound.mach.owner)) {
+ mutex_unlock(&dmasound_core_mutex);
+ return -ENODEV;
+ }
+ mixer.busy = 1;
+ mutex_unlock(&dmasound_core_mutex);
+ return 0;
+}
+
+static int mixer_release(struct inode *inode, struct file *file)
+{
+ mutex_lock(&dmasound_core_mutex);
+ mixer.busy = 0;
+ module_put(dmasound.mach.owner);
+ mutex_unlock(&dmasound_core_mutex);
+ return 0;
+}
+
+static int mixer_ioctl(struct file *file, u_int cmd, u_long arg)
+{
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+ mixer.modify_counter++;
+ switch (cmd) {
+ case OSS_GETVERSION:
+ return IOCTL_OUT(arg, SOUND_VERSION);
+ case SOUND_MIXER_INFO:
+ {
+ mixer_info info;
+ memset(&info, 0, sizeof(info));
+ strlcpy(info.id, dmasound.mach.name2, sizeof(info.id));
+ strlcpy(info.name, dmasound.mach.name2, sizeof(info.name));
+ info.modify_counter = mixer.modify_counter;
+ if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+ }
+ if (dmasound.mach.mixer_ioctl)
+ return dmasound.mach.mixer_ioctl(cmd, arg);
+ return -EINVAL;
+}
+
+static long mixer_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
+{
+ int ret;
+
+ mutex_lock(&dmasound_core_mutex);
+ ret = mixer_ioctl(file, cmd, arg);
+ mutex_unlock(&dmasound_core_mutex);
+
+ return ret;
+}
+
+static const struct file_operations mixer_fops =
+{
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .unlocked_ioctl = mixer_unlocked_ioctl,
+ .open = mixer_open,
+ .release = mixer_release,
+};
+
+static void mixer_init(void)
+{
+#ifndef MODULE
+ int mixer_unit;
+#endif
+ mixer_unit = register_sound_mixer(&mixer_fops, -1);
+ if (mixer_unit < 0)
+ return;
+
+ mixer.busy = 0;
+ dmasound.treble = 0;
+ dmasound.bass = 0;
+ if (dmasound.mach.mixer_init)
+ dmasound.mach.mixer_init();
+}
+
+
+ /*
+ * Sound queue stuff, the heart of the driver
+ */
+
+struct sound_queue dmasound_write_sq;
+static void sq_reset_output(void) ;
+
+static int sq_allocate_buffers(struct sound_queue *sq, int num, int size)
+{
+ int i;
+
+ if (sq->buffers)
+ return 0;
+ sq->numBufs = num;
+ sq->bufSize = size;
+ sq->buffers = kmalloc (num * sizeof(char *), GFP_KERNEL);
+ if (!sq->buffers)
+ return -ENOMEM;
+ for (i = 0; i < num; i++) {
+ sq->buffers[i] = dmasound.mach.dma_alloc(size, GFP_KERNEL);
+ if (!sq->buffers[i]) {
+ while (i--)
+ dmasound.mach.dma_free(sq->buffers[i], size);
+ kfree(sq->buffers);
+ sq->buffers = NULL;
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+static void sq_release_buffers(struct sound_queue *sq)
+{
+ int i;
+
+ if (sq->buffers) {
+ for (i = 0; i < sq->numBufs; i++)
+ dmasound.mach.dma_free(sq->buffers[i], sq->bufSize);
+ kfree(sq->buffers);
+ sq->buffers = NULL;
+ }
+}
+
+
+static int sq_setup(struct sound_queue *sq)
+{
+ int (*setup_func)(void) = NULL;
+ int hard_frame ;
+
+ if (sq->locked) { /* are we already set? - and not changeable */
+#ifdef DEBUG_DMASOUND
+printk("dmasound_core: tried to sq_setup a locked queue\n") ;
+#endif
+ return -EINVAL ;
+ }
+ sq->locked = 1 ; /* don't think we have a race prob. here _check_ */
+
+ /* make sure that the parameters are set up
+ This should have been done already...
+ */
+
+ dmasound.mach.init();
+
+ /* OK. If the user has set fragment parameters explicitly, then we
+ should leave them alone... as long as they are valid.
+ Invalid user fragment params can occur if we allow the whole buffer
+ to be used when the user requests the fragments sizes (with no soft
+ x-lation) and then the user subsequently sets a soft x-lation that
+ requires increased internal buffering.
+
+ Othwerwise (if the user did not set them) OSS says that we should
+ select frag params on the basis of 0.5 s output & 0.1 s input
+ latency. (TODO. For now we will copy in the defaults.)
+ */
+
+ if (sq->user_frags <= 0) {
+ sq->max_count = sq->numBufs ;
+ sq->max_active = sq->numBufs ;
+ sq->block_size = sq->bufSize;
+ /* set up the user info */
+ sq->user_frags = sq->numBufs ;
+ sq->user_frag_size = sq->bufSize ;
+ sq->user_frag_size *=
+ (dmasound.soft.size * (dmasound.soft.stereo+1) ) ;
+ sq->user_frag_size /=
+ (dmasound.hard.size * (dmasound.hard.stereo+1) ) ;
+ } else {
+ /* work out requested block size */
+ sq->block_size = sq->user_frag_size ;
+ sq->block_size *=
+ (dmasound.hard.size * (dmasound.hard.stereo+1) ) ;
+ sq->block_size /=
+ (dmasound.soft.size * (dmasound.soft.stereo+1) ) ;
+ /* the user wants to write frag-size chunks */
+ sq->block_size *= dmasound.hard.speed ;
+ sq->block_size /= dmasound.soft.speed ;
+ /* this only works for size values which are powers of 2 */
+ hard_frame =
+ (dmasound.hard.size * (dmasound.hard.stereo+1))/8 ;
+ sq->block_size += (hard_frame - 1) ;
+ sq->block_size &= ~(hard_frame - 1) ; /* make sure we are aligned */
+ /* let's just check for obvious mistakes */
+ if ( sq->block_size <= 0 || sq->block_size > sq->bufSize) {
+#ifdef DEBUG_DMASOUND
+printk("dmasound_core: invalid frag size (user set %d)\n", sq->user_frag_size) ;
+#endif
+ sq->block_size = sq->bufSize ;
+ }
+ if ( sq->user_frags <= sq->numBufs ) {
+ sq->max_count = sq->user_frags ;
+ /* if user has set max_active - then use it */
+ sq->max_active = (sq->max_active <= sq->max_count) ?
+ sq->max_active : sq->max_count ;
+ } else {
+#ifdef DEBUG_DMASOUND
+printk("dmasound_core: invalid frag count (user set %d)\n", sq->user_frags) ;
+#endif
+ sq->max_count =
+ sq->max_active = sq->numBufs ;
+ }
+ }
+ sq->front = sq->count = sq->rear_size = 0;
+ sq->syncing = 0;
+ sq->active = 0;
+
+ if (sq == &write_sq) {
+ sq->rear = -1;
+ setup_func = dmasound.mach.write_sq_setup;
+ }
+ if (setup_func)
+ return setup_func();
+ return 0 ;
+}
+
+static inline void sq_play(void)
+{
+ dmasound.mach.play();
+}
+
+static ssize_t sq_write(struct file *file, const char __user *src, size_t uLeft,
+ loff_t *ppos)
+{
+ ssize_t uWritten = 0;
+ u_char *dest;
+ ssize_t uUsed = 0, bUsed, bLeft;
+ unsigned long flags ;
+
+ /* ++TeSche: Is something like this necessary?
+ * Hey, that's an honest question! Or does any other part of the
+ * filesystem already checks this situation? I really don't know.
+ */
+ if (uLeft == 0)
+ return 0;
+
+ /* implement any changes we have made to the soft/hard params.
+ this is not satisfactory really, all we have done up to now is to
+ say what we would like - there hasn't been any real checking of capability
+ */
+
+ if (shared_resources_initialised == 0) {
+ dmasound.mach.init() ;
+ shared_resources_initialised = 1 ;
+ }
+
+ /* set up the sq if it is not already done. This may seem a dumb place
+ to do it - but it is what OSS requires. It means that write() can
+ return memory allocation errors. To avoid this possibility use the
+ GETBLKSIZE or GETOSPACE ioctls (after you've fiddled with all the
+ params you want to change) - these ioctls also force the setup.
+ */
+
+ if (write_sq.locked == 0) {
+ if ((uWritten = sq_setup(&write_sq)) < 0) return uWritten ;
+ uWritten = 0 ;
+ }
+
+/* FIXME: I think that this may be the wrong behaviour when we get strapped
+ for time and the cpu is close to being (or actually) behind in sending data.
+ - because we've lost the time that the N samples, already in the buffer,
+ would have given us to get here with the next lot from the user.
+*/
+ /* The interrupt doesn't start to play the last, incomplete frame.
+ * Thus we can append to it without disabling the interrupts! (Note
+ * also that write_sq.rear isn't affected by the interrupt.)
+ */
+
+ /* as of 1.6 this behaviour changes if SNDCTL_DSP_POST has been issued:
+ this will mimic the behaviour of syncing and allow the sq_play() to
+ queue a partial fragment. Since sq_play() may/will be called from
+ the IRQ handler - at least on Pmac we have to deal with it.
+ The strategy - possibly not optimum - is to kill _POST status if we
+ get here. This seems, at least, reasonable - in the sense that POST
+ is supposed to indicate that we might not write before the queue
+ is drained - and if we get here in time then it does not apply.
+ */
+
+ spin_lock_irqsave(&dmasound.lock, flags);
+ write_sq.syncing &= ~2 ; /* take out POST status */
+ spin_unlock_irqrestore(&dmasound.lock, flags);
+
+ if (write_sq.count > 0 &&
+ (bLeft = write_sq.block_size-write_sq.rear_size) > 0) {
+ dest = write_sq.buffers[write_sq.rear];
+ bUsed = write_sq.rear_size;
+ uUsed = sound_copy_translate(dmasound.trans_write, src, uLeft,
+ dest, &bUsed, bLeft);
+ if (uUsed <= 0)
+ return uUsed;
+ src += uUsed;
+ uWritten += uUsed;
+ uLeft = (uUsed <= uLeft) ? (uLeft - uUsed) : 0 ; /* paranoia */
+ write_sq.rear_size = bUsed;
+ }
+
+ while (uLeft) {
+ while (write_sq.count >= write_sq.max_active) {
+ sq_play();
+ if (write_sq.non_blocking)
+ return uWritten > 0 ? uWritten : -EAGAIN;
+ SLEEP(write_sq.action_queue);
+ if (signal_pending(current))
+ return uWritten > 0 ? uWritten : -EINTR;
+ }
+
+ /* Here, we can avoid disabling the interrupt by first
+ * copying and translating the data, and then updating
+ * the write_sq variables. Until this is done, the interrupt
+ * won't see the new frame and we can work on it
+ * undisturbed.
+ */
+
+ dest = write_sq.buffers[(write_sq.rear+1) % write_sq.max_count];
+ bUsed = 0;
+ bLeft = write_sq.block_size;
+ uUsed = sound_copy_translate(dmasound.trans_write, src, uLeft,
+ dest, &bUsed, bLeft);
+ if (uUsed <= 0)
+ break;
+ src += uUsed;
+ uWritten += uUsed;
+ uLeft = (uUsed <= uLeft) ? (uLeft - uUsed) : 0 ; /* paranoia */
+ if (bUsed) {
+ write_sq.rear = (write_sq.rear+1) % write_sq.max_count;
+ write_sq.rear_size = bUsed;
+ write_sq.count++;
+ }
+ } /* uUsed may have been 0 */
+
+ sq_play();
+
+ return uUsed < 0? uUsed: uWritten;
+}
+
+static unsigned int sq_poll(struct file *file, struct poll_table_struct *wait)
+{
+ unsigned int mask = 0;
+ int retVal;
+
+ if (write_sq.locked == 0) {
+ if ((retVal = sq_setup(&write_sq)) < 0)
+ return retVal;
+ return 0;
+ }
+ if (file->f_mode & FMODE_WRITE )
+ poll_wait(file, &write_sq.action_queue, wait);
+ if (file->f_mode & FMODE_WRITE)
+ if (write_sq.count < write_sq.max_active || write_sq.block_size - write_sq.rear_size > 0)
+ mask |= POLLOUT | POLLWRNORM;
+ return mask;
+
+}
+
+static inline void sq_init_waitqueue(struct sound_queue *sq)
+{
+ init_waitqueue_head(&sq->action_queue);
+ init_waitqueue_head(&sq->open_queue);
+ init_waitqueue_head(&sq->sync_queue);
+ sq->busy = 0;
+}
+
+#if 0 /* blocking open() */
+static inline void sq_wake_up(struct sound_queue *sq, struct file *file,
+ fmode_t mode)
+{
+ if (file->f_mode & mode) {
+ sq->busy = 0; /* CHECK: IS THIS OK??? */
+ WAKE_UP(sq->open_queue);
+ }
+}
+#endif
+
+static int sq_open2(struct sound_queue *sq, struct file *file, fmode_t mode,
+ int numbufs, int bufsize)
+{
+ int rc = 0;
+
+ if (file->f_mode & mode) {
+ if (sq->busy) {
+#if 0 /* blocking open() */
+ rc = -EBUSY;
+ if (file->f_flags & O_NONBLOCK)
+ return rc;
+ rc = -EINTR;
+ while (sq->busy) {
+ SLEEP(sq->open_queue);
+ if (signal_pending(current))
+ return rc;
+ }
+ rc = 0;
+#else
+ /* OSS manual says we will return EBUSY regardless
+ of O_NOBLOCK.
+ */
+ return -EBUSY ;
+#endif
+ }
+ sq->busy = 1; /* Let's play spot-the-race-condition */
+
+ /* allocate the default number & size of buffers.
+ (i.e. specified in _setup() or as module params)
+ can't be changed at the moment - but _could_ be perhaps
+ in the setfragments ioctl.
+ */
+ if (( rc = sq_allocate_buffers(sq, numbufs, bufsize))) {
+#if 0 /* blocking open() */
+ sq_wake_up(sq, file, mode);
+#else
+ sq->busy = 0 ;
+#endif
+ return rc;
+ }
+
+ sq->non_blocking = file->f_flags & O_NONBLOCK;
+ }
+ return rc;
+}
+
+#define write_sq_init_waitqueue() sq_init_waitqueue(&write_sq)
+#if 0 /* blocking open() */
+#define write_sq_wake_up(file) sq_wake_up(&write_sq, file, FMODE_WRITE)
+#endif
+#define write_sq_release_buffers() sq_release_buffers(&write_sq)
+#define write_sq_open(file) \
+ sq_open2(&write_sq, file, FMODE_WRITE, numWriteBufs, writeBufSize )
+
+static int sq_open(struct inode *inode, struct file *file)
+{
+ int rc;
+
+ mutex_lock(&dmasound_core_mutex);
+ if (!try_module_get(dmasound.mach.owner)) {
+ mutex_unlock(&dmasound_core_mutex);
+ return -ENODEV;
+ }
+
+ rc = write_sq_open(file); /* checks the f_mode */
+ if (rc)
+ goto out;
+ if (file->f_mode & FMODE_READ) {
+ /* TODO: if O_RDWR, release any resources grabbed by write part */
+ rc = -ENXIO ; /* I think this is what is required by open(2) */
+ goto out;
+ }
+
+ if (dmasound.mach.sq_open)
+ dmasound.mach.sq_open(file->f_mode);
+
+ /* CHECK whether this is sensible - in the case that dsp0 could be opened
+ O_RDONLY and dsp1 could be opened O_WRONLY
+ */
+
+ dmasound.minDev = iminor(inode) & 0x0f;
+
+ /* OK. - we should make some attempt at consistency. At least the H'ware
+ options should be set with a valid mode. We will make it that the LL
+ driver must supply defaults for hard & soft params.
+ */
+
+ if (shared_resource_owner == 0) {
+ /* you can make this AFMT_U8/mono/8K if you want to mimic old
+ OSS behaviour - while we still have soft translations ;-) */
+ dmasound.soft = dmasound.mach.default_soft ;
+ dmasound.dsp = dmasound.mach.default_soft ;
+ dmasound.hard = dmasound.mach.default_hard ;
+ }
+
+#ifndef DMASOUND_STRICT_OSS_COMPLIANCE
+ /* none of the current LL drivers can actually do this "native" at the moment
+ OSS does not really require us to supply /dev/audio if we can't do it.
+ */
+ if (dmasound.minDev == SND_DEV_AUDIO) {
+ sound_set_speed(8000);
+ sound_set_stereo(0);
+ sound_set_format(AFMT_MU_LAW);
+ }
+#endif
+ mutex_unlock(&dmasound_core_mutex);
+ return 0;
+ out:
+ module_put(dmasound.mach.owner);
+ mutex_unlock(&dmasound_core_mutex);
+ return rc;
+}
+
+static void sq_reset_output(void)
+{
+ sound_silence(); /* this _must_ stop DMA, we might be about to lose the buffers */
+ write_sq.active = 0;
+ write_sq.count = 0;
+ write_sq.rear_size = 0;
+ /* write_sq.front = (write_sq.rear+1) % write_sq.max_count;*/
+ write_sq.front = 0 ;
+ write_sq.rear = -1 ; /* same as for set-up */
+
+ /* OK - we can unlock the parameters and fragment settings */
+ write_sq.locked = 0 ;
+ write_sq.user_frags = 0 ;
+ write_sq.user_frag_size = 0 ;
+}
+
+static void sq_reset(void)
+{
+ sq_reset_output() ;
+ /* we could consider resetting the shared_resources_owner here... but I
+ think it is probably still rather non-obvious to application writer
+ */
+
+ /* we release everything else though */
+ shared_resources_initialised = 0 ;
+}
+
+static int sq_fsync(struct file *filp, struct dentry *dentry)
+{
+ int rc = 0;
+ int timeout = 5;
+
+ write_sq.syncing |= 1;
+ sq_play(); /* there may be an incomplete frame waiting */
+
+ while (write_sq.active) {
+ SLEEP(write_sq.sync_queue);
+ if (signal_pending(current)) {
+ /* While waiting for audio output to drain, an
+ * interrupt occurred. Stop audio output immediately
+ * and clear the queue. */
+ sq_reset_output();
+ rc = -EINTR;
+ break;
+ }
+ if (!--timeout) {
+ printk(KERN_WARNING "dmasound: Timeout draining output\n");
+ sq_reset_output();
+ rc = -EIO;
+ break;
+ }
+ }
+
+ /* flag no sync regardless of whether we had a DSP_POST or not */
+ write_sq.syncing = 0 ;
+ return rc;
+}
+
+static int sq_release(struct inode *inode, struct file *file)
+{
+ int rc = 0;
+
+ mutex_lock(&dmasound_core_mutex);
+
+ if (file->f_mode & FMODE_WRITE) {
+ if (write_sq.busy)
+ rc = sq_fsync(file, file->f_path.dentry);
+
+ sq_reset_output() ; /* make sure dma is stopped and all is quiet */
+ write_sq_release_buffers();
+ write_sq.busy = 0;
+ }
+
+ if (file->f_mode & shared_resource_owner) { /* it's us that has them */
+ shared_resource_owner = 0 ;
+ shared_resources_initialised = 0 ;
+ dmasound.hard = dmasound.mach.default_hard ;
+ }
+
+ module_put(dmasound.mach.owner);
+
+#if 0 /* blocking open() */
+ /* Wake up a process waiting for the queue being released.
+ * Note: There may be several processes waiting for a call
+ * to open() returning. */
+
+ /* Iain: hmm I don't understand this next comment ... */
+ /* There is probably a DOS atack here. They change the mode flag. */
+ /* XXX add check here,*/
+ read_sq_wake_up(file); /* checks f_mode */
+ write_sq_wake_up(file); /* checks f_mode */
+#endif /* blocking open() */
+
+ mutex_unlock(&dmasound_core_mutex);
+
+ return rc;
+}
+
+/* here we see if we have a right to modify format, channels, size and so on
+ if no-one else has claimed it already then we do...
+
+ TODO: We might change this to mask O_RDWR such that only one or the other channel
+ is the owner - if we have problems.
+*/
+
+static int shared_resources_are_mine(fmode_t md)
+{
+ if (shared_resource_owner)
+ return (shared_resource_owner & md) != 0;
+ else {
+ shared_resource_owner = md ;
+ return 1 ;
+ }
+}
+
+/* if either queue is locked we must deny the right to change shared params
+*/
+
+static int queues_are_quiescent(void)
+{
+ if (write_sq.locked)
+ return 0 ;
+ return 1 ;
+}
+
+/* check and set a queue's fragments per user's wishes...
+ we will check against the pre-defined literals and the actual sizes.
+ This is a bit fraught - because soft translations can mess with our
+ buffer requirements *after* this call - OSS says "call setfrags first"
+*/
+
+/* It is possible to replace all the -EINVAL returns with an override that
+ just puts the allowable value in. This may be what many OSS apps require
+*/
+
+static int set_queue_frags(struct sound_queue *sq, int bufs, int size)
+{
+ if (sq->locked) {
+#ifdef DEBUG_DMASOUND
+printk("dmasound_core: tried to set_queue_frags on a locked queue\n") ;
+#endif
+ return -EINVAL ;
+ }
+
+ if ((size < MIN_FRAG_SIZE) || (size > MAX_FRAG_SIZE))
+ return -EINVAL ;
+ size = (1<<size) ; /* now in bytes */
+ if (size > sq->bufSize)
+ return -EINVAL ; /* this might still not work */
+
+ if (bufs <= 0)
+ return -EINVAL ;
+ if (bufs > sq->numBufs) /* the user is allowed say "don't care" with 0x7fff */
+ bufs = sq->numBufs ;
+
+ /* there is, currently, no way to specify max_active separately
+ from max_count. This could be a LL driver issue - I guess
+ if there is a requirement for these values to be different then
+ we will have to pass that info. up to this level.
+ */
+ sq->user_frags =
+ sq->max_active = bufs ;
+ sq->user_frag_size = size ;
+
+ return 0 ;
+}
+
+static int sq_ioctl(struct file *file, u_int cmd, u_long arg)
+{
+ int val, result;
+ u_long fmt;
+ int data;
+ int size, nbufs;
+ audio_buf_info info;
+
+ switch (cmd) {
+ case SNDCTL_DSP_RESET:
+ sq_reset();
+ return 0;
+ break ;
+ case SNDCTL_DSP_GETFMTS:
+ fmt = dmasound.mach.hardware_afmts ; /* this is what OSS says.. */
+ return IOCTL_OUT(arg, fmt);
+ break ;
+ case SNDCTL_DSP_GETBLKSIZE:
+ /* this should tell the caller about bytes that the app can
+ read/write - the app doesn't care about our internal buffers.
+ We force sq_setup() here as per OSS 1.1 (which should
+ compute the values necessary).
+ Since there is no mechanism to specify read/write separately, for
+ fds opened O_RDWR, the write_sq values will, arbitrarily, overwrite
+ the read_sq ones.
+ */
+ size = 0 ;
+ if (file->f_mode & FMODE_WRITE) {
+ if ( !write_sq.locked )
+ sq_setup(&write_sq) ;
+ size = write_sq.user_frag_size ;
+ }
+ return IOCTL_OUT(arg, size);
+ break ;
+ case SNDCTL_DSP_POST:
+ /* all we are going to do is to tell the LL that any
+ partial frags can be queued for output.
+ The LL will have to clear this flag when last output
+ is queued.
+ */
+ write_sq.syncing |= 0x2 ;
+ sq_play() ;
+ return 0 ;
+ case SNDCTL_DSP_SYNC:
+ /* This call, effectively, has the same behaviour as SNDCTL_DSP_RESET
+ except that it waits for output to finish before resetting
+ everything - read, however, is killed immediately.
+ */
+ result = 0 ;
+ if (file->f_mode & FMODE_WRITE) {
+ result = sq_fsync(file, file->f_path.dentry);
+ sq_reset_output() ;
+ }
+ /* if we are the shared resource owner then release them */
+ if (file->f_mode & shared_resource_owner)
+ shared_resources_initialised = 0 ;
+ return result ;
+ break ;
+ case SOUND_PCM_READ_RATE:
+ return IOCTL_OUT(arg, dmasound.soft.speed);
+ case SNDCTL_DSP_SPEED:
+ /* changing this on the fly will have weird effects on the sound.
+ Where there are rate conversions implemented in soft form - it
+ will cause the _ctx_xxx() functions to be substituted.
+ However, there doesn't appear to be any reason to dis-allow it from
+ a driver pov.
+ */
+ if (shared_resources_are_mine(file->f_mode)) {
+ IOCTL_IN(arg, data);
+ data = sound_set_speed(data) ;
+ shared_resources_initialised = 0 ;
+ return IOCTL_OUT(arg, data);
+ } else
+ return -EINVAL ;
+ break ;
+ /* OSS says these next 4 actions are undefined when the device is
+ busy/active - we will just return -EINVAL.
+ To be allowed to change one - (a) you have to own the right
+ (b) the queue(s) must be quiescent
+ */
+ case SNDCTL_DSP_STEREO:
+ if (shared_resources_are_mine(file->f_mode) &&
+ queues_are_quiescent()) {
+ IOCTL_IN(arg, data);
+ shared_resources_initialised = 0 ;
+ return IOCTL_OUT(arg, sound_set_stereo(data));
+ } else
+ return -EINVAL ;
+ break ;
+ case SOUND_PCM_WRITE_CHANNELS:
+ if (shared_resources_are_mine(file->f_mode) &&
+ queues_are_quiescent()) {
+ IOCTL_IN(arg, data);
+ /* the user might ask for 20 channels, we will return 1 or 2 */
+ shared_resources_initialised = 0 ;
+ return IOCTL_OUT(arg, sound_set_stereo(data-1)+1);
+ } else
+ return -EINVAL ;
+ break ;
+ case SNDCTL_DSP_SETFMT:
+ if (shared_resources_are_mine(file->f_mode) &&
+ queues_are_quiescent()) {
+ int format;
+ IOCTL_IN(arg, data);
+ shared_resources_initialised = 0 ;
+ format = sound_set_format(data);
+ result = IOCTL_OUT(arg, format);
+ if (result < 0)
+ return result;
+ if (format != data && data != AFMT_QUERY)
+ return -EINVAL;
+ return 0;
+ } else
+ return -EINVAL ;
+ case SNDCTL_DSP_SUBDIVIDE:
+ return -EINVAL ;
+ case SNDCTL_DSP_SETFRAGMENT:
+ /* we can do this independently for the two queues - with the
+ proviso that for fds opened O_RDWR we cannot separate the
+ actions and both queues will be set per the last call.
+ NOTE: this does *NOT* actually set the queue up - merely
+ registers our intentions.
+ */
+ IOCTL_IN(arg, data);
+ result = 0 ;
+ nbufs = (data >> 16) & 0x7fff ; /* 0x7fff is 'use maximum' */
+ size = data & 0xffff;
+ if (file->f_mode & FMODE_WRITE) {
+ result = set_queue_frags(&write_sq, nbufs, size) ;
+ if (result)
+ return result ;
+ }
+ /* NOTE: this return value is irrelevant - OSS specifically says that
+ the value is 'random' and that the user _must_ check the actual
+ frags values using SNDCTL_DSP_GETBLKSIZE or similar */
+ return IOCTL_OUT(arg, data);
+ break ;
+ case SNDCTL_DSP_GETOSPACE:
+ /*
+ */
+ if (file->f_mode & FMODE_WRITE) {
+ if ( !write_sq.locked )
+ sq_setup(&write_sq) ;
+ info.fragments = write_sq.max_active - write_sq.count;
+ info.fragstotal = write_sq.max_active;
+ info.fragsize = write_sq.user_frag_size;
+ info.bytes = info.fragments * info.fragsize;
+ if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ } else
+ return -EINVAL ;
+ break ;
+ case SNDCTL_DSP_GETCAPS:
+ val = dmasound.mach.capabilities & 0xffffff00;
+ return IOCTL_OUT(arg,val);
+
+ default:
+ return mixer_ioctl(file, cmd, arg);
+ }
+ return -EINVAL;
+}
+
+static long sq_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
+{
+ int ret;
+
+ mutex_lock(&dmasound_core_mutex);
+ ret = sq_ioctl(file, cmd, arg);
+ mutex_unlock(&dmasound_core_mutex);
+
+ return ret;
+}
+
+static const struct file_operations sq_fops =
+{
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = sq_write,
+ .poll = sq_poll,
+ .unlocked_ioctl = sq_unlocked_ioctl,
+ .open = sq_open,
+ .release = sq_release,
+};
+
+static int sq_init(void)
+{
+ const struct file_operations *fops = &sq_fops;
+#ifndef MODULE
+ int sq_unit;
+#endif
+
+ sq_unit = register_sound_dsp(fops, -1);
+ if (sq_unit < 0) {
+ printk(KERN_ERR "dmasound_core: couldn't register fops\n") ;
+ return sq_unit ;
+ }
+
+ write_sq_init_waitqueue();
+
+ /* These parameters will be restored for every clean open()
+ * in the case of multiple open()s (e.g. dsp0 & dsp1) they
+ * will be set so long as the shared resources have no owner.
+ */
+
+ if (shared_resource_owner == 0) {
+ dmasound.soft = dmasound.mach.default_soft ;
+ dmasound.hard = dmasound.mach.default_hard ;
+ dmasound.dsp = dmasound.mach.default_soft ;
+ shared_resources_initialised = 0 ;
+ }
+ return 0 ;
+}
+
+
+ /*
+ * /dev/sndstat
+ */
+
+/* we allow more space for record-enabled because there are extra output lines.
+ the number here must include the amount we are prepared to give to the low-level
+ driver.
+*/
+
+#define STAT_BUFF_LEN 768
+
+/* this is how much space we will allow the low-level driver to use
+ in the stat buffer. Currently, 2 * (80 character line + <NL>).
+ We do not police this (it is up to the ll driver to be honest).
+*/
+
+#define LOW_LEVEL_STAT_ALLOC 162
+
+static struct {
+ int busy;
+ char buf[STAT_BUFF_LEN]; /* state.buf should not overflow! */
+ int len, ptr;
+} state;
+
+/* publish this function for use by low-level code, if required */
+
+static char *get_afmt_string(int afmt)
+{
+ switch(afmt) {
+ case AFMT_MU_LAW:
+ return "mu-law";
+ break;
+ case AFMT_A_LAW:
+ return "A-law";
+ break;
+ case AFMT_U8:
+ return "unsigned 8 bit";
+ break;
+ case AFMT_S8:
+ return "signed 8 bit";
+ break;
+ case AFMT_S16_BE:
+ return "signed 16 bit BE";
+ break;
+ case AFMT_U16_BE:
+ return "unsigned 16 bit BE";
+ break;
+ case AFMT_S16_LE:
+ return "signed 16 bit LE";
+ break;
+ case AFMT_U16_LE:
+ return "unsigned 16 bit LE";
+ break;
+ case 0:
+ return "format not set" ;
+ break ;
+ default:
+ break ;
+ }
+ return "ERROR: Unsupported AFMT_XXXX code" ;
+}
+
+static int state_open(struct inode *inode, struct file *file)
+{
+ char *buffer = state.buf;
+ int len = 0;
+ int ret;
+
+ mutex_lock(&dmasound_core_mutex);
+ ret = -EBUSY;
+ if (state.busy)
+ goto out;
+
+ ret = -ENODEV;
+ if (!try_module_get(dmasound.mach.owner))
+ goto out;
+
+ state.ptr = 0;
+ state.busy = 1;
+
+ len += sprintf(buffer+len, "%sDMA sound driver rev %03d :\n",
+ dmasound.mach.name, (DMASOUND_CORE_REVISION<<4) +
+ ((dmasound.mach.version>>8) & 0x0f));
+ len += sprintf(buffer+len,
+ "Core driver edition %02d.%02d : %s driver edition %02d.%02d\n",
+ DMASOUND_CORE_REVISION, DMASOUND_CORE_EDITION, dmasound.mach.name2,
+ (dmasound.mach.version >> 8), (dmasound.mach.version & 0xff)) ;
+
+ /* call the low-level module to fill in any stat info. that it has
+ if present. Maximum buffer usage is specified.
+ */
+
+ if (dmasound.mach.state_info)
+ len += dmasound.mach.state_info(buffer+len,
+ (size_t) LOW_LEVEL_STAT_ALLOC) ;
+
+ /* make usage of the state buffer as deterministic as poss.
+ exceptional conditions could cause overrun - and this is flagged as
+ a kernel error.
+ */
+
+ /* formats and settings */
+
+ len += sprintf(buffer+len,"\t\t === Formats & settings ===\n") ;
+ len += sprintf(buffer+len,"Parameter %20s%20s\n","soft","hard") ;
+ len += sprintf(buffer+len,"Format :%20s%20s\n",
+ get_afmt_string(dmasound.soft.format),
+ get_afmt_string(dmasound.hard.format));
+
+ len += sprintf(buffer+len,"Samp Rate:%14d s/sec%14d s/sec\n",
+ dmasound.soft.speed, dmasound.hard.speed);
+
+ len += sprintf(buffer+len,"Channels :%20s%20s\n",
+ dmasound.soft.stereo ? "stereo" : "mono",
+ dmasound.hard.stereo ? "stereo" : "mono" );
+
+ /* sound queue status */
+
+ len += sprintf(buffer+len,"\t\t === Sound Queue status ===\n");
+ len += sprintf(buffer+len,"Allocated:%8s%6s\n","Buffers","Size") ;
+ len += sprintf(buffer+len,"%9s:%8d%6d\n",
+ "write", write_sq.numBufs, write_sq.bufSize) ;
+ len += sprintf(buffer+len,
+ "Current : MaxFrg FragSiz MaxAct Frnt Rear "
+ "Cnt RrSize A B S L xruns\n") ;
+ len += sprintf(buffer+len,"%9s:%7d%8d%7d%5d%5d%4d%7d%2d%2d%2d%2d%7d\n",
+ "write", write_sq.max_count, write_sq.block_size,
+ write_sq.max_active, write_sq.front, write_sq.rear,
+ write_sq.count, write_sq.rear_size, write_sq.active,
+ write_sq.busy, write_sq.syncing, write_sq.locked, write_sq.xruns) ;
+#ifdef DEBUG_DMASOUND
+printk("dmasound: stat buffer used %d bytes\n", len) ;
+#endif
+
+ if (len >= STAT_BUFF_LEN)
+ printk(KERN_ERR "dmasound_core: stat buffer overflowed!\n");
+
+ state.len = len;
+ ret = 0;
+out:
+ mutex_unlock(&dmasound_core_mutex);
+ return ret;
+}
+
+static int state_release(struct inode *inode, struct file *file)
+{
+ mutex_lock(&dmasound_core_mutex);
+ state.busy = 0;
+ module_put(dmasound.mach.owner);
+ mutex_unlock(&dmasound_core_mutex);
+ return 0;
+}
+
+static ssize_t state_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ int n = state.len - state.ptr;
+ if (n > count)
+ n = count;
+ if (n <= 0)
+ return 0;
+ if (copy_to_user(buf, &state.buf[state.ptr], n))
+ return -EFAULT;
+ state.ptr += n;
+ return n;
+}
+
+static const struct file_operations state_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = state_read,
+ .open = state_open,
+ .release = state_release,
+};
+
+static int state_init(void)
+{
+#ifndef MODULE
+ int state_unit;
+#endif
+ state_unit = register_sound_special(&state_fops, SND_DEV_STATUS);
+ if (state_unit < 0)
+ return state_unit ;
+ state.busy = 0;
+ return 0 ;
+}
+
+
+ /*
+ * Config & Setup
+ *
+ * This function is called by _one_ chipset-specific driver
+ */
+
+int dmasound_init(void)
+{
+ int res ;
+#ifdef MODULE
+ if (irq_installed)
+ return -EBUSY;
+#endif
+
+ /* Set up sound queue, /dev/audio and /dev/dsp. */
+
+ /* Set default settings. */
+ if ((res = sq_init()) < 0)
+ return res ;
+
+ /* Set up /dev/sndstat. */
+ if ((res = state_init()) < 0)
+ return res ;
+
+ /* Set up /dev/mixer. */
+ mixer_init();
+
+ if (!dmasound.mach.irqinit()) {
+ printk(KERN_ERR "DMA sound driver: Interrupt initialization failed\n");
+ return -ENODEV;
+ }
+#ifdef MODULE
+ irq_installed = 1;
+#endif
+
+ printk(KERN_INFO "%s DMA sound driver rev %03d installed\n",
+ dmasound.mach.name, (DMASOUND_CORE_REVISION<<4) +
+ ((dmasound.mach.version>>8) & 0x0f));
+ printk(KERN_INFO
+ "Core driver edition %02d.%02d : %s driver edition %02d.%02d\n",
+ DMASOUND_CORE_REVISION, DMASOUND_CORE_EDITION, dmasound.mach.name2,
+ (dmasound.mach.version >> 8), (dmasound.mach.version & 0xff)) ;
+ printk(KERN_INFO "Write will use %4d fragments of %7d bytes as default\n",
+ numWriteBufs, writeBufSize) ;
+ return 0;
+}
+
+#ifdef MODULE
+
+void dmasound_deinit(void)
+{
+ if (irq_installed) {
+ sound_silence();
+ dmasound.mach.irqcleanup();
+ irq_installed = 0;
+ }
+
+ write_sq_release_buffers();
+
+ if (mixer_unit >= 0)
+ unregister_sound_mixer(mixer_unit);
+ if (state_unit >= 0)
+ unregister_sound_special(state_unit);
+ if (sq_unit >= 0)
+ unregister_sound_dsp(sq_unit);
+}
+
+#else /* !MODULE */
+
+static int dmasound_setup(char *str)
+{
+ int ints[6], size;
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ /* check the bootstrap parameter for "dmasound=" */
+
+ /* FIXME: other than in the most naive of cases there is no sense in these
+ * buffers being other than powers of two. This is not checked yet.
+ */
+
+ switch (ints[0]) {
+ case 3:
+ if ((ints[3] < 0) || (ints[3] > MAX_CATCH_RADIUS))
+ printk("dmasound_setup: invalid catch radius, using default = %d\n", catchRadius);
+ else
+ catchRadius = ints[3];
+ /* fall through */
+ case 2:
+ if (ints[1] < MIN_BUFFERS)
+ printk("dmasound_setup: invalid number of buffers, using default = %d\n", numWriteBufs);
+ else
+ numWriteBufs = ints[1];
+ /* fall through */
+ case 1:
+ if ((size = ints[2]) < 256) /* check for small buffer specs */
+ size <<= 10 ;
+ if (size < MIN_BUFSIZE || size > MAX_BUFSIZE)
+ printk("dmasound_setup: invalid write buffer size, using default = %d\n", writeBufSize);
+ else
+ writeBufSize = size;
+ case 0:
+ break;
+ default:
+ printk("dmasound_setup: invalid number of arguments\n");
+ return 0;
+ }
+ return 1;
+}
+
+__setup("dmasound=", dmasound_setup);
+
+#endif /* !MODULE */
+
+ /*
+ * Conversion tables
+ */
+
+#ifdef HAS_8BIT_TABLES
+/* 8 bit mu-law */
+
+char dmasound_ulaw2dma8[] = {
+ -126, -122, -118, -114, -110, -106, -102, -98,
+ -94, -90, -86, -82, -78, -74, -70, -66,
+ -63, -61, -59, -57, -55, -53, -51, -49,
+ -47, -45, -43, -41, -39, -37, -35, -33,
+ -31, -30, -29, -28, -27, -26, -25, -24,
+ -23, -22, -21, -20, -19, -18, -17, -16,
+ -16, -15, -15, -14, -14, -13, -13, -12,
+ -12, -11, -11, -10, -10, -9, -9, -8,
+ -8, -8, -7, -7, -7, -7, -6, -6,
+ -6, -6, -5, -5, -5, -5, -4, -4,
+ -4, -4, -4, -4, -3, -3, -3, -3,
+ -3, -3, -3, -3, -2, -2, -2, -2,
+ -2, -2, -2, -2, -2, -2, -2, -2,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 0,
+ 125, 121, 117, 113, 109, 105, 101, 97,
+ 93, 89, 85, 81, 77, 73, 69, 65,
+ 62, 60, 58, 56, 54, 52, 50, 48,
+ 46, 44, 42, 40, 38, 36, 34, 32,
+ 30, 29, 28, 27, 26, 25, 24, 23,
+ 22, 21, 20, 19, 18, 17, 16, 15,
+ 15, 14, 14, 13, 13, 12, 12, 11,
+ 11, 10, 10, 9, 9, 8, 8, 7,
+ 7, 7, 6, 6, 6, 6, 5, 5,
+ 5, 5, 4, 4, 4, 4, 3, 3,
+ 3, 3, 3, 3, 2, 2, 2, 2,
+ 2, 2, 2, 2, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* 8 bit A-law */
+
+char dmasound_alaw2dma8[] = {
+ -22, -21, -24, -23, -18, -17, -20, -19,
+ -30, -29, -32, -31, -26, -25, -28, -27,
+ -11, -11, -12, -12, -9, -9, -10, -10,
+ -15, -15, -16, -16, -13, -13, -14, -14,
+ -86, -82, -94, -90, -70, -66, -78, -74,
+ -118, -114, -126, -122, -102, -98, -110, -106,
+ -43, -41, -47, -45, -35, -33, -39, -37,
+ -59, -57, -63, -61, -51, -49, -55, -53,
+ -2, -2, -2, -2, -2, -2, -2, -2,
+ -2, -2, -2, -2, -2, -2, -2, -2,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -6, -6, -6, -6, -5, -5, -5, -5,
+ -8, -8, -8, -8, -7, -7, -7, -7,
+ -3, -3, -3, -3, -3, -3, -3, -3,
+ -4, -4, -4, -4, -4, -4, -4, -4,
+ 21, 20, 23, 22, 17, 16, 19, 18,
+ 29, 28, 31, 30, 25, 24, 27, 26,
+ 10, 10, 11, 11, 8, 8, 9, 9,
+ 14, 14, 15, 15, 12, 12, 13, 13,
+ 86, 82, 94, 90, 70, 66, 78, 74,
+ 118, 114, 126, 122, 102, 98, 110, 106,
+ 43, 41, 47, 45, 35, 33, 39, 37,
+ 59, 57, 63, 61, 51, 49, 55, 53,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 5, 5, 5, 5, 4, 4, 4, 4,
+ 7, 7, 7, 7, 6, 6, 6, 6,
+ 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3
+};
+#endif /* HAS_8BIT_TABLES */
+
+ /*
+ * Visible symbols for modules
+ */
+
+EXPORT_SYMBOL(dmasound);
+EXPORT_SYMBOL(dmasound_init);
+#ifdef MODULE
+EXPORT_SYMBOL(dmasound_deinit);
+#endif
+EXPORT_SYMBOL(dmasound_write_sq);
+EXPORT_SYMBOL(dmasound_catchRadius);
+#ifdef HAS_8BIT_TABLES
+EXPORT_SYMBOL(dmasound_ulaw2dma8);
+EXPORT_SYMBOL(dmasound_alaw2dma8);
+#endif
diff --git a/sound/oss/dmasound/dmasound_paula.c b/sound/oss/dmasound/dmasound_paula.c
new file mode 100644
index 00000000..87910e99
--- /dev/null
+++ b/sound/oss/dmasound/dmasound_paula.c
@@ -0,0 +1,751 @@
+/*
+ * linux/sound/oss/dmasound/dmasound_paula.c
+ *
+ * Amiga `Paula' DMA Sound Driver
+ *
+ * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits
+ * prior to 28/01/2001
+ *
+ * 28/01/2001 [0.1] Iain Sandoe
+ * - added versioning
+ * - put in and populated the hardware_afmts field.
+ * [0.2] - put in SNDCTL_DSP_GETCAPS value.
+ * [0.3] - put in constraint on state buffer usage.
+ * [0.4] - put in default hard/soft settings
+*/
+
+
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/soundcard.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <asm/uaccess.h>
+#include <asm/setup.h>
+#include <asm/amigahw.h>
+#include <asm/amigaints.h>
+#include <asm/machdep.h>
+
+#include "dmasound.h"
+
+#define DMASOUND_PAULA_REVISION 0
+#define DMASOUND_PAULA_EDITION 4
+
+#define custom amiga_custom
+ /*
+ * The minimum period for audio depends on htotal (for OCS/ECS/AGA)
+ * (Imported from arch/m68k/amiga/amisound.c)
+ */
+
+extern volatile u_short amiga_audio_min_period;
+
+
+ /*
+ * amiga_mksound() should be able to restore the period after beeping
+ * (Imported from arch/m68k/amiga/amisound.c)
+ */
+
+extern u_short amiga_audio_period;
+
+
+ /*
+ * Audio DMA masks
+ */
+
+#define AMI_AUDIO_OFF (DMAF_AUD0 | DMAF_AUD1 | DMAF_AUD2 | DMAF_AUD3)
+#define AMI_AUDIO_8 (DMAF_SETCLR | DMAF_MASTER | DMAF_AUD0 | DMAF_AUD1)
+#define AMI_AUDIO_14 (AMI_AUDIO_8 | DMAF_AUD2 | DMAF_AUD3)
+
+
+ /*
+ * Helper pointers for 16(14)-bit sound
+ */
+
+static int write_sq_block_size_half, write_sq_block_size_quarter;
+
+
+/*** Low level stuff *********************************************************/
+
+
+static void *AmiAlloc(unsigned int size, gfp_t flags);
+static void AmiFree(void *obj, unsigned int size);
+static int AmiIrqInit(void);
+#ifdef MODULE
+static void AmiIrqCleanUp(void);
+#endif
+static void AmiSilence(void);
+static void AmiInit(void);
+static int AmiSetFormat(int format);
+static int AmiSetVolume(int volume);
+static int AmiSetTreble(int treble);
+static void AmiPlayNextFrame(int index);
+static void AmiPlay(void);
+static irqreturn_t AmiInterrupt(int irq, void *dummy);
+
+#ifdef CONFIG_HEARTBEAT
+
+ /*
+ * Heartbeat interferes with sound since the 7 kHz low-pass filter and the
+ * power LED are controlled by the same line.
+ */
+
+static void (*saved_heartbeat)(int) = NULL;
+
+static inline void disable_heartbeat(void)
+{
+ if (mach_heartbeat) {
+ saved_heartbeat = mach_heartbeat;
+ mach_heartbeat = NULL;
+ }
+ AmiSetTreble(dmasound.treble);
+}
+
+static inline void enable_heartbeat(void)
+{
+ if (saved_heartbeat)
+ mach_heartbeat = saved_heartbeat;
+}
+#else /* !CONFIG_HEARTBEAT */
+#define disable_heartbeat() do { } while (0)
+#define enable_heartbeat() do { } while (0)
+#endif /* !CONFIG_HEARTBEAT */
+
+
+/*** Mid level stuff *********************************************************/
+
+static void AmiMixerInit(void);
+static int AmiMixerIoctl(u_int cmd, u_long arg);
+static int AmiWriteSqSetup(void);
+static int AmiStateInfo(char *buffer, size_t space);
+
+
+/*** Translations ************************************************************/
+
+/* ++TeSche: radically changed for new expanding purposes...
+ *
+ * These two routines now deal with copying/expanding/translating the samples
+ * from user space into our buffer at the right frequency. They take care about
+ * how much data there's actually to read, how much buffer space there is and
+ * to convert samples into the right frequency/encoding. They will only work on
+ * complete samples so it may happen they leave some bytes in the input stream
+ * if the user didn't write a multiple of the current sample size. They both
+ * return the number of bytes they've used from both streams so you may detect
+ * such a situation. Luckily all programs should be able to cope with that.
+ *
+ * I think I've optimized anything as far as one can do in plain C, all
+ * variables should fit in registers and the loops are really short. There's
+ * one loop for every possible situation. Writing a more generalized and thus
+ * parameterized loop would only produce slower code. Feel free to optimize
+ * this in assembler if you like. :)
+ *
+ * I think these routines belong here because they're not yet really hardware
+ * independent, especially the fact that the Falcon can play 16bit samples
+ * only in stereo is hardcoded in both of them!
+ *
+ * ++geert: split in even more functions (one per format)
+ */
+
+
+ /*
+ * Native format
+ */
+
+static ssize_t ami_ct_s8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed, ssize_t frameLeft)
+{
+ ssize_t count, used;
+
+ if (!dmasound.soft.stereo) {
+ void *p = &frame[*frameUsed];
+ count = min_t(unsigned long, userCount, frameLeft) & ~1;
+ used = count;
+ if (copy_from_user(p, userPtr, count))
+ return -EFAULT;
+ } else {
+ u_char *left = &frame[*frameUsed>>1];
+ u_char *right = left+write_sq_block_size_half;
+ count = min_t(unsigned long, userCount, frameLeft)>>1 & ~1;
+ used = count*2;
+ while (count > 0) {
+ if (get_user(*left++, userPtr++)
+ || get_user(*right++, userPtr++))
+ return -EFAULT;
+ count--;
+ }
+ }
+ *frameUsed += used;
+ return used;
+}
+
+
+ /*
+ * Copy and convert 8 bit data
+ */
+
+#define GENERATE_AMI_CT8(funcname, convsample) \
+static ssize_t funcname(const u_char __user *userPtr, size_t userCount, \
+ u_char frame[], ssize_t *frameUsed, \
+ ssize_t frameLeft) \
+{ \
+ ssize_t count, used; \
+ \
+ if (!dmasound.soft.stereo) { \
+ u_char *p = &frame[*frameUsed]; \
+ count = min_t(size_t, userCount, frameLeft) & ~1; \
+ used = count; \
+ while (count > 0) { \
+ u_char data; \
+ if (get_user(data, userPtr++)) \
+ return -EFAULT; \
+ *p++ = convsample(data); \
+ count--; \
+ } \
+ } else { \
+ u_char *left = &frame[*frameUsed>>1]; \
+ u_char *right = left+write_sq_block_size_half; \
+ count = min_t(size_t, userCount, frameLeft)>>1 & ~1; \
+ used = count*2; \
+ while (count > 0) { \
+ u_char data; \
+ if (get_user(data, userPtr++)) \
+ return -EFAULT; \
+ *left++ = convsample(data); \
+ if (get_user(data, userPtr++)) \
+ return -EFAULT; \
+ *right++ = convsample(data); \
+ count--; \
+ } \
+ } \
+ *frameUsed += used; \
+ return used; \
+}
+
+#define AMI_CT_ULAW(x) (dmasound_ulaw2dma8[(x)])
+#define AMI_CT_ALAW(x) (dmasound_alaw2dma8[(x)])
+#define AMI_CT_U8(x) ((x) ^ 0x80)
+
+GENERATE_AMI_CT8(ami_ct_ulaw, AMI_CT_ULAW)
+GENERATE_AMI_CT8(ami_ct_alaw, AMI_CT_ALAW)
+GENERATE_AMI_CT8(ami_ct_u8, AMI_CT_U8)
+
+
+ /*
+ * Copy and convert 16 bit data
+ */
+
+#define GENERATE_AMI_CT_16(funcname, convsample) \
+static ssize_t funcname(const u_char __user *userPtr, size_t userCount, \
+ u_char frame[], ssize_t *frameUsed, \
+ ssize_t frameLeft) \
+{ \
+ const u_short __user *ptr = (const u_short __user *)userPtr; \
+ ssize_t count, used; \
+ u_short data; \
+ \
+ if (!dmasound.soft.stereo) { \
+ u_char *high = &frame[*frameUsed>>1]; \
+ u_char *low = high+write_sq_block_size_half; \
+ count = min_t(size_t, userCount, frameLeft)>>1 & ~1; \
+ used = count*2; \
+ while (count > 0) { \
+ if (get_user(data, ptr++)) \
+ return -EFAULT; \
+ data = convsample(data); \
+ *high++ = data>>8; \
+ *low++ = (data>>2) & 0x3f; \
+ count--; \
+ } \
+ } else { \
+ u_char *lefth = &frame[*frameUsed>>2]; \
+ u_char *leftl = lefth+write_sq_block_size_quarter; \
+ u_char *righth = lefth+write_sq_block_size_half; \
+ u_char *rightl = righth+write_sq_block_size_quarter; \
+ count = min_t(size_t, userCount, frameLeft)>>2 & ~1; \
+ used = count*4; \
+ while (count > 0) { \
+ if (get_user(data, ptr++)) \
+ return -EFAULT; \
+ data = convsample(data); \
+ *lefth++ = data>>8; \
+ *leftl++ = (data>>2) & 0x3f; \
+ if (get_user(data, ptr++)) \
+ return -EFAULT; \
+ data = convsample(data); \
+ *righth++ = data>>8; \
+ *rightl++ = (data>>2) & 0x3f; \
+ count--; \
+ } \
+ } \
+ *frameUsed += used; \
+ return used; \
+}
+
+#define AMI_CT_S16BE(x) (x)
+#define AMI_CT_U16BE(x) ((x) ^ 0x8000)
+#define AMI_CT_S16LE(x) (le2be16((x)))
+#define AMI_CT_U16LE(x) (le2be16((x)) ^ 0x8000)
+
+GENERATE_AMI_CT_16(ami_ct_s16be, AMI_CT_S16BE)
+GENERATE_AMI_CT_16(ami_ct_u16be, AMI_CT_U16BE)
+GENERATE_AMI_CT_16(ami_ct_s16le, AMI_CT_S16LE)
+GENERATE_AMI_CT_16(ami_ct_u16le, AMI_CT_U16LE)
+
+
+static TRANS transAmiga = {
+ .ct_ulaw = ami_ct_ulaw,
+ .ct_alaw = ami_ct_alaw,
+ .ct_s8 = ami_ct_s8,
+ .ct_u8 = ami_ct_u8,
+ .ct_s16be = ami_ct_s16be,
+ .ct_u16be = ami_ct_u16be,
+ .ct_s16le = ami_ct_s16le,
+ .ct_u16le = ami_ct_u16le,
+};
+
+/*** Low level stuff *********************************************************/
+
+static inline void StopDMA(void)
+{
+ custom.aud[0].audvol = custom.aud[1].audvol = 0;
+ custom.aud[2].audvol = custom.aud[3].audvol = 0;
+ custom.dmacon = AMI_AUDIO_OFF;
+ enable_heartbeat();
+}
+
+static void *AmiAlloc(unsigned int size, gfp_t flags)
+{
+ return amiga_chip_alloc((long)size, "dmasound [Paula]");
+}
+
+static void AmiFree(void *obj, unsigned int size)
+{
+ amiga_chip_free (obj);
+}
+
+static int __init AmiIrqInit(void)
+{
+ /* turn off DMA for audio channels */
+ StopDMA();
+
+ /* Register interrupt handler. */
+ if (request_irq(IRQ_AMIGA_AUD0, AmiInterrupt, 0, "DMA sound",
+ AmiInterrupt))
+ return 0;
+ return 1;
+}
+
+#ifdef MODULE
+static void AmiIrqCleanUp(void)
+{
+ /* turn off DMA for audio channels */
+ StopDMA();
+ /* release the interrupt */
+ free_irq(IRQ_AMIGA_AUD0, AmiInterrupt);
+}
+#endif /* MODULE */
+
+static void AmiSilence(void)
+{
+ /* turn off DMA for audio channels */
+ StopDMA();
+}
+
+
+static void AmiInit(void)
+{
+ int period, i;
+
+ AmiSilence();
+
+ if (dmasound.soft.speed)
+ period = amiga_colorclock/dmasound.soft.speed-1;
+ else
+ period = amiga_audio_min_period;
+ dmasound.hard = dmasound.soft;
+ dmasound.trans_write = &transAmiga;
+
+ if (period < amiga_audio_min_period) {
+ /* we would need to squeeze the sound, but we won't do that */
+ period = amiga_audio_min_period;
+ } else if (period > 65535) {
+ period = 65535;
+ }
+ dmasound.hard.speed = amiga_colorclock/(period+1);
+
+ for (i = 0; i < 4; i++)
+ custom.aud[i].audper = period;
+ amiga_audio_period = period;
+}
+
+
+static int AmiSetFormat(int format)
+{
+ int size;
+
+ /* Amiga sound DMA supports 8bit and 16bit (pseudo 14 bit) modes */
+
+ switch (format) {
+ case AFMT_QUERY:
+ return dmasound.soft.format;
+ case AFMT_MU_LAW:
+ case AFMT_A_LAW:
+ case AFMT_U8:
+ case AFMT_S8:
+ size = 8;
+ break;
+ case AFMT_S16_BE:
+ case AFMT_U16_BE:
+ case AFMT_S16_LE:
+ case AFMT_U16_LE:
+ size = 16;
+ break;
+ default: /* :-) */
+ size = 8;
+ format = AFMT_S8;
+ }
+
+ dmasound.soft.format = format;
+ dmasound.soft.size = size;
+ if (dmasound.minDev == SND_DEV_DSP) {
+ dmasound.dsp.format = format;
+ dmasound.dsp.size = dmasound.soft.size;
+ }
+ AmiInit();
+
+ return format;
+}
+
+
+#define VOLUME_VOXWARE_TO_AMI(v) \
+ (((v) < 0) ? 0 : ((v) > 100) ? 64 : ((v) * 64)/100)
+#define VOLUME_AMI_TO_VOXWARE(v) ((v)*100/64)
+
+static int AmiSetVolume(int volume)
+{
+ dmasound.volume_left = VOLUME_VOXWARE_TO_AMI(volume & 0xff);
+ custom.aud[0].audvol = dmasound.volume_left;
+ dmasound.volume_right = VOLUME_VOXWARE_TO_AMI((volume & 0xff00) >> 8);
+ custom.aud[1].audvol = dmasound.volume_right;
+ if (dmasound.hard.size == 16) {
+ if (dmasound.volume_left == 64 && dmasound.volume_right == 64) {
+ custom.aud[2].audvol = 1;
+ custom.aud[3].audvol = 1;
+ } else {
+ custom.aud[2].audvol = 0;
+ custom.aud[3].audvol = 0;
+ }
+ }
+ return VOLUME_AMI_TO_VOXWARE(dmasound.volume_left) |
+ (VOLUME_AMI_TO_VOXWARE(dmasound.volume_right) << 8);
+}
+
+static int AmiSetTreble(int treble)
+{
+ dmasound.treble = treble;
+ if (treble < 50)
+ ciaa.pra &= ~0x02;
+ else
+ ciaa.pra |= 0x02;
+ return treble;
+}
+
+
+#define AMI_PLAY_LOADED 1
+#define AMI_PLAY_PLAYING 2
+#define AMI_PLAY_MASK 3
+
+
+static void AmiPlayNextFrame(int index)
+{
+ u_char *start, *ch0, *ch1, *ch2, *ch3;
+ u_long size;
+
+ /* used by AmiPlay() if all doubts whether there really is something
+ * to be played are already wiped out.
+ */
+ start = write_sq.buffers[write_sq.front];
+ size = (write_sq.count == index ? write_sq.rear_size
+ : write_sq.block_size)>>1;
+
+ if (dmasound.hard.stereo) {
+ ch0 = start;
+ ch1 = start+write_sq_block_size_half;
+ size >>= 1;
+ } else {
+ ch0 = start;
+ ch1 = start;
+ }
+
+ disable_heartbeat();
+ custom.aud[0].audvol = dmasound.volume_left;
+ custom.aud[1].audvol = dmasound.volume_right;
+ if (dmasound.hard.size == 8) {
+ custom.aud[0].audlc = (u_short *)ZTWO_PADDR(ch0);
+ custom.aud[0].audlen = size;
+ custom.aud[1].audlc = (u_short *)ZTWO_PADDR(ch1);
+ custom.aud[1].audlen = size;
+ custom.dmacon = AMI_AUDIO_8;
+ } else {
+ size >>= 1;
+ custom.aud[0].audlc = (u_short *)ZTWO_PADDR(ch0);
+ custom.aud[0].audlen = size;
+ custom.aud[1].audlc = (u_short *)ZTWO_PADDR(ch1);
+ custom.aud[1].audlen = size;
+ if (dmasound.volume_left == 64 && dmasound.volume_right == 64) {
+ /* We can play pseudo 14-bit only with the maximum volume */
+ ch3 = ch0+write_sq_block_size_quarter;
+ ch2 = ch1+write_sq_block_size_quarter;
+ custom.aud[2].audvol = 1; /* we are being affected by the beeps */
+ custom.aud[3].audvol = 1; /* restoring volume here helps a bit */
+ custom.aud[2].audlc = (u_short *)ZTWO_PADDR(ch2);
+ custom.aud[2].audlen = size;
+ custom.aud[3].audlc = (u_short *)ZTWO_PADDR(ch3);
+ custom.aud[3].audlen = size;
+ custom.dmacon = AMI_AUDIO_14;
+ } else {
+ custom.aud[2].audvol = 0;
+ custom.aud[3].audvol = 0;
+ custom.dmacon = AMI_AUDIO_8;
+ }
+ }
+ write_sq.front = (write_sq.front+1) % write_sq.max_count;
+ write_sq.active |= AMI_PLAY_LOADED;
+}
+
+
+static void AmiPlay(void)
+{
+ int minframes = 1;
+
+ custom.intena = IF_AUD0;
+
+ if (write_sq.active & AMI_PLAY_LOADED) {
+ /* There's already a frame loaded */
+ custom.intena = IF_SETCLR | IF_AUD0;
+ return;
+ }
+
+ if (write_sq.active & AMI_PLAY_PLAYING)
+ /* Increase threshold: frame 1 is already being played */
+ minframes = 2;
+
+ if (write_sq.count < minframes) {
+ /* Nothing to do */
+ custom.intena = IF_SETCLR | IF_AUD0;
+ return;
+ }
+
+ if (write_sq.count <= minframes &&
+ write_sq.rear_size < write_sq.block_size && !write_sq.syncing) {
+ /* hmmm, the only existing frame is not
+ * yet filled and we're not syncing?
+ */
+ custom.intena = IF_SETCLR | IF_AUD0;
+ return;
+ }
+
+ AmiPlayNextFrame(minframes);
+
+ custom.intena = IF_SETCLR | IF_AUD0;
+}
+
+
+static irqreturn_t AmiInterrupt(int irq, void *dummy)
+{
+ int minframes = 1;
+
+ custom.intena = IF_AUD0;
+
+ if (!write_sq.active) {
+ /* Playing was interrupted and sq_reset() has already cleared
+ * the sq variables, so better don't do anything here.
+ */
+ WAKE_UP(write_sq.sync_queue);
+ return IRQ_HANDLED;
+ }
+
+ if (write_sq.active & AMI_PLAY_PLAYING) {
+ /* We've just finished a frame */
+ write_sq.count--;
+ WAKE_UP(write_sq.action_queue);
+ }
+
+ if (write_sq.active & AMI_PLAY_LOADED)
+ /* Increase threshold: frame 1 is already being played */
+ minframes = 2;
+
+ /* Shift the flags */
+ write_sq.active = (write_sq.active<<1) & AMI_PLAY_MASK;
+
+ if (!write_sq.active)
+ /* No frame is playing, disable audio DMA */
+ StopDMA();
+
+ custom.intena = IF_SETCLR | IF_AUD0;
+
+ if (write_sq.count >= minframes)
+ /* Try to play the next frame */
+ AmiPlay();
+
+ if (!write_sq.active)
+ /* Nothing to play anymore.
+ Wake up a process waiting for audio output to drain. */
+ WAKE_UP(write_sq.sync_queue);
+ return IRQ_HANDLED;
+}
+
+/*** Mid level stuff *********************************************************/
+
+
+/*
+ * /dev/mixer abstraction
+ */
+
+static void __init AmiMixerInit(void)
+{
+ dmasound.volume_left = 64;
+ dmasound.volume_right = 64;
+ custom.aud[0].audvol = dmasound.volume_left;
+ custom.aud[3].audvol = 1; /* For pseudo 14bit */
+ custom.aud[1].audvol = dmasound.volume_right;
+ custom.aud[2].audvol = 1; /* For pseudo 14bit */
+ dmasound.treble = 50;
+}
+
+static int AmiMixerIoctl(u_int cmd, u_long arg)
+{
+ int data;
+ switch (cmd) {
+ case SOUND_MIXER_READ_DEVMASK:
+ return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_TREBLE);
+ case SOUND_MIXER_READ_RECMASK:
+ return IOCTL_OUT(arg, 0);
+ case SOUND_MIXER_READ_STEREODEVS:
+ return IOCTL_OUT(arg, SOUND_MASK_VOLUME);
+ case SOUND_MIXER_READ_VOLUME:
+ return IOCTL_OUT(arg,
+ VOLUME_AMI_TO_VOXWARE(dmasound.volume_left) |
+ VOLUME_AMI_TO_VOXWARE(dmasound.volume_right) << 8);
+ case SOUND_MIXER_WRITE_VOLUME:
+ IOCTL_IN(arg, data);
+ return IOCTL_OUT(arg, dmasound_set_volume(data));
+ case SOUND_MIXER_READ_TREBLE:
+ return IOCTL_OUT(arg, dmasound.treble);
+ case SOUND_MIXER_WRITE_TREBLE:
+ IOCTL_IN(arg, data);
+ return IOCTL_OUT(arg, dmasound_set_treble(data));
+ }
+ return -EINVAL;
+}
+
+
+static int AmiWriteSqSetup(void)
+{
+ write_sq_block_size_half = write_sq.block_size>>1;
+ write_sq_block_size_quarter = write_sq_block_size_half>>1;
+ return 0;
+}
+
+
+static int AmiStateInfo(char *buffer, size_t space)
+{
+ int len = 0;
+ len += sprintf(buffer+len, "\tsound.volume_left = %d [0...64]\n",
+ dmasound.volume_left);
+ len += sprintf(buffer+len, "\tsound.volume_right = %d [0...64]\n",
+ dmasound.volume_right);
+ if (len >= space) {
+ printk(KERN_ERR "dmasound_paula: overflowed state buffer alloc.\n") ;
+ len = space ;
+ }
+ return len;
+}
+
+
+/*** Machine definitions *****************************************************/
+
+static SETTINGS def_hard = {
+ .format = AFMT_S8,
+ .stereo = 0,
+ .size = 8,
+ .speed = 8000
+} ;
+
+static SETTINGS def_soft = {
+ .format = AFMT_U8,
+ .stereo = 0,
+ .size = 8,
+ .speed = 8000
+} ;
+
+static MACHINE machAmiga = {
+ .name = "Amiga",
+ .name2 = "AMIGA",
+ .owner = THIS_MODULE,
+ .dma_alloc = AmiAlloc,
+ .dma_free = AmiFree,
+ .irqinit = AmiIrqInit,
+#ifdef MODULE
+ .irqcleanup = AmiIrqCleanUp,
+#endif /* MODULE */
+ .init = AmiInit,
+ .silence = AmiSilence,
+ .setFormat = AmiSetFormat,
+ .setVolume = AmiSetVolume,
+ .setTreble = AmiSetTreble,
+ .play = AmiPlay,
+ .mixer_init = AmiMixerInit,
+ .mixer_ioctl = AmiMixerIoctl,
+ .write_sq_setup = AmiWriteSqSetup,
+ .state_info = AmiStateInfo,
+ .min_dsp_speed = 8000,
+ .version = ((DMASOUND_PAULA_REVISION<<8) | DMASOUND_PAULA_EDITION),
+ .hardware_afmts = (AFMT_S8 | AFMT_S16_BE), /* h'ware-supported formats *only* here */
+ .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */
+};
+
+
+/*** Config & Setup **********************************************************/
+
+
+static int __init amiga_audio_probe(struct platform_device *pdev)
+{
+ dmasound.mach = machAmiga;
+ dmasound.mach.default_hard = def_hard ;
+ dmasound.mach.default_soft = def_soft ;
+ return dmasound_init();
+}
+
+static int __exit amiga_audio_remove(struct platform_device *pdev)
+{
+ dmasound_deinit();
+ return 0;
+}
+
+static struct platform_driver amiga_audio_driver = {
+ .remove = __exit_p(amiga_audio_remove),
+ .driver = {
+ .name = "amiga-audio",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init amiga_audio_init(void)
+{
+ return platform_driver_probe(&amiga_audio_driver, amiga_audio_probe);
+}
+
+module_init(amiga_audio_init);
+
+static void __exit amiga_audio_exit(void)
+{
+ platform_driver_unregister(&amiga_audio_driver);
+}
+
+module_exit(amiga_audio_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:amiga-audio");
diff --git a/sound/oss/dmasound/dmasound_q40.c b/sound/oss/dmasound/dmasound_q40.c
new file mode 100644
index 00000000..99bcb21c
--- /dev/null
+++ b/sound/oss/dmasound/dmasound_q40.c
@@ -0,0 +1,638 @@
+/*
+ * linux/sound/oss/dmasound/dmasound_q40.c
+ *
+ * Q40 DMA Sound Driver
+ *
+ * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits
+ * prior to 28/01/2001
+ *
+ * 28/01/2001 [0.1] Iain Sandoe
+ * - added versioning
+ * - put in and populated the hardware_afmts field.
+ * [0.2] - put in SNDCTL_DSP_GETCAPS value.
+ * [0.3] - put in default hard/soft settings.
+ */
+
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/soundcard.h>
+#include <linux/interrupt.h>
+
+#include <asm/uaccess.h>
+#include <asm/q40ints.h>
+#include <asm/q40_master.h>
+
+#include "dmasound.h"
+
+#define DMASOUND_Q40_REVISION 0
+#define DMASOUND_Q40_EDITION 3
+
+static int expand_bal; /* Balance factor for expanding (not volume!) */
+static int expand_data; /* Data for expanding */
+
+
+/*** Low level stuff *********************************************************/
+
+
+static void *Q40Alloc(unsigned int size, gfp_t flags);
+static void Q40Free(void *, unsigned int);
+static int Q40IrqInit(void);
+#ifdef MODULE
+static void Q40IrqCleanUp(void);
+#endif
+static void Q40Silence(void);
+static void Q40Init(void);
+static int Q40SetFormat(int format);
+static int Q40SetVolume(int volume);
+static void Q40PlayNextFrame(int index);
+static void Q40Play(void);
+static irqreturn_t Q40StereoInterrupt(int irq, void *dummy);
+static irqreturn_t Q40MonoInterrupt(int irq, void *dummy);
+static void Q40Interrupt(void);
+
+
+/*** Mid level stuff *********************************************************/
+
+
+
+/* userCount, frameUsed, frameLeft == byte counts */
+static ssize_t q40_ct_law(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8;
+ ssize_t count, used;
+ u_char *p = (u_char *) &frame[*frameUsed];
+
+ used = count = min_t(size_t, userCount, frameLeft);
+ if (copy_from_user(p,userPtr,count))
+ return -EFAULT;
+ while (count > 0) {
+ *p = table[*p]+128;
+ p++;
+ count--;
+ }
+ *frameUsed += used ;
+ return used;
+}
+
+
+static ssize_t q40_ct_s8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ ssize_t count, used;
+ u_char *p = (u_char *) &frame[*frameUsed];
+
+ used = count = min_t(size_t, userCount, frameLeft);
+ if (copy_from_user(p,userPtr,count))
+ return -EFAULT;
+ while (count > 0) {
+ *p = *p + 128;
+ p++;
+ count--;
+ }
+ *frameUsed += used;
+ return used;
+}
+
+static ssize_t q40_ct_u8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ ssize_t count, used;
+ u_char *p = (u_char *) &frame[*frameUsed];
+
+ used = count = min_t(size_t, userCount, frameLeft);
+ if (copy_from_user(p,userPtr,count))
+ return -EFAULT;
+ *frameUsed += used;
+ return used;
+}
+
+
+/* a bit too complicated to optimise right now ..*/
+static ssize_t q40_ctx_law(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ unsigned char *table = (unsigned char *)
+ (dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8);
+ unsigned int data = expand_data;
+ u_char *p = (u_char *) &frame[*frameUsed];
+ int bal = expand_bal;
+ int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ int utotal, ftotal;
+
+ ftotal = frameLeft;
+ utotal = userCount;
+ while (frameLeft) {
+ u_char c;
+ if (bal < 0) {
+ if (userCount == 0)
+ break;
+ if (get_user(c, userPtr++))
+ return -EFAULT;
+ data = table[c];
+ data += 0x80;
+ userCount--;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft--;
+ bal -= sSpeed;
+ }
+ expand_bal = bal;
+ expand_data = data;
+ *frameUsed += (ftotal - frameLeft);
+ utotal -= userCount;
+ return utotal;
+}
+
+
+static ssize_t q40_ctx_s8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ u_char *p = (u_char *) &frame[*frameUsed];
+ unsigned int data = expand_data;
+ int bal = expand_bal;
+ int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ int utotal, ftotal;
+
+
+ ftotal = frameLeft;
+ utotal = userCount;
+ while (frameLeft) {
+ u_char c;
+ if (bal < 0) {
+ if (userCount == 0)
+ break;
+ if (get_user(c, userPtr++))
+ return -EFAULT;
+ data = c ;
+ data += 0x80;
+ userCount--;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft--;
+ bal -= sSpeed;
+ }
+ expand_bal = bal;
+ expand_data = data;
+ *frameUsed += (ftotal - frameLeft);
+ utotal -= userCount;
+ return utotal;
+}
+
+
+static ssize_t q40_ctx_u8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ u_char *p = (u_char *) &frame[*frameUsed];
+ unsigned int data = expand_data;
+ int bal = expand_bal;
+ int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ int utotal, ftotal;
+
+ ftotal = frameLeft;
+ utotal = userCount;
+ while (frameLeft) {
+ u_char c;
+ if (bal < 0) {
+ if (userCount == 0)
+ break;
+ if (get_user(c, userPtr++))
+ return -EFAULT;
+ data = c ;
+ userCount--;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft--;
+ bal -= sSpeed;
+ }
+ expand_bal = bal;
+ expand_data = data;
+ *frameUsed += (ftotal - frameLeft) ;
+ utotal -= userCount;
+ return utotal;
+}
+
+/* compressing versions */
+static ssize_t q40_ctc_law(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ unsigned char *table = (unsigned char *)
+ (dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8);
+ unsigned int data = expand_data;
+ u_char *p = (u_char *) &frame[*frameUsed];
+ int bal = expand_bal;
+ int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ int utotal, ftotal;
+
+ ftotal = frameLeft;
+ utotal = userCount;
+ while (frameLeft) {
+ u_char c;
+ while(bal<0) {
+ if (userCount == 0)
+ goto lout;
+ if (!(bal<(-hSpeed))) {
+ if (get_user(c, userPtr))
+ return -EFAULT;
+ data = 0x80 + table[c];
+ }
+ userPtr++;
+ userCount--;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft--;
+ bal -= sSpeed;
+ }
+ lout:
+ expand_bal = bal;
+ expand_data = data;
+ *frameUsed += (ftotal - frameLeft);
+ utotal -= userCount;
+ return utotal;
+}
+
+
+static ssize_t q40_ctc_s8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ u_char *p = (u_char *) &frame[*frameUsed];
+ unsigned int data = expand_data;
+ int bal = expand_bal;
+ int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ int utotal, ftotal;
+
+ ftotal = frameLeft;
+ utotal = userCount;
+ while (frameLeft) {
+ u_char c;
+ while (bal < 0) {
+ if (userCount == 0)
+ goto lout;
+ if (!(bal<(-hSpeed))) {
+ if (get_user(c, userPtr))
+ return -EFAULT;
+ data = c + 0x80;
+ }
+ userPtr++;
+ userCount--;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft--;
+ bal -= sSpeed;
+ }
+ lout:
+ expand_bal = bal;
+ expand_data = data;
+ *frameUsed += (ftotal - frameLeft);
+ utotal -= userCount;
+ return utotal;
+}
+
+
+static ssize_t q40_ctc_u8(const u_char __user *userPtr, size_t userCount,
+ u_char frame[], ssize_t *frameUsed,
+ ssize_t frameLeft)
+{
+ u_char *p = (u_char *) &frame[*frameUsed];
+ unsigned int data = expand_data;
+ int bal = expand_bal;
+ int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+ int utotal, ftotal;
+
+ ftotal = frameLeft;
+ utotal = userCount;
+ while (frameLeft) {
+ u_char c;
+ while (bal < 0) {
+ if (userCount == 0)
+ goto lout;
+ if (!(bal<(-hSpeed))) {
+ if (get_user(c, userPtr))
+ return -EFAULT;
+ data = c ;
+ }
+ userPtr++;
+ userCount--;
+ bal += hSpeed;
+ }
+ *p++ = data;
+ frameLeft--;
+ bal -= sSpeed;
+ }
+ lout:
+ expand_bal = bal;
+ expand_data = data;
+ *frameUsed += (ftotal - frameLeft) ;
+ utotal -= userCount;
+ return utotal;
+}
+
+
+static TRANS transQ40Normal = {
+ q40_ct_law, q40_ct_law, q40_ct_s8, q40_ct_u8, NULL, NULL, NULL, NULL
+};
+
+static TRANS transQ40Expanding = {
+ q40_ctx_law, q40_ctx_law, q40_ctx_s8, q40_ctx_u8, NULL, NULL, NULL, NULL
+};
+
+static TRANS transQ40Compressing = {
+ q40_ctc_law, q40_ctc_law, q40_ctc_s8, q40_ctc_u8, NULL, NULL, NULL, NULL
+};
+
+
+/*** Low level stuff *********************************************************/
+
+static void *Q40Alloc(unsigned int size, gfp_t flags)
+{
+ return kmalloc(size, flags); /* change to vmalloc */
+}
+
+static void Q40Free(void *ptr, unsigned int size)
+{
+ kfree(ptr);
+}
+
+static int __init Q40IrqInit(void)
+{
+ /* Register interrupt handler. */
+ if (request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,
+ "DMA sound", Q40Interrupt))
+ return 0;
+
+ return(1);
+}
+
+
+#ifdef MODULE
+static void Q40IrqCleanUp(void)
+{
+ master_outb(0,SAMPLE_ENABLE_REG);
+ free_irq(Q40_IRQ_SAMPLE, Q40Interrupt);
+}
+#endif /* MODULE */
+
+
+static void Q40Silence(void)
+{
+ master_outb(0,SAMPLE_ENABLE_REG);
+ *DAC_LEFT=*DAC_RIGHT=127;
+}
+
+static char *q40_pp;
+static unsigned int q40_sc;
+
+static void Q40PlayNextFrame(int index)
+{
+ u_char *start;
+ u_long size;
+ u_char speed;
+ int error;
+
+ /* used by Q40Play() if all doubts whether there really is something
+ * to be played are already wiped out.
+ */
+ start = write_sq.buffers[write_sq.front];
+ size = (write_sq.count == index ? write_sq.rear_size : write_sq.block_size);
+
+ q40_pp=start;
+ q40_sc=size;
+
+ write_sq.front = (write_sq.front+1) % write_sq.max_count;
+ write_sq.active++;
+
+ speed=(dmasound.hard.speed==10000 ? 0 : 1);
+
+ master_outb( 0,SAMPLE_ENABLE_REG);
+ free_irq(Q40_IRQ_SAMPLE, Q40Interrupt);
+ if (dmasound.soft.stereo)
+ error = request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,
+ "Q40 sound", Q40Interrupt);
+ else
+ error = request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0,
+ "Q40 sound", Q40Interrupt);
+ if (error && printk_ratelimit())
+ pr_err("Couldn't register sound interrupt\n");
+
+ master_outb( speed, SAMPLE_RATE_REG);
+ master_outb( 1,SAMPLE_CLEAR_REG);
+ master_outb( 1,SAMPLE_ENABLE_REG);
+}
+
+static void Q40Play(void)
+{
+ unsigned long flags;
+
+ if (write_sq.active || write_sq.count<=0 ) {
+ /* There's already a frame loaded */
+ return;
+ }
+
+ /* nothing in the queue */
+ if (write_sq.count <= 1 && write_sq.rear_size < write_sq.block_size && !write_sq.syncing) {
+ /* hmmm, the only existing frame is not
+ * yet filled and we're not syncing?
+ */
+ return;
+ }
+ spin_lock_irqsave(&dmasound.lock, flags);
+ Q40PlayNextFrame(1);
+ spin_unlock_irqrestore(&dmasound.lock, flags);
+}
+
+static irqreturn_t Q40StereoInterrupt(int irq, void *dummy)
+{
+ spin_lock(&dmasound.lock);
+ if (q40_sc>1){
+ *DAC_LEFT=*q40_pp++;
+ *DAC_RIGHT=*q40_pp++;
+ q40_sc -=2;
+ master_outb(1,SAMPLE_CLEAR_REG);
+ }else Q40Interrupt();
+ spin_unlock(&dmasound.lock);
+ return IRQ_HANDLED;
+}
+static irqreturn_t Q40MonoInterrupt(int irq, void *dummy)
+{
+ spin_lock(&dmasound.lock);
+ if (q40_sc>0){
+ *DAC_LEFT=*q40_pp;
+ *DAC_RIGHT=*q40_pp++;
+ q40_sc --;
+ master_outb(1,SAMPLE_CLEAR_REG);
+ }else Q40Interrupt();
+ spin_unlock(&dmasound.lock);
+ return IRQ_HANDLED;
+}
+static void Q40Interrupt(void)
+{
+ if (!write_sq.active) {
+ /* playing was interrupted and sq_reset() has already cleared
+ * the sq variables, so better don't do anything here.
+ */
+ WAKE_UP(write_sq.sync_queue);
+ master_outb(0,SAMPLE_ENABLE_REG); /* better safe */
+ goto exit;
+ } else write_sq.active=0;
+ write_sq.count--;
+ Q40Play();
+
+ if (q40_sc<2)
+ { /* there was nothing to play, disable irq */
+ master_outb(0,SAMPLE_ENABLE_REG);
+ *DAC_LEFT=*DAC_RIGHT=127;
+ }
+ WAKE_UP(write_sq.action_queue);
+
+ exit:
+ master_outb(1,SAMPLE_CLEAR_REG);
+}
+
+
+static void Q40Init(void)
+{
+ int i, idx;
+ const int freq[] = {10000, 20000};
+
+ /* search a frequency that fits into the allowed error range */
+
+ idx = -1;
+ for (i = 0; i < 2; i++)
+ if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) <= catchRadius)
+ idx = i;
+
+ dmasound.hard = dmasound.soft;
+ /*sound.hard.stereo=1;*/ /* no longer true */
+ dmasound.hard.size=8;
+
+ if (idx > -1) {
+ dmasound.soft.speed = freq[idx];
+ dmasound.trans_write = &transQ40Normal;
+ } else
+ dmasound.trans_write = &transQ40Expanding;
+
+ Q40Silence();
+
+ if (dmasound.hard.speed > 20200) {
+ /* squeeze the sound, we do that */
+ dmasound.hard.speed = 20000;
+ dmasound.trans_write = &transQ40Compressing;
+ } else if (dmasound.hard.speed > 10000) {
+ dmasound.hard.speed = 20000;
+ } else {
+ dmasound.hard.speed = 10000;
+ }
+ expand_bal = -dmasound.soft.speed;
+}
+
+
+static int Q40SetFormat(int format)
+{
+ /* Q40 sound supports only 8bit modes */
+
+ switch (format) {
+ case AFMT_QUERY:
+ return(dmasound.soft.format);
+ case AFMT_MU_LAW:
+ case AFMT_A_LAW:
+ case AFMT_S8:
+ case AFMT_U8:
+ break;
+ default:
+ format = AFMT_S8;
+ }
+
+ dmasound.soft.format = format;
+ dmasound.soft.size = 8;
+ if (dmasound.minDev == SND_DEV_DSP) {
+ dmasound.dsp.format = format;
+ dmasound.dsp.size = 8;
+ }
+ Q40Init();
+
+ return(format);
+}
+
+static int Q40SetVolume(int volume)
+{
+ return 0;
+}
+
+
+/*** Machine definitions *****************************************************/
+
+static SETTINGS def_hard = {
+ .format = AFMT_U8,
+ .stereo = 0,
+ .size = 8,
+ .speed = 10000
+} ;
+
+static SETTINGS def_soft = {
+ .format = AFMT_U8,
+ .stereo = 0,
+ .size = 8,
+ .speed = 8000
+} ;
+
+static MACHINE machQ40 = {
+ .name = "Q40",
+ .name2 = "Q40",
+ .owner = THIS_MODULE,
+ .dma_alloc = Q40Alloc,
+ .dma_free = Q40Free,
+ .irqinit = Q40IrqInit,
+#ifdef MODULE
+ .irqcleanup = Q40IrqCleanUp,
+#endif /* MODULE */
+ .init = Q40Init,
+ .silence = Q40Silence,
+ .setFormat = Q40SetFormat,
+ .setVolume = Q40SetVolume,
+ .play = Q40Play,
+ .min_dsp_speed = 10000,
+ .version = ((DMASOUND_Q40_REVISION<<8) | DMASOUND_Q40_EDITION),
+ .hardware_afmts = AFMT_U8, /* h'ware-supported formats *only* here */
+ .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */
+};
+
+
+/*** Config & Setup **********************************************************/
+
+
+static int __init dmasound_q40_init(void)
+{
+ if (MACH_IS_Q40) {
+ dmasound.mach = machQ40;
+ dmasound.mach.default_hard = def_hard ;
+ dmasound.mach.default_soft = def_soft ;
+ return dmasound_init();
+ } else
+ return -ENODEV;
+}
+
+static void __exit dmasound_q40_cleanup(void)
+{
+ dmasound_deinit();
+}
+
+module_init(dmasound_q40_init);
+module_exit(dmasound_q40_cleanup);
+
+MODULE_DESCRIPTION("Q40/Q60 sound driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/hex2hex.c b/sound/oss/hex2hex.c
new file mode 100644
index 00000000..041ef5c5
--- /dev/null
+++ b/sound/oss/hex2hex.c
@@ -0,0 +1,101 @@
+/*
+ * hex2hex reads stdin in Intel HEX format and produces an
+ * (unsigned char) array which contains the bytes and writes it
+ * to stdout using C syntax
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define ABANDON(why) { fprintf(stderr, "%s\n", why); exit(1); }
+#define MAX_SIZE (256*1024)
+unsigned char buf[MAX_SIZE];
+
+static int loadhex(FILE *inf, unsigned char *buf)
+{
+ int l=0, c, i;
+
+ while ((c=getc(inf))!=EOF)
+ {
+ if (c == ':') /* Sync with beginning of line */
+ {
+ int n, check;
+ unsigned char sum;
+ int addr;
+ int linetype;
+
+ if (fscanf(inf, "%02x", &n) != 1)
+ ABANDON("File format error");
+ sum = n;
+
+ if (fscanf(inf, "%04x", &addr) != 1)
+ ABANDON("File format error");
+ sum += addr/256;
+ sum += addr%256;
+
+ if (fscanf(inf, "%02x", &linetype) != 1)
+ ABANDON("File format error");
+ sum += linetype;
+
+ if (linetype != 0)
+ continue;
+
+ for (i=0;i<n;i++)
+ {
+ if (fscanf(inf, "%02x", &c) != 1)
+ ABANDON("File format error");
+ if (addr >= MAX_SIZE)
+ ABANDON("File too large");
+ buf[addr++] = c;
+ if (addr > l)
+ l = addr;
+ sum += c;
+ }
+
+ if (fscanf(inf, "%02x", &check) != 1)
+ ABANDON("File format error");
+
+ sum = ~sum + 1;
+ if (check != sum)
+ ABANDON("Line checksum error");
+ }
+ }
+
+ return l;
+}
+
+int main( int argc, const char * argv [] )
+{
+ const char * varline;
+ int i,l;
+ int id=0;
+
+ if(argv[1] && strcmp(argv[1], "-i")==0)
+ {
+ argv++;
+ argc--;
+ id=1;
+ }
+ if(argv[1]==NULL)
+ {
+ fprintf(stderr,"hex2hex: [-i] filename\n");
+ exit(1);
+ }
+ varline = argv[1];
+ l = loadhex(stdin, buf);
+
+ printf("/*\n *\t Computer generated file. Do not edit.\n */\n");
+ printf("static int %s_len = %d;\n", varline, l);
+ printf("static unsigned char %s[] %s = {\n", varline, id?"__initdata":"");
+
+ for (i=0;i<l;i++)
+ {
+ if (i) printf(",");
+ if (i && !(i % 16)) printf("\n");
+ printf("0x%02x", buf[i]);
+ }
+
+ printf("\n};\n\n");
+ return 0;
+}
diff --git a/sound/oss/kahlua.c b/sound/oss/kahlua.c
new file mode 100644
index 00000000..52d06a33
--- /dev/null
+++ b/sound/oss/kahlua.c
@@ -0,0 +1,231 @@
+/*
+ * Initialisation code for Cyrix/NatSemi VSA1 softaudio
+ *
+ * (C) Copyright 2003 Red Hat Inc <alan@lxorguk.ukuu.org.uk>
+ *
+ * XpressAudio(tm) is used on the Cyrix MediaGX (now NatSemi Geode) systems.
+ * The older version (VSA1) provides fairly good soundblaster emulation
+ * although there are a couple of bugs: large DMA buffers break record,
+ * and the MPU event handling seems suspect. VSA2 allows the native driver
+ * to control the AC97 audio engine directly and requires a different driver.
+ *
+ * Thanks to National Semiconductor for providing the needed information
+ * on the XpressAudio(tm) internals.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * TO DO:
+ * Investigate whether we can portably support Cognac (5520) in the
+ * same manner.
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include "sound_config.h"
+
+#include "sb.h"
+
+/*
+ * Read a soundblaster compatible mixer register.
+ * In this case we are actually reading an SMI trap
+ * not real hardware.
+ */
+
+static u8 __devinit mixer_read(unsigned long io, u8 reg)
+{
+ outb(reg, io + 4);
+ udelay(20);
+ reg = inb(io + 5);
+ udelay(20);
+ return reg;
+}
+
+static int __devinit probe_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ struct address_info *hw_config;
+ unsigned long base;
+ void __iomem *mem;
+ unsigned long io;
+ u16 map;
+ u8 irq, dma8, dma16;
+ int oldquiet;
+ extern int sb_be_quiet;
+
+ base = pci_resource_start(pdev, 0);
+ if(base == 0UL)
+ return 1;
+
+ mem = ioremap(base, 128);
+ if (!mem)
+ return 1;
+ map = readw(mem + 0x18); /* Read the SMI enables */
+ 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 */
+
+ io = 0x220 + 0x20 * (map & 3);
+
+ if(map & (1<<2))
+ printk(KERN_INFO "kahlua: XpressAudio at 0x%lx\n", io);
+ else
+ return 1;
+
+ if(map & (1<<5))
+ printk(KERN_INFO "kahlua: MPU at 0x300\n");
+ else if(map & (1<<6))
+ printk(KERN_INFO "kahlua: MPU at 0x330\n");
+
+ irq = mixer_read(io, 0x80) & 0x0F;
+ dma8 = mixer_read(io, 0x81);
+
+ // printk("IRQ=%x MAP=%x DMA=%x\n", irq, map, dma8);
+
+ if(dma8 & 0x20)
+ dma16 = 5;
+ else if(dma8 & 0x40)
+ dma16 = 6;
+ else if(dma8 & 0x80)
+ dma16 = 7;
+ else
+ {
+ printk(KERN_ERR "kahlua: No 16bit DMA enabled.\n");
+ return 1;
+ }
+
+ if(dma8 & 0x01)
+ dma8 = 0;
+ else if(dma8 & 0x02)
+ dma8 = 1;
+ else if(dma8 & 0x08)
+ dma8 = 3;
+ else
+ {
+ printk(KERN_ERR "kahlua: No 8bit DMA enabled.\n");
+ return 1;
+ }
+
+ 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 "kahlua: SB IRQ not set.\n");
+ return 1;
+ }
+
+ printk(KERN_INFO "kahlua: XpressAudio on IRQ %d, DMA %d, %d\n",
+ irq, dma8, dma16);
+
+ hw_config = kzalloc(sizeof(struct address_info), GFP_KERNEL);
+ if(hw_config == NULL)
+ {
+ printk(KERN_ERR "kahlua: out of memory.\n");
+ return 1;
+ }
+
+ pci_set_drvdata(pdev, hw_config);
+
+ hw_config->io_base = io;
+ hw_config->irq = irq;
+ hw_config->dma = dma8;
+ hw_config->dma2 = dma16;
+ hw_config->name = "Cyrix XpressAudio";
+ hw_config->driver_use_1 = SB_NO_MIDI | SB_PCI_IRQ;
+
+ if (!request_region(io, 16, "soundblaster"))
+ goto err_out_free;
+
+ if(sb_dsp_detect(hw_config, 0, 0, NULL)==0)
+ {
+ printk(KERN_ERR "kahlua: audio not responding.\n");
+ release_region(io, 16);
+ goto err_out_free;
+ }
+
+ oldquiet = sb_be_quiet;
+ sb_be_quiet = 1;
+ if(sb_dsp_init(hw_config, THIS_MODULE))
+ {
+ sb_be_quiet = oldquiet;
+ goto err_out_free;
+ }
+ sb_be_quiet = oldquiet;
+
+ return 0;
+
+err_out_free:
+ pci_set_drvdata(pdev, NULL);
+ kfree(hw_config);
+ return 1;
+}
+
+static void __devexit remove_one(struct pci_dev *pdev)
+{
+ struct address_info *hw_config = pci_get_drvdata(pdev);
+ sb_dsp_unload(hw_config, 0);
+ pci_set_drvdata(pdev, NULL);
+ kfree(hw_config);
+}
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("Kahlua VSA1 PCI Audio");
+MODULE_LICENSE("GPL");
+
+/*
+ * 5530 only. The 5510/5520 decode is different.
+ */
+
+static DEFINE_PCI_DEVICE_TABLE(id_tbl) = {
+ { PCI_VDEVICE(CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO), 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, id_tbl);
+
+static struct pci_driver kahlua_driver = {
+ .name = "kahlua",
+ .id_table = id_tbl,
+ .probe = probe_one,
+ .remove = __devexit_p(remove_one),
+};
+
+
+static int __init kahlua_init_module(void)
+{
+ printk(KERN_INFO "Cyrix Kahlua VSA1 XpressAudio support (c) Copyright 2003 Red Hat Inc\n");
+ return pci_register_driver(&kahlua_driver);
+}
+
+static void __devexit kahlua_cleanup_module(void)
+{
+ pci_unregister_driver(&kahlua_driver);
+}
+
+
+module_init(kahlua_init_module);
+module_exit(kahlua_cleanup_module);
+
diff --git a/sound/oss/midi_ctrl.h b/sound/oss/midi_ctrl.h
new file mode 100644
index 00000000..3353e5a6
--- /dev/null
+++ b/sound/oss/midi_ctrl.h
@@ -0,0 +1,22 @@
+static unsigned char ctrl_def_values[128] =
+{
+ 0x40,0x00,0x40,0x40, 0x40,0x40,0x40,0x7f, /* 0 to 7 */
+ 0x40,0x40,0x40,0x7f, 0x40,0x40,0x40,0x40, /* 8 to 15 */
+ 0x40,0x40,0x40,0x40, 0x40,0x40,0x40,0x40, /* 16 to 23 */
+ 0x40,0x40,0x40,0x40, 0x40,0x40,0x40,0x40, /* 24 to 31 */
+
+ 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* 32 to 39 */
+ 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* 40 to 47 */
+ 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* 48 to 55 */
+ 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* 56 to 63 */
+
+ 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* 64 to 71 */
+ 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* 72 to 79 */
+ 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* 80 to 87 */
+ 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* 88 to 95 */
+
+ 0x00,0x00,0x7f,0x7f, 0x7f,0x7f,0x00,0x00, /* 96 to 103 */
+ 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* 104 to 111 */
+ 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* 112 to 119 */
+ 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* 120 to 127 */
+};
diff --git a/sound/oss/midi_synth.c b/sound/oss/midi_synth.c
new file mode 100644
index 00000000..2292c230
--- /dev/null
+++ b/sound/oss/midi_synth.c
@@ -0,0 +1,712 @@
+/*
+ * sound/oss/midi_synth.c
+ *
+ * High level midi sequencer manager for dumb MIDI interfaces.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ * Andrew Veliath : fixed running status in MIDI input state machine
+ */
+#define USE_SEQ_MACROS
+#define USE_SIMPLE_MACROS
+
+#include "sound_config.h"
+
+#define _MIDI_SYNTH_C_
+
+#include "midi_synth.h"
+
+static int midi2synth[MAX_MIDI_DEV];
+static int sysex_state[MAX_MIDI_DEV] =
+{0};
+static unsigned char prev_out_status[MAX_MIDI_DEV];
+
+#define STORE(cmd) \
+{ \
+ int len; \
+ unsigned char obuf[8]; \
+ cmd; \
+ seq_input_event(obuf, len); \
+}
+
+#define _seqbuf obuf
+#define _seqbufptr 0
+#define _SEQ_ADVBUF(x) len=x
+
+void
+do_midi_msg(int synthno, unsigned char *msg, int mlen)
+{
+ switch (msg[0] & 0xf0)
+ {
+ case 0x90:
+ if (msg[2] != 0)
+ {
+ STORE(SEQ_START_NOTE(synthno, msg[0] & 0x0f, msg[1], msg[2]));
+ break;
+ }
+ msg[2] = 64;
+
+ case 0x80:
+ STORE(SEQ_STOP_NOTE(synthno, msg[0] & 0x0f, msg[1], msg[2]));
+ break;
+
+ case 0xA0:
+ STORE(SEQ_KEY_PRESSURE(synthno, msg[0] & 0x0f, msg[1], msg[2]));
+ break;
+
+ case 0xB0:
+ STORE(SEQ_CONTROL(synthno, msg[0] & 0x0f,
+ msg[1], msg[2]));
+ break;
+
+ case 0xC0:
+ STORE(SEQ_SET_PATCH(synthno, msg[0] & 0x0f, msg[1]));
+ break;
+
+ case 0xD0:
+ STORE(SEQ_CHN_PRESSURE(synthno, msg[0] & 0x0f, msg[1]));
+ break;
+
+ case 0xE0:
+ STORE(SEQ_BENDER(synthno, msg[0] & 0x0f,
+ (msg[1] & 0x7f) | ((msg[2] & 0x7f) << 7)));
+ break;
+
+ default:
+ /* printk( "MPU: Unknown midi channel message %02x\n", msg[0]); */
+ ;
+ }
+}
+EXPORT_SYMBOL(do_midi_msg);
+
+static void
+midi_outc(int midi_dev, int data)
+{
+ int timeout;
+
+ for (timeout = 0; timeout < 3200; timeout++)
+ if (midi_devs[midi_dev]->outputc(midi_dev, (unsigned char) (data & 0xff)))
+ {
+ if (data & 0x80) /*
+ * Status byte
+ */
+ prev_out_status[midi_dev] =
+ (unsigned char) (data & 0xff); /*
+ * Store for running status
+ */
+ return; /*
+ * Mission complete
+ */
+ }
+ /*
+ * Sorry! No space on buffers.
+ */
+ printk("Midi send timed out\n");
+}
+
+static int
+prefix_cmd(int midi_dev, unsigned char status)
+{
+ if ((char *) midi_devs[midi_dev]->prefix_cmd == NULL)
+ return 1;
+
+ return midi_devs[midi_dev]->prefix_cmd(midi_dev, status);
+}
+
+static void
+midi_synth_input(int orig_dev, unsigned char data)
+{
+ int dev;
+ struct midi_input_info *inc;
+
+ static unsigned char len_tab[] = /* # of data bytes following a status
+ */
+ {
+ 2, /* 8x */
+ 2, /* 9x */
+ 2, /* Ax */
+ 2, /* Bx */
+ 1, /* Cx */
+ 1, /* Dx */
+ 2, /* Ex */
+ 0 /* Fx */
+ };
+
+ if (orig_dev < 0 || orig_dev > num_midis || midi_devs[orig_dev] == NULL)
+ return;
+
+ if (data == 0xfe) /* Ignore active sensing */
+ return;
+
+ dev = midi2synth[orig_dev];
+ inc = &midi_devs[orig_dev]->in_info;
+
+ switch (inc->m_state)
+ {
+ case MST_INIT:
+ if (data & 0x80) /* MIDI status byte */
+ {
+ if ((data & 0xf0) == 0xf0) /* Common message */
+ {
+ switch (data)
+ {
+ case 0xf0: /* Sysex */
+ inc->m_state = MST_SYSEX;
+ break; /* Sysex */
+
+ case 0xf1: /* MTC quarter frame */
+ case 0xf3: /* Song select */
+ inc->m_state = MST_DATA;
+ inc->m_ptr = 1;
+ inc->m_left = 1;
+ inc->m_buf[0] = data;
+ break;
+
+ case 0xf2: /* Song position pointer */
+ inc->m_state = MST_DATA;
+ inc->m_ptr = 1;
+ inc->m_left = 2;
+ inc->m_buf[0] = data;
+ break;
+
+ default:
+ inc->m_buf[0] = data;
+ inc->m_ptr = 1;
+ do_midi_msg(dev, inc->m_buf, inc->m_ptr);
+ inc->m_ptr = 0;
+ inc->m_left = 0;
+ }
+ } else
+ {
+ inc->m_state = MST_DATA;
+ inc->m_ptr = 1;
+ inc->m_left = len_tab[(data >> 4) - 8];
+ inc->m_buf[0] = inc->m_prev_status = data;
+ }
+ } else if (inc->m_prev_status & 0x80) {
+ /* Data byte (use running status) */
+ inc->m_ptr = 2;
+ inc->m_buf[1] = data;
+ inc->m_buf[0] = inc->m_prev_status;
+ inc->m_left = len_tab[(inc->m_buf[0] >> 4) - 8] - 1;
+ if (inc->m_left > 0)
+ inc->m_state = MST_DATA; /* Not done yet */
+ else {
+ inc->m_state = MST_INIT;
+ do_midi_msg(dev, inc->m_buf, inc->m_ptr);
+ inc->m_ptr = 0;
+ }
+ }
+ break; /* MST_INIT */
+
+ case MST_DATA:
+ inc->m_buf[inc->m_ptr++] = data;
+ if (--inc->m_left <= 0)
+ {
+ inc->m_state = MST_INIT;
+ do_midi_msg(dev, inc->m_buf, inc->m_ptr);
+ inc->m_ptr = 0;
+ }
+ break; /* MST_DATA */
+
+ case MST_SYSEX:
+ if (data == 0xf7) /* Sysex end */
+ {
+ inc->m_state = MST_INIT;
+ inc->m_left = 0;
+ inc->m_ptr = 0;
+ }
+ break; /* MST_SYSEX */
+
+ default:
+ printk("MIDI%d: Unexpected state %d (%02x)\n", orig_dev, inc->m_state, (int) data);
+ inc->m_state = MST_INIT;
+ }
+}
+
+static void
+leave_sysex(int dev)
+{
+ int orig_dev = synth_devs[dev]->midi_dev;
+ int timeout = 0;
+
+ if (!sysex_state[dev])
+ return;
+
+ sysex_state[dev] = 0;
+
+ while (!midi_devs[orig_dev]->outputc(orig_dev, 0xf7) &&
+ timeout < 1000)
+ timeout++;
+
+ sysex_state[dev] = 0;
+}
+
+static void
+midi_synth_output(int dev)
+{
+ /*
+ * Currently NOP
+ */
+}
+
+int midi_synth_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+ /*
+ * int orig_dev = synth_devs[dev]->midi_dev;
+ */
+
+ switch (cmd) {
+
+ case SNDCTL_SYNTH_INFO:
+ if (__copy_to_user(arg, synth_devs[dev]->info, sizeof(struct synth_info)))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_SYNTH_MEMAVL:
+ return 0x7fffffff;
+
+ default:
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL(midi_synth_ioctl);
+
+int
+midi_synth_kill_note(int dev, int channel, int note, int velocity)
+{
+ int orig_dev = synth_devs[dev]->midi_dev;
+ int msg, chn;
+
+ if (note < 0 || note > 127)
+ return 0;
+ if (channel < 0 || channel > 15)
+ return 0;
+ if (velocity < 0)
+ velocity = 0;
+ if (velocity > 127)
+ velocity = 127;
+
+ leave_sysex(dev);
+
+ msg = prev_out_status[orig_dev] & 0xf0;
+ chn = prev_out_status[orig_dev] & 0x0f;
+
+ if (chn == channel && ((msg == 0x90 && velocity == 64) || msg == 0x80))
+ { /*
+ * Use running status
+ */
+ if (!prefix_cmd(orig_dev, note))
+ return 0;
+
+ midi_outc(orig_dev, note);
+
+ if (msg == 0x90) /*
+ * Running status = Note on
+ */
+ midi_outc(orig_dev, 0); /*
+ * Note on with velocity 0 == note
+ * off
+ */
+ else
+ midi_outc(orig_dev, velocity);
+ } else
+ {
+ if (velocity == 64)
+ {
+ if (!prefix_cmd(orig_dev, 0x90 | (channel & 0x0f)))
+ return 0;
+ midi_outc(orig_dev, 0x90 | (channel & 0x0f)); /*
+ * Note on
+ */
+ midi_outc(orig_dev, note);
+ midi_outc(orig_dev, 0); /*
+ * Zero G
+ */
+ } else
+ {
+ if (!prefix_cmd(orig_dev, 0x80 | (channel & 0x0f)))
+ return 0;
+ midi_outc(orig_dev, 0x80 | (channel & 0x0f)); /*
+ * Note off
+ */
+ midi_outc(orig_dev, note);
+ midi_outc(orig_dev, velocity);
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(midi_synth_kill_note);
+
+int
+midi_synth_set_instr(int dev, int channel, int instr_no)
+{
+ int orig_dev = synth_devs[dev]->midi_dev;
+
+ if (instr_no < 0 || instr_no > 127)
+ instr_no = 0;
+ if (channel < 0 || channel > 15)
+ return 0;
+
+ leave_sysex(dev);
+
+ if (!prefix_cmd(orig_dev, 0xc0 | (channel & 0x0f)))
+ return 0;
+ midi_outc(orig_dev, 0xc0 | (channel & 0x0f)); /*
+ * Program change
+ */
+ midi_outc(orig_dev, instr_no);
+
+ return 0;
+}
+EXPORT_SYMBOL(midi_synth_set_instr);
+
+int
+midi_synth_start_note(int dev, int channel, int note, int velocity)
+{
+ int orig_dev = synth_devs[dev]->midi_dev;
+ int msg, chn;
+
+ if (note < 0 || note > 127)
+ return 0;
+ if (channel < 0 || channel > 15)
+ return 0;
+ if (velocity < 0)
+ velocity = 0;
+ if (velocity > 127)
+ velocity = 127;
+
+ leave_sysex(dev);
+
+ msg = prev_out_status[orig_dev] & 0xf0;
+ chn = prev_out_status[orig_dev] & 0x0f;
+
+ if (chn == channel && msg == 0x90)
+ { /*
+ * Use running status
+ */
+ if (!prefix_cmd(orig_dev, note))
+ return 0;
+ midi_outc(orig_dev, note);
+ midi_outc(orig_dev, velocity);
+ } else
+ {
+ if (!prefix_cmd(orig_dev, 0x90 | (channel & 0x0f)))
+ return 0;
+ midi_outc(orig_dev, 0x90 | (channel & 0x0f)); /*
+ * Note on
+ */
+ midi_outc(orig_dev, note);
+ midi_outc(orig_dev, velocity);
+ }
+ return 0;
+}
+EXPORT_SYMBOL(midi_synth_start_note);
+
+void
+midi_synth_reset(int dev)
+{
+
+ leave_sysex(dev);
+}
+EXPORT_SYMBOL(midi_synth_reset);
+
+int
+midi_synth_open(int dev, int mode)
+{
+ int orig_dev = synth_devs[dev]->midi_dev;
+ int err;
+ struct midi_input_info *inc;
+
+ if (orig_dev < 0 || orig_dev >= num_midis || midi_devs[orig_dev] == NULL)
+ return -ENXIO;
+
+ midi2synth[orig_dev] = dev;
+ sysex_state[dev] = 0;
+ prev_out_status[orig_dev] = 0;
+
+ if ((err = midi_devs[orig_dev]->open(orig_dev, mode,
+ midi_synth_input, midi_synth_output)) < 0)
+ return err;
+ inc = &midi_devs[orig_dev]->in_info;
+
+ /* save_flags(flags);
+ cli();
+ don't know against what irqhandler to protect*/
+ inc->m_busy = 0;
+ inc->m_state = MST_INIT;
+ inc->m_ptr = 0;
+ inc->m_left = 0;
+ inc->m_prev_status = 0x00;
+ /* restore_flags(flags); */
+
+ return 1;
+}
+EXPORT_SYMBOL(midi_synth_open);
+
+void
+midi_synth_close(int dev)
+{
+ int orig_dev = synth_devs[dev]->midi_dev;
+
+ leave_sysex(dev);
+
+ /*
+ * Shut up the synths by sending just single active sensing message.
+ */
+ midi_devs[orig_dev]->outputc(orig_dev, 0xfe);
+
+ midi_devs[orig_dev]->close(orig_dev);
+}
+EXPORT_SYMBOL(midi_synth_close);
+
+void
+midi_synth_hw_control(int dev, unsigned char *event)
+{
+}
+EXPORT_SYMBOL(midi_synth_hw_control);
+
+int
+midi_synth_load_patch(int dev, int format, const char __user *addr,
+ int count, int pmgr_flag)
+{
+ int orig_dev = synth_devs[dev]->midi_dev;
+
+ struct sysex_info sysex;
+ int i;
+ unsigned long left, src_offs, eox_seen = 0;
+ int first_byte = 1;
+ int hdr_size = (unsigned long) &sysex.data[0] - (unsigned long) &sysex;
+
+ leave_sysex(dev);
+
+ if (!prefix_cmd(orig_dev, 0xf0))
+ return 0;
+
+ /* Invalid patch format */
+ if (format != SYSEX_PATCH)
+ return -EINVAL;
+
+ /* Patch header too short */
+ if (count < hdr_size)
+ return -EINVAL;
+
+ count -= hdr_size;
+
+ /*
+ * Copy the header from user space
+ */
+
+ if (copy_from_user(&sysex, addr, hdr_size))
+ return -EFAULT;
+
+ /* Sysex record too short */
+ if ((unsigned)count < (unsigned)sysex.len)
+ sysex.len = count;
+
+ left = sysex.len;
+ src_offs = 0;
+
+ for (i = 0; i < left && !signal_pending(current); i++)
+ {
+ unsigned char data;
+
+ if (get_user(data,
+ (unsigned char __user *)(addr + hdr_size + i)))
+ return -EFAULT;
+
+ eox_seen = (i > 0 && data & 0x80); /* End of sysex */
+
+ if (eox_seen && data != 0xf7)
+ data = 0xf7;
+
+ if (i == 0)
+ {
+ if (data != 0xf0)
+ {
+ printk(KERN_WARNING "midi_synth: Sysex start missing\n");
+ return -EINVAL;
+ }
+ }
+ while (!midi_devs[orig_dev]->outputc(orig_dev, (unsigned char) (data & 0xff)) &&
+ !signal_pending(current))
+ schedule();
+
+ if (!first_byte && data & 0x80)
+ return 0;
+ first_byte = 0;
+ }
+
+ if (!eox_seen)
+ midi_outc(orig_dev, 0xf7);
+ return 0;
+}
+EXPORT_SYMBOL(midi_synth_load_patch);
+
+void midi_synth_panning(int dev, int channel, int pressure)
+{
+}
+EXPORT_SYMBOL(midi_synth_panning);
+
+void midi_synth_aftertouch(int dev, int channel, int pressure)
+{
+ int orig_dev = synth_devs[dev]->midi_dev;
+ int msg, chn;
+
+ if (pressure < 0 || pressure > 127)
+ return;
+ if (channel < 0 || channel > 15)
+ return;
+
+ leave_sysex(dev);
+
+ msg = prev_out_status[orig_dev] & 0xf0;
+ chn = prev_out_status[orig_dev] & 0x0f;
+
+ if (msg != 0xd0 || chn != channel) /*
+ * Test for running status
+ */
+ {
+ if (!prefix_cmd(orig_dev, 0xd0 | (channel & 0x0f)))
+ return;
+ midi_outc(orig_dev, 0xd0 | (channel & 0x0f)); /*
+ * Channel pressure
+ */
+ } else if (!prefix_cmd(orig_dev, pressure))
+ return;
+
+ midi_outc(orig_dev, pressure);
+}
+EXPORT_SYMBOL(midi_synth_aftertouch);
+
+void
+midi_synth_controller(int dev, int channel, int ctrl_num, int value)
+{
+ int orig_dev = synth_devs[dev]->midi_dev;
+ int chn, msg;
+
+ if (ctrl_num < 0 || ctrl_num > 127)
+ return;
+ if (channel < 0 || channel > 15)
+ return;
+
+ leave_sysex(dev);
+
+ msg = prev_out_status[orig_dev] & 0xf0;
+ chn = prev_out_status[orig_dev] & 0x0f;
+
+ if (msg != 0xb0 || chn != channel)
+ {
+ if (!prefix_cmd(orig_dev, 0xb0 | (channel & 0x0f)))
+ return;
+ midi_outc(orig_dev, 0xb0 | (channel & 0x0f));
+ } else if (!prefix_cmd(orig_dev, ctrl_num))
+ return;
+
+ midi_outc(orig_dev, ctrl_num);
+ midi_outc(orig_dev, value & 0x7f);
+}
+EXPORT_SYMBOL(midi_synth_controller);
+
+void
+midi_synth_bender(int dev, int channel, int value)
+{
+ int orig_dev = synth_devs[dev]->midi_dev;
+ int msg, prev_chn;
+
+ if (channel < 0 || channel > 15)
+ return;
+
+ if (value < 0 || value > 16383)
+ return;
+
+ leave_sysex(dev);
+
+ msg = prev_out_status[orig_dev] & 0xf0;
+ prev_chn = prev_out_status[orig_dev] & 0x0f;
+
+ if (msg != 0xd0 || prev_chn != channel) /*
+ * Test for running status
+ */
+ {
+ if (!prefix_cmd(orig_dev, 0xe0 | (channel & 0x0f)))
+ return;
+ midi_outc(orig_dev, 0xe0 | (channel & 0x0f));
+ } else if (!prefix_cmd(orig_dev, value & 0x7f))
+ return;
+
+ midi_outc(orig_dev, value & 0x7f);
+ midi_outc(orig_dev, (value >> 7) & 0x7f);
+}
+EXPORT_SYMBOL(midi_synth_bender);
+
+void
+midi_synth_setup_voice(int dev, int voice, int channel)
+{
+}
+EXPORT_SYMBOL(midi_synth_setup_voice);
+
+int
+midi_synth_send_sysex(int dev, unsigned char *bytes, int len)
+{
+ int orig_dev = synth_devs[dev]->midi_dev;
+ int i;
+
+ for (i = 0; i < len; i++)
+ {
+ switch (bytes[i])
+ {
+ case 0xf0: /* Start sysex */
+ if (!prefix_cmd(orig_dev, 0xf0))
+ return 0;
+ sysex_state[dev] = 1;
+ break;
+
+ case 0xf7: /* End sysex */
+ if (!sysex_state[dev]) /* Orphan sysex end */
+ return 0;
+ sysex_state[dev] = 0;
+ break;
+
+ default:
+ if (!sysex_state[dev])
+ return 0;
+
+ if (bytes[i] & 0x80) /* Error. Another message before sysex end */
+ {
+ bytes[i] = 0xf7; /* Sysex end */
+ sysex_state[dev] = 0;
+ }
+ }
+
+ if (!midi_devs[orig_dev]->outputc(orig_dev, bytes[i]))
+ {
+/*
+ * Hardware level buffer is full. Abort the sysex message.
+ */
+
+ int timeout = 0;
+
+ bytes[i] = 0xf7;
+ sysex_state[dev] = 0;
+
+ while (!midi_devs[orig_dev]->outputc(orig_dev, bytes[i]) &&
+ timeout < 1000)
+ timeout++;
+ }
+ if (!sysex_state[dev])
+ return 0;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(midi_synth_send_sysex);
+
diff --git a/sound/oss/midi_synth.h b/sound/oss/midi_synth.h
new file mode 100644
index 00000000..b64ddd6c
--- /dev/null
+++ b/sound/oss/midi_synth.h
@@ -0,0 +1,47 @@
+int midi_synth_ioctl (int dev,
+ unsigned int cmd, void __user * arg);
+int midi_synth_kill_note (int dev, int channel, int note, int velocity);
+int midi_synth_set_instr (int dev, int channel, int instr_no);
+int midi_synth_start_note (int dev, int channel, int note, int volume);
+void midi_synth_reset (int dev);
+int midi_synth_open (int dev, int mode);
+void midi_synth_close (int dev);
+void midi_synth_hw_control (int dev, unsigned char *event);
+int midi_synth_load_patch (int dev, int format, const char __user * addr,
+ int count, int pmgr_flag);
+void midi_synth_panning (int dev, int channel, int pressure);
+void midi_synth_aftertouch (int dev, int channel, int pressure);
+void midi_synth_controller (int dev, int channel, int ctrl_num, int value);
+void midi_synth_bender (int dev, int chn, int value);
+void midi_synth_setup_voice (int dev, int voice, int chn);
+int midi_synth_send_sysex(int dev, unsigned char *bytes,int len);
+
+#ifndef _MIDI_SYNTH_C_
+static struct synth_info std_synth_info =
+{MIDI_SYNTH_NAME, 0, SYNTH_TYPE_MIDI, 0, 0, 128, 0, 128, MIDI_SYNTH_CAPS};
+
+static struct synth_operations std_midi_synth =
+{
+ .owner = THIS_MODULE,
+ .id = "MIDI",
+ .info = &std_synth_info,
+ .midi_dev = 0,
+ .synth_type = SYNTH_TYPE_MIDI,
+ .synth_subtype = 0,
+ .open = midi_synth_open,
+ .close = midi_synth_close,
+ .ioctl = midi_synth_ioctl,
+ .kill_note = midi_synth_kill_note,
+ .start_note = midi_synth_start_note,
+ .set_instr = midi_synth_set_instr,
+ .reset = midi_synth_reset,
+ .hw_control = midi_synth_hw_control,
+ .load_patch = midi_synth_load_patch,
+ .aftertouch = midi_synth_aftertouch,
+ .controller = midi_synth_controller,
+ .panning = midi_synth_panning,
+ .bender = midi_synth_bender,
+ .setup_voice = midi_synth_setup_voice,
+ .send_sysex = midi_synth_send_sysex
+};
+#endif
diff --git a/sound/oss/midibuf.c b/sound/oss/midibuf.c
new file mode 100644
index 00000000..8cdb2cfe
--- /dev/null
+++ b/sound/oss/midibuf.c
@@ -0,0 +1,425 @@
+/*
+ * sound/oss/midibuf.c
+ *
+ * Device file manager for /dev/midi#
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ */
+#include <linux/stddef.h>
+#include <linux/kmod.h>
+#include <linux/spinlock.h>
+#define MIDIBUF_C
+
+#include "sound_config.h"
+
+
+/*
+ * Don't make MAX_QUEUE_SIZE larger than 4000
+ */
+
+#define MAX_QUEUE_SIZE 4000
+
+static wait_queue_head_t midi_sleeper[MAX_MIDI_DEV];
+static wait_queue_head_t input_sleeper[MAX_MIDI_DEV];
+
+struct midi_buf
+{
+ int len, head, tail;
+ unsigned char queue[MAX_QUEUE_SIZE];
+};
+
+struct midi_parms
+{
+ long prech_timeout; /*
+ * Timeout before the first ch
+ */
+};
+
+static struct midi_buf *midi_out_buf[MAX_MIDI_DEV] = {NULL};
+static struct midi_buf *midi_in_buf[MAX_MIDI_DEV] = {NULL};
+static struct midi_parms parms[MAX_MIDI_DEV];
+
+static void midi_poll(unsigned long dummy);
+
+
+static DEFINE_TIMER(poll_timer, midi_poll, 0, 0);
+
+static volatile int open_devs;
+static DEFINE_SPINLOCK(lock);
+
+#define DATA_AVAIL(q) (q->len)
+#define SPACE_AVAIL(q) (MAX_QUEUE_SIZE - q->len)
+
+#define QUEUE_BYTE(q, data) \
+ if (SPACE_AVAIL(q)) \
+ { \
+ unsigned long flags; \
+ spin_lock_irqsave(&lock, flags); \
+ q->queue[q->tail] = (data); \
+ q->len++; q->tail = (q->tail+1) % MAX_QUEUE_SIZE; \
+ spin_unlock_irqrestore(&lock, flags); \
+ }
+
+#define REMOVE_BYTE(q, data) \
+ if (DATA_AVAIL(q)) \
+ { \
+ unsigned long flags; \
+ spin_lock_irqsave(&lock, flags); \
+ data = q->queue[q->head]; \
+ q->len--; q->head = (q->head+1) % MAX_QUEUE_SIZE; \
+ spin_unlock_irqrestore(&lock, flags); \
+ }
+
+static void drain_midi_queue(int dev)
+{
+
+ /*
+ * Give the Midi driver time to drain its output queues
+ */
+
+ if (midi_devs[dev]->buffer_status != NULL)
+ while (!signal_pending(current) && midi_devs[dev]->buffer_status(dev))
+ interruptible_sleep_on_timeout(&midi_sleeper[dev],
+ HZ/10);
+}
+
+static void midi_input_intr(int dev, unsigned char data)
+{
+ if (midi_in_buf[dev] == NULL)
+ return;
+
+ if (data == 0xfe) /*
+ * Active sensing
+ */
+ return; /*
+ * Ignore
+ */
+
+ if (SPACE_AVAIL(midi_in_buf[dev])) {
+ QUEUE_BYTE(midi_in_buf[dev], data);
+ wake_up(&input_sleeper[dev]);
+ }
+}
+
+static void midi_output_intr(int dev)
+{
+ /*
+ * Currently NOP
+ */
+}
+
+static void midi_poll(unsigned long dummy)
+{
+ unsigned long flags;
+ int dev;
+
+ spin_lock_irqsave(&lock, flags);
+ if (open_devs)
+ {
+ for (dev = 0; dev < num_midis; dev++)
+ if (midi_devs[dev] != NULL && midi_out_buf[dev] != NULL)
+ {
+ while (DATA_AVAIL(midi_out_buf[dev]))
+ {
+ int ok;
+ int c = midi_out_buf[dev]->queue[midi_out_buf[dev]->head];
+
+ spin_unlock_irqrestore(&lock,flags);/* Give some time to others */
+ ok = midi_devs[dev]->outputc(dev, c);
+ spin_lock_irqsave(&lock, flags);
+ if (!ok)
+ break;
+ midi_out_buf[dev]->head = (midi_out_buf[dev]->head + 1) % MAX_QUEUE_SIZE;
+ midi_out_buf[dev]->len--;
+ }
+
+ if (DATA_AVAIL(midi_out_buf[dev]) < 100)
+ wake_up(&midi_sleeper[dev]);
+ }
+ poll_timer.expires = (1) + jiffies;
+ add_timer(&poll_timer);
+ /*
+ * Come back later
+ */
+ }
+ spin_unlock_irqrestore(&lock, flags);
+}
+
+int MIDIbuf_open(int dev, struct file *file)
+{
+ int mode, err;
+
+ dev = dev >> 4;
+ mode = translate_mode(file);
+
+ if (num_midis > MAX_MIDI_DEV)
+ {
+ printk(KERN_ERR "midi: Too many midi interfaces\n");
+ num_midis = MAX_MIDI_DEV;
+ }
+ if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
+ return -ENXIO;
+ /*
+ * Interrupts disabled. Be careful
+ */
+
+ module_put(midi_devs[dev]->owner);
+
+ if ((err = midi_devs[dev]->open(dev, mode,
+ midi_input_intr, midi_output_intr)) < 0)
+ return err;
+
+ parms[dev].prech_timeout = MAX_SCHEDULE_TIMEOUT;
+ midi_in_buf[dev] = vmalloc(sizeof(struct midi_buf));
+
+ if (midi_in_buf[dev] == NULL)
+ {
+ printk(KERN_WARNING "midi: Can't allocate buffer\n");
+ midi_devs[dev]->close(dev);
+ return -EIO;
+ }
+ midi_in_buf[dev]->len = midi_in_buf[dev]->head = midi_in_buf[dev]->tail = 0;
+
+ midi_out_buf[dev] = vmalloc(sizeof(struct midi_buf));
+
+ if (midi_out_buf[dev] == NULL)
+ {
+ printk(KERN_WARNING "midi: Can't allocate buffer\n");
+ midi_devs[dev]->close(dev);
+ vfree(midi_in_buf[dev]);
+ midi_in_buf[dev] = NULL;
+ return -EIO;
+ }
+ midi_out_buf[dev]->len = midi_out_buf[dev]->head = midi_out_buf[dev]->tail = 0;
+ open_devs++;
+
+ init_waitqueue_head(&midi_sleeper[dev]);
+ init_waitqueue_head(&input_sleeper[dev]);
+
+ if (open_devs < 2) /* This was first open */
+ {
+ poll_timer.expires = 1 + jiffies;
+ add_timer(&poll_timer); /* Start polling */
+ }
+ return err;
+}
+
+void MIDIbuf_release(int dev, struct file *file)
+{
+ int mode;
+
+ dev = dev >> 4;
+ mode = translate_mode(file);
+
+ if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
+ return;
+
+ /*
+ * Wait until the queue is empty
+ */
+
+ if (mode != OPEN_READ)
+ {
+ midi_devs[dev]->outputc(dev, 0xfe); /*
+ * Active sensing to shut the
+ * devices
+ */
+
+ while (!signal_pending(current) && DATA_AVAIL(midi_out_buf[dev]))
+ interruptible_sleep_on(&midi_sleeper[dev]);
+ /*
+ * Sync
+ */
+
+ drain_midi_queue(dev); /*
+ * Ensure the output queues are empty
+ */
+ }
+
+ midi_devs[dev]->close(dev);
+
+ open_devs--;
+ if (open_devs == 0)
+ del_timer_sync(&poll_timer);
+ vfree(midi_in_buf[dev]);
+ vfree(midi_out_buf[dev]);
+ midi_in_buf[dev] = NULL;
+ midi_out_buf[dev] = NULL;
+
+ module_put(midi_devs[dev]->owner);
+}
+
+int MIDIbuf_write(int dev, struct file *file, const char __user *buf, int count)
+{
+ int c, n, i;
+ unsigned char tmp_data;
+
+ dev = dev >> 4;
+
+ if (!count)
+ return 0;
+
+ c = 0;
+
+ while (c < count)
+ {
+ n = SPACE_AVAIL(midi_out_buf[dev]);
+
+ if (n == 0) { /*
+ * No space just now.
+ */
+
+ if (file->f_flags & O_NONBLOCK) {
+ c = -EAGAIN;
+ goto out;
+ }
+
+ interruptible_sleep_on(&midi_sleeper[dev]);
+ if (signal_pending(current))
+ {
+ c = -EINTR;
+ goto out;
+ }
+ n = SPACE_AVAIL(midi_out_buf[dev]);
+ }
+ if (n > (count - c))
+ n = count - c;
+
+ for (i = 0; i < n; i++)
+ {
+ /* BROKE BROKE BROKE - CAN'T DO THIS WITH CLI !! */
+ /* yes, think the same, so I removed the cli() brackets
+ QUEUE_BYTE is protected against interrupts */
+ if (copy_from_user((char *) &tmp_data, &(buf)[c], 1)) {
+ c = -EFAULT;
+ goto out;
+ }
+ QUEUE_BYTE(midi_out_buf[dev], tmp_data);
+ c++;
+ }
+ }
+out:
+ return c;
+}
+
+
+int MIDIbuf_read(int dev, struct file *file, char __user *buf, int count)
+{
+ int n, c = 0;
+ unsigned char tmp_data;
+
+ dev = dev >> 4;
+
+ if (!DATA_AVAIL(midi_in_buf[dev])) { /*
+ * No data yet, wait
+ */
+ if (file->f_flags & O_NONBLOCK) {
+ c = -EAGAIN;
+ goto out;
+ }
+ interruptible_sleep_on_timeout(&input_sleeper[dev],
+ parms[dev].prech_timeout);
+
+ if (signal_pending(current))
+ c = -EINTR; /* The user is getting restless */
+ }
+ if (c == 0 && DATA_AVAIL(midi_in_buf[dev])) /*
+ * Got some bytes
+ */
+ {
+ n = DATA_AVAIL(midi_in_buf[dev]);
+ if (n > count)
+ n = count;
+ c = 0;
+
+ while (c < n)
+ {
+ char *fixit;
+ REMOVE_BYTE(midi_in_buf[dev], tmp_data);
+ fixit = (char *) &tmp_data;
+ /* BROKE BROKE BROKE */
+ /* yes removed the cli() brackets again
+ should q->len,tail&head be atomic_t? */
+ if (copy_to_user(&(buf)[c], fixit, 1)) {
+ c = -EFAULT;
+ goto out;
+ }
+ c++;
+ }
+ }
+out:
+ return c;
+}
+
+int MIDIbuf_ioctl(int dev, struct file *file,
+ unsigned int cmd, void __user *arg)
+{
+ int val;
+
+ dev = dev >> 4;
+
+ if (((cmd >> 8) & 0xff) == 'C')
+ {
+ if (midi_devs[dev]->coproc) /* Coprocessor ioctl */
+ return midi_devs[dev]->coproc->ioctl(midi_devs[dev]->coproc->devc, cmd, arg, 0);
+/* printk("/dev/midi%d: No coprocessor for this device\n", dev);*/
+ return -ENXIO;
+ }
+ else
+ {
+ switch (cmd)
+ {
+ case SNDCTL_MIDI_PRETIME:
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+ if (val < 0)
+ val = 0;
+ val = (HZ * val) / 10;
+ parms[dev].prech_timeout = val;
+ return put_user(val, (int __user *)arg);
+
+ default:
+ if (!midi_devs[dev]->ioctl)
+ return -EINVAL;
+ return midi_devs[dev]->ioctl(dev, cmd, arg);
+ }
+ }
+}
+
+/* No kernel lock - fine */
+unsigned int MIDIbuf_poll(int dev, struct file *file, poll_table * wait)
+{
+ unsigned int mask = 0;
+
+ dev = dev >> 4;
+
+ /* input */
+ poll_wait(file, &input_sleeper[dev], wait);
+ if (DATA_AVAIL(midi_in_buf[dev]))
+ mask |= POLLIN | POLLRDNORM;
+
+ /* output */
+ poll_wait(file, &midi_sleeper[dev], wait);
+ if (!SPACE_AVAIL(midi_out_buf[dev]))
+ mask |= POLLOUT | POLLWRNORM;
+
+ return mask;
+}
+
+
+int MIDIbuf_avail(int dev)
+{
+ if (midi_in_buf[dev])
+ return DATA_AVAIL (midi_in_buf[dev]);
+ return 0;
+}
+EXPORT_SYMBOL(MIDIbuf_avail);
+
diff --git a/sound/oss/mpu401.c b/sound/oss/mpu401.c
new file mode 100644
index 00000000..25e4609f
--- /dev/null
+++ b/sound/oss/mpu401.c
@@ -0,0 +1,1806 @@
+/*
+ * sound/oss/mpu401.c
+ *
+ * The low level driver for Roland MPU-401 compatible Midi cards.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer ioctl code reworked (vmalloc/vfree removed)
+ * Alan Cox modularisation, use normal request_irq, use dev_id
+ * Bartlomiej Zolnierkiewicz removed some __init to allow using many drivers
+ * Chris Rankin Update the module-usage counter for the coprocessor
+ * Zwane Mwaikambo Changed attach/unload resource freeing
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#define USE_SEQ_MACROS
+#define USE_SIMPLE_MACROS
+
+#include "sound_config.h"
+
+#include "coproc.h"
+#include "mpu401.h"
+
+static int timer_mode = TMR_INTERNAL, timer_caps = TMR_INTERNAL;
+
+struct mpu_config
+{
+ int base; /*
+ * I/O base
+ */
+ int irq;
+ int opened; /*
+ * Open mode
+ */
+ int devno;
+ int synthno;
+ int uart_mode;
+ int initialized;
+ int mode;
+#define MODE_MIDI 1
+#define MODE_SYNTH 2
+ unsigned char version, revision;
+ unsigned int capabilities;
+#define MPU_CAP_INTLG 0x10000000
+#define MPU_CAP_SYNC 0x00000010
+#define MPU_CAP_FSK 0x00000020
+#define MPU_CAP_CLS 0x00000040
+#define MPU_CAP_SMPTE 0x00000080
+#define MPU_CAP_2PORT 0x00000001
+ int timer_flag;
+
+#define MBUF_MAX 10
+#define BUFTEST(dc) if (dc->m_ptr >= MBUF_MAX || dc->m_ptr < 0) \
+ {printk( "MPU: Invalid buffer pointer %d/%d, s=%d\n", dc->m_ptr, dc->m_left, dc->m_state);dc->m_ptr--;}
+ int m_busy;
+ unsigned char m_buf[MBUF_MAX];
+ int m_ptr;
+ int m_state;
+ int m_left;
+ unsigned char last_status;
+ void (*inputintr) (int dev, unsigned char data);
+ int shared_irq;
+ int *osp;
+ spinlock_t lock;
+ };
+
+#define DATAPORT(base) (base)
+#define COMDPORT(base) (base+1)
+#define STATPORT(base) (base+1)
+
+
+static void mpu401_close(int dev);
+
+static inline int mpu401_status(struct mpu_config *devc)
+{
+ return inb(STATPORT(devc->base));
+}
+
+#define input_avail(devc) (!(mpu401_status(devc)&INPUT_AVAIL))
+#define output_ready(devc) (!(mpu401_status(devc)&OUTPUT_READY))
+
+static inline void write_command(struct mpu_config *devc, unsigned char cmd)
+{
+ outb(cmd, COMDPORT(devc->base));
+}
+
+static inline int read_data(struct mpu_config *devc)
+{
+ return inb(DATAPORT(devc->base));
+}
+
+static inline void write_data(struct mpu_config *devc, unsigned char byte)
+{
+ outb(byte, DATAPORT(devc->base));
+}
+
+#define OUTPUT_READY 0x40
+#define INPUT_AVAIL 0x80
+#define MPU_ACK 0xFE
+#define MPU_RESET 0xFF
+#define UART_MODE_ON 0x3F
+
+static struct mpu_config dev_conf[MAX_MIDI_DEV];
+
+static int n_mpu_devs;
+
+static int reset_mpu401(struct mpu_config *devc);
+static void set_uart_mode(int dev, struct mpu_config *devc, int arg);
+
+static int mpu_timer_init(int midi_dev);
+static void mpu_timer_interrupt(void);
+static void timer_ext_event(struct mpu_config *devc, int event, int parm);
+
+static struct synth_info mpu_synth_info_proto = {
+ "MPU-401 MIDI interface",
+ 0,
+ SYNTH_TYPE_MIDI,
+ MIDI_TYPE_MPU401,
+ 0, 128,
+ 0, 128,
+ SYNTH_CAP_INPUT
+};
+
+static struct synth_info mpu_synth_info[MAX_MIDI_DEV];
+
+/*
+ * States for the input scanner
+ */
+
+#define ST_INIT 0 /* Ready for timing byte or msg */
+#define ST_TIMED 1 /* Leading timing byte rcvd */
+#define ST_DATABYTE 2 /* Waiting for (nr_left) data bytes */
+
+#define ST_SYSMSG 100 /* System message (sysx etc). */
+#define ST_SYSEX 101 /* System exclusive msg */
+#define ST_MTC 102 /* Midi Time Code (MTC) qframe msg */
+#define ST_SONGSEL 103 /* Song select */
+#define ST_SONGPOS 104 /* Song position pointer */
+
+static unsigned char len_tab[] = /* # of data bytes following a status
+ */
+{
+ 2, /* 8x */
+ 2, /* 9x */
+ 2, /* Ax */
+ 2, /* Bx */
+ 1, /* Cx */
+ 1, /* Dx */
+ 2, /* Ex */
+ 0 /* Fx */
+};
+
+#define STORE(cmd) \
+{ \
+ int len; \
+ unsigned char obuf[8]; \
+ cmd; \
+ seq_input_event(obuf, len); \
+}
+
+#define _seqbuf obuf
+#define _seqbufptr 0
+#define _SEQ_ADVBUF(x) len=x
+
+static int mpu_input_scanner(struct mpu_config *devc, unsigned char midic)
+{
+
+ switch (devc->m_state)
+ {
+ case ST_INIT:
+ switch (midic)
+ {
+ case 0xf8:
+ /* Timer overflow */
+ break;
+
+ case 0xfc:
+ printk("<all end>");
+ break;
+
+ case 0xfd:
+ if (devc->timer_flag)
+ mpu_timer_interrupt();
+ break;
+
+ case 0xfe:
+ return MPU_ACK;
+
+ case 0xf0:
+ case 0xf1:
+ case 0xf2:
+ case 0xf3:
+ case 0xf4:
+ case 0xf5:
+ case 0xf6:
+ case 0xf7:
+ printk("<Trk data rq #%d>", midic & 0x0f);
+ break;
+
+ case 0xf9:
+ printk("<conductor rq>");
+ break;
+
+ case 0xff:
+ devc->m_state = ST_SYSMSG;
+ break;
+
+ default:
+ if (midic <= 0xef)
+ {
+ /* printk( "mpu time: %d ", midic); */
+ devc->m_state = ST_TIMED;
+ }
+ else
+ printk("<MPU: Unknown event %02x> ", midic);
+ }
+ break;
+
+ case ST_TIMED:
+ {
+ int msg = ((int) (midic & 0xf0) >> 4);
+
+ devc->m_state = ST_DATABYTE;
+
+ if (msg < 8) /* Data byte */
+ {
+ /* printk( "midi msg (running status) "); */
+ msg = ((int) (devc->last_status & 0xf0) >> 4);
+ msg -= 8;
+ devc->m_left = len_tab[msg] - 1;
+
+ devc->m_ptr = 2;
+ devc->m_buf[0] = devc->last_status;
+ devc->m_buf[1] = midic;
+
+ if (devc->m_left <= 0)
+ {
+ devc->m_state = ST_INIT;
+ do_midi_msg(devc->synthno, devc->m_buf, devc->m_ptr);
+ devc->m_ptr = 0;
+ }
+ }
+ else if (msg == 0xf) /* MPU MARK */
+ {
+ devc->m_state = ST_INIT;
+
+ switch (midic)
+ {
+ case 0xf8:
+ /* printk( "NOP "); */
+ break;
+
+ case 0xf9:
+ /* printk( "meas end "); */
+ break;
+
+ case 0xfc:
+ /* printk( "data end "); */
+ break;
+
+ default:
+ printk("Unknown MPU mark %02x\n", midic);
+ }
+ }
+ else
+ {
+ devc->last_status = midic;
+ /* printk( "midi msg "); */
+ msg -= 8;
+ devc->m_left = len_tab[msg];
+
+ devc->m_ptr = 1;
+ devc->m_buf[0] = midic;
+
+ if (devc->m_left <= 0)
+ {
+ devc->m_state = ST_INIT;
+ do_midi_msg(devc->synthno, devc->m_buf, devc->m_ptr);
+ devc->m_ptr = 0;
+ }
+ }
+ }
+ break;
+
+ case ST_SYSMSG:
+ switch (midic)
+ {
+ case 0xf0:
+ printk("<SYX>");
+ devc->m_state = ST_SYSEX;
+ break;
+
+ case 0xf1:
+ devc->m_state = ST_MTC;
+ break;
+
+ case 0xf2:
+ devc->m_state = ST_SONGPOS;
+ devc->m_ptr = 0;
+ break;
+
+ case 0xf3:
+ devc->m_state = ST_SONGSEL;
+ break;
+
+ case 0xf6:
+ /* printk( "tune_request\n"); */
+ devc->m_state = ST_INIT;
+
+ /*
+ * Real time messages
+ */
+ case 0xf8:
+ /* midi clock */
+ devc->m_state = ST_INIT;
+ timer_ext_event(devc, TMR_CLOCK, 0);
+ break;
+
+ case 0xfA:
+ devc->m_state = ST_INIT;
+ timer_ext_event(devc, TMR_START, 0);
+ break;
+
+ case 0xFB:
+ devc->m_state = ST_INIT;
+ timer_ext_event(devc, TMR_CONTINUE, 0);
+ break;
+
+ case 0xFC:
+ devc->m_state = ST_INIT;
+ timer_ext_event(devc, TMR_STOP, 0);
+ break;
+
+ case 0xFE:
+ /* active sensing */
+ devc->m_state = ST_INIT;
+ break;
+
+ case 0xff:
+ /* printk( "midi hard reset"); */
+ devc->m_state = ST_INIT;
+ break;
+
+ default:
+ printk("unknown MIDI sysmsg %0x\n", midic);
+ devc->m_state = ST_INIT;
+ }
+ break;
+
+ case ST_MTC:
+ devc->m_state = ST_INIT;
+ printk("MTC frame %x02\n", midic);
+ break;
+
+ case ST_SYSEX:
+ if (midic == 0xf7)
+ {
+ printk("<EOX>");
+ devc->m_state = ST_INIT;
+ }
+ else
+ printk("%02x ", midic);
+ break;
+
+ case ST_SONGPOS:
+ BUFTEST(devc);
+ devc->m_buf[devc->m_ptr++] = midic;
+ if (devc->m_ptr == 2)
+ {
+ devc->m_state = ST_INIT;
+ devc->m_ptr = 0;
+ timer_ext_event(devc, TMR_SPP,
+ ((devc->m_buf[1] & 0x7f) << 7) |
+ (devc->m_buf[0] & 0x7f));
+ }
+ break;
+
+ case ST_DATABYTE:
+ BUFTEST(devc);
+ devc->m_buf[devc->m_ptr++] = midic;
+ if ((--devc->m_left) <= 0)
+ {
+ devc->m_state = ST_INIT;
+ do_midi_msg(devc->synthno, devc->m_buf, devc->m_ptr);
+ devc->m_ptr = 0;
+ }
+ break;
+
+ default:
+ printk("Bad state %d ", devc->m_state);
+ devc->m_state = ST_INIT;
+ }
+ return 1;
+}
+
+static void mpu401_input_loop(struct mpu_config *devc)
+{
+ unsigned long flags;
+ int busy;
+ int n;
+
+ spin_lock_irqsave(&devc->lock,flags);
+ busy = devc->m_busy;
+ devc->m_busy = 1;
+ spin_unlock_irqrestore(&devc->lock,flags);
+
+ if (busy) /* Already inside the scanner */
+ return;
+
+ n = 50;
+
+ while (input_avail(devc) && n-- > 0)
+ {
+ unsigned char c = read_data(devc);
+
+ if (devc->mode == MODE_SYNTH)
+ {
+ mpu_input_scanner(devc, c);
+ }
+ else if (devc->opened & OPEN_READ && devc->inputintr != NULL)
+ devc->inputintr(devc->devno, c);
+ }
+ devc->m_busy = 0;
+}
+
+static irqreturn_t mpuintr(int irq, void *dev_id)
+{
+ struct mpu_config *devc;
+ int dev = (int)(unsigned long) dev_id;
+ int handled = 0;
+
+ devc = &dev_conf[dev];
+
+ if (input_avail(devc))
+ {
+ handled = 1;
+ if (devc->base != 0 && (devc->opened & OPEN_READ || devc->mode == MODE_SYNTH))
+ mpu401_input_loop(devc);
+ else
+ {
+ /* Dummy read (just to acknowledge the interrupt) */
+ read_data(devc);
+ }
+ }
+ return IRQ_RETVAL(handled);
+}
+
+static int mpu401_open(int dev, int mode,
+ void (*input) (int dev, unsigned char data),
+ void (*output) (int dev)
+)
+{
+ int err;
+ struct mpu_config *devc;
+ struct coproc_operations *coprocessor;
+
+ if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
+ return -ENXIO;
+
+ devc = &dev_conf[dev];
+
+ if (devc->opened)
+ return -EBUSY;
+ /*
+ * Verify that the device is really running.
+ * Some devices (such as Ensoniq SoundScape don't
+ * work before the on board processor (OBP) is initialized
+ * by downloading its microcode.
+ */
+
+ if (!devc->initialized)
+ {
+ if (mpu401_status(devc) == 0xff) /* Bus float */
+ {
+ printk(KERN_ERR "mpu401: Device not initialized properly\n");
+ return -EIO;
+ }
+ reset_mpu401(devc);
+ }
+
+ if ( (coprocessor = midi_devs[dev]->coproc) != NULL )
+ {
+ if (!try_module_get(coprocessor->owner)) {
+ mpu401_close(dev);
+ return -ENODEV;
+ }
+
+ if ((err = coprocessor->open(coprocessor->devc, COPR_MIDI)) < 0)
+ {
+ printk(KERN_WARNING "MPU-401: Can't access coprocessor device\n");
+ mpu401_close(dev);
+ return err;
+ }
+ }
+
+ set_uart_mode(dev, devc, 1);
+ devc->mode = MODE_MIDI;
+ devc->synthno = 0;
+
+ mpu401_input_loop(devc);
+
+ devc->inputintr = input;
+ devc->opened = mode;
+
+ return 0;
+}
+
+static void mpu401_close(int dev)
+{
+ struct mpu_config *devc;
+ struct coproc_operations *coprocessor;
+
+ devc = &dev_conf[dev];
+ if (devc->uart_mode)
+ reset_mpu401(devc); /*
+ * This disables the UART mode
+ */
+ devc->mode = 0;
+ devc->inputintr = NULL;
+
+ coprocessor = midi_devs[dev]->coproc;
+ if (coprocessor) {
+ coprocessor->close(coprocessor->devc, COPR_MIDI);
+ module_put(coprocessor->owner);
+ }
+ devc->opened = 0;
+}
+
+static int mpu401_out(int dev, unsigned char midi_byte)
+{
+ int timeout;
+ unsigned long flags;
+
+ struct mpu_config *devc;
+
+ devc = &dev_conf[dev];
+
+ /*
+ * Sometimes it takes about 30000 loops before the output becomes ready
+ * (After reset). Normally it takes just about 10 loops.
+ */
+
+ for (timeout = 30000; timeout > 0 && !output_ready(devc); timeout--);
+
+ spin_lock_irqsave(&devc->lock,flags);
+ if (!output_ready(devc))
+ {
+ printk(KERN_WARNING "mpu401: Send data timeout\n");
+ spin_unlock_irqrestore(&devc->lock,flags);
+ return 0;
+ }
+ write_data(devc, midi_byte);
+ spin_unlock_irqrestore(&devc->lock,flags);
+ return 1;
+}
+
+static int mpu401_command(int dev, mpu_command_rec * cmd)
+{
+ int i, timeout, ok;
+ int ret = 0;
+ unsigned long flags;
+ struct mpu_config *devc;
+
+ devc = &dev_conf[dev];
+
+ if (devc->uart_mode) /*
+ * Not possible in UART mode
+ */
+ {
+ printk(KERN_WARNING "mpu401: commands not possible in the UART mode\n");
+ return -EINVAL;
+ }
+ /*
+ * Test for input since pending input seems to block the output.
+ */
+ if (input_avail(devc))
+ mpu401_input_loop(devc);
+
+ /*
+ * Sometimes it takes about 50000 loops before the output becomes ready
+ * (After reset). Normally it takes just about 10 loops.
+ */
+
+ timeout = 50000;
+retry:
+ if (timeout-- <= 0)
+ {
+ printk(KERN_WARNING "mpu401: Command (0x%x) timeout\n", (int) cmd->cmd);
+ return -EIO;
+ }
+ spin_lock_irqsave(&devc->lock,flags);
+
+ if (!output_ready(devc))
+ {
+ spin_unlock_irqrestore(&devc->lock,flags);
+ goto retry;
+ }
+ write_command(devc, cmd->cmd);
+
+ ok = 0;
+ for (timeout = 50000; timeout > 0 && !ok; timeout--)
+ {
+ if (input_avail(devc))
+ {
+ if (devc->opened && devc->mode == MODE_SYNTH)
+ {
+ if (mpu_input_scanner(devc, read_data(devc)) == MPU_ACK)
+ ok = 1;
+ }
+ else
+ {
+ /* Device is not currently open. Use simpler method */
+ if (read_data(devc) == MPU_ACK)
+ ok = 1;
+ }
+ }
+ }
+ if (!ok)
+ {
+ spin_unlock_irqrestore(&devc->lock,flags);
+ return -EIO;
+ }
+ if (cmd->nr_args)
+ {
+ for (i = 0; i < cmd->nr_args; i++)
+ {
+ for (timeout = 3000; timeout > 0 && !output_ready(devc); timeout--);
+
+ if (!mpu401_out(dev, cmd->data[i]))
+ {
+ spin_unlock_irqrestore(&devc->lock,flags);
+ printk(KERN_WARNING "mpu401: Command (0x%x), parm send failed.\n", (int) cmd->cmd);
+ return -EIO;
+ }
+ }
+ }
+ ret = 0;
+ cmd->data[0] = 0;
+
+ if (cmd->nr_returns)
+ {
+ for (i = 0; i < cmd->nr_returns; i++)
+ {
+ ok = 0;
+ for (timeout = 5000; timeout > 0 && !ok; timeout--)
+ if (input_avail(devc))
+ {
+ cmd->data[i] = read_data(devc);
+ ok = 1;
+ }
+ if (!ok)
+ {
+ spin_unlock_irqrestore(&devc->lock,flags);
+ return -EIO;
+ }
+ }
+ }
+ spin_unlock_irqrestore(&devc->lock,flags);
+ return ret;
+}
+
+static int mpu_cmd(int dev, int cmd, int data)
+{
+ int ret;
+
+ static mpu_command_rec rec;
+
+ rec.cmd = cmd & 0xff;
+ rec.nr_args = ((cmd & 0xf0) == 0xE0);
+ rec.nr_returns = ((cmd & 0xf0) == 0xA0);
+ rec.data[0] = data & 0xff;
+
+ if ((ret = mpu401_command(dev, &rec)) < 0)
+ return ret;
+ return (unsigned char) rec.data[0];
+}
+
+static int mpu401_prefix_cmd(int dev, unsigned char status)
+{
+ struct mpu_config *devc = &dev_conf[dev];
+
+ if (devc->uart_mode)
+ return 1;
+
+ if (status < 0xf0)
+ {
+ if (mpu_cmd(dev, 0xD0, 0) < 0)
+ return 0;
+ return 1;
+ }
+ switch (status)
+ {
+ case 0xF0:
+ if (mpu_cmd(dev, 0xDF, 0) < 0)
+ return 0;
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+static int mpu401_start_read(int dev)
+{
+ return 0;
+}
+
+static int mpu401_end_read(int dev)
+{
+ return 0;
+}
+
+static int mpu401_ioctl(int dev, unsigned cmd, void __user *arg)
+{
+ struct mpu_config *devc;
+ mpu_command_rec rec;
+ int val, ret;
+
+ devc = &dev_conf[dev];
+ switch (cmd)
+ {
+ case SNDCTL_MIDI_MPUMODE:
+ if (!(devc->capabilities & MPU_CAP_INTLG)) { /* No intelligent mode */
+ printk(KERN_WARNING "mpu401: Intelligent mode not supported by the HW\n");
+ return -EINVAL;
+ }
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+ set_uart_mode(dev, devc, !val);
+ return 0;
+
+ case SNDCTL_MIDI_MPUCMD:
+ if (copy_from_user(&rec, arg, sizeof(rec)))
+ return -EFAULT;
+ if ((ret = mpu401_command(dev, &rec)) < 0)
+ return ret;
+ if (copy_to_user(arg, &rec, sizeof(rec)))
+ return -EFAULT;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static void mpu401_kick(int dev)
+{
+}
+
+static int mpu401_buffer_status(int dev)
+{
+ return 0; /*
+ * No data in buffers
+ */
+}
+
+static int mpu_synth_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+ int midi_dev;
+ struct mpu_config *devc;
+
+ midi_dev = synth_devs[dev]->midi_dev;
+
+ if (midi_dev < 0 || midi_dev >= num_midis || midi_devs[midi_dev] == NULL)
+ return -ENXIO;
+
+ devc = &dev_conf[midi_dev];
+
+ switch (cmd)
+ {
+
+ case SNDCTL_SYNTH_INFO:
+ if (copy_to_user(arg, &mpu_synth_info[midi_dev],
+ sizeof(struct synth_info)))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_SYNTH_MEMAVL:
+ return 0x7fffffff;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mpu_synth_open(int dev, int mode)
+{
+ int midi_dev, err;
+ struct mpu_config *devc;
+ struct coproc_operations *coprocessor;
+
+ midi_dev = synth_devs[dev]->midi_dev;
+
+ if (midi_dev < 0 || midi_dev > num_midis || midi_devs[midi_dev] == NULL)
+ return -ENXIO;
+
+ devc = &dev_conf[midi_dev];
+
+ /*
+ * Verify that the device is really running.
+ * Some devices (such as Ensoniq SoundScape don't
+ * work before the on board processor (OBP) is initialized
+ * by downloading its microcode.
+ */
+
+ if (!devc->initialized)
+ {
+ if (mpu401_status(devc) == 0xff) /* Bus float */
+ {
+ printk(KERN_ERR "mpu401: Device not initialized properly\n");
+ return -EIO;
+ }
+ reset_mpu401(devc);
+ }
+ if (devc->opened)
+ return -EBUSY;
+ devc->mode = MODE_SYNTH;
+ devc->synthno = dev;
+
+ devc->inputintr = NULL;
+
+ coprocessor = midi_devs[midi_dev]->coproc;
+ if (coprocessor) {
+ if (!try_module_get(coprocessor->owner))
+ return -ENODEV;
+
+ if ((err = coprocessor->open(coprocessor->devc, COPR_MIDI)) < 0)
+ {
+ printk(KERN_WARNING "mpu401: Can't access coprocessor device\n");
+ return err;
+ }
+ }
+ devc->opened = mode;
+ reset_mpu401(devc);
+
+ if (mode & OPEN_READ)
+ {
+ mpu_cmd(midi_dev, 0x8B, 0); /* Enable data in stop mode */
+ mpu_cmd(midi_dev, 0x34, 0); /* Return timing bytes in stop mode */
+ mpu_cmd(midi_dev, 0x87, 0); /* Enable pitch & controller */
+ }
+ return 0;
+}
+
+static void mpu_synth_close(int dev)
+{
+ int midi_dev;
+ struct mpu_config *devc;
+ struct coproc_operations *coprocessor;
+
+ midi_dev = synth_devs[dev]->midi_dev;
+
+ devc = &dev_conf[midi_dev];
+ mpu_cmd(midi_dev, 0x15, 0); /* Stop recording, playback and MIDI */
+ mpu_cmd(midi_dev, 0x8a, 0); /* Disable data in stopped mode */
+
+ devc->inputintr = NULL;
+
+ coprocessor = midi_devs[midi_dev]->coproc;
+ if (coprocessor) {
+ coprocessor->close(coprocessor->devc, COPR_MIDI);
+ module_put(coprocessor->owner);
+ }
+ devc->opened = 0;
+ devc->mode = 0;
+}
+
+#define MIDI_SYNTH_NAME "MPU-401 UART Midi"
+#define MIDI_SYNTH_CAPS SYNTH_CAP_INPUT
+#include "midi_synth.h"
+
+static struct synth_operations mpu401_synth_proto =
+{
+ .owner = THIS_MODULE,
+ .id = "MPU401",
+ .info = NULL,
+ .midi_dev = 0,
+ .synth_type = SYNTH_TYPE_MIDI,
+ .synth_subtype = 0,
+ .open = mpu_synth_open,
+ .close = mpu_synth_close,
+ .ioctl = mpu_synth_ioctl,
+ .kill_note = midi_synth_kill_note,
+ .start_note = midi_synth_start_note,
+ .set_instr = midi_synth_set_instr,
+ .reset = midi_synth_reset,
+ .hw_control = midi_synth_hw_control,
+ .load_patch = midi_synth_load_patch,
+ .aftertouch = midi_synth_aftertouch,
+ .controller = midi_synth_controller,
+ .panning = midi_synth_panning,
+ .bender = midi_synth_bender,
+ .setup_voice = midi_synth_setup_voice,
+ .send_sysex = midi_synth_send_sysex
+};
+
+static struct synth_operations *mpu401_synth_operations[MAX_MIDI_DEV];
+
+static struct midi_operations mpu401_midi_proto =
+{
+ .owner = THIS_MODULE,
+ .info = {"MPU-401 Midi", 0, MIDI_CAP_MPU401, SNDCARD_MPU401},
+ .in_info = {0},
+ .open = mpu401_open,
+ .close = mpu401_close,
+ .ioctl = mpu401_ioctl,
+ .outputc = mpu401_out,
+ .start_read = mpu401_start_read,
+ .end_read = mpu401_end_read,
+ .kick = mpu401_kick,
+ .buffer_status = mpu401_buffer_status,
+ .prefix_cmd = mpu401_prefix_cmd
+};
+
+static struct midi_operations mpu401_midi_operations[MAX_MIDI_DEV];
+
+static void mpu401_chk_version(int n, struct mpu_config *devc)
+{
+ int tmp;
+
+ devc->version = devc->revision = 0;
+
+ tmp = mpu_cmd(n, 0xAC, 0);
+ if (tmp < 0)
+ return;
+ if ((tmp & 0xf0) > 0x20) /* Why it's larger than 2.x ??? */
+ return;
+ devc->version = tmp;
+
+ if ((tmp = mpu_cmd(n, 0xAD, 0)) < 0) {
+ devc->version = 0;
+ return;
+ }
+ devc->revision = tmp;
+}
+
+int attach_mpu401(struct address_info *hw_config, struct module *owner)
+{
+ unsigned long flags;
+ char revision_char;
+
+ int m, ret;
+ struct mpu_config *devc;
+
+ hw_config->slots[1] = -1;
+ m = sound_alloc_mididev();
+ if (m == -1)
+ {
+ printk(KERN_WARNING "MPU-401: Too many midi devices detected\n");
+ ret = -ENOMEM;
+ goto out_err;
+ }
+ devc = &dev_conf[m];
+ devc->base = hw_config->io_base;
+ devc->osp = hw_config->osp;
+ devc->irq = hw_config->irq;
+ devc->opened = 0;
+ devc->uart_mode = 0;
+ devc->initialized = 0;
+ devc->version = 0;
+ devc->revision = 0;
+ devc->capabilities = 0;
+ devc->timer_flag = 0;
+ devc->m_busy = 0;
+ devc->m_state = ST_INIT;
+ devc->shared_irq = hw_config->always_detect;
+ devc->irq = hw_config->irq;
+ spin_lock_init(&devc->lock);
+
+ if (devc->irq < 0)
+ {
+ devc->irq *= -1;
+ devc->shared_irq = 1;
+ }
+
+ if (!hw_config->always_detect)
+ {
+ /* Verify the hardware again */
+ if (!reset_mpu401(devc))
+ {
+ printk(KERN_WARNING "mpu401: Device didn't respond\n");
+ ret = -ENODEV;
+ goto out_mididev;
+ }
+ if (!devc->shared_irq)
+ {
+ if (request_irq(devc->irq, mpuintr, 0, "mpu401",
+ hw_config) < 0)
+ {
+ printk(KERN_WARNING "mpu401: Failed to allocate IRQ%d\n", devc->irq);
+ ret = -ENOMEM;
+ goto out_mididev;
+ }
+ }
+ spin_lock_irqsave(&devc->lock,flags);
+ mpu401_chk_version(m, devc);
+ if (devc->version == 0)
+ mpu401_chk_version(m, devc);
+ spin_unlock_irqrestore(&devc->lock, flags);
+ }
+
+ if (devc->version != 0)
+ if (mpu_cmd(m, 0xC5, 0) >= 0) /* Set timebase OK */
+ if (mpu_cmd(m, 0xE0, 120) >= 0) /* Set tempo OK */
+ devc->capabilities |= MPU_CAP_INTLG; /* Supports intelligent mode */
+
+
+ mpu401_synth_operations[m] = kmalloc(sizeof(struct synth_operations), GFP_KERNEL);
+
+ if (mpu401_synth_operations[m] == NULL)
+ {
+ printk(KERN_ERR "mpu401: Can't allocate memory\n");
+ ret = -ENOMEM;
+ goto out_irq;
+ }
+ if (!(devc->capabilities & MPU_CAP_INTLG)) /* No intelligent mode */
+ {
+ memcpy((char *) mpu401_synth_operations[m],
+ (char *) &std_midi_synth,
+ sizeof(struct synth_operations));
+ }
+ else
+ {
+ memcpy((char *) mpu401_synth_operations[m],
+ (char *) &mpu401_synth_proto,
+ sizeof(struct synth_operations));
+ }
+ if (owner)
+ mpu401_synth_operations[m]->owner = owner;
+
+ memcpy((char *) &mpu401_midi_operations[m],
+ (char *) &mpu401_midi_proto,
+ sizeof(struct midi_operations));
+
+ mpu401_midi_operations[m].converter = mpu401_synth_operations[m];
+
+ memcpy((char *) &mpu_synth_info[m],
+ (char *) &mpu_synth_info_proto,
+ sizeof(struct synth_info));
+
+ n_mpu_devs++;
+
+ if (devc->version == 0x20 && devc->revision >= 0x07) /* MusicQuest interface */
+ {
+ int ports = (devc->revision & 0x08) ? 32 : 16;
+
+ devc->capabilities |= MPU_CAP_SYNC | MPU_CAP_SMPTE |
+ MPU_CAP_CLS | MPU_CAP_2PORT;
+
+ revision_char = (devc->revision == 0x7f) ? 'M' : ' ';
+ sprintf(mpu_synth_info[m].name, "MQX-%d%c MIDI Interface #%d",
+ ports,
+ revision_char,
+ n_mpu_devs);
+ }
+ else
+ {
+ revision_char = devc->revision ? devc->revision + '@' : ' ';
+ if ((int) devc->revision > ('Z' - '@'))
+ revision_char = '+';
+
+ devc->capabilities |= MPU_CAP_SYNC | MPU_CAP_FSK;
+
+ if (hw_config->name)
+ sprintf(mpu_synth_info[m].name, "%s (MPU401)", hw_config->name);
+ else
+ sprintf(mpu_synth_info[m].name,
+ "MPU-401 %d.%d%c MIDI #%d",
+ (int) (devc->version & 0xf0) >> 4,
+ devc->version & 0x0f,
+ revision_char,
+ n_mpu_devs);
+ }
+
+ strcpy(mpu401_midi_operations[m].info.name,
+ mpu_synth_info[m].name);
+
+ conf_printf(mpu_synth_info[m].name, hw_config);
+
+ mpu401_synth_operations[m]->midi_dev = devc->devno = m;
+ mpu401_synth_operations[devc->devno]->info = &mpu_synth_info[devc->devno];
+
+ if (devc->capabilities & MPU_CAP_INTLG) /* Intelligent mode */
+ hw_config->slots[2] = mpu_timer_init(m);
+
+ midi_devs[m] = &mpu401_midi_operations[devc->devno];
+
+ if (owner)
+ midi_devs[m]->owner = owner;
+
+ hw_config->slots[1] = m;
+ sequencer_init();
+
+ return 0;
+
+out_irq:
+ free_irq(devc->irq, hw_config);
+out_mididev:
+ sound_unload_mididev(m);
+out_err:
+ release_region(hw_config->io_base, 2);
+ return ret;
+}
+
+static int reset_mpu401(struct mpu_config *devc)
+{
+ unsigned long flags;
+ int ok, timeout, n;
+ int timeout_limit;
+
+ /*
+ * Send the RESET command. Try again if no success at the first time.
+ * (If the device is in the UART mode, it will not ack the reset cmd).
+ */
+
+ ok = 0;
+
+ timeout_limit = devc->initialized ? 30000 : 100000;
+ devc->initialized = 1;
+
+ for (n = 0; n < 2 && !ok; n++)
+ {
+ for (timeout = timeout_limit; timeout > 0 && !ok; timeout--)
+ ok = output_ready(devc);
+
+ write_command(devc, MPU_RESET); /*
+ * Send MPU-401 RESET Command
+ */
+
+ /*
+ * Wait at least 25 msec. This method is not accurate so let's make the
+ * loop bit longer. Cannot sleep since this is called during boot.
+ */
+
+ for (timeout = timeout_limit * 2; timeout > 0 && !ok; timeout--)
+ {
+ spin_lock_irqsave(&devc->lock,flags);
+ if (input_avail(devc))
+ if (read_data(devc) == MPU_ACK)
+ ok = 1;
+ spin_unlock_irqrestore(&devc->lock,flags);
+ }
+
+ }
+
+ devc->m_state = ST_INIT;
+ devc->m_ptr = 0;
+ devc->m_left = 0;
+ devc->last_status = 0;
+ devc->uart_mode = 0;
+
+ return ok;
+}
+
+static void set_uart_mode(int dev, struct mpu_config *devc, int arg)
+{
+ if (!arg && (devc->capabilities & MPU_CAP_INTLG))
+ return;
+ if ((devc->uart_mode == 0) == (arg == 0))
+ return; /* Already set */
+ reset_mpu401(devc); /* This exits the uart mode */
+
+ if (arg)
+ {
+ if (mpu_cmd(dev, UART_MODE_ON, 0) < 0)
+ {
+ printk(KERN_ERR "mpu401: Can't enter UART mode\n");
+ devc->uart_mode = 0;
+ return;
+ }
+ }
+ devc->uart_mode = arg;
+
+}
+
+int probe_mpu401(struct address_info *hw_config, struct resource *ports)
+{
+ int ok = 0;
+ struct mpu_config tmp_devc;
+
+ tmp_devc.base = hw_config->io_base;
+ tmp_devc.irq = hw_config->irq;
+ tmp_devc.initialized = 0;
+ tmp_devc.opened = 0;
+ tmp_devc.osp = hw_config->osp;
+
+ if (hw_config->always_detect)
+ return 1;
+
+ if (inb(hw_config->io_base + 1) == 0xff)
+ {
+ DDB(printk("MPU401: Port %x looks dead.\n", hw_config->io_base));
+ return 0; /* Just bus float? */
+ }
+ ok = reset_mpu401(&tmp_devc);
+
+ if (!ok)
+ {
+ DDB(printk("MPU401: Reset failed on port %x\n", hw_config->io_base));
+ }
+ return ok;
+}
+
+void unload_mpu401(struct address_info *hw_config)
+{
+ void *p;
+ int n=hw_config->slots[1];
+
+ if (n != -1) {
+ release_region(hw_config->io_base, 2);
+ if (hw_config->always_detect == 0 && hw_config->irq > 0)
+ free_irq(hw_config->irq, hw_config);
+ p=mpu401_synth_operations[n];
+ sound_unload_mididev(n);
+ sound_unload_timerdev(hw_config->slots[2]);
+ kfree(p);
+ }
+}
+
+/*****************************************************
+ * Timer stuff
+ ****************************************************/
+
+static volatile int timer_initialized = 0, timer_open = 0, tmr_running = 0;
+static volatile int curr_tempo, curr_timebase, hw_timebase;
+static int max_timebase = 8; /* 8*24=192 ppqn */
+static volatile unsigned long next_event_time;
+static volatile unsigned long curr_ticks, curr_clocks;
+static unsigned long prev_event_time;
+static int metronome_mode;
+
+static unsigned long clocks2ticks(unsigned long clocks)
+{
+ /*
+ * The MPU-401 supports just a limited set of possible timebase values.
+ * Since the applications require more choices, the driver has to
+ * program the HW to do its best and to convert between the HW and
+ * actual timebases.
+ */
+ return ((clocks * curr_timebase) + (hw_timebase / 2)) / hw_timebase;
+}
+
+static void set_timebase(int midi_dev, int val)
+{
+ int hw_val;
+
+ if (val < 48)
+ val = 48;
+ if (val > 1000)
+ val = 1000;
+
+ hw_val = val;
+ hw_val = (hw_val + 12) / 24;
+ if (hw_val > max_timebase)
+ hw_val = max_timebase;
+
+ if (mpu_cmd(midi_dev, 0xC0 | (hw_val & 0x0f), 0) < 0)
+ {
+ printk(KERN_WARNING "mpu401: Can't set HW timebase to %d\n", hw_val * 24);
+ return;
+ }
+ hw_timebase = hw_val * 24;
+ curr_timebase = val;
+
+}
+
+static void tmr_reset(struct mpu_config *devc)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&devc->lock,flags);
+ next_event_time = (unsigned long) -1;
+ prev_event_time = 0;
+ curr_ticks = curr_clocks = 0;
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void set_timer_mode(int midi_dev)
+{
+ if (timer_mode & TMR_MODE_CLS)
+ mpu_cmd(midi_dev, 0x3c, 0); /* Use CLS sync */
+ else if (timer_mode & TMR_MODE_SMPTE)
+ mpu_cmd(midi_dev, 0x3d, 0); /* Use SMPTE sync */
+
+ if (timer_mode & TMR_INTERNAL)
+ {
+ mpu_cmd(midi_dev, 0x80, 0); /* Use MIDI sync */
+ }
+ else
+ {
+ if (timer_mode & (TMR_MODE_MIDI | TMR_MODE_CLS))
+ {
+ mpu_cmd(midi_dev, 0x82, 0); /* Use MIDI sync */
+ mpu_cmd(midi_dev, 0x91, 0); /* Enable ext MIDI ctrl */
+ }
+ else if (timer_mode & TMR_MODE_FSK)
+ mpu_cmd(midi_dev, 0x81, 0); /* Use FSK sync */
+ }
+}
+
+static void stop_metronome(int midi_dev)
+{
+ mpu_cmd(midi_dev, 0x84, 0); /* Disable metronome */
+}
+
+static void setup_metronome(int midi_dev)
+{
+ int numerator, denominator;
+ int clks_per_click, num_32nds_per_beat;
+ int beats_per_measure;
+
+ numerator = ((unsigned) metronome_mode >> 24) & 0xff;
+ denominator = ((unsigned) metronome_mode >> 16) & 0xff;
+ clks_per_click = ((unsigned) metronome_mode >> 8) & 0xff;
+ num_32nds_per_beat = (unsigned) metronome_mode & 0xff;
+ beats_per_measure = (numerator * 4) >> denominator;
+
+ if (!metronome_mode)
+ mpu_cmd(midi_dev, 0x84, 0); /* Disable metronome */
+ else
+ {
+ mpu_cmd(midi_dev, 0xE4, clks_per_click);
+ mpu_cmd(midi_dev, 0xE6, beats_per_measure);
+ mpu_cmd(midi_dev, 0x83, 0); /* Enable metronome without accents */
+ }
+}
+
+static int mpu_start_timer(int midi_dev)
+{
+ struct mpu_config *devc= &dev_conf[midi_dev];
+
+ tmr_reset(devc);
+ set_timer_mode(midi_dev);
+
+ if (tmr_running)
+ return TIMER_NOT_ARMED; /* Already running */
+
+ if (timer_mode & TMR_INTERNAL)
+ {
+ mpu_cmd(midi_dev, 0x02, 0); /* Send MIDI start */
+ tmr_running = 1;
+ return TIMER_NOT_ARMED;
+ }
+ else
+ {
+ mpu_cmd(midi_dev, 0x35, 0); /* Enable mode messages to PC */
+ mpu_cmd(midi_dev, 0x38, 0); /* Enable sys common messages to PC */
+ mpu_cmd(midi_dev, 0x39, 0); /* Enable real time messages to PC */
+ mpu_cmd(midi_dev, 0x97, 0); /* Enable system exclusive messages to PC */
+ }
+ return TIMER_ARMED;
+}
+
+static int mpu_timer_open(int dev, int mode)
+{
+ int midi_dev = sound_timer_devs[dev]->devlink;
+ struct mpu_config *devc= &dev_conf[midi_dev];
+
+ if (timer_open)
+ return -EBUSY;
+
+ tmr_reset(devc);
+ curr_tempo = 50;
+ mpu_cmd(midi_dev, 0xE0, 50);
+ curr_timebase = hw_timebase = 120;
+ set_timebase(midi_dev, 120);
+ timer_open = 1;
+ metronome_mode = 0;
+ set_timer_mode(midi_dev);
+
+ mpu_cmd(midi_dev, 0xe7, 0x04); /* Send all clocks to host */
+ mpu_cmd(midi_dev, 0x95, 0); /* Enable clock to host */
+
+ return 0;
+}
+
+static void mpu_timer_close(int dev)
+{
+ int midi_dev = sound_timer_devs[dev]->devlink;
+
+ timer_open = tmr_running = 0;
+ mpu_cmd(midi_dev, 0x15, 0); /* Stop all */
+ mpu_cmd(midi_dev, 0x94, 0); /* Disable clock to host */
+ mpu_cmd(midi_dev, 0x8c, 0); /* Disable measure end messages to host */
+ stop_metronome(midi_dev);
+}
+
+static int mpu_timer_event(int dev, unsigned char *event)
+{
+ unsigned char command = event[1];
+ unsigned long parm = *(unsigned int *) &event[4];
+ int midi_dev = sound_timer_devs[dev]->devlink;
+
+ switch (command)
+ {
+ case TMR_WAIT_REL:
+ parm += prev_event_time;
+ case TMR_WAIT_ABS:
+ if (parm > 0)
+ {
+ long time;
+
+ if (parm <= curr_ticks) /* It's the time */
+ return TIMER_NOT_ARMED;
+ time = parm;
+ next_event_time = prev_event_time = time;
+
+ return TIMER_ARMED;
+ }
+ break;
+
+ case TMR_START:
+ if (tmr_running)
+ break;
+ return mpu_start_timer(midi_dev);
+
+ case TMR_STOP:
+ mpu_cmd(midi_dev, 0x01, 0); /* Send MIDI stop */
+ stop_metronome(midi_dev);
+ tmr_running = 0;
+ break;
+
+ case TMR_CONTINUE:
+ if (tmr_running)
+ break;
+ mpu_cmd(midi_dev, 0x03, 0); /* Send MIDI continue */
+ setup_metronome(midi_dev);
+ tmr_running = 1;
+ break;
+
+ case TMR_TEMPO:
+ if (parm)
+ {
+ if (parm < 8)
+ parm = 8;
+ if (parm > 250)
+ parm = 250;
+ if (mpu_cmd(midi_dev, 0xE0, parm) < 0)
+ printk(KERN_WARNING "mpu401: Can't set tempo to %d\n", (int) parm);
+ curr_tempo = parm;
+ }
+ break;
+
+ case TMR_ECHO:
+ seq_copy_to_input(event, 8);
+ break;
+
+ case TMR_TIMESIG:
+ if (metronome_mode) /* Metronome enabled */
+ {
+ metronome_mode = parm;
+ setup_metronome(midi_dev);
+ }
+ break;
+
+ default:;
+ }
+ return TIMER_NOT_ARMED;
+}
+
+static unsigned long mpu_timer_get_time(int dev)
+{
+ if (!timer_open)
+ return 0;
+
+ return curr_ticks;
+}
+
+static int mpu_timer_ioctl(int dev, unsigned int command, void __user *arg)
+{
+ int midi_dev = sound_timer_devs[dev]->devlink;
+ int __user *p = (int __user *)arg;
+
+ switch (command)
+ {
+ case SNDCTL_TMR_SOURCE:
+ {
+ int parm;
+
+ if (get_user(parm, p))
+ return -EFAULT;
+ parm &= timer_caps;
+
+ if (parm != 0)
+ {
+ timer_mode = parm;
+
+ if (timer_mode & TMR_MODE_CLS)
+ mpu_cmd(midi_dev, 0x3c, 0); /* Use CLS sync */
+ else if (timer_mode & TMR_MODE_SMPTE)
+ mpu_cmd(midi_dev, 0x3d, 0); /* Use SMPTE sync */
+ }
+ if (put_user(timer_mode, p))
+ return -EFAULT;
+ return timer_mode;
+ }
+ break;
+
+ case SNDCTL_TMR_START:
+ mpu_start_timer(midi_dev);
+ return 0;
+
+ case SNDCTL_TMR_STOP:
+ tmr_running = 0;
+ mpu_cmd(midi_dev, 0x01, 0); /* Send MIDI stop */
+ stop_metronome(midi_dev);
+ return 0;
+
+ case SNDCTL_TMR_CONTINUE:
+ if (tmr_running)
+ return 0;
+ tmr_running = 1;
+ mpu_cmd(midi_dev, 0x03, 0); /* Send MIDI continue */
+ return 0;
+
+ case SNDCTL_TMR_TIMEBASE:
+ {
+ int val;
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val)
+ set_timebase(midi_dev, val);
+ if (put_user(curr_timebase, p))
+ return -EFAULT;
+ return curr_timebase;
+ }
+ break;
+
+ case SNDCTL_TMR_TEMPO:
+ {
+ int val;
+ int ret;
+
+ if (get_user(val, p))
+ return -EFAULT;
+
+ if (val)
+ {
+ if (val < 8)
+ val = 8;
+ if (val > 250)
+ val = 250;
+ if ((ret = mpu_cmd(midi_dev, 0xE0, val)) < 0)
+ {
+ printk(KERN_WARNING "mpu401: Can't set tempo to %d\n", (int) val);
+ return ret;
+ }
+ curr_tempo = val;
+ }
+ if (put_user(curr_tempo, p))
+ return -EFAULT;
+ return curr_tempo;
+ }
+ break;
+
+ case SNDCTL_SEQ_CTRLRATE:
+ {
+ int val;
+ if (get_user(val, p))
+ return -EFAULT;
+
+ if (val != 0) /* Can't change */
+ return -EINVAL;
+ val = ((curr_tempo * curr_timebase) + 30)/60;
+ if (put_user(val, p))
+ return -EFAULT;
+ return val;
+ }
+ break;
+
+ case SNDCTL_SEQ_GETTIME:
+ if (put_user(curr_ticks, p))
+ return -EFAULT;
+ return curr_ticks;
+
+ case SNDCTL_TMR_METRONOME:
+ if (get_user(metronome_mode, p))
+ return -EFAULT;
+ setup_metronome(midi_dev);
+ return 0;
+
+ default:;
+ }
+ return -EINVAL;
+}
+
+static void mpu_timer_arm(int dev, long time)
+{
+ if (time < 0)
+ time = curr_ticks + 1;
+ else if (time <= curr_ticks) /* It's the time */
+ return;
+ next_event_time = prev_event_time = time;
+ return;
+}
+
+static struct sound_timer_operations mpu_timer =
+{
+ .owner = THIS_MODULE,
+ .info = {"MPU-401 Timer", 0},
+ .priority = 10, /* Priority */
+ .devlink = 0, /* Local device link */
+ .open = mpu_timer_open,
+ .close = mpu_timer_close,
+ .event = mpu_timer_event,
+ .get_time = mpu_timer_get_time,
+ .ioctl = mpu_timer_ioctl,
+ .arm_timer = mpu_timer_arm
+};
+
+static void mpu_timer_interrupt(void)
+{
+ if (!timer_open)
+ return;
+
+ if (!tmr_running)
+ return;
+
+ curr_clocks++;
+ curr_ticks = clocks2ticks(curr_clocks);
+
+ if (curr_ticks >= next_event_time)
+ {
+ next_event_time = (unsigned long) -1;
+ sequencer_timer(0);
+ }
+}
+
+static void timer_ext_event(struct mpu_config *devc, int event, int parm)
+{
+ int midi_dev = devc->devno;
+
+ if (!devc->timer_flag)
+ return;
+
+ switch (event)
+ {
+ case TMR_CLOCK:
+ printk("<MIDI clk>");
+ break;
+
+ case TMR_START:
+ printk("Ext MIDI start\n");
+ if (!tmr_running)
+ {
+ if (timer_mode & TMR_EXTERNAL)
+ {
+ tmr_running = 1;
+ setup_metronome(midi_dev);
+ next_event_time = 0;
+ STORE(SEQ_START_TIMER());
+ }
+ }
+ break;
+
+ case TMR_STOP:
+ printk("Ext MIDI stop\n");
+ if (timer_mode & TMR_EXTERNAL)
+ {
+ tmr_running = 0;
+ stop_metronome(midi_dev);
+ STORE(SEQ_STOP_TIMER());
+ }
+ break;
+
+ case TMR_CONTINUE:
+ printk("Ext MIDI continue\n");
+ if (timer_mode & TMR_EXTERNAL)
+ {
+ tmr_running = 1;
+ setup_metronome(midi_dev);
+ STORE(SEQ_CONTINUE_TIMER());
+ }
+ break;
+
+ case TMR_SPP:
+ printk("Songpos: %d\n", parm);
+ if (timer_mode & TMR_EXTERNAL)
+ {
+ STORE(SEQ_SONGPOS(parm));
+ }
+ break;
+ }
+}
+
+static int mpu_timer_init(int midi_dev)
+{
+ struct mpu_config *devc;
+ int n;
+
+ devc = &dev_conf[midi_dev];
+
+ if (timer_initialized)
+ return -1; /* There is already a similar timer */
+
+ timer_initialized = 1;
+
+ mpu_timer.devlink = midi_dev;
+ dev_conf[midi_dev].timer_flag = 1;
+
+ n = sound_alloc_timerdev();
+ if (n == -1)
+ n = 0;
+ sound_timer_devs[n] = &mpu_timer;
+
+ if (devc->version < 0x20) /* Original MPU-401 */
+ timer_caps = TMR_INTERNAL | TMR_EXTERNAL | TMR_MODE_FSK | TMR_MODE_MIDI;
+ else
+ {
+ /*
+ * The version number 2.0 is used (at least) by the
+ * MusicQuest cards and the Roland Super-MPU.
+ *
+ * MusicQuest has given a special meaning to the bits of the
+ * revision number. The Super-MPU returns 0.
+ */
+
+ if (devc->revision)
+ timer_caps |= TMR_EXTERNAL | TMR_MODE_MIDI;
+
+ if (devc->revision & 0x02)
+ timer_caps |= TMR_MODE_CLS;
+
+
+ if (devc->revision & 0x40)
+ max_timebase = 10; /* Has the 216 and 240 ppqn modes */
+ }
+
+ timer_mode = (TMR_INTERNAL | TMR_MODE_MIDI) & timer_caps;
+ return n;
+
+}
+
+EXPORT_SYMBOL(probe_mpu401);
+EXPORT_SYMBOL(attach_mpu401);
+EXPORT_SYMBOL(unload_mpu401);
+
+static struct address_info cfg;
+
+static int io = -1;
+static int irq = -1;
+
+module_param(irq, int, 0);
+module_param(io, int, 0);
+
+static int __init init_mpu401(void)
+{
+ int ret;
+ /* Can be loaded either for module use or to provide functions
+ to others */
+ if (io != -1 && irq != -1) {
+ struct resource *ports;
+ cfg.irq = irq;
+ cfg.io_base = io;
+ ports = request_region(io, 2, "mpu401");
+ if (!ports)
+ return -EBUSY;
+ if (probe_mpu401(&cfg, ports) == 0) {
+ release_region(io, 2);
+ return -ENODEV;
+ }
+ if ((ret = attach_mpu401(&cfg, THIS_MODULE)))
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit cleanup_mpu401(void)
+{
+ if (io != -1 && irq != -1) {
+ /* Check for use by, for example, sscape driver */
+ unload_mpu401(&cfg);
+ }
+}
+
+module_init(init_mpu401);
+module_exit(cleanup_mpu401);
+
+#ifndef MODULE
+static int __init setup_mpu401(char *str)
+{
+ /* io, irq */
+ int ints[3];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ io = ints[1];
+ irq = ints[2];
+
+ return 1;
+}
+
+__setup("mpu401=", setup_mpu401);
+#endif
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/mpu401.h b/sound/oss/mpu401.h
new file mode 100644
index 00000000..0ad1e9ee
--- /dev/null
+++ b/sound/oss/mpu401.h
@@ -0,0 +1,11 @@
+
+/* From uart401.c */
+int probe_uart401 (struct address_info *hw_config, struct module *owner);
+void unload_uart401 (struct address_info *hw_config);
+
+irqreturn_t uart401intr (int irq, void *dev_id);
+
+/* From mpu401.c */
+int probe_mpu401(struct address_info *hw_config, struct resource *ports);
+int attach_mpu401(struct address_info * hw_config, struct module *owner);
+void unload_mpu401(struct address_info *hw_info);
diff --git a/sound/oss/msnd.c b/sound/oss/msnd.c
new file mode 100644
index 00000000..c0cc951b
--- /dev/null
+++ b/sound/oss/msnd.c
@@ -0,0 +1,413 @@
+/*********************************************************************
+ *
+ * msnd.c - Driver Base
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/vmalloc.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/spinlock.h>
+#include <asm/irq.h>
+#include "msnd.h"
+
+#define LOGNAME "msnd"
+
+#define MSND_MAX_DEVS 4
+
+static multisound_dev_t *devs[MSND_MAX_DEVS];
+static int num_devs;
+
+int msnd_register(multisound_dev_t *dev)
+{
+ int i;
+
+ for (i = 0; i < MSND_MAX_DEVS; ++i)
+ if (devs[i] == NULL)
+ break;
+
+ if (i == MSND_MAX_DEVS)
+ return -ENOMEM;
+
+ devs[i] = dev;
+ ++num_devs;
+ return 0;
+}
+
+void msnd_unregister(multisound_dev_t *dev)
+{
+ int i;
+
+ for (i = 0; i < MSND_MAX_DEVS; ++i)
+ if (devs[i] == dev)
+ break;
+
+ if (i == MSND_MAX_DEVS) {
+ printk(KERN_WARNING LOGNAME ": Unregistering unknown device\n");
+ return;
+ }
+
+ devs[i] = NULL;
+ --num_devs;
+}
+
+void msnd_init_queue(void __iomem *base, int start, int size)
+{
+ writew(PCTODSP_BASED(start), base + JQS_wStart);
+ writew(PCTODSP_OFFSET(size) - 1, base + JQS_wSize);
+ writew(0, base + JQS_wHead);
+ writew(0, base + JQS_wTail);
+}
+
+void msnd_fifo_init(msnd_fifo *f)
+{
+ f->data = NULL;
+}
+
+void msnd_fifo_free(msnd_fifo *f)
+{
+ vfree(f->data);
+ f->data = NULL;
+}
+
+int msnd_fifo_alloc(msnd_fifo *f, size_t n)
+{
+ msnd_fifo_free(f);
+ f->data = vmalloc(n);
+ f->n = n;
+ f->tail = 0;
+ f->head = 0;
+ f->len = 0;
+
+ if (!f->data)
+ return -ENOMEM;
+
+ return 0;
+}
+
+void msnd_fifo_make_empty(msnd_fifo *f)
+{
+ f->len = f->tail = f->head = 0;
+}
+
+int msnd_fifo_write_io(msnd_fifo *f, char __iomem *buf, size_t len)
+{
+ int count = 0;
+
+ while ((count < len) && (f->len != f->n)) {
+
+ int nwritten;
+
+ if (f->head <= f->tail) {
+ nwritten = len - count;
+ if (nwritten > f->n - f->tail)
+ nwritten = f->n - f->tail;
+ }
+ else {
+ nwritten = f->head - f->tail;
+ if (nwritten > len - count)
+ nwritten = len - count;
+ }
+
+ memcpy_fromio(f->data + f->tail, buf, nwritten);
+
+ count += nwritten;
+ buf += nwritten;
+ f->len += nwritten;
+ f->tail += nwritten;
+ f->tail %= f->n;
+ }
+
+ return count;
+}
+
+int msnd_fifo_write(msnd_fifo *f, const char *buf, size_t len)
+{
+ int count = 0;
+
+ while ((count < len) && (f->len != f->n)) {
+
+ int nwritten;
+
+ if (f->head <= f->tail) {
+ nwritten = len - count;
+ if (nwritten > f->n - f->tail)
+ nwritten = f->n - f->tail;
+ }
+ else {
+ nwritten = f->head - f->tail;
+ if (nwritten > len - count)
+ nwritten = len - count;
+ }
+
+ memcpy(f->data + f->tail, buf, nwritten);
+
+ count += nwritten;
+ buf += nwritten;
+ f->len += nwritten;
+ f->tail += nwritten;
+ f->tail %= f->n;
+ }
+
+ return count;
+}
+
+int msnd_fifo_read_io(msnd_fifo *f, char __iomem *buf, size_t len)
+{
+ int count = 0;
+
+ while ((count < len) && (f->len > 0)) {
+
+ int nread;
+
+ if (f->tail <= f->head) {
+ nread = len - count;
+ if (nread > f->n - f->head)
+ nread = f->n - f->head;
+ }
+ else {
+ nread = f->tail - f->head;
+ if (nread > len - count)
+ nread = len - count;
+ }
+
+ memcpy_toio(buf, f->data + f->head, nread);
+
+ count += nread;
+ buf += nread;
+ f->len -= nread;
+ f->head += nread;
+ f->head %= f->n;
+ }
+
+ return count;
+}
+
+int msnd_fifo_read(msnd_fifo *f, char *buf, size_t len)
+{
+ int count = 0;
+
+ while ((count < len) && (f->len > 0)) {
+
+ int nread;
+
+ if (f->tail <= f->head) {
+ nread = len - count;
+ if (nread > f->n - f->head)
+ nread = f->n - f->head;
+ }
+ else {
+ nread = f->tail - f->head;
+ if (nread > len - count)
+ nread = len - count;
+ }
+
+ memcpy(buf, f->data + f->head, nread);
+
+ count += nread;
+ buf += nread;
+ f->len -= nread;
+ f->head += nread;
+ f->head %= f->n;
+ }
+
+ return count;
+}
+
+static int msnd_wait_TXDE(multisound_dev_t *dev)
+{
+ register unsigned int io = dev->io;
+ register int timeout = 1000;
+
+ while(timeout-- > 0)
+ if (msnd_inb(io + HP_ISR) & HPISR_TXDE)
+ return 0;
+
+ return -EIO;
+}
+
+static int msnd_wait_HC0(multisound_dev_t *dev)
+{
+ register unsigned int io = dev->io;
+ register int timeout = 1000;
+
+ while(timeout-- > 0)
+ if (!(msnd_inb(io + HP_CVR) & HPCVR_HC))
+ return 0;
+
+ return -EIO;
+}
+
+int msnd_send_dsp_cmd(multisound_dev_t *dev, BYTE cmd)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (msnd_wait_HC0(dev) == 0) {
+ msnd_outb(cmd, dev->io + HP_CVR);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return 0;
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ printk(KERN_DEBUG LOGNAME ": Send DSP command timeout\n");
+
+ return -EIO;
+}
+
+int msnd_send_word(multisound_dev_t *dev, unsigned char high,
+ unsigned char mid, unsigned char low)
+{
+ register unsigned int io = dev->io;
+
+ if (msnd_wait_TXDE(dev) == 0) {
+ msnd_outb(high, io + HP_TXH);
+ msnd_outb(mid, io + HP_TXM);
+ msnd_outb(low, io + HP_TXL);
+ return 0;
+ }
+
+ printk(KERN_DEBUG LOGNAME ": Send host word timeout\n");
+
+ return -EIO;
+}
+
+int msnd_upload_host(multisound_dev_t *dev, char *bin, int len)
+{
+ int i;
+
+ if (len % 3 != 0) {
+ printk(KERN_WARNING LOGNAME ": Upload host data not multiple of 3!\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < len; i += 3)
+ if (msnd_send_word(dev, bin[i], bin[i + 1], bin[i + 2]) != 0)
+ return -EIO;
+
+ msnd_inb(dev->io + HP_RXL);
+ msnd_inb(dev->io + HP_CVR);
+
+ return 0;
+}
+
+int msnd_enable_irq(multisound_dev_t *dev)
+{
+ unsigned long flags;
+
+ if (dev->irq_ref++)
+ return 0;
+
+ printk(KERN_DEBUG LOGNAME ": Enabling IRQ\n");
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (msnd_wait_TXDE(dev) == 0) {
+ msnd_outb(msnd_inb(dev->io + HP_ICR) | HPICR_TREQ, dev->io + HP_ICR);
+ if (dev->type == msndClassic)
+ msnd_outb(dev->irqid, dev->io + HP_IRQM);
+ msnd_outb(msnd_inb(dev->io + HP_ICR) & ~HPICR_TREQ, dev->io + HP_ICR);
+ msnd_outb(msnd_inb(dev->io + HP_ICR) | HPICR_RREQ, dev->io + HP_ICR);
+ enable_irq(dev->irq);
+ msnd_init_queue(dev->DSPQ, dev->dspq_data_buff, dev->dspq_buff_size);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return 0;
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ printk(KERN_DEBUG LOGNAME ": Enable IRQ failed\n");
+
+ return -EIO;
+}
+
+int msnd_disable_irq(multisound_dev_t *dev)
+{
+ unsigned long flags;
+
+ if (--dev->irq_ref > 0)
+ return 0;
+
+ if (dev->irq_ref < 0)
+ printk(KERN_DEBUG LOGNAME ": IRQ ref count is %d\n", dev->irq_ref);
+
+ printk(KERN_DEBUG LOGNAME ": Disabling IRQ\n");
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (msnd_wait_TXDE(dev) == 0) {
+ msnd_outb(msnd_inb(dev->io + HP_ICR) & ~HPICR_RREQ, dev->io + HP_ICR);
+ if (dev->type == msndClassic)
+ msnd_outb(HPIRQ_NONE, dev->io + HP_IRQM);
+ disable_irq(dev->irq);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return 0;
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ printk(KERN_DEBUG LOGNAME ": Disable IRQ failed\n");
+
+ return -EIO;
+}
+
+#ifndef LINUX20
+EXPORT_SYMBOL(msnd_register);
+EXPORT_SYMBOL(msnd_unregister);
+
+EXPORT_SYMBOL(msnd_init_queue);
+
+EXPORT_SYMBOL(msnd_fifo_init);
+EXPORT_SYMBOL(msnd_fifo_free);
+EXPORT_SYMBOL(msnd_fifo_alloc);
+EXPORT_SYMBOL(msnd_fifo_make_empty);
+EXPORT_SYMBOL(msnd_fifo_write_io);
+EXPORT_SYMBOL(msnd_fifo_read_io);
+EXPORT_SYMBOL(msnd_fifo_write);
+EXPORT_SYMBOL(msnd_fifo_read);
+
+EXPORT_SYMBOL(msnd_send_dsp_cmd);
+EXPORT_SYMBOL(msnd_send_word);
+EXPORT_SYMBOL(msnd_upload_host);
+
+EXPORT_SYMBOL(msnd_enable_irq);
+EXPORT_SYMBOL(msnd_disable_irq);
+#endif
+
+#ifdef MODULE
+MODULE_AUTHOR ("Andrew Veliath <andrewtv@usa.net>");
+MODULE_DESCRIPTION ("Turtle Beach MultiSound Driver Base");
+MODULE_LICENSE("GPL");
+
+
+int init_module(void)
+{
+ return 0;
+}
+
+void cleanup_module(void)
+{
+}
+#endif
diff --git a/sound/oss/msnd.h b/sound/oss/msnd.h
new file mode 100644
index 00000000..c8be47ec
--- /dev/null
+++ b/sound/oss/msnd.h
@@ -0,0 +1,278 @@
+/*********************************************************************
+ *
+ * msnd.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+#ifndef __MSND_H
+#define __MSND_H
+
+#define VERSION "0.8.3.1"
+
+#define DEFSAMPLERATE DSP_DEFAULT_SPEED
+#define DEFSAMPLESIZE AFMT_U8
+#define DEFCHANNELS 1
+
+#define DEFFIFOSIZE 128
+
+#define SNDCARD_MSND 38
+
+#define SRAM_BANK_SIZE 0x8000
+#define SRAM_CNTL_START 0x7F00
+
+#define DSP_BASE_ADDR 0x4000
+#define DSP_BANK_BASE 0x4000
+
+#define HP_ICR 0x00
+#define HP_CVR 0x01
+#define HP_ISR 0x02
+#define HP_IVR 0x03
+#define HP_NU 0x04
+#define HP_INFO 0x04
+#define HP_TXH 0x05
+#define HP_RXH 0x05
+#define HP_TXM 0x06
+#define HP_RXM 0x06
+#define HP_TXL 0x07
+#define HP_RXL 0x07
+
+#define HP_ICR_DEF 0x00
+#define HP_CVR_DEF 0x12
+#define HP_ISR_DEF 0x06
+#define HP_IVR_DEF 0x0f
+#define HP_NU_DEF 0x00
+
+#define HP_IRQM 0x09
+
+#define HPR_BLRC 0x08
+#define HPR_SPR1 0x09
+#define HPR_SPR2 0x0A
+#define HPR_TCL0 0x0B
+#define HPR_TCL1 0x0C
+#define HPR_TCL2 0x0D
+#define HPR_TCL3 0x0E
+#define HPR_TCL4 0x0F
+
+#define HPICR_INIT 0x80
+#define HPICR_HM1 0x40
+#define HPICR_HM0 0x20
+#define HPICR_HF1 0x10
+#define HPICR_HF0 0x08
+#define HPICR_TREQ 0x02
+#define HPICR_RREQ 0x01
+
+#define HPCVR_HC 0x80
+
+#define HPISR_HREQ 0x80
+#define HPISR_DMA 0x40
+#define HPISR_HF3 0x10
+#define HPISR_HF2 0x08
+#define HPISR_TRDY 0x04
+#define HPISR_TXDE 0x02
+#define HPISR_RXDF 0x01
+
+#define HPIO_290 0
+#define HPIO_260 1
+#define HPIO_250 2
+#define HPIO_240 3
+#define HPIO_230 4
+#define HPIO_220 5
+#define HPIO_210 6
+#define HPIO_3E0 7
+
+#define HPMEM_NONE 0
+#define HPMEM_B000 1
+#define HPMEM_C800 2
+#define HPMEM_D000 3
+#define HPMEM_D400 4
+#define HPMEM_D800 5
+#define HPMEM_E000 6
+#define HPMEM_E800 7
+
+#define HPIRQ_NONE 0
+#define HPIRQ_5 1
+#define HPIRQ_7 2
+#define HPIRQ_9 3
+#define HPIRQ_10 4
+#define HPIRQ_11 5
+#define HPIRQ_12 6
+#define HPIRQ_15 7
+
+#define HIMT_PLAY_DONE 0x00
+#define HIMT_RECORD_DONE 0x01
+#define HIMT_MIDI_EOS 0x02
+#define HIMT_MIDI_OUT 0x03
+
+#define HIMT_MIDI_IN_UCHAR 0x0E
+#define HIMT_DSP 0x0F
+
+#define HDEX_BASE 0x92
+#define HDEX_PLAY_START (0 + HDEX_BASE)
+#define HDEX_PLAY_STOP (1 + HDEX_BASE)
+#define HDEX_PLAY_PAUSE (2 + HDEX_BASE)
+#define HDEX_PLAY_RESUME (3 + HDEX_BASE)
+#define HDEX_RECORD_START (4 + HDEX_BASE)
+#define HDEX_RECORD_STOP (5 + HDEX_BASE)
+#define HDEX_MIDI_IN_START (6 + HDEX_BASE)
+#define HDEX_MIDI_IN_STOP (7 + HDEX_BASE)
+#define HDEX_MIDI_OUT_START (8 + HDEX_BASE)
+#define HDEX_MIDI_OUT_STOP (9 + HDEX_BASE)
+#define HDEX_AUX_REQ (10 + HDEX_BASE)
+
+#define HIWORD(l) ((WORD)((((DWORD)(l)) >> 16) & 0xFFFF))
+#define LOWORD(l) ((WORD)(DWORD)(l))
+#define HIBYTE(w) ((BYTE)(((WORD)(w) >> 8) & 0xFF))
+#define LOBYTE(w) ((BYTE)(w))
+#define MAKELONG(low,hi) ((long)(((WORD)(low))|(((DWORD)((WORD)(hi)))<<16)))
+#define MAKEWORD(low,hi) ((WORD)(((BYTE)(low))|(((WORD)((BYTE)(hi)))<<8)))
+
+#define PCTODSP_OFFSET(w) (USHORT)((w)/2)
+#define PCTODSP_BASED(w) (USHORT)(((w)/2) + DSP_BASE_ADDR)
+#define DSPTOPC_BASED(w) (((w) - DSP_BASE_ADDR) * 2)
+
+#ifdef SLOWIO
+#define msnd_outb outb_p
+#define msnd_inb inb_p
+#else
+#define msnd_outb outb
+#define msnd_inb inb
+#endif
+
+/* JobQueueStruct */
+#define JQS_wStart 0x00
+#define JQS_wSize 0x02
+#define JQS_wHead 0x04
+#define JQS_wTail 0x06
+#define JQS__size 0x08
+
+/* DAQueueDataStruct */
+#define DAQDS_wStart 0x00
+#define DAQDS_wSize 0x02
+#define DAQDS_wFormat 0x04
+#define DAQDS_wSampleSize 0x06
+#define DAQDS_wChannels 0x08
+#define DAQDS_wSampleRate 0x0A
+#define DAQDS_wIntMsg 0x0C
+#define DAQDS_wFlags 0x0E
+#define DAQDS__size 0x10
+
+typedef u8 BYTE;
+typedef u16 USHORT;
+typedef u16 WORD;
+typedef u32 DWORD;
+typedef void __iomem * LPDAQD;
+
+/* Generic FIFO */
+typedef struct {
+ size_t n, len;
+ char *data;
+ int head, tail;
+} msnd_fifo;
+
+typedef struct multisound_dev {
+ /* Linux device info */
+ char *name;
+ int dsp_minor, mixer_minor;
+ int ext_midi_dev, hdr_midi_dev;
+
+ /* Hardware resources */
+ int io, numio;
+ int memid, irqid;
+ int irq, irq_ref;
+ unsigned char info;
+ void __iomem *base;
+
+ /* Motorola 56k DSP SMA */
+ void __iomem *SMA;
+ void __iomem *DAPQ, *DARQ, *MODQ, *MIDQ, *DSPQ;
+ void __iomem *pwDSPQData, *pwMIDQData, *pwMODQData;
+ int dspq_data_buff, dspq_buff_size;
+
+ /* State variables */
+ enum { msndClassic, msndPinnacle } type;
+ fmode_t mode;
+ unsigned long flags;
+#define F_RESETTING 0
+#define F_HAVEDIGITAL 1
+#define F_AUDIO_WRITE_INUSE 2
+#define F_WRITING 3
+#define F_WRITEBLOCK 4
+#define F_WRITEFLUSH 5
+#define F_AUDIO_READ_INUSE 6
+#define F_READING 7
+#define F_READBLOCK 8
+#define F_EXT_MIDI_INUSE 9
+#define F_HDR_MIDI_INUSE 10
+#define F_DISABLE_WRITE_NDELAY 11
+ wait_queue_head_t writeblock;
+ wait_queue_head_t readblock;
+ wait_queue_head_t writeflush;
+ spinlock_t lock;
+ int nresets;
+ unsigned long recsrc;
+ int left_levels[32];
+ int right_levels[32];
+ int mixer_mod_count;
+ int calibrate_signal;
+ int play_sample_size, play_sample_rate, play_channels;
+ int play_ndelay;
+ int rec_sample_size, rec_sample_rate, rec_channels;
+ int rec_ndelay;
+ BYTE bCurrentMidiPatch;
+
+ /* Digital audio FIFOs */
+ msnd_fifo DAPF, DARF;
+ int fifosize;
+ int last_playbank, last_recbank;
+
+ /* MIDI in callback */
+ void (*midi_in_interrupt)(struct multisound_dev *);
+} multisound_dev_t;
+
+#ifndef mdelay
+# define mdelay(a) udelay((a) * 1000)
+#endif
+
+int msnd_register(multisound_dev_t *dev);
+void msnd_unregister(multisound_dev_t *dev);
+
+void msnd_init_queue(void __iomem *, int start, int size);
+
+void msnd_fifo_init(msnd_fifo *f);
+void msnd_fifo_free(msnd_fifo *f);
+int msnd_fifo_alloc(msnd_fifo *f, size_t n);
+void msnd_fifo_make_empty(msnd_fifo *f);
+int msnd_fifo_write_io(msnd_fifo *f, char __iomem *buf, size_t len);
+int msnd_fifo_read_io(msnd_fifo *f, char __iomem *buf, size_t len);
+int msnd_fifo_write(msnd_fifo *f, const char *buf, size_t len);
+int msnd_fifo_read(msnd_fifo *f, char *buf, size_t len);
+
+int msnd_send_dsp_cmd(multisound_dev_t *dev, BYTE cmd);
+int msnd_send_word(multisound_dev_t *dev, unsigned char high,
+ unsigned char mid, unsigned char low);
+int msnd_upload_host(multisound_dev_t *dev, char *bin, int len);
+int msnd_enable_irq(multisound_dev_t *dev);
+int msnd_disable_irq(multisound_dev_t *dev);
+
+#endif /* __MSND_H */
diff --git a/sound/oss/msnd_classic.c b/sound/oss/msnd_classic.c
new file mode 100644
index 00000000..3b23a096
--- /dev/null
+++ b/sound/oss/msnd_classic.c
@@ -0,0 +1,3 @@
+/* The work is in msnd_pinnacle.c, just define MSND_CLASSIC before it. */
+#define MSND_CLASSIC
+#include "msnd_pinnacle.c"
diff --git a/sound/oss/msnd_classic.h b/sound/oss/msnd_classic.h
new file mode 100644
index 00000000..1a17dde2
--- /dev/null
+++ b/sound/oss/msnd_classic.h
@@ -0,0 +1,185 @@
+/*********************************************************************
+ *
+ * msnd_classic.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+#ifndef __MSND_CLASSIC_H
+#define __MSND_CLASSIC_H
+
+
+#define DSP_NUMIO 0x10
+
+#define HP_MEMM 0x08
+
+#define HP_BITM 0x0E
+#define HP_WAIT 0x0D
+#define HP_DSPR 0x0A
+#define HP_PROR 0x0B
+#define HP_BLKS 0x0C
+
+#define HPPRORESET_OFF 0
+#define HPPRORESET_ON 1
+
+#define HPDSPRESET_OFF 0
+#define HPDSPRESET_ON 1
+
+#define HPBLKSEL_0 0
+#define HPBLKSEL_1 1
+
+#define HPWAITSTATE_0 0
+#define HPWAITSTATE_1 1
+
+#define HPBITMODE_16 0
+#define HPBITMODE_8 1
+
+#define HIDSP_INT_PLAY_UNDER 0x00
+#define HIDSP_INT_RECORD_OVER 0x01
+#define HIDSP_INPUT_CLIPPING 0x02
+#define HIDSP_MIDI_IN_OVER 0x10
+#define HIDSP_MIDI_OVERRUN_ERR 0x13
+
+#define HDEXAR_CLEAR_PEAKS 1
+#define HDEXAR_IN_SET_POTS 2
+#define HDEXAR_AUX_SET_POTS 3
+#define HDEXAR_CAL_A_TO_D 4
+#define HDEXAR_RD_EXT_DSP_BITS 5
+
+#define TIME_PRO_RESET_DONE 0x028A
+#define TIME_PRO_SYSEX 0x0040
+#define TIME_PRO_RESET 0x0032
+
+#define AGND 0x01
+#define SIGNAL 0x02
+
+#define EXT_DSP_BIT_DCAL 0x0001
+#define EXT_DSP_BIT_MIDI_CON 0x0002
+
+#define BUFFSIZE 0x8000
+#define HOSTQ_SIZE 0x40
+
+#define SRAM_CNTL_START 0x7F00
+#define SMA_STRUCT_START 0x7F40
+
+#define DAP_BUFF_SIZE 0x2400
+#define DAR_BUFF_SIZE 0x2000
+
+#define DAPQ_STRUCT_SIZE 0x10
+#define DARQ_STRUCT_SIZE 0x10
+#define DAPQ_BUFF_SIZE (3 * 0x10)
+#define DARQ_BUFF_SIZE (3 * 0x10)
+#define MODQ_BUFF_SIZE 0x400
+#define MIDQ_BUFF_SIZE 0x200
+#define DSPQ_BUFF_SIZE 0x40
+
+#define DAPQ_DATA_BUFF 0x6C00
+#define DARQ_DATA_BUFF 0x6C30
+#define MODQ_DATA_BUFF 0x6C60
+#define MIDQ_DATA_BUFF 0x7060
+#define DSPQ_DATA_BUFF 0x7260
+
+#define DAPQ_OFFSET SRAM_CNTL_START
+#define DARQ_OFFSET (SRAM_CNTL_START + 0x08)
+#define MODQ_OFFSET (SRAM_CNTL_START + 0x10)
+#define MIDQ_OFFSET (SRAM_CNTL_START + 0x18)
+#define DSPQ_OFFSET (SRAM_CNTL_START + 0x20)
+
+#define MOP_SYNTH 0x10
+#define MOP_EXTOUT 0x32
+#define MOP_EXTTHRU 0x02
+#define MOP_OUTMASK 0x01
+
+#define MIP_EXTIN 0x01
+#define MIP_SYNTH 0x00
+#define MIP_INMASK 0x32
+
+/* Classic SMA Common Data */
+#define SMA_wCurrPlayBytes 0x0000
+#define SMA_wCurrRecordBytes 0x0002
+#define SMA_wCurrPlayVolLeft 0x0004
+#define SMA_wCurrPlayVolRight 0x0006
+#define SMA_wCurrInVolLeft 0x0008
+#define SMA_wCurrInVolRight 0x000a
+#define SMA_wUser_3 0x000c
+#define SMA_wUser_4 0x000e
+#define SMA_dwUser_5 0x0010
+#define SMA_dwUser_6 0x0014
+#define SMA_wUser_7 0x0018
+#define SMA_wReserved_A 0x001a
+#define SMA_wReserved_B 0x001c
+#define SMA_wReserved_C 0x001e
+#define SMA_wReserved_D 0x0020
+#define SMA_wReserved_E 0x0022
+#define SMA_wReserved_F 0x0024
+#define SMA_wReserved_G 0x0026
+#define SMA_wReserved_H 0x0028
+#define SMA_wCurrDSPStatusFlags 0x002a
+#define SMA_wCurrHostStatusFlags 0x002c
+#define SMA_wCurrInputTagBits 0x002e
+#define SMA_wCurrLeftPeak 0x0030
+#define SMA_wCurrRightPeak 0x0032
+#define SMA_wExtDSPbits 0x0034
+#define SMA_bExtHostbits 0x0036
+#define SMA_bBoardLevel 0x0037
+#define SMA_bInPotPosRight 0x0038
+#define SMA_bInPotPosLeft 0x0039
+#define SMA_bAuxPotPosRight 0x003a
+#define SMA_bAuxPotPosLeft 0x003b
+#define SMA_wCurrMastVolLeft 0x003c
+#define SMA_wCurrMastVolRight 0x003e
+#define SMA_bUser_12 0x0040
+#define SMA_bUser_13 0x0041
+#define SMA_wUser_14 0x0042
+#define SMA_wUser_15 0x0044
+#define SMA_wCalFreqAtoD 0x0046
+#define SMA_wUser_16 0x0048
+#define SMA_wUser_17 0x004a
+#define SMA__size 0x004c
+
+#ifdef HAVE_DSPCODEH
+# include "msndperm.c"
+# include "msndinit.c"
+# define PERMCODE msndperm
+# define INITCODE msndinit
+# define PERMCODESIZE sizeof(msndperm)
+# define INITCODESIZE sizeof(msndinit)
+#else
+# ifndef CONFIG_MSNDCLAS_INIT_FILE
+# define CONFIG_MSNDCLAS_INIT_FILE \
+ "/etc/sound/msndinit.bin"
+# endif
+# ifndef CONFIG_MSNDCLAS_PERM_FILE
+# define CONFIG_MSNDCLAS_PERM_FILE \
+ "/etc/sound/msndperm.bin"
+# endif
+# define PERMCODEFILE CONFIG_MSNDCLAS_PERM_FILE
+# define INITCODEFILE CONFIG_MSNDCLAS_INIT_FILE
+# define PERMCODE dspini
+# define INITCODE permini
+# define PERMCODESIZE sizeof_dspini
+# define INITCODESIZE sizeof_permini
+#endif
+#define LONGNAME "MultiSound (Classic/Monterey/Tahiti)"
+
+#endif /* __MSND_CLASSIC_H */
diff --git a/sound/oss/msnd_pinnacle.c b/sound/oss/msnd_pinnacle.c
new file mode 100644
index 00000000..7b5c77b3
--- /dev/null
+++ b/sound/oss/msnd_pinnacle.c
@@ -0,0 +1,1931 @@
+/*********************************************************************
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ * Linux 2.0/2.2 Version
+ *
+ * msnd_pinnacle.c / msnd_classic.c
+ *
+ * -- If MSND_CLASSIC is defined:
+ *
+ * -> driver for Turtle Beach Classic/Monterey/Tahiti
+ *
+ * -- Else
+ *
+ * -> driver for Turtle Beach Pinnacle/Fiji
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * 12-3-2000 Modified IO port validation Steve Sycamore
+ *
+ ********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/gfp.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+#include "sound_config.h"
+#include "sound_firmware.h"
+#ifdef MSND_CLASSIC
+# ifndef __alpha__
+# define SLOWIO
+# endif
+#endif
+#include "msnd.h"
+#ifdef MSND_CLASSIC
+# ifdef CONFIG_MSNDCLAS_HAVE_BOOT
+# define HAVE_DSPCODEH
+# endif
+# include "msnd_classic.h"
+# define LOGNAME "msnd_classic"
+#else
+# ifdef CONFIG_MSNDPIN_HAVE_BOOT
+# define HAVE_DSPCODEH
+# endif
+# include "msnd_pinnacle.h"
+# define LOGNAME "msnd_pinnacle"
+#endif
+
+#ifndef CONFIG_MSND_WRITE_NDELAY
+# define CONFIG_MSND_WRITE_NDELAY 1
+#endif
+
+#define get_play_delay_jiffies(size) ((size) * HZ * \
+ dev.play_sample_size / 8 / \
+ dev.play_sample_rate / \
+ dev.play_channels)
+
+#define get_rec_delay_jiffies(size) ((size) * HZ * \
+ dev.rec_sample_size / 8 / \
+ dev.rec_sample_rate / \
+ dev.rec_channels)
+
+static DEFINE_MUTEX(msnd_pinnacle_mutex);
+static multisound_dev_t dev;
+
+#ifndef HAVE_DSPCODEH
+static char *dspini, *permini;
+static int sizeof_dspini, sizeof_permini;
+#endif
+
+static int dsp_full_reset(void);
+static void dsp_write_flush(void);
+
+static __inline__ int chk_send_dsp_cmd(multisound_dev_t *dev, register BYTE cmd)
+{
+ if (msnd_send_dsp_cmd(dev, cmd) == 0)
+ return 0;
+ dsp_full_reset();
+ return msnd_send_dsp_cmd(dev, cmd);
+}
+
+static void reset_play_queue(void)
+{
+ int n;
+ LPDAQD lpDAQ;
+
+ dev.last_playbank = -1;
+ writew(PCTODSP_OFFSET(0 * DAQDS__size), dev.DAPQ + JQS_wHead);
+ writew(PCTODSP_OFFSET(0 * DAQDS__size), dev.DAPQ + JQS_wTail);
+
+ for (n = 0, lpDAQ = dev.base + DAPQ_DATA_BUFF; n < 3; ++n, lpDAQ += DAQDS__size) {
+ writew(PCTODSP_BASED((DWORD)(DAP_BUFF_SIZE * n)), lpDAQ + DAQDS_wStart);
+ writew(0, lpDAQ + DAQDS_wSize);
+ writew(1, lpDAQ + DAQDS_wFormat);
+ writew(dev.play_sample_size, lpDAQ + DAQDS_wSampleSize);
+ writew(dev.play_channels, lpDAQ + DAQDS_wChannels);
+ writew(dev.play_sample_rate, lpDAQ + DAQDS_wSampleRate);
+ writew(HIMT_PLAY_DONE * 0x100 + n, lpDAQ + DAQDS_wIntMsg);
+ writew(n, lpDAQ + DAQDS_wFlags);
+ }
+}
+
+static void reset_record_queue(void)
+{
+ int n;
+ LPDAQD lpDAQ;
+ unsigned long flags;
+
+ dev.last_recbank = 2;
+ writew(PCTODSP_OFFSET(0 * DAQDS__size), dev.DARQ + JQS_wHead);
+ writew(PCTODSP_OFFSET(dev.last_recbank * DAQDS__size), dev.DARQ + JQS_wTail);
+
+ /* Critical section: bank 1 access */
+ spin_lock_irqsave(&dev.lock, flags);
+ msnd_outb(HPBLKSEL_1, dev.io + HP_BLKS);
+ memset_io(dev.base, 0, DAR_BUFF_SIZE * 3);
+ msnd_outb(HPBLKSEL_0, dev.io + HP_BLKS);
+ spin_unlock_irqrestore(&dev.lock, flags);
+
+ for (n = 0, lpDAQ = dev.base + DARQ_DATA_BUFF; n < 3; ++n, lpDAQ += DAQDS__size) {
+ writew(PCTODSP_BASED((DWORD)(DAR_BUFF_SIZE * n)) + 0x4000, lpDAQ + DAQDS_wStart);
+ writew(DAR_BUFF_SIZE, lpDAQ + DAQDS_wSize);
+ writew(1, lpDAQ + DAQDS_wFormat);
+ writew(dev.rec_sample_size, lpDAQ + DAQDS_wSampleSize);
+ writew(dev.rec_channels, lpDAQ + DAQDS_wChannels);
+ writew(dev.rec_sample_rate, lpDAQ + DAQDS_wSampleRate);
+ writew(HIMT_RECORD_DONE * 0x100 + n, lpDAQ + DAQDS_wIntMsg);
+ writew(n, lpDAQ + DAQDS_wFlags);
+ }
+}
+
+static void reset_queues(void)
+{
+ if (dev.mode & FMODE_WRITE) {
+ msnd_fifo_make_empty(&dev.DAPF);
+ reset_play_queue();
+ }
+ if (dev.mode & FMODE_READ) {
+ msnd_fifo_make_empty(&dev.DARF);
+ reset_record_queue();
+ }
+}
+
+static int dsp_set_format(struct file *file, int val)
+{
+ int data, i;
+ LPDAQD lpDAQ, lpDARQ;
+
+ lpDAQ = dev.base + DAPQ_DATA_BUFF;
+ lpDARQ = dev.base + DARQ_DATA_BUFF;
+
+ switch (val) {
+ case AFMT_U8:
+ case AFMT_S16_LE:
+ data = val;
+ break;
+ default:
+ data = DEFSAMPLESIZE;
+ break;
+ }
+
+ for (i = 0; i < 3; ++i, lpDAQ += DAQDS__size, lpDARQ += DAQDS__size) {
+ if (file->f_mode & FMODE_WRITE)
+ writew(data, lpDAQ + DAQDS_wSampleSize);
+ if (file->f_mode & FMODE_READ)
+ writew(data, lpDARQ + DAQDS_wSampleSize);
+ }
+ if (file->f_mode & FMODE_WRITE)
+ dev.play_sample_size = data;
+ if (file->f_mode & FMODE_READ)
+ dev.rec_sample_size = data;
+
+ return data;
+}
+
+static int dsp_get_frag_size(void)
+{
+ int size;
+ size = dev.fifosize / 4;
+ if (size > 32 * 1024)
+ size = 32 * 1024;
+ return size;
+}
+
+static int dsp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int val, i, data, tmp;
+ LPDAQD lpDAQ, lpDARQ;
+ audio_buf_info abinfo;
+ unsigned long flags;
+ int __user *p = (int __user *)arg;
+
+ lpDAQ = dev.base + DAPQ_DATA_BUFF;
+ lpDARQ = dev.base + DARQ_DATA_BUFF;
+
+ switch (cmd) {
+ case SNDCTL_DSP_SUBDIVIDE:
+ case SNDCTL_DSP_SETFRAGMENT:
+ case SNDCTL_DSP_SETDUPLEX:
+ case SNDCTL_DSP_POST:
+ return 0;
+
+ case SNDCTL_DSP_GETIPTR:
+ case SNDCTL_DSP_GETOPTR:
+ case SNDCTL_DSP_MAPINBUF:
+ case SNDCTL_DSP_MAPOUTBUF:
+ return -EINVAL;
+
+ case SNDCTL_DSP_GETOSPACE:
+ if (!(file->f_mode & FMODE_WRITE))
+ return -EINVAL;
+ spin_lock_irqsave(&dev.lock, flags);
+ abinfo.fragsize = dsp_get_frag_size();
+ abinfo.bytes = dev.DAPF.n - dev.DAPF.len;
+ abinfo.fragstotal = dev.DAPF.n / abinfo.fragsize;
+ abinfo.fragments = abinfo.bytes / abinfo.fragsize;
+ spin_unlock_irqrestore(&dev.lock, flags);
+ return copy_to_user((void __user *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0;
+
+ case SNDCTL_DSP_GETISPACE:
+ if (!(file->f_mode & FMODE_READ))
+ return -EINVAL;
+ spin_lock_irqsave(&dev.lock, flags);
+ abinfo.fragsize = dsp_get_frag_size();
+ abinfo.bytes = dev.DARF.n - dev.DARF.len;
+ abinfo.fragstotal = dev.DARF.n / abinfo.fragsize;
+ abinfo.fragments = abinfo.bytes / abinfo.fragsize;
+ spin_unlock_irqrestore(&dev.lock, flags);
+ return copy_to_user((void __user *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0;
+
+ case SNDCTL_DSP_RESET:
+ dev.nresets = 0;
+ reset_queues();
+ return 0;
+
+ case SNDCTL_DSP_SYNC:
+ dsp_write_flush();
+ return 0;
+
+ case SNDCTL_DSP_GETBLKSIZE:
+ tmp = dsp_get_frag_size();
+ if (put_user(tmp, p))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_GETFMTS:
+ val = AFMT_S16_LE | AFMT_U8;
+ if (put_user(val, p))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_SETFMT:
+ if (get_user(val, p))
+ return -EFAULT;
+
+ if (file->f_mode & FMODE_WRITE)
+ data = val == AFMT_QUERY
+ ? dev.play_sample_size
+ : dsp_set_format(file, val);
+ else
+ data = val == AFMT_QUERY
+ ? dev.rec_sample_size
+ : dsp_set_format(file, val);
+
+ if (put_user(data, p))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_NONBLOCK:
+ if (!test_bit(F_DISABLE_WRITE_NDELAY, &dev.flags) &&
+ file->f_mode & FMODE_WRITE)
+ dev.play_ndelay = 1;
+ if (file->f_mode & FMODE_READ)
+ dev.rec_ndelay = 1;
+ return 0;
+
+ case SNDCTL_DSP_GETCAPS:
+ val = DSP_CAP_DUPLEX | DSP_CAP_BATCH;
+ if (put_user(val, p))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_SPEED:
+ if (get_user(val, p))
+ return -EFAULT;
+
+ if (val < 8000)
+ val = 8000;
+
+ if (val > 48000)
+ val = 48000;
+
+ data = val;
+
+ for (i = 0; i < 3; ++i, lpDAQ += DAQDS__size, lpDARQ += DAQDS__size) {
+ if (file->f_mode & FMODE_WRITE)
+ writew(data, lpDAQ + DAQDS_wSampleRate);
+ if (file->f_mode & FMODE_READ)
+ writew(data, lpDARQ + DAQDS_wSampleRate);
+ }
+ if (file->f_mode & FMODE_WRITE)
+ dev.play_sample_rate = data;
+ if (file->f_mode & FMODE_READ)
+ dev.rec_sample_rate = data;
+
+ if (put_user(data, p))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_CHANNELS:
+ case SNDCTL_DSP_STEREO:
+ if (get_user(val, p))
+ return -EFAULT;
+
+ if (cmd == SNDCTL_DSP_CHANNELS) {
+ switch (val) {
+ case 1:
+ case 2:
+ data = val;
+ break;
+ default:
+ val = data = 2;
+ break;
+ }
+ } else {
+ switch (val) {
+ case 0:
+ data = 1;
+ break;
+ default:
+ val = 1;
+ case 1:
+ data = 2;
+ break;
+ }
+ }
+
+ for (i = 0; i < 3; ++i, lpDAQ += DAQDS__size, lpDARQ += DAQDS__size) {
+ if (file->f_mode & FMODE_WRITE)
+ writew(data, lpDAQ + DAQDS_wChannels);
+ if (file->f_mode & FMODE_READ)
+ writew(data, lpDARQ + DAQDS_wChannels);
+ }
+ if (file->f_mode & FMODE_WRITE)
+ dev.play_channels = data;
+ if (file->f_mode & FMODE_READ)
+ dev.rec_channels = data;
+
+ if (put_user(val, p))
+ return -EFAULT;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int mixer_get(int d)
+{
+ if (d > 31)
+ return -EINVAL;
+
+ switch (d) {
+ case SOUND_MIXER_VOLUME:
+ case SOUND_MIXER_PCM:
+ case SOUND_MIXER_LINE:
+ case SOUND_MIXER_IMIX:
+ case SOUND_MIXER_LINE1:
+#ifndef MSND_CLASSIC
+ case SOUND_MIXER_MIC:
+ case SOUND_MIXER_SYNTH:
+#endif
+ return (dev.left_levels[d] >> 8) * 100 / 0xff |
+ (((dev.right_levels[d] >> 8) * 100 / 0xff) << 8);
+ default:
+ return 0;
+ }
+}
+
+#define update_volm(a,b) \
+ writew((dev.left_levels[a] >> 1) * \
+ readw(dev.SMA + SMA_wCurrMastVolLeft) / 0xffff, \
+ dev.SMA + SMA_##b##Left); \
+ writew((dev.right_levels[a] >> 1) * \
+ readw(dev.SMA + SMA_wCurrMastVolRight) / 0xffff, \
+ dev.SMA + SMA_##b##Right);
+
+#define update_potm(d,s,ar) \
+ writeb((dev.left_levels[d] >> 8) * \
+ readw(dev.SMA + SMA_wCurrMastVolLeft) / 0xffff, \
+ dev.SMA + SMA_##s##Left); \
+ writeb((dev.right_levels[d] >> 8) * \
+ readw(dev.SMA + SMA_wCurrMastVolRight) / 0xffff, \
+ dev.SMA + SMA_##s##Right); \
+ if (msnd_send_word(&dev, 0, 0, ar) == 0) \
+ chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+
+#define update_pot(d,s,ar) \
+ writeb(dev.left_levels[d] >> 8, \
+ dev.SMA + SMA_##s##Left); \
+ writeb(dev.right_levels[d] >> 8, \
+ dev.SMA + SMA_##s##Right); \
+ if (msnd_send_word(&dev, 0, 0, ar) == 0) \
+ chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+
+static int mixer_set(int d, int value)
+{
+ int left = value & 0x000000ff;
+ int right = (value & 0x0000ff00) >> 8;
+ int bLeft, bRight;
+ int wLeft, wRight;
+ int updatemaster = 0;
+
+ if (d > 31)
+ return -EINVAL;
+
+ bLeft = left * 0xff / 100;
+ wLeft = left * 0xffff / 100;
+
+ bRight = right * 0xff / 100;
+ wRight = right * 0xffff / 100;
+
+ dev.left_levels[d] = wLeft;
+ dev.right_levels[d] = wRight;
+
+ switch (d) {
+ /* master volume unscaled controls */
+ case SOUND_MIXER_LINE: /* line pot control */
+ /* scaled by IMIX in digital mix */
+ writeb(bLeft, dev.SMA + SMA_bInPotPosLeft);
+ writeb(bRight, dev.SMA + SMA_bInPotPosRight);
+ if (msnd_send_word(&dev, 0, 0, HDEXAR_IN_SET_POTS) == 0)
+ chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+ break;
+#ifndef MSND_CLASSIC
+ case SOUND_MIXER_MIC: /* mic pot control */
+ /* scaled by IMIX in digital mix */
+ writeb(bLeft, dev.SMA + SMA_bMicPotPosLeft);
+ writeb(bRight, dev.SMA + SMA_bMicPotPosRight);
+ if (msnd_send_word(&dev, 0, 0, HDEXAR_MIC_SET_POTS) == 0)
+ chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+ break;
+#endif
+ case SOUND_MIXER_VOLUME: /* master volume */
+ writew(wLeft, dev.SMA + SMA_wCurrMastVolLeft);
+ writew(wRight, dev.SMA + SMA_wCurrMastVolRight);
+ /* fall through */
+
+ case SOUND_MIXER_LINE1: /* aux pot control */
+ /* scaled by master volume */
+ /* fall through */
+
+ /* digital controls */
+ case SOUND_MIXER_SYNTH: /* synth vol (dsp mix) */
+ case SOUND_MIXER_PCM: /* pcm vol (dsp mix) */
+ case SOUND_MIXER_IMIX: /* input monitor (dsp mix) */
+ /* scaled by master volume */
+ updatemaster = 1;
+ break;
+
+ default:
+ return 0;
+ }
+
+ if (updatemaster) {
+ /* update master volume scaled controls */
+ update_volm(SOUND_MIXER_PCM, wCurrPlayVol);
+ update_volm(SOUND_MIXER_IMIX, wCurrInVol);
+#ifndef MSND_CLASSIC
+ update_volm(SOUND_MIXER_SYNTH, wCurrMHdrVol);
+#endif
+ update_potm(SOUND_MIXER_LINE1, bAuxPotPos, HDEXAR_AUX_SET_POTS);
+ }
+
+ return mixer_get(d);
+}
+
+static void mixer_setup(void)
+{
+ update_pot(SOUND_MIXER_LINE, bInPotPos, HDEXAR_IN_SET_POTS);
+ update_potm(SOUND_MIXER_LINE1, bAuxPotPos, HDEXAR_AUX_SET_POTS);
+ update_volm(SOUND_MIXER_PCM, wCurrPlayVol);
+ update_volm(SOUND_MIXER_IMIX, wCurrInVol);
+#ifndef MSND_CLASSIC
+ update_pot(SOUND_MIXER_MIC, bMicPotPos, HDEXAR_MIC_SET_POTS);
+ update_volm(SOUND_MIXER_SYNTH, wCurrMHdrVol);
+#endif
+}
+
+static unsigned long set_recsrc(unsigned long recsrc)
+{
+ if (dev.recsrc == recsrc)
+ return dev.recsrc;
+#ifdef HAVE_NORECSRC
+ else if (recsrc == 0)
+ dev.recsrc = 0;
+#endif
+ else
+ dev.recsrc ^= recsrc;
+
+#ifndef MSND_CLASSIC
+ if (dev.recsrc & SOUND_MASK_IMIX) {
+ if (msnd_send_word(&dev, 0, 0, HDEXAR_SET_ANA_IN) == 0)
+ chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+ }
+ else if (dev.recsrc & SOUND_MASK_SYNTH) {
+ if (msnd_send_word(&dev, 0, 0, HDEXAR_SET_SYNTH_IN) == 0)
+ chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+ }
+ else if ((dev.recsrc & SOUND_MASK_DIGITAL1) && test_bit(F_HAVEDIGITAL, &dev.flags)) {
+ if (msnd_send_word(&dev, 0, 0, HDEXAR_SET_DAT_IN) == 0)
+ chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+ }
+ else {
+#ifdef HAVE_NORECSRC
+ /* Select no input (?) */
+ dev.recsrc = 0;
+#else
+ dev.recsrc = SOUND_MASK_IMIX;
+ if (msnd_send_word(&dev, 0, 0, HDEXAR_SET_ANA_IN) == 0)
+ chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+#endif
+ }
+#endif /* MSND_CLASSIC */
+
+ return dev.recsrc;
+}
+
+static unsigned long force_recsrc(unsigned long recsrc)
+{
+ dev.recsrc = 0;
+ return set_recsrc(recsrc);
+}
+
+#define set_mixer_info() \
+ memset(&info, 0, sizeof(info)); \
+ strlcpy(info.id, "MSNDMIXER", sizeof(info.id)); \
+ strlcpy(info.name, "MultiSound Mixer", sizeof(info.name));
+
+static int mixer_ioctl(unsigned int cmd, unsigned long arg)
+{
+ if (cmd == SOUND_MIXER_INFO) {
+ mixer_info info;
+ set_mixer_info();
+ info.modify_counter = dev.mixer_mod_count;
+ if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ } else if (cmd == SOUND_OLD_MIXER_INFO) {
+ _old_mixer_info info;
+ set_mixer_info();
+ if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ } else if (cmd == SOUND_MIXER_PRIVATE1) {
+ dev.nresets = 0;
+ dsp_full_reset();
+ return 0;
+ } else if (((cmd >> 8) & 0xff) == 'M') {
+ int val = 0;
+
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+ switch (cmd & 0xff) {
+ case SOUND_MIXER_RECSRC:
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+ val = set_recsrc(val);
+ break;
+
+ default:
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+ val = mixer_set(cmd & 0xff, val);
+ break;
+ }
+ ++dev.mixer_mod_count;
+ return put_user(val, (int __user *)arg);
+ } else {
+ switch (cmd & 0xff) {
+ case SOUND_MIXER_RECSRC:
+ val = dev.recsrc;
+ break;
+
+ case SOUND_MIXER_DEVMASK:
+ case SOUND_MIXER_STEREODEVS:
+ val = SOUND_MASK_PCM |
+ SOUND_MASK_LINE |
+ SOUND_MASK_IMIX |
+ SOUND_MASK_LINE1 |
+#ifndef MSND_CLASSIC
+ SOUND_MASK_MIC |
+ SOUND_MASK_SYNTH |
+#endif
+ SOUND_MASK_VOLUME;
+ break;
+
+ case SOUND_MIXER_RECMASK:
+#ifdef MSND_CLASSIC
+ val = 0;
+#else
+ val = SOUND_MASK_IMIX |
+ SOUND_MASK_SYNTH;
+ if (test_bit(F_HAVEDIGITAL, &dev.flags))
+ val |= SOUND_MASK_DIGITAL1;
+#endif
+ break;
+
+ case SOUND_MIXER_CAPS:
+ val = SOUND_CAP_EXCL_INPUT;
+ break;
+
+ default:
+ if ((val = mixer_get(cmd & 0xff)) < 0)
+ return -EINVAL;
+ break;
+ }
+ }
+
+ return put_user(val, (int __user *)arg);
+ }
+
+ return -EINVAL;
+}
+
+static long dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int minor = iminor(file->f_path.dentry->d_inode);
+ int ret;
+
+ if (cmd == OSS_GETVERSION) {
+ int sound_version = SOUND_VERSION;
+ return put_user(sound_version, (int __user *)arg);
+ }
+
+ ret = -EINVAL;
+
+ mutex_lock(&msnd_pinnacle_mutex);
+ if (minor == dev.dsp_minor)
+ ret = dsp_ioctl(file, cmd, arg);
+ else if (minor == dev.mixer_minor)
+ ret = mixer_ioctl(cmd, arg);
+ mutex_unlock(&msnd_pinnacle_mutex);
+
+ return ret;
+}
+
+static void dsp_write_flush(void)
+{
+ if (!(dev.mode & FMODE_WRITE) || !test_bit(F_WRITING, &dev.flags))
+ return;
+ set_bit(F_WRITEFLUSH, &dev.flags);
+ interruptible_sleep_on_timeout(
+ &dev.writeflush,
+ get_play_delay_jiffies(dev.DAPF.len));
+ clear_bit(F_WRITEFLUSH, &dev.flags);
+ if (!signal_pending(current)) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(get_play_delay_jiffies(DAP_BUFF_SIZE));
+ }
+ clear_bit(F_WRITING, &dev.flags);
+}
+
+static void dsp_halt(struct file *file)
+{
+ if ((file ? file->f_mode : dev.mode) & FMODE_READ) {
+ clear_bit(F_READING, &dev.flags);
+ chk_send_dsp_cmd(&dev, HDEX_RECORD_STOP);
+ msnd_disable_irq(&dev);
+ if (file) {
+ printk(KERN_DEBUG LOGNAME ": Stopping read for %p\n", file);
+ dev.mode &= ~FMODE_READ;
+ }
+ clear_bit(F_AUDIO_READ_INUSE, &dev.flags);
+ }
+ if ((file ? file->f_mode : dev.mode) & FMODE_WRITE) {
+ if (test_bit(F_WRITING, &dev.flags)) {
+ dsp_write_flush();
+ chk_send_dsp_cmd(&dev, HDEX_PLAY_STOP);
+ }
+ msnd_disable_irq(&dev);
+ if (file) {
+ printk(KERN_DEBUG LOGNAME ": Stopping write for %p\n", file);
+ dev.mode &= ~FMODE_WRITE;
+ }
+ clear_bit(F_AUDIO_WRITE_INUSE, &dev.flags);
+ }
+}
+
+static int dsp_release(struct file *file)
+{
+ dsp_halt(file);
+ return 0;
+}
+
+static int dsp_open(struct file *file)
+{
+ if ((file ? file->f_mode : dev.mode) & FMODE_WRITE) {
+ set_bit(F_AUDIO_WRITE_INUSE, &dev.flags);
+ clear_bit(F_WRITING, &dev.flags);
+ msnd_fifo_make_empty(&dev.DAPF);
+ reset_play_queue();
+ if (file) {
+ printk(KERN_DEBUG LOGNAME ": Starting write for %p\n", file);
+ dev.mode |= FMODE_WRITE;
+ }
+ msnd_enable_irq(&dev);
+ }
+ if ((file ? file->f_mode : dev.mode) & FMODE_READ) {
+ set_bit(F_AUDIO_READ_INUSE, &dev.flags);
+ clear_bit(F_READING, &dev.flags);
+ msnd_fifo_make_empty(&dev.DARF);
+ reset_record_queue();
+ if (file) {
+ printk(KERN_DEBUG LOGNAME ": Starting read for %p\n", file);
+ dev.mode |= FMODE_READ;
+ }
+ msnd_enable_irq(&dev);
+ }
+ return 0;
+}
+
+static void set_default_play_audio_parameters(void)
+{
+ dev.play_sample_size = DEFSAMPLESIZE;
+ dev.play_sample_rate = DEFSAMPLERATE;
+ dev.play_channels = DEFCHANNELS;
+}
+
+static void set_default_rec_audio_parameters(void)
+{
+ dev.rec_sample_size = DEFSAMPLESIZE;
+ dev.rec_sample_rate = DEFSAMPLERATE;
+ dev.rec_channels = DEFCHANNELS;
+}
+
+static void set_default_audio_parameters(void)
+{
+ set_default_play_audio_parameters();
+ set_default_rec_audio_parameters();
+}
+
+static int dev_open(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ int err = 0;
+
+ mutex_lock(&msnd_pinnacle_mutex);
+ if (minor == dev.dsp_minor) {
+ if ((file->f_mode & FMODE_WRITE &&
+ test_bit(F_AUDIO_WRITE_INUSE, &dev.flags)) ||
+ (file->f_mode & FMODE_READ &&
+ test_bit(F_AUDIO_READ_INUSE, &dev.flags))) {
+ err = -EBUSY;
+ goto out;
+ }
+
+ if ((err = dsp_open(file)) >= 0) {
+ dev.nresets = 0;
+ if (file->f_mode & FMODE_WRITE) {
+ set_default_play_audio_parameters();
+ if (!test_bit(F_DISABLE_WRITE_NDELAY, &dev.flags))
+ dev.play_ndelay = (file->f_flags & O_NDELAY) ? 1 : 0;
+ else
+ dev.play_ndelay = 0;
+ }
+ if (file->f_mode & FMODE_READ) {
+ set_default_rec_audio_parameters();
+ dev.rec_ndelay = (file->f_flags & O_NDELAY) ? 1 : 0;
+ }
+ }
+ }
+ else if (minor == dev.mixer_minor) {
+ /* nothing */
+ } else
+ err = -EINVAL;
+out:
+ mutex_unlock(&msnd_pinnacle_mutex);
+ return err;
+}
+
+static int dev_release(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ int err = 0;
+
+ mutex_lock(&msnd_pinnacle_mutex);
+ if (minor == dev.dsp_minor)
+ err = dsp_release(file);
+ else if (minor == dev.mixer_minor) {
+ /* nothing */
+ } else
+ err = -EINVAL;
+ mutex_unlock(&msnd_pinnacle_mutex);
+ return err;
+}
+
+static __inline__ int pack_DARQ_to_DARF(register int bank)
+{
+ register int size, timeout = 3;
+ register WORD wTmp;
+ LPDAQD DAQD;
+
+ /* Increment the tail and check for queue wrap */
+ wTmp = readw(dev.DARQ + JQS_wTail) + PCTODSP_OFFSET(DAQDS__size);
+ if (wTmp > readw(dev.DARQ + JQS_wSize))
+ wTmp = 0;
+ while (wTmp == readw(dev.DARQ + JQS_wHead) && timeout--)
+ udelay(1);
+ writew(wTmp, dev.DARQ + JQS_wTail);
+
+ /* Get our digital audio queue struct */
+ DAQD = bank * DAQDS__size + dev.base + DARQ_DATA_BUFF;
+
+ /* Get length of data */
+ size = readw(DAQD + DAQDS_wSize);
+
+ /* Read data from the head (unprotected bank 1 access okay
+ since this is only called inside an interrupt) */
+ msnd_outb(HPBLKSEL_1, dev.io + HP_BLKS);
+ msnd_fifo_write_io(
+ &dev.DARF,
+ dev.base + bank * DAR_BUFF_SIZE,
+ size);
+ msnd_outb(HPBLKSEL_0, dev.io + HP_BLKS);
+
+ return 1;
+}
+
+static __inline__ int pack_DAPF_to_DAPQ(register int start)
+{
+ register WORD DAPQ_tail;
+ register int protect = start, nbanks = 0;
+ LPDAQD DAQD;
+
+ DAPQ_tail = readw(dev.DAPQ + JQS_wTail);
+ while (DAPQ_tail != readw(dev.DAPQ + JQS_wHead) || start) {
+ register int bank_num = DAPQ_tail / PCTODSP_OFFSET(DAQDS__size);
+ register int n;
+ unsigned long flags;
+
+ /* Write the data to the new tail */
+ if (protect) {
+ /* Critical section: protect fifo in non-interrupt */
+ spin_lock_irqsave(&dev.lock, flags);
+ n = msnd_fifo_read_io(
+ &dev.DAPF,
+ dev.base + bank_num * DAP_BUFF_SIZE,
+ DAP_BUFF_SIZE);
+ spin_unlock_irqrestore(&dev.lock, flags);
+ } else {
+ n = msnd_fifo_read_io(
+ &dev.DAPF,
+ dev.base + bank_num * DAP_BUFF_SIZE,
+ DAP_BUFF_SIZE);
+ }
+ if (!n)
+ break;
+
+ if (start)
+ start = 0;
+
+ /* Get our digital audio queue struct */
+ DAQD = bank_num * DAQDS__size + dev.base + DAPQ_DATA_BUFF;
+
+ /* Write size of this bank */
+ writew(n, DAQD + DAQDS_wSize);
+ ++nbanks;
+
+ /* Then advance the tail */
+ DAPQ_tail = (++bank_num % 3) * PCTODSP_OFFSET(DAQDS__size);
+ writew(DAPQ_tail, dev.DAPQ + JQS_wTail);
+ /* Tell the DSP to play the bank */
+ msnd_send_dsp_cmd(&dev, HDEX_PLAY_START);
+ }
+ return nbanks;
+}
+
+static int dsp_read(char __user *buf, size_t len)
+{
+ int count = len;
+ char *page = (char *)__get_free_page(GFP_KERNEL);
+
+ if (!page)
+ return -ENOMEM;
+
+ while (count > 0) {
+ int n, k;
+ unsigned long flags;
+
+ k = PAGE_SIZE;
+ if (k > count)
+ k = count;
+
+ /* Critical section: protect fifo in non-interrupt */
+ spin_lock_irqsave(&dev.lock, flags);
+ n = msnd_fifo_read(&dev.DARF, page, k);
+ spin_unlock_irqrestore(&dev.lock, flags);
+ if (copy_to_user(buf, page, n)) {
+ free_page((unsigned long)page);
+ return -EFAULT;
+ }
+ buf += n;
+ count -= n;
+
+ if (n == k && count)
+ continue;
+
+ if (!test_bit(F_READING, &dev.flags) && dev.mode & FMODE_READ) {
+ dev.last_recbank = -1;
+ if (chk_send_dsp_cmd(&dev, HDEX_RECORD_START) == 0)
+ set_bit(F_READING, &dev.flags);
+ }
+
+ if (dev.rec_ndelay) {
+ free_page((unsigned long)page);
+ return count == len ? -EAGAIN : len - count;
+ }
+
+ if (count > 0) {
+ set_bit(F_READBLOCK, &dev.flags);
+ if (!interruptible_sleep_on_timeout(
+ &dev.readblock,
+ get_rec_delay_jiffies(DAR_BUFF_SIZE)))
+ clear_bit(F_READING, &dev.flags);
+ clear_bit(F_READBLOCK, &dev.flags);
+ if (signal_pending(current)) {
+ free_page((unsigned long)page);
+ return -EINTR;
+ }
+ }
+ }
+ free_page((unsigned long)page);
+ return len - count;
+}
+
+static int dsp_write(const char __user *buf, size_t len)
+{
+ int count = len;
+ char *page = (char *)__get_free_page(GFP_KERNEL);
+
+ if (!page)
+ return -ENOMEM;
+
+ while (count > 0) {
+ int n, k;
+ unsigned long flags;
+
+ k = PAGE_SIZE;
+ if (k > count)
+ k = count;
+
+ if (copy_from_user(page, buf, k)) {
+ free_page((unsigned long)page);
+ return -EFAULT;
+ }
+
+ /* Critical section: protect fifo in non-interrupt */
+ spin_lock_irqsave(&dev.lock, flags);
+ n = msnd_fifo_write(&dev.DAPF, page, k);
+ spin_unlock_irqrestore(&dev.lock, flags);
+ buf += n;
+ count -= n;
+
+ if (count && n == k)
+ continue;
+
+ if (!test_bit(F_WRITING, &dev.flags) && (dev.mode & FMODE_WRITE)) {
+ dev.last_playbank = -1;
+ if (pack_DAPF_to_DAPQ(1) > 0)
+ set_bit(F_WRITING, &dev.flags);
+ }
+
+ if (dev.play_ndelay) {
+ free_page((unsigned long)page);
+ return count == len ? -EAGAIN : len - count;
+ }
+
+ if (count > 0) {
+ set_bit(F_WRITEBLOCK, &dev.flags);
+ interruptible_sleep_on_timeout(
+ &dev.writeblock,
+ get_play_delay_jiffies(DAP_BUFF_SIZE));
+ clear_bit(F_WRITEBLOCK, &dev.flags);
+ if (signal_pending(current)) {
+ free_page((unsigned long)page);
+ return -EINTR;
+ }
+ }
+ }
+
+ free_page((unsigned long)page);
+ return len - count;
+}
+
+static ssize_t dev_read(struct file *file, char __user *buf, size_t count, loff_t *off)
+{
+ int minor = iminor(file->f_path.dentry->d_inode);
+ if (minor == dev.dsp_minor)
+ return dsp_read(buf, count);
+ else
+ return -EINVAL;
+}
+
+static ssize_t dev_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
+{
+ int minor = iminor(file->f_path.dentry->d_inode);
+ if (minor == dev.dsp_minor)
+ return dsp_write(buf, count);
+ else
+ return -EINVAL;
+}
+
+static __inline__ void eval_dsp_msg(register WORD wMessage)
+{
+ switch (HIBYTE(wMessage)) {
+ case HIMT_PLAY_DONE:
+ if (dev.last_playbank == LOBYTE(wMessage) || !test_bit(F_WRITING, &dev.flags))
+ break;
+ dev.last_playbank = LOBYTE(wMessage);
+
+ if (pack_DAPF_to_DAPQ(0) <= 0) {
+ if (!test_bit(F_WRITEBLOCK, &dev.flags)) {
+ if (test_and_clear_bit(F_WRITEFLUSH, &dev.flags))
+ wake_up_interruptible(&dev.writeflush);
+ }
+ clear_bit(F_WRITING, &dev.flags);
+ }
+
+ if (test_bit(F_WRITEBLOCK, &dev.flags))
+ wake_up_interruptible(&dev.writeblock);
+ break;
+
+ case HIMT_RECORD_DONE:
+ if (dev.last_recbank == LOBYTE(wMessage))
+ break;
+ dev.last_recbank = LOBYTE(wMessage);
+
+ pack_DARQ_to_DARF(dev.last_recbank);
+
+ if (test_bit(F_READBLOCK, &dev.flags))
+ wake_up_interruptible(&dev.readblock);
+ break;
+
+ case HIMT_DSP:
+ switch (LOBYTE(wMessage)) {
+#ifndef MSND_CLASSIC
+ case HIDSP_PLAY_UNDER:
+#endif
+ case HIDSP_INT_PLAY_UNDER:
+/* printk(KERN_DEBUG LOGNAME ": Play underflow\n"); */
+ clear_bit(F_WRITING, &dev.flags);
+ break;
+
+ case HIDSP_INT_RECORD_OVER:
+/* printk(KERN_DEBUG LOGNAME ": Record overflow\n"); */
+ clear_bit(F_READING, &dev.flags);
+ break;
+
+ default:
+/* printk(KERN_DEBUG LOGNAME ": DSP message %d 0x%02x\n",
+ LOBYTE(wMessage), LOBYTE(wMessage)); */
+ break;
+ }
+ break;
+
+ case HIMT_MIDI_IN_UCHAR:
+ if (dev.midi_in_interrupt)
+ (*dev.midi_in_interrupt)(&dev);
+ break;
+
+ default:
+/* printk(KERN_DEBUG LOGNAME ": HIMT message %d 0x%02x\n", HIBYTE(wMessage), HIBYTE(wMessage)); */
+ break;
+ }
+}
+
+static irqreturn_t intr(int irq, void *dev_id)
+{
+ /* Send ack to DSP */
+ msnd_inb(dev.io + HP_RXL);
+
+ /* Evaluate queued DSP messages */
+ while (readw(dev.DSPQ + JQS_wTail) != readw(dev.DSPQ + JQS_wHead)) {
+ register WORD wTmp;
+
+ eval_dsp_msg(readw(dev.pwDSPQData + 2*readw(dev.DSPQ + JQS_wHead)));
+
+ if ((wTmp = readw(dev.DSPQ + JQS_wHead) + 1) > readw(dev.DSPQ + JQS_wSize))
+ writew(0, dev.DSPQ + JQS_wHead);
+ else
+ writew(wTmp, dev.DSPQ + JQS_wHead);
+ }
+ return IRQ_HANDLED;
+}
+
+static const struct file_operations dev_fileops = {
+ .owner = THIS_MODULE,
+ .read = dev_read,
+ .write = dev_write,
+ .unlocked_ioctl = dev_ioctl,
+ .open = dev_open,
+ .release = dev_release,
+ .llseek = noop_llseek,
+};
+
+static int reset_dsp(void)
+{
+ int timeout = 100;
+
+ msnd_outb(HPDSPRESET_ON, dev.io + HP_DSPR);
+ mdelay(1);
+#ifndef MSND_CLASSIC
+ dev.info = msnd_inb(dev.io + HP_INFO);
+#endif
+ msnd_outb(HPDSPRESET_OFF, dev.io + HP_DSPR);
+ mdelay(1);
+ while (timeout-- > 0) {
+ if (msnd_inb(dev.io + HP_CVR) == HP_CVR_DEF)
+ return 0;
+ mdelay(1);
+ }
+ printk(KERN_ERR LOGNAME ": Cannot reset DSP\n");
+
+ return -EIO;
+}
+
+static int __init probe_multisound(void)
+{
+#ifndef MSND_CLASSIC
+ char *xv, *rev = NULL;
+ char *pin = "Pinnacle", *fiji = "Fiji";
+ char *pinfiji = "Pinnacle/Fiji";
+#endif
+
+ if (!request_region(dev.io, dev.numio, "probing")) {
+ printk(KERN_ERR LOGNAME ": I/O port conflict\n");
+ return -ENODEV;
+ }
+
+ if (reset_dsp() < 0) {
+ release_region(dev.io, dev.numio);
+ return -ENODEV;
+ }
+
+#ifdef MSND_CLASSIC
+ dev.name = "Classic/Tahiti/Monterey";
+ printk(KERN_INFO LOGNAME ": %s, "
+#else
+ switch (dev.info >> 4) {
+ case 0xf: xv = "<= 1.15"; break;
+ case 0x1: xv = "1.18/1.2"; break;
+ case 0x2: xv = "1.3"; break;
+ case 0x3: xv = "1.4"; break;
+ default: xv = "unknown"; break;
+ }
+
+ switch (dev.info & 0x7) {
+ case 0x0: rev = "I"; dev.name = pin; break;
+ case 0x1: rev = "F"; dev.name = pin; break;
+ case 0x2: rev = "G"; dev.name = pin; break;
+ case 0x3: rev = "H"; dev.name = pin; break;
+ case 0x4: rev = "E"; dev.name = fiji; break;
+ case 0x5: rev = "C"; dev.name = fiji; break;
+ case 0x6: rev = "D"; dev.name = fiji; break;
+ case 0x7:
+ rev = "A-B (Fiji) or A-E (Pinnacle)";
+ dev.name = pinfiji;
+ break;
+ }
+ printk(KERN_INFO LOGNAME ": %s revision %s, Xilinx version %s, "
+#endif /* MSND_CLASSIC */
+ "I/O 0x%x-0x%x, IRQ %d, memory mapped to %p-%p\n",
+ dev.name,
+#ifndef MSND_CLASSIC
+ rev, xv,
+#endif
+ dev.io, dev.io + dev.numio - 1,
+ dev.irq,
+ dev.base, dev.base + 0x7fff);
+
+ release_region(dev.io, dev.numio);
+ return 0;
+}
+
+static int init_sma(void)
+{
+ static int initted;
+ WORD mastVolLeft, mastVolRight;
+ unsigned long flags;
+
+#ifdef MSND_CLASSIC
+ msnd_outb(dev.memid, dev.io + HP_MEMM);
+#endif
+ msnd_outb(HPBLKSEL_0, dev.io + HP_BLKS);
+ if (initted) {
+ mastVolLeft = readw(dev.SMA + SMA_wCurrMastVolLeft);
+ mastVolRight = readw(dev.SMA + SMA_wCurrMastVolRight);
+ } else
+ mastVolLeft = mastVolRight = 0;
+ memset_io(dev.base, 0, 0x8000);
+
+ /* Critical section: bank 1 access */
+ spin_lock_irqsave(&dev.lock, flags);
+ msnd_outb(HPBLKSEL_1, dev.io + HP_BLKS);
+ memset_io(dev.base, 0, 0x8000);
+ msnd_outb(HPBLKSEL_0, dev.io + HP_BLKS);
+ spin_unlock_irqrestore(&dev.lock, flags);
+
+ dev.pwDSPQData = (dev.base + DSPQ_DATA_BUFF);
+ dev.pwMODQData = (dev.base + MODQ_DATA_BUFF);
+ dev.pwMIDQData = (dev.base + MIDQ_DATA_BUFF);
+
+ /* Motorola 56k shared memory base */
+ dev.SMA = dev.base + SMA_STRUCT_START;
+
+ /* Digital audio play queue */
+ dev.DAPQ = dev.base + DAPQ_OFFSET;
+ msnd_init_queue(dev.DAPQ, DAPQ_DATA_BUFF, DAPQ_BUFF_SIZE);
+
+ /* Digital audio record queue */
+ dev.DARQ = dev.base + DARQ_OFFSET;
+ msnd_init_queue(dev.DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE);
+
+ /* MIDI out queue */
+ dev.MODQ = dev.base + MODQ_OFFSET;
+ msnd_init_queue(dev.MODQ, MODQ_DATA_BUFF, MODQ_BUFF_SIZE);
+
+ /* MIDI in queue */
+ dev.MIDQ = dev.base + MIDQ_OFFSET;
+ msnd_init_queue(dev.MIDQ, MIDQ_DATA_BUFF, MIDQ_BUFF_SIZE);
+
+ /* DSP -> host message queue */
+ dev.DSPQ = dev.base + DSPQ_OFFSET;
+ msnd_init_queue(dev.DSPQ, DSPQ_DATA_BUFF, DSPQ_BUFF_SIZE);
+
+ /* Setup some DSP values */
+#ifndef MSND_CLASSIC
+ writew(1, dev.SMA + SMA_wCurrPlayFormat);
+ writew(dev.play_sample_size, dev.SMA + SMA_wCurrPlaySampleSize);
+ writew(dev.play_channels, dev.SMA + SMA_wCurrPlayChannels);
+ writew(dev.play_sample_rate, dev.SMA + SMA_wCurrPlaySampleRate);
+#endif
+ writew(dev.play_sample_rate, dev.SMA + SMA_wCalFreqAtoD);
+ writew(mastVolLeft, dev.SMA + SMA_wCurrMastVolLeft);
+ writew(mastVolRight, dev.SMA + SMA_wCurrMastVolRight);
+#ifndef MSND_CLASSIC
+ writel(0x00010000, dev.SMA + SMA_dwCurrPlayPitch);
+ writel(0x00000001, dev.SMA + SMA_dwCurrPlayRate);
+#endif
+ writew(0x303, dev.SMA + SMA_wCurrInputTagBits);
+
+ initted = 1;
+
+ return 0;
+}
+
+static int __init calibrate_adc(WORD srate)
+{
+ writew(srate, dev.SMA + SMA_wCalFreqAtoD);
+ if (dev.calibrate_signal == 0)
+ writew(readw(dev.SMA + SMA_wCurrHostStatusFlags)
+ | 0x0001, dev.SMA + SMA_wCurrHostStatusFlags);
+ else
+ writew(readw(dev.SMA + SMA_wCurrHostStatusFlags)
+ & ~0x0001, dev.SMA + SMA_wCurrHostStatusFlags);
+ if (msnd_send_word(&dev, 0, 0, HDEXAR_CAL_A_TO_D) == 0 &&
+ chk_send_dsp_cmd(&dev, HDEX_AUX_REQ) == 0) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ / 3);
+ return 0;
+ }
+ printk(KERN_WARNING LOGNAME ": ADC calibration failed\n");
+
+ return -EIO;
+}
+
+static int upload_dsp_code(void)
+{
+ msnd_outb(HPBLKSEL_0, dev.io + HP_BLKS);
+#ifndef HAVE_DSPCODEH
+ INITCODESIZE = mod_firmware_load(INITCODEFILE, &INITCODE);
+ if (!INITCODE) {
+ printk(KERN_ERR LOGNAME ": Error loading " INITCODEFILE);
+ return -EBUSY;
+ }
+
+ PERMCODESIZE = mod_firmware_load(PERMCODEFILE, &PERMCODE);
+ if (!PERMCODE) {
+ printk(KERN_ERR LOGNAME ": Error loading " PERMCODEFILE);
+ vfree(INITCODE);
+ return -EBUSY;
+ }
+#endif
+ memcpy_toio(dev.base, PERMCODE, PERMCODESIZE);
+ if (msnd_upload_host(&dev, INITCODE, INITCODESIZE) < 0) {
+ printk(KERN_WARNING LOGNAME ": Error uploading to DSP\n");
+ return -ENODEV;
+ }
+#ifdef HAVE_DSPCODEH
+ printk(KERN_INFO LOGNAME ": DSP firmware uploaded (resident)\n");
+#else
+ printk(KERN_INFO LOGNAME ": DSP firmware uploaded\n");
+#endif
+
+#ifndef HAVE_DSPCODEH
+ vfree(INITCODE);
+ vfree(PERMCODE);
+#endif
+
+ return 0;
+}
+
+#ifdef MSND_CLASSIC
+static void reset_proteus(void)
+{
+ msnd_outb(HPPRORESET_ON, dev.io + HP_PROR);
+ mdelay(TIME_PRO_RESET);
+ msnd_outb(HPPRORESET_OFF, dev.io + HP_PROR);
+ mdelay(TIME_PRO_RESET_DONE);
+}
+#endif
+
+static int initialize(void)
+{
+ int err, timeout;
+
+#ifdef MSND_CLASSIC
+ msnd_outb(HPWAITSTATE_0, dev.io + HP_WAIT);
+ msnd_outb(HPBITMODE_16, dev.io + HP_BITM);
+
+ reset_proteus();
+#endif
+ if ((err = init_sma()) < 0) {
+ printk(KERN_WARNING LOGNAME ": Cannot initialize SMA\n");
+ return err;
+ }
+
+ if ((err = reset_dsp()) < 0)
+ return err;
+
+ if ((err = upload_dsp_code()) < 0) {
+ printk(KERN_WARNING LOGNAME ": Cannot upload DSP code\n");
+ return err;
+ }
+
+ timeout = 200;
+ while (readw(dev.base)) {
+ mdelay(1);
+ if (!timeout--) {
+ printk(KERN_DEBUG LOGNAME ": DSP reset timeout\n");
+ return -EIO;
+ }
+ }
+
+ mixer_setup();
+
+ return 0;
+}
+
+static int dsp_full_reset(void)
+{
+ int rv;
+
+ if (test_bit(F_RESETTING, &dev.flags) || ++dev.nresets > 10)
+ return 0;
+
+ set_bit(F_RESETTING, &dev.flags);
+ printk(KERN_INFO LOGNAME ": DSP reset\n");
+ dsp_halt(NULL); /* Unconditionally halt */
+ if ((rv = initialize()))
+ printk(KERN_WARNING LOGNAME ": DSP reset failed\n");
+ force_recsrc(dev.recsrc);
+ dsp_open(NULL);
+ clear_bit(F_RESETTING, &dev.flags);
+
+ return rv;
+}
+
+static int __init attach_multisound(void)
+{
+ int err;
+
+ if ((err = request_irq(dev.irq, intr, 0, dev.name, &dev)) < 0) {
+ printk(KERN_ERR LOGNAME ": Couldn't grab IRQ %d\n", dev.irq);
+ return err;
+ }
+ if (request_region(dev.io, dev.numio, dev.name) == NULL) {
+ free_irq(dev.irq, &dev);
+ return -EBUSY;
+ }
+
+ err = dsp_full_reset();
+ if (err < 0) {
+ release_region(dev.io, dev.numio);
+ free_irq(dev.irq, &dev);
+ return err;
+ }
+
+ if ((err = msnd_register(&dev)) < 0) {
+ printk(KERN_ERR LOGNAME ": Unable to register MultiSound\n");
+ release_region(dev.io, dev.numio);
+ free_irq(dev.irq, &dev);
+ return err;
+ }
+
+ if ((dev.dsp_minor = register_sound_dsp(&dev_fileops, -1)) < 0) {
+ printk(KERN_ERR LOGNAME ": Unable to register DSP operations\n");
+ msnd_unregister(&dev);
+ release_region(dev.io, dev.numio);
+ free_irq(dev.irq, &dev);
+ return dev.dsp_minor;
+ }
+
+ if ((dev.mixer_minor = register_sound_mixer(&dev_fileops, -1)) < 0) {
+ printk(KERN_ERR LOGNAME ": Unable to register mixer operations\n");
+ unregister_sound_mixer(dev.mixer_minor);
+ msnd_unregister(&dev);
+ release_region(dev.io, dev.numio);
+ free_irq(dev.irq, &dev);
+ return dev.mixer_minor;
+ }
+
+ dev.ext_midi_dev = dev.hdr_midi_dev = -1;
+
+ disable_irq(dev.irq);
+ calibrate_adc(dev.play_sample_rate);
+#ifndef MSND_CLASSIC
+ force_recsrc(SOUND_MASK_IMIX);
+#endif
+
+ return 0;
+}
+
+static void __exit unload_multisound(void)
+{
+ release_region(dev.io, dev.numio);
+ free_irq(dev.irq, &dev);
+ unregister_sound_mixer(dev.mixer_minor);
+ unregister_sound_dsp(dev.dsp_minor);
+ msnd_unregister(&dev);
+}
+
+#ifndef MSND_CLASSIC
+
+/* Pinnacle/Fiji Logical Device Configuration */
+
+static int __init msnd_write_cfg(int cfg, int reg, int value)
+{
+ msnd_outb(reg, cfg);
+ msnd_outb(value, cfg + 1);
+ if (value != msnd_inb(cfg + 1)) {
+ printk(KERN_ERR LOGNAME ": msnd_write_cfg: I/O error\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+static int __init msnd_write_cfg_io0(int cfg, int num, WORD io)
+{
+ if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (msnd_write_cfg(cfg, IREG_IO0_BASEHI, HIBYTE(io)))
+ return -EIO;
+ if (msnd_write_cfg(cfg, IREG_IO0_BASELO, LOBYTE(io)))
+ return -EIO;
+ return 0;
+}
+
+static int __init msnd_write_cfg_io1(int cfg, int num, WORD io)
+{
+ if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (msnd_write_cfg(cfg, IREG_IO1_BASEHI, HIBYTE(io)))
+ return -EIO;
+ if (msnd_write_cfg(cfg, IREG_IO1_BASELO, LOBYTE(io)))
+ return -EIO;
+ return 0;
+}
+
+static int __init msnd_write_cfg_irq(int cfg, int num, WORD irq)
+{
+ if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (msnd_write_cfg(cfg, IREG_IRQ_NUMBER, LOBYTE(irq)))
+ return -EIO;
+ if (msnd_write_cfg(cfg, IREG_IRQ_TYPE, IRQTYPE_EDGE))
+ return -EIO;
+ return 0;
+}
+
+static int __init msnd_write_cfg_mem(int cfg, int num, int mem)
+{
+ WORD wmem;
+
+ mem >>= 8;
+ mem &= 0xfff;
+ wmem = (WORD)mem;
+ if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (msnd_write_cfg(cfg, IREG_MEMBASEHI, HIBYTE(wmem)))
+ return -EIO;
+ if (msnd_write_cfg(cfg, IREG_MEMBASELO, LOBYTE(wmem)))
+ return -EIO;
+ if (wmem && msnd_write_cfg(cfg, IREG_MEMCONTROL, (MEMTYPE_HIADDR | MEMTYPE_16BIT)))
+ return -EIO;
+ return 0;
+}
+
+static int __init msnd_activate_logical(int cfg, int num)
+{
+ if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (msnd_write_cfg(cfg, IREG_ACTIVATE, LD_ACTIVATE))
+ return -EIO;
+ return 0;
+}
+
+static int __init msnd_write_cfg_logical(int cfg, int num, WORD io0, WORD io1, WORD irq, int mem)
+{
+ if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+ return -EIO;
+ if (msnd_write_cfg_io0(cfg, num, io0))
+ return -EIO;
+ if (msnd_write_cfg_io1(cfg, num, io1))
+ return -EIO;
+ if (msnd_write_cfg_irq(cfg, num, irq))
+ return -EIO;
+ if (msnd_write_cfg_mem(cfg, num, mem))
+ return -EIO;
+ if (msnd_activate_logical(cfg, num))
+ return -EIO;
+ return 0;
+}
+
+typedef struct msnd_pinnacle_cfg_device {
+ WORD io0, io1, irq;
+ int mem;
+} msnd_pinnacle_cfg_t[4];
+
+static int __init msnd_pinnacle_cfg_devices(int cfg, int reset, msnd_pinnacle_cfg_t device)
+{
+ int i;
+
+ /* Reset devices if told to */
+ if (reset) {
+ printk(KERN_INFO LOGNAME ": Resetting all devices\n");
+ for (i = 0; i < 4; ++i)
+ if (msnd_write_cfg_logical(cfg, i, 0, 0, 0, 0))
+ return -EIO;
+ }
+
+ /* Configure specified devices */
+ for (i = 0; i < 4; ++i) {
+
+ switch (i) {
+ case 0: /* DSP */
+ if (!(device[i].io0 && device[i].irq && device[i].mem))
+ continue;
+ break;
+ case 1: /* MPU */
+ if (!(device[i].io0 && device[i].irq))
+ continue;
+ printk(KERN_INFO LOGNAME
+ ": Configuring MPU to I/O 0x%x IRQ %d\n",
+ device[i].io0, device[i].irq);
+ break;
+ case 2: /* IDE */
+ if (!(device[i].io0 && device[i].io1 && device[i].irq))
+ continue;
+ printk(KERN_INFO LOGNAME
+ ": Configuring IDE to I/O 0x%x, 0x%x IRQ %d\n",
+ device[i].io0, device[i].io1, device[i].irq);
+ break;
+ case 3: /* Joystick */
+ if (!(device[i].io0))
+ continue;
+ printk(KERN_INFO LOGNAME
+ ": Configuring joystick to I/O 0x%x\n",
+ device[i].io0);
+ break;
+ }
+
+ /* Configure the device */
+ if (msnd_write_cfg_logical(cfg, i, device[i].io0, device[i].io1, device[i].irq, device[i].mem))
+ return -EIO;
+ }
+
+ return 0;
+}
+#endif
+
+#ifdef MODULE
+MODULE_AUTHOR ("Andrew Veliath <andrewtv@usa.net>");
+MODULE_DESCRIPTION ("Turtle Beach " LONGNAME " Linux Driver");
+MODULE_LICENSE("GPL");
+
+static int io __initdata = -1;
+static int irq __initdata = -1;
+static int mem __initdata = -1;
+static int write_ndelay __initdata = -1;
+
+#ifndef MSND_CLASSIC
+/* Pinnacle/Fiji non-PnP Config Port */
+static int cfg __initdata = -1;
+
+/* Extra Peripheral Configuration */
+static int reset __initdata = 0;
+static int mpu_io __initdata = 0;
+static int mpu_irq __initdata = 0;
+static int ide_io0 __initdata = 0;
+static int ide_io1 __initdata = 0;
+static int ide_irq __initdata = 0;
+static int joystick_io __initdata = 0;
+
+/* If we have the digital daugherboard... */
+static int digital __initdata = 0;
+#endif
+
+static int fifosize __initdata = DEFFIFOSIZE;
+static int calibrate_signal __initdata = 0;
+
+#else /* not a module */
+
+static int write_ndelay __initdata = -1;
+
+#ifdef MSND_CLASSIC
+static int io __initdata = CONFIG_MSNDCLAS_IO;
+static int irq __initdata = CONFIG_MSNDCLAS_IRQ;
+static int mem __initdata = CONFIG_MSNDCLAS_MEM;
+#else /* Pinnacle/Fiji */
+
+static int io __initdata = CONFIG_MSNDPIN_IO;
+static int irq __initdata = CONFIG_MSNDPIN_IRQ;
+static int mem __initdata = CONFIG_MSNDPIN_MEM;
+
+/* Pinnacle/Fiji non-PnP Config Port */
+#ifdef CONFIG_MSNDPIN_NONPNP
+# ifndef CONFIG_MSNDPIN_CFG
+# define CONFIG_MSNDPIN_CFG 0x250
+# endif
+#else
+# ifdef CONFIG_MSNDPIN_CFG
+# undef CONFIG_MSNDPIN_CFG
+# endif
+# define CONFIG_MSNDPIN_CFG -1
+#endif
+static int cfg __initdata = CONFIG_MSNDPIN_CFG;
+/* If not a module, we don't need to bother with reset=1 */
+static int reset;
+
+/* Extra Peripheral Configuration (Default: Disable) */
+#ifndef CONFIG_MSNDPIN_MPU_IO
+# define CONFIG_MSNDPIN_MPU_IO 0
+#endif
+static int mpu_io __initdata = CONFIG_MSNDPIN_MPU_IO;
+
+#ifndef CONFIG_MSNDPIN_MPU_IRQ
+# define CONFIG_MSNDPIN_MPU_IRQ 0
+#endif
+static int mpu_irq __initdata = CONFIG_MSNDPIN_MPU_IRQ;
+
+#ifndef CONFIG_MSNDPIN_IDE_IO0
+# define CONFIG_MSNDPIN_IDE_IO0 0
+#endif
+static int ide_io0 __initdata = CONFIG_MSNDPIN_IDE_IO0;
+
+#ifndef CONFIG_MSNDPIN_IDE_IO1
+# define CONFIG_MSNDPIN_IDE_IO1 0
+#endif
+static int ide_io1 __initdata = CONFIG_MSNDPIN_IDE_IO1;
+
+#ifndef CONFIG_MSNDPIN_IDE_IRQ
+# define CONFIG_MSNDPIN_IDE_IRQ 0
+#endif
+static int ide_irq __initdata = CONFIG_MSNDPIN_IDE_IRQ;
+
+#ifndef CONFIG_MSNDPIN_JOYSTICK_IO
+# define CONFIG_MSNDPIN_JOYSTICK_IO 0
+#endif
+static int joystick_io __initdata = CONFIG_MSNDPIN_JOYSTICK_IO;
+
+/* Have SPDIF (Digital) Daughterboard */
+#ifndef CONFIG_MSNDPIN_DIGITAL
+# define CONFIG_MSNDPIN_DIGITAL 0
+#endif
+static int digital __initdata = CONFIG_MSNDPIN_DIGITAL;
+
+#endif /* MSND_CLASSIC */
+
+#ifndef CONFIG_MSND_FIFOSIZE
+# define CONFIG_MSND_FIFOSIZE DEFFIFOSIZE
+#endif
+static int fifosize __initdata = CONFIG_MSND_FIFOSIZE;
+
+#ifndef CONFIG_MSND_CALSIGNAL
+# define CONFIG_MSND_CALSIGNAL 0
+#endif
+static int
+calibrate_signal __initdata = CONFIG_MSND_CALSIGNAL;
+#endif /* MODULE */
+
+module_param (io, int, 0);
+module_param (irq, int, 0);
+module_param (mem, int, 0);
+module_param (write_ndelay, int, 0);
+module_param (fifosize, int, 0);
+module_param (calibrate_signal, int, 0);
+#ifndef MSND_CLASSIC
+module_param (digital, bool, 0);
+module_param (cfg, int, 0);
+module_param (reset, int, 0);
+module_param (mpu_io, int, 0);
+module_param (mpu_irq, int, 0);
+module_param (ide_io0, int, 0);
+module_param (ide_io1, int, 0);
+module_param (ide_irq, int, 0);
+module_param (joystick_io, int, 0);
+#endif
+
+static int __init msnd_init(void)
+{
+ int err;
+#ifndef MSND_CLASSIC
+ static msnd_pinnacle_cfg_t pinnacle_devs;
+#endif /* MSND_CLASSIC */
+
+ printk(KERN_INFO LOGNAME ": Turtle Beach " LONGNAME " Linux Driver Version "
+ VERSION ", Copyright (C) 1998 Andrew Veliath\n");
+
+ if (io == -1 || irq == -1 || mem == -1)
+ printk(KERN_WARNING LOGNAME ": io, irq and mem must be set\n");
+
+#ifdef MSND_CLASSIC
+ if (io == -1 ||
+ !(io == 0x290 ||
+ io == 0x260 ||
+ io == 0x250 ||
+ io == 0x240 ||
+ io == 0x230 ||
+ io == 0x220 ||
+ io == 0x210 ||
+ io == 0x3e0)) {
+ printk(KERN_ERR LOGNAME ": \"io\" - DSP I/O base must be set to 0x210, 0x220, 0x230, 0x240, 0x250, 0x260, 0x290, or 0x3E0\n");
+ return -EINVAL;
+ }
+#else
+ if (io == -1 ||
+ io < 0x100 ||
+ io > 0x3e0 ||
+ (io % 0x10) != 0) {
+ printk(KERN_ERR LOGNAME ": \"io\" - DSP I/O base must within the range 0x100 to 0x3E0 and must be evenly divisible by 0x10\n");
+ return -EINVAL;
+ }
+#endif /* MSND_CLASSIC */
+
+ if (irq == -1 ||
+ !(irq == 5 ||
+ irq == 7 ||
+ irq == 9 ||
+ irq == 10 ||
+ irq == 11 ||
+ irq == 12)) {
+ printk(KERN_ERR LOGNAME ": \"irq\" - must be set to 5, 7, 9, 10, 11 or 12\n");
+ return -EINVAL;
+ }
+
+ if (mem == -1 ||
+ !(mem == 0xb0000 ||
+ mem == 0xc8000 ||
+ mem == 0xd0000 ||
+ mem == 0xd8000 ||
+ mem == 0xe0000 ||
+ mem == 0xe8000)) {
+ printk(KERN_ERR LOGNAME ": \"mem\" - must be set to "
+ "0xb0000, 0xc8000, 0xd0000, 0xd8000, 0xe0000 or 0xe8000\n");
+ return -EINVAL;
+ }
+
+#ifdef MSND_CLASSIC
+ switch (irq) {
+ case 5: dev.irqid = HPIRQ_5; break;
+ case 7: dev.irqid = HPIRQ_7; break;
+ case 9: dev.irqid = HPIRQ_9; break;
+ case 10: dev.irqid = HPIRQ_10; break;
+ case 11: dev.irqid = HPIRQ_11; break;
+ case 12: dev.irqid = HPIRQ_12; break;
+ }
+
+ switch (mem) {
+ case 0xb0000: dev.memid = HPMEM_B000; break;
+ case 0xc8000: dev.memid = HPMEM_C800; break;
+ case 0xd0000: dev.memid = HPMEM_D000; break;
+ case 0xd8000: dev.memid = HPMEM_D800; break;
+ case 0xe0000: dev.memid = HPMEM_E000; break;
+ case 0xe8000: dev.memid = HPMEM_E800; break;
+ }
+#else
+ if (cfg == -1) {
+ printk(KERN_INFO LOGNAME ": Assuming PnP mode\n");
+ } else if (cfg != 0x250 && cfg != 0x260 && cfg != 0x270) {
+ printk(KERN_INFO LOGNAME ": Config port must be 0x250, 0x260 or 0x270 (or unspecified for PnP mode)\n");
+ return -EINVAL;
+ } else {
+ printk(KERN_INFO LOGNAME ": Non-PnP mode: configuring at port 0x%x\n", cfg);
+
+ /* DSP */
+ pinnacle_devs[0].io0 = io;
+ pinnacle_devs[0].irq = irq;
+ pinnacle_devs[0].mem = mem;
+
+ /* The following are Pinnacle specific */
+
+ /* MPU */
+ pinnacle_devs[1].io0 = mpu_io;
+ pinnacle_devs[1].irq = mpu_irq;
+
+ /* IDE */
+ pinnacle_devs[2].io0 = ide_io0;
+ pinnacle_devs[2].io1 = ide_io1;
+ pinnacle_devs[2].irq = ide_irq;
+
+ /* Joystick */
+ pinnacle_devs[3].io0 = joystick_io;
+
+ if (!request_region(cfg, 2, "Pinnacle/Fiji Config")) {
+ printk(KERN_ERR LOGNAME ": Config port 0x%x conflict\n", cfg);
+ return -EIO;
+ }
+
+ if (msnd_pinnacle_cfg_devices(cfg, reset, pinnacle_devs)) {
+ printk(KERN_ERR LOGNAME ": Device configuration error\n");
+ release_region(cfg, 2);
+ return -EIO;
+ }
+ release_region(cfg, 2);
+ }
+#endif /* MSND_CLASSIC */
+
+ if (fifosize < 16)
+ fifosize = 16;
+
+ if (fifosize > 1024)
+ fifosize = 1024;
+
+ set_default_audio_parameters();
+#ifdef MSND_CLASSIC
+ dev.type = msndClassic;
+#else
+ dev.type = msndPinnacle;
+#endif
+ dev.io = io;
+ dev.numio = DSP_NUMIO;
+ dev.irq = irq;
+ dev.base = ioremap(mem, 0x8000);
+ dev.fifosize = fifosize * 1024;
+ dev.calibrate_signal = calibrate_signal ? 1 : 0;
+ dev.recsrc = 0;
+ dev.dspq_data_buff = DSPQ_DATA_BUFF;
+ dev.dspq_buff_size = DSPQ_BUFF_SIZE;
+ if (write_ndelay == -1)
+ write_ndelay = CONFIG_MSND_WRITE_NDELAY;
+ if (write_ndelay)
+ clear_bit(F_DISABLE_WRITE_NDELAY, &dev.flags);
+ else
+ set_bit(F_DISABLE_WRITE_NDELAY, &dev.flags);
+#ifndef MSND_CLASSIC
+ if (digital)
+ set_bit(F_HAVEDIGITAL, &dev.flags);
+#endif
+ init_waitqueue_head(&dev.writeblock);
+ init_waitqueue_head(&dev.readblock);
+ init_waitqueue_head(&dev.writeflush);
+ msnd_fifo_init(&dev.DAPF);
+ msnd_fifo_init(&dev.DARF);
+ spin_lock_init(&dev.lock);
+ printk(KERN_INFO LOGNAME ": %u byte audio FIFOs (x2)\n", dev.fifosize);
+ if ((err = msnd_fifo_alloc(&dev.DAPF, dev.fifosize)) < 0) {
+ printk(KERN_ERR LOGNAME ": Couldn't allocate write FIFO\n");
+ return err;
+ }
+
+ if ((err = msnd_fifo_alloc(&dev.DARF, dev.fifosize)) < 0) {
+ printk(KERN_ERR LOGNAME ": Couldn't allocate read FIFO\n");
+ msnd_fifo_free(&dev.DAPF);
+ return err;
+ }
+
+ if ((err = probe_multisound()) < 0) {
+ printk(KERN_ERR LOGNAME ": Probe failed\n");
+ msnd_fifo_free(&dev.DAPF);
+ msnd_fifo_free(&dev.DARF);
+ return err;
+ }
+
+ if ((err = attach_multisound()) < 0) {
+ printk(KERN_ERR LOGNAME ": Attach failed\n");
+ msnd_fifo_free(&dev.DAPF);
+ msnd_fifo_free(&dev.DARF);
+ return err;
+ }
+
+ return 0;
+}
+
+static void __exit msdn_cleanup(void)
+{
+ unload_multisound();
+ msnd_fifo_free(&dev.DAPF);
+ msnd_fifo_free(&dev.DARF);
+}
+
+module_init(msnd_init);
+module_exit(msdn_cleanup);
diff --git a/sound/oss/msnd_pinnacle.h b/sound/oss/msnd_pinnacle.h
new file mode 100644
index 00000000..c18d66cb
--- /dev/null
+++ b/sound/oss/msnd_pinnacle.h
@@ -0,0 +1,246 @@
+/*********************************************************************
+ *
+ * msnd_pinnacle.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+#ifndef __MSND_PINNACLE_H
+#define __MSND_PINNACLE_H
+
+
+#define DSP_NUMIO 0x08
+
+#define IREG_LOGDEVICE 0x07
+#define IREG_ACTIVATE 0x30
+#define LD_ACTIVATE 0x01
+#define LD_DISACTIVATE 0x00
+#define IREG_EECONTROL 0x3F
+#define IREG_MEMBASEHI 0x40
+#define IREG_MEMBASELO 0x41
+#define IREG_MEMCONTROL 0x42
+#define IREG_MEMRANGEHI 0x43
+#define IREG_MEMRANGELO 0x44
+#define MEMTYPE_8BIT 0x00
+#define MEMTYPE_16BIT 0x02
+#define MEMTYPE_RANGE 0x00
+#define MEMTYPE_HIADDR 0x01
+#define IREG_IO0_BASEHI 0x60
+#define IREG_IO0_BASELO 0x61
+#define IREG_IO1_BASEHI 0x62
+#define IREG_IO1_BASELO 0x63
+#define IREG_IRQ_NUMBER 0x70
+#define IREG_IRQ_TYPE 0x71
+#define IRQTYPE_HIGH 0x02
+#define IRQTYPE_LOW 0x00
+#define IRQTYPE_LEVEL 0x01
+#define IRQTYPE_EDGE 0x00
+
+#define HP_DSPR 0x04
+#define HP_BLKS 0x04
+
+#define HPDSPRESET_OFF 2
+#define HPDSPRESET_ON 0
+
+#define HPBLKSEL_0 2
+#define HPBLKSEL_1 3
+
+#define HIMT_DAT_OFF 0x03
+
+#define HIDSP_PLAY_UNDER 0x00
+#define HIDSP_INT_PLAY_UNDER 0x01
+#define HIDSP_SSI_TX_UNDER 0x02
+#define HIDSP_RECQ_OVERFLOW 0x08
+#define HIDSP_INT_RECORD_OVER 0x09
+#define HIDSP_SSI_RX_OVERFLOW 0x0a
+
+#define HIDSP_MIDI_IN_OVER 0x10
+
+#define HIDSP_MIDI_FRAME_ERR 0x11
+#define HIDSP_MIDI_PARITY_ERR 0x12
+#define HIDSP_MIDI_OVERRUN_ERR 0x13
+
+#define HIDSP_INPUT_CLIPPING 0x20
+#define HIDSP_MIX_CLIPPING 0x30
+#define HIDSP_DAT_IN_OFF 0x21
+
+#define HDEXAR_SET_ANA_IN 0
+#define HDEXAR_CLEAR_PEAKS 1
+#define HDEXAR_IN_SET_POTS 2
+#define HDEXAR_AUX_SET_POTS 3
+#define HDEXAR_CAL_A_TO_D 4
+#define HDEXAR_RD_EXT_DSP_BITS 5
+
+#define HDEXAR_SET_SYNTH_IN 4
+#define HDEXAR_READ_DAT_IN 5
+#define HDEXAR_MIC_SET_POTS 6
+#define HDEXAR_SET_DAT_IN 7
+
+#define HDEXAR_SET_SYNTH_48 8
+#define HDEXAR_SET_SYNTH_44 9
+
+#define TIME_PRO_RESET_DONE 0x028A
+#define TIME_PRO_SYSEX 0x001E
+#define TIME_PRO_RESET 0x0032
+
+#define AGND 0x01
+#define SIGNAL 0x02
+
+#define EXT_DSP_BIT_DCAL 0x0001
+#define EXT_DSP_BIT_MIDI_CON 0x0002
+
+#define BUFFSIZE 0x8000
+#define HOSTQ_SIZE 0x40
+
+#define SRAM_CNTL_START 0x7F00
+#define SMA_STRUCT_START 0x7F40
+
+#define DAP_BUFF_SIZE 0x2400
+#define DAR_BUFF_SIZE 0x2000
+
+#define DAPQ_STRUCT_SIZE 0x10
+#define DARQ_STRUCT_SIZE 0x10
+#define DAPQ_BUFF_SIZE (3 * 0x10)
+#define DARQ_BUFF_SIZE (3 * 0x10)
+#define MODQ_BUFF_SIZE 0x400
+#define MIDQ_BUFF_SIZE 0x800
+#define DSPQ_BUFF_SIZE 0x5A0
+
+#define DAPQ_DATA_BUFF 0x6C00
+#define DARQ_DATA_BUFF 0x6C30
+#define MODQ_DATA_BUFF 0x6C60
+#define MIDQ_DATA_BUFF 0x7060
+#define DSPQ_DATA_BUFF 0x7860
+
+#define DAPQ_OFFSET SRAM_CNTL_START
+#define DARQ_OFFSET (SRAM_CNTL_START + 0x08)
+#define MODQ_OFFSET (SRAM_CNTL_START + 0x10)
+#define MIDQ_OFFSET (SRAM_CNTL_START + 0x18)
+#define DSPQ_OFFSET (SRAM_CNTL_START + 0x20)
+
+#define MOP_WAVEHDR 0
+#define MOP_EXTOUT 1
+#define MOP_HWINIT 0xfe
+#define MOP_NONE 0xff
+#define MOP_MAX 1
+
+#define MIP_EXTIN 0
+#define MIP_WAVEHDR 1
+#define MIP_HWINIT 0xfe
+#define MIP_MAX 1
+
+/* Pinnacle/Fiji SMA Common Data */
+#define SMA_wCurrPlayBytes 0x0000
+#define SMA_wCurrRecordBytes 0x0002
+#define SMA_wCurrPlayVolLeft 0x0004
+#define SMA_wCurrPlayVolRight 0x0006
+#define SMA_wCurrInVolLeft 0x0008
+#define SMA_wCurrInVolRight 0x000a
+#define SMA_wCurrMHdrVolLeft 0x000c
+#define SMA_wCurrMHdrVolRight 0x000e
+#define SMA_dwCurrPlayPitch 0x0010
+#define SMA_dwCurrPlayRate 0x0014
+#define SMA_wCurrMIDIIOPatch 0x0018
+#define SMA_wCurrPlayFormat 0x001a
+#define SMA_wCurrPlaySampleSize 0x001c
+#define SMA_wCurrPlayChannels 0x001e
+#define SMA_wCurrPlaySampleRate 0x0020
+#define SMA_wCurrRecordFormat 0x0022
+#define SMA_wCurrRecordSampleSize 0x0024
+#define SMA_wCurrRecordChannels 0x0026
+#define SMA_wCurrRecordSampleRate 0x0028
+#define SMA_wCurrDSPStatusFlags 0x002a
+#define SMA_wCurrHostStatusFlags 0x002c
+#define SMA_wCurrInputTagBits 0x002e
+#define SMA_wCurrLeftPeak 0x0030
+#define SMA_wCurrRightPeak 0x0032
+#define SMA_bMicPotPosLeft 0x0034
+#define SMA_bMicPotPosRight 0x0035
+#define SMA_bMicPotMaxLeft 0x0036
+#define SMA_bMicPotMaxRight 0x0037
+#define SMA_bInPotPosLeft 0x0038
+#define SMA_bInPotPosRight 0x0039
+#define SMA_bAuxPotPosLeft 0x003a
+#define SMA_bAuxPotPosRight 0x003b
+#define SMA_bInPotMaxLeft 0x003c
+#define SMA_bInPotMaxRight 0x003d
+#define SMA_bAuxPotMaxLeft 0x003e
+#define SMA_bAuxPotMaxRight 0x003f
+#define SMA_bInPotMaxMethod 0x0040
+#define SMA_bAuxPotMaxMethod 0x0041
+#define SMA_wCurrMastVolLeft 0x0042
+#define SMA_wCurrMastVolRight 0x0044
+#define SMA_wCalFreqAtoD 0x0046
+#define SMA_wCurrAuxVolLeft 0x0048
+#define SMA_wCurrAuxVolRight 0x004a
+#define SMA_wCurrPlay1VolLeft 0x004c
+#define SMA_wCurrPlay1VolRight 0x004e
+#define SMA_wCurrPlay2VolLeft 0x0050
+#define SMA_wCurrPlay2VolRight 0x0052
+#define SMA_wCurrPlay3VolLeft 0x0054
+#define SMA_wCurrPlay3VolRight 0x0056
+#define SMA_wCurrPlay4VolLeft 0x0058
+#define SMA_wCurrPlay4VolRight 0x005a
+#define SMA_wCurrPlay1PeakLeft 0x005c
+#define SMA_wCurrPlay1PeakRight 0x005e
+#define SMA_wCurrPlay2PeakLeft 0x0060
+#define SMA_wCurrPlay2PeakRight 0x0062
+#define SMA_wCurrPlay3PeakLeft 0x0064
+#define SMA_wCurrPlay3PeakRight 0x0066
+#define SMA_wCurrPlay4PeakLeft 0x0068
+#define SMA_wCurrPlay4PeakRight 0x006a
+#define SMA_wCurrPlayPeakLeft 0x006c
+#define SMA_wCurrPlayPeakRight 0x006e
+#define SMA_wCurrDATSR 0x0070
+#define SMA_wCurrDATRXCHNL 0x0072
+#define SMA_wCurrDATTXCHNL 0x0074
+#define SMA_wCurrDATRXRate 0x0076
+#define SMA_dwDSPPlayCount 0x0078
+#define SMA__size 0x007c
+
+#ifdef HAVE_DSPCODEH
+# include "pndsperm.c"
+# include "pndspini.c"
+# define PERMCODE pndsperm
+# define INITCODE pndspini
+# define PERMCODESIZE sizeof(pndsperm)
+# define INITCODESIZE sizeof(pndspini)
+#else
+# ifndef CONFIG_MSNDPIN_INIT_FILE
+# define CONFIG_MSNDPIN_INIT_FILE \
+ "/etc/sound/pndspini.bin"
+# endif
+# ifndef CONFIG_MSNDPIN_PERM_FILE
+# define CONFIG_MSNDPIN_PERM_FILE \
+ "/etc/sound/pndsperm.bin"
+# endif
+# define PERMCODEFILE CONFIG_MSNDPIN_PERM_FILE
+# define INITCODEFILE CONFIG_MSNDPIN_INIT_FILE
+# define PERMCODE dspini
+# define INITCODE permini
+# define PERMCODESIZE sizeof_dspini
+# define INITCODESIZE sizeof_permini
+#endif
+#define LONGNAME "MultiSound (Pinnacle/Fiji)"
+
+#endif /* __MSND_PINNACLE_H */
diff --git a/sound/oss/opl3.c b/sound/oss/opl3.c
new file mode 100644
index 00000000..407cd677
--- /dev/null
+++ b/sound/oss/opl3.c
@@ -0,0 +1,1258 @@
+/*
+ * sound/oss/opl3.c
+ *
+ * A low level driver for Yamaha YM3812 and OPL-3 -chips
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Changes
+ * Thomas Sailer ioctl code reworked (vmalloc/vfree removed)
+ * Alan Cox modularisation, fixed sound_mem allocs.
+ * Christoph Hellwig Adapted to module_init/module_exit
+ * Arnaldo C. de Melo get rid of check_region, use request_region for
+ * OPL4, release it on exit, some cleanups.
+ *
+ * Status
+ * Believed to work. Badly needs rewriting a bit to support multiple
+ * OPL3 devices.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+/*
+ * Major improvements to the FM handling 30AUG92 by Rob Hooft,
+ * hooft@chem.ruu.nl
+ */
+
+#include "sound_config.h"
+
+#include "opl3_hw.h"
+
+#define MAX_VOICE 18
+#define OFFS_4OP 11
+
+struct voice_info
+{
+ unsigned char keyon_byte;
+ long bender;
+ long bender_range;
+ unsigned long orig_freq;
+ unsigned long current_freq;
+ int volume;
+ int mode;
+ int panning; /* 0xffff means not set */
+};
+
+typedef struct opl_devinfo
+{
+ int base;
+ int left_io, right_io;
+ int nr_voice;
+ int lv_map[MAX_VOICE];
+
+ struct voice_info voc[MAX_VOICE];
+ struct voice_alloc_info *v_alloc;
+ struct channel_info *chn_info;
+
+ struct sbi_instrument i_map[SBFM_MAXINSTR];
+ struct sbi_instrument *act_i[MAX_VOICE];
+
+ struct synth_info fm_info;
+
+ int busy;
+ int model;
+ unsigned char cmask;
+
+ int is_opl4;
+} opl_devinfo;
+
+static struct opl_devinfo *devc = NULL;
+
+static int detected_model;
+
+static int store_instr(int instr_no, struct sbi_instrument *instr);
+static void freq_to_fnum(int freq, int *block, int *fnum);
+static void opl3_command(int io_addr, unsigned int addr, unsigned int val);
+static int opl3_kill_note(int dev, int voice, int note, int velocity);
+
+static void enter_4op_mode(void)
+{
+ int i;
+ static int v4op[MAX_VOICE] = {
+ 0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17
+ };
+
+ devc->cmask = 0x3f; /* Connect all possible 4 OP voice operators */
+ opl3_command(devc->right_io, CONNECTION_SELECT_REGISTER, 0x3f);
+
+ for (i = 0; i < 3; i++)
+ pv_map[i].voice_mode = 4;
+ for (i = 3; i < 6; i++)
+ pv_map[i].voice_mode = 0;
+
+ for (i = 9; i < 12; i++)
+ pv_map[i].voice_mode = 4;
+ for (i = 12; i < 15; i++)
+ pv_map[i].voice_mode = 0;
+
+ for (i = 0; i < 12; i++)
+ devc->lv_map[i] = v4op[i];
+ devc->v_alloc->max_voice = devc->nr_voice = 12;
+}
+
+static int opl3_ioctl(int dev, unsigned int cmd, void __user * arg)
+{
+ struct sbi_instrument ins;
+
+ switch (cmd) {
+ case SNDCTL_FM_LOAD_INSTR:
+ printk(KERN_WARNING "Warning: Obsolete ioctl(SNDCTL_FM_LOAD_INSTR) used. Fix the program.\n");
+ if (copy_from_user(&ins, arg, sizeof(ins)))
+ return -EFAULT;
+ if (ins.channel < 0 || ins.channel >= SBFM_MAXINSTR) {
+ printk(KERN_WARNING "FM Error: Invalid instrument number %d\n", ins.channel);
+ return -EINVAL;
+ }
+ return store_instr(ins.channel, &ins);
+
+ case SNDCTL_SYNTH_INFO:
+ devc->fm_info.nr_voices = (devc->nr_voice == 12) ? 6 : devc->nr_voice;
+ if (copy_to_user(arg, &devc->fm_info, sizeof(devc->fm_info)))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_SYNTH_MEMAVL:
+ return 0x7fffffff;
+
+ case SNDCTL_FM_4OP_ENABLE:
+ if (devc->model == 2)
+ enter_4op_mode();
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int opl3_detect(int ioaddr)
+{
+ /*
+ * This function returns 1 if the FM chip is present at the given I/O port
+ * The detection algorithm plays with the timer built in the FM chip and
+ * looks for a change in the status register.
+ *
+ * Note! The timers of the FM chip are not connected to AdLib (and compatible)
+ * boards.
+ *
+ * Note2! The chip is initialized if detected.
+ */
+
+ unsigned char stat1, signature;
+ int i;
+
+ if (devc != NULL)
+ {
+ printk(KERN_ERR "opl3: Only one OPL3 supported.\n");
+ return 0;
+ }
+
+ devc = kzalloc(sizeof(*devc), GFP_KERNEL);
+
+ if (devc == NULL)
+ {
+ printk(KERN_ERR "opl3: Can't allocate memory for the device control "
+ "structure \n ");
+ return 0;
+ }
+
+ strcpy(devc->fm_info.name, "OPL2");
+
+ if (!request_region(ioaddr, 4, devc->fm_info.name)) {
+ printk(KERN_WARNING "opl3: I/O port 0x%x already in use\n", ioaddr);
+ goto cleanup_devc;
+ }
+
+ devc->base = ioaddr;
+
+ /* Reset timers 1 and 2 */
+ opl3_command(ioaddr, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK);
+
+ /* Reset the IRQ of the FM chip */
+ opl3_command(ioaddr, TIMER_CONTROL_REGISTER, IRQ_RESET);
+
+ signature = stat1 = inb(ioaddr); /* Status register */
+
+ if (signature != 0x00 && signature != 0x06 && signature != 0x02 &&
+ signature != 0x0f)
+ {
+ MDB(printk(KERN_INFO "OPL3 not detected %x\n", signature));
+ goto cleanup_region;
+ }
+
+ if (signature == 0x06) /* OPL2 */
+ {
+ detected_model = 2;
+ }
+ else if (signature == 0x00 || signature == 0x0f) /* OPL3 or OPL4 */
+ {
+ unsigned char tmp;
+
+ detected_model = 3;
+
+ /*
+ * Detect availability of OPL4 (_experimental_). Works probably
+ * only after a cold boot. In addition the OPL4 port
+ * of the chip may not be connected to the PC bus at all.
+ */
+
+ opl3_command(ioaddr + 2, OPL3_MODE_REGISTER, 0x00);
+ opl3_command(ioaddr + 2, OPL3_MODE_REGISTER, OPL3_ENABLE | OPL4_ENABLE);
+
+ if ((tmp = inb(ioaddr)) == 0x02) /* Have a OPL4 */
+ {
+ detected_model = 4;
+ }
+
+ if (request_region(ioaddr - 8, 2, "OPL4")) /* OPL4 port was free */
+ {
+ int tmp;
+
+ outb((0x02), ioaddr - 8); /* Select OPL4 ID register */
+ udelay(10);
+ tmp = inb(ioaddr - 7); /* Read it */
+ udelay(10);
+
+ if (tmp == 0x20) /* OPL4 should return 0x20 here */
+ {
+ detected_model = 4;
+ outb((0xF8), ioaddr - 8); /* Select OPL4 FM mixer control */
+ udelay(10);
+ outb((0x1B), ioaddr - 7); /* Write value */
+ udelay(10);
+ }
+ else
+ { /* release OPL4 port */
+ release_region(ioaddr - 8, 2);
+ detected_model = 3;
+ }
+ }
+ opl3_command(ioaddr + 2, OPL3_MODE_REGISTER, 0);
+ }
+ for (i = 0; i < 9; i++)
+ opl3_command(ioaddr, KEYON_BLOCK + i, 0); /*
+ * Note off
+ */
+
+ opl3_command(ioaddr, TEST_REGISTER, ENABLE_WAVE_SELECT);
+ opl3_command(ioaddr, PERCOSSION_REGISTER, 0x00); /*
+ * Melodic mode.
+ */
+ return 1;
+cleanup_region:
+ release_region(ioaddr, 4);
+cleanup_devc:
+ kfree(devc);
+ devc = NULL;
+ return 0;
+}
+
+static int opl3_kill_note (int devno, int voice, int note, int velocity)
+{
+ struct physical_voice_info *map;
+
+ if (voice < 0 || voice >= devc->nr_voice)
+ return 0;
+
+ devc->v_alloc->map[voice] = 0;
+
+ map = &pv_map[devc->lv_map[voice]];
+ DEB(printk("Kill note %d\n", voice));
+
+ if (map->voice_mode == 0)
+ return 0;
+
+ opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num, devc->voc[voice].keyon_byte & ~0x20);
+ devc->voc[voice].keyon_byte = 0;
+ devc->voc[voice].bender = 0;
+ devc->voc[voice].volume = 64;
+ devc->voc[voice].panning = 0xffff; /* Not set */
+ devc->voc[voice].bender_range = 200;
+ devc->voc[voice].orig_freq = 0;
+ devc->voc[voice].current_freq = 0;
+ devc->voc[voice].mode = 0;
+ return 0;
+}
+
+#define HIHAT 0
+#define CYMBAL 1
+#define TOMTOM 2
+#define SNARE 3
+#define BDRUM 4
+#define UNDEFINED TOMTOM
+#define DEFAULT TOMTOM
+
+static int store_instr(int instr_no, struct sbi_instrument *instr)
+{
+ if (instr->key != FM_PATCH && (instr->key != OPL3_PATCH || devc->model != 2))
+ printk(KERN_WARNING "FM warning: Invalid patch format field (key) 0x%x\n", instr->key);
+ memcpy((char *) &(devc->i_map[instr_no]), (char *) instr, sizeof(*instr));
+ return 0;
+}
+
+static int opl3_set_instr (int dev, int voice, int instr_no)
+{
+ if (voice < 0 || voice >= devc->nr_voice)
+ return 0;
+ if (instr_no < 0 || instr_no >= SBFM_MAXINSTR)
+ instr_no = 0; /* Acoustic piano (usually) */
+
+ devc->act_i[voice] = &devc->i_map[instr_no];
+ return 0;
+}
+
+/*
+ * The next table looks magical, but it certainly is not. Its values have
+ * been calculated as table[i]=8*log(i/64)/log(2) with an obvious exception
+ * for i=0. This log-table converts a linear volume-scaling (0..127) to a
+ * logarithmic scaling as present in the FM-synthesizer chips. so : Volume
+ * 64 = 0 db = relative volume 0 and: Volume 32 = -6 db = relative
+ * volume -8 it was implemented as a table because it is only 128 bytes and
+ * it saves a lot of log() calculations. (RH)
+ */
+
+static char fm_volume_table[128] =
+{
+ -64, -48, -40, -35, -32, -29, -27, -26,
+ -24, -23, -21, -20, -19, -18, -18, -17,
+ -16, -15, -15, -14, -13, -13, -12, -12,
+ -11, -11, -10, -10, -10, -9, -9, -8,
+ -8, -8, -7, -7, -7, -6, -6, -6,
+ -5, -5, -5, -5, -4, -4, -4, -4,
+ -3, -3, -3, -3, -2, -2, -2, -2,
+ -2, -1, -1, -1, -1, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1,
+ 1, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 4,
+ 4, 4, 4, 4, 4, 4, 4, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 8, 8, 8, 8, 8
+};
+
+static void calc_vol(unsigned char *regbyte, int volume, int main_vol)
+{
+ int level = (~*regbyte & 0x3f);
+
+ if (main_vol > 127)
+ main_vol = 127;
+ volume = (volume * main_vol) / 127;
+
+ if (level)
+ level += fm_volume_table[volume];
+
+ if (level > 0x3f)
+ level = 0x3f;
+ if (level < 0)
+ level = 0;
+
+ *regbyte = (*regbyte & 0xc0) | (~level & 0x3f);
+}
+
+static void set_voice_volume(int voice, int volume, int main_vol)
+{
+ unsigned char vol1, vol2, vol3, vol4;
+ struct sbi_instrument *instr;
+ struct physical_voice_info *map;
+
+ if (voice < 0 || voice >= devc->nr_voice)
+ return;
+
+ map = &pv_map[devc->lv_map[voice]];
+ instr = devc->act_i[voice];
+
+ if (!instr)
+ instr = &devc->i_map[0];
+
+ if (instr->channel < 0)
+ return;
+
+ if (devc->voc[voice].mode == 0)
+ return;
+
+ if (devc->voc[voice].mode == 2)
+ {
+ vol1 = instr->operators[2];
+ vol2 = instr->operators[3];
+ if ((instr->operators[10] & 0x01))
+ {
+ calc_vol(&vol1, volume, main_vol);
+ calc_vol(&vol2, volume, main_vol);
+ }
+ else
+ {
+ calc_vol(&vol2, volume, main_vol);
+ }
+ opl3_command(map->ioaddr, KSL_LEVEL + map->op[0], vol1);
+ opl3_command(map->ioaddr, KSL_LEVEL + map->op[1], vol2);
+ }
+ else
+ { /*
+ * 4 OP voice
+ */
+ int connection;
+
+ vol1 = instr->operators[2];
+ vol2 = instr->operators[3];
+ vol3 = instr->operators[OFFS_4OP + 2];
+ vol4 = instr->operators[OFFS_4OP + 3];
+
+ /*
+ * The connection method for 4 OP devc->voc is defined by the rightmost
+ * bits at the offsets 10 and 10+OFFS_4OP
+ */
+
+ connection = ((instr->operators[10] & 0x01) << 1) | (instr->operators[10 + OFFS_4OP] & 0x01);
+
+ switch (connection)
+ {
+ case 0:
+ calc_vol(&vol4, volume, main_vol);
+ break;
+
+ case 1:
+ calc_vol(&vol2, volume, main_vol);
+ calc_vol(&vol4, volume, main_vol);
+ break;
+
+ case 2:
+ calc_vol(&vol1, volume, main_vol);
+ calc_vol(&vol4, volume, main_vol);
+ break;
+
+ case 3:
+ calc_vol(&vol1, volume, main_vol);
+ calc_vol(&vol3, volume, main_vol);
+ calc_vol(&vol4, volume, main_vol);
+ break;
+
+ default:
+ ;
+ }
+ opl3_command(map->ioaddr, KSL_LEVEL + map->op[0], vol1);
+ opl3_command(map->ioaddr, KSL_LEVEL + map->op[1], vol2);
+ opl3_command(map->ioaddr, KSL_LEVEL + map->op[2], vol3);
+ opl3_command(map->ioaddr, KSL_LEVEL + map->op[3], vol4);
+ }
+}
+
+static int opl3_start_note (int dev, int voice, int note, int volume)
+{
+ unsigned char data, fpc;
+ int block, fnum, freq, voice_mode, pan;
+ struct sbi_instrument *instr;
+ struct physical_voice_info *map;
+
+ if (voice < 0 || voice >= devc->nr_voice)
+ return 0;
+
+ map = &pv_map[devc->lv_map[voice]];
+ pan = devc->voc[voice].panning;
+
+ if (map->voice_mode == 0)
+ return 0;
+
+ if (note == 255) /*
+ * Just change the volume
+ */
+ {
+ set_voice_volume(voice, volume, devc->voc[voice].volume);
+ return 0;
+ }
+
+ /*
+ * Kill previous note before playing
+ */
+
+ opl3_command(map->ioaddr, KSL_LEVEL + map->op[1], 0xff); /*
+ * Carrier
+ * volume to
+ * min
+ */
+ opl3_command(map->ioaddr, KSL_LEVEL + map->op[0], 0xff); /*
+ * Modulator
+ * volume to
+ */
+
+ if (map->voice_mode == 4)
+ {
+ opl3_command(map->ioaddr, KSL_LEVEL + map->op[2], 0xff);
+ opl3_command(map->ioaddr, KSL_LEVEL + map->op[3], 0xff);
+ }
+
+ opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num, 0x00); /*
+ * Note
+ * off
+ */
+
+ instr = devc->act_i[voice];
+
+ if (!instr)
+ instr = &devc->i_map[0];
+
+ if (instr->channel < 0)
+ {
+ printk(KERN_WARNING "opl3: Initializing voice %d with undefined instrument\n", voice);
+ return 0;
+ }
+
+ if (map->voice_mode == 2 && instr->key == OPL3_PATCH)
+ return 0; /*
+ * Cannot play
+ */
+
+ voice_mode = map->voice_mode;
+
+ if (voice_mode == 4)
+ {
+ int voice_shift;
+
+ voice_shift = (map->ioaddr == devc->left_io) ? 0 : 3;
+ voice_shift += map->voice_num;
+
+ if (instr->key != OPL3_PATCH) /*
+ * Just 2 OP patch
+ */
+ {
+ voice_mode = 2;
+ devc->cmask &= ~(1 << voice_shift);
+ }
+ else
+ {
+ devc->cmask |= (1 << voice_shift);
+ }
+
+ opl3_command(devc->right_io, CONNECTION_SELECT_REGISTER, devc->cmask);
+ }
+
+ /*
+ * Set Sound Characteristics
+ */
+
+ opl3_command(map->ioaddr, AM_VIB + map->op[0], instr->operators[0]);
+ opl3_command(map->ioaddr, AM_VIB + map->op[1], instr->operators[1]);
+
+ /*
+ * Set Attack/Decay
+ */
+
+ opl3_command(map->ioaddr, ATTACK_DECAY + map->op[0], instr->operators[4]);
+ opl3_command(map->ioaddr, ATTACK_DECAY + map->op[1], instr->operators[5]);
+
+ /*
+ * Set Sustain/Release
+ */
+
+ opl3_command(map->ioaddr, SUSTAIN_RELEASE + map->op[0], instr->operators[6]);
+ opl3_command(map->ioaddr, SUSTAIN_RELEASE + map->op[1], instr->operators[7]);
+
+ /*
+ * Set Wave Select
+ */
+
+ opl3_command(map->ioaddr, WAVE_SELECT + map->op[0], instr->operators[8]);
+ opl3_command(map->ioaddr, WAVE_SELECT + map->op[1], instr->operators[9]);
+
+ /*
+ * Set Feedback/Connection
+ */
+
+ fpc = instr->operators[10];
+
+ if (pan != 0xffff)
+ {
+ fpc &= ~STEREO_BITS;
+ if (pan < -64)
+ fpc |= VOICE_TO_LEFT;
+ else
+ if (pan > 64)
+ fpc |= VOICE_TO_RIGHT;
+ else
+ fpc |= (VOICE_TO_LEFT | VOICE_TO_RIGHT);
+ }
+
+ if (!(fpc & 0x30))
+ fpc |= 0x30; /*
+ * Ensure that at least one chn is enabled
+ */
+ opl3_command(map->ioaddr, FEEDBACK_CONNECTION + map->voice_num, fpc);
+
+ /*
+ * If the voice is a 4 OP one, initialize the operators 3 and 4 also
+ */
+
+ if (voice_mode == 4)
+ {
+ /*
+ * Set Sound Characteristics
+ */
+
+ opl3_command(map->ioaddr, AM_VIB + map->op[2], instr->operators[OFFS_4OP + 0]);
+ opl3_command(map->ioaddr, AM_VIB + map->op[3], instr->operators[OFFS_4OP + 1]);
+
+ /*
+ * Set Attack/Decay
+ */
+
+ opl3_command(map->ioaddr, ATTACK_DECAY + map->op[2], instr->operators[OFFS_4OP + 4]);
+ opl3_command(map->ioaddr, ATTACK_DECAY + map->op[3], instr->operators[OFFS_4OP + 5]);
+
+ /*
+ * Set Sustain/Release
+ */
+
+ opl3_command(map->ioaddr, SUSTAIN_RELEASE + map->op[2], instr->operators[OFFS_4OP + 6]);
+ opl3_command(map->ioaddr, SUSTAIN_RELEASE + map->op[3], instr->operators[OFFS_4OP + 7]);
+
+ /*
+ * Set Wave Select
+ */
+
+ opl3_command(map->ioaddr, WAVE_SELECT + map->op[2], instr->operators[OFFS_4OP + 8]);
+ opl3_command(map->ioaddr, WAVE_SELECT + map->op[3], instr->operators[OFFS_4OP + 9]);
+
+ /*
+ * Set Feedback/Connection
+ */
+
+ fpc = instr->operators[OFFS_4OP + 10];
+ if (!(fpc & 0x30))
+ fpc |= 0x30; /*
+ * Ensure that at least one chn is enabled
+ */
+ opl3_command(map->ioaddr, FEEDBACK_CONNECTION + map->voice_num + 3, fpc);
+ }
+
+ devc->voc[voice].mode = voice_mode;
+ set_voice_volume(voice, volume, devc->voc[voice].volume);
+
+ freq = devc->voc[voice].orig_freq = note_to_freq(note) / 1000;
+
+ /*
+ * Since the pitch bender may have been set before playing the note, we
+ * have to calculate the bending now.
+ */
+
+ freq = compute_finetune(devc->voc[voice].orig_freq, devc->voc[voice].bender, devc->voc[voice].bender_range, 0);
+ devc->voc[voice].current_freq = freq;
+
+ freq_to_fnum(freq, &block, &fnum);
+
+ /*
+ * Play note
+ */
+
+ data = fnum & 0xff; /*
+ * Least significant bits of fnumber
+ */
+ opl3_command(map->ioaddr, FNUM_LOW + map->voice_num, data);
+
+ data = 0x20 | ((block & 0x7) << 2) | ((fnum >> 8) & 0x3);
+ devc->voc[voice].keyon_byte = data;
+ opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num, data);
+ if (voice_mode == 4)
+ opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num + 3, data);
+
+ return 0;
+}
+
+static void freq_to_fnum (int freq, int *block, int *fnum)
+{
+ int f, octave;
+
+ /*
+ * Converts the note frequency to block and fnum values for the FM chip
+ */
+ /*
+ * First try to compute the block -value (octave) where the note belongs
+ */
+
+ f = freq;
+
+ octave = 5;
+
+ if (f == 0)
+ octave = 0;
+ else if (f < 261)
+ {
+ while (f < 261)
+ {
+ octave--;
+ f <<= 1;
+ }
+ }
+ else if (f > 493)
+ {
+ while (f > 493)
+ {
+ octave++;
+ f >>= 1;
+ }
+ }
+
+ if (octave > 7)
+ octave = 7;
+
+ *fnum = freq * (1 << (20 - octave)) / 49716;
+ *block = octave;
+}
+
+static void opl3_command (int io_addr, unsigned int addr, unsigned int val)
+{
+ int i;
+
+ /*
+ * The original 2-OP synth requires a quite long delay after writing to a
+ * register. The OPL-3 survives with just two INBs
+ */
+
+ outb(((unsigned char) (addr & 0xff)), io_addr);
+
+ if (devc->model != 2)
+ udelay(10);
+ else
+ for (i = 0; i < 2; i++)
+ inb(io_addr);
+
+ outb(((unsigned char) (val & 0xff)), io_addr + 1);
+
+ if (devc->model != 2)
+ udelay(30);
+ else
+ for (i = 0; i < 2; i++)
+ inb(io_addr);
+}
+
+static void opl3_reset(int devno)
+{
+ int i;
+
+ for (i = 0; i < 18; i++)
+ devc->lv_map[i] = i;
+
+ for (i = 0; i < devc->nr_voice; i++)
+ {
+ opl3_command(pv_map[devc->lv_map[i]].ioaddr,
+ KSL_LEVEL + pv_map[devc->lv_map[i]].op[0], 0xff);
+
+ opl3_command(pv_map[devc->lv_map[i]].ioaddr,
+ KSL_LEVEL + pv_map[devc->lv_map[i]].op[1], 0xff);
+
+ if (pv_map[devc->lv_map[i]].voice_mode == 4)
+ {
+ opl3_command(pv_map[devc->lv_map[i]].ioaddr,
+ KSL_LEVEL + pv_map[devc->lv_map[i]].op[2], 0xff);
+
+ opl3_command(pv_map[devc->lv_map[i]].ioaddr,
+ KSL_LEVEL + pv_map[devc->lv_map[i]].op[3], 0xff);
+ }
+
+ opl3_kill_note(devno, i, 0, 64);
+ }
+
+ if (devc->model == 2)
+ {
+ devc->v_alloc->max_voice = devc->nr_voice = 18;
+
+ for (i = 0; i < 18; i++)
+ pv_map[i].voice_mode = 2;
+
+ }
+}
+
+static int opl3_open(int dev, int mode)
+{
+ int i;
+
+ if (devc->busy)
+ return -EBUSY;
+ devc->busy = 1;
+
+ devc->v_alloc->max_voice = devc->nr_voice = (devc->model == 2) ? 18 : 9;
+ devc->v_alloc->timestamp = 0;
+
+ for (i = 0; i < 18; i++)
+ {
+ devc->v_alloc->map[i] = 0;
+ devc->v_alloc->alloc_times[i] = 0;
+ }
+
+ devc->cmask = 0x00; /*
+ * Just 2 OP mode
+ */
+ if (devc->model == 2)
+ opl3_command(devc->right_io, CONNECTION_SELECT_REGISTER, devc->cmask);
+ return 0;
+}
+
+static void opl3_close(int dev)
+{
+ devc->busy = 0;
+ devc->v_alloc->max_voice = devc->nr_voice = (devc->model == 2) ? 18 : 9;
+
+ devc->fm_info.nr_drums = 0;
+ devc->fm_info.perc_mode = 0;
+
+ opl3_reset(dev);
+}
+
+static void opl3_hw_control(int dev, unsigned char *event)
+{
+}
+
+static int opl3_load_patch(int dev, int format, const char __user *addr,
+ int count, int pmgr_flag)
+{
+ struct sbi_instrument ins;
+
+ if (count <sizeof(ins))
+ {
+ printk(KERN_WARNING "FM Error: Patch record too short\n");
+ return -EINVAL;
+ }
+
+ if (copy_from_user(&ins, addr, sizeof(ins)))
+ return -EFAULT;
+
+ if (ins.channel < 0 || ins.channel >= SBFM_MAXINSTR)
+ {
+ printk(KERN_WARNING "FM Error: Invalid instrument number %d\n", ins.channel);
+ return -EINVAL;
+ }
+ ins.key = format;
+
+ return store_instr(ins.channel, &ins);
+}
+
+static void opl3_panning(int dev, int voice, int value)
+{
+
+ if (voice < 0 || voice >= devc->nr_voice)
+ return;
+
+ devc->voc[voice].panning = value;
+}
+
+static void opl3_volume_method(int dev, int mode)
+{
+}
+
+#define SET_VIBRATO(cell) { \
+ tmp = instr->operators[(cell-1)+(((cell-1)/2)*OFFS_4OP)]; \
+ if (pressure > 110) \
+ tmp |= 0x40; /* Vibrato on */ \
+ opl3_command (map->ioaddr, AM_VIB + map->op[cell-1], tmp);}
+
+static void opl3_aftertouch(int dev, int voice, int pressure)
+{
+ int tmp;
+ struct sbi_instrument *instr;
+ struct physical_voice_info *map;
+
+ if (voice < 0 || voice >= devc->nr_voice)
+ return;
+
+ map = &pv_map[devc->lv_map[voice]];
+
+ DEB(printk("Aftertouch %d\n", voice));
+
+ if (map->voice_mode == 0)
+ return;
+
+ /*
+ * Adjust the amount of vibrato depending the pressure
+ */
+
+ instr = devc->act_i[voice];
+
+ if (!instr)
+ instr = &devc->i_map[0];
+
+ if (devc->voc[voice].mode == 4)
+ {
+ int connection = ((instr->operators[10] & 0x01) << 1) | (instr->operators[10 + OFFS_4OP] & 0x01);
+
+ switch (connection)
+ {
+ case 0:
+ SET_VIBRATO(4);
+ break;
+
+ case 1:
+ SET_VIBRATO(2);
+ SET_VIBRATO(4);
+ break;
+
+ case 2:
+ SET_VIBRATO(1);
+ SET_VIBRATO(4);
+ break;
+
+ case 3:
+ SET_VIBRATO(1);
+ SET_VIBRATO(3);
+ SET_VIBRATO(4);
+ break;
+
+ }
+ /*
+ * Not implemented yet
+ */
+ }
+ else
+ {
+ SET_VIBRATO(1);
+
+ if ((instr->operators[10] & 0x01)) /*
+ * Additive synthesis
+ */
+ SET_VIBRATO(2);
+ }
+}
+
+#undef SET_VIBRATO
+
+static void bend_pitch(int dev, int voice, int value)
+{
+ unsigned char data;
+ int block, fnum, freq;
+ struct physical_voice_info *map;
+
+ map = &pv_map[devc->lv_map[voice]];
+
+ if (map->voice_mode == 0)
+ return;
+
+ devc->voc[voice].bender = value;
+ if (!value)
+ return;
+ if (!(devc->voc[voice].keyon_byte & 0x20))
+ return; /*
+ * Not keyed on
+ */
+
+ freq = compute_finetune(devc->voc[voice].orig_freq, devc->voc[voice].bender, devc->voc[voice].bender_range, 0);
+ devc->voc[voice].current_freq = freq;
+
+ freq_to_fnum(freq, &block, &fnum);
+
+ data = fnum & 0xff; /*
+ * Least significant bits of fnumber
+ */
+ opl3_command(map->ioaddr, FNUM_LOW + map->voice_num, data);
+
+ data = 0x20 | ((block & 0x7) << 2) | ((fnum >> 8) & 0x3);
+ devc->voc[voice].keyon_byte = data;
+ opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num, data);
+}
+
+static void opl3_controller (int dev, int voice, int ctrl_num, int value)
+{
+ if (voice < 0 || voice >= devc->nr_voice)
+ return;
+
+ switch (ctrl_num)
+ {
+ case CTRL_PITCH_BENDER:
+ bend_pitch(dev, voice, value);
+ break;
+
+ case CTRL_PITCH_BENDER_RANGE:
+ devc->voc[voice].bender_range = value;
+ break;
+
+ case CTL_MAIN_VOLUME:
+ devc->voc[voice].volume = value / 128;
+ break;
+
+ case CTL_PAN:
+ devc->voc[voice].panning = (value * 2) - 128;
+ break;
+ }
+}
+
+static void opl3_bender(int dev, int voice, int value)
+{
+ if (voice < 0 || voice >= devc->nr_voice)
+ return;
+
+ bend_pitch(dev, voice, value - 8192);
+}
+
+static int opl3_alloc_voice(int dev, int chn, int note, struct voice_alloc_info *alloc)
+{
+ int i, p, best, first, avail, best_time = 0x7fffffff;
+ struct sbi_instrument *instr;
+ int is4op;
+ int instr_no;
+
+ if (chn < 0 || chn > 15)
+ instr_no = 0;
+ else
+ instr_no = devc->chn_info[chn].pgm_num;
+
+ instr = &devc->i_map[instr_no];
+ if (instr->channel < 0 || /* Instrument not loaded */
+ devc->nr_voice != 12) /* Not in 4 OP mode */
+ is4op = 0;
+ else if (devc->nr_voice == 12) /* 4 OP mode */
+ is4op = (instr->key == OPL3_PATCH);
+ else
+ is4op = 0;
+
+ if (is4op)
+ {
+ first = p = 0;
+ avail = 6;
+ }
+ else
+ {
+ if (devc->nr_voice == 12) /* 4 OP mode. Use the '2 OP only' operators first */
+ first = p = 6;
+ else
+ first = p = 0;
+ avail = devc->nr_voice;
+ }
+
+ /*
+ * Now try to find a free voice
+ */
+ best = first;
+
+ for (i = 0; i < avail; i++)
+ {
+ if (alloc->map[p] == 0)
+ {
+ return p;
+ }
+ if (alloc->alloc_times[p] < best_time) /* Find oldest playing note */
+ {
+ best_time = alloc->alloc_times[p];
+ best = p;
+ }
+ p = (p + 1) % avail;
+ }
+
+ /*
+ * Insert some kind of priority mechanism here.
+ */
+
+ if (best < 0)
+ best = 0;
+ if (best > devc->nr_voice)
+ best -= devc->nr_voice;
+
+ return best; /* All devc->voc in use. Select the first one. */
+}
+
+static void opl3_setup_voice(int dev, int voice, int chn)
+{
+ struct channel_info *info;
+
+ if (voice < 0 || voice >= devc->nr_voice)
+ return;
+
+ if (chn < 0 || chn > 15)
+ return;
+
+ info = &synth_devs[dev]->chn_info[chn];
+
+ opl3_set_instr(dev, voice, info->pgm_num);
+
+ devc->voc[voice].bender = 0;
+ devc->voc[voice].bender_range = info->bender_range;
+ devc->voc[voice].volume = info->controllers[CTL_MAIN_VOLUME];
+ devc->voc[voice].panning = (info->controllers[CTL_PAN] * 2) - 128;
+}
+
+static struct synth_operations opl3_operations =
+{
+ .owner = THIS_MODULE,
+ .id = "OPL",
+ .info = NULL,
+ .midi_dev = 0,
+ .synth_type = SYNTH_TYPE_FM,
+ .synth_subtype = FM_TYPE_ADLIB,
+ .open = opl3_open,
+ .close = opl3_close,
+ .ioctl = opl3_ioctl,
+ .kill_note = opl3_kill_note,
+ .start_note = opl3_start_note,
+ .set_instr = opl3_set_instr,
+ .reset = opl3_reset,
+ .hw_control = opl3_hw_control,
+ .load_patch = opl3_load_patch,
+ .aftertouch = opl3_aftertouch,
+ .controller = opl3_controller,
+ .panning = opl3_panning,
+ .volume_method = opl3_volume_method,
+ .bender = opl3_bender,
+ .alloc_voice = opl3_alloc_voice,
+ .setup_voice = opl3_setup_voice
+};
+
+static int opl3_init(int ioaddr, struct module *owner)
+{
+ int i;
+ int me;
+
+ if (devc == NULL)
+ {
+ printk(KERN_ERR "opl3: Device control structure not initialized.\n");
+ return -1;
+ }
+
+ if ((me = sound_alloc_synthdev()) == -1)
+ {
+ printk(KERN_WARNING "opl3: Too many synthesizers\n");
+ return -1;
+ }
+
+ devc->nr_voice = 9;
+
+ devc->fm_info.device = 0;
+ devc->fm_info.synth_type = SYNTH_TYPE_FM;
+ devc->fm_info.synth_subtype = FM_TYPE_ADLIB;
+ devc->fm_info.perc_mode = 0;
+ devc->fm_info.nr_voices = 9;
+ devc->fm_info.nr_drums = 0;
+ devc->fm_info.instr_bank_size = SBFM_MAXINSTR;
+ devc->fm_info.capabilities = 0;
+ devc->left_io = ioaddr;
+ devc->right_io = ioaddr + 2;
+
+ if (detected_model <= 2)
+ devc->model = 1;
+ else
+ {
+ devc->model = 2;
+ if (detected_model == 4)
+ devc->is_opl4 = 1;
+ }
+
+ opl3_operations.info = &devc->fm_info;
+
+ synth_devs[me] = &opl3_operations;
+
+ if (owner)
+ synth_devs[me]->owner = owner;
+
+ sequencer_init();
+ devc->v_alloc = &opl3_operations.alloc;
+ devc->chn_info = &opl3_operations.chn_info[0];
+
+ if (devc->model == 2)
+ {
+ if (devc->is_opl4)
+ strcpy(devc->fm_info.name, "Yamaha OPL4/OPL3 FM");
+ else
+ strcpy(devc->fm_info.name, "Yamaha OPL3");
+
+ devc->v_alloc->max_voice = devc->nr_voice = 18;
+ devc->fm_info.nr_drums = 0;
+ devc->fm_info.synth_subtype = FM_TYPE_OPL3;
+ devc->fm_info.capabilities |= SYNTH_CAP_OPL3;
+
+ for (i = 0; i < 18; i++)
+ {
+ if (pv_map[i].ioaddr == USE_LEFT)
+ pv_map[i].ioaddr = devc->left_io;
+ else
+ pv_map[i].ioaddr = devc->right_io;
+ }
+ opl3_command(devc->right_io, OPL3_MODE_REGISTER, OPL3_ENABLE);
+ opl3_command(devc->right_io, CONNECTION_SELECT_REGISTER, 0x00);
+ }
+ else
+ {
+ strcpy(devc->fm_info.name, "Yamaha OPL2");
+ devc->v_alloc->max_voice = devc->nr_voice = 9;
+ devc->fm_info.nr_drums = 0;
+
+ for (i = 0; i < 18; i++)
+ pv_map[i].ioaddr = devc->left_io;
+ };
+ conf_printf2(devc->fm_info.name, ioaddr, 0, -1, -1);
+
+ for (i = 0; i < SBFM_MAXINSTR; i++)
+ devc->i_map[i].channel = -1;
+
+ return me;
+}
+
+static int me;
+
+static int io = -1;
+
+module_param(io, int, 0);
+
+static int __init init_opl3 (void)
+{
+ printk(KERN_INFO "YM3812 and OPL-3 driver Copyright (C) by Hannu Savolainen, Rob Hooft 1993-1996\n");
+
+ if (io != -1) /* User loading pure OPL3 module */
+ {
+ if (!opl3_detect(io))
+ {
+ return -ENODEV;
+ }
+
+ me = opl3_init(io, THIS_MODULE);
+ }
+
+ return 0;
+}
+
+static void __exit cleanup_opl3(void)
+{
+ if (devc && io != -1)
+ {
+ if (devc->base) {
+ release_region(devc->base,4);
+ if (devc->is_opl4)
+ release_region(devc->base - 8, 2);
+ }
+ kfree(devc);
+ devc = NULL;
+ sound_unload_synthdev(me);
+ }
+}
+
+module_init(init_opl3);
+module_exit(cleanup_opl3);
+
+#ifndef MODULE
+static int __init setup_opl3(char *str)
+{
+ /* io */
+ int ints[2];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ io = ints[1];
+
+ return 1;
+}
+
+__setup("opl3=", setup_opl3);
+#endif
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/opl3_hw.h b/sound/oss/opl3_hw.h
new file mode 100644
index 00000000..8b11c893
--- /dev/null
+++ b/sound/oss/opl3_hw.h
@@ -0,0 +1,246 @@
+/*
+ * opl3_hw.h - Definitions of the OPL-3 registers
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * The OPL-3 mode is switched on by writing 0x01, to the offset 5
+ * of the right side.
+ *
+ * Another special register at the right side is at offset 4. It contains
+ * a bit mask defining which voices are used as 4 OP voices.
+ *
+ * The percussive mode is implemented in the left side only.
+ *
+ * With the above exceptions the both sides can be operated independently.
+ *
+ * A 4 OP voice can be created by setting the corresponding
+ * bit at offset 4 of the right side.
+ *
+ * For example setting the rightmost bit (0x01) changes the
+ * first voice on the right side to the 4 OP mode. The fourth
+ * voice is made inaccessible.
+ *
+ * If a voice is set to the 2 OP mode, it works like 2 OP modes
+ * of the original YM3812 (AdLib). In addition the voice can
+ * be connected the left, right or both stereo channels. It can
+ * even be left unconnected. This works with 4 OP voices also.
+ *
+ * The stereo connection bits are located in the FEEDBACK_CONNECTION
+ * register of the voice (0xC0-0xC8). In 4 OP voices these bits are
+ * in the second half of the voice.
+ */
+
+/*
+ * Register numbers for the global registers
+ */
+
+#define TEST_REGISTER 0x01
+#define ENABLE_WAVE_SELECT 0x20
+
+#define TIMER1_REGISTER 0x02
+#define TIMER2_REGISTER 0x03
+#define TIMER_CONTROL_REGISTER 0x04 /* Left side */
+#define IRQ_RESET 0x80
+#define TIMER1_MASK 0x40
+#define TIMER2_MASK 0x20
+#define TIMER1_START 0x01
+#define TIMER2_START 0x02
+
+#define CONNECTION_SELECT_REGISTER 0x04 /* Right side */
+#define RIGHT_4OP_0 0x01
+#define RIGHT_4OP_1 0x02
+#define RIGHT_4OP_2 0x04
+#define LEFT_4OP_0 0x08
+#define LEFT_4OP_1 0x10
+#define LEFT_4OP_2 0x20
+
+#define OPL3_MODE_REGISTER 0x05 /* Right side */
+#define OPL3_ENABLE 0x01
+#define OPL4_ENABLE 0x02
+
+#define KBD_SPLIT_REGISTER 0x08 /* Left side */
+#define COMPOSITE_SINE_WAVE_MODE 0x80 /* Don't use with OPL-3? */
+#define KEYBOARD_SPLIT 0x40
+
+#define PERCOSSION_REGISTER 0xbd /* Left side only */
+#define TREMOLO_DEPTH 0x80
+#define VIBRATO_DEPTH 0x40
+#define PERCOSSION_ENABLE 0x20
+#define BASSDRUM_ON 0x10
+#define SNAREDRUM_ON 0x08
+#define TOMTOM_ON 0x04
+#define CYMBAL_ON 0x02
+#define HIHAT_ON 0x01
+
+/*
+ * Offsets to the register banks for operators. To get the
+ * register number just add the operator offset to the bank offset
+ *
+ * AM/VIB/EG/KSR/Multiple (0x20 to 0x35)
+ */
+#define AM_VIB 0x20
+#define TREMOLO_ON 0x80
+#define VIBRATO_ON 0x40
+#define SUSTAIN_ON 0x20
+#define KSR 0x10 /* Key scaling rate */
+#define MULTIPLE_MASK 0x0f /* Frequency multiplier */
+
+ /*
+ * KSL/Total level (0x40 to 0x55)
+ */
+#define KSL_LEVEL 0x40
+#define KSL_MASK 0xc0 /* Envelope scaling bits */
+#define TOTAL_LEVEL_MASK 0x3f /* Strength (volume) of OP */
+
+/*
+ * Attack / Decay rate (0x60 to 0x75)
+ */
+#define ATTACK_DECAY 0x60
+#define ATTACK_MASK 0xf0
+#define DECAY_MASK 0x0f
+
+/*
+ * Sustain level / Release rate (0x80 to 0x95)
+ */
+#define SUSTAIN_RELEASE 0x80
+#define SUSTAIN_MASK 0xf0
+#define RELEASE_MASK 0x0f
+
+/*
+ * Wave select (0xE0 to 0xF5)
+ */
+#define WAVE_SELECT 0xe0
+
+/*
+ * Offsets to the register banks for voices. Just add to the
+ * voice number to get the register number.
+ *
+ * F-Number low bits (0xA0 to 0xA8).
+ */
+#define FNUM_LOW 0xa0
+
+/*
+ * F-number high bits / Key on / Block (octave) (0xB0 to 0xB8)
+ */
+#define KEYON_BLOCK 0xb0
+#define KEYON_BIT 0x20
+#define BLOCKNUM_MASK 0x1c
+#define FNUM_HIGH_MASK 0x03
+
+/*
+ * Feedback / Connection (0xc0 to 0xc8)
+ *
+ * These registers have two new bits when the OPL-3 mode
+ * is selected. These bits controls connecting the voice
+ * to the stereo channels. For 4 OP voices this bit is
+ * defined in the second half of the voice (add 3 to the
+ * register offset).
+ *
+ * For 4 OP voices the connection bit is used in the
+ * both halves (gives 4 ways to connect the operators).
+ */
+#define FEEDBACK_CONNECTION 0xc0
+#define FEEDBACK_MASK 0x0e /* Valid just for 1st OP of a voice */
+#define CONNECTION_BIT 0x01
+/*
+ * In the 4 OP mode there is four possible configurations how the
+ * operators can be connected together (in 2 OP modes there is just
+ * AM or FM). The 4 OP connection mode is defined by the rightmost
+ * bit of the FEEDBACK_CONNECTION (0xC0-0xC8) on the both halves.
+ *
+ * First half Second half Mode
+ *
+ * +---+
+ * v |
+ * 0 0 >+-1-+--2--3--4-->
+ *
+ *
+ *
+ * +---+
+ * | |
+ * 0 1 >+-1-+--2-+
+ * |->
+ * >--3----4-+
+ *
+ * +---+
+ * | |
+ * 1 0 >+-1-+-----+
+ * |->
+ * >--2--3--4-+
+ *
+ * +---+
+ * | |
+ * 1 1 >+-1-+--+
+ * |
+ * >--2--3-+->
+ * |
+ * >--4----+
+ */
+#define STEREO_BITS 0x30 /* OPL-3 only */
+#define VOICE_TO_LEFT 0x10
+#define VOICE_TO_RIGHT 0x20
+
+/*
+ * Definition table for the physical voices
+ */
+
+struct physical_voice_info {
+ unsigned char voice_num;
+ unsigned char voice_mode; /* 0=unavailable, 2=2 OP, 4=4 OP */
+ unsigned short ioaddr; /* I/O port (left or right side) */
+ unsigned char op[4]; /* Operator offsets */
+ };
+
+/*
+ * There is 18 possible 2 OP voices
+ * (9 in the left and 9 in the right).
+ * The first OP is the modulator and 2nd is the carrier.
+ *
+ * The first three voices in the both sides may be connected
+ * with another voice to a 4 OP voice. For example voice 0
+ * can be connected with voice 3. The operators of voice 3 are
+ * used as operators 3 and 4 of the new 4 OP voice.
+ * In this case the 2 OP voice number 0 is the 'first half' and
+ * voice 3 is the second.
+ */
+
+#define USE_LEFT 0
+#define USE_RIGHT 1
+
+static struct physical_voice_info pv_map[18] =
+{
+/* No Mode Side OP1 OP2 OP3 OP4 */
+/* --------------------------------------------------- */
+ { 0, 2, USE_LEFT, {0x00, 0x03, 0x08, 0x0b}},
+ { 1, 2, USE_LEFT, {0x01, 0x04, 0x09, 0x0c}},
+ { 2, 2, USE_LEFT, {0x02, 0x05, 0x0a, 0x0d}},
+
+ { 3, 2, USE_LEFT, {0x08, 0x0b, 0x00, 0x00}},
+ { 4, 2, USE_LEFT, {0x09, 0x0c, 0x00, 0x00}},
+ { 5, 2, USE_LEFT, {0x0a, 0x0d, 0x00, 0x00}},
+
+ { 6, 2, USE_LEFT, {0x10, 0x13, 0x00, 0x00}}, /* Used by percussive voices */
+ { 7, 2, USE_LEFT, {0x11, 0x14, 0x00, 0x00}}, /* if the percussive mode */
+ { 8, 2, USE_LEFT, {0x12, 0x15, 0x00, 0x00}}, /* is selected */
+
+ { 0, 2, USE_RIGHT, {0x00, 0x03, 0x08, 0x0b}},
+ { 1, 2, USE_RIGHT, {0x01, 0x04, 0x09, 0x0c}},
+ { 2, 2, USE_RIGHT, {0x02, 0x05, 0x0a, 0x0d}},
+
+ { 3, 2, USE_RIGHT, {0x08, 0x0b, 0x00, 0x00}},
+ { 4, 2, USE_RIGHT, {0x09, 0x0c, 0x00, 0x00}},
+ { 5, 2, USE_RIGHT, {0x0a, 0x0d, 0x00, 0x00}},
+
+ { 6, 2, USE_RIGHT, {0x10, 0x13, 0x00, 0x00}},
+ { 7, 2, USE_RIGHT, {0x11, 0x14, 0x00, 0x00}},
+ { 8, 2, USE_RIGHT, {0x12, 0x15, 0x00, 0x00}}
+};
+/*
+ * DMA buffer calls
+ */
diff --git a/sound/oss/os.h b/sound/oss/os.h
new file mode 100644
index 00000000..a1a962d7
--- /dev/null
+++ b/sound/oss/os.h
@@ -0,0 +1,46 @@
+#define ALLOW_SELECT
+#undef NO_INLINE_ASM
+#define SHORT_BANNERS
+#define MANUAL_PNP
+#undef DO_TIMINGS
+
+#include <linux/module.h>
+
+#ifdef __KERNEL__
+#include <linux/string.h>
+#include <linux/fs.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/param.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <asm/page.h>
+#include <asm/system.h>
+#include <linux/vmalloc.h>
+#include <asm/uaccess.h>
+#include <linux/poll.h>
+#include <linux/pci.h>
+#endif
+
+#include <linux/soundcard.h>
+
+#define FALSE 0
+#define TRUE 1
+
+extern int sound_alloc_dma(int chn, char *deviceID);
+extern int sound_open_dma(int chn, char *deviceID);
+extern void sound_free_dma(int chn);
+extern void sound_close_dma(int chn);
+
+extern void reprogram_timer(void);
+
+#define USE_AUTOINIT_DMA
+
+extern void *sound_mem_blocks[1024];
+extern int sound_nblocks;
+
+#undef PSEUDO_DMA_AUTOINIT
+#define ALLOW_BUFFER_MAPPING
+
+extern const struct file_operations oss_sound_fops;
diff --git a/sound/oss/pas2.h b/sound/oss/pas2.h
new file mode 100644
index 00000000..fa12c55f
--- /dev/null
+++ b/sound/oss/pas2.h
@@ -0,0 +1,17 @@
+
+/* From pas_card.c */
+int pas_set_intr(int mask);
+int pas_remove_intr(int mask);
+unsigned char pas_read(int ioaddr);
+void pas_write(unsigned char data, int ioaddr);
+
+/* From pas_audio.c */
+void pas_pcm_interrupt(unsigned char status, int cause);
+void pas_pcm_init(struct address_info *hw_config);
+
+/* From pas_mixer.c */
+int pas_init_mixer(void);
+
+/* From pas_midi.c */
+void pas_midi_init(void);
+void pas_midi_interrupt(void);
diff --git a/sound/oss/pas2_card.c b/sound/oss/pas2_card.c
new file mode 100644
index 00000000..7f377ec3
--- /dev/null
+++ b/sound/oss/pas2_card.c
@@ -0,0 +1,455 @@
+/*
+ * sound/oss/pas2_card.c
+ *
+ * Detection routine for the Pro Audio Spectrum cards.
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+#include "pas2.h"
+#include "sb.h"
+
+static unsigned char dma_bits[] = {
+ 4, 1, 2, 3, 0, 5, 6, 7
+};
+
+static unsigned char irq_bits[] = {
+ 0, 0, 1, 2, 3, 4, 5, 6, 0, 1, 7, 8, 9, 0, 10, 11
+};
+
+static unsigned char sb_irq_bits[] = {
+ 0x00, 0x00, 0x08, 0x10, 0x00, 0x18, 0x00, 0x20,
+ 0x00, 0x08, 0x28, 0x30, 0x38, 0, 0
+};
+
+static unsigned char sb_dma_bits[] = {
+ 0x00, 0x40, 0x80, 0xC0, 0, 0, 0, 0
+};
+
+/*
+ * The Address Translation code is used to convert I/O register addresses to
+ * be relative to the given base -register
+ */
+
+int pas_translate_code = 0;
+static int pas_intr_mask;
+static int pas_irq;
+static int pas_sb_base;
+DEFINE_SPINLOCK(pas_lock);
+#ifndef CONFIG_PAS_JOYSTICK
+static int joystick;
+#else
+static int joystick = 1;
+#endif
+#ifdef SYMPHONY_PAS
+static int symphony = 1;
+#else
+static int symphony;
+#endif
+#ifdef BROKEN_BUS_CLOCK
+static int broken_bus_clock = 1;
+#else
+static int broken_bus_clock;
+#endif
+
+static struct address_info cfg;
+static struct address_info cfg2;
+
+char pas_model = 0;
+static char *pas_model_names[] = {
+ "",
+ "Pro AudioSpectrum+",
+ "CDPC",
+ "Pro AudioSpectrum 16",
+ "Pro AudioSpectrum 16D"
+};
+
+/*
+ * pas_read() and pas_write() are equivalents of inb and outb
+ * These routines perform the I/O address translation required
+ * to support other than the default base address
+ */
+
+extern void mix_write(unsigned char data, int ioaddr);
+
+unsigned char pas_read(int ioaddr)
+{
+ return inb(ioaddr + pas_translate_code);
+}
+
+void pas_write(unsigned char data, int ioaddr)
+{
+ outb((data), ioaddr + pas_translate_code);
+}
+
+/******************* Begin of the Interrupt Handler ********************/
+
+static irqreturn_t pasintr(int irq, void *dev_id)
+{
+ int status;
+
+ status = pas_read(0x0B89);
+ pas_write(status, 0x0B89); /* Clear interrupt */
+
+ if (status & 0x08)
+ {
+ pas_pcm_interrupt(status, 1);
+ status &= ~0x08;
+ }
+ if (status & 0x10)
+ {
+ pas_midi_interrupt();
+ status &= ~0x10;
+ }
+ return IRQ_HANDLED;
+}
+
+int pas_set_intr(int mask)
+{
+ if (!mask)
+ return 0;
+
+ pas_intr_mask |= mask;
+
+ pas_write(pas_intr_mask, 0x0B8B);
+ return 0;
+}
+
+int pas_remove_intr(int mask)
+{
+ if (!mask)
+ return 0;
+
+ pas_intr_mask &= ~mask;
+ pas_write(pas_intr_mask, 0x0B8B);
+
+ return 0;
+}
+
+/******************* End of the Interrupt handler **********************/
+
+/******************* Begin of the Initialization Code ******************/
+
+static int __init config_pas_hw(struct address_info *hw_config)
+{
+ char ok = 1;
+ unsigned int_ptrs; /* scsi/sound interrupt pointers */
+
+ pas_irq = hw_config->irq;
+
+ pas_write(0x00, 0x0B8B);
+ pas_write(0x36, 0x138B);
+ pas_write(0x36, 0x1388);
+ pas_write(0, 0x1388);
+ pas_write(0x74, 0x138B);
+ pas_write(0x74, 0x1389);
+ pas_write(0, 0x1389);
+
+ pas_write(0x80 | 0x40 | 0x20 | 1, 0x0B8A);
+ pas_write(0x80 | 0x20 | 0x10 | 0x08 | 0x01, 0xF8A);
+ pas_write(0x01 | 0x02 | 0x04 | 0x10 /*
+ * |
+ * 0x80
+ */ , 0xB88);
+
+ pas_write(0x80 | (joystick ? 0x40 : 0), 0xF388);
+
+ if (pas_irq < 0 || pas_irq > 15)
+ {
+ printk(KERN_ERR "PAS16: Invalid IRQ %d", pas_irq);
+ hw_config->irq=-1;
+ ok = 0;
+ }
+ else
+ {
+ int_ptrs = pas_read(0xF38A);
+ int_ptrs = (int_ptrs & 0xf0) | irq_bits[pas_irq];
+ pas_write(int_ptrs, 0xF38A);
+ if (!irq_bits[pas_irq])
+ {
+ printk(KERN_ERR "PAS16: Invalid IRQ %d", pas_irq);
+ hw_config->irq=-1;
+ ok = 0;
+ }
+ else
+ {
+ if (request_irq(pas_irq, pasintr, 0, "PAS16",hw_config) < 0) {
+ printk(KERN_ERR "PAS16: Cannot allocate IRQ %d\n",pas_irq);
+ hw_config->irq=-1;
+ ok = 0;
+ }
+ }
+ }
+
+ if (hw_config->dma < 0 || hw_config->dma > 7)
+ {
+ printk(KERN_ERR "PAS16: Invalid DMA selection %d", hw_config->dma);
+ hw_config->dma=-1;
+ ok = 0;
+ }
+ else
+ {
+ pas_write(dma_bits[hw_config->dma], 0xF389);
+ if (!dma_bits[hw_config->dma])
+ {
+ printk(KERN_ERR "PAS16: Invalid DMA selection %d", hw_config->dma);
+ hw_config->dma=-1;
+ ok = 0;
+ }
+ else
+ {
+ if (sound_alloc_dma(hw_config->dma, "PAS16"))
+ {
+ printk(KERN_ERR "pas2_card.c: Can't allocate DMA channel\n");
+ hw_config->dma=-1;
+ ok = 0;
+ }
+ }
+ }
+
+ /*
+ * This fixes the timing problems of the PAS due to the Symphony chipset
+ * as per Media Vision. Only define this if your PAS doesn't work correctly.
+ */
+
+ if(symphony)
+ {
+ outb((0x05), 0xa8);
+ outb((0x60), 0xa9);
+ }
+
+ if(broken_bus_clock)
+ pas_write(0x01 | 0x10 | 0x20 | 0x04, 0x8388);
+ else
+ /*
+ * pas_write(0x01, 0x8388);
+ */
+ pas_write(0x01 | 0x10 | 0x20, 0x8388);
+
+ pas_write(0x18, 0x838A); /* ??? */
+ pas_write(0x20 | 0x01, 0x0B8A); /* Mute off, filter = 17.897 kHz */
+ pas_write(8, 0xBF8A);
+
+ mix_write(0x80 | 5, 0x078B);
+ mix_write(5, 0x078B);
+
+ {
+ struct address_info *sb_config;
+
+ sb_config = &cfg2;
+ if (sb_config->io_base)
+ {
+ unsigned char irq_dma;
+
+ /*
+ * Turn on Sound Blaster compatibility
+ * bit 1 = SB emulation
+ * bit 0 = MPU401 emulation (CDPC only :-( )
+ */
+
+ pas_write(0x02, 0xF788);
+
+ /*
+ * "Emulation address"
+ */
+
+ pas_write((sb_config->io_base >> 4) & 0x0f, 0xF789);
+ pas_sb_base = sb_config->io_base;
+
+ if (!sb_dma_bits[sb_config->dma])
+ printk(KERN_ERR "PAS16 Warning: Invalid SB DMA %d\n\n", sb_config->dma);
+
+ if (!sb_irq_bits[sb_config->irq])
+ printk(KERN_ERR "PAS16 Warning: Invalid SB IRQ %d\n\n", sb_config->irq);
+
+ irq_dma = sb_dma_bits[sb_config->dma] |
+ sb_irq_bits[sb_config->irq];
+
+ pas_write(irq_dma, 0xFB8A);
+ }
+ else
+ pas_write(0x00, 0xF788);
+ }
+
+ if (!ok)
+ printk(KERN_WARNING "PAS16: Driver not enabled\n");
+
+ return ok;
+}
+
+static int __init detect_pas_hw(struct address_info *hw_config)
+{
+ unsigned char board_id, foo;
+
+ /*
+ * WARNING: Setting an option like W:1 or so that disables warm boot reset
+ * of the card will screw up this detect code something fierce. Adding code
+ * to handle this means possibly interfering with other cards on the bus if
+ * you have something on base port 0x388. SO be forewarned.
+ */
+
+ outb((0xBC), 0x9A01); /* Activate first board */
+ outb((hw_config->io_base >> 2), 0x9A01); /* Set base address */
+ pas_translate_code = hw_config->io_base - 0x388;
+ pas_write(1, 0xBF88); /* Select one wait states */
+
+ board_id = pas_read(0x0B8B);
+
+ if (board_id == 0xff)
+ return 0;
+
+ /*
+ * We probably have a PAS-series board, now check for a PAS16-series board
+ * by trying to change the board revision bits. PAS16-series hardware won't
+ * let you do this - the bits are read-only.
+ */
+
+ foo = board_id ^ 0xe0;
+
+ pas_write(foo, 0x0B8B);
+ foo = pas_read(0x0B8B);
+ pas_write(board_id, 0x0B8B);
+
+ if (board_id != foo)
+ return 0;
+
+ pas_model = pas_read(0xFF88);
+
+ return pas_model;
+}
+
+static void __init attach_pas_card(struct address_info *hw_config)
+{
+ pas_irq = hw_config->irq;
+
+ if (detect_pas_hw(hw_config))
+ {
+
+ if ((pas_model = pas_read(0xFF88)))
+ {
+ char temp[100];
+
+ sprintf(temp,
+ "%s rev %d", pas_model_names[(int) pas_model],
+ pas_read(0x2789));
+ conf_printf(temp, hw_config);
+ }
+ if (config_pas_hw(hw_config))
+ {
+ pas_pcm_init(hw_config);
+ pas_midi_init();
+ pas_init_mixer();
+ }
+ }
+}
+
+static inline int __init probe_pas(struct address_info *hw_config)
+{
+ return detect_pas_hw(hw_config);
+}
+
+static void __exit unload_pas(struct address_info *hw_config)
+{
+ extern int pas_audiodev;
+ extern int pas2_mididev;
+
+ if (hw_config->dma>0)
+ sound_free_dma(hw_config->dma);
+ if (hw_config->irq>0)
+ free_irq(hw_config->irq, hw_config);
+
+ if(pas_audiodev!=-1)
+ sound_unload_mixerdev(audio_devs[pas_audiodev]->mixer_dev);
+ if(pas2_mididev!=-1)
+ sound_unload_mididev(pas2_mididev);
+ if(pas_audiodev!=-1)
+ sound_unload_audiodev(pas_audiodev);
+}
+
+static int __initdata io = -1;
+static int __initdata irq = -1;
+static int __initdata dma = -1;
+static int __initdata dma16 = -1; /* Set this for modules that need it */
+
+static int __initdata sb_io = 0;
+static int __initdata sb_irq = -1;
+static int __initdata sb_dma = -1;
+static int __initdata sb_dma16 = -1;
+
+module_param(io, int, 0);
+module_param(irq, int, 0);
+module_param(dma, int, 0);
+module_param(dma16, int, 0);
+
+module_param(sb_io, int, 0);
+module_param(sb_irq, int, 0);
+module_param(sb_dma, int, 0);
+module_param(sb_dma16, int, 0);
+
+module_param(joystick, bool, 0);
+module_param(symphony, bool, 0);
+module_param(broken_bus_clock, bool, 0);
+
+MODULE_LICENSE("GPL");
+
+static int __init init_pas2(void)
+{
+ printk(KERN_INFO "Pro Audio Spectrum driver Copyright (C) by Hannu Savolainen 1993-1996\n");
+
+ cfg.io_base = io;
+ cfg.irq = irq;
+ cfg.dma = dma;
+ cfg.dma2 = dma16;
+
+ cfg2.io_base = sb_io;
+ cfg2.irq = sb_irq;
+ cfg2.dma = sb_dma;
+ cfg2.dma2 = sb_dma16;
+
+ if (cfg.io_base == -1 || cfg.dma == -1 || cfg.irq == -1) {
+ printk(KERN_INFO "I/O, IRQ, DMA and type are mandatory\n");
+ return -EINVAL;
+ }
+
+ if (!probe_pas(&cfg))
+ return -ENODEV;
+ attach_pas_card(&cfg);
+
+ return 0;
+}
+
+static void __exit cleanup_pas2(void)
+{
+ unload_pas(&cfg);
+}
+
+module_init(init_pas2);
+module_exit(cleanup_pas2);
+
+#ifndef MODULE
+static int __init setup_pas2(char *str)
+{
+ /* io, irq, dma, dma2, sb_io, sb_irq, sb_dma, sb_dma2 */
+ int ints[9];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ io = ints[1];
+ irq = ints[2];
+ dma = ints[3];
+ dma16 = ints[4];
+
+ sb_io = ints[5];
+ sb_irq = ints[6];
+ sb_dma = ints[7];
+ sb_dma16 = ints[8];
+
+ return 1;
+}
+
+__setup("pas2=", setup_pas2);
+#endif
diff --git a/sound/oss/pas2_midi.c b/sound/oss/pas2_midi.c
new file mode 100644
index 00000000..1122d10a
--- /dev/null
+++ b/sound/oss/pas2_midi.c
@@ -0,0 +1,262 @@
+/*
+ * sound/oss/pas2_midi.c
+ *
+ * The low level driver for the PAS Midi Interface.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Bartlomiej Zolnierkiewicz : Added __init to pas_init_mixer()
+ */
+
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+#include "pas2.h"
+
+extern spinlock_t pas_lock;
+
+static int midi_busy, input_opened;
+static int my_dev;
+
+int pas2_mididev=-1;
+
+static unsigned char tmp_queue[256];
+static volatile int qlen;
+static volatile unsigned char qhead, qtail;
+
+static void (*midi_input_intr) (int dev, unsigned char data);
+
+static int pas_midi_open(int dev, int mode,
+ void (*input) (int dev, unsigned char data),
+ void (*output) (int dev)
+)
+{
+ int err;
+ unsigned long flags;
+ unsigned char ctrl;
+
+
+ if (midi_busy)
+ return -EBUSY;
+
+ /*
+ * Reset input and output FIFO pointers
+ */
+ pas_write(0x20 | 0x40,
+ 0x178b);
+
+ spin_lock_irqsave(&pas_lock, flags);
+
+ if ((err = pas_set_intr(0x10)) < 0)
+ {
+ spin_unlock_irqrestore(&pas_lock, flags);
+ return err;
+ }
+ /*
+ * Enable input available and output FIFO empty interrupts
+ */
+
+ ctrl = 0;
+ input_opened = 0;
+ midi_input_intr = input;
+
+ if (mode == OPEN_READ || mode == OPEN_READWRITE)
+ {
+ ctrl |= 0x04; /* Enable input */
+ input_opened = 1;
+ }
+ if (mode == OPEN_WRITE || mode == OPEN_READWRITE)
+ {
+ ctrl |= 0x08 | 0x10; /* Enable output */
+ }
+ pas_write(ctrl, 0x178b);
+
+ /*
+ * Acknowledge any pending interrupts
+ */
+
+ pas_write(0xff, 0x1B88);
+
+ spin_unlock_irqrestore(&pas_lock, flags);
+
+ midi_busy = 1;
+ qlen = qhead = qtail = 0;
+ return 0;
+}
+
+static void pas_midi_close(int dev)
+{
+
+ /*
+ * Reset FIFO pointers, disable intrs
+ */
+ pas_write(0x20 | 0x40, 0x178b);
+
+ pas_remove_intr(0x10);
+ midi_busy = 0;
+}
+
+static int dump_to_midi(unsigned char midi_byte)
+{
+ int fifo_space, x;
+
+ fifo_space = ((x = pas_read(0x1B89)) >> 4) & 0x0f;
+
+ /*
+ * The MIDI FIFO space register and it's documentation is nonunderstandable.
+ * There seem to be no way to differentiate between buffer full and buffer
+ * empty situations. For this reason we don't never write the buffer
+ * completely full. In this way we can assume that 0 (or is it 15)
+ * means that the buffer is empty.
+ */
+
+ if (fifo_space < 2 && fifo_space != 0) /* Full (almost) */
+ return 0; /* Ask upper layers to retry after some time */
+
+ pas_write(midi_byte, 0x178A);
+
+ return 1;
+}
+
+static int pas_midi_out(int dev, unsigned char midi_byte)
+{
+
+ unsigned long flags;
+
+ /*
+ * Drain the local queue first
+ */
+
+ spin_lock_irqsave(&pas_lock, flags);
+
+ while (qlen && dump_to_midi(tmp_queue[qhead]))
+ {
+ qlen--;
+ qhead++;
+ }
+
+ spin_unlock_irqrestore(&pas_lock, flags);
+
+ /*
+ * Output the byte if the local queue is empty.
+ */
+
+ if (!qlen)
+ if (dump_to_midi(midi_byte))
+ return 1;
+
+ /*
+ * Put to the local queue
+ */
+
+ if (qlen >= 256)
+ return 0; /* Local queue full */
+
+ spin_lock_irqsave(&pas_lock, flags);
+
+ tmp_queue[qtail] = midi_byte;
+ qlen++;
+ qtail++;
+
+ spin_unlock_irqrestore(&pas_lock, flags);
+
+ return 1;
+}
+
+static int pas_midi_start_read(int dev)
+{
+ return 0;
+}
+
+static int pas_midi_end_read(int dev)
+{
+ return 0;
+}
+
+static void pas_midi_kick(int dev)
+{
+}
+
+static int pas_buffer_status(int dev)
+{
+ return qlen;
+}
+
+#define MIDI_SYNTH_NAME "Pro Audio Spectrum Midi"
+#define MIDI_SYNTH_CAPS SYNTH_CAP_INPUT
+#include "midi_synth.h"
+
+static struct midi_operations pas_midi_operations =
+{
+ .owner = THIS_MODULE,
+ .info = {"Pro Audio Spectrum", 0, 0, SNDCARD_PAS},
+ .converter = &std_midi_synth,
+ .in_info = {0},
+ .open = pas_midi_open,
+ .close = pas_midi_close,
+ .outputc = pas_midi_out,
+ .start_read = pas_midi_start_read,
+ .end_read = pas_midi_end_read,
+ .kick = pas_midi_kick,
+ .buffer_status = pas_buffer_status,
+};
+
+void __init pas_midi_init(void)
+{
+ int dev = sound_alloc_mididev();
+
+ if (dev == -1)
+ {
+ printk(KERN_WARNING "pas_midi_init: Too many midi devices detected\n");
+ return;
+ }
+ std_midi_synth.midi_dev = my_dev = dev;
+ midi_devs[dev] = &pas_midi_operations;
+ pas2_mididev = dev;
+ sequencer_init();
+}
+
+void pas_midi_interrupt(void)
+{
+ unsigned char stat;
+ int i, incount;
+
+ stat = pas_read(0x1B88);
+
+ if (stat & 0x04) /* Input data available */
+ {
+ incount = pas_read(0x1B89) & 0x0f; /* Input FIFO size */
+ if (!incount)
+ incount = 16;
+
+ for (i = 0; i < incount; i++)
+ if (input_opened)
+ {
+ midi_input_intr(my_dev, pas_read(0x178A));
+ } else
+ pas_read(0x178A); /* Flush */
+ }
+ if (stat & (0x08 | 0x10))
+ {
+ spin_lock(&pas_lock);/* called in irq context */
+
+ while (qlen && dump_to_midi(tmp_queue[qhead]))
+ {
+ qlen--;
+ qhead++;
+ }
+
+ spin_unlock(&pas_lock);
+ }
+ if (stat & 0x40)
+ {
+ printk(KERN_WARNING "MIDI output overrun %x,%x\n", pas_read(0x1B89), stat);
+ }
+ pas_write(stat, 0x1B88); /* Acknowledge interrupts */
+}
diff --git a/sound/oss/pas2_mixer.c b/sound/oss/pas2_mixer.c
new file mode 100644
index 00000000..a0bcb85c
--- /dev/null
+++ b/sound/oss/pas2_mixer.c
@@ -0,0 +1,336 @@
+
+/*
+ * sound/oss/pas2_mixer.c
+ *
+ * Mixer routines for the Pro Audio Spectrum cards.
+ */
+
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ * Bartlomiej Zolnierkiewicz : added __init to pas_init_mixer()
+ */
+#include <linux/init.h>
+#include "sound_config.h"
+
+#include "pas2.h"
+
+#ifndef DEB
+#define DEB(what) /* (what) */
+#endif
+
+extern int pas_translate_code;
+extern char pas_model;
+extern int *pas_osp;
+extern int pas_audiodev;
+
+static int rec_devices = (SOUND_MASK_MIC); /* Default recording source */
+static int mode_control;
+
+#define POSSIBLE_RECORDING_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
+ SOUND_MASK_CD | SOUND_MASK_ALTPCM)
+
+#define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
+ SOUND_MASK_CD | SOUND_MASK_ALTPCM | SOUND_MASK_IMIX | \
+ SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_RECLEV)
+
+static int *levels;
+
+static int default_levels[32] =
+{
+ 0x3232, /* Master Volume */
+ 0x3232, /* Bass */
+ 0x3232, /* Treble */
+ 0x5050, /* FM */
+ 0x4b4b, /* PCM */
+ 0x3232, /* PC Speaker */
+ 0x4b4b, /* Ext Line */
+ 0x4b4b, /* Mic */
+ 0x4b4b, /* CD */
+ 0x6464, /* Recording monitor */
+ 0x4b4b, /* SB PCM */
+ 0x6464 /* Recording level */
+};
+
+void
+mix_write(unsigned char data, int ioaddr)
+{
+ /*
+ * The Revision D cards have a problem with their MVA508 interface. The
+ * kludge-o-rama fix is to make a 16-bit quantity with identical LSB and
+ * MSBs out of the output byte and to do a 16-bit out to the mixer port -
+ * 1. We need to do this because it isn't timing problem but chip access
+ * sequence problem.
+ */
+
+ if (pas_model == 4)
+ {
+ outw(data | (data << 8), (ioaddr + pas_translate_code) - 1);
+ outb((0x80), 0);
+ } else
+ pas_write(data, ioaddr);
+}
+
+static int
+mixer_output(int right_vol, int left_vol, int div, int bits,
+ int mixer) /* Input or output mixer */
+{
+ int left = left_vol * div / 100;
+ int right = right_vol * div / 100;
+
+
+ if (bits & 0x10)
+ {
+ left |= mixer;
+ right |= mixer;
+ }
+ if (bits == 0x03 || bits == 0x04)
+ {
+ mix_write(0x80 | bits, 0x078B);
+ mix_write(left, 0x078B);
+ right_vol = left_vol;
+ } else
+ {
+ mix_write(0x80 | 0x20 | bits, 0x078B);
+ mix_write(left, 0x078B);
+ mix_write(0x80 | 0x40 | bits, 0x078B);
+ mix_write(right, 0x078B);
+ }
+
+ return (left_vol | (right_vol << 8));
+}
+
+static void
+set_mode(int new_mode)
+{
+ mix_write(0x80 | 0x05, 0x078B);
+ mix_write(new_mode, 0x078B);
+
+ mode_control = new_mode;
+}
+
+static int
+pas_mixer_set(int whichDev, unsigned int level)
+{
+ int left, right, devmask, changed, i, mixer = 0;
+
+ DEB(printk("static int pas_mixer_set(int whichDev = %d, unsigned int level = %X)\n", whichDev, level));
+
+ left = level & 0x7f;
+ right = (level & 0x7f00) >> 8;
+
+ if (whichDev < SOUND_MIXER_NRDEVICES) {
+ if ((1 << whichDev) & rec_devices)
+ mixer = 0x20;
+ else
+ mixer = 0x00;
+ }
+
+ switch (whichDev)
+ {
+ case SOUND_MIXER_VOLUME: /* Master volume (0-63) */
+ levels[whichDev] = mixer_output(right, left, 63, 0x01, 0);
+ break;
+
+ /*
+ * Note! Bass and Treble are mono devices. Will use just the left
+ * channel.
+ */
+ case SOUND_MIXER_BASS: /* Bass (0-12) */
+ levels[whichDev] = mixer_output(right, left, 12, 0x03, 0);
+ break;
+ case SOUND_MIXER_TREBLE: /* Treble (0-12) */
+ levels[whichDev] = mixer_output(right, left, 12, 0x04, 0);
+ break;
+
+ case SOUND_MIXER_SYNTH: /* Internal synthesizer (0-31) */
+ levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x00, mixer);
+ break;
+ case SOUND_MIXER_PCM: /* PAS PCM (0-31) */
+ levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x05, mixer);
+ break;
+ case SOUND_MIXER_ALTPCM: /* SB PCM (0-31) */
+ levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x07, mixer);
+ break;
+ case SOUND_MIXER_SPEAKER: /* PC speaker (0-31) */
+ levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x06, mixer);
+ break;
+ case SOUND_MIXER_LINE: /* External line (0-31) */
+ levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x02, mixer);
+ break;
+ case SOUND_MIXER_CD: /* CD (0-31) */
+ levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x03, mixer);
+ break;
+ case SOUND_MIXER_MIC: /* External microphone (0-31) */
+ levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x04, mixer);
+ break;
+ case SOUND_MIXER_IMIX: /* Recording monitor (0-31) (Output mixer only) */
+ levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x01,
+ 0x00);
+ break;
+ case SOUND_MIXER_RECLEV: /* Recording level (0-15) */
+ levels[whichDev] = mixer_output(right, left, 15, 0x02, 0);
+ break;
+
+
+ case SOUND_MIXER_RECSRC:
+ devmask = level & POSSIBLE_RECORDING_DEVICES;
+
+ changed = devmask ^ rec_devices;
+ rec_devices = devmask;
+
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ if (changed & (1 << i))
+ {
+ pas_mixer_set(i, levels[i]);
+ }
+ return rec_devices;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return (levels[whichDev]);
+}
+
+/*****/
+
+static void
+pas_mixer_reset(void)
+{
+ int foo;
+
+ DEB(printk("pas2_mixer.c: void pas_mixer_reset(void)\n"));
+
+ for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++)
+ pas_mixer_set(foo, levels[foo]);
+
+ set_mode(0x04 | 0x01);
+}
+
+static int pas_mixer_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+ int level,v ;
+ int __user *p = (int __user *)arg;
+
+ DEB(printk("pas2_mixer.c: int pas_mixer_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg));
+ if (cmd == SOUND_MIXER_PRIVATE1) { /* Set loudness bit */
+ if (get_user(level, p))
+ return -EFAULT;
+ if (level == -1) /* Return current settings */
+ level = (mode_control & 0x04);
+ else {
+ mode_control &= ~0x04;
+ if (level)
+ mode_control |= 0x04;
+ set_mode(mode_control);
+ }
+ level = !!level;
+ return put_user(level, p);
+ }
+ if (cmd == SOUND_MIXER_PRIVATE2) { /* Set enhance bit */
+ if (get_user(level, p))
+ return -EFAULT;
+ if (level == -1) { /* Return current settings */
+ if (!(mode_control & 0x03))
+ level = 0;
+ else
+ level = ((mode_control & 0x03) + 1) * 20;
+ } else {
+ int i = 0;
+
+ level &= 0x7f;
+ if (level)
+ i = (level / 20) - 1;
+ mode_control &= ~0x03;
+ mode_control |= i & 0x03;
+ set_mode(mode_control);
+ if (i)
+ i = (i + 1) * 20;
+ level = i;
+ }
+ return put_user(level, p);
+ }
+ if (cmd == SOUND_MIXER_PRIVATE3) { /* Set mute bit */
+ if (get_user(level, p))
+ return -EFAULT;
+ if (level == -1) /* Return current settings */
+ level = !(pas_read(0x0B8A) & 0x20);
+ else {
+ if (level)
+ pas_write(pas_read(0x0B8A) & (~0x20), 0x0B8A);
+ else
+ pas_write(pas_read(0x0B8A) | 0x20, 0x0B8A);
+
+ level = !(pas_read(0x0B8A) & 0x20);
+ }
+ return put_user(level, p);
+ }
+ if (((cmd >> 8) & 0xff) == 'M') {
+ if (get_user(v, p))
+ return -EFAULT;
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+ v = pas_mixer_set(cmd & 0xff, v);
+ } else {
+ switch (cmd & 0xff) {
+ case SOUND_MIXER_RECSRC:
+ v = rec_devices;
+ break;
+
+ case SOUND_MIXER_STEREODEVS:
+ v = SUPPORTED_MIXER_DEVICES & ~(SOUND_MASK_BASS | SOUND_MASK_TREBLE);
+ break;
+
+ case SOUND_MIXER_DEVMASK:
+ v = SUPPORTED_MIXER_DEVICES;
+ break;
+
+ case SOUND_MIXER_RECMASK:
+ v = POSSIBLE_RECORDING_DEVICES & SUPPORTED_MIXER_DEVICES;
+ break;
+
+ case SOUND_MIXER_CAPS:
+ v = 0; /* No special capabilities */
+ break;
+
+ default:
+ v = levels[cmd & 0xff];
+ break;
+ }
+ }
+ return put_user(v, p);
+ }
+ return -EINVAL;
+}
+
+static struct mixer_operations pas_mixer_operations =
+{
+ .owner = THIS_MODULE,
+ .id = "PAS16",
+ .name = "Pro Audio Spectrum 16",
+ .ioctl = pas_mixer_ioctl
+};
+
+int __init
+pas_init_mixer(void)
+{
+ int d;
+
+ levels = load_mixer_volumes("PAS16_1", default_levels, 1);
+
+ pas_mixer_reset();
+
+ if ((d = sound_alloc_mixerdev()) != -1)
+ {
+ audio_devs[pas_audiodev]->mixer_dev = d;
+ mixer_devs[d] = &pas_mixer_operations;
+ }
+ return 1;
+}
diff --git a/sound/oss/pas2_pcm.c b/sound/oss/pas2_pcm.c
new file mode 100644
index 00000000..8f7d1757
--- /dev/null
+++ b/sound/oss/pas2_pcm.c
@@ -0,0 +1,437 @@
+/*
+ * pas2_pcm.c Audio routines for PAS16
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ * Alan Cox : Swatted a double allocation of device bug. Made a few
+ * more things module options.
+ * Bartlomiej Zolnierkiewicz : Added __init to pas_pcm_init()
+ */
+
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/timex.h>
+#include "sound_config.h"
+
+#include "pas2.h"
+
+#ifndef DEB
+#define DEB(WHAT)
+#endif
+
+#define PAS_PCM_INTRBITS (0x08)
+/*
+ * Sample buffer timer interrupt enable
+ */
+
+#define PCM_NON 0
+#define PCM_DAC 1
+#define PCM_ADC 2
+
+static unsigned long pcm_speed; /* sampling rate */
+static unsigned char pcm_channels = 1; /* channels (1 or 2) */
+static unsigned char pcm_bits = 8; /* bits/sample (8 or 16) */
+static unsigned char pcm_filter; /* filter FLAG */
+static unsigned char pcm_mode = PCM_NON;
+static unsigned long pcm_count;
+static unsigned short pcm_bitsok = 8; /* mask of OK bits */
+static int pcm_busy;
+int pas_audiodev = -1;
+static int open_mode;
+
+extern spinlock_t pas_lock;
+
+static int pcm_set_speed(int arg)
+{
+ int foo, tmp;
+ unsigned long flags;
+
+ if (arg == 0)
+ return pcm_speed;
+
+ if (arg > 44100)
+ arg = 44100;
+ if (arg < 5000)
+ arg = 5000;
+
+ if (pcm_channels & 2)
+ {
+ foo = ((CLOCK_TICK_RATE / 2) + (arg / 2)) / arg;
+ arg = ((CLOCK_TICK_RATE / 2) + (foo / 2)) / foo;
+ }
+ else
+ {
+ foo = (CLOCK_TICK_RATE + (arg / 2)) / arg;
+ arg = (CLOCK_TICK_RATE + (foo / 2)) / foo;
+ }
+
+ pcm_speed = arg;
+
+ tmp = pas_read(0x0B8A);
+
+ /*
+ * Set anti-aliasing filters according to sample rate. You really *NEED*
+ * to enable this feature for all normal recording unless you want to
+ * experiment with aliasing effects.
+ * These filters apply to the selected "recording" source.
+ * I (pfw) don't know the encoding of these 5 bits. The values shown
+ * come from the SDK found on ftp.uwp.edu:/pub/msdos/proaudio/.
+ *
+ * I cleared bit 5 of these values, since that bit controls the master
+ * mute flag. (Olav Wölfelschneider)
+ *
+ */
+#if !defined NO_AUTO_FILTER_SET
+ tmp &= 0xe0;
+ if (pcm_speed >= 2 * 17897)
+ tmp |= 0x01;
+ else if (pcm_speed >= 2 * 15909)
+ tmp |= 0x02;
+ else if (pcm_speed >= 2 * 11931)
+ tmp |= 0x09;
+ else if (pcm_speed >= 2 * 8948)
+ tmp |= 0x11;
+ else if (pcm_speed >= 2 * 5965)
+ tmp |= 0x19;
+ else if (pcm_speed >= 2 * 2982)
+ tmp |= 0x04;
+ pcm_filter = tmp;
+#endif
+
+ spin_lock_irqsave(&pas_lock, flags);
+
+ pas_write(tmp & ~(0x40 | 0x80), 0x0B8A);
+ pas_write(0x00 | 0x30 | 0x04, 0x138B);
+ pas_write(foo & 0xff, 0x1388);
+ pas_write((foo >> 8) & 0xff, 0x1388);
+ pas_write(tmp, 0x0B8A);
+
+ spin_unlock_irqrestore(&pas_lock, flags);
+
+ return pcm_speed;
+}
+
+static int pcm_set_channels(int arg)
+{
+
+ if ((arg != 1) && (arg != 2))
+ return pcm_channels;
+
+ if (arg != pcm_channels)
+ {
+ pas_write(pas_read(0xF8A) ^ 0x20, 0xF8A);
+
+ pcm_channels = arg;
+ pcm_set_speed(pcm_speed); /* The speed must be reinitialized */
+ }
+ return pcm_channels;
+}
+
+static int pcm_set_bits(int arg)
+{
+ if (arg == 0)
+ return pcm_bits;
+
+ if ((arg & pcm_bitsok) != arg)
+ return pcm_bits;
+
+ if (arg != pcm_bits)
+ {
+ pas_write(pas_read(0x8389) ^ 0x04, 0x8389);
+
+ pcm_bits = arg;
+ }
+ return pcm_bits;
+}
+
+static int pas_audio_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+ int val, ret;
+ int __user *p = arg;
+
+ DEB(printk("pas2_pcm.c: static int pas_audio_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg));
+
+ switch (cmd)
+ {
+ case SOUND_PCM_WRITE_RATE:
+ if (get_user(val, p))
+ return -EFAULT;
+ ret = pcm_set_speed(val);
+ break;
+
+ case SOUND_PCM_READ_RATE:
+ ret = pcm_speed;
+ break;
+
+ case SNDCTL_DSP_STEREO:
+ if (get_user(val, p))
+ return -EFAULT;
+ ret = pcm_set_channels(val + 1) - 1;
+ break;
+
+ case SOUND_PCM_WRITE_CHANNELS:
+ if (get_user(val, p))
+ return -EFAULT;
+ ret = pcm_set_channels(val);
+ break;
+
+ case SOUND_PCM_READ_CHANNELS:
+ ret = pcm_channels;
+ break;
+
+ case SNDCTL_DSP_SETFMT:
+ if (get_user(val, p))
+ return -EFAULT;
+ ret = pcm_set_bits(val);
+ break;
+
+ case SOUND_PCM_READ_BITS:
+ ret = pcm_bits;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return put_user(ret, p);
+}
+
+static void pas_audio_reset(int dev)
+{
+ DEB(printk("pas2_pcm.c: static void pas_audio_reset(void)\n"));
+
+ pas_write(pas_read(0xF8A) & ~0x40, 0xF8A); /* Disable PCM */
+}
+
+static int pas_audio_open(int dev, int mode)
+{
+ int err;
+ unsigned long flags;
+
+ DEB(printk("pas2_pcm.c: static int pas_audio_open(int mode = %X)\n", mode));
+
+ spin_lock_irqsave(&pas_lock, flags);
+ if (pcm_busy)
+ {
+ spin_unlock_irqrestore(&pas_lock, flags);
+ return -EBUSY;
+ }
+ pcm_busy = 1;
+ spin_unlock_irqrestore(&pas_lock, flags);
+
+ if ((err = pas_set_intr(PAS_PCM_INTRBITS)) < 0)
+ return err;
+
+
+ pcm_count = 0;
+ open_mode = mode;
+
+ return 0;
+}
+
+static void pas_audio_close(int dev)
+{
+ unsigned long flags;
+
+ DEB(printk("pas2_pcm.c: static void pas_audio_close(void)\n"));
+
+ spin_lock_irqsave(&pas_lock, flags);
+
+ pas_audio_reset(dev);
+ pas_remove_intr(PAS_PCM_INTRBITS);
+ pcm_mode = PCM_NON;
+
+ pcm_busy = 0;
+ spin_unlock_irqrestore(&pas_lock, flags);
+}
+
+static void pas_audio_output_block(int dev, unsigned long buf, int count,
+ int intrflag)
+{
+ unsigned long flags, cnt;
+
+ DEB(printk("pas2_pcm.c: static void pas_audio_output_block(char *buf = %P, int count = %X)\n", buf, count));
+
+ cnt = count;
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ cnt >>= 1;
+
+ if (audio_devs[dev]->flags & DMA_AUTOMODE &&
+ intrflag &&
+ cnt == pcm_count)
+ return;
+
+ spin_lock_irqsave(&pas_lock, flags);
+
+ pas_write(pas_read(0xF8A) & ~0x40,
+ 0xF8A);
+
+ /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */
+
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ count >>= 1;
+
+ if (count != pcm_count)
+ {
+ pas_write(pas_read(0x0B8A) & ~0x80, 0x0B8A);
+ pas_write(0x40 | 0x30 | 0x04, 0x138B);
+ pas_write(count & 0xff, 0x1389);
+ pas_write((count >> 8) & 0xff, 0x1389);
+ pas_write(pas_read(0x0B8A) | 0x80, 0x0B8A);
+
+ pcm_count = count;
+ }
+ pas_write(pas_read(0x0B8A) | 0x80 | 0x40, 0x0B8A);
+#ifdef NO_TRIGGER
+ pas_write(pas_read(0xF8A) | 0x40 | 0x10, 0xF8A);
+#endif
+
+ pcm_mode = PCM_DAC;
+
+ spin_unlock_irqrestore(&pas_lock, flags);
+}
+
+static void pas_audio_start_input(int dev, unsigned long buf, int count,
+ int intrflag)
+{
+ unsigned long flags;
+ int cnt;
+
+ DEB(printk("pas2_pcm.c: static void pas_audio_start_input(char *buf = %P, int count = %X)\n", buf, count));
+
+ cnt = count;
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ cnt >>= 1;
+
+ if (audio_devs[pas_audiodev]->flags & DMA_AUTOMODE &&
+ intrflag &&
+ cnt == pcm_count)
+ return;
+
+ spin_lock_irqsave(&pas_lock, flags);
+
+ /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */
+
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ count >>= 1;
+
+ if (count != pcm_count)
+ {
+ pas_write(pas_read(0x0B8A) & ~0x80, 0x0B8A);
+ pas_write(0x40 | 0x30 | 0x04, 0x138B);
+ pas_write(count & 0xff, 0x1389);
+ pas_write((count >> 8) & 0xff, 0x1389);
+ pas_write(pas_read(0x0B8A) | 0x80, 0x0B8A);
+
+ pcm_count = count;
+ }
+ pas_write(pas_read(0x0B8A) | 0x80 | 0x40, 0x0B8A);
+#ifdef NO_TRIGGER
+ pas_write((pas_read(0xF8A) | 0x40) & ~0x10, 0xF8A);
+#endif
+
+ pcm_mode = PCM_ADC;
+
+ spin_unlock_irqrestore(&pas_lock, flags);
+}
+
+#ifndef NO_TRIGGER
+static void pas_audio_trigger(int dev, int state)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&pas_lock, flags);
+ state &= open_mode;
+
+ if (state & PCM_ENABLE_OUTPUT)
+ pas_write(pas_read(0xF8A) | 0x40 | 0x10, 0xF8A);
+ else if (state & PCM_ENABLE_INPUT)
+ pas_write((pas_read(0xF8A) | 0x40) & ~0x10, 0xF8A);
+ else
+ pas_write(pas_read(0xF8A) & ~0x40, 0xF8A);
+
+ spin_unlock_irqrestore(&pas_lock, flags);
+}
+#endif
+
+static int pas_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+ pas_audio_reset(dev);
+ return 0;
+}
+
+static int pas_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+ pas_audio_reset(dev);
+ return 0;
+}
+
+static struct audio_driver pas_audio_driver =
+{
+ .owner = THIS_MODULE,
+ .open = pas_audio_open,
+ .close = pas_audio_close,
+ .output_block = pas_audio_output_block,
+ .start_input = pas_audio_start_input,
+ .ioctl = pas_audio_ioctl,
+ .prepare_for_input = pas_audio_prepare_for_input,
+ .prepare_for_output = pas_audio_prepare_for_output,
+ .halt_io = pas_audio_reset,
+ .trigger = pas_audio_trigger
+};
+
+void __init pas_pcm_init(struct address_info *hw_config)
+{
+ DEB(printk("pas2_pcm.c: long pas_pcm_init()\n"));
+
+ pcm_bitsok = 8;
+ if (pas_read(0xEF8B) & 0x08)
+ pcm_bitsok |= 16;
+
+ pcm_set_speed(DSP_DEFAULT_SPEED);
+
+ if ((pas_audiodev = sound_install_audiodrv(AUDIO_DRIVER_VERSION,
+ "Pro Audio Spectrum",
+ &pas_audio_driver,
+ sizeof(struct audio_driver),
+ DMA_AUTOMODE,
+ AFMT_U8 | AFMT_S16_LE,
+ NULL,
+ hw_config->dma,
+ hw_config->dma)) < 0)
+ printk(KERN_WARNING "PAS16: Too many PCM devices available\n");
+}
+
+void pas_pcm_interrupt(unsigned char status, int cause)
+{
+ if (cause == 1)
+ {
+ /*
+ * Halt the PCM first. Otherwise we don't have time to start a new
+ * block before the PCM chip proceeds to the next sample
+ */
+
+ if (!(audio_devs[pas_audiodev]->flags & DMA_AUTOMODE))
+ pas_write(pas_read(0xF8A) & ~0x40, 0xF8A);
+
+ switch (pcm_mode)
+ {
+ case PCM_DAC:
+ DMAbuf_outputintr(pas_audiodev, 1);
+ break;
+
+ case PCM_ADC:
+ DMAbuf_inputintr(pas_audiodev);
+ break;
+
+ default:
+ printk(KERN_WARNING "PAS: Unexpected PCM interrupt\n");
+ }
+ }
+}
diff --git a/sound/oss/pss.c b/sound/oss/pss.c
new file mode 100644
index 00000000..9b800ce5
--- /dev/null
+++ b/sound/oss/pss.c
@@ -0,0 +1,1266 @@
+/*
+ * sound/oss/pss.c
+ *
+ * The low level driver for the Personal Sound System (ECHO ESC614).
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer ioctl code reworked (vmalloc/vfree removed)
+ * Alan Cox modularisation, clean up.
+ *
+ * 98-02-21: Vladimir Michl <vladimir.michl@upol.cz>
+ * Added mixer device for Beethoven ADSP-16 (master volume,
+ * bass, treble, synth), only for speakers.
+ * Fixed bug in pss_write (exchange parameters)
+ * Fixed config port of SB
+ * Requested two regions for PSS (PSS mixer, PSS config)
+ * Modified pss_download_boot
+ * To probe_pss_mss added test for initialize AD1848
+ * 98-05-28: Vladimir Michl <vladimir.michl@upol.cz>
+ * Fixed computation of mixer volumes
+ * 04-05-1999: Anthony Barbachan <barbcode@xmen.cis.fordham.edu>
+ * Added code that allows the user to enable his cdrom and/or
+ * joystick through the module parameters pss_cdrom_port and
+ * pss_enable_joystick. pss_cdrom_port takes a port address as its
+ * argument. pss_enable_joystick takes either a 0 or a non-0 as its
+ * argument.
+ * 04-06-1999: Anthony Barbachan <barbcode@xmen.cis.fordham.edu>
+ * Separated some code into new functions for easier reuse.
+ * Cleaned up and streamlined new code. Added code to allow a user
+ * to only use this driver for enabling non-sound components
+ * through the new module parameter pss_no_sound (flag). Added
+ * code that would allow a user to decide whether the driver should
+ * reset the configured hardware settings for the PSS board through
+ * the module parameter pss_keep_settings (flag). This flag will
+ * allow a user to free up resources in use by this card if needbe,
+ * furthermore it allows him to use this driver to just enable the
+ * emulations and then be unloaded as it is no longer needed. Both
+ * new settings are only available to this driver if compiled as a
+ * module. The default settings of all new parameters are set to
+ * load the driver as it did in previous versions.
+ * 04-07-1999: Anthony Barbachan <barbcode@xmen.cis.fordham.edu>
+ * Added module parameter pss_firmware to allow the user to tell
+ * the driver where the firmware file is located. The default
+ * setting is the previous hardcoded setting "/etc/sound/pss_synth".
+ * 00-03-03: Christoph Hellwig <chhellwig@infradead.org>
+ * Adapted to module_init/module_exit
+ * 11-10-2000: Bartlomiej Zolnierkiewicz <bkz@linux-ide.org>
+ * Added __init to probe_pss(), attach_pss() and probe_pss_mpu()
+ * 02-Jan-2001: Chris Rankin
+ * Specify that this module owns the coprocessor
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+
+#include "sound_config.h"
+#include "sound_firmware.h"
+
+#include "ad1848.h"
+#include "mpu401.h"
+
+/*
+ * PSS registers.
+ */
+#define REG(x) (devc->base+x)
+#define PSS_DATA 0
+#define PSS_STATUS 2
+#define PSS_CONTROL 2
+#define PSS_ID 4
+#define PSS_IRQACK 4
+#define PSS_PIO 0x1a
+
+/*
+ * Config registers
+ */
+#define CONF_PSS 0x10
+#define CONF_WSS 0x12
+#define CONF_SB 0x14
+#define CONF_CDROM 0x16
+#define CONF_MIDI 0x18
+
+/*
+ * Status bits.
+ */
+#define PSS_FLAG3 0x0800
+#define PSS_FLAG2 0x0400
+#define PSS_FLAG1 0x1000
+#define PSS_FLAG0 0x0800
+#define PSS_WRITE_EMPTY 0x8000
+#define PSS_READ_FULL 0x4000
+
+/*
+ * WSS registers
+ */
+#define WSS_INDEX 4
+#define WSS_DATA 5
+
+/*
+ * WSS status bits
+ */
+#define WSS_INITIALIZING 0x80
+#define WSS_AUTOCALIBRATION 0x20
+
+#define NO_WSS_MIXER -1
+
+#include "coproc.h"
+
+#include "pss_boot.h"
+
+/* If compiled into kernel, it enable or disable pss mixer */
+#ifdef CONFIG_PSS_MIXER
+static int pss_mixer = 1;
+#else
+static int pss_mixer;
+#endif
+
+
+typedef struct pss_mixerdata {
+ unsigned int volume_l;
+ unsigned int volume_r;
+ unsigned int bass;
+ unsigned int treble;
+ unsigned int synth;
+} pss_mixerdata;
+
+typedef struct pss_confdata {
+ int base;
+ int irq;
+ int dma;
+ int *osp;
+ pss_mixerdata mixer;
+ int ad_mixer_dev;
+} pss_confdata;
+
+static pss_confdata pss_data;
+static pss_confdata *devc = &pss_data;
+static DEFINE_SPINLOCK(lock);
+
+static int pss_initialized;
+static int nonstandard_microcode;
+static int pss_cdrom_port = -1; /* Parameter for the PSS cdrom port */
+static int pss_enable_joystick; /* Parameter for enabling the joystick */
+static coproc_operations pss_coproc_operations;
+
+static void pss_write(pss_confdata *devc, int data)
+{
+ unsigned long i, limit;
+
+ limit = jiffies + HZ/10; /* The timeout is 0.1 seconds */
+ /*
+ * Note! the i<5000000 is an emergency exit. The dsp_command() is sometimes
+ * called while interrupts are disabled. This means that the timer is
+ * disabled also. However the timeout situation is a abnormal condition.
+ * Normally the DSP should be ready to accept commands after just couple of
+ * loops.
+ */
+
+ for (i = 0; i < 5000000 && time_before(jiffies, limit); i++)
+ {
+ if (inw(REG(PSS_STATUS)) & PSS_WRITE_EMPTY)
+ {
+ outw(data, REG(PSS_DATA));
+ return;
+ }
+ }
+ printk(KERN_WARNING "PSS: DSP Command (%04x) Timeout.\n", data);
+}
+
+static int __init probe_pss(struct address_info *hw_config)
+{
+ unsigned short id;
+ int irq, dma;
+
+ devc->base = hw_config->io_base;
+ irq = devc->irq = hw_config->irq;
+ dma = devc->dma = hw_config->dma;
+ devc->osp = hw_config->osp;
+
+ if (devc->base != 0x220 && devc->base != 0x240)
+ if (devc->base != 0x230 && devc->base != 0x250) /* Some cards use these */
+ return 0;
+
+ if (!request_region(devc->base, 0x10, "PSS mixer, SB emulation")) {
+ printk(KERN_ERR "PSS: I/O port conflict\n");
+ return 0;
+ }
+ id = inw(REG(PSS_ID));
+ if ((id >> 8) != 'E') {
+ printk(KERN_ERR "No PSS signature detected at 0x%x (0x%x)\n", devc->base, id);
+ release_region(devc->base, 0x10);
+ return 0;
+ }
+ if (!request_region(devc->base + 0x10, 0x9, "PSS config")) {
+ printk(KERN_ERR "PSS: I/O port conflict\n");
+ release_region(devc->base, 0x10);
+ return 0;
+ }
+ return 1;
+}
+
+static int set_irq(pss_confdata * devc, int dev, int irq)
+{
+ static unsigned short irq_bits[16] =
+ {
+ 0x0000, 0x0000, 0x0000, 0x0008,
+ 0x0000, 0x0010, 0x0000, 0x0018,
+ 0x0000, 0x0020, 0x0028, 0x0030,
+ 0x0038, 0x0000, 0x0000, 0x0000
+ };
+
+ unsigned short tmp, bits;
+
+ if (irq < 0 || irq > 15)
+ return 0;
+
+ tmp = inw(REG(dev)) & ~0x38; /* Load confreg, mask IRQ bits out */
+
+ if ((bits = irq_bits[irq]) == 0 && irq != 0)
+ {
+ printk(KERN_ERR "PSS: Invalid IRQ %d\n", irq);
+ return 0;
+ }
+ outw(tmp | bits, REG(dev));
+ return 1;
+}
+
+static void set_io_base(pss_confdata * devc, int dev, int base)
+{
+ unsigned short tmp = inw(REG(dev)) & 0x003f;
+ unsigned short bits = (base & 0x0ffc) << 4;
+
+ outw(bits | tmp, REG(dev));
+}
+
+static int set_dma(pss_confdata * devc, int dev, int dma)
+{
+ static unsigned short dma_bits[8] =
+ {
+ 0x0001, 0x0002, 0x0000, 0x0003,
+ 0x0000, 0x0005, 0x0006, 0x0007
+ };
+
+ unsigned short tmp, bits;
+
+ if (dma < 0 || dma > 7)
+ return 0;
+
+ tmp = inw(REG(dev)) & ~0x07; /* Load confreg, mask DMA bits out */
+
+ if ((bits = dma_bits[dma]) == 0 && dma != 4)
+ {
+ printk(KERN_ERR "PSS: Invalid DMA %d\n", dma);
+ return 0;
+ }
+ outw(tmp | bits, REG(dev));
+ return 1;
+}
+
+static int pss_reset_dsp(pss_confdata * devc)
+{
+ unsigned long i, limit = jiffies + HZ/10;
+
+ outw(0x2000, REG(PSS_CONTROL));
+ for (i = 0; i < 32768 && time_after_eq(limit, jiffies); i++)
+ inw(REG(PSS_CONTROL));
+ outw(0x0000, REG(PSS_CONTROL));
+ return 1;
+}
+
+static int pss_put_dspword(pss_confdata * devc, unsigned short word)
+{
+ int i, val;
+
+ for (i = 0; i < 327680; i++)
+ {
+ val = inw(REG(PSS_STATUS));
+ if (val & PSS_WRITE_EMPTY)
+ {
+ outw(word, REG(PSS_DATA));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int pss_get_dspword(pss_confdata * devc, unsigned short *word)
+{
+ int i, val;
+
+ for (i = 0; i < 327680; i++)
+ {
+ val = inw(REG(PSS_STATUS));
+ if (val & PSS_READ_FULL)
+ {
+ *word = inw(REG(PSS_DATA));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int pss_download_boot(pss_confdata * devc, unsigned char *block, int size, int flags)
+{
+ int i, val, count;
+ unsigned long limit;
+
+ if (flags & CPF_FIRST)
+ {
+/*_____ Warn DSP software that a boot is coming */
+ outw(0x00fe, REG(PSS_DATA));
+
+ limit = jiffies + HZ/10;
+ for (i = 0; i < 32768 && time_before(jiffies, limit); i++)
+ if (inw(REG(PSS_DATA)) == 0x5500)
+ break;
+
+ outw(*block++, REG(PSS_DATA));
+ pss_reset_dsp(devc);
+ }
+ count = 1;
+ while ((flags&CPF_LAST) || count<size )
+ {
+ int j;
+
+ for (j = 0; j < 327670; j++)
+ {
+/*_____ Wait for BG to appear */
+ if (inw(REG(PSS_STATUS)) & PSS_FLAG3)
+ break;
+ }
+
+ if (j == 327670)
+ {
+ /* It's ok we timed out when the file was empty */
+ if (count >= size && flags & CPF_LAST)
+ break;
+ else
+ {
+ printk("\n");
+ printk(KERN_ERR "PSS: Download timeout problems, byte %d=%d\n", count, size);
+ return 0;
+ }
+ }
+/*_____ Send the next byte */
+ if (count >= size)
+ {
+ /* If not data in block send 0xffff */
+ outw (0xffff, REG (PSS_DATA));
+ }
+ else
+ {
+ /*_____ Send the next byte */
+ outw (*block++, REG (PSS_DATA));
+ };
+ count++;
+ }
+
+ if (flags & CPF_LAST)
+ {
+/*_____ Why */
+ outw(0, REG(PSS_DATA));
+
+ limit = jiffies + HZ/10;
+ for (i = 0; i < 32768 && time_after_eq(limit, jiffies); i++)
+ val = inw(REG(PSS_STATUS));
+
+ limit = jiffies + HZ/10;
+ for (i = 0; i < 32768 && time_after_eq(limit, jiffies); i++)
+ {
+ val = inw(REG(PSS_STATUS));
+ if (val & 0x4000)
+ break;
+ }
+
+ /* now read the version */
+ for (i = 0; i < 32000; i++)
+ {
+ val = inw(REG(PSS_STATUS));
+ if (val & PSS_READ_FULL)
+ break;
+ }
+ if (i == 32000)
+ return 0;
+
+ val = inw(REG(PSS_DATA));
+ /* printk( "<PSS: microcode version %d.%d loaded>", val/16, val % 16); */
+ }
+ return 1;
+}
+
+/* Mixer */
+static void set_master_volume(pss_confdata *devc, int left, int right)
+{
+ static unsigned char log_scale[101] = {
+ 0xdb, 0xe0, 0xe3, 0xe5, 0xe7, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xed, 0xee,
+ 0xef, 0xef, 0xf0, 0xf0, 0xf1, 0xf1, 0xf2, 0xf2, 0xf2, 0xf3, 0xf3, 0xf3,
+ 0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf6, 0xf7,
+ 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9,
+ 0xf9, 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb,
+ 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc,
+ 0xfc, 0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd,
+ 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff
+ };
+ pss_write(devc, 0x0010);
+ pss_write(devc, log_scale[left] | 0x0000);
+ pss_write(devc, 0x0010);
+ pss_write(devc, log_scale[right] | 0x0100);
+}
+
+static void set_synth_volume(pss_confdata *devc, int volume)
+{
+ int vol = ((0x8000*volume)/100L);
+ pss_write(devc, 0x0080);
+ pss_write(devc, vol);
+ pss_write(devc, 0x0081);
+ pss_write(devc, vol);
+}
+
+static void set_bass(pss_confdata *devc, int level)
+{
+ int vol = (int)(((0xfd - 0xf0) * level)/100L) + 0xf0;
+ pss_write(devc, 0x0010);
+ pss_write(devc, vol | 0x0200);
+};
+
+static void set_treble(pss_confdata *devc, int level)
+{
+ int vol = (((0xfd - 0xf0) * level)/100L) + 0xf0;
+ pss_write(devc, 0x0010);
+ pss_write(devc, vol | 0x0300);
+};
+
+static void pss_mixer_reset(pss_confdata *devc)
+{
+ set_master_volume(devc, 33, 33);
+ set_bass(devc, 50);
+ set_treble(devc, 50);
+ set_synth_volume(devc, 30);
+ pss_write (devc, 0x0010);
+ pss_write (devc, 0x0800 | 0xce); /* Stereo */
+
+ if(pss_mixer)
+ {
+ devc->mixer.volume_l = devc->mixer.volume_r = 33;
+ devc->mixer.bass = 50;
+ devc->mixer.treble = 50;
+ devc->mixer.synth = 30;
+ }
+}
+
+static int set_volume_mono(unsigned __user *p, unsigned int *aleft)
+{
+ unsigned int left, volume;
+ if (get_user(volume, p))
+ return -EFAULT;
+
+ left = volume & 0xff;
+ if (left > 100)
+ left = 100;
+ *aleft = left;
+ return 0;
+}
+
+static int set_volume_stereo(unsigned __user *p,
+ unsigned int *aleft,
+ unsigned int *aright)
+{
+ unsigned int left, right, volume;
+ if (get_user(volume, p))
+ return -EFAULT;
+
+ left = volume & 0xff;
+ if (left > 100)
+ left = 100;
+ right = (volume >> 8) & 0xff;
+ if (right > 100)
+ right = 100;
+ *aleft = left;
+ *aright = right;
+ return 0;
+}
+
+static int ret_vol_mono(int left)
+{
+ return ((left << 8) | left);
+}
+
+static int ret_vol_stereo(int left, int right)
+{
+ return ((right << 8) | left);
+}
+
+static int call_ad_mixer(pss_confdata *devc,unsigned int cmd, void __user *arg)
+{
+ if (devc->ad_mixer_dev != NO_WSS_MIXER)
+ return mixer_devs[devc->ad_mixer_dev]->ioctl(devc->ad_mixer_dev, cmd, arg);
+ else
+ return -EINVAL;
+}
+
+static int pss_mixer_ioctl (int dev, unsigned int cmd, void __user *arg)
+{
+ pss_confdata *devc = mixer_devs[dev]->devc;
+ int cmdf = cmd & 0xff;
+
+ if ((cmdf != SOUND_MIXER_VOLUME) && (cmdf != SOUND_MIXER_BASS) &&
+ (cmdf != SOUND_MIXER_TREBLE) && (cmdf != SOUND_MIXER_SYNTH) &&
+ (cmdf != SOUND_MIXER_DEVMASK) && (cmdf != SOUND_MIXER_STEREODEVS) &&
+ (cmdf != SOUND_MIXER_RECMASK) && (cmdf != SOUND_MIXER_CAPS) &&
+ (cmdf != SOUND_MIXER_RECSRC))
+ {
+ return call_ad_mixer(devc, cmd, arg);
+ }
+
+ if (((cmd >> 8) & 0xff) != 'M')
+ return -EINVAL;
+
+ if (_SIOC_DIR (cmd) & _SIOC_WRITE)
+ {
+ switch (cmdf)
+ {
+ case SOUND_MIXER_RECSRC:
+ if (devc->ad_mixer_dev != NO_WSS_MIXER)
+ return call_ad_mixer(devc, cmd, arg);
+ else
+ {
+ int v;
+ if (get_user(v, (int __user *)arg))
+ return -EFAULT;
+ if (v != 0)
+ return -EINVAL;
+ return 0;
+ }
+ case SOUND_MIXER_VOLUME:
+ if (set_volume_stereo(arg,
+ &devc->mixer.volume_l,
+ &devc->mixer.volume_r))
+ return -EFAULT;
+ set_master_volume(devc, devc->mixer.volume_l,
+ devc->mixer.volume_r);
+ return ret_vol_stereo(devc->mixer.volume_l,
+ devc->mixer.volume_r);
+
+ case SOUND_MIXER_BASS:
+ if (set_volume_mono(arg, &devc->mixer.bass))
+ return -EFAULT;
+ set_bass(devc, devc->mixer.bass);
+ return ret_vol_mono(devc->mixer.bass);
+
+ case SOUND_MIXER_TREBLE:
+ if (set_volume_mono(arg, &devc->mixer.treble))
+ return -EFAULT;
+ set_treble(devc, devc->mixer.treble);
+ return ret_vol_mono(devc->mixer.treble);
+
+ case SOUND_MIXER_SYNTH:
+ if (set_volume_mono(arg, &devc->mixer.synth))
+ return -EFAULT;
+ set_synth_volume(devc, devc->mixer.synth);
+ return ret_vol_mono(devc->mixer.synth);
+
+ default:
+ return -EINVAL;
+ }
+ }
+ else
+ {
+ int val, and_mask = 0, or_mask = 0;
+ /*
+ * Return parameters
+ */
+ switch (cmdf)
+ {
+ case SOUND_MIXER_DEVMASK:
+ if (call_ad_mixer(devc, cmd, arg) == -EINVAL)
+ break;
+ and_mask = ~0;
+ or_mask = SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_SYNTH;
+ break;
+
+ case SOUND_MIXER_STEREODEVS:
+ if (call_ad_mixer(devc, cmd, arg) == -EINVAL)
+ break;
+ and_mask = ~0;
+ or_mask = SOUND_MASK_VOLUME;
+ break;
+
+ case SOUND_MIXER_RECMASK:
+ if (devc->ad_mixer_dev != NO_WSS_MIXER)
+ return call_ad_mixer(devc, cmd, arg);
+ break;
+
+ case SOUND_MIXER_CAPS:
+ if (devc->ad_mixer_dev != NO_WSS_MIXER)
+ return call_ad_mixer(devc, cmd, arg);
+ or_mask = SOUND_CAP_EXCL_INPUT;
+ break;
+
+ case SOUND_MIXER_RECSRC:
+ if (devc->ad_mixer_dev != NO_WSS_MIXER)
+ return call_ad_mixer(devc, cmd, arg);
+ break;
+
+ case SOUND_MIXER_VOLUME:
+ or_mask = ret_vol_stereo(devc->mixer.volume_l, devc->mixer.volume_r);
+ break;
+
+ case SOUND_MIXER_BASS:
+ or_mask = ret_vol_mono(devc->mixer.bass);
+ break;
+
+ case SOUND_MIXER_TREBLE:
+ or_mask = ret_vol_mono(devc->mixer.treble);
+ break;
+
+ case SOUND_MIXER_SYNTH:
+ or_mask = ret_vol_mono(devc->mixer.synth);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+ val &= and_mask;
+ val |= or_mask;
+ if (put_user(val, (int __user *)arg))
+ return -EFAULT;
+ return val;
+ }
+}
+
+static struct mixer_operations pss_mixer_operations =
+{
+ .owner = THIS_MODULE,
+ .id = "SOUNDPORT",
+ .name = "PSS-AD1848",
+ .ioctl = pss_mixer_ioctl
+};
+
+static void disable_all_emulations(void)
+{
+ outw(0x0000, REG(CONF_PSS)); /* 0x0400 enables joystick */
+ outw(0x0000, REG(CONF_WSS));
+ outw(0x0000, REG(CONF_SB));
+ outw(0x0000, REG(CONF_MIDI));
+ outw(0x0000, REG(CONF_CDROM));
+}
+
+static void configure_nonsound_components(void)
+{
+ /* Configure Joystick port */
+
+ if(pss_enable_joystick)
+ {
+ outw(0x0400, REG(CONF_PSS)); /* 0x0400 enables joystick */
+ printk(KERN_INFO "PSS: joystick enabled.\n");
+ }
+ else
+ {
+ printk(KERN_INFO "PSS: joystick port not enabled.\n");
+ }
+
+ /* Configure CDROM port */
+
+ if (pss_cdrom_port == -1) { /* If cdrom port enablation wasn't requested */
+ printk(KERN_INFO "PSS: CDROM port not enabled.\n");
+ } else if (check_region(pss_cdrom_port, 2)) {
+ printk(KERN_ERR "PSS: CDROM I/O port conflict.\n");
+ } else {
+ set_io_base(devc, CONF_CDROM, pss_cdrom_port);
+ printk(KERN_INFO "PSS: CDROM I/O port set to 0x%x.\n", pss_cdrom_port);
+ }
+}
+
+static int __init attach_pss(struct address_info *hw_config)
+{
+ unsigned short id;
+ char tmp[100];
+
+ devc->base = hw_config->io_base;
+ devc->irq = hw_config->irq;
+ devc->dma = hw_config->dma;
+ devc->osp = hw_config->osp;
+ devc->ad_mixer_dev = NO_WSS_MIXER;
+
+ if (!probe_pss(hw_config))
+ return 0;
+
+ id = inw(REG(PSS_ID)) & 0x00ff;
+
+ /*
+ * Disable all emulations. Will be enabled later (if required).
+ */
+
+ disable_all_emulations();
+
+#ifdef YOU_REALLY_WANT_TO_ALLOCATE_THESE_RESOURCES
+ if (sound_alloc_dma(hw_config->dma, "PSS"))
+ {
+ printk("pss.c: Can't allocate DMA channel.\n");
+ release_region(hw_config->io_base, 0x10);
+ release_region(hw_config->io_base+0x10, 0x9);
+ return 0;
+ }
+ if (!set_irq(devc, CONF_PSS, devc->irq))
+ {
+ printk("PSS: IRQ allocation error.\n");
+ release_region(hw_config->io_base, 0x10);
+ release_region(hw_config->io_base+0x10, 0x9);
+ return 0;
+ }
+ if (!set_dma(devc, CONF_PSS, devc->dma))
+ {
+ printk(KERN_ERR "PSS: DMA allocation error\n");
+ release_region(hw_config->io_base, 0x10);
+ release_region(hw_config->io_base+0x10, 0x9);
+ return 0;
+ }
+#endif
+
+ configure_nonsound_components();
+ pss_initialized = 1;
+ sprintf(tmp, "ECHO-PSS Rev. %d", id);
+ conf_printf(tmp, hw_config);
+ return 1;
+}
+
+static int __init probe_pss_mpu(struct address_info *hw_config)
+{
+ struct resource *ports;
+ int timeout;
+
+ if (!pss_initialized)
+ return 0;
+
+ ports = request_region(hw_config->io_base, 2, "mpu401");
+
+ if (!ports) {
+ printk(KERN_ERR "PSS: MPU I/O port conflict\n");
+ return 0;
+ }
+ set_io_base(devc, CONF_MIDI, hw_config->io_base);
+ if (!set_irq(devc, CONF_MIDI, hw_config->irq)) {
+ printk(KERN_ERR "PSS: MIDI IRQ allocation error.\n");
+ goto fail;
+ }
+ if (!pss_synthLen) {
+ printk(KERN_ERR "PSS: Can't enable MPU. MIDI synth microcode not available.\n");
+ goto fail;
+ }
+ if (!pss_download_boot(devc, pss_synth, pss_synthLen, CPF_FIRST | CPF_LAST)) {
+ printk(KERN_ERR "PSS: Unable to load MIDI synth microcode to DSP.\n");
+ goto fail;
+ }
+
+ /*
+ * Finally wait until the DSP algorithm has initialized itself and
+ * deactivates receive interrupt.
+ */
+
+ for (timeout = 900000; timeout > 0; timeout--)
+ {
+ if ((inb(hw_config->io_base + 1) & 0x80) == 0) /* Input data avail */
+ inb(hw_config->io_base); /* Discard it */
+ else
+ break; /* No more input */
+ }
+
+ if (!probe_mpu401(hw_config, ports))
+ goto fail;
+
+ attach_mpu401(hw_config, THIS_MODULE); /* Slot 1 */
+ if (hw_config->slots[1] != -1) /* The MPU driver installed itself */
+ midi_devs[hw_config->slots[1]]->coproc = &pss_coproc_operations;
+ return 1;
+fail:
+ release_region(hw_config->io_base, 2);
+ return 0;
+}
+
+static int pss_coproc_open(void *dev_info, int sub_device)
+{
+ switch (sub_device)
+ {
+ case COPR_MIDI:
+ if (pss_synthLen == 0)
+ {
+ printk(KERN_ERR "PSS: MIDI synth microcode not available.\n");
+ return -EIO;
+ }
+ if (nonstandard_microcode)
+ if (!pss_download_boot(devc, pss_synth, pss_synthLen, CPF_FIRST | CPF_LAST))
+ {
+ printk(KERN_ERR "PSS: Unable to load MIDI synth microcode to DSP.\n");
+ return -EIO;
+ }
+ nonstandard_microcode = 0;
+ break;
+
+ default:
+ break;
+ }
+ return 0;
+}
+
+static void pss_coproc_close(void *dev_info, int sub_device)
+{
+ return;
+}
+
+static void pss_coproc_reset(void *dev_info)
+{
+ if (pss_synthLen)
+ if (!pss_download_boot(devc, pss_synth, pss_synthLen, CPF_FIRST | CPF_LAST))
+ {
+ printk(KERN_ERR "PSS: Unable to load MIDI synth microcode to DSP.\n");
+ }
+ nonstandard_microcode = 0;
+}
+
+static int download_boot_block(void *dev_info, copr_buffer * buf)
+{
+ if (buf->len <= 0 || buf->len > sizeof(buf->data))
+ return -EINVAL;
+
+ if (!pss_download_boot(devc, buf->data, buf->len, buf->flags))
+ {
+ printk(KERN_ERR "PSS: Unable to load microcode block to DSP.\n");
+ return -EIO;
+ }
+ nonstandard_microcode = 1; /* The MIDI microcode has been overwritten */
+ return 0;
+}
+
+static int pss_coproc_ioctl(void *dev_info, unsigned int cmd, void __user *arg, int local)
+{
+ copr_buffer *buf;
+ copr_msg *mbuf;
+ copr_debug_buf dbuf;
+ unsigned short tmp;
+ unsigned long flags;
+ unsigned short *data;
+ int i, err;
+ /* printk( "PSS coproc ioctl %x %x %d\n", cmd, arg, local); */
+
+ switch (cmd)
+ {
+ case SNDCTL_COPR_RESET:
+ pss_coproc_reset(dev_info);
+ return 0;
+
+ case SNDCTL_COPR_LOAD:
+ buf = vmalloc(sizeof(copr_buffer));
+ if (buf == NULL)
+ return -ENOSPC;
+ if (copy_from_user(buf, arg, sizeof(copr_buffer))) {
+ vfree(buf);
+ return -EFAULT;
+ }
+ err = download_boot_block(dev_info, buf);
+ vfree(buf);
+ return err;
+
+ case SNDCTL_COPR_SENDMSG:
+ mbuf = vmalloc(sizeof(copr_msg));
+ if (mbuf == NULL)
+ return -ENOSPC;
+ if (copy_from_user(mbuf, arg, sizeof(copr_msg))) {
+ vfree(mbuf);
+ return -EFAULT;
+ }
+ data = (unsigned short *)(mbuf->data);
+ spin_lock_irqsave(&lock, flags);
+ for (i = 0; i < mbuf->len; i++) {
+ if (!pss_put_dspword(devc, *data++)) {
+ spin_unlock_irqrestore(&lock,flags);
+ mbuf->len = i; /* feed back number of WORDs sent */
+ err = copy_to_user(arg, mbuf, sizeof(copr_msg));
+ vfree(mbuf);
+ return err ? -EFAULT : -EIO;
+ }
+ }
+ spin_unlock_irqrestore(&lock,flags);
+ vfree(mbuf);
+ return 0;
+
+ case SNDCTL_COPR_RCVMSG:
+ err = 0;
+ mbuf = vmalloc(sizeof(copr_msg));
+ if (mbuf == NULL)
+ return -ENOSPC;
+ data = (unsigned short *)mbuf->data;
+ spin_lock_irqsave(&lock, flags);
+ for (i = 0; i < sizeof(mbuf->data)/sizeof(unsigned short); i++) {
+ mbuf->len = i; /* feed back number of WORDs read */
+ if (!pss_get_dspword(devc, data++)) {
+ if (i == 0)
+ err = -EIO;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&lock,flags);
+ if (copy_to_user(arg, mbuf, sizeof(copr_msg)))
+ err = -EFAULT;
+ vfree(mbuf);
+ return err;
+
+ case SNDCTL_COPR_RDATA:
+ if (copy_from_user(&dbuf, arg, sizeof(dbuf)))
+ return -EFAULT;
+ spin_lock_irqsave(&lock, flags);
+ if (!pss_put_dspword(devc, 0x00d0)) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ if (!pss_put_dspword(devc, (unsigned short)(dbuf.parm1 & 0xffff))) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ if (!pss_get_dspword(devc, &tmp)) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ dbuf.parm1 = tmp;
+ spin_unlock_irqrestore(&lock,flags);
+ if (copy_to_user(arg, &dbuf, sizeof(dbuf)))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_COPR_WDATA:
+ if (copy_from_user(&dbuf, arg, sizeof(dbuf)))
+ return -EFAULT;
+ spin_lock_irqsave(&lock, flags);
+ if (!pss_put_dspword(devc, 0x00d1)) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ if (!pss_put_dspword(devc, (unsigned short) (dbuf.parm1 & 0xffff))) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ tmp = (unsigned int)dbuf.parm2 & 0xffff;
+ if (!pss_put_dspword(devc, tmp)) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ spin_unlock_irqrestore(&lock,flags);
+ return 0;
+
+ case SNDCTL_COPR_WCODE:
+ if (copy_from_user(&dbuf, arg, sizeof(dbuf)))
+ return -EFAULT;
+ spin_lock_irqsave(&lock, flags);
+ if (!pss_put_dspword(devc, 0x00d3)) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ if (!pss_put_dspword(devc, (unsigned short)(dbuf.parm1 & 0xffff))) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ tmp = (unsigned int)dbuf.parm2 & 0x00ff;
+ if (!pss_put_dspword(devc, tmp)) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ tmp = ((unsigned int)dbuf.parm2 >> 8) & 0xffff;
+ if (!pss_put_dspword(devc, tmp)) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ spin_unlock_irqrestore(&lock,flags);
+ return 0;
+
+ case SNDCTL_COPR_RCODE:
+ if (copy_from_user(&dbuf, arg, sizeof(dbuf)))
+ return -EFAULT;
+ spin_lock_irqsave(&lock, flags);
+ if (!pss_put_dspword(devc, 0x00d2)) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ if (!pss_put_dspword(devc, (unsigned short)(dbuf.parm1 & 0xffff))) {
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ if (!pss_get_dspword(devc, &tmp)) { /* Read MSB */
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ dbuf.parm1 = tmp << 8;
+ if (!pss_get_dspword(devc, &tmp)) { /* Read LSB */
+ spin_unlock_irqrestore(&lock,flags);
+ return -EIO;
+ }
+ dbuf.parm1 |= tmp & 0x00ff;
+ spin_unlock_irqrestore(&lock,flags);
+ if (copy_to_user(arg, &dbuf, sizeof(dbuf)))
+ return -EFAULT;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+ return -EINVAL;
+}
+
+static coproc_operations pss_coproc_operations =
+{
+ "ADSP-2115",
+ THIS_MODULE,
+ pss_coproc_open,
+ pss_coproc_close,
+ pss_coproc_ioctl,
+ pss_coproc_reset,
+ &pss_data
+};
+
+static int __init probe_pss_mss(struct address_info *hw_config)
+{
+ volatile int timeout;
+ struct resource *ports;
+ int my_mix = -999; /* gcc shut up */
+
+ if (!pss_initialized)
+ return 0;
+
+ if (!request_region(hw_config->io_base, 4, "WSS config")) {
+ printk(KERN_ERR "PSS: WSS I/O port conflicts.\n");
+ return 0;
+ }
+ ports = request_region(hw_config->io_base + 4, 4, "ad1848");
+ if (!ports) {
+ printk(KERN_ERR "PSS: WSS I/O port conflicts.\n");
+ release_region(hw_config->io_base, 4);
+ return 0;
+ }
+ set_io_base(devc, CONF_WSS, hw_config->io_base);
+ if (!set_irq(devc, CONF_WSS, hw_config->irq)) {
+ printk("PSS: WSS IRQ allocation error.\n");
+ goto fail;
+ }
+ if (!set_dma(devc, CONF_WSS, hw_config->dma)) {
+ printk(KERN_ERR "PSS: WSS DMA allocation error\n");
+ goto fail;
+ }
+ /*
+ * For some reason the card returns 0xff in the WSS status register
+ * immediately after boot. Probably MIDI+SB emulation algorithm
+ * downloaded to the ADSP2115 spends some time initializing the card.
+ * Let's try to wait until it finishes this task.
+ */
+ for (timeout = 0; timeout < 100000 && (inb(hw_config->io_base + WSS_INDEX) &
+ WSS_INITIALIZING); timeout++)
+ ;
+
+ outb((0x0b), hw_config->io_base + WSS_INDEX); /* Required by some cards */
+
+ for (timeout = 0; (inb(hw_config->io_base + WSS_DATA) & WSS_AUTOCALIBRATION) &&
+ (timeout < 100000); timeout++)
+ ;
+
+ if (!probe_ms_sound(hw_config, ports))
+ goto fail;
+
+ devc->ad_mixer_dev = NO_WSS_MIXER;
+ if (pss_mixer)
+ {
+ if ((my_mix = sound_install_mixer (MIXER_DRIVER_VERSION,
+ "PSS-SPEAKERS and AD1848 (through MSS audio codec)",
+ &pss_mixer_operations,
+ sizeof (struct mixer_operations),
+ devc)) < 0)
+ {
+ printk(KERN_ERR "Could not install PSS mixer\n");
+ goto fail;
+ }
+ }
+ pss_mixer_reset(devc);
+ attach_ms_sound(hw_config, ports, THIS_MODULE); /* Slot 0 */
+
+ if (hw_config->slots[0] != -1)
+ {
+ /* The MSS driver installed itself */
+ audio_devs[hw_config->slots[0]]->coproc = &pss_coproc_operations;
+ if (pss_mixer && (num_mixers == (my_mix + 2)))
+ {
+ /* The MSS mixer installed */
+ devc->ad_mixer_dev = audio_devs[hw_config->slots[0]]->mixer_dev;
+ }
+ }
+ return 1;
+fail:
+ release_region(hw_config->io_base + 4, 4);
+ release_region(hw_config->io_base, 4);
+ return 0;
+}
+
+static inline void __exit unload_pss(struct address_info *hw_config)
+{
+ release_region(hw_config->io_base, 0x10);
+ release_region(hw_config->io_base+0x10, 0x9);
+}
+
+static inline void __exit unload_pss_mpu(struct address_info *hw_config)
+{
+ unload_mpu401(hw_config);
+}
+
+static inline void __exit unload_pss_mss(struct address_info *hw_config)
+{
+ unload_ms_sound(hw_config);
+}
+
+
+static struct address_info cfg;
+static struct address_info cfg2;
+static struct address_info cfg_mpu;
+
+static int pss_io __initdata = -1;
+static int mss_io __initdata = -1;
+static int mss_irq __initdata = -1;
+static int mss_dma __initdata = -1;
+static int mpu_io __initdata = -1;
+static int mpu_irq __initdata = -1;
+static int pss_no_sound = 0; /* Just configure non-sound components */
+static int pss_keep_settings = 1; /* Keep hardware settings at module exit */
+static char *pss_firmware = "/etc/sound/pss_synth";
+
+module_param(pss_io, int, 0);
+MODULE_PARM_DESC(pss_io, "Set i/o base of PSS card (probably 0x220 or 0x240)");
+module_param(mss_io, int, 0);
+MODULE_PARM_DESC(mss_io, "Set WSS (audio) i/o base (0x530, 0x604, 0xE80, 0xF40, or other. Address must end in 0 or 4 and must be from 0x100 to 0xFF4)");
+module_param(mss_irq, int, 0);
+MODULE_PARM_DESC(mss_irq, "Set WSS (audio) IRQ (3, 5, 7, 9, 10, 11, 12)");
+module_param(mss_dma, int, 0);
+MODULE_PARM_DESC(mss_dma, "Set WSS (audio) DMA (0, 1, 3)");
+module_param(mpu_io, int, 0);
+MODULE_PARM_DESC(mpu_io, "Set MIDI i/o base (0x330 or other. Address must be on 4 location boundaries and must be from 0x100 to 0xFFC)");
+module_param(mpu_irq, int, 0);
+MODULE_PARM_DESC(mpu_irq, "Set MIDI IRQ (3, 5, 7, 9, 10, 11, 12)");
+module_param(pss_cdrom_port, int, 0);
+MODULE_PARM_DESC(pss_cdrom_port, "Set the PSS CDROM port i/o base (0x340 or other)");
+module_param(pss_enable_joystick, bool, 0);
+MODULE_PARM_DESC(pss_enable_joystick, "Enables the PSS joystick port (1 to enable, 0 to disable)");
+module_param(pss_no_sound, bool, 0);
+MODULE_PARM_DESC(pss_no_sound, "Configure sound compoents (0 - no, 1 - yes)");
+module_param(pss_keep_settings, bool, 0);
+MODULE_PARM_DESC(pss_keep_settings, "Keep hardware setting at driver unloading (0 - no, 1 - yes)");
+module_param(pss_firmware, charp, 0);
+MODULE_PARM_DESC(pss_firmware, "Location of the firmware file (default - /etc/sound/pss_synth)");
+module_param(pss_mixer, bool, 0);
+MODULE_PARM_DESC(pss_mixer, "Enable (1) or disable (0) PSS mixer (controlling of output volume, bass, treble, synth volume). The mixer is not available on all PSS cards.");
+MODULE_AUTHOR("Hannu Savolainen, Vladimir Michl");
+MODULE_DESCRIPTION("Module for PSS sound cards (based on AD1848, ADSP-2115 and ESC614). This module includes control of output amplifier and synth volume of the Beethoven ADSP-16 card (this may work with other PSS cards).");
+MODULE_LICENSE("GPL");
+
+
+static int fw_load = 0;
+static int pssmpu = 0, pssmss = 0;
+
+/*
+ * Load a PSS sound card module
+ */
+
+static int __init init_pss(void)
+{
+
+ if(pss_no_sound) /* If configuring only nonsound components */
+ {
+ cfg.io_base = pss_io;
+ if(!probe_pss(&cfg))
+ return -ENODEV;
+ printk(KERN_INFO "ECHO-PSS Rev. %d\n", inw(REG(PSS_ID)) & 0x00ff);
+ printk(KERN_INFO "PSS: loading in no sound mode.\n");
+ disable_all_emulations();
+ configure_nonsound_components();
+ release_region(pss_io, 0x10);
+ release_region(pss_io + 0x10, 0x9);
+ return 0;
+ }
+
+ cfg.io_base = pss_io;
+
+ cfg2.io_base = mss_io;
+ cfg2.irq = mss_irq;
+ cfg2.dma = mss_dma;
+
+ cfg_mpu.io_base = mpu_io;
+ cfg_mpu.irq = mpu_irq;
+
+ if (cfg.io_base == -1 || cfg2.io_base == -1 || cfg2.irq == -1 || cfg.dma == -1) {
+ printk(KERN_INFO "pss: mss_io, mss_dma, mss_irq and pss_io must be set.\n");
+ return -EINVAL;
+ }
+
+ if (!pss_synth) {
+ fw_load = 1;
+ pss_synthLen = mod_firmware_load(pss_firmware, (void *) &pss_synth);
+ }
+ if (!attach_pss(&cfg))
+ return -ENODEV;
+ /*
+ * Attach stuff
+ */
+ if (probe_pss_mpu(&cfg_mpu))
+ pssmpu = 1;
+
+ if (probe_pss_mss(&cfg2))
+ pssmss = 1;
+
+ return 0;
+}
+
+static void __exit cleanup_pss(void)
+{
+ if(!pss_no_sound)
+ {
+ if(fw_load && pss_synth)
+ vfree(pss_synth);
+ if(pssmss)
+ unload_pss_mss(&cfg2);
+ if(pssmpu)
+ unload_pss_mpu(&cfg_mpu);
+ unload_pss(&cfg);
+ }
+
+ if(!pss_keep_settings) /* Keep hardware settings if asked */
+ {
+ disable_all_emulations();
+ printk(KERN_INFO "Resetting PSS sound card configurations.\n");
+ }
+}
+
+module_init(init_pss);
+module_exit(cleanup_pss);
+
+#ifndef MODULE
+static int __init setup_pss(char *str)
+{
+ /* io, mss_io, mss_irq, mss_dma, mpu_io, mpu_irq */
+ int ints[7];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ pss_io = ints[1];
+ mss_io = ints[2];
+ mss_irq = ints[3];
+ mss_dma = ints[4];
+ mpu_io = ints[5];
+ mpu_irq = ints[6];
+
+ return 1;
+}
+
+__setup("pss=", setup_pss);
+#endif
diff --git a/sound/oss/sb.h b/sound/oss/sb.h
new file mode 100644
index 00000000..77e8891c
--- /dev/null
+++ b/sound/oss/sb.h
@@ -0,0 +1,185 @@
+#define DSP_RESET (devc->base + 0x6)
+#define DSP_READ (devc->base + 0xA)
+#define DSP_WRITE (devc->base + 0xC)
+#define DSP_COMMAND (devc->base + 0xC)
+#define DSP_STATUS (devc->base + 0xC)
+#define DSP_DATA_AVAIL (devc->base + 0xE)
+#define DSP_DATA_AVL16 (devc->base + 0xF)
+#define MIXER_ADDR (devc->base + 0x4)
+#define MIXER_DATA (devc->base + 0x5)
+#define OPL3_LEFT (devc->base + 0x0)
+#define OPL3_RIGHT (devc->base + 0x2)
+#define OPL3_BOTH (devc->base + 0x8)
+/* DSP Commands */
+
+#define DSP_CMD_SPKON 0xD1
+#define DSP_CMD_SPKOFF 0xD3
+#define DSP_CMD_DMAON 0xD0
+#define DSP_CMD_DMAOFF 0xD4
+
+#define IMODE_NONE 0
+#define IMODE_OUTPUT PCM_ENABLE_OUTPUT
+#define IMODE_INPUT PCM_ENABLE_INPUT
+#define IMODE_INIT 3
+#define IMODE_MIDI 4
+
+#define NORMAL_MIDI 0
+#define UART_MIDI 1
+
+
+/*
+ * Device models
+ */
+#define MDL_NONE 0
+#define MDL_SB1 1 /* SB1.0 or 1.5 */
+#define MDL_SB2 2 /* SB2.0 */
+#define MDL_SB201 3 /* SB2.01 */
+#define MDL_SBPRO 4 /* SB Pro */
+#define MDL_SB16 5 /* SB16/32/AWE */
+#define MDL_SBPNP 6 /* SB16/32/AWE PnP */
+#define MDL_JAZZ 10 /* Media Vision Jazz16 */
+#define MDL_SMW 11 /* Logitech SoundMan Wave (Jazz16) */
+#define MDL_ESS 12 /* ESS ES688 and ES1688 */
+#define MDL_AZTECH 13 /* Aztech Sound Galaxy family */
+#define MDL_ES1868MIDI 14 /* MIDI port of ESS1868 */
+#define MDL_AEDSP 15 /* Audio Excel DSP 16 */
+#define MDL_ESSPCI 16 /* ESS PCI card */
+#define MDL_YMPCI 17 /* Yamaha PCI sb in emulation */
+
+#define SUBMDL_ALS007 42 /* ALS-007 differs from SB16 only in mixer */
+ /* register assignment */
+#define SUBMDL_ALS100 43 /* ALS-100 allows sampling rates of up */
+ /* to 48kHz */
+
+/*
+ * Config flags
+ */
+#define SB_NO_MIDI 0x00000001
+#define SB_NO_MIXER 0x00000002
+#define SB_NO_AUDIO 0x00000004
+#define SB_NO_RECORDING 0x00000008 /* No audio recording */
+#define SB_MIDI_ONLY (SB_NO_AUDIO|SB_NO_MIXER)
+#define SB_PCI_IRQ 0x00000010 /* PCI shared IRQ */
+
+struct mixer_def {
+ unsigned int regno: 8;
+ unsigned int bitoffs:4;
+ unsigned int nbits:4;
+};
+
+typedef struct mixer_def mixer_tab[32][2];
+typedef struct mixer_def mixer_ent;
+
+struct sb_module_options
+{
+ int esstype; /* ESS chip type */
+ int acer; /* Do acer notebook init? */
+ int sm_games; /* Logitech soundman games? */
+};
+
+typedef struct sb_devc {
+ int dev;
+
+ /* Hardware parameters */
+ int *osp;
+ int minor, major;
+ int type;
+ int model, submodel;
+ int caps;
+# define SBCAP_STEREO 0x00000001
+# define SBCAP_16BITS 0x00000002
+
+ /* Hardware resources */
+ int base;
+ int irq;
+ int dma8, dma16;
+
+ int pcibase; /* For ESS Maestro etc */
+
+ /* State variables */
+ int opened;
+ /* new audio fields for full duplex support */
+ int fullduplex;
+ int duplex;
+ int speed, bits, channels;
+ volatile int irq_ok;
+ volatile int intr_active, irq_mode;
+ /* duplicate audio fields for full duplex support */
+ volatile int intr_active_16, irq_mode_16;
+
+ /* Mixer fields */
+ int *levels;
+ mixer_tab *iomap;
+ size_t iomap_sz; /* number or records in the iomap table */
+ int mixer_caps, recmask, outmask, supported_devices;
+ int supported_rec_devices, supported_out_devices;
+ int my_mixerdev;
+ int sbmixnum;
+
+ /* Audio fields */
+ unsigned long trg_buf;
+ int trigger_bits;
+ int trg_bytes;
+ int trg_intrflag;
+ int trg_restart;
+ /* duplicate audio fields for full duplex support */
+ unsigned long trg_buf_16;
+ int trigger_bits_16;
+ int trg_bytes_16;
+ int trg_intrflag_16;
+ int trg_restart_16;
+
+ unsigned char tconst;
+
+ /* MIDI fields */
+ int my_mididev;
+ int input_opened;
+ int midi_broken;
+ void (*midi_input_intr) (int dev, unsigned char data);
+ void *midi_irq_cookie; /* IRQ cookie for the midi */
+
+ spinlock_t lock;
+
+ struct sb_module_options sbmo; /* Module options */
+
+ } sb_devc;
+
+/*
+ * PCI card types
+ */
+
+#define SB_PCI_ESSMAESTRO 1 /* ESS Maestro Legacy */
+#define SB_PCI_YAMAHA 2 /* Yamaha Legacy */
+
+/*
+ * Functions
+ */
+
+int sb_dsp_command (sb_devc *devc, unsigned char val);
+int sb_dsp_get_byte(sb_devc * devc);
+int sb_dsp_reset (sb_devc *devc);
+void sb_setmixer (sb_devc *devc, unsigned int port, unsigned int value);
+unsigned int sb_getmixer (sb_devc *devc, unsigned int port);
+int sb_dsp_detect (struct address_info *hw_config, int pci, int pciio, struct sb_module_options *sbmo);
+int sb_dsp_init (struct address_info *hw_config, struct module *owner);
+void sb_dsp_unload(struct address_info *hw_config, int sbmpu);
+int sb_mixer_init(sb_devc *devc, struct module *owner);
+void sb_mixer_unload(sb_devc *devc);
+void sb_mixer_set_stereo (sb_devc *devc, int mode);
+void smw_mixer_init(sb_devc *devc);
+void sb_dsp_midi_init (sb_devc *devc, struct module *owner);
+void sb_audio_init (sb_devc *devc, char *name, struct module *owner);
+void sb_midi_interrupt (sb_devc *devc);
+void sb_chgmixer (sb_devc * devc, unsigned int reg, unsigned int mask, unsigned int val);
+int sb_common_mixer_set(sb_devc * devc, int dev, int left, int right);
+
+int sb_audio_open(int dev, int mode);
+void sb_audio_close(int dev);
+
+/* From sb_common.c */
+void sb_dsp_disable_midi(int port);
+int probe_sbmpu (struct address_info *hw_config, struct module *owner);
+void unload_sbmpu (struct address_info *hw_config);
+
+void unload_sb16(struct address_info *hw_info);
+void unload_sb16midi(struct address_info *hw_info);
diff --git a/sound/oss/sb_audio.c b/sound/oss/sb_audio.c
new file mode 100644
index 00000000..733b014e
--- /dev/null
+++ b/sound/oss/sb_audio.c
@@ -0,0 +1,1098 @@
+/*
+ * sound/oss/sb_audio.c
+ *
+ * Audio routines for Sound Blaster compatible cards.
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Changes
+ * Alan Cox : Formatting and clean ups
+ *
+ * Status
+ * Mostly working. Weird uart bug causing irq storms
+ *
+ * Daniel J. Rodriksson: Changes to make sb16 work full duplex.
+ * Maybe other 16 bit cards in this code could behave
+ * the same.
+ * Chris Rankin: Use spinlocks instead of CLI/STI
+ */
+
+#include <linux/spinlock.h>
+
+#include "sound_config.h"
+
+#include "sb_mixer.h"
+#include "sb.h"
+
+#include "sb_ess.h"
+
+int sb_audio_open(int dev, int mode)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ unsigned long flags;
+
+ if (devc == NULL)
+ {
+ printk(KERN_ERR "Sound Blaster: incomplete initialization.\n");
+ return -ENXIO;
+ }
+ if (devc->caps & SB_NO_RECORDING && mode & OPEN_READ)
+ {
+ if (mode == OPEN_READ)
+ return -EPERM;
+ }
+ spin_lock_irqsave(&devc->lock, flags);
+ if (devc->opened)
+ {
+ spin_unlock_irqrestore(&devc->lock, flags);
+ return -EBUSY;
+ }
+ if (devc->dma16 != -1 && devc->dma16 != devc->dma8 && !devc->duplex)
+ {
+ if (sound_open_dma(devc->dma16, "Sound Blaster 16 bit"))
+ {
+ spin_unlock_irqrestore(&devc->lock, flags);
+ return -EBUSY;
+ }
+ }
+ devc->opened = mode;
+ spin_unlock_irqrestore(&devc->lock, flags);
+
+ devc->irq_mode = IMODE_NONE;
+ devc->irq_mode_16 = IMODE_NONE;
+ devc->fullduplex = devc->duplex &&
+ ((mode & OPEN_READ) && (mode & OPEN_WRITE));
+ sb_dsp_reset(devc);
+
+ /* At first glance this check isn't enough, some ESS chips might not
+ * have a RECLEV. However if they don't common_mixer_set will refuse
+ * cause devc->iomap has no register mapping for RECLEV
+ */
+ if (devc->model == MDL_ESS) ess_mixer_reload (devc, SOUND_MIXER_RECLEV);
+
+ /* The ALS007 seems to require that the DSP be removed from the output */
+ /* in order for recording to be activated properly. This is done by */
+ /* setting the appropriate bits of the output control register 4ch to */
+ /* zero. This code assumes that the output control registers are not */
+ /* used anywhere else and therefore the DSP bits are *always* ON for */
+ /* output and OFF for sampling. */
+
+ if (devc->submodel == SUBMDL_ALS007)
+ {
+ if (mode & OPEN_READ)
+ sb_setmixer(devc,ALS007_OUTPUT_CTRL2,
+ sb_getmixer(devc,ALS007_OUTPUT_CTRL2) & 0xf9);
+ else
+ sb_setmixer(devc,ALS007_OUTPUT_CTRL2,
+ sb_getmixer(devc,ALS007_OUTPUT_CTRL2) | 0x06);
+ }
+ return 0;
+}
+
+void sb_audio_close(int dev)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ /* fix things if mmap turned off fullduplex */
+ if(devc->duplex
+ && !devc->fullduplex
+ && (devc->opened & OPEN_READ) && (devc->opened & OPEN_WRITE))
+ {
+ struct dma_buffparms *dmap_temp;
+ dmap_temp = audio_devs[dev]->dmap_out;
+ audio_devs[dev]->dmap_out = audio_devs[dev]->dmap_in;
+ audio_devs[dev]->dmap_in = dmap_temp;
+ }
+ audio_devs[dev]->dmap_out->dma = devc->dma8;
+ audio_devs[dev]->dmap_in->dma = ( devc->duplex ) ?
+ devc->dma16 : devc->dma8;
+
+ if (devc->dma16 != -1 && devc->dma16 != devc->dma8 && !devc->duplex)
+ sound_close_dma(devc->dma16);
+
+ /* For ALS007, turn DSP output back on if closing the device for read */
+
+ if ((devc->submodel == SUBMDL_ALS007) && (devc->opened & OPEN_READ))
+ {
+ sb_setmixer(devc,ALS007_OUTPUT_CTRL2,
+ sb_getmixer(devc,ALS007_OUTPUT_CTRL2) | 0x06);
+ }
+ devc->opened = 0;
+}
+
+static void sb_set_output_parms(int dev, unsigned long buf, int nr_bytes,
+ int intrflag)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (!devc->fullduplex || devc->bits == AFMT_S16_LE)
+ {
+ devc->trg_buf = buf;
+ devc->trg_bytes = nr_bytes;
+ devc->trg_intrflag = intrflag;
+ devc->irq_mode = IMODE_OUTPUT;
+ }
+ else
+ {
+ devc->trg_buf_16 = buf;
+ devc->trg_bytes_16 = nr_bytes;
+ devc->trg_intrflag_16 = intrflag;
+ devc->irq_mode_16 = IMODE_OUTPUT;
+ }
+}
+
+static void sb_set_input_parms(int dev, unsigned long buf, int count, int intrflag)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (!devc->fullduplex || devc->bits != AFMT_S16_LE)
+ {
+ devc->trg_buf = buf;
+ devc->trg_bytes = count;
+ devc->trg_intrflag = intrflag;
+ devc->irq_mode = IMODE_INPUT;
+ }
+ else
+ {
+ devc->trg_buf_16 = buf;
+ devc->trg_bytes_16 = count;
+ devc->trg_intrflag_16 = intrflag;
+ devc->irq_mode_16 = IMODE_INPUT;
+ }
+}
+
+/*
+ * SB1.x compatible routines
+ */
+
+static void sb1_audio_output_block(int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ unsigned long flags;
+ int count = nr_bytes;
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */
+
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ count >>= 1;
+ count--;
+
+ devc->irq_mode = IMODE_OUTPUT;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ if (sb_dsp_command(devc, 0x14)) /* 8 bit DAC using DMA */
+ {
+ sb_dsp_command(devc, (unsigned char) (count & 0xff));
+ sb_dsp_command(devc, (unsigned char) ((count >> 8) & 0xff));
+ }
+ else
+ printk(KERN_WARNING "Sound Blaster: unable to start DAC.\n");
+ spin_unlock_irqrestore(&devc->lock, flags);
+ devc->intr_active = 1;
+}
+
+static void sb1_audio_start_input(int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ unsigned long flags;
+ int count = nr_bytes;
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ /*
+ * Start a DMA input to the buffer pointed by dmaqtail
+ */
+
+ /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */
+
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ count >>= 1;
+ count--;
+
+ devc->irq_mode = IMODE_INPUT;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ if (sb_dsp_command(devc, 0x24)) /* 8 bit ADC using DMA */
+ {
+ sb_dsp_command(devc, (unsigned char) (count & 0xff));
+ sb_dsp_command(devc, (unsigned char) ((count >> 8) & 0xff));
+ }
+ else
+ printk(KERN_ERR "Sound Blaster: unable to start ADC.\n");
+ spin_unlock_irqrestore(&devc->lock, flags);
+
+ devc->intr_active = 1;
+}
+
+static void sb1_audio_trigger(int dev, int bits)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ bits &= devc->irq_mode;
+
+ if (!bits)
+ sb_dsp_command(devc, 0xd0); /* Halt DMA */
+ else
+ {
+ switch (devc->irq_mode)
+ {
+ case IMODE_INPUT:
+ sb1_audio_start_input(dev, devc->trg_buf, devc->trg_bytes,
+ devc->trg_intrflag);
+ break;
+
+ case IMODE_OUTPUT:
+ sb1_audio_output_block(dev, devc->trg_buf, devc->trg_bytes,
+ devc->trg_intrflag);
+ break;
+ }
+ }
+ devc->trigger_bits = bits;
+}
+
+static int sb1_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ if (sb_dsp_command(devc, 0x40))
+ sb_dsp_command(devc, devc->tconst);
+ sb_dsp_command(devc, DSP_CMD_SPKOFF);
+ spin_unlock_irqrestore(&devc->lock, flags);
+
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static int sb1_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ if (sb_dsp_command(devc, 0x40))
+ sb_dsp_command(devc, devc->tconst);
+ sb_dsp_command(devc, DSP_CMD_SPKON);
+ spin_unlock_irqrestore(&devc->lock, flags);
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static int sb1_audio_set_speed(int dev, int speed)
+{
+ int max_speed = 23000;
+ sb_devc *devc = audio_devs[dev]->devc;
+ int tmp;
+
+ if (devc->opened & OPEN_READ)
+ max_speed = 13000;
+
+ if (speed > 0)
+ {
+ if (speed < 4000)
+ speed = 4000;
+
+ if (speed > max_speed)
+ speed = max_speed;
+
+ devc->tconst = (256 - ((1000000 + speed / 2) / speed)) & 0xff;
+ tmp = 256 - devc->tconst;
+ speed = (1000000 + tmp / 2) / tmp;
+
+ devc->speed = speed;
+ }
+ return devc->speed;
+}
+
+static short sb1_audio_set_channels(int dev, short channels)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ return devc->channels = 1;
+}
+
+static unsigned int sb1_audio_set_bits(int dev, unsigned int bits)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ return devc->bits = 8;
+}
+
+static void sb1_audio_halt_xfer(int dev)
+{
+ unsigned long flags;
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ sb_dsp_reset(devc);
+ spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+/*
+ * SB 2.0 and SB 2.01 compatible routines
+ */
+
+static void sb20_audio_output_block(int dev, unsigned long buf, int nr_bytes,
+ int intrflag)
+{
+ unsigned long flags;
+ int count = nr_bytes;
+ sb_devc *devc = audio_devs[dev]->devc;
+ unsigned char cmd;
+
+ /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */
+
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ count >>= 1;
+ count--;
+
+ devc->irq_mode = IMODE_OUTPUT;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ if (sb_dsp_command(devc, 0x48)) /* DSP Block size */
+ {
+ sb_dsp_command(devc, (unsigned char) (count & 0xff));
+ sb_dsp_command(devc, (unsigned char) ((count >> 8) & 0xff));
+
+ if (devc->speed * devc->channels <= 23000)
+ cmd = 0x1c; /* 8 bit PCM output */
+ else
+ cmd = 0x90; /* 8 bit high speed PCM output (SB2.01/Pro) */
+
+ if (!sb_dsp_command(devc, cmd))
+ printk(KERN_ERR "Sound Blaster: unable to start DAC.\n");
+ }
+ else
+ printk(KERN_ERR "Sound Blaster: unable to start DAC.\n");
+ spin_unlock_irqrestore(&devc->lock, flags);
+ devc->intr_active = 1;
+}
+
+static void sb20_audio_start_input(int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ unsigned long flags;
+ int count = nr_bytes;
+ sb_devc *devc = audio_devs[dev]->devc;
+ unsigned char cmd;
+
+ /*
+ * Start a DMA input to the buffer pointed by dmaqtail
+ */
+
+ /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */
+
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ count >>= 1;
+ count--;
+
+ devc->irq_mode = IMODE_INPUT;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ if (sb_dsp_command(devc, 0x48)) /* DSP Block size */
+ {
+ sb_dsp_command(devc, (unsigned char) (count & 0xff));
+ sb_dsp_command(devc, (unsigned char) ((count >> 8) & 0xff));
+
+ if (devc->speed * devc->channels <= (devc->major == 3 ? 23000 : 13000))
+ cmd = 0x2c; /* 8 bit PCM input */
+ else
+ cmd = 0x98; /* 8 bit high speed PCM input (SB2.01/Pro) */
+
+ if (!sb_dsp_command(devc, cmd))
+ printk(KERN_ERR "Sound Blaster: unable to start ADC.\n");
+ }
+ else
+ printk(KERN_ERR "Sound Blaster: unable to start ADC.\n");
+ spin_unlock_irqrestore(&devc->lock, flags);
+ devc->intr_active = 1;
+}
+
+static void sb20_audio_trigger(int dev, int bits)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ bits &= devc->irq_mode;
+
+ if (!bits)
+ sb_dsp_command(devc, 0xd0); /* Halt DMA */
+ else
+ {
+ switch (devc->irq_mode)
+ {
+ case IMODE_INPUT:
+ sb20_audio_start_input(dev, devc->trg_buf, devc->trg_bytes,
+ devc->trg_intrflag);
+ break;
+
+ case IMODE_OUTPUT:
+ sb20_audio_output_block(dev, devc->trg_buf, devc->trg_bytes,
+ devc->trg_intrflag);
+ break;
+ }
+ }
+ devc->trigger_bits = bits;
+}
+
+/*
+ * SB2.01 specific speed setup
+ */
+
+static int sb201_audio_set_speed(int dev, int speed)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ int tmp;
+ int s = speed * devc->channels;
+
+ if (speed > 0)
+ {
+ if (speed < 4000)
+ speed = 4000;
+ if (speed > 44100)
+ speed = 44100;
+ if (devc->opened & OPEN_READ && speed > 15000)
+ speed = 15000;
+ devc->tconst = (256 - ((1000000 + s / 2) / s)) & 0xff;
+ tmp = 256 - devc->tconst;
+ speed = ((1000000 + tmp / 2) / tmp) / devc->channels;
+
+ devc->speed = speed;
+ }
+ return devc->speed;
+}
+
+/*
+ * SB Pro specific routines
+ */
+
+static int sbpro_audio_prepare_for_input(int dev, int bsize, int bcount)
+{ /* For SB Pro and Jazz16 */
+ sb_devc *devc = audio_devs[dev]->devc;
+ unsigned long flags;
+ unsigned char bits = 0;
+
+ if (devc->dma16 >= 0 && devc->dma16 != devc->dma8)
+ audio_devs[dev]->dmap_out->dma = audio_devs[dev]->dmap_in->dma =
+ devc->bits == 16 ? devc->dma16 : devc->dma8;
+
+ if (devc->model == MDL_JAZZ || devc->model == MDL_SMW)
+ if (devc->bits == AFMT_S16_LE)
+ bits = 0x04; /* 16 bit mode */
+
+ spin_lock_irqsave(&devc->lock, flags);
+ if (sb_dsp_command(devc, 0x40))
+ sb_dsp_command(devc, devc->tconst);
+ sb_dsp_command(devc, DSP_CMD_SPKOFF);
+ if (devc->channels == 1)
+ sb_dsp_command(devc, 0xa0 | bits); /* Mono input */
+ else
+ sb_dsp_command(devc, 0xa8 | bits); /* Stereo input */
+ spin_unlock_irqrestore(&devc->lock, flags);
+
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static int sbpro_audio_prepare_for_output(int dev, int bsize, int bcount)
+{ /* For SB Pro and Jazz16 */
+ sb_devc *devc = audio_devs[dev]->devc;
+ unsigned long flags;
+ unsigned char tmp;
+ unsigned char bits = 0;
+
+ if (devc->dma16 >= 0 && devc->dma16 != devc->dma8)
+ audio_devs[dev]->dmap_out->dma = audio_devs[dev]->dmap_in->dma = devc->bits == 16 ? devc->dma16 : devc->dma8;
+ if (devc->model == MDL_SBPRO)
+ sb_mixer_set_stereo(devc, devc->channels == 2);
+
+ spin_lock_irqsave(&devc->lock, flags);
+ if (sb_dsp_command(devc, 0x40))
+ sb_dsp_command(devc, devc->tconst);
+ sb_dsp_command(devc, DSP_CMD_SPKON);
+
+ if (devc->model == MDL_JAZZ || devc->model == MDL_SMW)
+ {
+ if (devc->bits == AFMT_S16_LE)
+ bits = 0x04; /* 16 bit mode */
+
+ if (devc->channels == 1)
+ sb_dsp_command(devc, 0xa0 | bits); /* Mono output */
+ else
+ sb_dsp_command(devc, 0xa8 | bits); /* Stereo output */
+ spin_unlock_irqrestore(&devc->lock, flags);
+ }
+ else
+ {
+ spin_unlock_irqrestore(&devc->lock, flags);
+ tmp = sb_getmixer(devc, 0x0e);
+ if (devc->channels == 1)
+ tmp &= ~0x02;
+ else
+ tmp |= 0x02;
+ sb_setmixer(devc, 0x0e, tmp);
+ }
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static int sbpro_audio_set_speed(int dev, int speed)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (speed > 0)
+ {
+ if (speed < 4000)
+ speed = 4000;
+ if (speed > 44100)
+ speed = 44100;
+ if (devc->channels > 1 && speed > 22050)
+ speed = 22050;
+ sb201_audio_set_speed(dev, speed);
+ }
+ return devc->speed;
+}
+
+static short sbpro_audio_set_channels(int dev, short channels)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (channels == 1 || channels == 2)
+ {
+ if (channels != devc->channels)
+ {
+ devc->channels = channels;
+ if (devc->model == MDL_SBPRO && devc->channels == 2)
+ sbpro_audio_set_speed(dev, devc->speed);
+ }
+ }
+ return devc->channels;
+}
+
+static int jazz16_audio_set_speed(int dev, int speed)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (speed > 0)
+ {
+ int tmp;
+ int s = speed * devc->channels;
+
+ if (speed < 5000)
+ speed = 5000;
+ if (speed > 44100)
+ speed = 44100;
+
+ devc->tconst = (256 - ((1000000 + s / 2) / s)) & 0xff;
+
+ tmp = 256 - devc->tconst;
+ speed = ((1000000 + tmp / 2) / tmp) / devc->channels;
+
+ devc->speed = speed;
+ }
+ return devc->speed;
+}
+
+/*
+ * SB16 specific routines
+ */
+
+static int sb16_audio_set_speed(int dev, int speed)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ int max_speed = devc->submodel == SUBMDL_ALS100 ? 48000 : 44100;
+
+ if (speed > 0)
+ {
+ if (speed < 5000)
+ speed = 5000;
+
+ if (speed > max_speed)
+ speed = max_speed;
+
+ devc->speed = speed;
+ }
+ return devc->speed;
+}
+
+static unsigned int sb16_audio_set_bits(int dev, unsigned int bits)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (bits != 0)
+ {
+ if (bits == AFMT_U8 || bits == AFMT_S16_LE)
+ devc->bits = bits;
+ else
+ devc->bits = AFMT_U8;
+ }
+
+ return devc->bits;
+}
+
+static int sb16_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (!devc->fullduplex)
+ {
+ audio_devs[dev]->dmap_out->dma =
+ audio_devs[dev]->dmap_in->dma =
+ devc->bits == AFMT_S16_LE ?
+ devc->dma16 : devc->dma8;
+ }
+ else if (devc->bits == AFMT_S16_LE)
+ {
+ audio_devs[dev]->dmap_out->dma = devc->dma8;
+ audio_devs[dev]->dmap_in->dma = devc->dma16;
+ }
+ else
+ {
+ audio_devs[dev]->dmap_out->dma = devc->dma16;
+ audio_devs[dev]->dmap_in->dma = devc->dma8;
+ }
+
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static int sb16_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (!devc->fullduplex)
+ {
+ audio_devs[dev]->dmap_out->dma =
+ audio_devs[dev]->dmap_in->dma =
+ devc->bits == AFMT_S16_LE ?
+ devc->dma16 : devc->dma8;
+ }
+ else if (devc->bits == AFMT_S16_LE)
+ {
+ audio_devs[dev]->dmap_out->dma = devc->dma8;
+ audio_devs[dev]->dmap_in->dma = devc->dma16;
+ }
+ else
+ {
+ audio_devs[dev]->dmap_out->dma = devc->dma16;
+ audio_devs[dev]->dmap_in->dma = devc->dma8;
+ }
+
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static void sb16_audio_output_block(int dev, unsigned long buf, int count,
+ int intrflag)
+{
+ unsigned long flags, cnt;
+ sb_devc *devc = audio_devs[dev]->devc;
+ unsigned long bits;
+
+ if (!devc->fullduplex || devc->bits == AFMT_S16_LE)
+ {
+ devc->irq_mode = IMODE_OUTPUT;
+ devc->intr_active = 1;
+ }
+ else
+ {
+ devc->irq_mode_16 = IMODE_OUTPUT;
+ devc->intr_active_16 = 1;
+ }
+
+ /* save value */
+ spin_lock_irqsave(&devc->lock, flags);
+ bits = devc->bits;
+ if (devc->fullduplex)
+ devc->bits = (devc->bits == AFMT_S16_LE) ?
+ AFMT_U8 : AFMT_S16_LE;
+ spin_unlock_irqrestore(&devc->lock, flags);
+
+ cnt = count;
+ if (devc->bits == AFMT_S16_LE)
+ cnt >>= 1;
+ cnt--;
+
+ spin_lock_irqsave(&devc->lock, flags);
+
+ /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */
+
+ sb_dsp_command(devc, 0x41);
+ sb_dsp_command(devc, (unsigned char) ((devc->speed >> 8) & 0xff));
+ sb_dsp_command(devc, (unsigned char) (devc->speed & 0xff));
+
+ sb_dsp_command(devc, (devc->bits == AFMT_S16_LE ? 0xb6 : 0xc6));
+ sb_dsp_command(devc, ((devc->channels == 2 ? 0x20 : 0) +
+ (devc->bits == AFMT_S16_LE ? 0x10 : 0)));
+ sb_dsp_command(devc, (unsigned char) (cnt & 0xff));
+ sb_dsp_command(devc, (unsigned char) (cnt >> 8));
+
+ /* restore real value after all programming */
+ devc->bits = bits;
+ spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+
+/*
+ * This fails on the Cyrix MediaGX. If you don't have the DMA enabled
+ * before the first sample arrives it locks up. However even if you
+ * do enable the DMA in time you just get DMA timeouts and missing
+ * interrupts and stuff, so for now I've not bothered fixing this either.
+ */
+
+static void sb16_audio_start_input(int dev, unsigned long buf, int count, int intrflag)
+{
+ unsigned long flags, cnt;
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (!devc->fullduplex || devc->bits != AFMT_S16_LE)
+ {
+ devc->irq_mode = IMODE_INPUT;
+ devc->intr_active = 1;
+ }
+ else
+ {
+ devc->irq_mode_16 = IMODE_INPUT;
+ devc->intr_active_16 = 1;
+ }
+
+ cnt = count;
+ if (devc->bits == AFMT_S16_LE)
+ cnt >>= 1;
+ cnt--;
+
+ spin_lock_irqsave(&devc->lock, flags);
+
+ /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */
+
+ sb_dsp_command(devc, 0x42);
+ sb_dsp_command(devc, (unsigned char) ((devc->speed >> 8) & 0xff));
+ sb_dsp_command(devc, (unsigned char) (devc->speed & 0xff));
+
+ sb_dsp_command(devc, (devc->bits == AFMT_S16_LE ? 0xbe : 0xce));
+ sb_dsp_command(devc, ((devc->channels == 2 ? 0x20 : 0) +
+ (devc->bits == AFMT_S16_LE ? 0x10 : 0)));
+ sb_dsp_command(devc, (unsigned char) (cnt & 0xff));
+ sb_dsp_command(devc, (unsigned char) (cnt >> 8));
+
+ spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+static void sb16_audio_trigger(int dev, int bits)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ int bits_16 = bits & devc->irq_mode_16;
+ bits &= devc->irq_mode;
+
+ if (!bits && !bits_16)
+ sb_dsp_command(devc, 0xd0); /* Halt DMA */
+ else
+ {
+ if (bits)
+ {
+ switch (devc->irq_mode)
+ {
+ case IMODE_INPUT:
+ sb16_audio_start_input(dev,
+ devc->trg_buf,
+ devc->trg_bytes,
+ devc->trg_intrflag);
+ break;
+
+ case IMODE_OUTPUT:
+ sb16_audio_output_block(dev,
+ devc->trg_buf,
+ devc->trg_bytes,
+ devc->trg_intrflag);
+ break;
+ }
+ }
+ if (bits_16)
+ {
+ switch (devc->irq_mode_16)
+ {
+ case IMODE_INPUT:
+ sb16_audio_start_input(dev,
+ devc->trg_buf_16,
+ devc->trg_bytes_16,
+ devc->trg_intrflag_16);
+ break;
+
+ case IMODE_OUTPUT:
+ sb16_audio_output_block(dev,
+ devc->trg_buf_16,
+ devc->trg_bytes_16,
+ devc->trg_intrflag_16);
+ break;
+ }
+ }
+ }
+
+ devc->trigger_bits = bits | bits_16;
+}
+
+static unsigned char lbuf8[2048];
+static signed short *lbuf16 = (signed short *)lbuf8;
+#define LBUFCOPYSIZE 1024
+static void
+sb16_copy_from_user(int dev,
+ char *localbuf, int localoffs,
+ const char __user *userbuf, int useroffs,
+ int max_in, int max_out,
+ int *used, int *returned,
+ int len)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ int i, c, p, locallen;
+ unsigned char *buf8;
+ signed short *buf16;
+
+ /* if not duplex no conversion */
+ if (!devc->fullduplex)
+ {
+ if (copy_from_user(localbuf + localoffs,
+ userbuf + useroffs, len))
+ return;
+ *used = len;
+ *returned = len;
+ }
+ else if (devc->bits == AFMT_S16_LE)
+ {
+ /* 16 -> 8 */
+ /* max_in >> 1, max number of samples in ( 16 bits ) */
+ /* max_out, max number of samples out ( 8 bits ) */
+ /* len, number of samples that will be taken ( 16 bits )*/
+ /* c, count of samples remaining in buffer ( 16 bits )*/
+ /* p, count of samples already processed ( 16 bits )*/
+ len = ( (max_in >> 1) > max_out) ? max_out : (max_in >> 1);
+ c = len;
+ p = 0;
+ buf8 = (unsigned char *)(localbuf + localoffs);
+ while (c)
+ {
+ locallen = (c >= LBUFCOPYSIZE ? LBUFCOPYSIZE : c);
+ /* << 1 in order to get 16 bit samples */
+ if (copy_from_user(lbuf16,
+ userbuf + useroffs + (p << 1),
+ locallen << 1))
+ return;
+ for (i = 0; i < locallen; i++)
+ {
+ buf8[p+i] = ~((lbuf16[i] >> 8) & 0xff) ^ 0x80;
+ }
+ c -= locallen; p += locallen;
+ }
+ /* used = ( samples * 16 bits size ) */
+ *used = max_in > ( max_out << 1) ? (max_out << 1) : max_in;
+ /* returned = ( samples * 8 bits size ) */
+ *returned = len;
+ }
+ else
+ {
+ /* 8 -> 16 */
+ /* max_in, max number of samples in ( 8 bits ) */
+ /* max_out >> 1, max number of samples out ( 16 bits ) */
+ /* len, number of samples that will be taken ( 8 bits )*/
+ /* c, count of samples remaining in buffer ( 8 bits )*/
+ /* p, count of samples already processed ( 8 bits )*/
+ len = max_in > (max_out >> 1) ? (max_out >> 1) : max_in;
+ c = len;
+ p = 0;
+ buf16 = (signed short *)(localbuf + localoffs);
+ while (c)
+ {
+ locallen = (c >= LBUFCOPYSIZE ? LBUFCOPYSIZE : c);
+ if (copy_from_user(lbuf8,
+ userbuf+useroffs + p,
+ locallen))
+ return;
+ for (i = 0; i < locallen; i++)
+ {
+ buf16[p+i] = (~lbuf8[i] ^ 0x80) << 8;
+ }
+ c -= locallen; p += locallen;
+ }
+ /* used = ( samples * 8 bits size ) */
+ *used = len;
+ /* returned = ( samples * 16 bits size ) */
+ *returned = len << 1;
+ }
+}
+
+static void
+sb16_audio_mmap(int dev)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ devc->fullduplex = 0;
+}
+
+static struct audio_driver sb1_audio_driver = /* SB1.x */
+{
+ .owner = THIS_MODULE,
+ .open = sb_audio_open,
+ .close = sb_audio_close,
+ .output_block = sb_set_output_parms,
+ .start_input = sb_set_input_parms,
+ .prepare_for_input = sb1_audio_prepare_for_input,
+ .prepare_for_output = sb1_audio_prepare_for_output,
+ .halt_io = sb1_audio_halt_xfer,
+ .trigger = sb1_audio_trigger,
+ .set_speed = sb1_audio_set_speed,
+ .set_bits = sb1_audio_set_bits,
+ .set_channels = sb1_audio_set_channels
+};
+
+static struct audio_driver sb20_audio_driver = /* SB2.0 */
+{
+ .owner = THIS_MODULE,
+ .open = sb_audio_open,
+ .close = sb_audio_close,
+ .output_block = sb_set_output_parms,
+ .start_input = sb_set_input_parms,
+ .prepare_for_input = sb1_audio_prepare_for_input,
+ .prepare_for_output = sb1_audio_prepare_for_output,
+ .halt_io = sb1_audio_halt_xfer,
+ .trigger = sb20_audio_trigger,
+ .set_speed = sb1_audio_set_speed,
+ .set_bits = sb1_audio_set_bits,
+ .set_channels = sb1_audio_set_channels
+};
+
+static struct audio_driver sb201_audio_driver = /* SB2.01 */
+{
+ .owner = THIS_MODULE,
+ .open = sb_audio_open,
+ .close = sb_audio_close,
+ .output_block = sb_set_output_parms,
+ .start_input = sb_set_input_parms,
+ .prepare_for_input = sb1_audio_prepare_for_input,
+ .prepare_for_output = sb1_audio_prepare_for_output,
+ .halt_io = sb1_audio_halt_xfer,
+ .trigger = sb20_audio_trigger,
+ .set_speed = sb201_audio_set_speed,
+ .set_bits = sb1_audio_set_bits,
+ .set_channels = sb1_audio_set_channels
+};
+
+static struct audio_driver sbpro_audio_driver = /* SB Pro */
+{
+ .owner = THIS_MODULE,
+ .open = sb_audio_open,
+ .close = sb_audio_close,
+ .output_block = sb_set_output_parms,
+ .start_input = sb_set_input_parms,
+ .prepare_for_input = sbpro_audio_prepare_for_input,
+ .prepare_for_output = sbpro_audio_prepare_for_output,
+ .halt_io = sb1_audio_halt_xfer,
+ .trigger = sb20_audio_trigger,
+ .set_speed = sbpro_audio_set_speed,
+ .set_bits = sb1_audio_set_bits,
+ .set_channels = sbpro_audio_set_channels
+};
+
+static struct audio_driver jazz16_audio_driver = /* Jazz16 and SM Wave */
+{
+ .owner = THIS_MODULE,
+ .open = sb_audio_open,
+ .close = sb_audio_close,
+ .output_block = sb_set_output_parms,
+ .start_input = sb_set_input_parms,
+ .prepare_for_input = sbpro_audio_prepare_for_input,
+ .prepare_for_output = sbpro_audio_prepare_for_output,
+ .halt_io = sb1_audio_halt_xfer,
+ .trigger = sb20_audio_trigger,
+ .set_speed = jazz16_audio_set_speed,
+ .set_bits = sb16_audio_set_bits,
+ .set_channels = sbpro_audio_set_channels
+};
+
+static struct audio_driver sb16_audio_driver = /* SB16 */
+{
+ .owner = THIS_MODULE,
+ .open = sb_audio_open,
+ .close = sb_audio_close,
+ .output_block = sb_set_output_parms,
+ .start_input = sb_set_input_parms,
+ .prepare_for_input = sb16_audio_prepare_for_input,
+ .prepare_for_output = sb16_audio_prepare_for_output,
+ .halt_io = sb1_audio_halt_xfer,
+ .copy_user = sb16_copy_from_user,
+ .trigger = sb16_audio_trigger,
+ .set_speed = sb16_audio_set_speed,
+ .set_bits = sb16_audio_set_bits,
+ .set_channels = sbpro_audio_set_channels,
+ .mmap = sb16_audio_mmap
+};
+
+void sb_audio_init(sb_devc * devc, char *name, struct module *owner)
+{
+ int audio_flags = 0;
+ int format_mask = AFMT_U8;
+
+ struct audio_driver *driver = &sb1_audio_driver;
+
+ switch (devc->model)
+ {
+ case MDL_SB1: /* SB1.0 or SB 1.5 */
+ DDB(printk("Will use standard SB1.x driver\n"));
+ audio_flags = DMA_HARDSTOP;
+ break;
+
+ case MDL_SB2:
+ DDB(printk("Will use SB2.0 driver\n"));
+ audio_flags = DMA_AUTOMODE;
+ driver = &sb20_audio_driver;
+ break;
+
+ case MDL_SB201:
+ DDB(printk("Will use SB2.01 (high speed) driver\n"));
+ audio_flags = DMA_AUTOMODE;
+ driver = &sb201_audio_driver;
+ break;
+
+ case MDL_JAZZ:
+ case MDL_SMW:
+ DDB(printk("Will use Jazz16 driver\n"));
+ audio_flags = DMA_AUTOMODE;
+ format_mask |= AFMT_S16_LE;
+ driver = &jazz16_audio_driver;
+ break;
+
+ case MDL_ESS:
+ DDB(printk("Will use ESS ES688/1688 driver\n"));
+ driver = ess_audio_init (devc, &audio_flags, &format_mask);
+ break;
+
+ case MDL_SB16:
+ DDB(printk("Will use SB16 driver\n"));
+ audio_flags = DMA_AUTOMODE;
+ format_mask |= AFMT_S16_LE;
+ if (devc->dma8 != devc->dma16 && devc->dma16 != -1)
+ {
+ audio_flags |= DMA_DUPLEX;
+ devc->duplex = 1;
+ }
+ driver = &sb16_audio_driver;
+ break;
+
+ default:
+ DDB(printk("Will use SB Pro driver\n"));
+ audio_flags = DMA_AUTOMODE;
+ driver = &sbpro_audio_driver;
+ }
+
+ if (owner)
+ driver->owner = owner;
+
+ if ((devc->dev = sound_install_audiodrv(AUDIO_DRIVER_VERSION,
+ name,driver, sizeof(struct audio_driver),
+ audio_flags, format_mask, devc,
+ devc->dma8,
+ devc->duplex ? devc->dma16 : devc->dma8)) < 0)
+ {
+ printk(KERN_ERR "Sound Blaster: unable to install audio.\n");
+ return;
+ }
+ audio_devs[devc->dev]->mixer_dev = devc->my_mixerdev;
+ audio_devs[devc->dev]->min_fragment = 5;
+}
diff --git a/sound/oss/sb_card.c b/sound/oss/sb_card.c
new file mode 100644
index 00000000..fb5d7250
--- /dev/null
+++ b/sound/oss/sb_card.c
@@ -0,0 +1,354 @@
+/*
+ * sound/oss/sb_card.c
+ *
+ * Detection routine for the ISA Sound Blaster and compatible sound
+ * cards.
+ *
+ * This file is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this
+ * software for more info.
+ *
+ * This is a complete rewrite of the detection routines. This was
+ * prompted by the PnP API change during v2.5 and the ugly state the
+ * code was in.
+ *
+ * Copyright (C) by Paul Laufer 2002. Based on code originally by
+ * Hannu Savolainen which was modified by many others over the
+ * years. Authors specifically mentioned in the previous version were:
+ * Daniel Stone, Alessandro Zummo, Jeff Garzik, Arnaldo Carvalho de
+ * Melo, Daniel Church, and myself.
+ *
+ * 02-05-2003 Original Release, Paul Laufer <paul@laufernet.com>
+ * 02-07-2003 Bug made it into first release. Take two.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include "sound_config.h"
+#include "sb_mixer.h"
+#include "sb.h"
+#ifdef CONFIG_PNP
+#include <linux/pnp.h>
+#endif /* CONFIG_PNP */
+#include "sb_card.h"
+
+MODULE_DESCRIPTION("OSS Soundblaster ISA PnP and legacy sound driver");
+MODULE_LICENSE("GPL");
+
+extern void *smw_free;
+
+static int __initdata mpu_io = 0;
+static int __initdata io = -1;
+static int __initdata irq = -1;
+static int __initdata dma = -1;
+static int __initdata dma16 = -1;
+static int __initdata type = 0; /* Can set this to a specific card type */
+static int __initdata esstype = 0; /* ESS chip type */
+static int __initdata acer = 0; /* Do acer notebook init? */
+static int __initdata sm_games = 0; /* Logitech soundman games? */
+
+static struct sb_card_config *legacy = NULL;
+
+#ifdef CONFIG_PNP
+static int pnp_registered;
+static int __initdata pnp = 1;
+/*
+static int __initdata uart401 = 0;
+*/
+#else
+static int __initdata pnp = 0;
+#endif
+
+module_param(io, int, 000);
+MODULE_PARM_DESC(io, "Soundblaster i/o base address (0x220,0x240,0x260,0x280)");
+module_param(irq, int, 000);
+MODULE_PARM_DESC(irq, "IRQ (5,7,9,10)");
+module_param(dma, int, 000);
+MODULE_PARM_DESC(dma, "8-bit DMA channel (0,1,3)");
+module_param(dma16, int, 000);
+MODULE_PARM_DESC(dma16, "16-bit DMA channel (5,6,7)");
+module_param(mpu_io, int, 000);
+MODULE_PARM_DESC(mpu_io, "MPU base address");
+module_param(type, int, 000);
+MODULE_PARM_DESC(type, "You can set this to specific card type (doesn't " \
+ "work with pnp)");
+module_param(sm_games, int, 000);
+MODULE_PARM_DESC(sm_games, "Enable support for Logitech soundman games " \
+ "(doesn't work with pnp)");
+module_param(esstype, int, 000);
+MODULE_PARM_DESC(esstype, "ESS chip type (doesn't work with pnp)");
+module_param(acer, int, 000);
+MODULE_PARM_DESC(acer, "Set this to detect cards in some ACER notebooks "\
+ "(doesn't work with pnp)");
+
+#ifdef CONFIG_PNP
+module_param(pnp, int, 000);
+MODULE_PARM_DESC(pnp, "Went set to 0 will disable detection using PnP. "\
+ "Default is 1.\n");
+/* Not done yet.... */
+/*
+module_param(uart401, int, 000);
+MODULE_PARM_DESC(uart401, "When set to 1, will attempt to detect and enable"\
+ "the mpu on some clones");
+*/
+#endif /* CONFIG_PNP */
+
+/* OSS subsystem card registration shared by PnP and legacy routines */
+static int sb_register_oss(struct sb_card_config *scc, struct sb_module_options *sbmo)
+{
+ if (!request_region(scc->conf.io_base, 16, "soundblaster")) {
+ printk(KERN_ERR "sb: ports busy.\n");
+ kfree(scc);
+ return -EBUSY;
+ }
+
+ if (!sb_dsp_detect(&scc->conf, 0, 0, sbmo)) {
+ release_region(scc->conf.io_base, 16);
+ printk(KERN_ERR "sb: Failed DSP Detect.\n");
+ kfree(scc);
+ return -ENODEV;
+ }
+ if(!sb_dsp_init(&scc->conf, THIS_MODULE)) {
+ printk(KERN_ERR "sb: Failed DSP init.\n");
+ kfree(scc);
+ return -ENODEV;
+ }
+ if(scc->mpucnf.io_base > 0) {
+ scc->mpu = 1;
+ printk(KERN_INFO "sb: Turning on MPU\n");
+ if(!probe_sbmpu(&scc->mpucnf, THIS_MODULE))
+ scc->mpu = 0;
+ }
+
+ return 1;
+}
+
+static void sb_unload(struct sb_card_config *scc)
+{
+ sb_dsp_unload(&scc->conf, 0);
+ if(scc->mpu)
+ unload_sbmpu(&scc->mpucnf);
+ kfree(scc);
+}
+
+/* Register legacy card with OSS subsystem */
+static int __init sb_init_legacy(void)
+{
+ struct sb_module_options sbmo = {0};
+
+ if((legacy = kzalloc(sizeof(struct sb_card_config), GFP_KERNEL)) == NULL) {
+ printk(KERN_ERR "sb: Error: Could not allocate memory\n");
+ return -ENOMEM;
+ }
+
+ legacy->conf.io_base = io;
+ legacy->conf.irq = irq;
+ legacy->conf.dma = dma;
+ legacy->conf.dma2 = dma16;
+ legacy->conf.card_subtype = type;
+
+ legacy->mpucnf.io_base = mpu_io;
+ legacy->mpucnf.irq = -1;
+ legacy->mpucnf.dma = -1;
+ legacy->mpucnf.dma2 = -1;
+
+ sbmo.esstype = esstype;
+ sbmo.sm_games = sm_games;
+ sbmo.acer = acer;
+
+ return sb_register_oss(legacy, &sbmo);
+}
+
+#ifdef CONFIG_PNP
+
+/* Populate the OSS subsystem structures with information from PnP */
+static void sb_dev2cfg(struct pnp_dev *dev, struct sb_card_config *scc)
+{
+ scc->conf.io_base = -1;
+ scc->conf.irq = -1;
+ scc->conf.dma = -1;
+ scc->conf.dma2 = -1;
+ scc->mpucnf.io_base = -1;
+ scc->mpucnf.irq = -1;
+ scc->mpucnf.dma = -1;
+ scc->mpucnf.dma2 = -1;
+
+ /* All clones layout their PnP tables differently and some use
+ different logical devices for the MPU */
+ if(!strncmp("CTL",scc->card_id,3)) {
+ scc->conf.io_base = pnp_port_start(dev,0);
+ scc->conf.irq = pnp_irq(dev,0);
+ scc->conf.dma = pnp_dma(dev,0);
+ scc->conf.dma2 = pnp_dma(dev,1);
+ scc->mpucnf.io_base = pnp_port_start(dev,1);
+ return;
+ }
+ if(!strncmp("tBA",scc->card_id,3)) {
+ scc->conf.io_base = pnp_port_start(dev,0);
+ scc->conf.irq = pnp_irq(dev,0);
+ scc->conf.dma = pnp_dma(dev,0);
+ scc->conf.dma2 = pnp_dma(dev,1);
+ return;
+ }
+ if(!strncmp("ESS",scc->card_id,3)) {
+ scc->conf.io_base = pnp_port_start(dev,0);
+ scc->conf.irq = pnp_irq(dev,0);
+ scc->conf.dma = pnp_dma(dev,0);
+ scc->conf.dma2 = pnp_dma(dev,1);
+ scc->mpucnf.io_base = pnp_port_start(dev,2);
+ return;
+ }
+ if(!strncmp("CMI",scc->card_id,3)) {
+ scc->conf.io_base = pnp_port_start(dev,0);
+ scc->conf.irq = pnp_irq(dev,0);
+ scc->conf.dma = pnp_dma(dev,0);
+ scc->conf.dma2 = pnp_dma(dev,1);
+ return;
+ }
+ if(!strncmp("RWB",scc->card_id,3)) {
+ scc->conf.io_base = pnp_port_start(dev,0);
+ scc->conf.irq = pnp_irq(dev,0);
+ scc->conf.dma = pnp_dma(dev,0);
+ return;
+ }
+ if(!strncmp("ALS",scc->card_id,3)) {
+ if(!strncmp("ALS0007",scc->card_id,7)) {
+ scc->conf.io_base = pnp_port_start(dev,0);
+ scc->conf.irq = pnp_irq(dev,0);
+ scc->conf.dma = pnp_dma(dev,0);
+ } else {
+ scc->conf.io_base = pnp_port_start(dev,0);
+ scc->conf.irq = pnp_irq(dev,0);
+ scc->conf.dma = pnp_dma(dev,1);
+ scc->conf.dma2 = pnp_dma(dev,0);
+ }
+ return;
+ }
+ if(!strncmp("RTL",scc->card_id,3)) {
+ scc->conf.io_base = pnp_port_start(dev,0);
+ scc->conf.irq = pnp_irq(dev,0);
+ scc->conf.dma = pnp_dma(dev,1);
+ scc->conf.dma2 = pnp_dma(dev,0);
+ }
+}
+
+static unsigned int sb_pnp_devices;
+
+/* Probe callback function for the PnP API */
+static int sb_pnp_probe(struct pnp_card_link *card, const struct pnp_card_device_id *card_id)
+{
+ struct sb_card_config *scc;
+ struct sb_module_options sbmo = {0}; /* Default to 0 for PnP */
+ struct pnp_dev *dev = pnp_request_card_device(card, card_id->devs[0].id, NULL);
+
+ if(!dev){
+ return -EBUSY;
+ }
+
+ if((scc = kzalloc(sizeof(struct sb_card_config), GFP_KERNEL)) == NULL) {
+ printk(KERN_ERR "sb: Error: Could not allocate memory\n");
+ return -ENOMEM;
+ }
+
+ printk(KERN_INFO "sb: PnP: Found Card Named = \"%s\", Card PnP id = " \
+ "%s, Device PnP id = %s\n", card->card->name, card_id->id,
+ dev->id->id);
+
+ scc->card_id = card_id->id;
+ scc->dev_id = dev->id->id;
+ sb_dev2cfg(dev, scc);
+
+ printk(KERN_INFO "sb: PnP: Detected at: io=0x%x, irq=%d, " \
+ "dma=%d, dma16=%d\n", scc->conf.io_base, scc->conf.irq,
+ scc->conf.dma, scc->conf.dma2);
+
+ pnp_set_card_drvdata(card, scc);
+ sb_pnp_devices++;
+
+ return sb_register_oss(scc, &sbmo);
+}
+
+static void sb_pnp_remove(struct pnp_card_link *card)
+{
+ struct sb_card_config *scc = pnp_get_card_drvdata(card);
+
+ if(!scc)
+ return;
+
+ printk(KERN_INFO "sb: PnP: Removing %s\n", scc->card_id);
+
+ sb_unload(scc);
+}
+
+static struct pnp_card_driver sb_pnp_driver = {
+ .name = "OSS SndBlstr", /* 16 character limit */
+ .id_table = sb_pnp_card_table,
+ .probe = sb_pnp_probe,
+ .remove = sb_pnp_remove,
+};
+MODULE_DEVICE_TABLE(pnp_card, sb_pnp_card_table);
+#endif /* CONFIG_PNP */
+
+static void sb_unregister_all(void)
+{
+#ifdef CONFIG_PNP
+ if (pnp_registered)
+ pnp_unregister_card_driver(&sb_pnp_driver);
+#endif
+}
+
+static int __init sb_init(void)
+{
+ int lres = 0;
+ int pres = 0;
+
+ printk(KERN_INFO "sb: Init: Starting Probe...\n");
+
+ if(io != -1 && irq != -1 && dma != -1) {
+ printk(KERN_INFO "sb: Probing legacy card with io=%x, "\
+ "irq=%d, dma=%d, dma16=%d\n",io, irq, dma, dma16);
+ lres = sb_init_legacy();
+ } else if((io != -1 || irq != -1 || dma != -1) ||
+ (!pnp && (io == -1 && irq == -1 && dma == -1)))
+ printk(KERN_ERR "sb: Error: At least io, irq, and dma "\
+ "must be set for legacy cards.\n");
+
+#ifdef CONFIG_PNP
+ if(pnp) {
+ int err = pnp_register_card_driver(&sb_pnp_driver);
+ if (!err)
+ pnp_registered = 1;
+ pres = sb_pnp_devices;
+ }
+#endif
+ printk(KERN_INFO "sb: Init: Done\n");
+
+ /* If either PnP or Legacy registered a card then return
+ * success */
+ if (pres == 0 && lres <= 0) {
+ sb_unregister_all();
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static void __exit sb_exit(void)
+{
+ printk(KERN_INFO "sb: Unloading...\n");
+
+ /* Unload legacy card */
+ if (legacy) {
+ printk (KERN_INFO "sb: Unloading legacy card\n");
+ sb_unload(legacy);
+ }
+
+ sb_unregister_all();
+
+ vfree(smw_free);
+ smw_free = NULL;
+}
+
+module_init(sb_init);
+module_exit(sb_exit);
diff --git a/sound/oss/sb_card.h b/sound/oss/sb_card.h
new file mode 100644
index 00000000..5535cff8
--- /dev/null
+++ b/sound/oss/sb_card.h
@@ -0,0 +1,149 @@
+/*
+ * sound/oss/sb_card.h
+ *
+ * This file is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this
+ * software for more info.
+ *
+ * 02-05-2002 Original Release, Paul Laufer <paul@laufernet.com>
+ */
+
+struct sb_card_config {
+ struct address_info conf;
+ struct address_info mpucnf;
+ const char *card_id;
+ const char *dev_id;
+ int mpu;
+};
+
+#ifdef CONFIG_PNP
+
+/*
+ * SoundBlaster PnP tables and structures.
+ */
+
+/* Card PnP ID Table */
+static struct pnp_card_device_id sb_pnp_card_table[] = {
+ /* Sound Blaster 16 */
+ {.id = "CTL0024", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster 16 */
+ {.id = "CTL0025", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster 16 */
+ {.id = "CTL0026", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster 16 */
+ {.id = "CTL0027", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster 16 */
+ {.id = "CTL0028", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster 16 */
+ {.id = "CTL0029", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster 16 */
+ {.id = "CTL002a", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster 16 */
+ {.id = "CTL002b", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster 16 */
+ {.id = "CTL002c", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster 16 */
+ {.id = "CTL00ed", .driver_data = 0, .devs = { {.id="CTL0041"}, } },
+ /* Sound Blaster 16 */
+ {.id = "CTL0086", .driver_data = 0, .devs = { {.id="CTL0041"}, } },
+ /* Sound Blaster Vibra16S */
+ {.id = "CTL0051", .driver_data = 0, .devs = { {.id="CTL0001"}, } },
+ /* Sound Blaster Vibra16C */
+ {.id = "CTL0070", .driver_data = 0, .devs = { {.id="CTL0001"}, } },
+ /* Sound Blaster Vibra16CL */
+ {.id = "CTL0080", .driver_data = 0, .devs = { {.id="CTL0041"}, } },
+ /* Sound Blaster Vibra16CL */
+ {.id = "CTL00F0", .driver_data = 0, .devs = { {.id="CTL0043"}, } },
+ /* Sound Blaster AWE 32 */
+ {.id = "CTL0039", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster AWE 32 */
+ {.id = "CTL0042", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster AWE 32 */
+ {.id = "CTL0043", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster AWE 32 */
+ {.id = "CTL0044", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster AWE 32 */
+ {.id = "CTL0045", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster AWE 32 */
+ {.id = "CTL0046", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster AWE 32 */
+ {.id = "CTL0047", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster AWE 32 */
+ {.id = "CTL0048", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster AWE 32 */
+ {.id = "CTL0054", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+ /* Sound Blaster AWE 32 */
+ {.id = "CTL009C", .driver_data = 0, .devs = { {.id="CTL0041"}, } },
+ /* Createive SB32 PnP */
+ {.id = "CTL009F", .driver_data = 0, .devs = { {.id="CTL0041"}, } },
+ /* Sound Blaster AWE 64 */
+ {.id = "CTL009D", .driver_data = 0, .devs = { {.id="CTL0042"}, } },
+ /* Sound Blaster AWE 64 Gold */
+ {.id = "CTL009E", .driver_data = 0, .devs = { {.id="CTL0044"}, } },
+ /* Sound Blaster AWE 64 Gold */
+ {.id = "CTL00B2", .driver_data = 0, .devs = { {.id="CTL0044"}, } },
+ /* Sound Blaster AWE 64 */
+ {.id = "CTL00C1", .driver_data = 0, .devs = { {.id="CTL0042"}, } },
+ /* Sound Blaster AWE 64 */
+ {.id = "CTL00C3", .driver_data = 0, .devs = { {.id="CTL0045"}, } },
+ /* Sound Blaster AWE 64 */
+ {.id = "CTL00C5", .driver_data = 0, .devs = { {.id="CTL0045"}, } },
+ /* Sound Blaster AWE 64 */
+ {.id = "CTL00C7", .driver_data = 0, .devs = { {.id="CTL0045"}, } },
+ /* Sound Blaster AWE 64 */
+ {.id = "CTL00E4", .driver_data = 0, .devs = { {.id="CTL0045"}, } },
+ /* Sound Blaster AWE 64 */
+ {.id = "CTL00E9", .driver_data = 0, .devs = { {.id="CTL0045"}, } },
+ /* ESS 1868 */
+ {.id = "ESS0968", .driver_data = 0, .devs = { {.id="ESS0968"}, } },
+ /* ESS 1868 */
+ {.id = "ESS1868", .driver_data = 0, .devs = { {.id="ESS1868"}, } },
+ /* ESS 1868 */
+ {.id = "ESS1868", .driver_data = 0, .devs = { {.id="ESS8611"}, } },
+ /* ESS 1869 PnP AudioDrive */
+ {.id = "ESS0003", .driver_data = 0, .devs = { {.id="ESS1869"}, } },
+ /* ESS 1869 */
+ {.id = "ESS1869", .driver_data = 0, .devs = { {.id="ESS1869"}, } },
+ /* ESS 1878 */
+ {.id = "ESS1878", .driver_data = 0, .devs = { {.id="ESS1878"}, } },
+ /* ESS 1879 */
+ {.id = "ESS1879", .driver_data = 0, .devs = { {.id="ESS1879"}, } },
+ /* CMI 8330 SoundPRO */
+ {.id = "CMI0001", .driver_data = 0, .devs = { {.id="@X@0001"},
+ {.id="@H@0001"},
+ {.id="@@@0001"}, } },
+ /* Diamond DT0197H */
+ {.id = "RWR1688", .driver_data = 0, .devs = { {.id="@@@0001"},
+ {.id="@X@0001"},
+ {.id="@H@0001"}, } },
+ /* ALS007 */
+ {.id = "ALS0007", .driver_data = 0, .devs = { {.id="@@@0001"},
+ {.id="@X@0001"},
+ {.id="@H@0001"}, } },
+ /* ALS100 */
+ {.id = "ALS0001", .driver_data = 0, .devs = { {.id="@@@0001"},
+ {.id="@X@0001"},
+ {.id="@H@0001"}, } },
+ /* ALS110 */
+ {.id = "ALS0110", .driver_data = 0, .devs = { {.id="@@@1001"},
+ {.id="@X@1001"},
+ {.id="@H@0001"}, } },
+ /* ALS120 */
+ {.id = "ALS0120", .driver_data = 0, .devs = { {.id="@@@2001"},
+ {.id="@X@2001"},
+ {.id="@H@0001"}, } },
+ /* ALS200 */
+ {.id = "ALS0200", .driver_data = 0, .devs = { {.id="@@@0020"},
+ {.id="@X@0030"},
+ {.id="@H@0001"}, } },
+ /* ALS200 */
+ {.id = "RTL3000", .driver_data = 0, .devs = { {.id="@@@2001"},
+ {.id="@X@2001"},
+ {.id="@H@0001"}, } },
+ /* Sound Blaster 16 (Virtual PC 2004) */
+ {.id = "tBA03b0", .driver_data = 0, .devs = { {.id="PNPb003"}, } },
+ /* -end- */
+ {.id = "", }
+};
+
+#endif
diff --git a/sound/oss/sb_common.c b/sound/oss/sb_common.c
new file mode 100644
index 00000000..7d42c541
--- /dev/null
+++ b/sound/oss/sb_common.c
@@ -0,0 +1,1292 @@
+/*
+ * sound/oss/sb_common.c
+ *
+ * Common routines for Sound Blaster compatible cards.
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Daniel J. Rodriksson: Modified sbintr to handle 8 and 16 bit interrupts
+ * for full duplex support ( only sb16 by now )
+ * Rolf Fokkens: Added (BETA?) support for ES1887 chips.
+ * (fokkensr@vertis.nl) Which means: You can adjust the recording levels.
+ *
+ * 2000/01/18 - separated sb_card and sb_common -
+ * Jeff Garzik <jgarzik@pobox.com>
+ *
+ * 2000/09/18 - got rid of attach_uart401
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ *
+ * 2001/01/26 - replaced CLI/STI with spinlocks
+ * Chris Rankin <rankinc@zipworld.com.au>
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+
+#include "sound_config.h"
+#include "sound_firmware.h"
+
+#include "mpu401.h"
+
+#include "sb_mixer.h"
+#include "sb.h"
+#include "sb_ess.h"
+
+/*
+ * global module flag
+ */
+
+int sb_be_quiet;
+
+static sb_devc *detected_devc; /* For communication from probe to init */
+static sb_devc *last_devc; /* For MPU401 initialization */
+
+static unsigned char jazz_irq_bits[] = {
+ 0, 0, 2, 3, 0, 1, 0, 4, 0, 2, 5, 0, 0, 0, 0, 6
+};
+
+static unsigned char jazz_dma_bits[] = {
+ 0, 1, 0, 2, 0, 3, 0, 4
+};
+
+void *smw_free;
+
+/*
+ * Jazz16 chipset specific control variables
+ */
+
+static int jazz16_base; /* Not detected */
+static unsigned char jazz16_bits; /* I/O relocation bits */
+static DEFINE_SPINLOCK(jazz16_lock);
+
+/*
+ * Logitech Soundman Wave specific initialization code
+ */
+
+#ifdef SMW_MIDI0001_INCLUDED
+#include "smw-midi0001.h"
+#else
+static unsigned char *smw_ucode;
+static int smw_ucodeLen;
+
+#endif
+
+static sb_devc *last_sb; /* Last sb loaded */
+
+int sb_dsp_command(sb_devc * devc, unsigned char val)
+{
+ int i;
+ unsigned long limit;
+
+ limit = jiffies + HZ / 10; /* Timeout */
+
+ /*
+ * Note! the i<500000 is an emergency exit. The sb_dsp_command() is sometimes
+ * called while interrupts are disabled. This means that the timer is
+ * disabled also. However the timeout situation is a abnormal condition.
+ * Normally the DSP should be ready to accept commands after just couple of
+ * loops.
+ */
+
+ for (i = 0; i < 500000 && (limit-jiffies)>0; i++)
+ {
+ if ((inb(DSP_STATUS) & 0x80) == 0)
+ {
+ outb((val), DSP_COMMAND);
+ return 1;
+ }
+ }
+ printk(KERN_WARNING "Sound Blaster: DSP command(%x) timeout.\n", val);
+ return 0;
+}
+
+int sb_dsp_get_byte(sb_devc * devc)
+{
+ int i;
+
+ for (i = 1000; i; i--)
+ {
+ if (inb(DSP_DATA_AVAIL) & 0x80)
+ return inb(DSP_READ);
+ }
+ return 0xffff;
+}
+
+static void sb_intr (sb_devc *devc)
+{
+ int status;
+ unsigned char src = 0xff;
+
+ if (devc->model == MDL_SB16)
+ {
+ src = sb_getmixer(devc, IRQ_STAT); /* Interrupt source register */
+
+ if (src & 4) /* MPU401 interrupt */
+ if(devc->midi_irq_cookie)
+ uart401intr(devc->irq, devc->midi_irq_cookie);
+
+ if (!(src & 3))
+ return; /* Not a DSP interrupt */
+ }
+ if (devc->intr_active && (!devc->fullduplex || (src & 0x01)))
+ {
+ switch (devc->irq_mode)
+ {
+ case IMODE_OUTPUT:
+ DMAbuf_outputintr(devc->dev, 1);
+ break;
+
+ case IMODE_INPUT:
+ DMAbuf_inputintr(devc->dev);
+ break;
+
+ case IMODE_INIT:
+ break;
+
+ case IMODE_MIDI:
+ sb_midi_interrupt(devc);
+ break;
+
+ default:
+ /* printk(KERN_WARNING "Sound Blaster: Unexpected interrupt\n"); */
+ ;
+ }
+ }
+ else if (devc->intr_active_16 && (src & 0x02))
+ {
+ switch (devc->irq_mode_16)
+ {
+ case IMODE_OUTPUT:
+ DMAbuf_outputintr(devc->dev, 1);
+ break;
+
+ case IMODE_INPUT:
+ DMAbuf_inputintr(devc->dev);
+ break;
+
+ case IMODE_INIT:
+ break;
+
+ default:
+ /* printk(KERN_WARNING "Sound Blaster: Unexpected interrupt\n"); */
+ ;
+ }
+ }
+ /*
+ * Acknowledge interrupts
+ */
+
+ if (src & 0x01)
+ status = inb(DSP_DATA_AVAIL);
+
+ if (devc->model == MDL_SB16 && src & 0x02)
+ status = inb(DSP_DATA_AVL16);
+}
+
+static void pci_intr(sb_devc *devc)
+{
+ int src = inb(devc->pcibase+0x1A);
+ src&=3;
+ if(src)
+ sb_intr(devc);
+}
+
+static irqreturn_t sbintr(int irq, void *dev_id)
+{
+ sb_devc *devc = dev_id;
+
+ devc->irq_ok = 1;
+
+ switch (devc->model) {
+ case MDL_ESSPCI:
+ pci_intr (devc);
+ break;
+
+ case MDL_ESS:
+ ess_intr (devc);
+ break;
+ default:
+ sb_intr (devc);
+ break;
+ }
+ return IRQ_HANDLED;
+}
+
+int sb_dsp_reset(sb_devc * devc)
+{
+ int loopc;
+
+ DEB(printk("Entered sb_dsp_reset()\n"));
+
+ if (devc->model == MDL_ESS) return ess_dsp_reset (devc);
+
+ /* This is only for non-ESS chips */
+
+ outb(1, DSP_RESET);
+
+ udelay(10);
+ outb(0, DSP_RESET);
+ udelay(30);
+
+ for (loopc = 0; loopc < 1000 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++);
+
+ if (inb(DSP_READ) != 0xAA)
+ {
+ DDB(printk("sb: No response to RESET\n"));
+ return 0; /* Sorry */
+ }
+
+ DEB(printk("sb_dsp_reset() OK\n"));
+
+ return 1;
+}
+
+static void dsp_get_vers(sb_devc * devc)
+{
+ int i;
+
+ unsigned long flags;
+
+ DDB(printk("Entered dsp_get_vers()\n"));
+ spin_lock_irqsave(&devc->lock, flags);
+ devc->major = devc->minor = 0;
+ sb_dsp_command(devc, 0xe1); /* Get version */
+
+ for (i = 100000; i; i--)
+ {
+ if (inb(DSP_DATA_AVAIL) & 0x80)
+ {
+ if (devc->major == 0)
+ devc->major = inb(DSP_READ);
+ else
+ {
+ devc->minor = inb(DSP_READ);
+ break;
+ }
+ }
+ }
+ spin_unlock_irqrestore(&devc->lock, flags);
+ DDB(printk("DSP version %d.%02d\n", devc->major, devc->minor));
+}
+
+static int sb16_set_dma_hw(sb_devc * devc)
+{
+ int bits;
+
+ if (devc->dma8 != 0 && devc->dma8 != 1 && devc->dma8 != 3)
+ {
+ printk(KERN_ERR "SB16: Invalid 8 bit DMA (%d)\n", devc->dma8);
+ return 0;
+ }
+ bits = (1 << devc->dma8);
+
+ if (devc->dma16 >= 5 && devc->dma16 <= 7)
+ bits |= (1 << devc->dma16);
+
+ sb_setmixer(devc, DMA_NR, bits);
+ return 1;
+}
+
+static void sb16_set_mpu_port(sb_devc * devc, struct address_info *hw_config)
+{
+ /*
+ * This routine initializes new MIDI port setup register of SB Vibra (CT2502).
+ */
+ unsigned char bits = sb_getmixer(devc, 0x84) & ~0x06;
+
+ switch (hw_config->io_base)
+ {
+ case 0x300:
+ sb_setmixer(devc, 0x84, bits | 0x04);
+ break;
+
+ case 0x330:
+ sb_setmixer(devc, 0x84, bits | 0x00);
+ break;
+
+ default:
+ sb_setmixer(devc, 0x84, bits | 0x02); /* Disable MPU */
+ printk(KERN_ERR "SB16: Invalid MIDI I/O port %x\n", hw_config->io_base);
+ }
+}
+
+static int sb16_set_irq_hw(sb_devc * devc, int level)
+{
+ int ival;
+
+ switch (level)
+ {
+ case 5:
+ ival = 2;
+ break;
+ case 7:
+ ival = 4;
+ break;
+ case 9:
+ ival = 1;
+ break;
+ case 10:
+ ival = 8;
+ break;
+ default:
+ printk(KERN_ERR "SB16: Invalid IRQ%d\n", level);
+ return 0;
+ }
+ sb_setmixer(devc, IRQ_NR, ival);
+ return 1;
+}
+
+static void relocate_Jazz16(sb_devc * devc, struct address_info *hw_config)
+{
+ unsigned char bits = 0;
+ unsigned long flags;
+
+ if (jazz16_base != 0 && jazz16_base != hw_config->io_base)
+ return;
+
+ switch (hw_config->io_base)
+ {
+ case 0x220:
+ bits = 1;
+ break;
+ case 0x240:
+ bits = 2;
+ break;
+ case 0x260:
+ bits = 3;
+ break;
+ default:
+ return;
+ }
+ bits = jazz16_bits = bits << 5;
+ jazz16_base = hw_config->io_base;
+
+ /*
+ * Magic wake up sequence by writing to 0x201 (aka Joystick port)
+ */
+ spin_lock_irqsave(&jazz16_lock, flags);
+ outb((0xAF), 0x201);
+ outb((0x50), 0x201);
+ outb((bits), 0x201);
+ spin_unlock_irqrestore(&jazz16_lock, flags);
+}
+
+static int init_Jazz16(sb_devc * devc, struct address_info *hw_config)
+{
+ char name[100];
+ /*
+ * First try to check that the card has Jazz16 chip. It identifies itself
+ * by returning 0x12 as response to DSP command 0xfa.
+ */
+
+ if (!sb_dsp_command(devc, 0xfa))
+ return 0;
+
+ if (sb_dsp_get_byte(devc) != 0x12)
+ return 0;
+
+ /*
+ * OK so far. Now configure the IRQ and DMA channel used by the card.
+ */
+ if (hw_config->irq < 1 || hw_config->irq > 15 || jazz_irq_bits[hw_config->irq] == 0)
+ {
+ printk(KERN_ERR "Jazz16: Invalid interrupt (IRQ%d)\n", hw_config->irq);
+ return 0;
+ }
+ if (hw_config->dma < 0 || hw_config->dma > 3 || jazz_dma_bits[hw_config->dma] == 0)
+ {
+ printk(KERN_ERR "Jazz16: Invalid 8 bit DMA (DMA%d)\n", hw_config->dma);
+ return 0;
+ }
+ if (hw_config->dma2 < 0)
+ {
+ printk(KERN_ERR "Jazz16: No 16 bit DMA channel defined\n");
+ return 0;
+ }
+ if (hw_config->dma2 < 5 || hw_config->dma2 > 7 || jazz_dma_bits[hw_config->dma2] == 0)
+ {
+ printk(KERN_ERR "Jazz16: Invalid 16 bit DMA (DMA%d)\n", hw_config->dma2);
+ return 0;
+ }
+ devc->dma16 = hw_config->dma2;
+
+ if (!sb_dsp_command(devc, 0xfb))
+ return 0;
+
+ if (!sb_dsp_command(devc, jazz_dma_bits[hw_config->dma] |
+ (jazz_dma_bits[hw_config->dma2] << 4)))
+ return 0;
+
+ if (!sb_dsp_command(devc, jazz_irq_bits[hw_config->irq]))
+ return 0;
+
+ /*
+ * Now we have configured a standard Jazz16 device.
+ */
+ devc->model = MDL_JAZZ;
+ strcpy(name, "Jazz16");
+
+ hw_config->name = "Jazz16";
+ devc->caps |= SB_NO_MIDI;
+ return 1;
+}
+
+static void relocate_ess1688(sb_devc * devc)
+{
+ unsigned char bits;
+
+ switch (devc->base)
+ {
+ case 0x220:
+ bits = 0x04;
+ break;
+ case 0x230:
+ bits = 0x05;
+ break;
+ case 0x240:
+ bits = 0x06;
+ break;
+ case 0x250:
+ bits = 0x07;
+ break;
+ default:
+ return; /* Wrong port */
+ }
+
+ DDB(printk("Doing ESS1688 address selection\n"));
+
+ /*
+ * ES1688 supports two alternative ways for software address config.
+ * First try the so called Read-Sequence-Key method.
+ */
+
+ /* Reset the sequence logic */
+ inb(0x229);
+ inb(0x229);
+ inb(0x229);
+
+ /* Perform the read sequence */
+ inb(0x22b);
+ inb(0x229);
+ inb(0x22b);
+ inb(0x229);
+ inb(0x229);
+ inb(0x22b);
+ inb(0x229);
+
+ /* Select the base address by reading from it. Then probe using the port. */
+ inb(devc->base);
+ if (sb_dsp_reset(devc)) /* Bingo */
+ return;
+
+#if 0 /* This causes system lockups (Nokia 386/25 at least) */
+ /*
+ * The last resort is the system control register method.
+ */
+
+ outb((0x00), 0xfb); /* 0xFB is the unlock register */
+ outb((0x00), 0xe0); /* Select index 0 */
+ outb((bits), 0xe1); /* Write the config bits */
+ outb((0x00), 0xf9); /* 0xFB is the lock register */
+#endif
+}
+
+int sb_dsp_detect(struct address_info *hw_config, int pci, int pciio, struct sb_module_options *sbmo)
+{
+ sb_devc sb_info;
+ sb_devc *devc = &sb_info;
+
+ memset((char *) &sb_info, 0, sizeof(sb_info)); /* Zero everything */
+
+ /* Copy module options in place */
+ if(sbmo) memcpy(&devc->sbmo, sbmo, sizeof(struct sb_module_options));
+
+ sb_info.my_mididev = -1;
+ sb_info.my_mixerdev = -1;
+ sb_info.dev = -1;
+
+ /*
+ * Initialize variables
+ */
+
+ DDB(printk("sb_dsp_detect(%x) entered\n", hw_config->io_base));
+
+ spin_lock_init(&devc->lock);
+ devc->type = hw_config->card_subtype;
+
+ devc->base = hw_config->io_base;
+ devc->irq = hw_config->irq;
+ devc->dma8 = hw_config->dma;
+
+ devc->dma16 = -1;
+ devc->pcibase = pciio;
+
+ if(pci == SB_PCI_ESSMAESTRO)
+ {
+ devc->model = MDL_ESSPCI;
+ devc->caps |= SB_PCI_IRQ;
+ hw_config->driver_use_1 |= SB_PCI_IRQ;
+ hw_config->card_subtype = MDL_ESSPCI;
+ }
+
+ if(pci == SB_PCI_YAMAHA)
+ {
+ devc->model = MDL_YMPCI;
+ devc->caps |= SB_PCI_IRQ;
+ hw_config->driver_use_1 |= SB_PCI_IRQ;
+ hw_config->card_subtype = MDL_YMPCI;
+
+ printk("Yamaha PCI mode.\n");
+ }
+
+ if (devc->sbmo.acer)
+ {
+ unsigned long flags;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ inb(devc->base + 0x09);
+ inb(devc->base + 0x09);
+ inb(devc->base + 0x09);
+ inb(devc->base + 0x0b);
+ inb(devc->base + 0x09);
+ inb(devc->base + 0x0b);
+ inb(devc->base + 0x09);
+ inb(devc->base + 0x09);
+ inb(devc->base + 0x0b);
+ inb(devc->base + 0x09);
+ inb(devc->base + 0x00);
+ spin_unlock_irqrestore(&devc->lock, flags);
+ }
+ /*
+ * Detect the device
+ */
+
+ if (sb_dsp_reset(devc))
+ dsp_get_vers(devc);
+ else
+ devc->major = 0;
+
+ if (devc->type == 0 || devc->type == MDL_JAZZ || devc->type == MDL_SMW)
+ if (devc->major == 0 || (devc->major == 3 && devc->minor == 1))
+ relocate_Jazz16(devc, hw_config);
+
+ if (devc->major == 0 && (devc->type == MDL_ESS || devc->type == 0))
+ relocate_ess1688(devc);
+
+ if (!sb_dsp_reset(devc))
+ {
+ DDB(printk("SB reset failed\n"));
+#ifdef MODULE
+ printk(KERN_INFO "sb: dsp reset failed.\n");
+#endif
+ return 0;
+ }
+ if (devc->major == 0)
+ dsp_get_vers(devc);
+
+ if (devc->major == 3 && devc->minor == 1)
+ {
+ if (devc->type == MDL_AZTECH) /* SG Washington? */
+ {
+ if (sb_dsp_command(devc, 0x09))
+ if (sb_dsp_command(devc, 0x00)) /* Enter WSS mode */
+ {
+ int i;
+
+ /* Have some delay */
+ for (i = 0; i < 10000; i++)
+ inb(DSP_DATA_AVAIL);
+ devc->caps = SB_NO_AUDIO | SB_NO_MIDI; /* Mixer only */
+ devc->model = MDL_AZTECH;
+ }
+ }
+ }
+
+ if(devc->type == MDL_ESSPCI)
+ devc->model = MDL_ESSPCI;
+
+ if(devc->type == MDL_YMPCI)
+ {
+ printk("YMPCI selected\n");
+ devc->model = MDL_YMPCI;
+ }
+
+ /*
+ * Save device information for sb_dsp_init()
+ */
+
+
+ detected_devc = kmalloc(sizeof(sb_devc), GFP_KERNEL);
+ if (detected_devc == NULL)
+ {
+ printk(KERN_ERR "sb: Can't allocate memory for device information\n");
+ return 0;
+ }
+ memcpy(detected_devc, devc, sizeof(sb_devc));
+ MDB(printk(KERN_INFO "SB %d.%02d detected OK (%x)\n", devc->major, devc->minor, hw_config->io_base));
+ return 1;
+}
+
+int sb_dsp_init(struct address_info *hw_config, struct module *owner)
+{
+ sb_devc *devc;
+ char name[100];
+ extern int sb_be_quiet;
+ int mixer22, mixer30;
+
+/*
+ * Check if we had detected a SB device earlier
+ */
+ DDB(printk("sb_dsp_init(%x) entered\n", hw_config->io_base));
+ name[0] = 0;
+
+ if (detected_devc == NULL)
+ {
+ MDB(printk("No detected device\n"));
+ return 0;
+ }
+ devc = detected_devc;
+ detected_devc = NULL;
+
+ if (devc->base != hw_config->io_base)
+ {
+ DDB(printk("I/O port mismatch\n"));
+ release_region(devc->base, 16);
+ return 0;
+ }
+ /*
+ * Now continue initialization of the device
+ */
+
+ devc->caps = hw_config->driver_use_1;
+
+ if (!((devc->caps & SB_NO_AUDIO) && (devc->caps & SB_NO_MIDI)) && hw_config->irq > 0)
+ { /* IRQ setup */
+
+ /*
+ * ESS PCI cards do shared PCI IRQ stuff. Since they
+ * will get shared PCI irq lines we must cope.
+ */
+
+ int i=(devc->caps&SB_PCI_IRQ)?IRQF_SHARED:0;
+
+ if (request_irq(hw_config->irq, sbintr, i, "soundblaster", devc) < 0)
+ {
+ printk(KERN_ERR "SB: Can't allocate IRQ%d\n", hw_config->irq);
+ release_region(devc->base, 16);
+ return 0;
+ }
+ devc->irq_ok = 0;
+
+ if (devc->major == 4)
+ if (!sb16_set_irq_hw(devc, devc->irq)) /* Unsupported IRQ */
+ {
+ free_irq(devc->irq, devc);
+ release_region(devc->base, 16);
+ return 0;
+ }
+ if ((devc->type == 0 || devc->type == MDL_ESS) &&
+ devc->major == 3 && devc->minor == 1)
+ { /* Handle various chipsets which claim they are SB Pro compatible */
+ if ((devc->type != 0 && devc->type != MDL_ESS) ||
+ !ess_init(devc, hw_config))
+ {
+ if ((devc->type != 0 && devc->type != MDL_JAZZ &&
+ devc->type != MDL_SMW) || !init_Jazz16(devc, hw_config))
+ {
+ DDB(printk("This is a genuine SB Pro\n"));
+ }
+ }
+ }
+ if (devc->major == 4 && devc->minor <= 11 ) /* Won't work */
+ devc->irq_ok = 1;
+ else
+ {
+ int n;
+
+ for (n = 0; n < 3 && devc->irq_ok == 0; n++)
+ {
+ if (sb_dsp_command(devc, 0xf2)) /* Cause interrupt immediately */
+ {
+ int i;
+
+ for (i = 0; !devc->irq_ok && i < 10000; i++);
+ }
+ }
+ if (!devc->irq_ok)
+ printk(KERN_WARNING "sb: Interrupt test on IRQ%d failed - Probable IRQ conflict\n", devc->irq);
+ else
+ {
+ DDB(printk("IRQ test OK (IRQ%d)\n", devc->irq));
+ }
+ }
+ } /* IRQ setup */
+
+ last_sb = devc;
+
+ switch (devc->major)
+ {
+ case 1: /* SB 1.0 or 1.5 */
+ devc->model = hw_config->card_subtype = MDL_SB1;
+ break;
+
+ case 2: /* SB 2.x */
+ if (devc->minor == 0)
+ devc->model = hw_config->card_subtype = MDL_SB2;
+ else
+ devc->model = hw_config->card_subtype = MDL_SB201;
+ break;
+
+ case 3: /* SB Pro and most clones */
+ switch (devc->model) {
+ case 0:
+ devc->model = hw_config->card_subtype = MDL_SBPRO;
+ if (hw_config->name == NULL)
+ hw_config->name = "Sound Blaster Pro (8 BIT ONLY)";
+ break;
+ case MDL_ESS:
+ ess_dsp_init(devc, hw_config);
+ break;
+ }
+ break;
+
+ case 4:
+ devc->model = hw_config->card_subtype = MDL_SB16;
+ /*
+ * ALS007 and ALS100 return DSP version 4.2 and have 2 post-reset !=0
+ * registers at 0x3c and 0x4c (output ctrl registers on ALS007) whereas
+ * a "standard" SB16 doesn't have a register at 0x4c. ALS100 actively
+ * updates register 0x22 whenever 0x30 changes, as per the SB16 spec.
+ * Since ALS007 doesn't, this can be used to differentiate the 2 cards.
+ */
+ if ((devc->minor == 2) && sb_getmixer(devc,0x3c) && sb_getmixer(devc,0x4c))
+ {
+ mixer30 = sb_getmixer(devc,0x30);
+ sb_setmixer(devc,0x22,(mixer22=sb_getmixer(devc,0x22)) & 0x0f);
+ sb_setmixer(devc,0x30,0xff);
+ /* ALS100 will force 0x30 to 0xf8 like SB16; ALS007 will allow 0xff. */
+ /* Register 0x22 & 0xf0 on ALS100 == 0xf0; on ALS007 it == 0x10. */
+ if ((sb_getmixer(devc,0x30) != 0xff) || ((sb_getmixer(devc,0x22) & 0xf0) != 0x10))
+ {
+ devc->submodel = SUBMDL_ALS100;
+ if (hw_config->name == NULL)
+ hw_config->name = "Sound Blaster 16 (ALS-100)";
+ }
+ else
+ {
+ sb_setmixer(devc,0x3c,0x1f); /* Enable all inputs */
+ sb_setmixer(devc,0x4c,0x1f);
+ sb_setmixer(devc,0x22,mixer22); /* Restore 0x22 to original value */
+ devc->submodel = SUBMDL_ALS007;
+ if (hw_config->name == NULL)
+ hw_config->name = "Sound Blaster 16 (ALS-007)";
+ }
+ sb_setmixer(devc,0x30,mixer30);
+ }
+ else if (hw_config->name == NULL)
+ hw_config->name = "Sound Blaster 16";
+
+ if (hw_config->dma2 == -1)
+ devc->dma16 = devc->dma8;
+ else if (hw_config->dma2 < 5 || hw_config->dma2 > 7)
+ {
+ printk(KERN_WARNING "SB16: Bad or missing 16 bit DMA channel\n");
+ devc->dma16 = devc->dma8;
+ }
+ else
+ devc->dma16 = hw_config->dma2;
+
+ if(!sb16_set_dma_hw(devc)) {
+ free_irq(devc->irq, devc);
+ release_region(hw_config->io_base, 16);
+ return 0;
+ }
+
+ devc->caps |= SB_NO_MIDI;
+ }
+
+ if (!(devc->caps & SB_NO_MIXER))
+ if (devc->major == 3 || devc->major == 4)
+ sb_mixer_init(devc, owner);
+
+ if (!(devc->caps & SB_NO_MIDI))
+ sb_dsp_midi_init(devc, owner);
+
+ if (hw_config->name == NULL)
+ hw_config->name = "Sound Blaster (8 BIT/MONO ONLY)";
+
+ sprintf(name, "%s (%d.%02d)", hw_config->name, devc->major, devc->minor);
+ conf_printf(name, hw_config);
+
+ /*
+ * Assuming that a sound card is Sound Blaster (compatible) is the most common
+ * configuration error and the mother of all problems. Usually sound cards
+ * emulate SB Pro but in addition they have a 16 bit native mode which should be
+ * used in Unix. See Readme.cards for more information about configuring OSS/Free
+ * properly.
+ */
+ if (devc->model <= MDL_SBPRO)
+ {
+ if (devc->major == 3 && devc->minor != 1) /* "True" SB Pro should have v3.1 (rare ones may have 3.2). */
+ {
+ printk(KERN_INFO "This sound card may not be fully Sound Blaster Pro compatible.\n");
+ printk(KERN_INFO "In many cases there is another way to configure OSS so that\n");
+ printk(KERN_INFO "it works properly with OSS (for example in 16 bit mode).\n");
+ printk(KERN_INFO "Please ignore this message if you _really_ have a SB Pro.\n");
+ }
+ else if (!sb_be_quiet && devc->model == MDL_SBPRO)
+ {
+ printk(KERN_INFO "SB DSP version is just %d.%02d which means that your card is\n", devc->major, devc->minor);
+ printk(KERN_INFO "several years old (8 bit only device) or alternatively the sound driver\n");
+ printk(KERN_INFO "is incorrectly configured.\n");
+ }
+ }
+ hw_config->card_subtype = devc->model;
+ hw_config->slots[0]=devc->dev;
+ last_devc = devc; /* For SB MPU detection */
+
+ if (!(devc->caps & SB_NO_AUDIO) && devc->dma8 >= 0)
+ {
+ if (sound_alloc_dma(devc->dma8, "SoundBlaster8"))
+ {
+ printk(KERN_WARNING "Sound Blaster: Can't allocate 8 bit DMA channel %d\n", devc->dma8);
+ }
+ if (devc->dma16 >= 0 && devc->dma16 != devc->dma8)
+ {
+ if (sound_alloc_dma(devc->dma16, "SoundBlaster16"))
+ printk(KERN_WARNING "Sound Blaster: can't allocate 16 bit DMA channel %d.\n", devc->dma16);
+ }
+ sb_audio_init(devc, name, owner);
+ hw_config->slots[0]=devc->dev;
+ }
+ else
+ {
+ MDB(printk("Sound Blaster: no audio devices found.\n"));
+ }
+ return 1;
+}
+
+/* if (sbmpu) below we allow mpu401 to manage the midi devs
+ otherwise we have to unload them. (Andrzej Krzysztofowicz) */
+
+void sb_dsp_unload(struct address_info *hw_config, int sbmpu)
+{
+ sb_devc *devc;
+
+ devc = audio_devs[hw_config->slots[0]]->devc;
+
+ if (devc && devc->base == hw_config->io_base)
+ {
+ if ((devc->model & MDL_ESS) && devc->pcibase)
+ release_region(devc->pcibase, 8);
+
+ release_region(devc->base, 16);
+
+ if (!(devc->caps & SB_NO_AUDIO))
+ {
+ sound_free_dma(devc->dma8);
+ if (devc->dma16 >= 0)
+ sound_free_dma(devc->dma16);
+ }
+ if (!(devc->caps & SB_NO_AUDIO && devc->caps & SB_NO_MIDI))
+ {
+ if (devc->irq > 0)
+ free_irq(devc->irq, devc);
+
+ sb_mixer_unload(devc);
+ /* We don't have to do this bit any more the UART401 is its own
+ master -- Krzysztof Halasa */
+ /* But we have to do it, if UART401 is not detected */
+ if (!sbmpu)
+ sound_unload_mididev(devc->my_mididev);
+ sound_unload_audiodev(devc->dev);
+ }
+ kfree(devc);
+ }
+ else
+ release_region(hw_config->io_base, 16);
+
+ kfree(detected_devc);
+}
+
+/*
+ * Mixer access routines
+ *
+ * ES1887 modifications: some mixer registers reside in the
+ * range above 0xa0. These must be accessed in another way.
+ */
+
+void sb_setmixer(sb_devc * devc, unsigned int port, unsigned int value)
+{
+ unsigned long flags;
+
+ if (devc->model == MDL_ESS) {
+ ess_setmixer (devc, port, value);
+ return;
+ }
+
+ spin_lock_irqsave(&devc->lock, flags);
+
+ outb(((unsigned char) (port & 0xff)), MIXER_ADDR);
+ udelay(20);
+ outb(((unsigned char) (value & 0xff)), MIXER_DATA);
+ udelay(20);
+
+ spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+unsigned int sb_getmixer(sb_devc * devc, unsigned int port)
+{
+ unsigned int val;
+ unsigned long flags;
+
+ if (devc->model == MDL_ESS) return ess_getmixer (devc, port);
+
+ spin_lock_irqsave(&devc->lock, flags);
+
+ outb(((unsigned char) (port & 0xff)), MIXER_ADDR);
+ udelay(20);
+ val = inb(MIXER_DATA);
+ udelay(20);
+
+ spin_unlock_irqrestore(&devc->lock, flags);
+
+ return val;
+}
+
+void sb_chgmixer
+ (sb_devc * devc, unsigned int reg, unsigned int mask, unsigned int val)
+{
+ int value;
+
+ value = sb_getmixer(devc, reg);
+ value = (value & ~mask) | (val & mask);
+ sb_setmixer(devc, reg, value);
+}
+
+/*
+ * MPU401 MIDI initialization.
+ */
+
+static void smw_putmem(sb_devc * devc, int base, int addr, unsigned char val)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&jazz16_lock, flags); /* NOT the SB card? */
+
+ outb((addr & 0xff), base + 1); /* Low address bits */
+ outb((addr >> 8), base + 2); /* High address bits */
+ outb((val), base); /* Data */
+
+ spin_unlock_irqrestore(&jazz16_lock, flags);
+}
+
+static unsigned char smw_getmem(sb_devc * devc, int base, int addr)
+{
+ unsigned long flags;
+ unsigned char val;
+
+ spin_lock_irqsave(&jazz16_lock, flags); /* NOT the SB card? */
+
+ outb((addr & 0xff), base + 1); /* Low address bits */
+ outb((addr >> 8), base + 2); /* High address bits */
+ val = inb(base); /* Data */
+
+ spin_unlock_irqrestore(&jazz16_lock, flags);
+ return val;
+}
+
+static int smw_midi_init(sb_devc * devc, struct address_info *hw_config)
+{
+ int mpu_base = hw_config->io_base;
+ int mp_base = mpu_base + 4; /* Microcontroller base */
+ int i;
+ unsigned char control;
+
+
+ /*
+ * Reset the microcontroller so that the RAM can be accessed
+ */
+
+ control = inb(mpu_base + 7);
+ outb((control | 3), mpu_base + 7); /* Set last two bits to 1 (?) */
+ outb(((control & 0xfe) | 2), mpu_base + 7); /* xxxxxxx0 resets the mc */
+
+ mdelay(3); /* Wait at least 1ms */
+
+ outb((control & 0xfc), mpu_base + 7); /* xxxxxx00 enables RAM */
+
+ /*
+ * Detect microcontroller by probing the 8k RAM area
+ */
+ smw_putmem(devc, mp_base, 0, 0x00);
+ smw_putmem(devc, mp_base, 1, 0xff);
+ udelay(10);
+
+ if (smw_getmem(devc, mp_base, 0) != 0x00 || smw_getmem(devc, mp_base, 1) != 0xff)
+ {
+ DDB(printk("SM Wave: No microcontroller RAM detected (%02x, %02x)\n", smw_getmem(devc, mp_base, 0), smw_getmem(devc, mp_base, 1)));
+ return 0; /* No RAM */
+ }
+ /*
+ * There is RAM so assume it's really a SM Wave
+ */
+
+ devc->model = MDL_SMW;
+ smw_mixer_init(devc);
+
+#ifdef MODULE
+ if (!smw_ucode)
+ {
+ smw_ucodeLen = mod_firmware_load("/etc/sound/midi0001.bin", (void *) &smw_ucode);
+ smw_free = smw_ucode;
+ }
+#endif
+ if (smw_ucodeLen > 0)
+ {
+ if (smw_ucodeLen != 8192)
+ {
+ printk(KERN_ERR "SM Wave: Invalid microcode (MIDI0001.BIN) length\n");
+ return 1;
+ }
+ /*
+ * Download microcode
+ */
+
+ for (i = 0; i < 8192; i++)
+ smw_putmem(devc, mp_base, i, smw_ucode[i]);
+
+ /*
+ * Verify microcode
+ */
+
+ for (i = 0; i < 8192; i++)
+ if (smw_getmem(devc, mp_base, i) != smw_ucode[i])
+ {
+ printk(KERN_ERR "SM Wave: Microcode verification failed\n");
+ return 0;
+ }
+ }
+ control = 0;
+#ifdef SMW_SCSI_IRQ
+ /*
+ * Set the SCSI interrupt (IRQ2/9, IRQ3 or IRQ10). The SCSI interrupt
+ * is disabled by default.
+ *
+ * FIXME - make this a module option
+ *
+ * BTW the Zilog 5380 SCSI controller is located at MPU base + 0x10.
+ */
+ {
+ static unsigned char scsi_irq_bits[] = {
+ 0, 0, 3, 1, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0
+ };
+ control |= scsi_irq_bits[SMW_SCSI_IRQ] << 6;
+ }
+#endif
+
+#ifdef SMW_OPL4_ENABLE
+ /*
+ * Make the OPL4 chip visible on the PC bus at 0x380.
+ *
+ * There is no need to enable this feature since this driver
+ * doesn't support OPL4 yet. Also there is no RAM in SM Wave so
+ * enabling OPL4 is pretty useless.
+ */
+ control |= 0x10; /* Uses IRQ12 if bit 0x20 == 0 */
+ /* control |= 0x20; Uncomment this if you want to use IRQ7 */
+#endif
+ outb((control | 0x03), mpu_base + 7); /* xxxxxx11 restarts */
+ hw_config->name = "SoundMan Wave";
+ return 1;
+}
+
+static int init_Jazz16_midi(sb_devc * devc, struct address_info *hw_config)
+{
+ int mpu_base = hw_config->io_base;
+ int sb_base = devc->base;
+ int irq = hw_config->irq;
+
+ unsigned char bits = 0;
+ unsigned long flags;
+
+ if (irq < 0)
+ irq *= -1;
+
+ if (irq < 1 || irq > 15 ||
+ jazz_irq_bits[irq] == 0)
+ {
+ printk(KERN_ERR "Jazz16: Invalid MIDI interrupt (IRQ%d)\n", irq);
+ return 0;
+ }
+ switch (sb_base)
+ {
+ case 0x220:
+ bits = 1;
+ break;
+ case 0x240:
+ bits = 2;
+ break;
+ case 0x260:
+ bits = 3;
+ break;
+ default:
+ return 0;
+ }
+ bits = jazz16_bits = bits << 5;
+ switch (mpu_base)
+ {
+ case 0x310:
+ bits |= 1;
+ break;
+ case 0x320:
+ bits |= 2;
+ break;
+ case 0x330:
+ bits |= 3;
+ break;
+ default:
+ printk(KERN_ERR "Jazz16: Invalid MIDI I/O port %x\n", mpu_base);
+ return 0;
+ }
+ /*
+ * Magic wake up sequence by writing to 0x201 (aka Joystick port)
+ */
+ spin_lock_irqsave(&jazz16_lock, flags);
+ outb(0xAF, 0x201);
+ outb(0x50, 0x201);
+ outb(bits, 0x201);
+ spin_unlock_irqrestore(&jazz16_lock, flags);
+
+ hw_config->name = "Jazz16";
+ smw_midi_init(devc, hw_config);
+
+ if (!sb_dsp_command(devc, 0xfb))
+ return 0;
+
+ if (!sb_dsp_command(devc, jazz_dma_bits[devc->dma8] |
+ (jazz_dma_bits[devc->dma16] << 4)))
+ return 0;
+
+ if (!sb_dsp_command(devc, jazz_irq_bits[devc->irq] |
+ (jazz_irq_bits[irq] << 4)))
+ return 0;
+
+ return 1;
+}
+
+int probe_sbmpu(struct address_info *hw_config, struct module *owner)
+{
+ sb_devc *devc = last_devc;
+ int ret;
+
+ if (last_devc == NULL)
+ return 0;
+
+ last_devc = NULL;
+
+ if (hw_config->io_base <= 0)
+ {
+ /* The real vibra16 is fine about this, but we have to go
+ wipe up after Cyrix again */
+
+ if(devc->model == MDL_SB16 && devc->minor >= 12)
+ {
+ unsigned char bits = sb_getmixer(devc, 0x84) & ~0x06;
+ sb_setmixer(devc, 0x84, bits | 0x02); /* Disable MPU */
+ }
+ return 0;
+ }
+
+#if defined(CONFIG_SOUND_MPU401)
+ if (devc->model == MDL_ESS)
+ {
+ struct resource *ports;
+ ports = request_region(hw_config->io_base, 2, "mpu401");
+ if (!ports) {
+ printk(KERN_ERR "sbmpu: I/O port conflict (%x)\n", hw_config->io_base);
+ return 0;
+ }
+ if (!ess_midi_init(devc, hw_config)) {
+ release_region(hw_config->io_base, 2);
+ return 0;
+ }
+ hw_config->name = "ESS1xxx MPU";
+ devc->midi_irq_cookie = NULL;
+ if (!probe_mpu401(hw_config, ports)) {
+ release_region(hw_config->io_base, 2);
+ return 0;
+ }
+ attach_mpu401(hw_config, owner);
+ if (last_sb->irq == -hw_config->irq)
+ last_sb->midi_irq_cookie =
+ (void *)(long) hw_config->slots[1];
+ return 1;
+ }
+#endif
+
+ switch (devc->model)
+ {
+ case MDL_SB16:
+ if (hw_config->io_base != 0x300 && hw_config->io_base != 0x330)
+ {
+ printk(KERN_ERR "SB16: Invalid MIDI port %x\n", hw_config->io_base);
+ return 0;
+ }
+ hw_config->name = "Sound Blaster 16";
+ if (hw_config->irq < 3 || hw_config->irq == devc->irq)
+ hw_config->irq = -devc->irq;
+ if (devc->minor > 12) /* What is Vibra's version??? */
+ sb16_set_mpu_port(devc, hw_config);
+ break;
+
+ case MDL_JAZZ:
+ if (hw_config->irq < 3 || hw_config->irq == devc->irq)
+ hw_config->irq = -devc->irq;
+ if (!init_Jazz16_midi(devc, hw_config))
+ return 0;
+ break;
+
+ case MDL_YMPCI:
+ hw_config->name = "Yamaha PCI Legacy";
+ printk("Yamaha PCI legacy UART401 check.\n");
+ break;
+ default:
+ return 0;
+ }
+
+ ret = probe_uart401(hw_config, owner);
+ if (ret)
+ last_sb->midi_irq_cookie=midi_devs[hw_config->slots[4]]->devc;
+ return ret;
+}
+
+void unload_sbmpu(struct address_info *hw_config)
+{
+#if defined(CONFIG_SOUND_MPU401)
+ if (!strcmp (hw_config->name, "ESS1xxx MPU")) {
+ unload_mpu401(hw_config);
+ return;
+ }
+#endif
+ unload_uart401(hw_config);
+}
+
+EXPORT_SYMBOL(sb_dsp_init);
+EXPORT_SYMBOL(sb_dsp_detect);
+EXPORT_SYMBOL(sb_dsp_unload);
+EXPORT_SYMBOL(sb_be_quiet);
+EXPORT_SYMBOL(probe_sbmpu);
+EXPORT_SYMBOL(unload_sbmpu);
+EXPORT_SYMBOL(smw_free);
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/sb_ess.c b/sound/oss/sb_ess.c
new file mode 100644
index 00000000..5c773dff
--- /dev/null
+++ b/sound/oss/sb_ess.c
@@ -0,0 +1,1831 @@
+#undef FKS_LOGGING
+#undef FKS_TEST
+
+/*
+ * tabs should be 4 spaces, in vi(m): set tabstop=4
+ *
+ * TODO: consistency speed calculations!!
+ * cleanup!
+ * ????: Did I break MIDI support?
+ *
+ * History:
+ *
+ * Rolf Fokkens (Dec 20 1998): ES188x recording level support on a per
+ * fokkensr@vertis.nl input basis.
+ * (Dec 24 1998): Recognition of ES1788, ES1887, ES1888,
+ * ES1868, ES1869 and ES1878. Could be used for
+ * specific handling in the future. All except
+ * ES1887 and ES1888 and ES688 are handled like
+ * ES1688.
+ * (Dec 27 1998): RECLEV for all (?) ES1688+ chips. ES188x now
+ * have the "Dec 20" support + RECLEV
+ * (Jan 2 1999): Preparation for Full Duplex. This means
+ * Audio 2 is now used for playback when dma16
+ * is specified. The next step would be to use
+ * Audio 1 and Audio 2 at the same time.
+ * (Jan 9 1999): Put all ESS stuff into sb_ess.[ch], this
+ * includes both the ESS stuff that has been in
+ * sb_*[ch] before I touched it and the ESS support
+ * I added later
+ * (Jan 23 1999): Full Duplex seems to work. I wrote a small
+ * test proggy which works OK. Haven't found
+ * any applications to test it though. So why did
+ * I bother to create it anyway?? :) Just for
+ * fun.
+ * (May 2 1999): I tried to be too smart by "introducing"
+ * ess_calc_best_speed (). The idea was that two
+ * dividers could be used to setup a samplerate,
+ * ess_calc_best_speed () would choose the best.
+ * This works for playback, but results in
+ * recording problems for high samplerates. I
+ * fixed this by removing ess_calc_best_speed ()
+ * and just doing what the documentation says.
+ * Andy Sloane (Jun 4 1999): Stole some code from ALSA to fix the playback
+ * andy@guildsoftware.com speed on ES1869, ES1879, ES1887, and ES1888.
+ * 1879's were previously ignored by this driver;
+ * added (untested) support for those.
+ * Cvetan Ivanov (Oct 27 1999): Fixed ess_dsp_init to call ess_set_dma_hw for
+ * zezo@inet.bg _ALL_ ESS models, not only ES1887
+ *
+ * This files contains ESS chip specifics. It's based on the existing ESS
+ * handling as it resided in sb_common.c, sb_mixer.c and sb_audio.c. This
+ * file adds features like:
+ * - Chip Identification (as shown in /proc/sound)
+ * - RECLEV support for ES1688 and later
+ * - 6 bits playback level support chips later than ES1688
+ * - Recording level support on a per-device basis for ES1887
+ * - Full-Duplex for ES1887
+ *
+ * Full duplex is enabled by specifying dma16. While the normal dma must
+ * be one of 0, 1 or 3, dma16 can be one of 0, 1, 3 or 5. DMA 5 is a 16 bit
+ * DMA channel, while the others are 8 bit..
+ *
+ * ESS detection isn't full proof (yet). If it fails an additional module
+ * parameter esstype can be specified to be one of the following:
+ * -1, 0, 688, 1688, 1868, 1869, 1788, 1887, 1888
+ * -1 means: mimic 2.0 behaviour,
+ * 0 means: auto detect.
+ * others: explicitly specify chip
+ * -1 is default, cause auto detect still doesn't work.
+ */
+
+/*
+ * About the documentation
+ *
+ * I don't know if the chips all are OK, but the documentation is buggy. 'cause
+ * I don't have all the cips myself, there's a lot I cannot verify. I'll try to
+ * keep track of my latest insights about his here. If you have additional info,
+ * please enlighten me (fokkensr@vertis.nl)!
+ *
+ * I had the impression that ES1688 also has 6 bit master volume control. The
+ * documentation about ES1888 (rev C, october '95) claims that ES1888 has
+ * the following features ES1688 doesn't have:
+ * - 6 bit master volume
+ * - Full Duplex
+ * So ES1688 apparently doesn't have 6 bit master volume control, but the
+ * ES1688 does have RECLEV control. Makes me wonder: does ES688 have it too?
+ * Without RECLEV ES688 won't be much fun I guess.
+ *
+ * From the ES1888 (rev C, october '95) documentation I got the impression
+ * that registers 0x68 to 0x6e don't exist which means: no recording volume
+ * controls. To my surprise the ES888 documentation (1/14/96) claims that
+ * ES888 does have these record mixer registers, but that ES1888 doesn't have
+ * 0x69 and 0x6b. So the rest should be there.
+ *
+ * I'm trying to get ES1887 Full Duplex. Audio 2 is playback only, while Audio 2
+ * is both record and playback. I think I should use Audio 2 for all playback.
+ *
+ * The documentation is an adventure: it's close but not fully accurate. I
+ * found out that after a reset some registers are *NOT* reset, though the
+ * docs say the would be. Interesting ones are 0x7f, 0x7d and 0x7a. They are
+ * related to the Audio 2 channel. I also was surprised about the consequences
+ * of writing 0x00 to 0x7f (which should be done by reset): The ES1887 moves
+ * into ES1888 mode. This means that it claims IRQ 11, which happens to be my
+ * ISDN adapter. Needless to say it no longer worked. I now understand why
+ * after rebooting 0x7f already was 0x05, the value of my choice: the BIOS
+ * did it.
+ *
+ * Oh, and this is another trap: in ES1887 docs mixer register 0x70 is
+ * described as if it's exactly the same as register 0xa1. This is *NOT* true.
+ * The description of 0x70 in ES1869 docs is accurate however.
+ * Well, the assumption about ES1869 was wrong: register 0x70 is very much
+ * like register 0xa1, except that bit 7 is always 1, whatever you want
+ * it to be.
+ *
+ * When using audio 2 mixer register 0x72 seems te be meaningless. Only 0xa2
+ * has effect.
+ *
+ * Software reset not being able to reset all registers is great! Especially
+ * the fact that register 0x78 isn't reset is great when you wanna change back
+ * to single dma operation (simplex): audio 2 is still operational, and uses
+ * the same dma as audio 1: your ess changes into a funny echo machine.
+ *
+ * Received the news that ES1688 is detected as a ES1788. Did some thinking:
+ * the ES1887 detection scheme suggests in step 2 to try if bit 3 of register
+ * 0x64 can be changed. This is inaccurate, first I inverted the * check: "If
+ * can be modified, it's a 1688", which lead to a correct detection
+ * of my ES1887. It resulted however in bad detection of 1688 (reported by mail)
+ * and 1868 (if no PnP detection first): they result in a 1788 being detected.
+ * I don't have docs on 1688, but I do have docs on 1868: The documentation is
+ * probably inaccurate in the fact that I should check bit 2, not bit 3. This
+ * is what I do now.
+ */
+
+/*
+ * About recognition of ESS chips
+ *
+ * The distinction of ES688, ES1688, ES1788, ES1887 and ES1888 is described in
+ * a (preliminary ??) datasheet on ES1887. Its aim is to identify ES1887, but
+ * during detection the text claims that "this chip may be ..." when a step
+ * fails. This scheme is used to distinct between the above chips.
+ * It appears however that some PnP chips like ES1868 are recognized as ES1788
+ * by the ES1887 detection scheme. These PnP chips can be detected in another
+ * way however: ES1868, ES1869 and ES1878 can be recognized (full proof I think)
+ * by repeatedly reading mixer register 0x40. This is done by ess_identify in
+ * sb_common.c.
+ * This results in the following detection steps:
+ * - distinct between ES688 and ES1688+ (as always done in this driver)
+ * if ES688 we're ready
+ * - try to detect ES1868, ES1869 or ES1878
+ * if successful we're ready
+ * - try to detect ES1888, ES1887 or ES1788
+ * if successful we're ready
+ * - Dunno. Must be 1688. Will do in general
+ *
+ * About RECLEV support:
+ *
+ * The existing ES1688 support didn't take care of the ES1688+ recording
+ * levels very well. Whenever a device was selected (recmask) for recording
+ * its recording level was loud, and it couldn't be changed. The fact that
+ * internal register 0xb4 could take care of RECLEV, didn't work meaning until
+ * its value was restored every time the chip was reset; this reset the
+ * value of 0xb4 too. I guess that's what 4front also had (have?) trouble with.
+ *
+ * About ES1887 support:
+ *
+ * The ES1887 has separate registers to control the recording levels, for all
+ * inputs. The ES1887 specific software makes these levels the same as their
+ * corresponding playback levels, unless recmask says they aren't recorded. In
+ * the latter case the recording volumes are 0.
+ * Now recording levels of inputs can be controlled, by changing the playback
+ * levels. Furthermore several devices can be recorded together (which is not
+ * possible with the ES1688).
+ * Besides the separate recording level control for each input, the common
+ * recording level can also be controlled by RECLEV as described above.
+ *
+ * Not only ES1887 have this recording mixer. I know the following from the
+ * documentation:
+ * ES688 no
+ * ES1688 no
+ * ES1868 no
+ * ES1869 yes
+ * ES1878 no
+ * ES1879 yes
+ * ES1888 no/yes Contradicting documentation; most recent: yes
+ * ES1946 yes This is a PCI chip; not handled by this driver
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+
+#include "sound_config.h"
+#include "sb_mixer.h"
+#include "sb.h"
+
+#include "sb_ess.h"
+
+#define ESSTYPE_LIKE20 -1 /* Mimic 2.0 behaviour */
+#define ESSTYPE_DETECT 0 /* Mimic 2.0 behaviour */
+
+#define SUBMDL_ES1788 0x10 /* Subtype ES1788 for specific handling */
+#define SUBMDL_ES1868 0x11 /* Subtype ES1868 for specific handling */
+#define SUBMDL_ES1869 0x12 /* Subtype ES1869 for specific handling */
+#define SUBMDL_ES1878 0x13 /* Subtype ES1878 for specific handling */
+#define SUBMDL_ES1879 0x16 /* ES1879 was initially forgotten */
+#define SUBMDL_ES1887 0x14 /* Subtype ES1887 for specific handling */
+#define SUBMDL_ES1888 0x15 /* Subtype ES1888 for specific handling */
+
+#define SB_CAP_ES18XX_RATE 0x100
+
+#define ES1688_CLOCK1 795444 /* 128 - div */
+#define ES1688_CLOCK2 397722 /* 256 - div */
+#define ES18XX_CLOCK1 793800 /* 128 - div */
+#define ES18XX_CLOCK2 768000 /* 256 - div */
+
+#ifdef FKS_LOGGING
+static void ess_show_mixerregs (sb_devc *devc);
+#endif
+static int ess_read (sb_devc * devc, unsigned char reg);
+static int ess_write (sb_devc * devc, unsigned char reg, unsigned char data);
+static void ess_chgmixer
+ (sb_devc * devc, unsigned int reg, unsigned int mask, unsigned int val);
+
+/****************************************************************************
+ * *
+ * ESS audio *
+ * *
+ ****************************************************************************/
+
+struct ess_command {short cmd; short data;};
+
+/*
+ * Commands for initializing Audio 1 for input (record)
+ */
+static struct ess_command ess_i08m[] = /* input 8 bit mono */
+ { {0xb7, 0x51}, {0xb7, 0xd0}, {-1, 0} };
+static struct ess_command ess_i16m[] = /* input 16 bit mono */
+ { {0xb7, 0x71}, {0xb7, 0xf4}, {-1, 0} };
+static struct ess_command ess_i08s[] = /* input 8 bit stereo */
+ { {0xb7, 0x51}, {0xb7, 0x98}, {-1, 0} };
+static struct ess_command ess_i16s[] = /* input 16 bit stereo */
+ { {0xb7, 0x71}, {0xb7, 0xbc}, {-1, 0} };
+
+static struct ess_command *ess_inp_cmds[] =
+ { ess_i08m, ess_i16m, ess_i08s, ess_i16s };
+
+
+/*
+ * Commands for initializing Audio 1 for output (playback)
+ */
+static struct ess_command ess_o08m[] = /* output 8 bit mono */
+ { {0xb6, 0x80}, {0xb7, 0x51}, {0xb7, 0xd0}, {-1, 0} };
+static struct ess_command ess_o16m[] = /* output 16 bit mono */
+ { {0xb6, 0x00}, {0xb7, 0x71}, {0xb7, 0xf4}, {-1, 0} };
+static struct ess_command ess_o08s[] = /* output 8 bit stereo */
+ { {0xb6, 0x80}, {0xb7, 0x51}, {0xb7, 0x98}, {-1, 0} };
+static struct ess_command ess_o16s[] = /* output 16 bit stereo */
+ { {0xb6, 0x00}, {0xb7, 0x71}, {0xb7, 0xbc}, {-1, 0} };
+
+static struct ess_command *ess_out_cmds[] =
+ { ess_o08m, ess_o16m, ess_o08s, ess_o16s };
+
+static void ess_exec_commands
+ (sb_devc *devc, struct ess_command *cmdtab[])
+{
+ struct ess_command *cmd;
+
+ cmd = cmdtab [ ((devc->channels != 1) << 1) + (devc->bits != AFMT_U8) ];
+
+ while (cmd->cmd != -1) {
+ ess_write (devc, cmd->cmd, cmd->data);
+ cmd++;
+ }
+}
+
+static void ess_change
+ (sb_devc *devc, unsigned int reg, unsigned int mask, unsigned int val)
+{
+ int value;
+
+ value = ess_read (devc, reg);
+ value = (value & ~mask) | (val & mask);
+ ess_write (devc, reg, value);
+}
+
+static void ess_set_output_parms
+ (int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (devc->duplex) {
+ devc->trg_buf_16 = buf;
+ devc->trg_bytes_16 = nr_bytes;
+ devc->trg_intrflag_16 = intrflag;
+ devc->irq_mode_16 = IMODE_OUTPUT;
+ } else {
+ devc->trg_buf = buf;
+ devc->trg_bytes = nr_bytes;
+ devc->trg_intrflag = intrflag;
+ devc->irq_mode = IMODE_OUTPUT;
+ }
+}
+
+static void ess_set_input_parms
+ (int dev, unsigned long buf, int count, int intrflag)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ devc->trg_buf = buf;
+ devc->trg_bytes = count;
+ devc->trg_intrflag = intrflag;
+ devc->irq_mode = IMODE_INPUT;
+}
+
+static int ess_calc_div (int clock, int revert, int *speedp, int *diffp)
+{
+ int divider;
+ int speed, diff;
+ int retval;
+
+ speed = *speedp;
+ divider = (clock + speed / 2) / speed;
+ retval = revert - divider;
+ if (retval > revert - 1) {
+ retval = revert - 1;
+ divider = revert - retval;
+ }
+ /* This line is suggested. Must be wrong I think
+ *speedp = (clock + divider / 2) / divider;
+ So I chose the next one */
+
+ *speedp = clock / divider;
+ diff = speed - *speedp;
+ if (diff < 0) diff =-diff;
+ *diffp = diff;
+
+ return retval;
+}
+
+static int ess_calc_best_speed
+ (int clock1, int rev1, int clock2, int rev2, int *divp, int *speedp)
+{
+ int speed1 = *speedp, speed2 = *speedp;
+ int div1, div2;
+ int diff1, diff2;
+ int retval;
+
+ div1 = ess_calc_div (clock1, rev1, &speed1, &diff1);
+ div2 = ess_calc_div (clock2, rev2, &speed2, &diff2);
+
+ if (diff1 < diff2) {
+ *divp = div1;
+ *speedp = speed1;
+ retval = 1;
+ } else {
+ /* *divp = div2; */
+ *divp = 0x80 | div2;
+ *speedp = speed2;
+ retval = 2;
+ }
+
+ return retval;
+}
+
+/*
+ * Depending on the audiochannel ESS devices can
+ * have different clock settings. These are made consistent for duplex
+ * however.
+ * callers of ess_speed only do an audionum suggestion, which means
+ * input suggests 1, output suggests 2. This suggestion is only true
+ * however when doing duplex.
+ */
+static void ess_common_speed (sb_devc *devc, int *speedp, int *divp)
+{
+ int diff = 0, div;
+
+ if (devc->duplex) {
+ /*
+ * The 0x80 is important for the first audio channel
+ */
+ if (devc->submodel == SUBMDL_ES1888) {
+ div = 0x80 | ess_calc_div (795500, 256, speedp, &diff);
+ } else {
+ div = 0x80 | ess_calc_div (795500, 128, speedp, &diff);
+ }
+ } else if(devc->caps & SB_CAP_ES18XX_RATE) {
+ if (devc->submodel == SUBMDL_ES1888) {
+ ess_calc_best_speed(397700, 128, 795500, 256,
+ &div, speedp);
+ } else {
+ ess_calc_best_speed(ES18XX_CLOCK1, 128, ES18XX_CLOCK2, 256,
+ &div, speedp);
+ }
+ } else {
+ if (*speedp > 22000) {
+ div = 0x80 | ess_calc_div (ES1688_CLOCK1, 256, speedp, &diff);
+ } else {
+ div = 0x00 | ess_calc_div (ES1688_CLOCK2, 128, speedp, &diff);
+ }
+ }
+ *divp = div;
+}
+
+static void ess_speed (sb_devc *devc, int audionum)
+{
+ int speed;
+ int div, div2;
+
+ ess_common_speed (devc, &(devc->speed), &div);
+
+#ifdef FKS_REG_LOGGING
+printk (KERN_INFO "FKS: ess_speed (%d) b speed = %d, div=%x\n", audionum, devc->speed, div);
+#endif
+
+ /* Set filter roll-off to 90% of speed/2 */
+ speed = (devc->speed * 9) / 20;
+
+ div2 = 256 - 7160000 / (speed * 82);
+
+ if (!devc->duplex) audionum = 1;
+
+ if (audionum == 1) {
+ /* Change behaviour of register A1 *
+ sb_chg_mixer(devc, 0x71, 0x20, 0x20)
+ * For ES1869 only??? */
+ ess_write (devc, 0xa1, div);
+ ess_write (devc, 0xa2, div2);
+ } else {
+ ess_setmixer (devc, 0x70, div);
+ /*
+ * FKS: fascinating: 0x72 doesn't seem to work.
+ */
+ ess_write (devc, 0xa2, div2);
+ ess_setmixer (devc, 0x72, div2);
+ }
+}
+
+static int ess_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ ess_speed(devc, 1);
+
+ sb_dsp_command(devc, DSP_CMD_SPKOFF);
+
+ ess_write (devc, 0xb8, 0x0e); /* Auto init DMA mode */
+ ess_change (devc, 0xa8, 0x03, 3 - devc->channels); /* Mono/stereo */
+ ess_write (devc, 0xb9, 2); /* Demand mode (4 bytes/DMA request) */
+
+ ess_exec_commands (devc, ess_inp_cmds);
+
+ ess_change (devc, 0xb1, 0xf0, 0x50);
+ ess_change (devc, 0xb2, 0xf0, 0x50);
+
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static int ess_audio_prepare_for_output_audio1 (int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ sb_dsp_reset(devc);
+ ess_speed(devc, 1);
+ ess_write (devc, 0xb8, 4); /* Auto init DMA mode */
+ ess_change (devc, 0xa8, 0x03, 3 - devc->channels); /* Mono/stereo */
+ ess_write (devc, 0xb9, 2); /* Demand mode (4 bytes/request) */
+
+ ess_exec_commands (devc, ess_out_cmds);
+
+ ess_change (devc, 0xb1, 0xf0, 0x50); /* Enable DMA */
+ ess_change (devc, 0xb2, 0xf0, 0x50); /* Enable IRQ */
+
+ sb_dsp_command(devc, DSP_CMD_SPKON); /* There be sound! */
+
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static int ess_audio_prepare_for_output_audio2 (int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ unsigned char bits;
+
+/* FKS: qqq
+ sb_dsp_reset(devc);
+*/
+
+ /*
+ * Auto-Initialize:
+ * DMA mode + demand mode (8 bytes/request, yes I want it all!)
+ * But leave 16-bit DMA bit untouched!
+ */
+ ess_chgmixer (devc, 0x78, 0xd0, 0xd0);
+
+ ess_speed(devc, 2);
+
+ /* bits 4:3 on ES1887 represent recording source. Keep them! */
+ bits = ess_getmixer (devc, 0x7a) & 0x18;
+
+ /* Set stereo/mono */
+ if (devc->channels != 1) bits |= 0x02;
+
+ /* Init DACs; UNSIGNED mode for 8 bit; SIGNED mode for 16 bit */
+ if (devc->bits != AFMT_U8) bits |= 0x05; /* 16 bit */
+
+ /* Enable DMA, IRQ will be shared (hopefully)*/
+ bits |= 0x60;
+
+ ess_setmixer (devc, 0x7a, bits);
+
+ ess_mixer_reload (devc, SOUND_MIXER_PCM); /* There be sound! */
+
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static int ess_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "ess_audio_prepare_for_output: dma_out=%d,dma_in=%d\n"
+, audio_devs[dev]->dmap_out->dma, audio_devs[dev]->dmap_in->dma);
+#endif
+
+ if (devc->duplex) {
+ return ess_audio_prepare_for_output_audio2 (dev, bsize, bcount);
+ } else {
+ return ess_audio_prepare_for_output_audio1 (dev, bsize, bcount);
+ }
+}
+
+static void ess_audio_halt_xfer(int dev)
+{
+ unsigned long flags;
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ sb_dsp_reset(devc);
+ spin_unlock_irqrestore(&devc->lock, flags);
+
+ /*
+ * Audio 2 may still be operational! Creates awful sounds!
+ */
+ if (devc->duplex) ess_chgmixer(devc, 0x78, 0x03, 0x00);
+}
+
+static void ess_audio_start_input
+ (int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ int count = nr_bytes;
+ sb_devc *devc = audio_devs[dev]->devc;
+ short c = -nr_bytes;
+
+ /*
+ * Start a DMA input to the buffer pointed by dmaqtail
+ */
+
+ if (audio_devs[dev]->dmap_in->dma > 3) count >>= 1;
+ count--;
+
+ devc->irq_mode = IMODE_INPUT;
+
+ ess_write (devc, 0xa4, (unsigned char) ((unsigned short) c & 0xff));
+ ess_write (devc, 0xa5, (unsigned char) (((unsigned short) c >> 8) & 0xff));
+
+ ess_change (devc, 0xb8, 0x0f, 0x0f); /* Go */
+ devc->intr_active = 1;
+}
+
+static void ess_audio_output_block_audio1
+ (int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ int count = nr_bytes;
+ sb_devc *devc = audio_devs[dev]->devc;
+ short c = -nr_bytes;
+
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ count >>= 1;
+ count--;
+
+ devc->irq_mode = IMODE_OUTPUT;
+
+ ess_write (devc, 0xa4, (unsigned char) ((unsigned short) c & 0xff));
+ ess_write (devc, 0xa5, (unsigned char) (((unsigned short) c >> 8) & 0xff));
+
+ ess_change (devc, 0xb8, 0x05, 0x05); /* Go */
+ devc->intr_active = 1;
+}
+
+static void ess_audio_output_block_audio2
+ (int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ int count = nr_bytes;
+ sb_devc *devc = audio_devs[dev]->devc;
+ short c = -nr_bytes;
+
+ if (audio_devs[dev]->dmap_out->dma > 3) count >>= 1;
+ count--;
+
+ ess_setmixer (devc, 0x74, (unsigned char) ((unsigned short) c & 0xff));
+ ess_setmixer (devc, 0x76, (unsigned char) (((unsigned short) c >> 8) & 0xff));
+ ess_chgmixer (devc, 0x78, 0x03, 0x03); /* Go */
+
+ devc->irq_mode_16 = IMODE_OUTPUT;
+ devc->intr_active_16 = 1;
+}
+
+static void ess_audio_output_block
+ (int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (devc->duplex) {
+ ess_audio_output_block_audio2 (dev, buf, nr_bytes, intrflag);
+ } else {
+ ess_audio_output_block_audio1 (dev, buf, nr_bytes, intrflag);
+ }
+}
+
+/*
+ * FKS: the if-statements for both bits and bits_16 are quite alike.
+ * Combine this...
+ */
+static void ess_audio_trigger(int dev, int bits)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ int bits_16 = bits & devc->irq_mode_16;
+ bits &= devc->irq_mode;
+
+ if (!bits && !bits_16) {
+ /* FKS oh oh.... wrong?? for dma 16? */
+ sb_dsp_command(devc, 0xd0); /* Halt DMA */
+ }
+
+ if (bits) {
+ switch (devc->irq_mode)
+ {
+ case IMODE_INPUT:
+ ess_audio_start_input(dev, devc->trg_buf, devc->trg_bytes,
+ devc->trg_intrflag);
+ break;
+
+ case IMODE_OUTPUT:
+ ess_audio_output_block(dev, devc->trg_buf, devc->trg_bytes,
+ devc->trg_intrflag);
+ break;
+ }
+ }
+
+ if (bits_16) {
+ switch (devc->irq_mode_16) {
+ case IMODE_INPUT:
+ ess_audio_start_input(dev, devc->trg_buf_16, devc->trg_bytes_16,
+ devc->trg_intrflag_16);
+ break;
+
+ case IMODE_OUTPUT:
+ ess_audio_output_block(dev, devc->trg_buf_16, devc->trg_bytes_16,
+ devc->trg_intrflag_16);
+ break;
+ }
+ }
+
+ devc->trigger_bits = bits | bits_16;
+}
+
+static int ess_audio_set_speed(int dev, int speed)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ int minspeed, maxspeed, dummydiv;
+
+ if (speed > 0) {
+ minspeed = (devc->duplex ? 6215 : 5000 );
+ maxspeed = (devc->duplex ? 44100 : 48000);
+ if (speed < minspeed) speed = minspeed;
+ if (speed > maxspeed) speed = maxspeed;
+
+ ess_common_speed (devc, &speed, &dummydiv);
+
+ devc->speed = speed;
+ }
+ return devc->speed;
+}
+
+/*
+ * FKS: This is a one-on-one copy of sb1_audio_set_bits
+ */
+static unsigned int ess_audio_set_bits(int dev, unsigned int bits)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (bits != 0) {
+ if (bits == AFMT_U8 || bits == AFMT_S16_LE) {
+ devc->bits = bits;
+ } else {
+ devc->bits = AFMT_U8;
+ }
+ }
+
+ return devc->bits;
+}
+
+/*
+ * FKS: This is a one-on-one copy of sbpro_audio_set_channels
+ * (*) Modified it!!
+ */
+static short ess_audio_set_channels(int dev, short channels)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (channels == 1 || channels == 2) devc->channels = channels;
+
+ return devc->channels;
+}
+
+static struct audio_driver ess_audio_driver = /* ESS ES688/1688 */
+{
+ .owner = THIS_MODULE,
+ .open = sb_audio_open,
+ .close = sb_audio_close,
+ .output_block = ess_set_output_parms,
+ .start_input = ess_set_input_parms,
+ .prepare_for_input = ess_audio_prepare_for_input,
+ .prepare_for_output = ess_audio_prepare_for_output,
+ .halt_io = ess_audio_halt_xfer,
+ .trigger = ess_audio_trigger,
+ .set_speed = ess_audio_set_speed,
+ .set_bits = ess_audio_set_bits,
+ .set_channels = ess_audio_set_channels
+};
+
+/*
+ * ess_audio_init must be called from sb_audio_init
+ */
+struct audio_driver *ess_audio_init
+ (sb_devc *devc, int *audio_flags, int *format_mask)
+{
+ *audio_flags = DMA_AUTOMODE;
+ *format_mask |= AFMT_S16_LE;
+
+ if (devc->duplex) {
+ int tmp_dma;
+ /*
+ * sb_audio_init thinks dma8 is for playback and
+ * dma16 is for record. Not now! So swap them.
+ */
+ tmp_dma = devc->dma16;
+ devc->dma16 = devc->dma8;
+ devc->dma8 = tmp_dma;
+
+ *audio_flags |= DMA_DUPLEX;
+ }
+
+ return &ess_audio_driver;
+}
+
+/****************************************************************************
+ * *
+ * ESS common *
+ * *
+ ****************************************************************************/
+static void ess_handle_channel
+ (char *channel, int dev, int intr_active, unsigned char flag, int irq_mode)
+{
+ if (!intr_active || !flag) return;
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: ess_handle_channel %s irq_mode=%d\n", channel, irq_mode);
+#endif
+ switch (irq_mode) {
+ case IMODE_OUTPUT:
+ DMAbuf_outputintr (dev, 1);
+ break;
+
+ case IMODE_INPUT:
+ DMAbuf_inputintr (dev);
+ break;
+
+ case IMODE_INIT:
+ break;
+
+ default:;
+ /* printk(KERN_WARNING "ESS: Unexpected interrupt\n"); */
+ }
+}
+
+/*
+ * FKS: TODO!!! Finish this!
+ *
+ * I think midi stuff uses uart401, without interrupts.
+ * So IMODE_MIDI isn't a value for devc->irq_mode.
+ */
+void ess_intr (sb_devc *devc)
+{
+ int status;
+ unsigned char src;
+
+ if (devc->submodel == SUBMDL_ES1887) {
+ src = ess_getmixer (devc, 0x7f) >> 4;
+ } else {
+ src = 0xff;
+ }
+
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: sbintr src=%x\n",(int)src);
+#endif
+ ess_handle_channel
+ ( "Audio 1"
+ , devc->dev, devc->intr_active , src & 0x01, devc->irq_mode );
+ ess_handle_channel
+ ( "Audio 2"
+ , devc->dev, devc->intr_active_16, src & 0x02, devc->irq_mode_16);
+ /*
+ * Acknowledge interrupts
+ */
+ if (devc->submodel == SUBMDL_ES1887 && (src & 0x02)) {
+ ess_chgmixer (devc, 0x7a, 0x80, 0x00);
+ }
+
+ if (src & 0x01) {
+ status = inb(DSP_DATA_AVAIL);
+ }
+}
+
+static void ess_extended (sb_devc * devc)
+{
+ /* Enable extended mode */
+
+ sb_dsp_command(devc, 0xc6);
+}
+
+static int ess_write (sb_devc * devc, unsigned char reg, unsigned char data)
+{
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: write reg %x: %x\n", reg, data);
+#endif
+ /* Write a byte to an extended mode register of ES1688 */
+
+ if (!sb_dsp_command(devc, reg))
+ return 0;
+
+ return sb_dsp_command(devc, data);
+}
+
+static int ess_read (sb_devc * devc, unsigned char reg)
+{
+ /* Read a byte from an extended mode register of ES1688 */
+
+ /* Read register command */
+ if (!sb_dsp_command(devc, 0xc0)) return -1;
+
+ if (!sb_dsp_command(devc, reg )) return -1;
+
+ return sb_dsp_get_byte(devc);
+}
+
+int ess_dsp_reset(sb_devc * devc)
+{
+ int loopc;
+
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: ess_dsp_reset 1\n");
+ess_show_mixerregs (devc);
+#endif
+
+ DEB(printk("Entered ess_dsp_reset()\n"));
+
+ outb(3, DSP_RESET); /* Reset FIFO too */
+
+ udelay(10);
+ outb(0, DSP_RESET);
+ udelay(30);
+
+ for (loopc = 0; loopc < 1000 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++);
+
+ if (inb(DSP_READ) != 0xAA) {
+ DDB(printk("sb: No response to RESET\n"));
+ return 0; /* Sorry */
+ }
+ ess_extended (devc);
+
+ DEB(printk("sb_dsp_reset() OK\n"));
+
+#ifdef FKS_LOGGING
+printk(KERN_INFO "FKS: dsp_reset 2\n");
+ess_show_mixerregs (devc);
+#endif
+
+ return 1;
+}
+
+static int ess_irq_bits (int irq)
+{
+ switch (irq) {
+ case 2:
+ case 9:
+ return 0;
+
+ case 5:
+ return 1;
+
+ case 7:
+ return 2;
+
+ case 10:
+ return 3;
+
+ default:
+ printk(KERN_ERR "ESS1688: Invalid IRQ %d\n", irq);
+ return -1;
+ }
+}
+
+/*
+ * Set IRQ configuration register for all ESS models
+ */
+static int ess_common_set_irq_hw (sb_devc * devc)
+{
+ int irq_bits;
+
+ if ((irq_bits = ess_irq_bits (devc->irq)) == -1) return 0;
+
+ if (!ess_write (devc, 0xb1, 0x50 | (irq_bits << 2))) {
+ printk(KERN_ERR "ES1688: Failed to write to IRQ config register\n");
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * I wanna use modern ES1887 mixer irq handling. Funny is the
+ * fact that my BIOS wants the same. But suppose someone's BIOS
+ * doesn't do this!
+ * This is independent of duplex. If there's a 1887 this will
+ * prevent it from going into 1888 mode.
+ */
+static void ess_es1887_set_irq_hw (sb_devc * devc)
+{
+ int irq_bits;
+
+ if ((irq_bits = ess_irq_bits (devc->irq)) == -1) return;
+
+ ess_chgmixer (devc, 0x7f, 0x0f, 0x01 | ((irq_bits + 1) << 1));
+}
+
+static int ess_set_irq_hw (sb_devc * devc)
+{
+ if (devc->submodel == SUBMDL_ES1887) ess_es1887_set_irq_hw (devc);
+
+ return ess_common_set_irq_hw (devc);
+}
+
+#ifdef FKS_TEST
+
+/*
+ * FKS_test:
+ * for ES1887: 00, 18, non wr bits: 0001 1000
+ * for ES1868: 00, b8, non wr bits: 1011 1000
+ * for ES1888: 00, f8, non wr bits: 1111 1000
+ * for ES1688: 00, f8, non wr bits: 1111 1000
+ * + ES968
+ */
+
+static void FKS_test (sb_devc * devc)
+{
+ int val1, val2;
+ val1 = ess_getmixer (devc, 0x64);
+ ess_setmixer (devc, 0x64, ~val1);
+ val2 = ess_getmixer (devc, 0x64) ^ ~val1;
+ ess_setmixer (devc, 0x64, val1);
+ val1 ^= ess_getmixer (devc, 0x64);
+printk (KERN_INFO "FKS: FKS_test %02x, %02x\n", (val1 & 0x0ff), (val2 & 0x0ff));
+};
+#endif
+
+static unsigned int ess_identify (sb_devc * devc)
+{
+ unsigned int val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ outb(((unsigned char) (0x40 & 0xff)), MIXER_ADDR);
+
+ udelay(20);
+ val = inb(MIXER_DATA) << 8;
+ udelay(20);
+ val |= inb(MIXER_DATA);
+ udelay(20);
+ spin_unlock_irqrestore(&devc->lock, flags);
+
+ return val;
+}
+
+/*
+ * ESS technology describes a detection scheme in their docs. It involves
+ * fiddling with the bits in certain mixer registers. ess_probe is supposed
+ * to help.
+ *
+ * FKS: tracing shows ess_probe writes wrong value to 0x64. Bit 3 reads 1, but
+ * should be written 0 only. Check this.
+ */
+static int ess_probe (sb_devc * devc, int reg, int xorval)
+{
+ int val1, val2, val3;
+
+ val1 = ess_getmixer (devc, reg);
+ val2 = val1 ^ xorval;
+ ess_setmixer (devc, reg, val2);
+ val3 = ess_getmixer (devc, reg);
+ ess_setmixer (devc, reg, val1);
+
+ return (val2 == val3);
+}
+
+int ess_init(sb_devc * devc, struct address_info *hw_config)
+{
+ unsigned char cfg;
+ int ess_major = 0, ess_minor = 0;
+ int i;
+ static char name[100], modelname[10];
+
+ /*
+ * Try to detect ESS chips.
+ */
+
+ sb_dsp_command(devc, 0xe7); /* Return identification */
+
+ for (i = 1000; i; i--) {
+ if (inb(DSP_DATA_AVAIL) & 0x80) {
+ if (ess_major == 0) {
+ ess_major = inb(DSP_READ);
+ } else {
+ ess_minor = inb(DSP_READ);
+ break;
+ }
+ }
+ }
+
+ if (ess_major == 0) return 0;
+
+ if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) {
+ sprintf(name, "ESS ES488 AudioDrive (rev %d)",
+ ess_minor & 0x0f);
+ hw_config->name = name;
+ devc->model = MDL_SBPRO;
+ return 1;
+ }
+
+ /*
+ * This the detection heuristic of ESS technology, though somewhat
+ * changed to actually make it work.
+ * This results in the following detection steps:
+ * - distinct between ES688 and ES1688+ (as always done in this driver)
+ * if ES688 we're ready
+ * - try to detect ES1868, ES1869 or ES1878 (ess_identify)
+ * if successful we're ready
+ * - try to detect ES1888, ES1887 or ES1788 (aim: detect ES1887)
+ * if successful we're ready
+ * - Dunno. Must be 1688. Will do in general
+ *
+ * This is the most BETA part of the software: Will the detection
+ * always work?
+ */
+ devc->model = MDL_ESS;
+ devc->submodel = ess_minor & 0x0f;
+
+ if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) {
+ char *chip = NULL;
+ int submodel = -1;
+
+ switch (devc->sbmo.esstype) {
+ case ESSTYPE_DETECT:
+ case ESSTYPE_LIKE20:
+ break;
+ case 688:
+ submodel = 0x00;
+ break;
+ case 1688:
+ submodel = 0x08;
+ break;
+ case 1868:
+ submodel = SUBMDL_ES1868;
+ break;
+ case 1869:
+ submodel = SUBMDL_ES1869;
+ break;
+ case 1788:
+ submodel = SUBMDL_ES1788;
+ break;
+ case 1878:
+ submodel = SUBMDL_ES1878;
+ break;
+ case 1879:
+ submodel = SUBMDL_ES1879;
+ break;
+ case 1887:
+ submodel = SUBMDL_ES1887;
+ break;
+ case 1888:
+ submodel = SUBMDL_ES1888;
+ break;
+ default:
+ printk (KERN_ERR "Invalid esstype=%d specified\n", devc->sbmo.esstype);
+ return 0;
+ };
+ if (submodel != -1) {
+ devc->submodel = submodel;
+ sprintf (modelname, "ES%d", devc->sbmo.esstype);
+ chip = modelname;
+ };
+ if (chip == NULL && (ess_minor & 0x0f) < 8) {
+ chip = "ES688";
+ };
+#ifdef FKS_TEST
+FKS_test (devc);
+#endif
+ /*
+ * If Nothing detected yet, and we want 2.0 behaviour...
+ * Then let's assume it's ES1688.
+ */
+ if (chip == NULL && devc->sbmo.esstype == ESSTYPE_LIKE20) {
+ chip = "ES1688";
+ };
+
+ if (chip == NULL) {
+ int type;
+
+ type = ess_identify (devc);
+
+ switch (type) {
+ case 0x1868:
+ chip = "ES1868";
+ devc->submodel = SUBMDL_ES1868;
+ break;
+ case 0x1869:
+ chip = "ES1869";
+ devc->submodel = SUBMDL_ES1869;
+ break;
+ case 0x1878:
+ chip = "ES1878";
+ devc->submodel = SUBMDL_ES1878;
+ break;
+ case 0x1879:
+ chip = "ES1879";
+ devc->submodel = SUBMDL_ES1879;
+ break;
+ default:
+ if ((type & 0x00ff) != ((type >> 8) & 0x00ff)) {
+ printk ("ess_init: Unrecognized %04x\n", type);
+ }
+ };
+ };
+#if 0
+ /*
+ * this one failed:
+ * the probing of bit 4 is another thought: from ES1788 and up, all
+ * chips seem to have hardware volume control. Bit 4 is readonly to
+ * check if a hardware volume interrupt has fired.
+ * Cause ES688/ES1688 don't have this feature, bit 4 might be writeable
+ * for these chips.
+ */
+ if (chip == NULL && !ess_probe(devc, 0x64, (1 << 4))) {
+#endif
+ /*
+ * the probing of bit 2 is my idea. The ES1887 docs want me to probe
+ * bit 3. This results in ES1688 being detected as ES1788.
+ * Bit 2 is for "Enable HWV IRQE", but as ES(1)688 chips don't have
+ * HardWare Volume, I think they don't have this IRQE.
+ */
+ if (chip == NULL && ess_probe(devc, 0x64, (1 << 2))) {
+ if (ess_probe (devc, 0x70, 0x7f)) {
+ if (ess_probe (devc, 0x64, (1 << 5))) {
+ chip = "ES1887";
+ devc->submodel = SUBMDL_ES1887;
+ } else {
+ chip = "ES1888";
+ devc->submodel = SUBMDL_ES1888;
+ }
+ } else {
+ chip = "ES1788";
+ devc->submodel = SUBMDL_ES1788;
+ }
+ };
+ if (chip == NULL) {
+ chip = "ES1688";
+ };
+
+ printk ( KERN_INFO "ESS chip %s %s%s\n"
+ , chip
+ , ( devc->sbmo.esstype == ESSTYPE_DETECT || devc->sbmo.esstype == ESSTYPE_LIKE20
+ ? "detected"
+ : "specified"
+ )
+ , ( devc->sbmo.esstype == ESSTYPE_LIKE20
+ ? " (kernel 2.0 compatible)"
+ : ""
+ )
+ );
+
+ sprintf(name,"ESS %s AudioDrive (rev %d)", chip, ess_minor & 0x0f);
+ } else {
+ strcpy(name, "Jazz16");
+ }
+
+ /* AAS: info stolen from ALSA: these boards have different clocks */
+ switch(devc->submodel) {
+/* APPARENTLY NOT 1869 AND 1887
+ case SUBMDL_ES1869:
+ case SUBMDL_ES1887:
+*/
+ case SUBMDL_ES1888:
+ devc->caps |= SB_CAP_ES18XX_RATE;
+ break;
+ }
+
+ hw_config->name = name;
+ /* FKS: sb_dsp_reset to enable extended mode???? */
+ sb_dsp_reset(devc); /* Turn on extended mode */
+
+ /*
+ * Enable joystick and OPL3
+ */
+ cfg = ess_getmixer (devc, 0x40);
+ ess_setmixer (devc, 0x40, cfg | 0x03);
+ if (devc->submodel >= 8) { /* ES1688 */
+ devc->caps |= SB_NO_MIDI; /* ES1688 uses MPU401 MIDI mode */
+ }
+ sb_dsp_reset (devc);
+
+ /*
+ * This is important! If it's not done, the IRQ probe in sb_dsp_init
+ * may fail.
+ */
+ return ess_set_irq_hw (devc);
+}
+
+static int ess_set_dma_hw(sb_devc * devc)
+{
+ unsigned char cfg, dma_bits = 0, dma16_bits;
+ int dma;
+
+#ifdef FKS_LOGGING
+printk(KERN_INFO "ess_set_dma_hw: dma8=%d,dma16=%d,dup=%d\n"
+, devc->dma8, devc->dma16, devc->duplex);
+#endif
+
+ /*
+ * FKS: It seems as if this duplex flag isn't set yet. Check it.
+ */
+ dma = devc->dma8;
+
+ if (dma > 3 || dma < 0 || dma == 2) {
+ dma_bits = 0;
+ printk(KERN_ERR "ESS1688: Invalid DMA8 %d\n", dma);
+ return 0;
+ } else {
+ /* Extended mode DMA enable */
+ cfg = 0x50;
+
+ if (dma == 3) {
+ dma_bits = 3;
+ } else {
+ dma_bits = dma + 1;
+ }
+ }
+
+ if (!ess_write (devc, 0xb2, cfg | (dma_bits << 2))) {
+ printk(KERN_ERR "ESS1688: Failed to write to DMA config register\n");
+ return 0;
+ }
+
+ if (devc->duplex) {
+ dma = devc->dma16;
+ dma16_bits = 0;
+
+ if (dma >= 0) {
+ switch (dma) {
+ case 0:
+ dma_bits = 0x04;
+ break;
+ case 1:
+ dma_bits = 0x05;
+ break;
+ case 3:
+ dma_bits = 0x06;
+ break;
+ case 5:
+ dma_bits = 0x07;
+ dma16_bits = 0x20;
+ break;
+ default:
+ printk(KERN_ERR "ESS1887: Invalid DMA16 %d\n", dma);
+ return 0;
+ };
+ ess_chgmixer (devc, 0x78, 0x20, dma16_bits);
+ ess_chgmixer (devc, 0x7d, 0x07, dma_bits);
+ }
+ }
+ return 1;
+}
+
+/*
+ * This one is called from sb_dsp_init.
+ *
+ * Return values:
+ * 0: Failed
+ * 1: Succeeded or doesn't apply (not SUBMDL_ES1887)
+ */
+int ess_dsp_init (sb_devc *devc, struct address_info *hw_config)
+{
+ /*
+ * Caller also checks this, but anyway
+ */
+ if (devc->model != MDL_ESS) {
+ printk (KERN_INFO "ess_dsp_init for non ESS chip\n");
+ return 1;
+ }
+ /*
+ * This for ES1887 to run Full Duplex. Actually ES1888
+ * is allowed to do so too. I have no idea yet if this
+ * will work for ES1888 however.
+ *
+ * For SB16 having both dma8 and dma16 means enable
+ * Full Duplex. Let's try this for ES1887 too
+ *
+ */
+ if (devc->submodel == SUBMDL_ES1887) {
+ if (hw_config->dma2 != -1) {
+ devc->dma16 = hw_config->dma2;
+ }
+ /*
+ * devc->duplex initialization is put here, cause
+ * ess_set_dma_hw needs it.
+ */
+ if (devc->dma8 != devc->dma16 && devc->dma16 != -1) {
+ devc->duplex = 1;
+ }
+ }
+ if (!ess_set_dma_hw (devc)) {
+ free_irq(devc->irq, devc);
+ return 0;
+ }
+ return 1;
+}
+
+/****************************************************************************
+ * *
+ * ESS mixer *
+ * *
+ ****************************************************************************/
+
+#define ES688_RECORDING_DEVICES \
+ ( SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD )
+#define ES688_MIXER_DEVICES \
+ ( SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE \
+ | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_VOLUME \
+ | SOUND_MASK_LINE2 | SOUND_MASK_SPEAKER )
+
+#define ES1688_RECORDING_DEVICES \
+ ( ES688_RECORDING_DEVICES )
+#define ES1688_MIXER_DEVICES \
+ ( ES688_MIXER_DEVICES | SOUND_MASK_RECLEV )
+
+#define ES1887_RECORDING_DEVICES \
+ ( ES1688_RECORDING_DEVICES | SOUND_MASK_LINE2 | SOUND_MASK_SYNTH)
+#define ES1887_MIXER_DEVICES \
+ ( ES1688_MIXER_DEVICES )
+
+/*
+ * Mixer registers of ES1887
+ *
+ * These registers specifically take care of recording levels. To make the
+ * mapping from playback devices to recording devices every recording
+ * devices = playback device + ES_REC_MIXER_RECDIFF
+ */
+#define ES_REC_MIXER_RECBASE (SOUND_MIXER_LINE3 + 1)
+#define ES_REC_MIXER_RECDIFF (ES_REC_MIXER_RECBASE - SOUND_MIXER_SYNTH)
+
+#define ES_REC_MIXER_RECSYNTH (SOUND_MIXER_SYNTH + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECPCM (SOUND_MIXER_PCM + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECSPEAKER (SOUND_MIXER_SPEAKER + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE (SOUND_MIXER_LINE + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECMIC (SOUND_MIXER_MIC + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECCD (SOUND_MIXER_CD + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECIMIX (SOUND_MIXER_IMIX + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECALTPCM (SOUND_MIXER_ALTPCM + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECRECLEV (SOUND_MIXER_RECLEV + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECIGAIN (SOUND_MIXER_IGAIN + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECOGAIN (SOUND_MIXER_OGAIN + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE1 (SOUND_MIXER_LINE1 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE2 (SOUND_MIXER_LINE2 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE3 (SOUND_MIXER_LINE3 + ES_REC_MIXER_RECDIFF)
+
+static mixer_tab es688_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME, 0x32, 7, 4, 0x32, 3, 4),
+MIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH, 0x36, 7, 4, 0x36, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM, 0x14, 7, 4, 0x14, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER, 0x3c, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE, 0x3e, 7, 4, 0x3e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC, 0x1a, 7, 4, 0x1a, 3, 4),
+MIX_ENT(SOUND_MIXER_CD, 0x38, 7, 4, 0x38, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2, 0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+/*
+ * The ES1688 specifics... hopefully correct...
+ * - 6 bit master volume
+ * I was wrong, ES1888 docs say ES1688 didn't have it.
+ * - RECLEV control
+ * These may apply to ES688 too. I have no idea.
+ */
+static mixer_tab es1688_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME, 0x32, 7, 4, 0x32, 3, 4),
+MIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH, 0x36, 7, 4, 0x36, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM, 0x14, 7, 4, 0x14, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER, 0x3c, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE, 0x3e, 7, 4, 0x3e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC, 0x1a, 7, 4, 0x1a, 3, 4),
+MIX_ENT(SOUND_MIXER_CD, 0x38, 7, 4, 0x38, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2, 0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+static mixer_tab es1688later_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME, 0x60, 5, 6, 0x62, 5, 6),
+MIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH, 0x36, 7, 4, 0x36, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM, 0x14, 7, 4, 0x14, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER, 0x3c, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE, 0x3e, 7, 4, 0x3e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC, 0x1a, 7, 4, 0x1a, 3, 4),
+MIX_ENT(SOUND_MIXER_CD, 0x38, 7, 4, 0x38, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2, 0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+/*
+ * This one is for all ESS chips with a record mixer.
+ * It's not used (yet) however
+ */
+static mixer_tab es_rec_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME, 0x60, 5, 6, 0x62, 5, 6),
+MIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH, 0x36, 7, 4, 0x36, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM, 0x14, 7, 4, 0x14, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER, 0x3c, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE, 0x3e, 7, 4, 0x3e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC, 0x1a, 7, 4, 0x1a, 3, 4),
+MIX_ENT(SOUND_MIXER_CD, 0x38, 7, 4, 0x38, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2, 0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSYNTH, 0x6b, 7, 4, 0x6b, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSPEAKER, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE, 0x6e, 7, 4, 0x6e, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECMIC, 0x68, 7, 4, 0x68, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECCD, 0x6a, 7, 4, 0x6a, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECIMIX, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECRECLEV, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECIGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECOGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE2, 0x6c, 7, 4, 0x6c, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECLINE3, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+/*
+ * This one is for ES1887. It's little different from es_rec_mix: it
+ * has 0x7c for PCM playback level. This is because ES1887 uses
+ * Audio 2 for playback.
+ */
+static mixer_tab es1887_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME, 0x60, 5, 6, 0x62, 5, 6),
+MIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH, 0x36, 7, 4, 0x36, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM, 0x7c, 7, 4, 0x7c, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER, 0x3c, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE, 0x3e, 7, 4, 0x3e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC, 0x1a, 7, 4, 0x1a, 3, 4),
+MIX_ENT(SOUND_MIXER_CD, 0x38, 7, 4, 0x38, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2, 0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSYNTH, 0x6b, 7, 4, 0x6b, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSPEAKER, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE, 0x6e, 7, 4, 0x6e, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECMIC, 0x68, 7, 4, 0x68, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECCD, 0x6a, 7, 4, 0x6a, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECIMIX, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECRECLEV, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECIGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECOGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE2, 0x6c, 7, 4, 0x6c, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECLINE3, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+static int ess_has_rec_mixer (int submodel)
+{
+ switch (submodel) {
+ case SUBMDL_ES1887:
+ return 1;
+ default:
+ return 0;
+ };
+};
+
+#ifdef FKS_LOGGING
+static int ess_mixer_mon_regs[]
+ = { 0x70, 0x71, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7d, 0x7f
+ , 0xa1, 0xa2, 0xa4, 0xa5, 0xa8, 0xa9
+ , 0xb1, 0xb2, 0xb4, 0xb5, 0xb6, 0xb7, 0xb9
+ , 0x00};
+
+static void ess_show_mixerregs (sb_devc *devc)
+{
+ int *mp = ess_mixer_mon_regs;
+
+return;
+
+ while (*mp != 0) {
+ printk (KERN_INFO "res (%x)=%x\n", *mp, (int)(ess_getmixer (devc, *mp)));
+ mp++;
+ }
+}
+#endif
+
+void ess_setmixer (sb_devc * devc, unsigned int port, unsigned int value)
+{
+ unsigned long flags;
+
+#ifdef FKS_LOGGING
+printk(KERN_INFO "FKS: write mixer %x: %x\n", port, value);
+#endif
+
+ spin_lock_irqsave(&devc->lock, flags);
+ if (port >= 0xa0) {
+ ess_write (devc, port, value);
+ } else {
+ outb(((unsigned char) (port & 0xff)), MIXER_ADDR);
+
+ udelay(20);
+ outb(((unsigned char) (value & 0xff)), MIXER_DATA);
+ udelay(20);
+ };
+ spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+unsigned int ess_getmixer (sb_devc * devc, unsigned int port)
+{
+ unsigned int val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devc->lock, flags);
+
+ if (port >= 0xa0) {
+ val = ess_read (devc, port);
+ } else {
+ outb(((unsigned char) (port & 0xff)), MIXER_ADDR);
+
+ udelay(20);
+ val = inb(MIXER_DATA);
+ udelay(20);
+ }
+ spin_unlock_irqrestore(&devc->lock, flags);
+
+ return val;
+}
+
+static void ess_chgmixer
+ (sb_devc * devc, unsigned int reg, unsigned int mask, unsigned int val)
+{
+ int value;
+
+ value = ess_getmixer (devc, reg);
+ value = (value & ~mask) | (val & mask);
+ ess_setmixer (devc, reg, value);
+}
+
+/*
+ * ess_mixer_init must be called from sb_mixer_init
+ */
+void ess_mixer_init (sb_devc * devc)
+{
+ devc->mixer_caps = SOUND_CAP_EXCL_INPUT;
+
+ /*
+ * Take care of ES1887 specifics...
+ */
+ switch (devc->submodel) {
+ case SUBMDL_ES1887:
+ devc->supported_devices = ES1887_MIXER_DEVICES;
+ devc->supported_rec_devices = ES1887_RECORDING_DEVICES;
+#ifdef FKS_LOGGING
+printk (KERN_INFO "FKS: ess_mixer_init dup = %d\n", devc->duplex);
+#endif
+ if (devc->duplex) {
+ devc->iomap = &es1887_mix;
+ devc->iomap_sz = ARRAY_SIZE(es1887_mix);
+ } else {
+ devc->iomap = &es_rec_mix;
+ devc->iomap_sz = ARRAY_SIZE(es_rec_mix);
+ }
+ break;
+ default:
+ if (devc->submodel < 8) {
+ devc->supported_devices = ES688_MIXER_DEVICES;
+ devc->supported_rec_devices = ES688_RECORDING_DEVICES;
+ devc->iomap = &es688_mix;
+ devc->iomap_sz = ARRAY_SIZE(es688_mix);
+ } else {
+ /*
+ * es1688 has 4 bits master vol.
+ * later chips have 6 bits (?)
+ */
+ devc->supported_devices = ES1688_MIXER_DEVICES;
+ devc->supported_rec_devices = ES1688_RECORDING_DEVICES;
+ if (devc->submodel < 0x10) {
+ devc->iomap = &es1688_mix;
+ devc->iomap_sz = ARRAY_SIZE(es688_mix);
+ } else {
+ devc->iomap = &es1688later_mix;
+ devc->iomap_sz = ARRAY_SIZE(es1688later_mix);
+ }
+ }
+ }
+}
+
+/*
+ * Changing playback levels at an ESS chip with record mixer means having to
+ * take care of recording levels of recorded inputs (devc->recmask) too!
+ */
+int ess_mixer_set(sb_devc *devc, int dev, int left, int right)
+{
+ if (ess_has_rec_mixer (devc->submodel) && (devc->recmask & (1 << dev))) {
+ sb_common_mixer_set (devc, dev + ES_REC_MIXER_RECDIFF, left, right);
+ }
+ return sb_common_mixer_set (devc, dev, left, right);
+}
+
+/*
+ * After a sb_dsp_reset extended register 0xb4 (RECLEV) is reset too. After
+ * sb_dsp_reset RECLEV has to be restored. This is where ess_mixer_reload
+ * helps.
+ */
+void ess_mixer_reload (sb_devc *devc, int dev)
+{
+ int left, right, value;
+
+ value = devc->levels[dev];
+ left = value & 0x000000ff;
+ right = (value & 0x0000ff00) >> 8;
+
+ sb_common_mixer_set(devc, dev, left, right);
+}
+
+static int es_rec_set_recmask(sb_devc * devc, int mask)
+{
+ int i, i_mask, cur_mask, diff_mask;
+ int value, left, right;
+
+#ifdef FKS_LOGGING
+printk (KERN_INFO "FKS: es_rec_set_recmask mask = %x\n", mask);
+#endif
+ /*
+ * Changing the recmask on an ESS chip with recording mixer means:
+ * (1) Find the differences
+ * (2) For "turned-on" inputs: make the recording level the playback level
+ * (3) For "turned-off" inputs: make the recording level zero
+ */
+ cur_mask = devc->recmask;
+ diff_mask = (cur_mask ^ mask);
+
+ for (i = 0; i < 32; i++) {
+ i_mask = (1 << i);
+ if (diff_mask & i_mask) { /* Difference? (1) */
+ if (mask & i_mask) { /* Turn it on (2) */
+ value = devc->levels[i];
+ left = value & 0x000000ff;
+ right = (value & 0x0000ff00) >> 8;
+ } else { /* Turn it off (3) */
+ left = 0;
+ right = 0;
+ }
+ sb_common_mixer_set(devc, i + ES_REC_MIXER_RECDIFF, left, right);
+ }
+ }
+ return mask;
+}
+
+int ess_set_recmask(sb_devc * devc, int *mask)
+{
+ /* This applies to ESS chips with record mixers only! */
+
+ if (ess_has_rec_mixer (devc->submodel)) {
+ *mask = es_rec_set_recmask (devc, *mask);
+ return 1; /* Applied */
+ } else {
+ return 0; /* Not applied */
+ }
+}
+
+/*
+ * ess_mixer_reset must be called from sb_mixer_reset
+ */
+int ess_mixer_reset (sb_devc * devc)
+{
+ /*
+ * Separate actions for ESS chips with a record mixer:
+ */
+ if (ess_has_rec_mixer (devc->submodel)) {
+ switch (devc->submodel) {
+ case SUBMDL_ES1887:
+ /*
+ * Separate actions for ES1887:
+ * Change registers 7a and 1c to make the record mixer the
+ * actual recording source.
+ */
+ ess_chgmixer(devc, 0x7a, 0x18, 0x08);
+ ess_chgmixer(devc, 0x1c, 0x07, 0x07);
+ break;
+ };
+ /*
+ * Call set_recmask for proper initialization
+ */
+ devc->recmask = devc->supported_rec_devices;
+ es_rec_set_recmask(devc, 0);
+ devc->recmask = 0;
+
+ return 1; /* We took care of recmask. */
+ } else {
+ return 0; /* We didn't take care; caller do it */
+ }
+}
+
+/****************************************************************************
+ * *
+ * ESS midi *
+ * *
+ ****************************************************************************/
+
+/*
+ * FKS: IRQ may be shared. Hm. And if so? Then What?
+ */
+int ess_midi_init(sb_devc * devc, struct address_info *hw_config)
+{
+ unsigned char cfg, tmp;
+
+ cfg = ess_getmixer (devc, 0x40) & 0x03;
+
+ if (devc->submodel < 8) {
+ ess_setmixer (devc, 0x40, cfg | 0x03); /* Enable OPL3 & joystick */
+ return 0; /* ES688 doesn't support MPU401 mode */
+ }
+ tmp = (hw_config->io_base & 0x0f0) >> 4;
+
+ if (tmp > 3) {
+ ess_setmixer (devc, 0x40, cfg);
+ return 0;
+ }
+ cfg |= tmp << 3;
+
+ tmp = 1; /* MPU enabled without interrupts */
+
+ /* May be shared: if so the value is -ve */
+
+ switch (abs(hw_config->irq)) {
+ case 9:
+ tmp = 0x4;
+ break;
+ case 5:
+ tmp = 0x5;
+ break;
+ case 7:
+ tmp = 0x6;
+ break;
+ case 10:
+ tmp = 0x7;
+ break;
+ default:
+ return 0;
+ }
+
+ cfg |= tmp << 5;
+ ess_setmixer (devc, 0x40, cfg | 0x03);
+
+ return 1;
+}
+
diff --git a/sound/oss/sb_ess.h b/sound/oss/sb_ess.h
new file mode 100644
index 00000000..38aa072e
--- /dev/null
+++ b/sound/oss/sb_ess.h
@@ -0,0 +1,34 @@
+/*
+ * Created: 9-Jan-1999 Rolf Fokkens
+ */
+
+extern void ess_intr
+ (sb_devc *devc);
+extern int ess_dsp_init
+ (sb_devc *devc, struct address_info *hw_config);
+
+extern struct audio_driver *ess_audio_init
+ (sb_devc *devc, int *audio_flags, int *format_mask);
+extern int ess_midi_init
+ (sb_devc *devc, struct address_info *hw_config);
+extern void ess_mixer_init
+ (sb_devc *devc);
+
+extern int ess_init
+ (sb_devc *devc, struct address_info *hw_config);
+extern int ess_dsp_reset
+ (sb_devc *devc);
+
+extern void ess_setmixer
+ (sb_devc *devc, unsigned int port, unsigned int value);
+extern unsigned int ess_getmixer
+ (sb_devc *devc, unsigned int port);
+extern int ess_mixer_set
+ (sb_devc *devc, int dev, int left, int right);
+extern int ess_mixer_reset
+ (sb_devc *devc);
+extern void ess_mixer_reload
+ (sb_devc * devc, int dev);
+extern int ess_set_recmask
+ (sb_devc *devc, int *mask);
+
diff --git a/sound/oss/sb_midi.c b/sound/oss/sb_midi.c
new file mode 100644
index 00000000..f139028e
--- /dev/null
+++ b/sound/oss/sb_midi.c
@@ -0,0 +1,206 @@
+/*
+ * sound/oss/sb_midi.c
+ *
+ * The low level driver for the Sound Blaster DS chips.
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+
+#include "sound_config.h"
+
+#include "sb.h"
+#undef SB_TEST_IRQ
+
+/*
+ * The DSP channel can be used either for input or output. Variable
+ * 'sb_irq_mode' will be set when the program calls read or write first time
+ * after open. Current version doesn't support mode changes without closing
+ * and reopening the device. Support for this feature may be implemented in a
+ * future version of this driver.
+ */
+
+
+static int sb_midi_open(int dev, int mode,
+ void (*input) (int dev, unsigned char data),
+ void (*output) (int dev)
+)
+{
+ sb_devc *devc = midi_devs[dev]->devc;
+ unsigned long flags;
+
+ if (devc == NULL)
+ return -ENXIO;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ if (devc->opened)
+ {
+ spin_unlock_irqrestore(&devc->lock, flags);
+ return -EBUSY;
+ }
+ devc->opened = 1;
+ spin_unlock_irqrestore(&devc->lock, flags);
+
+ devc->irq_mode = IMODE_MIDI;
+ devc->midi_broken = 0;
+
+ sb_dsp_reset(devc);
+
+ if (!sb_dsp_command(devc, 0x35)) /* Start MIDI UART mode */
+ {
+ devc->opened = 0;
+ return -EIO;
+ }
+ devc->intr_active = 1;
+
+ if (mode & OPEN_READ)
+ {
+ devc->input_opened = 1;
+ devc->midi_input_intr = input;
+ }
+ return 0;
+}
+
+static void sb_midi_close(int dev)
+{
+ sb_devc *devc = midi_devs[dev]->devc;
+ unsigned long flags;
+
+ if (devc == NULL)
+ return;
+
+ spin_lock_irqsave(&devc->lock, flags);
+ sb_dsp_reset(devc);
+ devc->intr_active = 0;
+ devc->input_opened = 0;
+ devc->opened = 0;
+ spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+static int sb_midi_out(int dev, unsigned char midi_byte)
+{
+ sb_devc *devc = midi_devs[dev]->devc;
+
+ if (devc == NULL)
+ return 1;
+
+ if (devc->midi_broken)
+ return 1;
+
+ if (!sb_dsp_command(devc, midi_byte))
+ {
+ devc->midi_broken = 1;
+ return 1;
+ }
+ return 1;
+}
+
+static int sb_midi_start_read(int dev)
+{
+ return 0;
+}
+
+static int sb_midi_end_read(int dev)
+{
+ sb_devc *devc = midi_devs[dev]->devc;
+
+ if (devc == NULL)
+ return -ENXIO;
+
+ sb_dsp_reset(devc);
+ devc->intr_active = 0;
+ return 0;
+}
+
+static int sb_midi_ioctl(int dev, unsigned cmd, void __user *arg)
+{
+ return -EINVAL;
+}
+
+void sb_midi_interrupt(sb_devc * devc)
+{
+ unsigned long flags;
+ unsigned char data;
+
+ if (devc == NULL)
+ return;
+
+ spin_lock_irqsave(&devc->lock, flags);
+
+ data = inb(DSP_READ);
+ if (devc->input_opened)
+ devc->midi_input_intr(devc->my_mididev, data);
+
+ spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+#define MIDI_SYNTH_NAME "Sound Blaster Midi"
+#define MIDI_SYNTH_CAPS 0
+#include "midi_synth.h"
+
+static struct midi_operations sb_midi_operations =
+{
+ .owner = THIS_MODULE,
+ .info = {"Sound Blaster", 0, 0, SNDCARD_SB},
+ .converter = &std_midi_synth,
+ .in_info = {0},
+ .open = sb_midi_open,
+ .close = sb_midi_close,
+ .ioctl = sb_midi_ioctl,
+ .outputc = sb_midi_out,
+ .start_read = sb_midi_start_read,
+ .end_read = sb_midi_end_read,
+};
+
+void sb_dsp_midi_init(sb_devc * devc, struct module *owner)
+{
+ int dev;
+
+ if (devc->model < 2) /* No MIDI support for SB 1.x */
+ return;
+
+ dev = sound_alloc_mididev();
+
+ if (dev == -1)
+ {
+ printk(KERN_ERR "sb_midi: too many MIDI devices detected\n");
+ return;
+ }
+ std_midi_synth.midi_dev = devc->my_mididev = dev;
+ midi_devs[dev] = kmalloc(sizeof(struct midi_operations), GFP_KERNEL);
+ if (midi_devs[dev] == NULL)
+ {
+ printk(KERN_WARNING "Sound Blaster: failed to allocate MIDI memory.\n");
+ sound_unload_mididev(dev);
+ return;
+ }
+ memcpy((char *) midi_devs[dev], (char *) &sb_midi_operations,
+ sizeof(struct midi_operations));
+
+ if (owner)
+ midi_devs[dev]->owner = owner;
+
+ midi_devs[dev]->devc = devc;
+
+
+ midi_devs[dev]->converter = kmalloc(sizeof(struct synth_operations), GFP_KERNEL);
+ if (midi_devs[dev]->converter == NULL)
+ {
+ printk(KERN_WARNING "Sound Blaster: failed to allocate MIDI memory.\n");
+ kfree(midi_devs[dev]);
+ sound_unload_mididev(dev);
+ return;
+ }
+ memcpy((char *) midi_devs[dev]->converter, (char *) &std_midi_synth,
+ sizeof(struct synth_operations));
+
+ midi_devs[dev]->converter->id = "SBMIDI";
+ sequencer_init();
+}
diff --git a/sound/oss/sb_mixer.c b/sound/oss/sb_mixer.c
new file mode 100644
index 00000000..2039d31b
--- /dev/null
+++ b/sound/oss/sb_mixer.c
@@ -0,0 +1,770 @@
+/*
+ * sound/oss/sb_mixer.c
+ *
+ * The low level mixer driver for the Sound Blaster compatible cards.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ * Rolf Fokkens (Dec 20 1998) : Moved ESS stuff into sb_ess.[ch]
+ * Stanislav Voronyi <stas@esc.kharkov.com> : Support for AWE 3DSE device (Jun 7 1999)
+ */
+
+#include <linux/slab.h>
+
+#include "sound_config.h"
+
+#define __SB_MIXER_C__
+
+#include "sb.h"
+#include "sb_mixer.h"
+
+#include "sb_ess.h"
+
+#define SBPRO_RECORDING_DEVICES (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD)
+
+/* Same as SB Pro, unless I find otherwise */
+#define SGNXPRO_RECORDING_DEVICES SBPRO_RECORDING_DEVICES
+
+#define SBPRO_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | \
+ SOUND_MASK_CD | SOUND_MASK_VOLUME)
+
+/* SG NX Pro has treble and bass settings on the mixer. The 'speaker'
+ * channel is the COVOX/DisneySoundSource emulation volume control
+ * on the mixer. It does NOT control speaker volume. Should have own
+ * mask eventually?
+ */
+#define SGNXPRO_MIXER_DEVICES (SBPRO_MIXER_DEVICES|SOUND_MASK_BASS| \
+ SOUND_MASK_TREBLE|SOUND_MASK_SPEAKER )
+
+#define SB16_RECORDING_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_LINE | SOUND_MASK_MIC | \
+ SOUND_MASK_CD)
+
+#define SB16_OUTFILTER_DEVICES (SOUND_MASK_LINE | SOUND_MASK_MIC | \
+ SOUND_MASK_CD)
+
+#define SB16_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
+ SOUND_MASK_CD | \
+ SOUND_MASK_IGAIN | SOUND_MASK_OGAIN | \
+ SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | \
+ SOUND_MASK_IMIX)
+
+/* These are the only devices that are working at the moment. Others could
+ * be added once they are identified and a method is found to control them.
+ */
+#define ALS007_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_LINE | \
+ SOUND_MASK_PCM | SOUND_MASK_MIC | \
+ SOUND_MASK_CD | \
+ SOUND_MASK_VOLUME)
+
+static mixer_tab sbpro_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME, 0x22, 7, 4, 0x22, 3, 4),
+MIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH, 0x26, 7, 4, 0x26, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM, 0x04, 7, 4, 0x04, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE, 0x2e, 7, 4, 0x2e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC, 0x0a, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_CD, 0x28, 7, 4, 0x28, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+static mixer_tab sb16_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME, 0x30, 7, 5, 0x31, 7, 5),
+MIX_ENT(SOUND_MIXER_BASS, 0x46, 7, 4, 0x47, 7, 4),
+MIX_ENT(SOUND_MIXER_TREBLE, 0x44, 7, 4, 0x45, 7, 4),
+MIX_ENT(SOUND_MIXER_SYNTH, 0x34, 7, 5, 0x35, 7, 5),
+MIX_ENT(SOUND_MIXER_PCM, 0x32, 7, 5, 0x33, 7, 5),
+MIX_ENT(SOUND_MIXER_SPEAKER, 0x3b, 7, 2, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE, 0x38, 7, 5, 0x39, 7, 5),
+MIX_ENT(SOUND_MIXER_MIC, 0x3a, 7, 5, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_CD, 0x36, 7, 5, 0x37, 7, 5),
+MIX_ENT(SOUND_MIXER_IMIX, 0x3c, 0, 1, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0x3f, 7, 2, 0x40, 7, 2), /* Obsolete. Use IGAIN */
+MIX_ENT(SOUND_MIXER_IGAIN, 0x3f, 7, 2, 0x40, 7, 2),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x41, 7, 2, 0x42, 7, 2)
+};
+
+static mixer_tab als007_mix =
+{
+MIX_ENT(SOUND_MIXER_VOLUME, 0x62, 7, 4, 0x62, 3, 4),
+MIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH, 0x66, 7, 4, 0x66, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM, 0x64, 7, 4, 0x64, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE, 0x6e, 7, 4, 0x6e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC, 0x6a, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_CD, 0x68, 7, 4, 0x68, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0), /* Obsolete. Use IGAIN */
+MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+
+/* SM_GAMES Master volume is lower and PCM & FM volumes
+ higher than with SB Pro. This improves the
+ sound quality */
+
+static int smg_default_levels[32] =
+{
+ 0x2020, /* Master Volume */
+ 0x4b4b, /* Bass */
+ 0x4b4b, /* Treble */
+ 0x6464, /* FM */
+ 0x6464, /* PCM */
+ 0x4b4b, /* PC Speaker */
+ 0x4b4b, /* Ext Line */
+ 0x0000, /* Mic */
+ 0x4b4b, /* CD */
+ 0x4b4b, /* Recording monitor */
+ 0x4b4b, /* SB PCM */
+ 0x4b4b, /* Recording level */
+ 0x4b4b, /* Input gain */
+ 0x4b4b, /* Output gain */
+ 0x4040, /* Line1 */
+ 0x4040, /* Line2 */
+ 0x1515 /* Line3 */
+};
+
+static int sb_default_levels[32] =
+{
+ 0x5a5a, /* Master Volume */
+ 0x4b4b, /* Bass */
+ 0x4b4b, /* Treble */
+ 0x4b4b, /* FM */
+ 0x4b4b, /* PCM */
+ 0x4b4b, /* PC Speaker */
+ 0x4b4b, /* Ext Line */
+ 0x1010, /* Mic */
+ 0x4b4b, /* CD */
+ 0x0000, /* Recording monitor */
+ 0x4b4b, /* SB PCM */
+ 0x4b4b, /* Recording level */
+ 0x4b4b, /* Input gain */
+ 0x4b4b, /* Output gain */
+ 0x4040, /* Line1 */
+ 0x4040, /* Line2 */
+ 0x1515 /* Line3 */
+};
+
+static unsigned char sb16_recmasks_L[SOUND_MIXER_NRDEVICES] =
+{
+ 0x00, /* SOUND_MIXER_VOLUME */
+ 0x00, /* SOUND_MIXER_BASS */
+ 0x00, /* SOUND_MIXER_TREBLE */
+ 0x40, /* SOUND_MIXER_SYNTH */
+ 0x00, /* SOUND_MIXER_PCM */
+ 0x00, /* SOUND_MIXER_SPEAKER */
+ 0x10, /* SOUND_MIXER_LINE */
+ 0x01, /* SOUND_MIXER_MIC */
+ 0x04, /* SOUND_MIXER_CD */
+ 0x00, /* SOUND_MIXER_IMIX */
+ 0x00, /* SOUND_MIXER_ALTPCM */
+ 0x00, /* SOUND_MIXER_RECLEV */
+ 0x00, /* SOUND_MIXER_IGAIN */
+ 0x00 /* SOUND_MIXER_OGAIN */
+};
+
+static unsigned char sb16_recmasks_R[SOUND_MIXER_NRDEVICES] =
+{
+ 0x00, /* SOUND_MIXER_VOLUME */
+ 0x00, /* SOUND_MIXER_BASS */
+ 0x00, /* SOUND_MIXER_TREBLE */
+ 0x20, /* SOUND_MIXER_SYNTH */
+ 0x00, /* SOUND_MIXER_PCM */
+ 0x00, /* SOUND_MIXER_SPEAKER */
+ 0x08, /* SOUND_MIXER_LINE */
+ 0x01, /* SOUND_MIXER_MIC */
+ 0x02, /* SOUND_MIXER_CD */
+ 0x00, /* SOUND_MIXER_IMIX */
+ 0x00, /* SOUND_MIXER_ALTPCM */
+ 0x00, /* SOUND_MIXER_RECLEV */
+ 0x00, /* SOUND_MIXER_IGAIN */
+ 0x00 /* SOUND_MIXER_OGAIN */
+};
+
+static char smw_mix_regs[] = /* Left mixer registers */
+{
+ 0x0b, /* SOUND_MIXER_VOLUME */
+ 0x0d, /* SOUND_MIXER_BASS */
+ 0x0d, /* SOUND_MIXER_TREBLE */
+ 0x05, /* SOUND_MIXER_SYNTH */
+ 0x09, /* SOUND_MIXER_PCM */
+ 0x00, /* SOUND_MIXER_SPEAKER */
+ 0x03, /* SOUND_MIXER_LINE */
+ 0x01, /* SOUND_MIXER_MIC */
+ 0x07, /* SOUND_MIXER_CD */
+ 0x00, /* SOUND_MIXER_IMIX */
+ 0x00, /* SOUND_MIXER_ALTPCM */
+ 0x00, /* SOUND_MIXER_RECLEV */
+ 0x00, /* SOUND_MIXER_IGAIN */
+ 0x00, /* SOUND_MIXER_OGAIN */
+ 0x00, /* SOUND_MIXER_LINE1 */
+ 0x00, /* SOUND_MIXER_LINE2 */
+ 0x00 /* SOUND_MIXER_LINE3 */
+};
+
+static int sbmixnum = 1;
+
+static void sb_mixer_reset(sb_devc * devc);
+
+void sb_mixer_set_stereo(sb_devc * devc, int mode)
+{
+ sb_chgmixer(devc, OUT_FILTER, STEREO_DAC, (mode ? STEREO_DAC : MONO_DAC));
+}
+
+static int detect_mixer(sb_devc * devc)
+{
+ /* Just trust the mixer is there */
+ return 1;
+}
+
+static void change_bits(sb_devc * devc, unsigned char *regval, int dev, int chn, int newval)
+{
+ unsigned char mask;
+ int shift;
+
+ mask = (1 << (*devc->iomap)[dev][chn].nbits) - 1;
+ newval = (int) ((newval * mask) + 50) / 100; /* Scale */
+
+ shift = (*devc->iomap)[dev][chn].bitoffs - (*devc->iomap)[dev][LEFT_CHN].nbits + 1;
+
+ *regval &= ~(mask << shift); /* Mask out previous value */
+ *regval |= (newval & mask) << shift; /* Set the new value */
+}
+
+static int sb_mixer_get(sb_devc * devc, int dev)
+{
+ if (!((1 << dev) & devc->supported_devices))
+ return -EINVAL;
+ return devc->levels[dev];
+}
+
+void smw_mixer_init(sb_devc * devc)
+{
+ int i;
+
+ sb_setmixer(devc, 0x00, 0x18); /* Mute unused (Telephone) line */
+ sb_setmixer(devc, 0x10, 0x38); /* Config register 2 */
+
+ devc->supported_devices = 0;
+ for (i = 0; i < sizeof(smw_mix_regs); i++)
+ if (smw_mix_regs[i] != 0)
+ devc->supported_devices |= (1 << i);
+
+ devc->supported_rec_devices = devc->supported_devices &
+ ~(SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_PCM | SOUND_MASK_VOLUME);
+ sb_mixer_reset(devc);
+}
+
+int sb_common_mixer_set(sb_devc * devc, int dev, int left, int right)
+{
+ int regoffs;
+ unsigned char val;
+
+ if ((dev < 0) || (dev >= devc->iomap_sz))
+ return -EINVAL;
+
+ regoffs = (*devc->iomap)[dev][LEFT_CHN].regno;
+
+ if (regoffs == 0)
+ return -EINVAL;
+
+ val = sb_getmixer(devc, regoffs);
+ change_bits(devc, &val, dev, LEFT_CHN, left);
+
+ if ((*devc->iomap)[dev][RIGHT_CHN].regno != regoffs) /*
+ * Change register
+ */
+ {
+ sb_setmixer(devc, regoffs, val); /*
+ * Save the old one
+ */
+ regoffs = (*devc->iomap)[dev][RIGHT_CHN].regno;
+
+ if (regoffs == 0)
+ return left | (left << 8); /*
+ * Just left channel present
+ */
+
+ val = sb_getmixer(devc, regoffs); /*
+ * Read the new one
+ */
+ }
+ change_bits(devc, &val, dev, RIGHT_CHN, right);
+
+ sb_setmixer(devc, regoffs, val);
+
+ return left | (right << 8);
+}
+
+static int smw_mixer_set(sb_devc * devc, int dev, int left, int right)
+{
+ int reg, val;
+
+ switch (dev)
+ {
+ case SOUND_MIXER_VOLUME:
+ sb_setmixer(devc, 0x0b, 96 - (96 * left / 100)); /* 96=mute, 0=max */
+ sb_setmixer(devc, 0x0c, 96 - (96 * right / 100));
+ break;
+
+ case SOUND_MIXER_BASS:
+ case SOUND_MIXER_TREBLE:
+ devc->levels[dev] = left | (right << 8);
+ /* Set left bass and treble values */
+ val = ((devc->levels[SOUND_MIXER_TREBLE] & 0xff) * 16 / (unsigned) 100) << 4;
+ val |= ((devc->levels[SOUND_MIXER_BASS] & 0xff) * 16 / (unsigned) 100) & 0x0f;
+ sb_setmixer(devc, 0x0d, val);
+
+ /* Set right bass and treble values */
+ val = (((devc->levels[SOUND_MIXER_TREBLE] >> 8) & 0xff) * 16 / (unsigned) 100) << 4;
+ val |= (((devc->levels[SOUND_MIXER_BASS] >> 8) & 0xff) * 16 / (unsigned) 100) & 0x0f;
+ sb_setmixer(devc, 0x0e, val);
+
+ break;
+
+ default:
+ /* bounds check */
+ if (dev < 0 || dev >= ARRAY_SIZE(smw_mix_regs))
+ return -EINVAL;
+ reg = smw_mix_regs[dev];
+ if (reg == 0)
+ return -EINVAL;
+ sb_setmixer(devc, reg, (24 - (24 * left / 100)) | 0x20); /* 24=mute, 0=max */
+ sb_setmixer(devc, reg + 1, (24 - (24 * right / 100)) | 0x40);
+ }
+
+ devc->levels[dev] = left | (right << 8);
+ return left | (right << 8);
+}
+
+static int sb_mixer_set(sb_devc * devc, int dev, int value)
+{
+ int left = value & 0x000000ff;
+ int right = (value & 0x0000ff00) >> 8;
+ int retval;
+
+ if (left > 100)
+ left = 100;
+ if (right > 100)
+ right = 100;
+
+ if ((dev < 0) || (dev > 31))
+ return -EINVAL;
+
+ if (!(devc->supported_devices & (1 << dev))) /*
+ * Not supported
+ */
+ return -EINVAL;
+
+ /* Differentiate depending on the chipsets */
+ switch (devc->model) {
+ case MDL_SMW:
+ retval = smw_mixer_set(devc, dev, left, right);
+ break;
+ case MDL_ESS:
+ retval = ess_mixer_set(devc, dev, left, right);
+ break;
+ default:
+ retval = sb_common_mixer_set(devc, dev, left, right);
+ }
+ if (retval >= 0) devc->levels[dev] = retval;
+
+ return retval;
+}
+
+/*
+ * set_recsrc doesn't apply to ES188x
+ */
+static void set_recsrc(sb_devc * devc, int src)
+{
+ sb_setmixer(devc, RECORD_SRC, (sb_getmixer(devc, RECORD_SRC) & ~7) | (src & 0x7));
+}
+
+static int set_recmask(sb_devc * devc, int mask)
+{
+ int devmask, i;
+ unsigned char regimageL, regimageR;
+
+ devmask = mask & devc->supported_rec_devices;
+
+ switch (devc->model)
+ {
+ case MDL_SBPRO:
+ case MDL_ESS:
+ case MDL_JAZZ:
+ case MDL_SMW:
+ if (devc->model == MDL_ESS && ess_set_recmask (devc, &devmask)) {
+ break;
+ };
+ if (devmask != SOUND_MASK_MIC &&
+ devmask != SOUND_MASK_LINE &&
+ devmask != SOUND_MASK_CD)
+ {
+ /*
+ * More than one device selected. Drop the
+ * previous selection
+ */
+ devmask &= ~devc->recmask;
+ }
+ if (devmask != SOUND_MASK_MIC &&
+ devmask != SOUND_MASK_LINE &&
+ devmask != SOUND_MASK_CD)
+ {
+ /*
+ * More than one device selected. Default to
+ * mic
+ */
+ devmask = SOUND_MASK_MIC;
+ }
+ if (devmask ^ devc->recmask) /*
+ * Input source changed
+ */
+ {
+ switch (devmask)
+ {
+ case SOUND_MASK_MIC:
+ set_recsrc(devc, SRC__MIC);
+ break;
+
+ case SOUND_MASK_LINE:
+ set_recsrc(devc, SRC__LINE);
+ break;
+
+ case SOUND_MASK_CD:
+ set_recsrc(devc, SRC__CD);
+ break;
+
+ default:
+ set_recsrc(devc, SRC__MIC);
+ }
+ }
+ break;
+
+ case MDL_SB16:
+ if (!devmask)
+ devmask = SOUND_MASK_MIC;
+
+ if (devc->submodel == SUBMDL_ALS007)
+ {
+ switch (devmask)
+ {
+ case SOUND_MASK_LINE:
+ sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_LINE);
+ break;
+ case SOUND_MASK_CD:
+ sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_CD);
+ break;
+ case SOUND_MASK_SYNTH:
+ sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_SYNTH);
+ break;
+ default: /* Also takes care of SOUND_MASK_MIC case */
+ sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_MIC);
+ break;
+ }
+ }
+ else
+ {
+ regimageL = regimageR = 0;
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ {
+ if ((1 << i) & devmask)
+ {
+ regimageL |= sb16_recmasks_L[i];
+ regimageR |= sb16_recmasks_R[i];
+ }
+ sb_setmixer (devc, SB16_IMASK_L, regimageL);
+ sb_setmixer (devc, SB16_IMASK_R, regimageR);
+ }
+ }
+ break;
+ }
+ devc->recmask = devmask;
+ return devc->recmask;
+}
+
+static int set_outmask(sb_devc * devc, int mask)
+{
+ int devmask, i;
+ unsigned char regimage;
+
+ devmask = mask & devc->supported_out_devices;
+
+ switch (devc->model)
+ {
+ case MDL_SB16:
+ if (devc->submodel == SUBMDL_ALS007)
+ break;
+ else
+ {
+ regimage = 0;
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ {
+ if ((1 << i) & devmask)
+ {
+ regimage |= (sb16_recmasks_L[i] | sb16_recmasks_R[i]);
+ }
+ sb_setmixer (devc, SB16_OMASK, regimage);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ devc->outmask = devmask;
+ return devc->outmask;
+}
+
+static int sb_mixer_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+ sb_devc *devc = mixer_devs[dev]->devc;
+ int val, ret;
+ int __user *p = arg;
+
+ /*
+ * Use ioctl(fd, SOUND_MIXER_AGC, &mode) to turn AGC off (0) or on (1).
+ * Use ioctl(fd, SOUND_MIXER_3DSE, &mode) to turn 3DSE off (0) or on (1)
+ * or mode==2 put 3DSE state to mode.
+ */
+ if (devc->model == MDL_SB16) {
+ if (cmd == SOUND_MIXER_AGC)
+ {
+ if (get_user(val, p))
+ return -EFAULT;
+ sb_setmixer(devc, 0x43, (~val) & 0x01);
+ return 0;
+ }
+ if (cmd == SOUND_MIXER_3DSE)
+ {
+ /* I put here 15, but I don't know the exact version.
+ At least my 4.13 havn't 3DSE, 4.16 has it. */
+ if (devc->minor < 15)
+ return -EINVAL;
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val == 0 || val == 1)
+ sb_chgmixer(devc, AWE_3DSE, 0x01, val);
+ else if (val == 2)
+ {
+ ret = sb_getmixer(devc, AWE_3DSE)&0x01;
+ return put_user(ret, p);
+ }
+ else
+ return -EINVAL;
+ return 0;
+ }
+ }
+ if (((cmd >> 8) & 0xff) == 'M')
+ {
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+ {
+ if (get_user(val, p))
+ return -EFAULT;
+ switch (cmd & 0xff)
+ {
+ case SOUND_MIXER_RECSRC:
+ ret = set_recmask(devc, val);
+ break;
+
+ case SOUND_MIXER_OUTSRC:
+ ret = set_outmask(devc, val);
+ break;
+
+ default:
+ ret = sb_mixer_set(devc, cmd & 0xff, val);
+ }
+ }
+ else switch (cmd & 0xff)
+ {
+ case SOUND_MIXER_RECSRC:
+ ret = devc->recmask;
+ break;
+
+ case SOUND_MIXER_OUTSRC:
+ ret = devc->outmask;
+ break;
+
+ case SOUND_MIXER_DEVMASK:
+ ret = devc->supported_devices;
+ break;
+
+ case SOUND_MIXER_STEREODEVS:
+ ret = devc->supported_devices;
+ /* The ESS seems to have stereo mic controls */
+ if (devc->model == MDL_ESS)
+ ret &= ~(SOUND_MASK_SPEAKER|SOUND_MASK_IMIX);
+ else if (devc->model != MDL_JAZZ && devc->model != MDL_SMW)
+ ret &= ~(SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_IMIX);
+ break;
+
+ case SOUND_MIXER_RECMASK:
+ ret = devc->supported_rec_devices;
+ break;
+
+ case SOUND_MIXER_OUTMASK:
+ ret = devc->supported_out_devices;
+ break;
+
+ case SOUND_MIXER_CAPS:
+ ret = devc->mixer_caps;
+ break;
+
+ default:
+ ret = sb_mixer_get(devc, cmd & 0xff);
+ break;
+ }
+ return put_user(ret, p);
+ } else
+ return -EINVAL;
+}
+
+static struct mixer_operations sb_mixer_operations =
+{
+ .owner = THIS_MODULE,
+ .id = "SB",
+ .name = "Sound Blaster",
+ .ioctl = sb_mixer_ioctl
+};
+
+static struct mixer_operations als007_mixer_operations =
+{
+ .owner = THIS_MODULE,
+ .id = "ALS007",
+ .name = "Avance ALS-007",
+ .ioctl = sb_mixer_ioctl
+};
+
+static void sb_mixer_reset(sb_devc * devc)
+{
+ char name[32];
+ int i;
+
+ sprintf(name, "SB_%d", devc->sbmixnum);
+
+ if (devc->sbmo.sm_games)
+ devc->levels = load_mixer_volumes(name, smg_default_levels, 1);
+ else
+ devc->levels = load_mixer_volumes(name, sb_default_levels, 1);
+
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ sb_mixer_set(devc, i, devc->levels[i]);
+
+ if (devc->model != MDL_ESS || !ess_mixer_reset (devc)) {
+ set_recmask(devc, SOUND_MASK_MIC);
+ };
+}
+
+int sb_mixer_init(sb_devc * devc, struct module *owner)
+{
+ int mixer_type = 0;
+ int m;
+
+ devc->sbmixnum = sbmixnum++;
+ devc->levels = NULL;
+
+ sb_setmixer(devc, 0x00, 0); /* Reset mixer */
+
+ if (!(mixer_type = detect_mixer(devc)))
+ return 0; /* No mixer. Why? */
+
+ switch (devc->model)
+ {
+ case MDL_ESSPCI:
+ case MDL_YMPCI:
+ case MDL_SBPRO:
+ case MDL_AZTECH:
+ case MDL_JAZZ:
+ devc->mixer_caps = SOUND_CAP_EXCL_INPUT;
+ devc->supported_devices = SBPRO_MIXER_DEVICES;
+ devc->supported_rec_devices = SBPRO_RECORDING_DEVICES;
+ devc->iomap = &sbpro_mix;
+ devc->iomap_sz = ARRAY_SIZE(sbpro_mix);
+ break;
+
+ case MDL_ESS:
+ ess_mixer_init (devc);
+ break;
+
+ case MDL_SMW:
+ devc->mixer_caps = SOUND_CAP_EXCL_INPUT;
+ devc->supported_devices = 0;
+ devc->supported_rec_devices = 0;
+ devc->iomap = &sbpro_mix;
+ devc->iomap_sz = ARRAY_SIZE(sbpro_mix);
+ smw_mixer_init(devc);
+ break;
+
+ case MDL_SB16:
+ devc->mixer_caps = 0;
+ devc->supported_rec_devices = SB16_RECORDING_DEVICES;
+ devc->supported_out_devices = SB16_OUTFILTER_DEVICES;
+ if (devc->submodel != SUBMDL_ALS007)
+ {
+ devc->supported_devices = SB16_MIXER_DEVICES;
+ devc->iomap = &sb16_mix;
+ devc->iomap_sz = ARRAY_SIZE(sb16_mix);
+ }
+ else
+ {
+ devc->supported_devices = ALS007_MIXER_DEVICES;
+ devc->iomap = &als007_mix;
+ devc->iomap_sz = ARRAY_SIZE(als007_mix);
+ }
+ break;
+
+ default:
+ printk(KERN_WARNING "sb_mixer: Unsupported mixer type %d\n", devc->model);
+ return 0;
+ }
+
+ m = sound_alloc_mixerdev();
+ if (m == -1)
+ return 0;
+
+ mixer_devs[m] = kmalloc(sizeof(struct mixer_operations), GFP_KERNEL);
+ if (mixer_devs[m] == NULL)
+ {
+ printk(KERN_ERR "sb_mixer: Can't allocate memory\n");
+ sound_unload_mixerdev(m);
+ return 0;
+ }
+
+ if (devc->submodel != SUBMDL_ALS007)
+ memcpy ((char *) mixer_devs[m], (char *) &sb_mixer_operations, sizeof (struct mixer_operations));
+ else
+ memcpy ((char *) mixer_devs[m], (char *) &als007_mixer_operations, sizeof (struct mixer_operations));
+
+ mixer_devs[m]->devc = devc;
+
+ if (owner)
+ mixer_devs[m]->owner = owner;
+
+ devc->my_mixerdev = m;
+ sb_mixer_reset(devc);
+ return 1;
+}
+
+void sb_mixer_unload(sb_devc *devc)
+{
+ if (devc->my_mixerdev == -1)
+ return;
+
+ kfree(mixer_devs[devc->my_mixerdev]);
+ sound_unload_mixerdev(devc->my_mixerdev);
+ sbmixnum--;
+}
diff --git a/sound/oss/sb_mixer.h b/sound/oss/sb_mixer.h
new file mode 100644
index 00000000..4b9425f0
--- /dev/null
+++ b/sound/oss/sb_mixer.h
@@ -0,0 +1,105 @@
+/*
+ * sound/oss/sb_mixer.h
+ *
+ * Definitions for the SB Pro and SB16 mixers
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+/*
+ * Modified:
+ * Hunyue Yau Jan 6 1994
+ * Added defines for the Sound Galaxy NX Pro mixer.
+ *
+ * Rolf Fokkens Dec 20 1998
+ * Added defines for some ES188x chips.
+ *
+ * Rolf Fokkens Dec 27 1998
+ * Moved static stuff to sb_mixer.c
+ *
+ */
+/*
+ * Mixer registers
+ *
+ * NOTE! RECORD_SRC == IN_FILTER
+ */
+
+/*
+ * Mixer registers of SB Pro
+ */
+#define VOC_VOL 0x04
+#define MIC_VOL 0x0A
+#define MIC_MIX 0x0A
+#define RECORD_SRC 0x0C
+#define IN_FILTER 0x0C
+#define OUT_FILTER 0x0E
+#define MASTER_VOL 0x22
+#define FM_VOL 0x26
+#define CD_VOL 0x28
+#define LINE_VOL 0x2E
+#define IRQ_NR 0x80
+#define DMA_NR 0x81
+#define IRQ_STAT 0x82
+#define OPSW 0x3c
+
+/*
+ * Additional registers on the SG NX Pro
+ */
+#define COVOX_VOL 0x42
+#define TREBLE_LVL 0x44
+#define BASS_LVL 0x46
+
+#define FREQ_HI (1 << 3)/* Use High-frequency ANFI filters */
+#define FREQ_LOW 0 /* Use Low-frequency ANFI filters */
+#define FILT_ON 0 /* Yes, 0 to turn it on, 1 for off */
+#define FILT_OFF (1 << 5)
+
+#define MONO_DAC 0x00
+#define STEREO_DAC 0x02
+
+/*
+ * Mixer registers of SB16
+ */
+#define SB16_OMASK 0x3c
+#define SB16_IMASK_L 0x3d
+#define SB16_IMASK_R 0x3e
+
+#define LEFT_CHN 0
+#define RIGHT_CHN 1
+
+/*
+ * 3DSE register of AWE32/64
+ */
+#define AWE_3DSE 0x90
+
+/*
+ * Mixer registers of ALS007
+ */
+#define ALS007_RECORD_SRC 0x6c
+#define ALS007_OUTPUT_CTRL1 0x3c
+#define ALS007_OUTPUT_CTRL2 0x4c
+
+#define MIX_ENT(name, reg_l, bit_l, len_l, reg_r, bit_r, len_r) \
+ {{reg_l, bit_l, len_l}, {reg_r, bit_r, len_r}}
+
+/*
+ * Recording sources (SB Pro)
+ */
+
+#define SRC__MIC 1 /* Select Microphone recording source */
+#define SRC__CD 3 /* Select CD recording source */
+#define SRC__LINE 7 /* Use Line-in for recording source */
+
+/*
+ * Recording sources for ALS-007
+ */
+
+#define ALS007_MIC 4
+#define ALS007_LINE 6
+#define ALS007_CD 2
+#define ALS007_SYNTH 7
diff --git a/sound/oss/sequencer.c b/sound/oss/sequencer.c
new file mode 100644
index 00000000..30bcfe47
--- /dev/null
+++ b/sound/oss/sequencer.c
@@ -0,0 +1,1671 @@
+/*
+ * sound/oss/sequencer.c
+ *
+ * The sequencer personality manager.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ * Alan Cox : reformatted and fixed a pair of null pointer bugs
+ */
+#include <linux/kmod.h>
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+#include "midi_ctrl.h"
+
+static int sequencer_ok;
+static struct sound_timer_operations *tmr;
+static int tmr_no = -1; /* Currently selected timer */
+static int pending_timer = -1; /* For timer change operation */
+extern unsigned long seq_time;
+
+static int obsolete_api_used;
+static DEFINE_SPINLOCK(lock);
+
+/*
+ * Local counts for number of synth and MIDI devices. These are initialized
+ * by the sequencer_open.
+ */
+static int max_mididev;
+static int max_synthdev;
+
+/*
+ * The seq_mode gives the operating mode of the sequencer:
+ * 1 = level1 (the default)
+ * 2 = level2 (extended capabilities)
+ */
+
+#define SEQ_1 1
+#define SEQ_2 2
+static int seq_mode = SEQ_1;
+
+static DECLARE_WAIT_QUEUE_HEAD(seq_sleeper);
+static DECLARE_WAIT_QUEUE_HEAD(midi_sleeper);
+
+static int midi_opened[MAX_MIDI_DEV];
+
+static int midi_written[MAX_MIDI_DEV];
+
+static unsigned long prev_input_time;
+static int prev_event_time;
+
+#include "tuning.h"
+
+#define EV_SZ 8
+#define IEV_SZ 8
+
+static unsigned char *queue;
+static unsigned char *iqueue;
+
+static volatile int qhead, qtail, qlen;
+static volatile int iqhead, iqtail, iqlen;
+static volatile int seq_playing;
+static volatile int sequencer_busy;
+static int output_threshold;
+static long pre_event_timeout;
+static unsigned synth_open_mask;
+
+static int seq_queue(unsigned char *note, char nonblock);
+static void seq_startplay(void);
+static int seq_sync(void);
+static void seq_reset(void);
+
+#if MAX_SYNTH_DEV > 15
+#error Too many synthesizer devices enabled.
+#endif
+
+int sequencer_read(int dev, struct file *file, char __user *buf, int count)
+{
+ int c = count, p = 0;
+ int ev_len;
+ unsigned long flags;
+
+ dev = dev >> 4;
+
+ ev_len = seq_mode == SEQ_1 ? 4 : 8;
+
+ spin_lock_irqsave(&lock,flags);
+
+ if (!iqlen)
+ {
+ spin_unlock_irqrestore(&lock,flags);
+ if (file->f_flags & O_NONBLOCK) {
+ return -EAGAIN;
+ }
+
+ interruptible_sleep_on_timeout(&midi_sleeper,
+ pre_event_timeout);
+ spin_lock_irqsave(&lock,flags);
+ if (!iqlen)
+ {
+ spin_unlock_irqrestore(&lock,flags);
+ return 0;
+ }
+ }
+ while (iqlen && c >= ev_len)
+ {
+ char *fixit = (char *) &iqueue[iqhead * IEV_SZ];
+ spin_unlock_irqrestore(&lock,flags);
+ if (copy_to_user(&(buf)[p], fixit, ev_len))
+ return count - c;
+ p += ev_len;
+ c -= ev_len;
+
+ spin_lock_irqsave(&lock,flags);
+ iqhead = (iqhead + 1) % SEQ_MAX_QUEUE;
+ iqlen--;
+ }
+ spin_unlock_irqrestore(&lock,flags);
+ return count - c;
+}
+
+static void sequencer_midi_output(int dev)
+{
+ /*
+ * Currently NOP
+ */
+}
+
+void seq_copy_to_input(unsigned char *event_rec, int len)
+{
+ unsigned long flags;
+
+ /*
+ * Verify that the len is valid for the current mode.
+ */
+
+ if (len != 4 && len != 8)
+ return;
+ if ((seq_mode == SEQ_1) != (len == 4))
+ return;
+
+ if (iqlen >= (SEQ_MAX_QUEUE - 1))
+ return; /* Overflow */
+
+ spin_lock_irqsave(&lock,flags);
+ memcpy(&iqueue[iqtail * IEV_SZ], event_rec, len);
+ iqlen++;
+ iqtail = (iqtail + 1) % SEQ_MAX_QUEUE;
+ wake_up(&midi_sleeper);
+ spin_unlock_irqrestore(&lock,flags);
+}
+EXPORT_SYMBOL(seq_copy_to_input);
+
+static void sequencer_midi_input(int dev, unsigned char data)
+{
+ unsigned int tstamp;
+ unsigned char event_rec[4];
+
+ if (data == 0xfe) /* Ignore active sensing */
+ return;
+
+ tstamp = jiffies - seq_time;
+
+ if (tstamp != prev_input_time)
+ {
+ tstamp = (tstamp << 8) | SEQ_WAIT;
+ seq_copy_to_input((unsigned char *) &tstamp, 4);
+ prev_input_time = tstamp;
+ }
+ event_rec[0] = SEQ_MIDIPUTC;
+ event_rec[1] = data;
+ event_rec[2] = dev;
+ event_rec[3] = 0;
+
+ seq_copy_to_input(event_rec, 4);
+}
+
+void seq_input_event(unsigned char *event_rec, int len)
+{
+ unsigned long this_time;
+
+ if (seq_mode == SEQ_2)
+ this_time = tmr->get_time(tmr_no);
+ else
+ this_time = jiffies - seq_time;
+
+ if (this_time != prev_input_time)
+ {
+ unsigned char tmp_event[8];
+
+ tmp_event[0] = EV_TIMING;
+ tmp_event[1] = TMR_WAIT_ABS;
+ tmp_event[2] = 0;
+ tmp_event[3] = 0;
+ *(unsigned int *) &tmp_event[4] = this_time;
+
+ seq_copy_to_input(tmp_event, 8);
+ prev_input_time = this_time;
+ }
+ seq_copy_to_input(event_rec, len);
+}
+EXPORT_SYMBOL(seq_input_event);
+
+int sequencer_write(int dev, struct file *file, const char __user *buf, int count)
+{
+ unsigned char event_rec[EV_SZ], ev_code;
+ int p = 0, c, ev_size;
+ int mode = translate_mode(file);
+
+ dev = dev >> 4;
+
+ DEB(printk("sequencer_write(dev=%d, count=%d)\n", dev, count));
+
+ if (mode == OPEN_READ)
+ return -EIO;
+
+ c = count;
+
+ while (c >= 4)
+ {
+ if (copy_from_user((char *) event_rec, &(buf)[p], 4))
+ goto out;
+ ev_code = event_rec[0];
+
+ if (ev_code == SEQ_FULLSIZE)
+ {
+ int err, fmt;
+
+ dev = *(unsigned short *) &event_rec[2];
+ if (dev < 0 || dev >= max_synthdev || synth_devs[dev] == NULL)
+ return -ENXIO;
+
+ if (!(synth_open_mask & (1 << dev)))
+ return -ENXIO;
+
+ fmt = (*(short *) &event_rec[0]) & 0xffff;
+ err = synth_devs[dev]->load_patch(dev, fmt, buf + p, c, 0);
+ if (err < 0)
+ return err;
+
+ return err;
+ }
+ if (ev_code >= 128)
+ {
+ if (seq_mode == SEQ_2 && ev_code == SEQ_EXTENDED)
+ {
+ printk(KERN_WARNING "Sequencer: Invalid level 2 event %x\n", ev_code);
+ return -EINVAL;
+ }
+ ev_size = 8;
+
+ if (c < ev_size)
+ {
+ if (!seq_playing)
+ seq_startplay();
+ return count - c;
+ }
+ if (copy_from_user((char *)&event_rec[4],
+ &(buf)[p + 4], 4))
+ goto out;
+
+ }
+ else
+ {
+ if (seq_mode == SEQ_2)
+ {
+ printk(KERN_WARNING "Sequencer: 4 byte event in level 2 mode\n");
+ return -EINVAL;
+ }
+ ev_size = 4;
+
+ if (event_rec[0] != SEQ_MIDIPUTC)
+ obsolete_api_used = 1;
+ }
+
+ if (event_rec[0] == SEQ_MIDIPUTC)
+ {
+ if (!midi_opened[event_rec[2]])
+ {
+ int err, mode;
+ int dev = event_rec[2];
+
+ if (dev >= max_mididev || midi_devs[dev]==NULL)
+ {
+ /*printk("Sequencer Error: Nonexistent MIDI device %d\n", dev);*/
+ return -ENXIO;
+ }
+ mode = translate_mode(file);
+
+ if ((err = midi_devs[dev]->open(dev, mode,
+ sequencer_midi_input, sequencer_midi_output)) < 0)
+ {
+ seq_reset();
+ printk(KERN_WARNING "Sequencer Error: Unable to open Midi #%d\n", dev);
+ return err;
+ }
+ midi_opened[dev] = 1;
+ }
+ }
+ if (!seq_queue(event_rec, (file->f_flags & (O_NONBLOCK) ? 1 : 0)))
+ {
+ int processed = count - c;
+
+ if (!seq_playing)
+ seq_startplay();
+
+ if (!processed && (file->f_flags & O_NONBLOCK))
+ return -EAGAIN;
+ else
+ return processed;
+ }
+ p += ev_size;
+ c -= ev_size;
+ }
+
+ if (!seq_playing)
+ seq_startplay();
+out:
+ return count;
+}
+
+static int seq_queue(unsigned char *note, char nonblock)
+{
+
+ /*
+ * Test if there is space in the queue
+ */
+
+ if (qlen >= SEQ_MAX_QUEUE)
+ if (!seq_playing)
+ seq_startplay(); /*
+ * Give chance to drain the queue
+ */
+
+ if (!nonblock && qlen >= SEQ_MAX_QUEUE && !waitqueue_active(&seq_sleeper)) {
+ /*
+ * Sleep until there is enough space on the queue
+ */
+ interruptible_sleep_on(&seq_sleeper);
+ }
+ if (qlen >= SEQ_MAX_QUEUE)
+ {
+ return 0; /*
+ * To be sure
+ */
+ }
+ memcpy(&queue[qtail * EV_SZ], note, EV_SZ);
+
+ qtail = (qtail + 1) % SEQ_MAX_QUEUE;
+ qlen++;
+
+ return 1;
+}
+
+static int extended_event(unsigned char *q)
+{
+ int dev = q[2];
+
+ if (dev < 0 || dev >= max_synthdev)
+ return -ENXIO;
+
+ if (!(synth_open_mask & (1 << dev)))
+ return -ENXIO;
+
+ switch (q[1])
+ {
+ case SEQ_NOTEOFF:
+ synth_devs[dev]->kill_note(dev, q[3], q[4], q[5]);
+ break;
+
+ case SEQ_NOTEON:
+ if (q[4] > 127 && q[4] != 255)
+ return 0;
+
+ if (q[5] == 0)
+ {
+ synth_devs[dev]->kill_note(dev, q[3], q[4], q[5]);
+ break;
+ }
+ synth_devs[dev]->start_note(dev, q[3], q[4], q[5]);
+ break;
+
+ case SEQ_PGMCHANGE:
+ synth_devs[dev]->set_instr(dev, q[3], q[4]);
+ break;
+
+ case SEQ_AFTERTOUCH:
+ synth_devs[dev]->aftertouch(dev, q[3], q[4]);
+ break;
+
+ case SEQ_BALANCE:
+ synth_devs[dev]->panning(dev, q[3], (char) q[4]);
+ break;
+
+ case SEQ_CONTROLLER:
+ synth_devs[dev]->controller(dev, q[3], q[4], (short) (q[5] | (q[6] << 8)));
+ break;
+
+ case SEQ_VOLMODE:
+ if (synth_devs[dev]->volume_method != NULL)
+ synth_devs[dev]->volume_method(dev, q[3]);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int find_voice(int dev, int chn, int note)
+{
+ unsigned short key;
+ int i;
+
+ key = (chn << 8) | (note + 1);
+ for (i = 0; i < synth_devs[dev]->alloc.max_voice; i++)
+ if (synth_devs[dev]->alloc.map[i] == key)
+ return i;
+ return -1;
+}
+
+static int alloc_voice(int dev, int chn, int note)
+{
+ unsigned short key;
+ int voice;
+
+ key = (chn << 8) | (note + 1);
+
+ voice = synth_devs[dev]->alloc_voice(dev, chn, note,
+ &synth_devs[dev]->alloc);
+ synth_devs[dev]->alloc.map[voice] = key;
+ synth_devs[dev]->alloc.alloc_times[voice] =
+ synth_devs[dev]->alloc.timestamp++;
+ return voice;
+}
+
+static void seq_chn_voice_event(unsigned char *event_rec)
+{
+#define dev event_rec[1]
+#define cmd event_rec[2]
+#define chn event_rec[3]
+#define note event_rec[4]
+#define parm event_rec[5]
+
+ int voice = -1;
+
+ if ((int) dev > max_synthdev || synth_devs[dev] == NULL)
+ return;
+ if (!(synth_open_mask & (1 << dev)))
+ return;
+ if (!synth_devs[dev])
+ return;
+
+ if (seq_mode == SEQ_2)
+ {
+ if (synth_devs[dev]->alloc_voice)
+ voice = find_voice(dev, chn, note);
+
+ if (cmd == MIDI_NOTEON && parm == 0)
+ {
+ cmd = MIDI_NOTEOFF;
+ parm = 64;
+ }
+ }
+
+ switch (cmd)
+ {
+ case MIDI_NOTEON:
+ if (note > 127 && note != 255) /* Not a seq2 feature */
+ return;
+
+ if (voice == -1 && seq_mode == SEQ_2 && synth_devs[dev]->alloc_voice)
+ {
+ /* Internal synthesizer (FM, GUS, etc) */
+ voice = alloc_voice(dev, chn, note);
+ }
+ if (voice == -1)
+ voice = chn;
+
+ if (seq_mode == SEQ_2 && (int) dev < num_synths)
+ {
+ /*
+ * The MIDI channel 10 is a percussive channel. Use the note
+ * number to select the proper patch (128 to 255) to play.
+ */
+
+ if (chn == 9)
+ {
+ synth_devs[dev]->set_instr(dev, voice, 128 + note);
+ synth_devs[dev]->chn_info[chn].pgm_num = 128 + note;
+ }
+ synth_devs[dev]->setup_voice(dev, voice, chn);
+ }
+ synth_devs[dev]->start_note(dev, voice, note, parm);
+ break;
+
+ case MIDI_NOTEOFF:
+ if (voice == -1)
+ voice = chn;
+ synth_devs[dev]->kill_note(dev, voice, note, parm);
+ break;
+
+ case MIDI_KEY_PRESSURE:
+ if (voice == -1)
+ voice = chn;
+ synth_devs[dev]->aftertouch(dev, voice, parm);
+ break;
+
+ default:;
+ }
+#undef dev
+#undef cmd
+#undef chn
+#undef note
+#undef parm
+}
+
+
+static void seq_chn_common_event(unsigned char *event_rec)
+{
+ unsigned char dev = event_rec[1];
+ unsigned char cmd = event_rec[2];
+ unsigned char chn = event_rec[3];
+ unsigned char p1 = event_rec[4];
+
+ /* unsigned char p2 = event_rec[5]; */
+ unsigned short w14 = *(short *) &event_rec[6];
+
+ if ((int) dev > max_synthdev || synth_devs[dev] == NULL)
+ return;
+ if (!(synth_open_mask & (1 << dev)))
+ return;
+ if (!synth_devs[dev])
+ return;
+
+ switch (cmd)
+ {
+ case MIDI_PGM_CHANGE:
+ if (seq_mode == SEQ_2)
+ {
+ synth_devs[dev]->chn_info[chn].pgm_num = p1;
+ if ((int) dev >= num_synths)
+ synth_devs[dev]->set_instr(dev, chn, p1);
+ }
+ else
+ synth_devs[dev]->set_instr(dev, chn, p1);
+
+ break;
+
+ case MIDI_CTL_CHANGE:
+ if (seq_mode == SEQ_2)
+ {
+ if (chn > 15 || p1 > 127)
+ break;
+
+ synth_devs[dev]->chn_info[chn].controllers[p1] = w14 & 0x7f;
+
+ if (p1 < 32) /* Setting MSB should clear LSB to 0 */
+ synth_devs[dev]->chn_info[chn].controllers[p1 + 32] = 0;
+
+ if ((int) dev < num_synths)
+ {
+ int val = w14 & 0x7f;
+ int i, key;
+
+ if (p1 < 64) /* Combine MSB and LSB */
+ {
+ val = ((synth_devs[dev]->
+ chn_info[chn].controllers[p1 & ~32] & 0x7f) << 7)
+ | (synth_devs[dev]->
+ chn_info[chn].controllers[p1 | 32] & 0x7f);
+ p1 &= ~32;
+ }
+ /* Handle all playing notes on this channel */
+
+ key = ((int) chn << 8);
+
+ for (i = 0; i < synth_devs[dev]->alloc.max_voice; i++)
+ if ((synth_devs[dev]->alloc.map[i] & 0xff00) == key)
+ synth_devs[dev]->controller(dev, i, p1, val);
+ }
+ else
+ synth_devs[dev]->controller(dev, chn, p1, w14);
+ }
+ else /* Mode 1 */
+ synth_devs[dev]->controller(dev, chn, p1, w14);
+ break;
+
+ case MIDI_PITCH_BEND:
+ if (seq_mode == SEQ_2)
+ {
+ synth_devs[dev]->chn_info[chn].bender_value = w14;
+
+ if ((int) dev < num_synths)
+ {
+ /* Handle all playing notes on this channel */
+ int i, key;
+
+ key = (chn << 8);
+
+ for (i = 0; i < synth_devs[dev]->alloc.max_voice; i++)
+ if ((synth_devs[dev]->alloc.map[i] & 0xff00) == key)
+ synth_devs[dev]->bender(dev, i, w14);
+ }
+ else
+ synth_devs[dev]->bender(dev, chn, w14);
+ }
+ else /* MODE 1 */
+ synth_devs[dev]->bender(dev, chn, w14);
+ break;
+
+ default:;
+ }
+}
+
+static int seq_timing_event(unsigned char *event_rec)
+{
+ unsigned char cmd = event_rec[1];
+ unsigned int parm = *(int *) &event_rec[4];
+
+ if (seq_mode == SEQ_2)
+ {
+ int ret;
+
+ if ((ret = tmr->event(tmr_no, event_rec)) == TIMER_ARMED)
+ if ((SEQ_MAX_QUEUE - qlen) >= output_threshold)
+ wake_up(&seq_sleeper);
+ return ret;
+ }
+ switch (cmd)
+ {
+ case TMR_WAIT_REL:
+ parm += prev_event_time;
+
+ /*
+ * NOTE! No break here. Execution of TMR_WAIT_REL continues in the
+ * next case (TMR_WAIT_ABS)
+ */
+
+ case TMR_WAIT_ABS:
+ if (parm > 0)
+ {
+ long time;
+
+ time = parm;
+ prev_event_time = time;
+
+ seq_playing = 1;
+ request_sound_timer(time);
+
+ if ((SEQ_MAX_QUEUE - qlen) >= output_threshold)
+ wake_up(&seq_sleeper);
+ return TIMER_ARMED;
+ }
+ break;
+
+ case TMR_START:
+ seq_time = jiffies;
+ prev_input_time = 0;
+ prev_event_time = 0;
+ break;
+
+ case TMR_STOP:
+ break;
+
+ case TMR_CONTINUE:
+ break;
+
+ case TMR_TEMPO:
+ break;
+
+ case TMR_ECHO:
+ if (seq_mode == SEQ_2)
+ seq_copy_to_input(event_rec, 8);
+ else
+ {
+ parm = (parm << 8 | SEQ_ECHO);
+ seq_copy_to_input((unsigned char *) &parm, 4);
+ }
+ break;
+
+ default:;
+ }
+
+ return TIMER_NOT_ARMED;
+}
+
+static void seq_local_event(unsigned char *event_rec)
+{
+ unsigned char cmd = event_rec[1];
+ unsigned int parm = *((unsigned int *) &event_rec[4]);
+
+ switch (cmd)
+ {
+ case LOCL_STARTAUDIO:
+ DMAbuf_start_devices(parm);
+ break;
+
+ default:;
+ }
+}
+
+static void seq_sysex_message(unsigned char *event_rec)
+{
+ unsigned int dev = event_rec[1];
+ int i, l = 0;
+ unsigned char *buf = &event_rec[2];
+
+ if (dev > max_synthdev)
+ return;
+ if (!(synth_open_mask & (1 << dev)))
+ return;
+ if (!synth_devs[dev])
+ return;
+
+ l = 0;
+ for (i = 0; i < 6 && buf[i] != 0xff; i++)
+ l = i + 1;
+
+ if (!synth_devs[dev]->send_sysex)
+ return;
+ if (l > 0)
+ synth_devs[dev]->send_sysex(dev, buf, l);
+}
+
+static int play_event(unsigned char *q)
+{
+ /*
+ * NOTE! This routine returns
+ * 0 = normal event played.
+ * 1 = Timer armed. Suspend playback until timer callback.
+ * 2 = MIDI output buffer full. Restore queue and suspend until timer
+ */
+ unsigned int *delay;
+
+ switch (q[0])
+ {
+ case SEQ_NOTEOFF:
+ if (synth_open_mask & (1 << 0))
+ if (synth_devs[0])
+ synth_devs[0]->kill_note(0, q[1], 255, q[3]);
+ break;
+
+ case SEQ_NOTEON:
+ if (q[4] < 128 || q[4] == 255)
+ if (synth_open_mask & (1 << 0))
+ if (synth_devs[0])
+ synth_devs[0]->start_note(0, q[1], q[2], q[3]);
+ break;
+
+ case SEQ_WAIT:
+ delay = (unsigned int *) q; /*
+ * Bytes 1 to 3 are containing the *
+ * delay in 'ticks'
+ */
+ *delay = (*delay >> 8) & 0xffffff;
+
+ if (*delay > 0)
+ {
+ long time;
+
+ seq_playing = 1;
+ time = *delay;
+ prev_event_time = time;
+
+ request_sound_timer(time);
+
+ if ((SEQ_MAX_QUEUE - qlen) >= output_threshold)
+ wake_up(&seq_sleeper);
+ /*
+ * The timer is now active and will reinvoke this function
+ * after the timer expires. Return to the caller now.
+ */
+ return 1;
+ }
+ break;
+
+ case SEQ_PGMCHANGE:
+ if (synth_open_mask & (1 << 0))
+ if (synth_devs[0])
+ synth_devs[0]->set_instr(0, q[1], q[2]);
+ break;
+
+ case SEQ_SYNCTIMER: /*
+ * Reset timer
+ */
+ seq_time = jiffies;
+ prev_input_time = 0;
+ prev_event_time = 0;
+ break;
+
+ case SEQ_MIDIPUTC: /*
+ * Put a midi character
+ */
+ if (midi_opened[q[2]])
+ {
+ int dev;
+
+ dev = q[2];
+
+ if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
+ break;
+
+ if (!midi_devs[dev]->outputc(dev, q[1]))
+ {
+ /*
+ * Output FIFO is full. Wait one timer cycle and try again.
+ */
+
+ seq_playing = 1;
+ request_sound_timer(-1);
+ return 2;
+ }
+ else
+ midi_written[dev] = 1;
+ }
+ break;
+
+ case SEQ_ECHO:
+ seq_copy_to_input(q, 4); /*
+ * Echo back to the process
+ */
+ break;
+
+ case SEQ_PRIVATE:
+ if ((int) q[1] < max_synthdev)
+ synth_devs[q[1]]->hw_control(q[1], q);
+ break;
+
+ case SEQ_EXTENDED:
+ extended_event(q);
+ break;
+
+ case EV_CHN_VOICE:
+ seq_chn_voice_event(q);
+ break;
+
+ case EV_CHN_COMMON:
+ seq_chn_common_event(q);
+ break;
+
+ case EV_TIMING:
+ if (seq_timing_event(q) == TIMER_ARMED)
+ {
+ return 1;
+ }
+ break;
+
+ case EV_SEQ_LOCAL:
+ seq_local_event(q);
+ break;
+
+ case EV_SYSEX:
+ seq_sysex_message(q);
+ break;
+
+ default:;
+ }
+ return 0;
+}
+
+/* called also as timer in irq context */
+static void seq_startplay(void)
+{
+ int this_one, action;
+ unsigned long flags;
+
+ while (qlen > 0)
+ {
+
+ spin_lock_irqsave(&lock,flags);
+ qhead = ((this_one = qhead) + 1) % SEQ_MAX_QUEUE;
+ qlen--;
+ spin_unlock_irqrestore(&lock,flags);
+
+ seq_playing = 1;
+
+ if ((action = play_event(&queue[this_one * EV_SZ])))
+ { /* Suspend playback. Next timer routine invokes this routine again */
+ if (action == 2)
+ {
+ qlen++;
+ qhead = this_one;
+ }
+ return;
+ }
+ }
+
+ seq_playing = 0;
+
+ if ((SEQ_MAX_QUEUE - qlen) >= output_threshold)
+ wake_up(&seq_sleeper);
+}
+
+static void reset_controllers(int dev, unsigned char *controller, int update_dev)
+{
+ int i;
+ for (i = 0; i < 128; i++)
+ controller[i] = ctrl_def_values[i];
+}
+
+static void setup_mode2(void)
+{
+ int dev;
+
+ max_synthdev = num_synths;
+
+ for (dev = 0; dev < num_midis; dev++)
+ {
+ if (midi_devs[dev] && midi_devs[dev]->converter != NULL)
+ {
+ synth_devs[max_synthdev++] = midi_devs[dev]->converter;
+ }
+ }
+
+ for (dev = 0; dev < max_synthdev; dev++)
+ {
+ int chn;
+
+ synth_devs[dev]->sysex_ptr = 0;
+ synth_devs[dev]->emulation = 0;
+
+ for (chn = 0; chn < 16; chn++)
+ {
+ synth_devs[dev]->chn_info[chn].pgm_num = 0;
+ reset_controllers(dev,
+ synth_devs[dev]->chn_info[chn].controllers,0);
+ synth_devs[dev]->chn_info[chn].bender_value = (1 << 7); /* Neutral */
+ synth_devs[dev]->chn_info[chn].bender_range = 200;
+ }
+ }
+ max_mididev = 0;
+ seq_mode = SEQ_2;
+}
+
+int sequencer_open(int dev, struct file *file)
+{
+ int retval, mode, i;
+ int level, tmp;
+
+ if (!sequencer_ok)
+ sequencer_init();
+
+ level = ((dev & 0x0f) == SND_DEV_SEQ2) ? 2 : 1;
+
+ dev = dev >> 4;
+ mode = translate_mode(file);
+
+ DEB(printk("sequencer_open(dev=%d)\n", dev));
+
+ if (!sequencer_ok)
+ {
+/* printk("Sound card: sequencer not initialized\n");*/
+ return -ENXIO;
+ }
+ if (dev) /* Patch manager device (obsolete) */
+ return -ENXIO;
+
+ if(synth_devs[dev] == NULL)
+ request_module("synth0");
+
+ if (mode == OPEN_READ)
+ {
+ if (!num_midis)
+ {
+ /*printk("Sequencer: No MIDI devices. Input not possible\n");*/
+ sequencer_busy = 0;
+ return -ENXIO;
+ }
+ }
+ if (sequencer_busy)
+ {
+ return -EBUSY;
+ }
+ sequencer_busy = 1;
+ obsolete_api_used = 0;
+
+ max_mididev = num_midis;
+ max_synthdev = num_synths;
+ pre_event_timeout = MAX_SCHEDULE_TIMEOUT;
+ seq_mode = SEQ_1;
+
+ if (pending_timer != -1)
+ {
+ tmr_no = pending_timer;
+ pending_timer = -1;
+ }
+ if (tmr_no == -1) /* Not selected yet */
+ {
+ int i, best;
+
+ best = -1;
+ for (i = 0; i < num_sound_timers; i++)
+ if (sound_timer_devs[i] && sound_timer_devs[i]->priority > best)
+ {
+ tmr_no = i;
+ best = sound_timer_devs[i]->priority;
+ }
+ if (tmr_no == -1) /* Should not be */
+ tmr_no = 0;
+ }
+ tmr = sound_timer_devs[tmr_no];
+
+ if (level == 2)
+ {
+ if (tmr == NULL)
+ {
+ /*printk("sequencer: No timer for level 2\n");*/
+ sequencer_busy = 0;
+ return -ENXIO;
+ }
+ setup_mode2();
+ }
+ if (!max_synthdev && !max_mididev)
+ {
+ sequencer_busy=0;
+ return -ENXIO;
+ }
+
+ synth_open_mask = 0;
+
+ for (i = 0; i < max_mididev; i++)
+ {
+ midi_opened[i] = 0;
+ midi_written[i] = 0;
+ }
+
+ for (i = 0; i < max_synthdev; i++)
+ {
+ if (synth_devs[i]==NULL)
+ continue;
+
+ if (!try_module_get(synth_devs[i]->owner))
+ continue;
+
+ if ((tmp = synth_devs[i]->open(i, mode)) < 0)
+ {
+ printk(KERN_WARNING "Sequencer: Warning! Cannot open synth device #%d (%d)\n", i, tmp);
+ if (synth_devs[i]->midi_dev)
+ printk(KERN_WARNING "(Maps to MIDI dev #%d)\n", synth_devs[i]->midi_dev);
+ }
+ else
+ {
+ synth_open_mask |= (1 << i);
+ if (synth_devs[i]->midi_dev)
+ midi_opened[synth_devs[i]->midi_dev] = 1;
+ }
+ }
+
+ seq_time = jiffies;
+
+ prev_input_time = 0;
+ prev_event_time = 0;
+
+ if (seq_mode == SEQ_1 && (mode == OPEN_READ || mode == OPEN_READWRITE))
+ {
+ /*
+ * Initialize midi input devices
+ */
+
+ for (i = 0; i < max_mididev; i++)
+ if (!midi_opened[i] && midi_devs[i])
+ {
+ if (!try_module_get(midi_devs[i]->owner))
+ continue;
+
+ if ((retval = midi_devs[i]->open(i, mode,
+ sequencer_midi_input, sequencer_midi_output)) >= 0)
+ {
+ midi_opened[i] = 1;
+ }
+ }
+ }
+
+ if (seq_mode == SEQ_2) {
+ if (try_module_get(tmr->owner))
+ tmr->open(tmr_no, seq_mode);
+ }
+
+ init_waitqueue_head(&seq_sleeper);
+ init_waitqueue_head(&midi_sleeper);
+ output_threshold = SEQ_MAX_QUEUE / 2;
+
+ return 0;
+}
+
+static void seq_drain_midi_queues(void)
+{
+ int i, n;
+
+ /*
+ * Give the Midi drivers time to drain their output queues
+ */
+
+ n = 1;
+
+ while (!signal_pending(current) && n)
+ {
+ n = 0;
+
+ for (i = 0; i < max_mididev; i++)
+ if (midi_opened[i] && midi_written[i])
+ if (midi_devs[i]->buffer_status != NULL)
+ if (midi_devs[i]->buffer_status(i))
+ n++;
+
+ /*
+ * Let's have a delay
+ */
+
+ if (n)
+ interruptible_sleep_on_timeout(&seq_sleeper,
+ HZ/10);
+ }
+}
+
+void sequencer_release(int dev, struct file *file)
+{
+ int i;
+ int mode = translate_mode(file);
+
+ dev = dev >> 4;
+
+ DEB(printk("sequencer_release(dev=%d)\n", dev));
+
+ /*
+ * Wait until the queue is empty (if we don't have nonblock)
+ */
+
+ if (mode != OPEN_READ && !(file->f_flags & O_NONBLOCK))
+ {
+ while (!signal_pending(current) && qlen > 0)
+ {
+ seq_sync();
+ interruptible_sleep_on_timeout(&seq_sleeper,
+ 3*HZ);
+ /* Extra delay */
+ }
+ }
+
+ if (mode != OPEN_READ)
+ seq_drain_midi_queues(); /*
+ * Ensure the output queues are empty
+ */
+ seq_reset();
+ if (mode != OPEN_READ)
+ seq_drain_midi_queues(); /*
+ * Flush the all notes off messages
+ */
+
+ for (i = 0; i < max_synthdev; i++)
+ {
+ if (synth_open_mask & (1 << i)) /*
+ * Actually opened
+ */
+ if (synth_devs[i])
+ {
+ synth_devs[i]->close(i);
+
+ module_put(synth_devs[i]->owner);
+
+ if (synth_devs[i]->midi_dev)
+ midi_opened[synth_devs[i]->midi_dev] = 0;
+ }
+ }
+
+ for (i = 0; i < max_mididev; i++)
+ {
+ if (midi_opened[i]) {
+ midi_devs[i]->close(i);
+ module_put(midi_devs[i]->owner);
+ }
+ }
+
+ if (seq_mode == SEQ_2) {
+ tmr->close(tmr_no);
+ module_put(tmr->owner);
+ }
+
+ if (obsolete_api_used)
+ printk(KERN_WARNING "/dev/music: Obsolete (4 byte) API was used by %s\n", current->comm);
+ sequencer_busy = 0;
+}
+
+static int seq_sync(void)
+{
+ if (qlen && !seq_playing && !signal_pending(current))
+ seq_startplay();
+
+ if (qlen > 0)
+ interruptible_sleep_on_timeout(&seq_sleeper, HZ);
+ return qlen;
+}
+
+static void midi_outc(int dev, unsigned char data)
+{
+ /*
+ * NOTE! Calls sleep(). Don't call this from interrupt.
+ */
+
+ int n;
+ unsigned long flags;
+
+ /*
+ * This routine sends one byte to the Midi channel.
+ * If the output FIFO is full, it waits until there
+ * is space in the queue
+ */
+
+ n = 3 * HZ; /* Timeout */
+
+ spin_lock_irqsave(&lock,flags);
+ while (n && !midi_devs[dev]->outputc(dev, data)) {
+ interruptible_sleep_on_timeout(&seq_sleeper, HZ/25);
+ n--;
+ }
+ spin_unlock_irqrestore(&lock,flags);
+}
+
+static void seq_reset(void)
+{
+ /*
+ * NOTE! Calls sleep(). Don't call this from interrupt.
+ */
+
+ int i;
+ int chn;
+ unsigned long flags;
+
+ sound_stop_timer();
+
+ seq_time = jiffies;
+ prev_input_time = 0;
+ prev_event_time = 0;
+
+ qlen = qhead = qtail = 0;
+ iqlen = iqhead = iqtail = 0;
+
+ for (i = 0; i < max_synthdev; i++)
+ if (synth_open_mask & (1 << i))
+ if (synth_devs[i])
+ synth_devs[i]->reset(i);
+
+ if (seq_mode == SEQ_2)
+ {
+ for (chn = 0; chn < 16; chn++)
+ for (i = 0; i < max_synthdev; i++)
+ if (synth_open_mask & (1 << i))
+ if (synth_devs[i])
+ {
+ synth_devs[i]->controller(i, chn, 123, 0); /* All notes off */
+ synth_devs[i]->controller(i, chn, 121, 0); /* Reset all ctl */
+ synth_devs[i]->bender(i, chn, 1 << 13); /* Bender off */
+ }
+ }
+ else /* seq_mode == SEQ_1 */
+ {
+ for (i = 0; i < max_mididev; i++)
+ if (midi_written[i]) /*
+ * Midi used. Some notes may still be playing
+ */
+ {
+ /*
+ * Sending just a ACTIVE SENSING message should be enough to stop all
+ * playing notes. Since there are devices not recognizing the
+ * active sensing, we have to send some all notes off messages also.
+ */
+ midi_outc(i, 0xfe);
+
+ for (chn = 0; chn < 16; chn++)
+ {
+ midi_outc(i, (unsigned char) (0xb0 + (chn & 0x0f))); /* control change */
+ midi_outc(i, 0x7b); /* All notes off */
+ midi_outc(i, 0); /* Dummy parameter */
+ }
+
+ midi_devs[i]->close(i);
+
+ midi_written[i] = 0;
+ midi_opened[i] = 0;
+ }
+ }
+
+ seq_playing = 0;
+
+ spin_lock_irqsave(&lock,flags);
+
+ if (waitqueue_active(&seq_sleeper)) {
+ /* printk( "Sequencer Warning: Unexpected sleeping process - Waking up\n"); */
+ wake_up(&seq_sleeper);
+ }
+ spin_unlock_irqrestore(&lock,flags);
+}
+
+static void seq_panic(void)
+{
+ /*
+ * This routine is called by the application in case the user
+ * wants to reset the system to the default state.
+ */
+
+ seq_reset();
+
+ /*
+ * Since some of the devices don't recognize the active sensing and
+ * all notes off messages, we have to shut all notes manually.
+ *
+ * TO BE IMPLEMENTED LATER
+ */
+
+ /*
+ * Also return the controllers to their default states
+ */
+}
+
+int sequencer_ioctl(int dev, struct file *file, unsigned int cmd, void __user *arg)
+{
+ int midi_dev, orig_dev, val, err;
+ int mode = translate_mode(file);
+ struct synth_info inf;
+ struct seq_event_rec event_rec;
+ unsigned long flags;
+ int __user *p = arg;
+
+ orig_dev = dev = dev >> 4;
+
+ switch (cmd)
+ {
+ case SNDCTL_TMR_TIMEBASE:
+ case SNDCTL_TMR_TEMPO:
+ case SNDCTL_TMR_START:
+ case SNDCTL_TMR_STOP:
+ case SNDCTL_TMR_CONTINUE:
+ case SNDCTL_TMR_METRONOME:
+ case SNDCTL_TMR_SOURCE:
+ if (seq_mode != SEQ_2)
+ return -EINVAL;
+ return tmr->ioctl(tmr_no, cmd, arg);
+
+ case SNDCTL_TMR_SELECT:
+ if (seq_mode != SEQ_2)
+ return -EINVAL;
+ if (get_user(pending_timer, p))
+ return -EFAULT;
+ if (pending_timer < 0 || pending_timer >= num_sound_timers || sound_timer_devs[pending_timer] == NULL)
+ {
+ pending_timer = -1;
+ return -EINVAL;
+ }
+ val = pending_timer;
+ break;
+
+ case SNDCTL_SEQ_PANIC:
+ seq_panic();
+ return -EINVAL;
+
+ case SNDCTL_SEQ_SYNC:
+ if (mode == OPEN_READ)
+ return 0;
+ while (qlen > 0 && !signal_pending(current))
+ seq_sync();
+ return qlen ? -EINTR : 0;
+
+ case SNDCTL_SEQ_RESET:
+ seq_reset();
+ return 0;
+
+ case SNDCTL_SEQ_TESTMIDI:
+ if (__get_user(midi_dev, p))
+ return -EFAULT;
+ if (midi_dev < 0 || midi_dev >= max_mididev || !midi_devs[midi_dev])
+ return -ENXIO;
+
+ if (!midi_opened[midi_dev] &&
+ (err = midi_devs[midi_dev]->open(midi_dev, mode, sequencer_midi_input,
+ sequencer_midi_output)) < 0)
+ return err;
+ midi_opened[midi_dev] = 1;
+ return 0;
+
+ case SNDCTL_SEQ_GETINCOUNT:
+ if (mode == OPEN_WRITE)
+ return 0;
+ val = iqlen;
+ break;
+
+ case SNDCTL_SEQ_GETOUTCOUNT:
+ if (mode == OPEN_READ)
+ return 0;
+ val = SEQ_MAX_QUEUE - qlen;
+ break;
+
+ case SNDCTL_SEQ_GETTIME:
+ if (seq_mode == SEQ_2)
+ return tmr->ioctl(tmr_no, cmd, arg);
+ val = jiffies - seq_time;
+ break;
+
+ case SNDCTL_SEQ_CTRLRATE:
+ /*
+ * If *arg == 0, just return the current rate
+ */
+ if (seq_mode == SEQ_2)
+ return tmr->ioctl(tmr_no, cmd, arg);
+
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val != 0)
+ return -EINVAL;
+ val = HZ;
+ break;
+
+ case SNDCTL_SEQ_RESETSAMPLES:
+ case SNDCTL_SYNTH_REMOVESAMPLE:
+ case SNDCTL_SYNTH_CONTROL:
+ if (get_user(dev, p))
+ return -EFAULT;
+ if (dev < 0 || dev >= num_synths || synth_devs[dev] == NULL)
+ return -ENXIO;
+ if (!(synth_open_mask & (1 << dev)) && !orig_dev)
+ return -EBUSY;
+ return synth_devs[dev]->ioctl(dev, cmd, arg);
+
+ case SNDCTL_SEQ_NRSYNTHS:
+ val = max_synthdev;
+ break;
+
+ case SNDCTL_SEQ_NRMIDIS:
+ val = max_mididev;
+ break;
+
+ case SNDCTL_SYNTH_MEMAVL:
+ if (get_user(dev, p))
+ return -EFAULT;
+ if (dev < 0 || dev >= num_synths || synth_devs[dev] == NULL)
+ return -ENXIO;
+ if (!(synth_open_mask & (1 << dev)) && !orig_dev)
+ return -EBUSY;
+ val = synth_devs[dev]->ioctl(dev, cmd, arg);
+ break;
+
+ case SNDCTL_FM_4OP_ENABLE:
+ if (get_user(dev, p))
+ return -EFAULT;
+ if (dev < 0 || dev >= num_synths || synth_devs[dev] == NULL)
+ return -ENXIO;
+ if (!(synth_open_mask & (1 << dev)))
+ return -ENXIO;
+ synth_devs[dev]->ioctl(dev, cmd, arg);
+ return 0;
+
+ case SNDCTL_SYNTH_INFO:
+ if (get_user(dev, &((struct synth_info __user *)arg)->device))
+ return -EFAULT;
+ if (dev < 0 || dev >= max_synthdev)
+ return -ENXIO;
+ if (!(synth_open_mask & (1 << dev)) && !orig_dev)
+ return -EBUSY;
+ return synth_devs[dev]->ioctl(dev, cmd, arg);
+
+ /* Like SYNTH_INFO but returns ID in the name field */
+ case SNDCTL_SYNTH_ID:
+ if (get_user(dev, &((struct synth_info __user *)arg)->device))
+ return -EFAULT;
+ if (dev < 0 || dev >= max_synthdev)
+ return -ENXIO;
+ if (!(synth_open_mask & (1 << dev)) && !orig_dev)
+ return -EBUSY;
+ memcpy(&inf, synth_devs[dev]->info, sizeof(inf));
+ strlcpy(inf.name, synth_devs[dev]->id, sizeof(inf.name));
+ inf.device = dev;
+ return copy_to_user(arg, &inf, sizeof(inf))?-EFAULT:0;
+
+ case SNDCTL_SEQ_OUTOFBAND:
+ if (copy_from_user(&event_rec, arg, sizeof(event_rec)))
+ return -EFAULT;
+ spin_lock_irqsave(&lock,flags);
+ play_event(event_rec.arr);
+ spin_unlock_irqrestore(&lock,flags);
+ return 0;
+
+ case SNDCTL_MIDI_INFO:
+ if (get_user(dev, &((struct midi_info __user *)arg)->device))
+ return -EFAULT;
+ if (dev < 0 || dev >= max_mididev || !midi_devs[dev])
+ return -ENXIO;
+ midi_devs[dev]->info.device = dev;
+ return copy_to_user(arg, &midi_devs[dev]->info, sizeof(struct midi_info))?-EFAULT:0;
+
+ case SNDCTL_SEQ_THRESHOLD:
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val < 1)
+ val = 1;
+ if (val >= SEQ_MAX_QUEUE)
+ val = SEQ_MAX_QUEUE - 1;
+ output_threshold = val;
+ return 0;
+
+ case SNDCTL_MIDI_PRETIME:
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val < 0)
+ val = 0;
+ val = (HZ * val) / 10;
+ pre_event_timeout = val;
+ break;
+
+ default:
+ if (mode == OPEN_READ)
+ return -EIO;
+ if (!synth_devs[0])
+ return -ENXIO;
+ if (!(synth_open_mask & (1 << 0)))
+ return -ENXIO;
+ if (!synth_devs[0]->ioctl)
+ return -EINVAL;
+ return synth_devs[0]->ioctl(0, cmd, arg);
+ }
+ return put_user(val, p);
+}
+
+/* No kernel lock - we're using the global irq lock here */
+unsigned int sequencer_poll(int dev, struct file *file, poll_table * wait)
+{
+ unsigned long flags;
+ unsigned int mask = 0;
+
+ dev = dev >> 4;
+
+ spin_lock_irqsave(&lock,flags);
+ /* input */
+ poll_wait(file, &midi_sleeper, wait);
+ if (iqlen)
+ mask |= POLLIN | POLLRDNORM;
+
+ /* output */
+ poll_wait(file, &seq_sleeper, wait);
+ if ((SEQ_MAX_QUEUE - qlen) >= output_threshold)
+ mask |= POLLOUT | POLLWRNORM;
+ spin_unlock_irqrestore(&lock,flags);
+ return mask;
+}
+
+
+void sequencer_timer(unsigned long dummy)
+{
+ seq_startplay();
+}
+EXPORT_SYMBOL(sequencer_timer);
+
+int note_to_freq(int note_num)
+{
+
+ /*
+ * This routine converts a midi note to a frequency (multiplied by 1000)
+ */
+
+ int note, octave, note_freq;
+ static int notes[] =
+ {
+ 261632, 277189, 293671, 311132, 329632, 349232,
+ 369998, 391998, 415306, 440000, 466162, 493880
+ };
+
+#define BASE_OCTAVE 5
+
+ octave = note_num / 12;
+ note = note_num % 12;
+
+ note_freq = notes[note];
+
+ if (octave < BASE_OCTAVE)
+ note_freq >>= (BASE_OCTAVE - octave);
+ else if (octave > BASE_OCTAVE)
+ note_freq <<= (octave - BASE_OCTAVE);
+
+ /*
+ * note_freq >>= 1;
+ */
+
+ return note_freq;
+}
+EXPORT_SYMBOL(note_to_freq);
+
+unsigned long compute_finetune(unsigned long base_freq, int bend, int range,
+ int vibrato_cents)
+{
+ unsigned long amount;
+ int negative, semitones, cents, multiplier = 1;
+
+ if (!bend)
+ return base_freq;
+ if (!range)
+ return base_freq;
+
+ if (!base_freq)
+ return base_freq;
+
+ if (range >= 8192)
+ range = 8192;
+
+ bend = bend * range / 8192; /* Convert to cents */
+ bend += vibrato_cents;
+
+ if (!bend)
+ return base_freq;
+
+ negative = bend < 0 ? 1 : 0;
+
+ if (bend < 0)
+ bend *= -1;
+ if (bend > range)
+ bend = range;
+
+ /*
+ if (bend > 2399)
+ bend = 2399;
+ */
+ while (bend > 2399)
+ {
+ multiplier *= 4;
+ bend -= 2400;
+ }
+
+ semitones = bend / 100;
+ cents = bend % 100;
+
+ amount = (int) (semitone_tuning[semitones] * multiplier * cent_tuning[cents]) / 10000;
+
+ if (negative)
+ return (base_freq * 10000) / amount; /* Bend down */
+ else
+ return (base_freq * amount) / 10000; /* Bend up */
+}
+EXPORT_SYMBOL(compute_finetune);
+
+void sequencer_init(void)
+{
+ if (sequencer_ok)
+ return;
+ queue = vmalloc(SEQ_MAX_QUEUE * EV_SZ);
+ if (queue == NULL)
+ {
+ printk(KERN_ERR "sequencer: Can't allocate memory for sequencer output queue\n");
+ return;
+ }
+ iqueue = vmalloc(SEQ_MAX_QUEUE * IEV_SZ);
+ if (iqueue == NULL)
+ {
+ printk(KERN_ERR "sequencer: Can't allocate memory for sequencer input queue\n");
+ vfree(queue);
+ return;
+ }
+ sequencer_ok = 1;
+}
+EXPORT_SYMBOL(sequencer_init);
+
+void sequencer_unload(void)
+{
+ vfree(queue);
+ vfree(iqueue);
+ queue = iqueue = NULL;
+}
diff --git a/sound/oss/sound_calls.h b/sound/oss/sound_calls.h
new file mode 100644
index 00000000..87d8ad4a
--- /dev/null
+++ b/sound/oss/sound_calls.h
@@ -0,0 +1,87 @@
+/*
+ * DMA buffer calls
+ */
+
+int DMAbuf_open(int dev, int mode);
+int DMAbuf_release(int dev, int mode);
+int DMAbuf_getwrbuffer(int dev, char **buf, int *size, int dontblock);
+int DMAbuf_getrdbuffer(int dev, char **buf, int *len, int dontblock);
+int DMAbuf_rmchars(int dev, int buff_no, int c);
+int DMAbuf_start_output(int dev, int buff_no, int l);
+int DMAbuf_move_wrpointer(int dev, int l);
+/* int DMAbuf_ioctl(int dev, unsigned int cmd, void __user *arg, int local); */
+void DMAbuf_init(int dev, int dma1, int dma2);
+void DMAbuf_deinit(int dev);
+int DMAbuf_start_dma (int dev, unsigned long physaddr, int count, int dma_mode);
+void DMAbuf_inputintr(int dev);
+void DMAbuf_outputintr(int dev, int underflow_flag);
+struct dma_buffparms;
+int DMAbuf_space_in_queue (int dev);
+int DMAbuf_activate_recording (int dev, struct dma_buffparms *dmap);
+int DMAbuf_get_buffer_pointer (int dev, struct dma_buffparms *dmap, int direction);
+void DMAbuf_launch_output(int dev, struct dma_buffparms *dmap);
+unsigned int DMAbuf_poll(struct file *file, int dev, poll_table *wait);
+void DMAbuf_start_devices(unsigned int devmask);
+void DMAbuf_reset (int dev);
+int DMAbuf_sync (int dev);
+
+/*
+ * System calls for /dev/dsp and /dev/audio (audio.c)
+ */
+
+int audio_read (int dev, struct file *file, char __user *buf, int count);
+int audio_write (int dev, struct file *file, const char __user *buf, int count);
+int audio_open (int dev, struct file *file);
+void audio_release (int dev, struct file *file);
+int audio_ioctl (int dev, struct file *file,
+ unsigned int cmd, void __user *arg);
+void audio_init_devices (void);
+void reorganize_buffers (int dev, struct dma_buffparms *dmap, int recording);
+
+/*
+ * System calls for the /dev/sequencer
+ */
+
+int sequencer_read (int dev, struct file *file, char __user *buf, int count);
+int sequencer_write (int dev, struct file *file, const char __user *buf, int count);
+int sequencer_open (int dev, struct file *file);
+void sequencer_release (int dev, struct file *file);
+int sequencer_ioctl (int dev, struct file *file, unsigned int cmd, void __user *arg);
+unsigned int sequencer_poll(int dev, struct file *file, poll_table * wait);
+
+void sequencer_init (void);
+void sequencer_unload (void);
+void sequencer_timer(unsigned long dummy);
+int note_to_freq(int note_num);
+unsigned long compute_finetune(unsigned long base_freq, int bend, int range,
+ int vibrato_bend);
+void seq_input_event(unsigned char *event, int len);
+void seq_copy_to_input (unsigned char *event, int len);
+
+/*
+ * System calls for the /dev/midi
+ */
+
+int MIDIbuf_read (int dev, struct file *file, char __user *buf, int count);
+int MIDIbuf_write (int dev, struct file *file, const char __user *buf, int count);
+int MIDIbuf_open (int dev, struct file *file);
+void MIDIbuf_release (int dev, struct file *file);
+int MIDIbuf_ioctl (int dev, struct file *file, unsigned int cmd, void __user *arg);
+unsigned int MIDIbuf_poll(int dev, struct file *file, poll_table * wait);
+int MIDIbuf_avail(int dev);
+
+void MIDIbuf_bytes_received(int dev, unsigned char *buf, int count);
+
+
+/* From soundcard.c */
+void request_sound_timer (int count);
+void sound_stop_timer(void);
+void conf_printf(char *name, struct address_info *hw_config);
+void conf_printf2(char *name, int base, int irq, int dma, int dma2);
+
+/* From sound_timer.c */
+void sound_timer_interrupt(void);
+void sound_timer_syncinterval(unsigned int new_usecs);
+
+/* From midi_synth.c */
+void do_midi_msg (int synthno, unsigned char *msg, int mlen);
diff --git a/sound/oss/sound_config.h b/sound/oss/sound_config.h
new file mode 100644
index 00000000..9d35c4c6
--- /dev/null
+++ b/sound/oss/sound_config.h
@@ -0,0 +1,147 @@
+/* sound_config.h
+ *
+ * A driver for sound cards, misc. configuration parameters.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+
+#ifndef _SOUND_CONFIG_H_
+#define _SOUND_CONFIG_H_
+
+#include <linux/fs.h>
+#include <linux/sound.h>
+
+#include "os.h"
+#include "soundvers.h"
+
+
+#ifndef SND_DEFAULT_ENABLE
+#define SND_DEFAULT_ENABLE 1
+#endif
+
+#ifndef MAX_REALTIME_FACTOR
+#define MAX_REALTIME_FACTOR 4
+#endif
+
+/*
+ * Use always 64k buffer size. There is no reason to use shorter.
+ */
+#undef DSP_BUFFSIZE
+#define DSP_BUFFSIZE (64*1024)
+
+#ifndef DSP_BUFFCOUNT
+#define DSP_BUFFCOUNT 1 /* 1 is recommended. */
+#endif
+
+#define FM_MONO 0x388 /* This is the I/O address used by AdLib */
+
+#ifndef CONFIG_PAS_BASE
+#define CONFIG_PAS_BASE 0x388
+#endif
+
+/* SEQ_MAX_QUEUE is the maximum number of sequencer events buffered by the
+ driver. (There is no need to alter this) */
+#define SEQ_MAX_QUEUE 1024
+
+#define SBFM_MAXINSTR (256) /* Size of the FM Instrument bank */
+/* 128 instruments for general MIDI setup and 16 unassigned */
+
+#define SND_NDEVS 256 /* Number of supported devices */
+
+#define DSP_DEFAULT_SPEED 8000
+
+#define MAX_AUDIO_DEV 5
+#define MAX_MIXER_DEV 5
+#define MAX_SYNTH_DEV 5
+#define MAX_MIDI_DEV 6
+#define MAX_TIMER_DEV 4
+
+struct address_info {
+ int io_base;
+ int irq;
+ int dma;
+ int dma2;
+ int always_detect; /* 1=Trust me, it's there */
+ char *name;
+ int driver_use_1; /* Driver defined field 1 */
+ int driver_use_2; /* Driver defined field 2 */
+ int *osp; /* OS specific info */
+ int card_subtype; /* Driver specific. Usually 0 */
+ void *memptr; /* Module memory chainer */
+ int slots[6]; /* To remember driver slot ids */
+};
+
+#define SYNTH_MAX_VOICES 32
+
+struct voice_alloc_info {
+ int max_voice;
+ int used_voices;
+ int ptr; /* For device specific use */
+ unsigned short map[SYNTH_MAX_VOICES]; /* (ch << 8) | (note+1) */
+ int timestamp;
+ int alloc_times[SYNTH_MAX_VOICES];
+ };
+
+struct channel_info {
+ int pgm_num;
+ int bender_value;
+ int bender_range;
+ unsigned char controllers[128];
+ };
+
+/*
+ * Process wakeup reasons
+ */
+#define WK_NONE 0x00
+#define WK_WAKEUP 0x01
+#define WK_TIMEOUT 0x02
+#define WK_SIGNAL 0x04
+#define WK_SLEEP 0x08
+#define WK_SELECT 0x10
+#define WK_ABORT 0x20
+
+#define OPEN_READ PCM_ENABLE_INPUT
+#define OPEN_WRITE PCM_ENABLE_OUTPUT
+#define OPEN_READWRITE (OPEN_READ|OPEN_WRITE)
+
+static inline int translate_mode(struct file *file)
+{
+ if (OPEN_READ == (__force int)FMODE_READ &&
+ OPEN_WRITE == (__force int)FMODE_WRITE)
+ return (__force int)(file->f_mode & (FMODE_READ | FMODE_WRITE));
+ else
+ return ((file->f_mode & FMODE_READ) ? OPEN_READ : 0) |
+ ((file->f_mode & FMODE_WRITE) ? OPEN_WRITE : 0);
+}
+
+#include "sound_calls.h"
+#include "dev_table.h"
+
+#ifndef DEB
+#define DEB(x)
+#endif
+
+#ifndef DDB
+#define DDB(x) do {} while (0)
+#endif
+
+#ifndef MDB
+#ifdef MODULE
+#define MDB(x) x
+#else
+#define MDB(x)
+#endif
+#endif
+
+#define TIMER_ARMED 121234
+#define TIMER_NOT_ARMED 1
+
+#define MAX_MEM_BLOCKS 1024
+
+#endif
diff --git a/sound/oss/sound_firmware.h b/sound/oss/sound_firmware.h
new file mode 100644
index 00000000..0a0cbfdf
--- /dev/null
+++ b/sound/oss/sound_firmware.h
@@ -0,0 +1,2 @@
+extern int mod_firmware_load(const char *fn, char **fp);
+
diff --git a/sound/oss/sound_timer.c b/sound/oss/sound_timer.c
new file mode 100644
index 00000000..48cda6c4
--- /dev/null
+++ b/sound/oss/sound_timer.c
@@ -0,0 +1,327 @@
+/*
+ * sound/oss/sound_timer.c
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ */
+#include <linux/string.h>
+#include <linux/spinlock.h>
+
+#include "sound_config.h"
+
+static volatile int initialized, opened, tmr_running;
+static volatile time_t tmr_offs, tmr_ctr;
+static volatile unsigned long ticks_offs;
+static volatile int curr_tempo, curr_timebase;
+static volatile unsigned long curr_ticks;
+static volatile unsigned long next_event_time;
+static unsigned long prev_event_time;
+static volatile unsigned long usecs_per_tmr; /* Length of the current interval */
+
+static struct sound_lowlev_timer *tmr;
+static DEFINE_SPINLOCK(lock);
+
+static unsigned long tmr2ticks(int tmr_value)
+{
+ /*
+ * Convert timer ticks to MIDI ticks
+ */
+
+ unsigned long tmp;
+ unsigned long scale;
+
+ tmp = tmr_value * usecs_per_tmr; /* Convert to usecs */
+ scale = (60 * 1000000) / (curr_tempo * curr_timebase); /* usecs per MIDI tick */
+ return (tmp + (scale / 2)) / scale;
+}
+
+void reprogram_timer(void)
+{
+ unsigned long usecs_per_tick;
+
+ /*
+ * The user is changing the timer rate before setting a timer
+ * slap, bad bad not allowed.
+ */
+
+ if(!tmr)
+ return;
+
+ usecs_per_tick = (60 * 1000000) / (curr_tempo * curr_timebase);
+
+ /*
+ * Don't kill the system by setting too high timer rate
+ */
+ if (usecs_per_tick < 2000)
+ usecs_per_tick = 2000;
+
+ usecs_per_tmr = tmr->tmr_start(tmr->dev, usecs_per_tick);
+}
+
+void sound_timer_syncinterval(unsigned int new_usecs)
+{
+ /*
+ * This routine is called by the hardware level if
+ * the clock frequency has changed for some reason.
+ */
+ tmr_offs = tmr_ctr;
+ ticks_offs += tmr2ticks(tmr_ctr);
+ tmr_ctr = 0;
+ usecs_per_tmr = new_usecs;
+}
+EXPORT_SYMBOL(sound_timer_syncinterval);
+
+static void tmr_reset(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&lock,flags);
+ tmr_offs = 0;
+ ticks_offs = 0;
+ tmr_ctr = 0;
+ next_event_time = (unsigned long) -1;
+ prev_event_time = 0;
+ curr_ticks = 0;
+ spin_unlock_irqrestore(&lock,flags);
+}
+
+static int timer_open(int dev, int mode)
+{
+ if (opened)
+ return -EBUSY;
+ tmr_reset();
+ curr_tempo = 60;
+ curr_timebase = 100;
+ opened = 1;
+ reprogram_timer();
+ return 0;
+}
+
+static void timer_close(int dev)
+{
+ opened = tmr_running = 0;
+ tmr->tmr_disable(tmr->dev);
+}
+
+static int timer_event(int dev, unsigned char *event)
+{
+ unsigned char cmd = event[1];
+ unsigned long parm = *(int *) &event[4];
+
+ switch (cmd)
+ {
+ case TMR_WAIT_REL:
+ parm += prev_event_time;
+ case TMR_WAIT_ABS:
+ if (parm > 0)
+ {
+ long time;
+
+ if (parm <= curr_ticks) /* It's the time */
+ return TIMER_NOT_ARMED;
+ time = parm;
+ next_event_time = prev_event_time = time;
+ return TIMER_ARMED;
+ }
+ break;
+
+ case TMR_START:
+ tmr_reset();
+ tmr_running = 1;
+ reprogram_timer();
+ break;
+
+ case TMR_STOP:
+ tmr_running = 0;
+ break;
+
+ case TMR_CONTINUE:
+ tmr_running = 1;
+ reprogram_timer();
+ break;
+
+ case TMR_TEMPO:
+ if (parm)
+ {
+ if (parm < 8)
+ parm = 8;
+ if (parm > 250)
+ parm = 250;
+ tmr_offs = tmr_ctr;
+ ticks_offs += tmr2ticks(tmr_ctr);
+ tmr_ctr = 0;
+ curr_tempo = parm;
+ reprogram_timer();
+ }
+ break;
+
+ case TMR_ECHO:
+ seq_copy_to_input(event, 8);
+ break;
+
+ default:;
+ }
+ return TIMER_NOT_ARMED;
+}
+
+static unsigned long timer_get_time(int dev)
+{
+ if (!opened)
+ return 0;
+ return curr_ticks;
+}
+
+static int timer_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+ int __user *p = arg;
+ int val;
+
+ switch (cmd)
+ {
+ case SNDCTL_TMR_SOURCE:
+ val = TMR_INTERNAL;
+ break;
+
+ case SNDCTL_TMR_START:
+ tmr_reset();
+ tmr_running = 1;
+ return 0;
+
+ case SNDCTL_TMR_STOP:
+ tmr_running = 0;
+ return 0;
+
+ case SNDCTL_TMR_CONTINUE:
+ tmr_running = 1;
+ return 0;
+
+ case SNDCTL_TMR_TIMEBASE:
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val)
+ {
+ if (val < 1)
+ val = 1;
+ if (val > 1000)
+ val = 1000;
+ curr_timebase = val;
+ }
+ val = curr_timebase;
+ break;
+
+ case SNDCTL_TMR_TEMPO:
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val)
+ {
+ if (val < 8)
+ val = 8;
+ if (val > 250)
+ val = 250;
+ tmr_offs = tmr_ctr;
+ ticks_offs += tmr2ticks(tmr_ctr);
+ tmr_ctr = 0;
+ curr_tempo = val;
+ reprogram_timer();
+ }
+ val = curr_tempo;
+ break;
+
+ case SNDCTL_SEQ_CTRLRATE:
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val != 0) /* Can't change */
+ return -EINVAL;
+ val = ((curr_tempo * curr_timebase) + 30) / 60;
+ break;
+
+ case SNDCTL_SEQ_GETTIME:
+ val = curr_ticks;
+ break;
+
+ case SNDCTL_TMR_METRONOME:
+ default:
+ return -EINVAL;
+ }
+ return put_user(val, p);
+}
+
+static void timer_arm(int dev, long time)
+{
+ if (time < 0)
+ time = curr_ticks + 1;
+ else if (time <= curr_ticks) /* It's the time */
+ return;
+
+ next_event_time = prev_event_time = time;
+ return;
+}
+
+static struct sound_timer_operations sound_timer =
+{
+ .owner = THIS_MODULE,
+ .info = {"Sound Timer", 0},
+ .priority = 1, /* Priority */
+ .devlink = 0, /* Local device link */
+ .open = timer_open,
+ .close = timer_close,
+ .event = timer_event,
+ .get_time = timer_get_time,
+ .ioctl = timer_ioctl,
+ .arm_timer = timer_arm
+};
+
+void sound_timer_interrupt(void)
+{
+ unsigned long flags;
+
+ if (!opened)
+ return;
+
+ tmr->tmr_restart(tmr->dev);
+
+ if (!tmr_running)
+ return;
+
+ spin_lock_irqsave(&lock,flags);
+ tmr_ctr++;
+ curr_ticks = ticks_offs + tmr2ticks(tmr_ctr);
+
+ if (curr_ticks >= next_event_time)
+ {
+ next_event_time = (unsigned long) -1;
+ sequencer_timer(0);
+ }
+ spin_unlock_irqrestore(&lock,flags);
+}
+EXPORT_SYMBOL(sound_timer_interrupt);
+
+void sound_timer_init(struct sound_lowlev_timer *t, char *name)
+{
+ int n;
+
+ if (initialized)
+ {
+ if (t->priority <= tmr->priority)
+ return; /* There is already a similar or better timer */
+ tmr = t;
+ return;
+ }
+ initialized = 1;
+ tmr = t;
+
+ n = sound_alloc_timerdev();
+ if (n == -1)
+ n = 0; /* Overwrite the system timer */
+ strcpy(sound_timer.info.name, name);
+ sound_timer_devs[n] = &sound_timer;
+}
+EXPORT_SYMBOL(sound_timer_init);
+
diff --git a/sound/oss/soundcard.c b/sound/oss/soundcard.c
new file mode 100644
index 00000000..7c7793a0
--- /dev/null
+++ b/sound/oss/soundcard.c
@@ -0,0 +1,739 @@
+/*
+ * linux/sound/oss/soundcard.c
+ *
+ * Sound card driver for Linux
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ * integrated sound_switch.c
+ * Stefan Reinauer : integrated /proc/sound (equals to /dev/sndstat,
+ * which should disappear in the near future)
+ * Eric Dumas : devfs support (22-Jan-98) <dumas@linux.eu.org> with
+ * fixups by C. Scott Ananian <cananian@alumni.princeton.edu>
+ * Richard Gooch : moved common (non OSS-specific) devices to sound_core.c
+ * Rob Riggs : Added persistent DMA buffers support (1998/10/17)
+ * Christoph Hellwig : Some cleanup work (2000/03/01)
+ */
+
+
+#include "sound_config.h"
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/fcntl.h>
+#include <linux/ctype.h>
+#include <linux/stddef.h>
+#include <linux/kmod.h>
+#include <linux/kernel.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <linux/wait.h>
+#include <linux/ioport.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/device.h>
+
+/*
+ * This ought to be moved into include/asm/dma.h
+ */
+#ifndef valid_dma
+#define valid_dma(n) ((n) >= 0 && (n) < MAX_DMA_CHANNELS && (n) != 4)
+#endif
+
+/*
+ * Table for permanently allocated memory (used when unloading the module)
+ */
+void * sound_mem_blocks[MAX_MEM_BLOCKS];
+static DEFINE_MUTEX(soundcard_mutex);
+int sound_nblocks = 0;
+
+/* Persistent DMA buffers */
+#ifdef CONFIG_SOUND_DMAP
+int sound_dmap_flag = 1;
+#else
+int sound_dmap_flag = 0;
+#endif
+
+static char dma_alloc_map[MAX_DMA_CHANNELS];
+
+#define DMA_MAP_UNAVAIL 0
+#define DMA_MAP_FREE 1
+#define DMA_MAP_BUSY 2
+
+
+unsigned long seq_time = 0; /* Time for /dev/sequencer */
+extern struct class *sound_class;
+
+/*
+ * Table for configurable mixer volume handling
+ */
+static mixer_vol_table mixer_vols[MAX_MIXER_DEV];
+static int num_mixer_volumes;
+
+int *load_mixer_volumes(char *name, int *levels, int present)
+{
+ int i, n;
+
+ for (i = 0; i < num_mixer_volumes; i++) {
+ if (strncmp(name, mixer_vols[i].name, 32) == 0) {
+ if (present)
+ mixer_vols[i].num = i;
+ return mixer_vols[i].levels;
+ }
+ }
+ if (num_mixer_volumes >= MAX_MIXER_DEV) {
+ printk(KERN_ERR "Sound: Too many mixers (%s)\n", name);
+ return levels;
+ }
+ n = num_mixer_volumes++;
+
+ strncpy(mixer_vols[n].name, name, 32);
+
+ if (present)
+ mixer_vols[n].num = n;
+ else
+ mixer_vols[n].num = -1;
+
+ for (i = 0; i < 32; i++)
+ mixer_vols[n].levels[i] = levels[i];
+ return mixer_vols[n].levels;
+}
+EXPORT_SYMBOL(load_mixer_volumes);
+
+static int set_mixer_levels(void __user * arg)
+{
+ /* mixer_vol_table is 174 bytes, so IMHO no reason to not allocate it on the stack */
+ mixer_vol_table buf;
+
+ if (__copy_from_user(&buf, arg, sizeof(buf)))
+ return -EFAULT;
+ load_mixer_volumes(buf.name, buf.levels, 0);
+ if (__copy_to_user(arg, &buf, sizeof(buf)))
+ return -EFAULT;
+ return 0;
+}
+
+static int get_mixer_levels(void __user * arg)
+{
+ int n;
+
+ if (__get_user(n, (int __user *)(&(((mixer_vol_table __user *)arg)->num))))
+ return -EFAULT;
+ if (n < 0 || n >= num_mixer_volumes)
+ return -EINVAL;
+ if (__copy_to_user(arg, &mixer_vols[n], sizeof(mixer_vol_table)))
+ return -EFAULT;
+ return 0;
+}
+
+/* 4K page size but our output routines use some slack for overruns */
+#define PROC_BLOCK_SIZE (3*1024)
+
+static ssize_t sound_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+ int dev = iminor(file->f_path.dentry->d_inode);
+ int ret = -EINVAL;
+
+ /*
+ * The OSS drivers aren't remotely happy without this locking,
+ * and unless someone fixes them when they are about to bite the
+ * big one anyway, we might as well bandage here..
+ */
+
+ mutex_lock(&soundcard_mutex);
+
+ DEB(printk("sound_read(dev=%d, count=%d)\n", dev, count));
+ switch (dev & 0x0f) {
+ case SND_DEV_DSP:
+ case SND_DEV_DSP16:
+ case SND_DEV_AUDIO:
+ ret = audio_read(dev, file, buf, count);
+ break;
+
+ case SND_DEV_SEQ:
+ case SND_DEV_SEQ2:
+ ret = sequencer_read(dev, file, buf, count);
+ break;
+
+ case SND_DEV_MIDIN:
+ ret = MIDIbuf_read(dev, file, buf, count);
+ }
+ mutex_unlock(&soundcard_mutex);
+ return ret;
+}
+
+static ssize_t sound_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ int dev = iminor(file->f_path.dentry->d_inode);
+ int ret = -EINVAL;
+
+ mutex_lock(&soundcard_mutex);
+ DEB(printk("sound_write(dev=%d, count=%d)\n", dev, count));
+ switch (dev & 0x0f) {
+ case SND_DEV_SEQ:
+ case SND_DEV_SEQ2:
+ ret = sequencer_write(dev, file, buf, count);
+ break;
+
+ case SND_DEV_DSP:
+ case SND_DEV_DSP16:
+ case SND_DEV_AUDIO:
+ ret = audio_write(dev, file, buf, count);
+ break;
+
+ case SND_DEV_MIDIN:
+ ret = MIDIbuf_write(dev, file, buf, count);
+ break;
+ }
+ mutex_unlock(&soundcard_mutex);
+ return ret;
+}
+
+static int sound_open(struct inode *inode, struct file *file)
+{
+ int dev = iminor(inode);
+ int retval;
+
+ DEB(printk("sound_open(dev=%d)\n", dev));
+ if ((dev >= SND_NDEVS) || (dev < 0)) {
+ printk(KERN_ERR "Invalid minor device %d\n", dev);
+ return -ENXIO;
+ }
+ mutex_lock(&soundcard_mutex);
+ switch (dev & 0x0f) {
+ case SND_DEV_CTL:
+ dev >>= 4;
+ if (dev >= 0 && dev < MAX_MIXER_DEV && mixer_devs[dev] == NULL) {
+ request_module("mixer%d", dev);
+ }
+ retval = -ENXIO;
+ if (dev && (dev >= num_mixers || mixer_devs[dev] == NULL))
+ break;
+
+ if (!try_module_get(mixer_devs[dev]->owner))
+ break;
+
+ retval = 0;
+ break;
+
+ case SND_DEV_SEQ:
+ case SND_DEV_SEQ2:
+ retval = sequencer_open(dev, file);
+ break;
+
+ case SND_DEV_MIDIN:
+ retval = MIDIbuf_open(dev, file);
+ break;
+
+ case SND_DEV_DSP:
+ case SND_DEV_DSP16:
+ case SND_DEV_AUDIO:
+ retval = audio_open(dev, file);
+ break;
+
+ default:
+ printk(KERN_ERR "Invalid minor device %d\n", dev);
+ retval = -ENXIO;
+ }
+
+ mutex_unlock(&soundcard_mutex);
+ return retval;
+}
+
+static int sound_release(struct inode *inode, struct file *file)
+{
+ int dev = iminor(inode);
+
+ mutex_lock(&soundcard_mutex);
+ DEB(printk("sound_release(dev=%d)\n", dev));
+ switch (dev & 0x0f) {
+ case SND_DEV_CTL:
+ module_put(mixer_devs[dev >> 4]->owner);
+ break;
+
+ case SND_DEV_SEQ:
+ case SND_DEV_SEQ2:
+ sequencer_release(dev, file);
+ break;
+
+ case SND_DEV_MIDIN:
+ MIDIbuf_release(dev, file);
+ break;
+
+ case SND_DEV_DSP:
+ case SND_DEV_DSP16:
+ case SND_DEV_AUDIO:
+ audio_release(dev, file);
+ break;
+
+ default:
+ printk(KERN_ERR "Sound error: Releasing unknown device 0x%02x\n", dev);
+ }
+ mutex_unlock(&soundcard_mutex);
+
+ return 0;
+}
+
+static int get_mixer_info(int dev, void __user *arg)
+{
+ mixer_info info;
+ memset(&info, 0, sizeof(info));
+ strlcpy(info.id, mixer_devs[dev]->id, sizeof(info.id));
+ strlcpy(info.name, mixer_devs[dev]->name, sizeof(info.name));
+ info.modify_counter = mixer_devs[dev]->modify_counter;
+ if (__copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int get_old_mixer_info(int dev, void __user *arg)
+{
+ _old_mixer_info info;
+ memset(&info, 0, sizeof(info));
+ strlcpy(info.id, mixer_devs[dev]->id, sizeof(info.id));
+ strlcpy(info.name, mixer_devs[dev]->name, sizeof(info.name));
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int sound_mixer_ioctl(int mixdev, unsigned int cmd, void __user *arg)
+{
+ if (mixdev < 0 || mixdev >= MAX_MIXER_DEV)
+ return -ENXIO;
+ /* Try to load the mixer... */
+ if (mixer_devs[mixdev] == NULL) {
+ request_module("mixer%d", mixdev);
+ }
+ if (mixdev >= num_mixers || !mixer_devs[mixdev])
+ return -ENXIO;
+ if (cmd == SOUND_MIXER_INFO)
+ return get_mixer_info(mixdev, arg);
+ if (cmd == SOUND_OLD_MIXER_INFO)
+ return get_old_mixer_info(mixdev, arg);
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+ mixer_devs[mixdev]->modify_counter++;
+ if (!mixer_devs[mixdev]->ioctl)
+ return -EINVAL;
+ return mixer_devs[mixdev]->ioctl(mixdev, cmd, arg);
+}
+
+static long sound_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int len = 0, dtype;
+ int dev = iminor(file->f_dentry->d_inode);
+ long ret = -EINVAL;
+ void __user *p = (void __user *)arg;
+
+ if (_SIOC_DIR(cmd) != _SIOC_NONE && _SIOC_DIR(cmd) != 0) {
+ /*
+ * Have to validate the address given by the process.
+ */
+ len = _SIOC_SIZE(cmd);
+ if (len < 1 || len > 65536 || !p)
+ return -EFAULT;
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+ if (!access_ok(VERIFY_READ, p, len))
+ return -EFAULT;
+ if (_SIOC_DIR(cmd) & _SIOC_READ)
+ if (!access_ok(VERIFY_WRITE, p, len))
+ return -EFAULT;
+ }
+ DEB(printk("sound_ioctl(dev=%d, cmd=0x%x, arg=0x%x)\n", dev, cmd, arg));
+ if (cmd == OSS_GETVERSION)
+ return __put_user(SOUND_VERSION, (int __user *)p);
+
+ mutex_lock(&soundcard_mutex);
+ if (_IOC_TYPE(cmd) == 'M' && num_mixers > 0 && /* Mixer ioctl */
+ (dev & 0x0f) != SND_DEV_CTL) {
+ dtype = dev & 0x0f;
+ switch (dtype) {
+ case SND_DEV_DSP:
+ case SND_DEV_DSP16:
+ case SND_DEV_AUDIO:
+ ret = sound_mixer_ioctl(audio_devs[dev >> 4]->mixer_dev,
+ cmd, p);
+ break;
+ default:
+ ret = sound_mixer_ioctl(dev >> 4, cmd, p);
+ break;
+ }
+ mutex_unlock(&soundcard_mutex);
+ return ret;
+ }
+
+ switch (dev & 0x0f) {
+ case SND_DEV_CTL:
+ if (cmd == SOUND_MIXER_GETLEVELS)
+ ret = get_mixer_levels(p);
+ else if (cmd == SOUND_MIXER_SETLEVELS)
+ ret = set_mixer_levels(p);
+ else
+ ret = sound_mixer_ioctl(dev >> 4, cmd, p);
+ break;
+
+ case SND_DEV_SEQ:
+ case SND_DEV_SEQ2:
+ ret = sequencer_ioctl(dev, file, cmd, p);
+ break;
+
+ case SND_DEV_DSP:
+ case SND_DEV_DSP16:
+ case SND_DEV_AUDIO:
+ ret = audio_ioctl(dev, file, cmd, p);
+ break;
+
+ case SND_DEV_MIDIN:
+ ret = MIDIbuf_ioctl(dev, file, cmd, p);
+ break;
+
+ }
+ mutex_unlock(&soundcard_mutex);
+ return ret;
+}
+
+static unsigned int sound_poll(struct file *file, poll_table * wait)
+{
+ struct inode *inode = file->f_path.dentry->d_inode;
+ int dev = iminor(inode);
+
+ DEB(printk("sound_poll(dev=%d)\n", dev));
+ switch (dev & 0x0f) {
+ case SND_DEV_SEQ:
+ case SND_DEV_SEQ2:
+ return sequencer_poll(dev, file, wait);
+
+ case SND_DEV_MIDIN:
+ return MIDIbuf_poll(dev, file, wait);
+
+ case SND_DEV_DSP:
+ case SND_DEV_DSP16:
+ case SND_DEV_AUDIO:
+ return DMAbuf_poll(file, dev >> 4, wait);
+ }
+ return 0;
+}
+
+static int sound_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ int dev_class;
+ unsigned long size;
+ struct dma_buffparms *dmap = NULL;
+ int dev = iminor(file->f_path.dentry->d_inode);
+
+ dev_class = dev & 0x0f;
+ dev >>= 4;
+
+ if (dev_class != SND_DEV_DSP && dev_class != SND_DEV_DSP16 && dev_class != SND_DEV_AUDIO) {
+ printk(KERN_ERR "Sound: mmap() not supported for other than audio devices\n");
+ return -EINVAL;
+ }
+ mutex_lock(&soundcard_mutex);
+ if (vma->vm_flags & VM_WRITE) /* Map write and read/write to the output buf */
+ dmap = audio_devs[dev]->dmap_out;
+ else if (vma->vm_flags & VM_READ)
+ dmap = audio_devs[dev]->dmap_in;
+ else {
+ printk(KERN_ERR "Sound: Undefined mmap() access\n");
+ mutex_unlock(&soundcard_mutex);
+ return -EINVAL;
+ }
+
+ if (dmap == NULL) {
+ printk(KERN_ERR "Sound: mmap() error. dmap == NULL\n");
+ mutex_unlock(&soundcard_mutex);
+ return -EIO;
+ }
+ if (dmap->raw_buf == NULL) {
+ printk(KERN_ERR "Sound: mmap() called when raw_buf == NULL\n");
+ mutex_unlock(&soundcard_mutex);
+ return -EIO;
+ }
+ if (dmap->mapping_flags) {
+ printk(KERN_ERR "Sound: mmap() called twice for the same DMA buffer\n");
+ mutex_unlock(&soundcard_mutex);
+ return -EIO;
+ }
+ if (vma->vm_pgoff != 0) {
+ printk(KERN_ERR "Sound: mmap() offset must be 0.\n");
+ mutex_unlock(&soundcard_mutex);
+ return -EINVAL;
+ }
+ size = vma->vm_end - vma->vm_start;
+
+ if (size != dmap->bytes_in_use) {
+ printk(KERN_WARNING "Sound: mmap() size = %ld. Should be %d\n", size, dmap->bytes_in_use);
+ }
+ if (remap_pfn_range(vma, vma->vm_start,
+ virt_to_phys(dmap->raw_buf) >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
+ mutex_unlock(&soundcard_mutex);
+ return -EAGAIN;
+ }
+
+ dmap->mapping_flags |= DMA_MAP_MAPPED;
+
+ if( audio_devs[dev]->d->mmap)
+ audio_devs[dev]->d->mmap(dev);
+
+ memset(dmap->raw_buf,
+ dmap->neutral_byte,
+ dmap->bytes_in_use);
+ mutex_unlock(&soundcard_mutex);
+ return 0;
+}
+
+const struct file_operations oss_sound_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = sound_read,
+ .write = sound_write,
+ .poll = sound_poll,
+ .unlocked_ioctl = sound_ioctl,
+ .mmap = sound_mmap,
+ .open = sound_open,
+ .release = sound_release,
+};
+
+/*
+ * Create the required special subdevices
+ */
+
+static int create_special_devices(void)
+{
+ int seq1,seq2;
+ seq1=register_sound_special(&oss_sound_fops, 1);
+ if(seq1==-1)
+ goto bad;
+ seq2=register_sound_special(&oss_sound_fops, 8);
+ if(seq2!=-1)
+ return 0;
+ unregister_sound_special(1);
+bad:
+ return -1;
+}
+
+
+static int dmabuf;
+static int dmabug;
+
+module_param(dmabuf, int, 0444);
+module_param(dmabug, int, 0444);
+
+/* additional minors for compatibility */
+struct oss_minor_dev {
+ unsigned short minor;
+ unsigned int enabled;
+} dev_list[] = {
+ { SND_DEV_DSP16 },
+ { SND_DEV_AUDIO },
+};
+
+static int __init oss_init(void)
+{
+ int err;
+ int i, j;
+
+#ifdef CONFIG_PCI
+ if(dmabug)
+ isa_dma_bridge_buggy = dmabug;
+#endif
+
+ err = create_special_devices();
+ if (err) {
+ printk(KERN_ERR "sound: driver already loaded/included in kernel\n");
+ return err;
+ }
+
+ /* Protecting the innocent */
+ sound_dmap_flag = (dmabuf > 0 ? 1 : 0);
+
+ for (i = 0; i < ARRAY_SIZE(dev_list); i++) {
+ j = 0;
+ do {
+ unsigned short minor = dev_list[i].minor + j * 0x10;
+ if (!register_sound_special(&oss_sound_fops, minor))
+ dev_list[i].enabled = (1 << j);
+ } while (++j < num_audiodevs);
+ }
+
+ if (sound_nblocks >= MAX_MEM_BLOCKS - 1)
+ printk(KERN_ERR "Sound warning: Deallocation table was too small.\n");
+
+ return 0;
+}
+
+static void __exit oss_cleanup(void)
+{
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(dev_list); i++) {
+ j = 0;
+ do {
+ if (dev_list[i].enabled & (1 << j))
+ unregister_sound_special(dev_list[i].minor);
+ } while (++j < num_audiodevs);
+ }
+
+ unregister_sound_special(1);
+ unregister_sound_special(8);
+
+ sound_stop_timer();
+
+ sequencer_unload();
+
+ for (i = 0; i < MAX_DMA_CHANNELS; i++)
+ if (dma_alloc_map[i] != DMA_MAP_UNAVAIL) {
+ printk(KERN_ERR "Sound: Hmm, DMA%d was left allocated - fixed\n", i);
+ sound_free_dma(i);
+ }
+
+ for (i = 0; i < sound_nblocks; i++)
+ vfree(sound_mem_blocks[i]);
+
+}
+
+module_init(oss_init);
+module_exit(oss_cleanup);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("OSS Sound subsystem");
+MODULE_AUTHOR("Hannu Savolainen, et al.");
+
+
+int sound_alloc_dma(int chn, char *deviceID)
+{
+ int err;
+
+ if ((err = request_dma(chn, deviceID)) != 0)
+ return err;
+
+ dma_alloc_map[chn] = DMA_MAP_FREE;
+
+ return 0;
+}
+EXPORT_SYMBOL(sound_alloc_dma);
+
+int sound_open_dma(int chn, char *deviceID)
+{
+ if (!valid_dma(chn)) {
+ printk(KERN_ERR "sound_open_dma: Invalid DMA channel %d\n", chn);
+ return 1;
+ }
+
+ if (dma_alloc_map[chn] != DMA_MAP_FREE) {
+ printk("sound_open_dma: DMA channel %d busy or not allocated (%d)\n", chn, dma_alloc_map[chn]);
+ return 1;
+ }
+ dma_alloc_map[chn] = DMA_MAP_BUSY;
+ return 0;
+}
+EXPORT_SYMBOL(sound_open_dma);
+
+void sound_free_dma(int chn)
+{
+ if (dma_alloc_map[chn] == DMA_MAP_UNAVAIL) {
+ /* printk( "sound_free_dma: Bad access to DMA channel %d\n", chn); */
+ return;
+ }
+ free_dma(chn);
+ dma_alloc_map[chn] = DMA_MAP_UNAVAIL;
+}
+EXPORT_SYMBOL(sound_free_dma);
+
+void sound_close_dma(int chn)
+{
+ if (dma_alloc_map[chn] != DMA_MAP_BUSY) {
+ printk(KERN_ERR "sound_close_dma: Bad access to DMA channel %d\n", chn);
+ return;
+ }
+ dma_alloc_map[chn] = DMA_MAP_FREE;
+}
+EXPORT_SYMBOL(sound_close_dma);
+
+static void do_sequencer_timer(unsigned long dummy)
+{
+ sequencer_timer(0);
+}
+
+
+static DEFINE_TIMER(seq_timer, do_sequencer_timer, 0, 0);
+
+void request_sound_timer(int count)
+{
+ extern unsigned long seq_time;
+
+ if (count < 0) {
+ seq_timer.expires = (-count) + jiffies;
+ add_timer(&seq_timer);
+ return;
+ }
+ count += seq_time;
+
+ count -= jiffies;
+
+ if (count < 1)
+ count = 1;
+
+ seq_timer.expires = (count) + jiffies;
+ add_timer(&seq_timer);
+}
+
+void sound_stop_timer(void)
+{
+ del_timer(&seq_timer);
+}
+
+void conf_printf(char *name, struct address_info *hw_config)
+{
+#ifndef CONFIG_SOUND_TRACEINIT
+ return;
+#else
+ printk("<%s> at 0x%03x", name, hw_config->io_base);
+
+ if (hw_config->irq)
+ printk(" irq %d", (hw_config->irq > 0) ? hw_config->irq : -hw_config->irq);
+
+ if (hw_config->dma != -1 || hw_config->dma2 != -1)
+ {
+ printk(" dma %d", hw_config->dma);
+ if (hw_config->dma2 != -1)
+ printk(",%d", hw_config->dma2);
+ }
+ printk("\n");
+#endif
+}
+EXPORT_SYMBOL(conf_printf);
+
+void conf_printf2(char *name, int base, int irq, int dma, int dma2)
+{
+#ifndef CONFIG_SOUND_TRACEINIT
+ return;
+#else
+ printk("<%s> at 0x%03x", name, base);
+
+ if (irq)
+ printk(" irq %d", (irq > 0) ? irq : -irq);
+
+ if (dma != -1 || dma2 != -1)
+ {
+ printk(" dma %d", dma);
+ if (dma2 != -1)
+ printk(",%d", dma2);
+ }
+ printk("\n");
+#endif
+}
+EXPORT_SYMBOL(conf_printf2);
+
diff --git a/sound/oss/soundvers.h b/sound/oss/soundvers.h
new file mode 100644
index 00000000..e9084d2f
--- /dev/null
+++ b/sound/oss/soundvers.h
@@ -0,0 +1,2 @@
+#define SOUND_VERSION_STRING "3.8s2++-971130"
+#define SOUND_INTERNAL_VERSION 0x030804
diff --git a/sound/oss/swarm_cs4297a.c b/sound/oss/swarm_cs4297a.c
new file mode 100644
index 00000000..09d46484
--- /dev/null
+++ b/sound/oss/swarm_cs4297a.c
@@ -0,0 +1,2768 @@
+/*******************************************************************************
+*
+* "swarm_cs4297a.c" -- Cirrus Logic-Crystal CS4297a linux audio driver.
+*
+* Copyright (C) 2001 Broadcom Corporation.
+* Copyright (C) 2000,2001 Cirrus Logic Corp.
+* -- adapted from drivers by Thomas Sailer,
+* -- but don't bug him; Problems should go to:
+* -- tom woller (twoller@crystal.cirrus.com) or
+* (audio@crystal.cirrus.com).
+* -- adapted from cs4281 PCI driver for cs4297a on
+* BCM1250 Synchronous Serial interface
+* (Kip Walker, Broadcom Corp.)
+* Copyright (C) 2004 Maciej W. Rozycki
+* Copyright (C) 2005 Ralf Baechle (ralf@linux-mips.org)
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+* Module command line parameters:
+* none
+*
+* Supported devices:
+* /dev/dsp standard /dev/dsp device, (mostly) OSS compatible
+* /dev/mixer standard /dev/mixer device, (mostly) OSS compatible
+* /dev/midi simple MIDI UART interface, no ioctl
+*
+* Modification History
+* 08/20/00 trw - silence and no stopping DAC until release
+* 08/23/00 trw - added CS_DBG statements, fix interrupt hang issue on DAC stop.
+* 09/18/00 trw - added 16bit only record with conversion
+* 09/24/00 trw - added Enhanced Full duplex (separate simultaneous
+* capture/playback rates)
+* 10/03/00 trw - fixed mmap (fixed GRECORD and the XMMS mmap test plugin
+* libOSSm.so)
+* 10/11/00 trw - modified for 2.4.0-test9 kernel enhancements (NR_MAP removal)
+* 11/03/00 trw - fixed interrupt loss/stutter, added debug.
+* 11/10/00 bkz - added __devinit to cs4297a_hw_init()
+* 11/10/00 trw - fixed SMP and capture spinlock hang.
+* 12/04/00 trw - cleaned up CSDEBUG flags and added "defaultorder" moduleparm.
+* 12/05/00 trw - fixed polling (myth2), and added underrun swptr fix.
+* 12/08/00 trw - added PM support.
+* 12/14/00 trw - added wrapper code, builds under 2.4.0, 2.2.17-20, 2.2.17-8
+* (RH/Dell base), 2.2.18, 2.2.12. cleaned up code mods by ident.
+* 12/19/00 trw - added PM support for 2.2 base (apm_callback). other PM cleanup.
+* 12/21/00 trw - added fractional "defaultorder" inputs. if >100 then use
+* defaultorder-100 as power of 2 for the buffer size. example:
+* 106 = 2^(106-100) = 2^6 = 64 bytes for the buffer size.
+*
+*******************************************************************************/
+
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/sound.h>
+#include <linux/slab.h>
+#include <linux/soundcard.h>
+#include <linux/ac97_codec.h>
+#include <linux/pci.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/mutex.h>
+#include <linux/kernel.h>
+
+#include <asm/byteorder.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include <asm/sibyte/sb1250_regs.h>
+#include <asm/sibyte/sb1250_int.h>
+#include <asm/sibyte/sb1250_dma.h>
+#include <asm/sibyte/sb1250_scd.h>
+#include <asm/sibyte/sb1250_syncser.h>
+#include <asm/sibyte/sb1250_mac.h>
+#include <asm/sibyte/sb1250.h>
+
+struct cs4297a_state;
+
+static DEFINE_MUTEX(swarm_cs4297a_mutex);
+static void stop_dac(struct cs4297a_state *s);
+static void stop_adc(struct cs4297a_state *s);
+static void start_dac(struct cs4297a_state *s);
+static void start_adc(struct cs4297a_state *s);
+#undef OSS_DOCUMENTED_MIXER_SEMANTICS
+
+// ---------------------------------------------------------------------
+
+#define CS4297a_MAGIC 0xf00beef1
+
+// buffer order determines the size of the dma buffer for the driver.
+// under Linux, a smaller buffer allows more responsiveness from many of the
+// applications (e.g. games). A larger buffer allows some of the apps (esound)
+// to not underrun the dma buffer as easily. As default, use 32k (order=3)
+// rather than 64k as some of the games work more responsively.
+// log base 2( buff sz = 32k).
+
+//
+// Turn on/off debugging compilation by commenting out "#define CSDEBUG"
+//
+#define CSDEBUG 0
+#if CSDEBUG
+#define CSDEBUG_INTERFACE 1
+#else
+#undef CSDEBUG_INTERFACE
+#endif
+//
+// cs_debugmask areas
+//
+#define CS_INIT 0x00000001 // initialization and probe functions
+#define CS_ERROR 0x00000002 // tmp debugging bit placeholder
+#define CS_INTERRUPT 0x00000004 // interrupt handler (separate from all other)
+#define CS_FUNCTION 0x00000008 // enter/leave functions
+#define CS_WAVE_WRITE 0x00000010 // write information for wave
+#define CS_WAVE_READ 0x00000020 // read information for wave
+#define CS_AC97 0x00000040 // AC97 register access
+#define CS_DESCR 0x00000080 // descriptor management
+#define CS_OPEN 0x00000400 // all open functions in the driver
+#define CS_RELEASE 0x00000800 // all release functions in the driver
+#define CS_PARMS 0x00001000 // functional and operational parameters
+#define CS_IOCTL 0x00002000 // ioctl (non-mixer)
+#define CS_TMP 0x10000000 // tmp debug mask bit
+
+//
+// CSDEBUG is usual mode is set to 1, then use the
+// cs_debuglevel and cs_debugmask to turn on or off debugging.
+// Debug level of 1 has been defined to be kernel errors and info
+// that should be printed on any released driver.
+//
+#if CSDEBUG
+#define CS_DBGOUT(mask,level,x) if((cs_debuglevel >= (level)) && ((mask) & cs_debugmask) ) {x;}
+#else
+#define CS_DBGOUT(mask,level,x)
+#endif
+
+#if CSDEBUG
+static unsigned long cs_debuglevel = 4; // levels range from 1-9
+static unsigned long cs_debugmask = CS_INIT /*| CS_IOCTL*/;
+module_param(cs_debuglevel, int, 0);
+module_param(cs_debugmask, int, 0);
+#endif
+#define CS_TRUE 1
+#define CS_FALSE 0
+
+#define CS_TYPE_ADC 0
+#define CS_TYPE_DAC 1
+
+#define SER_BASE (A_SER_BASE_1 + KSEG1)
+#define SS_CSR(t) (SER_BASE+t)
+#define SS_TXTBL(t) (SER_BASE+R_SER_TX_TABLE_BASE+(t*8))
+#define SS_RXTBL(t) (SER_BASE+R_SER_RX_TABLE_BASE+(t*8))
+
+#define FRAME_BYTES 32
+#define FRAME_SAMPLE_BYTES 4
+
+/* Should this be variable? */
+#define SAMPLE_BUF_SIZE (16*1024)
+#define SAMPLE_FRAME_COUNT (SAMPLE_BUF_SIZE / FRAME_SAMPLE_BYTES)
+/* The driver can explode/shrink the frames to/from a smaller sample
+ buffer */
+#define DMA_BLOAT_FACTOR 1
+#define DMA_DESCR (SAMPLE_FRAME_COUNT / DMA_BLOAT_FACTOR)
+#define DMA_BUF_SIZE (DMA_DESCR * FRAME_BYTES)
+
+/* Use the maxmium count (255 == 5.1 ms between interrupts) */
+#define DMA_INT_CNT ((1 << S_DMA_INT_PKTCNT) - 1)
+
+/* Figure this out: how many TX DMAs ahead to schedule a reg access */
+#define REG_LATENCY 150
+
+#define FRAME_TX_US 20
+
+#define SERDMA_NEXTBUF(d,f) (((d)->f+1) % (d)->ringsz)
+
+static const char invalid_magic[] =
+ KERN_CRIT "cs4297a: invalid magic value\n";
+
+#define VALIDATE_STATE(s) \
+({ \
+ if (!(s) || (s)->magic != CS4297a_MAGIC) { \
+ printk(invalid_magic); \
+ return -ENXIO; \
+ } \
+})
+
+struct list_head cs4297a_devs = { &cs4297a_devs, &cs4297a_devs };
+
+typedef struct serdma_descr_s {
+ u64 descr_a;
+ u64 descr_b;
+} serdma_descr_t;
+
+typedef unsigned long paddr_t;
+
+typedef struct serdma_s {
+ unsigned ringsz;
+ serdma_descr_t *descrtab;
+ serdma_descr_t *descrtab_end;
+ paddr_t descrtab_phys;
+
+ serdma_descr_t *descr_add;
+ serdma_descr_t *descr_rem;
+
+ u64 *dma_buf; // buffer for DMA contents (frames)
+ paddr_t dma_buf_phys;
+ u16 *sample_buf; // tmp buffer for sample conversions
+ u16 *sb_swptr;
+ u16 *sb_hwptr;
+ u16 *sb_end;
+
+ dma_addr_t dmaaddr;
+// unsigned buforder; // Log base 2 of 'dma_buf' size in bytes..
+ unsigned numfrag; // # of 'fragments' in the buffer.
+ unsigned fragshift; // Log base 2 of fragment size.
+ unsigned hwptr, swptr;
+ unsigned total_bytes; // # bytes process since open.
+ unsigned blocks; // last returned blocks value GETOPTR
+ unsigned wakeup; // interrupt occurred on block
+ int count;
+ unsigned underrun; // underrun flag
+ unsigned error; // over/underrun
+ wait_queue_head_t wait;
+ wait_queue_head_t reg_wait;
+ // redundant, but makes calculations easier
+ unsigned fragsize; // 2**fragshift..
+ unsigned sbufsz; // 2**buforder.
+ unsigned fragsamples;
+ // OSS stuff
+ unsigned mapped:1; // Buffer mapped in cs4297a_mmap()?
+ unsigned ready:1; // prog_dmabuf_dac()/adc() successful?
+ unsigned endcleared:1;
+ unsigned type:1; // adc or dac buffer (CS_TYPE_XXX)
+ unsigned ossfragshift;
+ int ossmaxfrags;
+ unsigned subdivision;
+} serdma_t;
+
+struct cs4297a_state {
+ // magic
+ unsigned int magic;
+
+ struct list_head list;
+
+ // soundcore stuff
+ int dev_audio;
+ int dev_mixer;
+
+ // hardware resources
+ unsigned int irq;
+
+ struct {
+ unsigned int rx_ovrrn; /* FIFO */
+ unsigned int rx_overflow; /* staging buffer */
+ unsigned int tx_underrun;
+ unsigned int rx_bad;
+ unsigned int rx_good;
+ } stats;
+
+ // mixer registers
+ struct {
+ unsigned short vol[10];
+ unsigned int recsrc;
+ unsigned int modcnt;
+ unsigned short micpreamp;
+ } mix;
+
+ // wave stuff
+ struct properties {
+ unsigned fmt;
+ unsigned fmt_original; // original requested format
+ unsigned channels;
+ unsigned rate;
+ } prop_dac, prop_adc;
+ unsigned conversion:1; // conversion from 16 to 8 bit in progress
+ unsigned ena;
+ spinlock_t lock;
+ struct mutex open_mutex;
+ struct mutex open_sem_adc;
+ struct mutex open_sem_dac;
+ fmode_t open_mode;
+ wait_queue_head_t open_wait;
+ wait_queue_head_t open_wait_adc;
+ wait_queue_head_t open_wait_dac;
+
+ dma_addr_t dmaaddr_sample_buf;
+ unsigned buforder_sample_buf; // Log base 2 of 'dma_buf' size in bytes..
+
+ serdma_t dma_dac, dma_adc;
+
+ volatile u16 read_value;
+ volatile u16 read_reg;
+ volatile u64 reg_request;
+};
+
+#if 1
+#define prog_codec(a,b)
+#define dealloc_dmabuf(a,b);
+#endif
+
+static int prog_dmabuf_adc(struct cs4297a_state *s)
+{
+ s->dma_adc.ready = 1;
+ return 0;
+}
+
+
+static int prog_dmabuf_dac(struct cs4297a_state *s)
+{
+ s->dma_dac.ready = 1;
+ return 0;
+}
+
+static void clear_advance(void *buf, unsigned bsize, unsigned bptr,
+ unsigned len, unsigned char c)
+{
+ if (bptr + len > bsize) {
+ unsigned x = bsize - bptr;
+ memset(((char *) buf) + bptr, c, x);
+ bptr = 0;
+ len -= x;
+ }
+ CS_DBGOUT(CS_WAVE_WRITE, 4, printk(KERN_INFO
+ "cs4297a: clear_advance(): memset %d at 0x%.8x for %d size \n",
+ (unsigned)c, (unsigned)((char *) buf) + bptr, len));
+ memset(((char *) buf) + bptr, c, len);
+}
+
+#if CSDEBUG
+
+// DEBUG ROUTINES
+
+#define SOUND_MIXER_CS_GETDBGLEVEL _SIOWR('M',120, int)
+#define SOUND_MIXER_CS_SETDBGLEVEL _SIOWR('M',121, int)
+#define SOUND_MIXER_CS_GETDBGMASK _SIOWR('M',122, int)
+#define SOUND_MIXER_CS_SETDBGMASK _SIOWR('M',123, int)
+
+static void cs_printioctl(unsigned int x)
+{
+ unsigned int i;
+ unsigned char vidx;
+ // Index of mixtable1[] member is Device ID
+ // and must be <= SOUND_MIXER_NRDEVICES.
+ // Value of array member is index into s->mix.vol[]
+ static const unsigned char mixtable1[SOUND_MIXER_NRDEVICES] = {
+ [SOUND_MIXER_PCM] = 1, // voice
+ [SOUND_MIXER_LINE1] = 2, // AUX
+ [SOUND_MIXER_CD] = 3, // CD
+ [SOUND_MIXER_LINE] = 4, // Line
+ [SOUND_MIXER_SYNTH] = 5, // FM
+ [SOUND_MIXER_MIC] = 6, // Mic
+ [SOUND_MIXER_SPEAKER] = 7, // Speaker
+ [SOUND_MIXER_RECLEV] = 8, // Recording level
+ [SOUND_MIXER_VOLUME] = 9 // Master Volume
+ };
+
+ switch (x) {
+ case SOUND_MIXER_CS_GETDBGMASK:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_CS_GETDBGMASK:\n"));
+ break;
+ case SOUND_MIXER_CS_GETDBGLEVEL:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_CS_GETDBGLEVEL:\n"));
+ break;
+ case SOUND_MIXER_CS_SETDBGMASK:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_CS_SETDBGMASK:\n"));
+ break;
+ case SOUND_MIXER_CS_SETDBGLEVEL:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_CS_SETDBGLEVEL:\n"));
+ break;
+ case OSS_GETVERSION:
+ CS_DBGOUT(CS_IOCTL, 4, printk("OSS_GETVERSION:\n"));
+ break;
+ case SNDCTL_DSP_SYNC:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SYNC:\n"));
+ break;
+ case SNDCTL_DSP_SETDUPLEX:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETDUPLEX:\n"));
+ break;
+ case SNDCTL_DSP_GETCAPS:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETCAPS:\n"));
+ break;
+ case SNDCTL_DSP_RESET:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_RESET:\n"));
+ break;
+ case SNDCTL_DSP_SPEED:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SPEED:\n"));
+ break;
+ case SNDCTL_DSP_STEREO:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_STEREO:\n"));
+ break;
+ case SNDCTL_DSP_CHANNELS:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_CHANNELS:\n"));
+ break;
+ case SNDCTL_DSP_GETFMTS:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETFMTS:\n"));
+ break;
+ case SNDCTL_DSP_SETFMT:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETFMT:\n"));
+ break;
+ case SNDCTL_DSP_POST:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_POST:\n"));
+ break;
+ case SNDCTL_DSP_GETTRIGGER:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETTRIGGER:\n"));
+ break;
+ case SNDCTL_DSP_SETTRIGGER:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETTRIGGER:\n"));
+ break;
+ case SNDCTL_DSP_GETOSPACE:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETOSPACE:\n"));
+ break;
+ case SNDCTL_DSP_GETISPACE:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETISPACE:\n"));
+ break;
+ case SNDCTL_DSP_NONBLOCK:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_NONBLOCK:\n"));
+ break;
+ case SNDCTL_DSP_GETODELAY:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETODELAY:\n"));
+ break;
+ case SNDCTL_DSP_GETIPTR:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETIPTR:\n"));
+ break;
+ case SNDCTL_DSP_GETOPTR:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETOPTR:\n"));
+ break;
+ case SNDCTL_DSP_GETBLKSIZE:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETBLKSIZE:\n"));
+ break;
+ case SNDCTL_DSP_SETFRAGMENT:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SNDCTL_DSP_SETFRAGMENT:\n"));
+ break;
+ case SNDCTL_DSP_SUBDIVIDE:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SUBDIVIDE:\n"));
+ break;
+ case SOUND_PCM_READ_RATE:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_RATE:\n"));
+ break;
+ case SOUND_PCM_READ_CHANNELS:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_PCM_READ_CHANNELS:\n"));
+ break;
+ case SOUND_PCM_READ_BITS:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_BITS:\n"));
+ break;
+ case SOUND_PCM_WRITE_FILTER:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_PCM_WRITE_FILTER:\n"));
+ break;
+ case SNDCTL_DSP_SETSYNCRO:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETSYNCRO:\n"));
+ break;
+ case SOUND_PCM_READ_FILTER:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_FILTER:\n"));
+ break;
+ case SOUND_MIXER_PRIVATE1:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE1:\n"));
+ break;
+ case SOUND_MIXER_PRIVATE2:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE2:\n"));
+ break;
+ case SOUND_MIXER_PRIVATE3:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE3:\n"));
+ break;
+ case SOUND_MIXER_PRIVATE4:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE4:\n"));
+ break;
+ case SOUND_MIXER_PRIVATE5:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE5:\n"));
+ break;
+ case SOUND_MIXER_INFO:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_INFO:\n"));
+ break;
+ case SOUND_OLD_MIXER_INFO:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_OLD_MIXER_INFO:\n"));
+ break;
+
+ default:
+ switch (_IOC_NR(x)) {
+ case SOUND_MIXER_VOLUME:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_VOLUME:\n"));
+ break;
+ case SOUND_MIXER_SPEAKER:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_SPEAKER:\n"));
+ break;
+ case SOUND_MIXER_RECLEV:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_RECLEV:\n"));
+ break;
+ case SOUND_MIXER_MIC:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_MIC:\n"));
+ break;
+ case SOUND_MIXER_SYNTH:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_SYNTH:\n"));
+ break;
+ case SOUND_MIXER_RECSRC:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_RECSRC:\n"));
+ break;
+ case SOUND_MIXER_DEVMASK:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_DEVMASK:\n"));
+ break;
+ case SOUND_MIXER_RECMASK:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_RECMASK:\n"));
+ break;
+ case SOUND_MIXER_STEREODEVS:
+ CS_DBGOUT(CS_IOCTL, 4,
+ printk("SOUND_MIXER_STEREODEVS:\n"));
+ break;
+ case SOUND_MIXER_CAPS:
+ CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_CAPS:\n"));
+ break;
+ default:
+ i = _IOC_NR(x);
+ if (i >= SOUND_MIXER_NRDEVICES
+ || !(vidx = mixtable1[i])) {
+ CS_DBGOUT(CS_IOCTL, 4, printk
+ ("UNKNOWN IOCTL: 0x%.8x NR=%d\n",
+ x, i));
+ } else {
+ CS_DBGOUT(CS_IOCTL, 4, printk
+ ("SOUND_MIXER_IOCTL AC9x: 0x%.8x NR=%d\n",
+ x, i));
+ }
+ break;
+ }
+ }
+}
+#endif
+
+
+static int ser_init(struct cs4297a_state *s)
+{
+ int i;
+
+ CS_DBGOUT(CS_INIT, 2,
+ printk(KERN_INFO "cs4297a: Setting up serial parameters\n"));
+
+ __raw_writeq(M_SYNCSER_CMD_RX_RESET | M_SYNCSER_CMD_TX_RESET, SS_CSR(R_SER_CMD));
+
+ __raw_writeq(M_SYNCSER_MSB_FIRST, SS_CSR(R_SER_MODE));
+ __raw_writeq(32, SS_CSR(R_SER_MINFRM_SZ));
+ __raw_writeq(32, SS_CSR(R_SER_MAXFRM_SZ));
+
+ __raw_writeq(1, SS_CSR(R_SER_TX_RD_THRSH));
+ __raw_writeq(4, SS_CSR(R_SER_TX_WR_THRSH));
+ __raw_writeq(8, SS_CSR(R_SER_RX_RD_THRSH));
+
+ /* This looks good from experimentation */
+ __raw_writeq((M_SYNCSER_TXSYNC_INT | V_SYNCSER_TXSYNC_DLY(0) | M_SYNCSER_TXCLK_EXT |
+ M_SYNCSER_RXSYNC_INT | V_SYNCSER_RXSYNC_DLY(1) | M_SYNCSER_RXCLK_EXT | M_SYNCSER_RXSYNC_EDGE),
+ SS_CSR(R_SER_LINE_MODE));
+
+ /* This looks good from experimentation */
+ __raw_writeq(V_SYNCSER_SEQ_COUNT(14) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_STROBE,
+ SS_TXTBL(0));
+ __raw_writeq(V_SYNCSER_SEQ_COUNT(15) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_BYTE,
+ SS_TXTBL(1));
+ __raw_writeq(V_SYNCSER_SEQ_COUNT(13) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_BYTE,
+ SS_TXTBL(2));
+ __raw_writeq(V_SYNCSER_SEQ_COUNT( 0) | M_SYNCSER_SEQ_ENABLE |
+ M_SYNCSER_SEQ_STROBE | M_SYNCSER_SEQ_LAST, SS_TXTBL(3));
+
+ __raw_writeq(V_SYNCSER_SEQ_COUNT(14) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_STROBE,
+ SS_RXTBL(0));
+ __raw_writeq(V_SYNCSER_SEQ_COUNT(15) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_BYTE,
+ SS_RXTBL(1));
+ __raw_writeq(V_SYNCSER_SEQ_COUNT(13) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_BYTE,
+ SS_RXTBL(2));
+ __raw_writeq(V_SYNCSER_SEQ_COUNT( 0) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_STROBE |
+ M_SYNCSER_SEQ_LAST, SS_RXTBL(3));
+
+ for (i=4; i<16; i++) {
+ /* Just in case... */
+ __raw_writeq(M_SYNCSER_SEQ_LAST, SS_TXTBL(i));
+ __raw_writeq(M_SYNCSER_SEQ_LAST, SS_RXTBL(i));
+ }
+
+ return 0;
+}
+
+static int init_serdma(serdma_t *dma)
+{
+ CS_DBGOUT(CS_INIT, 2,
+ printk(KERN_ERR "cs4297a: desc - %d sbufsize - %d dbufsize - %d\n",
+ DMA_DESCR, SAMPLE_BUF_SIZE, DMA_BUF_SIZE));
+
+ /* Descriptors */
+ dma->ringsz = DMA_DESCR;
+ dma->descrtab = kzalloc(dma->ringsz * sizeof(serdma_descr_t), GFP_KERNEL);
+ if (!dma->descrtab) {
+ printk(KERN_ERR "cs4297a: kzalloc descrtab failed\n");
+ return -1;
+ }
+ dma->descrtab_end = dma->descrtab + dma->ringsz;
+ /* XXX bloddy mess, use proper DMA API here ... */
+ dma->descrtab_phys = CPHYSADDR((long)dma->descrtab);
+ dma->descr_add = dma->descr_rem = dma->descrtab;
+
+ /* Frame buffer area */
+ dma->dma_buf = kzalloc(DMA_BUF_SIZE, GFP_KERNEL);
+ if (!dma->dma_buf) {
+ printk(KERN_ERR "cs4297a: kzalloc dma_buf failed\n");
+ kfree(dma->descrtab);
+ return -1;
+ }
+ dma->dma_buf_phys = CPHYSADDR((long)dma->dma_buf);
+
+ /* Samples buffer area */
+ dma->sbufsz = SAMPLE_BUF_SIZE;
+ dma->sample_buf = kmalloc(dma->sbufsz, GFP_KERNEL);
+ if (!dma->sample_buf) {
+ printk(KERN_ERR "cs4297a: kmalloc sample_buf failed\n");
+ kfree(dma->descrtab);
+ kfree(dma->dma_buf);
+ return -1;
+ }
+ dma->sb_swptr = dma->sb_hwptr = dma->sample_buf;
+ dma->sb_end = (u16 *)((void *)dma->sample_buf + dma->sbufsz);
+ dma->fragsize = dma->sbufsz >> 1;
+
+ CS_DBGOUT(CS_INIT, 4,
+ printk(KERN_ERR "cs4297a: descrtab - %08x dma_buf - %x sample_buf - %x\n",
+ (int)dma->descrtab, (int)dma->dma_buf,
+ (int)dma->sample_buf));
+
+ return 0;
+}
+
+static int dma_init(struct cs4297a_state *s)
+{
+ int i;
+
+ CS_DBGOUT(CS_INIT, 2,
+ printk(KERN_INFO "cs4297a: Setting up DMA\n"));
+
+ if (init_serdma(&s->dma_adc) ||
+ init_serdma(&s->dma_dac))
+ return -1;
+
+ if (__raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_RX))||
+ __raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_TX))) {
+ panic("DMA state corrupted?!");
+ }
+
+ /* Initialize now - the descr/buffer pairings will never
+ change... */
+ for (i=0; i<DMA_DESCR; i++) {
+ s->dma_dac.descrtab[i].descr_a = M_DMA_SERRX_SOP | V_DMA_DSCRA_A_SIZE(1) |
+ (s->dma_dac.dma_buf_phys + i*FRAME_BYTES);
+ s->dma_dac.descrtab[i].descr_b = V_DMA_DSCRB_PKT_SIZE(FRAME_BYTES);
+ s->dma_adc.descrtab[i].descr_a = V_DMA_DSCRA_A_SIZE(1) |
+ (s->dma_adc.dma_buf_phys + i*FRAME_BYTES);
+ s->dma_adc.descrtab[i].descr_b = 0;
+ }
+
+ __raw_writeq((M_DMA_EOP_INT_EN | V_DMA_INT_PKTCNT(DMA_INT_CNT) |
+ V_DMA_RINGSZ(DMA_DESCR) | M_DMA_TDX_EN),
+ SS_CSR(R_SER_DMA_CONFIG0_RX));
+ __raw_writeq(M_DMA_L2CA, SS_CSR(R_SER_DMA_CONFIG1_RX));
+ __raw_writeq(s->dma_adc.descrtab_phys, SS_CSR(R_SER_DMA_DSCR_BASE_RX));
+
+ __raw_writeq(V_DMA_RINGSZ(DMA_DESCR), SS_CSR(R_SER_DMA_CONFIG0_TX));
+ __raw_writeq(M_DMA_L2CA | M_DMA_NO_DSCR_UPDT, SS_CSR(R_SER_DMA_CONFIG1_TX));
+ __raw_writeq(s->dma_dac.descrtab_phys, SS_CSR(R_SER_DMA_DSCR_BASE_TX));
+
+ /* Prep the receive DMA descriptor ring */
+ __raw_writeq(DMA_DESCR, SS_CSR(R_SER_DMA_DSCR_COUNT_RX));
+
+ __raw_writeq(M_SYNCSER_DMA_RX_EN | M_SYNCSER_DMA_TX_EN, SS_CSR(R_SER_DMA_ENABLE));
+
+ __raw_writeq((M_SYNCSER_RX_SYNC_ERR | M_SYNCSER_RX_OVERRUN | M_SYNCSER_RX_EOP_COUNT),
+ SS_CSR(R_SER_INT_MASK));
+
+ /* Enable the rx/tx; let the codec warm up to the sync and
+ start sending good frames before the receive FIFO is
+ enabled */
+ __raw_writeq(M_SYNCSER_CMD_TX_EN, SS_CSR(R_SER_CMD));
+ udelay(1000);
+ __raw_writeq(M_SYNCSER_CMD_RX_EN | M_SYNCSER_CMD_TX_EN, SS_CSR(R_SER_CMD));
+
+ /* XXXKW is this magic? (the "1" part) */
+ while ((__raw_readq(SS_CSR(R_SER_STATUS)) & 0xf1) != 1)
+ ;
+
+ CS_DBGOUT(CS_INIT, 4,
+ printk(KERN_INFO "cs4297a: status: %08x\n",
+ (unsigned int)(__raw_readq(SS_CSR(R_SER_STATUS)) & 0xffffffff)));
+
+ return 0;
+}
+
+static int serdma_reg_access(struct cs4297a_state *s, u64 data)
+{
+ serdma_t *d = &s->dma_dac;
+ u64 *data_p;
+ unsigned swptr;
+ unsigned long flags;
+ serdma_descr_t *descr;
+
+ if (s->reg_request) {
+ printk(KERN_ERR "cs4297a: attempt to issue multiple reg_access\n");
+ return -1;
+ }
+
+ if (s->ena & FMODE_WRITE) {
+ /* Since a writer has the DSP open, we have to mux the
+ request in */
+ s->reg_request = data;
+ interruptible_sleep_on(&s->dma_dac.reg_wait);
+ /* XXXKW how can I deal with the starvation case where
+ the opener isn't writing? */
+ } else {
+ /* Be safe when changing ring pointers */
+ spin_lock_irqsave(&s->lock, flags);
+ if (d->hwptr != d->swptr) {
+ printk(KERN_ERR "cs4297a: reg access found bookkeeping error (hw/sw = %d/%d\n",
+ d->hwptr, d->swptr);
+ spin_unlock_irqrestore(&s->lock, flags);
+ return -1;
+ }
+ swptr = d->swptr;
+ d->hwptr = d->swptr = (d->swptr + 1) % d->ringsz;
+ spin_unlock_irqrestore(&s->lock, flags);
+
+ descr = &d->descrtab[swptr];
+ data_p = &d->dma_buf[swptr * 4];
+ *data_p = cpu_to_be64(data);
+ __raw_writeq(1, SS_CSR(R_SER_DMA_DSCR_COUNT_TX));
+ CS_DBGOUT(CS_DESCR, 4,
+ printk(KERN_INFO "cs4297a: add_tx %p (%x -> %x)\n",
+ data_p, swptr, d->hwptr));
+ }
+
+ CS_DBGOUT(CS_FUNCTION, 6,
+ printk(KERN_INFO "cs4297a: serdma_reg_access()-\n"));
+
+ return 0;
+}
+
+//****************************************************************************
+// "cs4297a_read_ac97" -- Reads an AC97 register
+//****************************************************************************
+static int cs4297a_read_ac97(struct cs4297a_state *s, u32 offset,
+ u32 * value)
+{
+ CS_DBGOUT(CS_AC97, 1,
+ printk(KERN_INFO "cs4297a: read reg %2x\n", offset));
+ if (serdma_reg_access(s, (0xCLL << 60) | (1LL << 47) | ((u64)(offset & 0x7F) << 40)))
+ return -1;
+
+ interruptible_sleep_on(&s->dma_adc.reg_wait);
+ *value = s->read_value;
+ CS_DBGOUT(CS_AC97, 2,
+ printk(KERN_INFO "cs4297a: rdr reg %x -> %x\n", s->read_reg, s->read_value));
+
+ return 0;
+}
+
+
+//****************************************************************************
+// "cs4297a_write_ac97()"-- writes an AC97 register
+//****************************************************************************
+static int cs4297a_write_ac97(struct cs4297a_state *s, u32 offset,
+ u32 value)
+{
+ CS_DBGOUT(CS_AC97, 1,
+ printk(KERN_INFO "cs4297a: write reg %2x -> %04x\n", offset, value));
+ return (serdma_reg_access(s, (0xELL << 60) | ((u64)(offset & 0x7F) << 40) | ((value & 0xffff) << 12)));
+}
+
+static void stop_dac(struct cs4297a_state *s)
+{
+ unsigned long flags;
+
+ CS_DBGOUT(CS_WAVE_WRITE, 3, printk(KERN_INFO "cs4297a: stop_dac():\n"));
+ spin_lock_irqsave(&s->lock, flags);
+ s->ena &= ~FMODE_WRITE;
+#if 0
+ /* XXXKW what do I really want here? My theory for now is
+ that I just flip the "ena" bit, and the interrupt handler
+ will stop processing the xmit channel */
+ __raw_writeq((s->ena & FMODE_READ) ? M_SYNCSER_DMA_RX_EN : 0,
+ SS_CSR(R_SER_DMA_ENABLE));
+#endif
+
+ spin_unlock_irqrestore(&s->lock, flags);
+}
+
+
+static void start_dac(struct cs4297a_state *s)
+{
+ unsigned long flags;
+
+ CS_DBGOUT(CS_FUNCTION, 3, printk(KERN_INFO "cs4297a: start_dac()+\n"));
+ spin_lock_irqsave(&s->lock, flags);
+ if (!(s->ena & FMODE_WRITE) && (s->dma_dac.mapped ||
+ (s->dma_dac.count > 0
+ && s->dma_dac.ready))) {
+ s->ena |= FMODE_WRITE;
+ /* XXXKW what do I really want here? My theory for
+ now is that I just flip the "ena" bit, and the
+ interrupt handler will start processing the xmit
+ channel */
+
+ CS_DBGOUT(CS_WAVE_WRITE | CS_PARMS, 8, printk(KERN_INFO
+ "cs4297a: start_dac(): start dma\n"));
+
+ }
+ spin_unlock_irqrestore(&s->lock, flags);
+ CS_DBGOUT(CS_FUNCTION, 3,
+ printk(KERN_INFO "cs4297a: start_dac()-\n"));
+}
+
+
+static void stop_adc(struct cs4297a_state *s)
+{
+ unsigned long flags;
+
+ CS_DBGOUT(CS_FUNCTION, 3,
+ printk(KERN_INFO "cs4297a: stop_adc()+\n"));
+
+ spin_lock_irqsave(&s->lock, flags);
+ s->ena &= ~FMODE_READ;
+
+ if (s->conversion == 1) {
+ s->conversion = 0;
+ s->prop_adc.fmt = s->prop_adc.fmt_original;
+ }
+ /* Nothing to do really, I need to keep the DMA going
+ XXXKW when do I get here, and is there more I should do? */
+ spin_unlock_irqrestore(&s->lock, flags);
+ CS_DBGOUT(CS_FUNCTION, 3,
+ printk(KERN_INFO "cs4297a: stop_adc()-\n"));
+}
+
+
+static void start_adc(struct cs4297a_state *s)
+{
+ unsigned long flags;
+
+ CS_DBGOUT(CS_FUNCTION, 2,
+ printk(KERN_INFO "cs4297a: start_adc()+\n"));
+
+ if (!(s->ena & FMODE_READ) &&
+ (s->dma_adc.mapped || s->dma_adc.count <=
+ (signed) (s->dma_adc.sbufsz - 2 * s->dma_adc.fragsize))
+ && s->dma_adc.ready) {
+ if (s->prop_adc.fmt & AFMT_S8 || s->prop_adc.fmt & AFMT_U8) {
+ //
+ // now only use 16 bit capture, due to truncation issue
+ // in the chip, noticeable distortion occurs.
+ // allocate buffer and then convert from 16 bit to
+ // 8 bit for the user buffer.
+ //
+ s->prop_adc.fmt_original = s->prop_adc.fmt;
+ if (s->prop_adc.fmt & AFMT_S8) {
+ s->prop_adc.fmt &= ~AFMT_S8;
+ s->prop_adc.fmt |= AFMT_S16_LE;
+ }
+ if (s->prop_adc.fmt & AFMT_U8) {
+ s->prop_adc.fmt &= ~AFMT_U8;
+ s->prop_adc.fmt |= AFMT_U16_LE;
+ }
+ //
+ // prog_dmabuf_adc performs a stop_adc() but that is
+ // ok since we really haven't started the DMA yet.
+ //
+ prog_codec(s, CS_TYPE_ADC);
+
+ prog_dmabuf_adc(s);
+ s->conversion = 1;
+ }
+ spin_lock_irqsave(&s->lock, flags);
+ s->ena |= FMODE_READ;
+ /* Nothing to do really, I am probably already
+ DMAing... XXXKW when do I get here, and is there
+ more I should do? */
+ spin_unlock_irqrestore(&s->lock, flags);
+
+ CS_DBGOUT(CS_PARMS, 6, printk(KERN_INFO
+ "cs4297a: start_adc(): start adc\n"));
+ }
+ CS_DBGOUT(CS_FUNCTION, 2,
+ printk(KERN_INFO "cs4297a: start_adc()-\n"));
+
+}
+
+
+// call with spinlock held!
+static void cs4297a_update_ptr(struct cs4297a_state *s, int intflag)
+{
+ int good_diff, diff, diff2;
+ u64 *data_p, data;
+ u32 *s_ptr;
+ unsigned hwptr;
+ u32 status;
+ serdma_t *d;
+ serdma_descr_t *descr;
+
+ // update ADC pointer
+ status = intflag ? __raw_readq(SS_CSR(R_SER_STATUS)) : 0;
+
+ if ((s->ena & FMODE_READ) || (status & (M_SYNCSER_RX_EOP_COUNT))) {
+ d = &s->dma_adc;
+ hwptr = (unsigned) (((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_RX)) & M_DMA_CURDSCR_ADDR) -
+ d->descrtab_phys) / sizeof(serdma_descr_t));
+
+ if (s->ena & FMODE_READ) {
+ CS_DBGOUT(CS_FUNCTION, 2,
+ printk(KERN_INFO "cs4297a: upd_rcv sw->hw->hw %x/%x/%x (int-%d)n",
+ d->swptr, d->hwptr, hwptr, intflag));
+ /* Number of DMA buffers available for software: */
+ diff2 = diff = (d->ringsz + hwptr - d->hwptr) % d->ringsz;
+ d->hwptr = hwptr;
+ good_diff = 0;
+ s_ptr = (u32 *)&(d->dma_buf[d->swptr*4]);
+ descr = &d->descrtab[d->swptr];
+ while (diff2--) {
+ u64 data = be64_to_cpu(*(u64 *)s_ptr);
+ u64 descr_a;
+ u16 left, right;
+ descr_a = descr->descr_a;
+ descr->descr_a &= ~M_DMA_SERRX_SOP;
+ if ((descr_a & M_DMA_DSCRA_A_ADDR) != CPHYSADDR((long)s_ptr)) {
+ printk(KERN_ERR "cs4297a: RX Bad address (read)\n");
+ }
+ if (((data & 0x9800000000000000) != 0x9800000000000000) ||
+ (!(descr_a & M_DMA_SERRX_SOP)) ||
+ (G_DMA_DSCRB_PKT_SIZE(descr->descr_b) != FRAME_BYTES)) {
+ s->stats.rx_bad++;
+ printk(KERN_DEBUG "cs4297a: RX Bad attributes (read)\n");
+ continue;
+ }
+ s->stats.rx_good++;
+ if ((data >> 61) == 7) {
+ s->read_value = (data >> 12) & 0xffff;
+ s->read_reg = (data >> 40) & 0x7f;
+ wake_up(&d->reg_wait);
+ }
+ if (d->count && (d->sb_hwptr == d->sb_swptr)) {
+ s->stats.rx_overflow++;
+ printk(KERN_DEBUG "cs4297a: RX overflow\n");
+ continue;
+ }
+ good_diff++;
+ left = ((be32_to_cpu(s_ptr[1]) & 0xff) << 8) |
+ ((be32_to_cpu(s_ptr[2]) >> 24) & 0xff);
+ right = (be32_to_cpu(s_ptr[2]) >> 4) & 0xffff;
+ *d->sb_hwptr++ = cpu_to_be16(left);
+ *d->sb_hwptr++ = cpu_to_be16(right);
+ if (d->sb_hwptr == d->sb_end)
+ d->sb_hwptr = d->sample_buf;
+ descr++;
+ if (descr == d->descrtab_end) {
+ descr = d->descrtab;
+ s_ptr = (u32 *)s->dma_adc.dma_buf;
+ } else {
+ s_ptr += 8;
+ }
+ }
+ d->total_bytes += good_diff * FRAME_SAMPLE_BYTES;
+ d->count += good_diff * FRAME_SAMPLE_BYTES;
+ if (d->count > d->sbufsz) {
+ printk(KERN_ERR "cs4297a: bogus receive overflow!!\n");
+ }
+ d->swptr = (d->swptr + diff) % d->ringsz;
+ __raw_writeq(diff, SS_CSR(R_SER_DMA_DSCR_COUNT_RX));
+ if (d->mapped) {
+ if (d->count >= (signed) d->fragsize)
+ wake_up(&d->wait);
+ } else {
+ if (d->count > 0) {
+ CS_DBGOUT(CS_WAVE_READ, 4,
+ printk(KERN_INFO
+ "cs4297a: update count -> %d\n", d->count));
+ wake_up(&d->wait);
+ }
+ }
+ } else {
+ /* Receive is going even if no one is
+ listening (for register accesses and to
+ avoid FIFO overrun) */
+ diff2 = diff = (hwptr + d->ringsz - d->hwptr) % d->ringsz;
+ if (!diff) {
+ printk(KERN_ERR "cs4297a: RX full or empty?\n");
+ }
+
+ descr = &d->descrtab[d->swptr];
+ data_p = &d->dma_buf[d->swptr*4];
+
+ /* Force this to happen at least once; I got
+ here because of an interrupt, so there must
+ be a buffer to process. */
+ do {
+ data = be64_to_cpu(*data_p);
+ if ((descr->descr_a & M_DMA_DSCRA_A_ADDR) != CPHYSADDR((long)data_p)) {
+ printk(KERN_ERR "cs4297a: RX Bad address %d (%llx %lx)\n", d->swptr,
+ (long long)(descr->descr_a & M_DMA_DSCRA_A_ADDR),
+ (long)CPHYSADDR((long)data_p));
+ }
+ if (!(data & (1LL << 63)) ||
+ !(descr->descr_a & M_DMA_SERRX_SOP) ||
+ (G_DMA_DSCRB_PKT_SIZE(descr->descr_b) != FRAME_BYTES)) {
+ s->stats.rx_bad++;
+ printk(KERN_DEBUG "cs4297a: RX Bad attributes\n");
+ } else {
+ s->stats.rx_good++;
+ if ((data >> 61) == 7) {
+ s->read_value = (data >> 12) & 0xffff;
+ s->read_reg = (data >> 40) & 0x7f;
+ wake_up(&d->reg_wait);
+ }
+ }
+ descr->descr_a &= ~M_DMA_SERRX_SOP;
+ descr++;
+ d->swptr++;
+ data_p += 4;
+ if (descr == d->descrtab_end) {
+ descr = d->descrtab;
+ d->swptr = 0;
+ data_p = d->dma_buf;
+ }
+ __raw_writeq(1, SS_CSR(R_SER_DMA_DSCR_COUNT_RX));
+ } while (--diff);
+ d->hwptr = hwptr;
+
+ CS_DBGOUT(CS_DESCR, 6,
+ printk(KERN_INFO "cs4297a: hw/sw %x/%x\n", d->hwptr, d->swptr));
+ }
+
+ CS_DBGOUT(CS_PARMS, 8, printk(KERN_INFO
+ "cs4297a: cs4297a_update_ptr(): s=0x%.8x hwptr=%d total_bytes=%d count=%d \n",
+ (unsigned)s, d->hwptr,
+ d->total_bytes, d->count));
+ }
+
+ /* XXXKW worry about s->reg_request -- there is a starvation
+ case if s->ena has FMODE_WRITE on, but the client isn't
+ doing writes */
+
+ // update DAC pointer
+ //
+ // check for end of buffer, means that we are going to wait for another interrupt
+ // to allow silence to fill the fifos on the part, to keep pops down to a minimum.
+ //
+ if (s->ena & FMODE_WRITE) {
+ serdma_t *d = &s->dma_dac;
+ hwptr = (unsigned) (((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_TX)) & M_DMA_CURDSCR_ADDR) -
+ d->descrtab_phys) / sizeof(serdma_descr_t));
+ diff = (d->ringsz + hwptr - d->hwptr) % d->ringsz;
+ CS_DBGOUT(CS_WAVE_WRITE, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_update_ptr(): hw/hw/sw %x/%x/%x diff %d count %d\n",
+ d->hwptr, hwptr, d->swptr, diff, d->count));
+ d->hwptr = hwptr;
+ /* XXXKW stereo? conversion? Just assume 2 16-bit samples for now */
+ d->total_bytes += diff * FRAME_SAMPLE_BYTES;
+ if (d->mapped) {
+ d->count += diff * FRAME_SAMPLE_BYTES;
+ if (d->count >= d->fragsize) {
+ d->wakeup = 1;
+ wake_up(&d->wait);
+ if (d->count > d->sbufsz)
+ d->count &= d->sbufsz - 1;
+ }
+ } else {
+ d->count -= diff * FRAME_SAMPLE_BYTES;
+ if (d->count <= 0) {
+ //
+ // fill with silence, and do not shut down the DAC.
+ // Continue to play silence until the _release.
+ //
+ CS_DBGOUT(CS_WAVE_WRITE, 6, printk(KERN_INFO
+ "cs4297a: cs4297a_update_ptr(): memset %d at 0x%.8x for %d size \n",
+ (unsigned)(s->prop_dac.fmt &
+ (AFMT_U8 | AFMT_U16_LE)) ? 0x80 : 0,
+ (unsigned)d->dma_buf,
+ d->ringsz));
+ memset(d->dma_buf, 0, d->ringsz * FRAME_BYTES);
+ if (d->count < 0) {
+ d->underrun = 1;
+ s->stats.tx_underrun++;
+ d->count = 0;
+ CS_DBGOUT(CS_ERROR, 9, printk(KERN_INFO
+ "cs4297a: cs4297a_update_ptr(): underrun\n"));
+ }
+ } else if (d->count <=
+ (signed) d->fragsize
+ && !d->endcleared) {
+ /* XXXKW what is this for? */
+ clear_advance(d->dma_buf,
+ d->sbufsz,
+ d->swptr,
+ d->fragsize,
+ 0);
+ d->endcleared = 1;
+ }
+ if ( (d->count <= (signed) d->sbufsz/2) || intflag)
+ {
+ CS_DBGOUT(CS_WAVE_WRITE, 4,
+ printk(KERN_INFO
+ "cs4297a: update count -> %d\n", d->count));
+ wake_up(&d->wait);
+ }
+ }
+ CS_DBGOUT(CS_PARMS, 8, printk(KERN_INFO
+ "cs4297a: cs4297a_update_ptr(): s=0x%.8x hwptr=%d total_bytes=%d count=%d \n",
+ (unsigned) s, d->hwptr,
+ d->total_bytes, d->count));
+ }
+}
+
+static int mixer_ioctl(struct cs4297a_state *s, unsigned int cmd,
+ unsigned long arg)
+{
+ // Index to mixer_src[] is value of AC97 Input Mux Select Reg.
+ // Value of array member is recording source Device ID Mask.
+ static const unsigned int mixer_src[8] = {
+ SOUND_MASK_MIC, SOUND_MASK_CD, 0, SOUND_MASK_LINE1,
+ SOUND_MASK_LINE, SOUND_MASK_VOLUME, 0, 0
+ };
+
+ // Index of mixtable1[] member is Device ID
+ // and must be <= SOUND_MIXER_NRDEVICES.
+ // Value of array member is index into s->mix.vol[]
+ static const unsigned char mixtable1[SOUND_MIXER_NRDEVICES] = {
+ [SOUND_MIXER_PCM] = 1, // voice
+ [SOUND_MIXER_LINE1] = 2, // AUX
+ [SOUND_MIXER_CD] = 3, // CD
+ [SOUND_MIXER_LINE] = 4, // Line
+ [SOUND_MIXER_SYNTH] = 5, // FM
+ [SOUND_MIXER_MIC] = 6, // Mic
+ [SOUND_MIXER_SPEAKER] = 7, // Speaker
+ [SOUND_MIXER_RECLEV] = 8, // Recording level
+ [SOUND_MIXER_VOLUME] = 9 // Master Volume
+ };
+
+ static const unsigned mixreg[] = {
+ AC97_PCMOUT_VOL,
+ AC97_AUX_VOL,
+ AC97_CD_VOL,
+ AC97_LINEIN_VOL
+ };
+ unsigned char l, r, rl, rr, vidx;
+ unsigned char attentbl[11] =
+ { 63, 42, 26, 17, 14, 11, 8, 6, 4, 2, 0 };
+ unsigned temp1;
+ int i, val;
+
+ VALIDATE_STATE(s);
+ CS_DBGOUT(CS_FUNCTION, 4, printk(KERN_INFO
+ "cs4297a: mixer_ioctl(): s=0x%.8x cmd=0x%.8x\n",
+ (unsigned) s, cmd));
+#if CSDEBUG
+ cs_printioctl(cmd);
+#endif
+#if CSDEBUG_INTERFACE
+
+ if ((cmd == SOUND_MIXER_CS_GETDBGMASK) ||
+ (cmd == SOUND_MIXER_CS_SETDBGMASK) ||
+ (cmd == SOUND_MIXER_CS_GETDBGLEVEL) ||
+ (cmd == SOUND_MIXER_CS_SETDBGLEVEL))
+ {
+ switch (cmd) {
+
+ case SOUND_MIXER_CS_GETDBGMASK:
+ return put_user(cs_debugmask,
+ (unsigned long *) arg);
+
+ case SOUND_MIXER_CS_GETDBGLEVEL:
+ return put_user(cs_debuglevel,
+ (unsigned long *) arg);
+
+ case SOUND_MIXER_CS_SETDBGMASK:
+ if (get_user(val, (unsigned long *) arg))
+ return -EFAULT;
+ cs_debugmask = val;
+ return 0;
+
+ case SOUND_MIXER_CS_SETDBGLEVEL:
+ if (get_user(val, (unsigned long *) arg))
+ return -EFAULT;
+ cs_debuglevel = val;
+ return 0;
+ default:
+ CS_DBGOUT(CS_ERROR, 1, printk(KERN_INFO
+ "cs4297a: mixer_ioctl(): ERROR unknown debug cmd\n"));
+ return 0;
+ }
+ }
+#endif
+
+ if (cmd == SOUND_MIXER_PRIVATE1) {
+ return -EINVAL;
+ }
+ if (cmd == SOUND_MIXER_PRIVATE2) {
+ // enable/disable/query spatializer
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ if (val != -1) {
+ temp1 = (val & 0x3f) >> 2;
+ cs4297a_write_ac97(s, AC97_3D_CONTROL, temp1);
+ cs4297a_read_ac97(s, AC97_GENERAL_PURPOSE,
+ &temp1);
+ cs4297a_write_ac97(s, AC97_GENERAL_PURPOSE,
+ temp1 | 0x2000);
+ }
+ cs4297a_read_ac97(s, AC97_3D_CONTROL, &temp1);
+ return put_user((temp1 << 2) | 3, (int *) arg);
+ }
+ if (cmd == SOUND_MIXER_INFO) {
+ mixer_info info;
+ memset(&info, 0, sizeof(info));
+ strlcpy(info.id, "CS4297a", sizeof(info.id));
+ strlcpy(info.name, "Crystal CS4297a", sizeof(info.name));
+ info.modify_counter = s->mix.modcnt;
+ if (copy_to_user((void *) arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+ if (cmd == SOUND_OLD_MIXER_INFO) {
+ _old_mixer_info info;
+ memset(&info, 0, sizeof(info));
+ strlcpy(info.id, "CS4297a", sizeof(info.id));
+ strlcpy(info.name, "Crystal CS4297a", sizeof(info.name));
+ if (copy_to_user((void *) arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+ if (cmd == OSS_GETVERSION)
+ return put_user(SOUND_VERSION, (int *) arg);
+
+ if (_IOC_TYPE(cmd) != 'M' || _SIOC_SIZE(cmd) != sizeof(int))
+ return -EINVAL;
+
+ // If ioctl has only the SIOC_READ bit(bit 31)
+ // on, process the only-read commands.
+ if (_SIOC_DIR(cmd) == _SIOC_READ) {
+ switch (_IOC_NR(cmd)) {
+ case SOUND_MIXER_RECSRC: // Arg contains a bit for each recording source
+ cs4297a_read_ac97(s, AC97_RECORD_SELECT,
+ &temp1);
+ return put_user(mixer_src[temp1 & 7], (int *) arg);
+
+ case SOUND_MIXER_DEVMASK: // Arg contains a bit for each supported device
+ return put_user(SOUND_MASK_PCM | SOUND_MASK_LINE |
+ SOUND_MASK_VOLUME | SOUND_MASK_RECLEV,
+ (int *) arg);
+
+ case SOUND_MIXER_RECMASK: // Arg contains a bit for each supported recording source
+ return put_user(SOUND_MASK_LINE | SOUND_MASK_VOLUME,
+ (int *) arg);
+
+ case SOUND_MIXER_STEREODEVS: // Mixer channels supporting stereo
+ return put_user(SOUND_MASK_PCM | SOUND_MASK_LINE |
+ SOUND_MASK_VOLUME | SOUND_MASK_RECLEV,
+ (int *) arg);
+
+ case SOUND_MIXER_CAPS:
+ return put_user(SOUND_CAP_EXCL_INPUT, (int *) arg);
+
+ default:
+ i = _IOC_NR(cmd);
+ if (i >= SOUND_MIXER_NRDEVICES
+ || !(vidx = mixtable1[i]))
+ return -EINVAL;
+ return put_user(s->mix.vol[vidx - 1], (int *) arg);
+ }
+ }
+ // If ioctl doesn't have both the SIOC_READ and
+ // the SIOC_WRITE bit set, return invalid.
+ if (_SIOC_DIR(cmd) != (_SIOC_READ | _SIOC_WRITE))
+ return -EINVAL;
+
+ // Increment the count of volume writes.
+ s->mix.modcnt++;
+
+ // Isolate the command; it must be a write.
+ switch (_IOC_NR(cmd)) {
+
+ case SOUND_MIXER_RECSRC: // Arg contains a bit for each recording source
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ i = hweight32(val); // i = # bits on in val.
+ if (i != 1) // One & only 1 bit must be on.
+ return 0;
+ for (i = 0; i < sizeof(mixer_src) / sizeof(int); i++) {
+ if (val == mixer_src[i]) {
+ temp1 = (i << 8) | i;
+ cs4297a_write_ac97(s,
+ AC97_RECORD_SELECT,
+ temp1);
+ return 0;
+ }
+ }
+ return 0;
+
+ case SOUND_MIXER_VOLUME:
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ l = val & 0xff;
+ if (l > 100)
+ l = 100; // Max soundcard.h vol is 100.
+ if (l < 6) {
+ rl = 63;
+ l = 0;
+ } else
+ rl = attentbl[(10 * l) / 100]; // Convert 0-100 vol to 63-0 atten.
+
+ r = (val >> 8) & 0xff;
+ if (r > 100)
+ r = 100; // Max right volume is 100, too
+ if (r < 6) {
+ rr = 63;
+ r = 0;
+ } else
+ rr = attentbl[(10 * r) / 100]; // Convert volume to attenuation.
+
+ if ((rl > 60) && (rr > 60)) // If both l & r are 'low',
+ temp1 = 0x8000; // turn on the mute bit.
+ else
+ temp1 = 0;
+
+ temp1 |= (rl << 8) | rr;
+
+ cs4297a_write_ac97(s, AC97_MASTER_VOL_STEREO, temp1);
+ cs4297a_write_ac97(s, AC97_PHONE_VOL, temp1);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+ s->mix.vol[8] = ((unsigned int) r << 8) | l;
+#else
+ s->mix.vol[8] = val;
+#endif
+ return put_user(s->mix.vol[8], (int *) arg);
+
+ case SOUND_MIXER_SPEAKER:
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ l = val & 0xff;
+ if (l > 100)
+ l = 100;
+ if (l < 3) {
+ rl = 0;
+ l = 0;
+ } else {
+ rl = (l * 2 - 5) / 13; // Convert 0-100 range to 0-15.
+ l = (rl * 13 + 5) / 2;
+ }
+
+ if (rl < 3) {
+ temp1 = 0x8000;
+ rl = 0;
+ } else
+ temp1 = 0;
+ rl = 15 - rl; // Convert volume to attenuation.
+ temp1 |= rl << 1;
+ cs4297a_write_ac97(s, AC97_PCBEEP_VOL, temp1);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+ s->mix.vol[6] = l << 8;
+#else
+ s->mix.vol[6] = val;
+#endif
+ return put_user(s->mix.vol[6], (int *) arg);
+
+ case SOUND_MIXER_RECLEV:
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ l = val & 0xff;
+ if (l > 100)
+ l = 100;
+ r = (val >> 8) & 0xff;
+ if (r > 100)
+ r = 100;
+ rl = (l * 2 - 5) / 13; // Convert 0-100 scale to 0-15.
+ rr = (r * 2 - 5) / 13;
+ if (rl < 3 && rr < 3)
+ temp1 = 0x8000;
+ else
+ temp1 = 0;
+
+ temp1 = temp1 | (rl << 8) | rr;
+ cs4297a_write_ac97(s, AC97_RECORD_GAIN, temp1);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+ s->mix.vol[7] = ((unsigned int) r << 8) | l;
+#else
+ s->mix.vol[7] = val;
+#endif
+ return put_user(s->mix.vol[7], (int *) arg);
+
+ case SOUND_MIXER_MIC:
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ l = val & 0xff;
+ if (l > 100)
+ l = 100;
+ if (l < 1) {
+ l = 0;
+ rl = 0;
+ } else {
+ rl = ((unsigned) l * 5 - 4) / 16; // Convert 0-100 range to 0-31.
+ l = (rl * 16 + 4) / 5;
+ }
+ cs4297a_read_ac97(s, AC97_MIC_VOL, &temp1);
+ temp1 &= 0x40; // Isolate 20db gain bit.
+ if (rl < 3) {
+ temp1 |= 0x8000;
+ rl = 0;
+ }
+ rl = 31 - rl; // Convert volume to attenuation.
+ temp1 |= rl;
+ cs4297a_write_ac97(s, AC97_MIC_VOL, temp1);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+ s->mix.vol[5] = val << 8;
+#else
+ s->mix.vol[5] = val;
+#endif
+ return put_user(s->mix.vol[5], (int *) arg);
+
+
+ case SOUND_MIXER_SYNTH:
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ l = val & 0xff;
+ if (l > 100)
+ l = 100;
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ r = (val >> 8) & 0xff;
+ if (r > 100)
+ r = 100;
+ rl = (l * 2 - 11) / 3; // Convert 0-100 range to 0-63.
+ rr = (r * 2 - 11) / 3;
+ if (rl < 3) // If l is low, turn on
+ temp1 = 0x0080; // the mute bit.
+ else
+ temp1 = 0;
+
+ rl = 63 - rl; // Convert vol to attenuation.
+// writel(temp1 | rl, s->pBA0 + FMLVC);
+ if (rr < 3) // If rr is low, turn on
+ temp1 = 0x0080; // the mute bit.
+ else
+ temp1 = 0;
+ rr = 63 - rr; // Convert vol to attenuation.
+// writel(temp1 | rr, s->pBA0 + FMRVC);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+ s->mix.vol[4] = (r << 8) | l;
+#else
+ s->mix.vol[4] = val;
+#endif
+ return put_user(s->mix.vol[4], (int *) arg);
+
+
+ default:
+ CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO
+ "cs4297a: mixer_ioctl(): default\n"));
+
+ i = _IOC_NR(cmd);
+ if (i >= SOUND_MIXER_NRDEVICES || !(vidx = mixtable1[i]))
+ return -EINVAL;
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ l = val & 0xff;
+ if (l > 100)
+ l = 100;
+ if (l < 1) {
+ l = 0;
+ rl = 31;
+ } else
+ rl = (attentbl[(l * 10) / 100]) >> 1;
+
+ r = (val >> 8) & 0xff;
+ if (r > 100)
+ r = 100;
+ if (r < 1) {
+ r = 0;
+ rr = 31;
+ } else
+ rr = (attentbl[(r * 10) / 100]) >> 1;
+ if ((rl > 30) && (rr > 30))
+ temp1 = 0x8000;
+ else
+ temp1 = 0;
+ temp1 = temp1 | (rl << 8) | rr;
+ cs4297a_write_ac97(s, mixreg[vidx - 1], temp1);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+ s->mix.vol[vidx - 1] = ((unsigned int) r << 8) | l;
+#else
+ s->mix.vol[vidx - 1] = val;
+#endif
+ return put_user(s->mix.vol[vidx - 1], (int *) arg);
+ }
+}
+
+
+// ---------------------------------------------------------------------
+
+static int cs4297a_open_mixdev(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ struct cs4297a_state *s=NULL;
+ struct list_head *entry;
+
+ CS_DBGOUT(CS_FUNCTION | CS_OPEN, 4,
+ printk(KERN_INFO "cs4297a: cs4297a_open_mixdev()+\n"));
+
+ mutex_lock(&swarm_cs4297a_mutex);
+ list_for_each(entry, &cs4297a_devs)
+ {
+ s = list_entry(entry, struct cs4297a_state, list);
+ if(s->dev_mixer == minor)
+ break;
+ }
+ if (!s)
+ {
+ CS_DBGOUT(CS_FUNCTION | CS_OPEN | CS_ERROR, 2,
+ printk(KERN_INFO "cs4297a: cs4297a_open_mixdev()- -ENODEV\n"));
+
+ mutex_unlock(&swarm_cs4297a_mutex);
+ return -ENODEV;
+ }
+ VALIDATE_STATE(s);
+ file->private_data = s;
+
+ CS_DBGOUT(CS_FUNCTION | CS_OPEN, 4,
+ printk(KERN_INFO "cs4297a: cs4297a_open_mixdev()- 0\n"));
+ mutex_unlock(&swarm_cs4297a_mutex);
+
+ return nonseekable_open(inode, file);
+}
+
+
+static int cs4297a_release_mixdev(struct inode *inode, struct file *file)
+{
+ struct cs4297a_state *s =
+ (struct cs4297a_state *) file->private_data;
+
+ VALIDATE_STATE(s);
+ return 0;
+}
+
+
+static int cs4297a_ioctl_mixdev(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int ret;
+ mutex_lock(&swarm_cs4297a_mutex);
+ ret = mixer_ioctl((struct cs4297a_state *) file->private_data, cmd,
+ arg);
+ mutex_unlock(&swarm_cs4297a_mutex);
+ return ret;
+}
+
+
+// ******************************************************************************************
+// Mixer file operations struct.
+// ******************************************************************************************
+static const struct file_operations cs4297a_mixer_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .unlocked_ioctl = cs4297a_ioctl_mixdev,
+ .open = cs4297a_open_mixdev,
+ .release = cs4297a_release_mixdev,
+};
+
+// ---------------------------------------------------------------------
+
+
+static int drain_adc(struct cs4297a_state *s, int nonblock)
+{
+ /* This routine serves no purpose currently - any samples
+ sitting in the receive queue will just be processed by the
+ background consumer. This would be different if DMA
+ actually stopped when there were no clients. */
+ return 0;
+}
+
+static int drain_dac(struct cs4297a_state *s, int nonblock)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long flags;
+ unsigned hwptr;
+ unsigned tmo;
+ int count;
+
+ if (s->dma_dac.mapped)
+ return 0;
+ if (nonblock)
+ return -EBUSY;
+ add_wait_queue(&s->dma_dac.wait, &wait);
+ while ((count = __raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_TX))) ||
+ (s->dma_dac.count > 0)) {
+ if (!signal_pending(current)) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ /* XXXKW is this calculation working? */
+ tmo = ((count * FRAME_TX_US) * HZ) / 1000000;
+ schedule_timeout(tmo + 1);
+ } else {
+ /* XXXKW do I care if there is a signal pending? */
+ }
+ }
+ spin_lock_irqsave(&s->lock, flags);
+ /* Reset the bookkeeping */
+ hwptr = (int)(((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_TX)) & M_DMA_CURDSCR_ADDR) -
+ s->dma_dac.descrtab_phys) / sizeof(serdma_descr_t));
+ s->dma_dac.hwptr = s->dma_dac.swptr = hwptr;
+ spin_unlock_irqrestore(&s->lock, flags);
+ remove_wait_queue(&s->dma_dac.wait, &wait);
+ current->state = TASK_RUNNING;
+ return 0;
+}
+
+
+// ---------------------------------------------------------------------
+
+static ssize_t cs4297a_read(struct file *file, char *buffer, size_t count,
+ loff_t * ppos)
+{
+ struct cs4297a_state *s =
+ (struct cs4297a_state *) file->private_data;
+ ssize_t ret;
+ unsigned long flags;
+ int cnt, count_fr, cnt_by;
+ unsigned copied = 0;
+
+ CS_DBGOUT(CS_FUNCTION | CS_WAVE_READ, 2,
+ printk(KERN_INFO "cs4297a: cs4297a_read()+ %d \n", count));
+
+ VALIDATE_STATE(s);
+ if (s->dma_adc.mapped)
+ return -ENXIO;
+ if (!s->dma_adc.ready && (ret = prog_dmabuf_adc(s)))
+ return ret;
+ if (!access_ok(VERIFY_WRITE, buffer, count))
+ return -EFAULT;
+ ret = 0;
+//
+// "count" is the amount of bytes to read (from app), is decremented each loop
+// by the amount of bytes that have been returned to the user buffer.
+// "cnt" is the running total of each read from the buffer (changes each loop)
+// "buffer" points to the app's buffer
+// "ret" keeps a running total of the amount of bytes that have been copied
+// to the user buffer.
+// "copied" is the total bytes copied into the user buffer for each loop.
+//
+ while (count > 0) {
+ CS_DBGOUT(CS_WAVE_READ, 8, printk(KERN_INFO
+ "_read() count>0 count=%d .count=%d .swptr=%d .hwptr=%d \n",
+ count, s->dma_adc.count,
+ s->dma_adc.swptr, s->dma_adc.hwptr));
+ spin_lock_irqsave(&s->lock, flags);
+
+ /* cnt will be the number of available samples (16-bit
+ stereo); it starts out as the maxmimum consequetive
+ samples */
+ cnt = (s->dma_adc.sb_end - s->dma_adc.sb_swptr) / 2;
+ count_fr = s->dma_adc.count / FRAME_SAMPLE_BYTES;
+
+ // dma_adc.count is the current total bytes that have not been read.
+ // if the amount of unread bytes from the current sw pointer to the
+ // end of the buffer is greater than the current total bytes that
+ // have not been read, then set the "cnt" (unread bytes) to the
+ // amount of unread bytes.
+
+ if (count_fr < cnt)
+ cnt = count_fr;
+ cnt_by = cnt * FRAME_SAMPLE_BYTES;
+ spin_unlock_irqrestore(&s->lock, flags);
+ //
+ // if we are converting from 8/16 then we need to copy
+ // twice the number of 16 bit bytes then 8 bit bytes.
+ //
+ if (s->conversion) {
+ if (cnt_by > (count * 2)) {
+ cnt = (count * 2) / FRAME_SAMPLE_BYTES;
+ cnt_by = count * 2;
+ }
+ } else {
+ if (cnt_by > count) {
+ cnt = count / FRAME_SAMPLE_BYTES;
+ cnt_by = count;
+ }
+ }
+ //
+ // "cnt" NOW is the smaller of the amount that will be read,
+ // and the amount that is requested in this read (or partial).
+ // if there are no bytes in the buffer to read, then start the
+ // ADC and wait for the interrupt handler to wake us up.
+ //
+ if (cnt <= 0) {
+
+ // start up the dma engine and then continue back to the top of
+ // the loop when wake up occurs.
+ start_adc(s);
+ if (file->f_flags & O_NONBLOCK)
+ return ret ? ret : -EAGAIN;
+ interruptible_sleep_on(&s->dma_adc.wait);
+ if (signal_pending(current))
+ return ret ? ret : -ERESTARTSYS;
+ continue;
+ }
+ // there are bytes in the buffer to read.
+ // copy from the hw buffer over to the user buffer.
+ // user buffer is designated by "buffer"
+ // virtual address to copy from is dma_buf+swptr
+ // the "cnt" is the number of bytes to read.
+
+ CS_DBGOUT(CS_WAVE_READ, 2, printk(KERN_INFO
+ "_read() copy_to cnt=%d count=%d ", cnt_by, count));
+ CS_DBGOUT(CS_WAVE_READ, 8, printk(KERN_INFO
+ " .sbufsz=%d .count=%d buffer=0x%.8x ret=%d\n",
+ s->dma_adc.sbufsz, s->dma_adc.count,
+ (unsigned) buffer, ret));
+
+ if (copy_to_user (buffer, ((void *)s->dma_adc.sb_swptr), cnt_by))
+ return ret ? ret : -EFAULT;
+ copied = cnt_by;
+
+ /* Return the descriptors */
+ spin_lock_irqsave(&s->lock, flags);
+ CS_DBGOUT(CS_FUNCTION, 2,
+ printk(KERN_INFO "cs4297a: upd_rcv sw->hw %x/%x\n", s->dma_adc.swptr, s->dma_adc.hwptr));
+ s->dma_adc.count -= cnt_by;
+ s->dma_adc.sb_swptr += cnt * 2;
+ if (s->dma_adc.sb_swptr == s->dma_adc.sb_end)
+ s->dma_adc.sb_swptr = s->dma_adc.sample_buf;
+ spin_unlock_irqrestore(&s->lock, flags);
+ count -= copied;
+ buffer += copied;
+ ret += copied;
+ start_adc(s);
+ }
+ CS_DBGOUT(CS_FUNCTION | CS_WAVE_READ, 2,
+ printk(KERN_INFO "cs4297a: cs4297a_read()- %d\n", ret));
+ return ret;
+}
+
+
+static ssize_t cs4297a_write(struct file *file, const char *buffer,
+ size_t count, loff_t * ppos)
+{
+ struct cs4297a_state *s =
+ (struct cs4297a_state *) file->private_data;
+ ssize_t ret;
+ unsigned long flags;
+ unsigned swptr, hwptr;
+ int cnt;
+
+ CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE, 2,
+ printk(KERN_INFO "cs4297a: cs4297a_write()+ count=%d\n",
+ count));
+ VALIDATE_STATE(s);
+
+ if (s->dma_dac.mapped)
+ return -ENXIO;
+ if (!s->dma_dac.ready && (ret = prog_dmabuf_dac(s)))
+ return ret;
+ if (!access_ok(VERIFY_READ, buffer, count))
+ return -EFAULT;
+ ret = 0;
+ while (count > 0) {
+ serdma_t *d = &s->dma_dac;
+ int copy_cnt;
+ u32 *s_tmpl;
+ u32 *t_tmpl;
+ u32 left, right;
+ int swap = (s->prop_dac.fmt == AFMT_S16_LE) || (s->prop_dac.fmt == AFMT_U16_LE);
+
+ /* XXXXXX this is broken for BLOAT_FACTOR */
+ spin_lock_irqsave(&s->lock, flags);
+ if (d->count < 0) {
+ d->count = 0;
+ d->swptr = d->hwptr;
+ }
+ if (d->underrun) {
+ d->underrun = 0;
+ hwptr = (unsigned) (((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_TX)) & M_DMA_CURDSCR_ADDR) -
+ d->descrtab_phys) / sizeof(serdma_descr_t));
+ d->swptr = d->hwptr = hwptr;
+ }
+ swptr = d->swptr;
+ cnt = d->sbufsz - (swptr * FRAME_SAMPLE_BYTES);
+ /* Will this write fill up the buffer? */
+ if (d->count + cnt > d->sbufsz)
+ cnt = d->sbufsz - d->count;
+ spin_unlock_irqrestore(&s->lock, flags);
+ if (cnt > count)
+ cnt = count;
+ if (cnt <= 0) {
+ start_dac(s);
+ if (file->f_flags & O_NONBLOCK)
+ return ret ? ret : -EAGAIN;
+ interruptible_sleep_on(&d->wait);
+ if (signal_pending(current))
+ return ret ? ret : -ERESTARTSYS;
+ continue;
+ }
+ if (copy_from_user(d->sample_buf, buffer, cnt))
+ return ret ? ret : -EFAULT;
+
+ copy_cnt = cnt;
+ s_tmpl = (u32 *)d->sample_buf;
+ t_tmpl = (u32 *)(d->dma_buf + (swptr * 4));
+
+ /* XXXKW assuming 16-bit stereo! */
+ do {
+ u32 tmp;
+
+ t_tmpl[0] = cpu_to_be32(0x98000000);
+
+ tmp = be32_to_cpu(s_tmpl[0]);
+ left = tmp & 0xffff;
+ right = tmp >> 16;
+ if (swap) {
+ left = swab16(left);
+ right = swab16(right);
+ }
+ t_tmpl[1] = cpu_to_be32(left >> 8);
+ t_tmpl[2] = cpu_to_be32(((left & 0xff) << 24) |
+ (right << 4));
+
+ s_tmpl++;
+ t_tmpl += 8;
+ copy_cnt -= 4;
+ } while (copy_cnt);
+
+ /* Mux in any pending read/write accesses */
+ if (s->reg_request) {
+ *(u64 *)(d->dma_buf + (swptr * 4)) |=
+ cpu_to_be64(s->reg_request);
+ s->reg_request = 0;
+ wake_up(&s->dma_dac.reg_wait);
+ }
+
+ CS_DBGOUT(CS_WAVE_WRITE, 4,
+ printk(KERN_INFO
+ "cs4297a: copy in %d to swptr %x\n", cnt, swptr));
+
+ swptr = (swptr + (cnt/FRAME_SAMPLE_BYTES)) % d->ringsz;
+ __raw_writeq(cnt/FRAME_SAMPLE_BYTES, SS_CSR(R_SER_DMA_DSCR_COUNT_TX));
+ spin_lock_irqsave(&s->lock, flags);
+ d->swptr = swptr;
+ d->count += cnt;
+ d->endcleared = 0;
+ spin_unlock_irqrestore(&s->lock, flags);
+ count -= cnt;
+ buffer += cnt;
+ ret += cnt;
+ start_dac(s);
+ }
+ CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE, 2,
+ printk(KERN_INFO "cs4297a: cs4297a_write()- %d\n", ret));
+ return ret;
+}
+
+
+static unsigned int cs4297a_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct cs4297a_state *s =
+ (struct cs4297a_state *) file->private_data;
+ unsigned long flags;
+ unsigned int mask = 0;
+
+ CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE | CS_WAVE_READ, 4,
+ printk(KERN_INFO "cs4297a: cs4297a_poll()+\n"));
+ VALIDATE_STATE(s);
+ if (file->f_mode & FMODE_WRITE) {
+ CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE | CS_WAVE_READ, 4,
+ printk(KERN_INFO
+ "cs4297a: cs4297a_poll() wait on FMODE_WRITE\n"));
+ if(!s->dma_dac.ready && prog_dmabuf_dac(s))
+ return 0;
+ poll_wait(file, &s->dma_dac.wait, wait);
+ }
+ if (file->f_mode & FMODE_READ) {
+ CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE | CS_WAVE_READ, 4,
+ printk(KERN_INFO
+ "cs4297a: cs4297a_poll() wait on FMODE_READ\n"));
+ if(!s->dma_dac.ready && prog_dmabuf_adc(s))
+ return 0;
+ poll_wait(file, &s->dma_adc.wait, wait);
+ }
+ spin_lock_irqsave(&s->lock, flags);
+ cs4297a_update_ptr(s,CS_FALSE);
+ if (file->f_mode & FMODE_WRITE) {
+ if (s->dma_dac.mapped) {
+ if (s->dma_dac.count >=
+ (signed) s->dma_dac.fragsize) {
+ if (s->dma_dac.wakeup)
+ mask |= POLLOUT | POLLWRNORM;
+ else
+ mask = 0;
+ s->dma_dac.wakeup = 0;
+ }
+ } else {
+ if ((signed) (s->dma_dac.sbufsz/2) >= s->dma_dac.count)
+ mask |= POLLOUT | POLLWRNORM;
+ }
+ } else if (file->f_mode & FMODE_READ) {
+ if (s->dma_adc.mapped) {
+ if (s->dma_adc.count >= (signed) s->dma_adc.fragsize)
+ mask |= POLLIN | POLLRDNORM;
+ } else {
+ if (s->dma_adc.count > 0)
+ mask |= POLLIN | POLLRDNORM;
+ }
+ }
+ spin_unlock_irqrestore(&s->lock, flags);
+ CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE | CS_WAVE_READ, 4,
+ printk(KERN_INFO "cs4297a: cs4297a_poll()- 0x%.8x\n",
+ mask));
+ return mask;
+}
+
+
+static int cs4297a_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ /* XXXKW currently no mmap support */
+ return -EINVAL;
+ return 0;
+}
+
+
+static int cs4297a_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct cs4297a_state *s =
+ (struct cs4297a_state *) file->private_data;
+ unsigned long flags;
+ audio_buf_info abinfo;
+ count_info cinfo;
+ int val, mapped, ret;
+
+ CS_DBGOUT(CS_FUNCTION|CS_IOCTL, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): file=0x%.8x cmd=0x%.8x\n",
+ (unsigned) file, cmd));
+#if CSDEBUG
+ cs_printioctl(cmd);
+#endif
+ VALIDATE_STATE(s);
+ mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac.mapped) ||
+ ((file->f_mode & FMODE_READ) && s->dma_adc.mapped);
+ switch (cmd) {
+ case OSS_GETVERSION:
+ CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): SOUND_VERSION=0x%.8x\n",
+ SOUND_VERSION));
+ return put_user(SOUND_VERSION, (int *) arg);
+
+ case SNDCTL_DSP_SYNC:
+ CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): DSP_SYNC\n"));
+ if (file->f_mode & FMODE_WRITE)
+ return drain_dac(s,
+ 0 /*file->f_flags & O_NONBLOCK */
+ );
+ return 0;
+
+ case SNDCTL_DSP_SETDUPLEX:
+ return 0;
+
+ case SNDCTL_DSP_GETCAPS:
+ return put_user(DSP_CAP_DUPLEX | DSP_CAP_REALTIME |
+ DSP_CAP_TRIGGER | DSP_CAP_MMAP,
+ (int *) arg);
+
+ case SNDCTL_DSP_RESET:
+ CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): DSP_RESET\n"));
+ if (file->f_mode & FMODE_WRITE) {
+ stop_dac(s);
+ synchronize_irq(s->irq);
+ s->dma_dac.count = s->dma_dac.total_bytes =
+ s->dma_dac.blocks = s->dma_dac.wakeup = 0;
+ s->dma_dac.swptr = s->dma_dac.hwptr =
+ (int)(((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_TX)) & M_DMA_CURDSCR_ADDR) -
+ s->dma_dac.descrtab_phys) / sizeof(serdma_descr_t));
+ }
+ if (file->f_mode & FMODE_READ) {
+ stop_adc(s);
+ synchronize_irq(s->irq);
+ s->dma_adc.count = s->dma_adc.total_bytes =
+ s->dma_adc.blocks = s->dma_dac.wakeup = 0;
+ s->dma_adc.swptr = s->dma_adc.hwptr =
+ (int)(((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_RX)) & M_DMA_CURDSCR_ADDR) -
+ s->dma_adc.descrtab_phys) / sizeof(serdma_descr_t));
+ }
+ return 0;
+
+ case SNDCTL_DSP_SPEED:
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): DSP_SPEED val=%d -> 48000\n", val));
+ val = 48000;
+ return put_user(val, (int *) arg);
+
+ case SNDCTL_DSP_STEREO:
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): DSP_STEREO val=%d\n", val));
+ if (file->f_mode & FMODE_READ) {
+ stop_adc(s);
+ s->dma_adc.ready = 0;
+ s->prop_adc.channels = val ? 2 : 1;
+ }
+ if (file->f_mode & FMODE_WRITE) {
+ stop_dac(s);
+ s->dma_dac.ready = 0;
+ s->prop_dac.channels = val ? 2 : 1;
+ }
+ return 0;
+
+ case SNDCTL_DSP_CHANNELS:
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): DSP_CHANNELS val=%d\n",
+ val));
+ if (val != 0) {
+ if (file->f_mode & FMODE_READ) {
+ stop_adc(s);
+ s->dma_adc.ready = 0;
+ if (val >= 2)
+ s->prop_adc.channels = 2;
+ else
+ s->prop_adc.channels = 1;
+ }
+ if (file->f_mode & FMODE_WRITE) {
+ stop_dac(s);
+ s->dma_dac.ready = 0;
+ if (val >= 2)
+ s->prop_dac.channels = 2;
+ else
+ s->prop_dac.channels = 1;
+ }
+ }
+
+ if (file->f_mode & FMODE_WRITE)
+ val = s->prop_dac.channels;
+ else if (file->f_mode & FMODE_READ)
+ val = s->prop_adc.channels;
+
+ return put_user(val, (int *) arg);
+
+ case SNDCTL_DSP_GETFMTS: // Returns a mask
+ CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): DSP_GETFMT val=0x%.8x\n",
+ AFMT_S16_LE | AFMT_U16_LE | AFMT_S8 |
+ AFMT_U8));
+ return put_user(AFMT_S16_LE | AFMT_U16_LE | AFMT_S8 |
+ AFMT_U8, (int *) arg);
+
+ case SNDCTL_DSP_SETFMT:
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): DSP_SETFMT val=0x%.8x\n",
+ val));
+ if (val != AFMT_QUERY) {
+ if (file->f_mode & FMODE_READ) {
+ stop_adc(s);
+ s->dma_adc.ready = 0;
+ if (val != AFMT_S16_LE
+ && val != AFMT_U16_LE && val != AFMT_S8
+ && val != AFMT_U8)
+ val = AFMT_U8;
+ s->prop_adc.fmt = val;
+ s->prop_adc.fmt_original = s->prop_adc.fmt;
+ }
+ if (file->f_mode & FMODE_WRITE) {
+ stop_dac(s);
+ s->dma_dac.ready = 0;
+ if (val != AFMT_S16_LE
+ && val != AFMT_U16_LE && val != AFMT_S8
+ && val != AFMT_U8)
+ val = AFMT_U8;
+ s->prop_dac.fmt = val;
+ s->prop_dac.fmt_original = s->prop_dac.fmt;
+ }
+ } else {
+ if (file->f_mode & FMODE_WRITE)
+ val = s->prop_dac.fmt_original;
+ else if (file->f_mode & FMODE_READ)
+ val = s->prop_adc.fmt_original;
+ }
+ CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): DSP_SETFMT return val=0x%.8x\n",
+ val));
+ return put_user(val, (int *) arg);
+
+ case SNDCTL_DSP_POST:
+ CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): DSP_POST\n"));
+ return 0;
+
+ case SNDCTL_DSP_GETTRIGGER:
+ val = 0;
+ if (file->f_mode & s->ena & FMODE_READ)
+ val |= PCM_ENABLE_INPUT;
+ if (file->f_mode & s->ena & FMODE_WRITE)
+ val |= PCM_ENABLE_OUTPUT;
+ return put_user(val, (int *) arg);
+
+ case SNDCTL_DSP_SETTRIGGER:
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ if (file->f_mode & FMODE_READ) {
+ if (val & PCM_ENABLE_INPUT) {
+ if (!s->dma_adc.ready
+ && (ret = prog_dmabuf_adc(s)))
+ return ret;
+ start_adc(s);
+ } else
+ stop_adc(s);
+ }
+ if (file->f_mode & FMODE_WRITE) {
+ if (val & PCM_ENABLE_OUTPUT) {
+ if (!s->dma_dac.ready
+ && (ret = prog_dmabuf_dac(s)))
+ return ret;
+ start_dac(s);
+ } else
+ stop_dac(s);
+ }
+ return 0;
+
+ case SNDCTL_DSP_GETOSPACE:
+ if (!(file->f_mode & FMODE_WRITE))
+ return -EINVAL;
+ if (!s->dma_dac.ready && (val = prog_dmabuf_dac(s)))
+ return val;
+ spin_lock_irqsave(&s->lock, flags);
+ cs4297a_update_ptr(s,CS_FALSE);
+ abinfo.fragsize = s->dma_dac.fragsize;
+ if (s->dma_dac.mapped)
+ abinfo.bytes = s->dma_dac.sbufsz;
+ else
+ abinfo.bytes =
+ s->dma_dac.sbufsz - s->dma_dac.count;
+ abinfo.fragstotal = s->dma_dac.numfrag;
+ abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift;
+ CS_DBGOUT(CS_FUNCTION | CS_PARMS, 4, printk(KERN_INFO
+ "cs4297a: cs4297a_ioctl(): GETOSPACE .fragsize=%d .bytes=%d .fragstotal=%d .fragments=%d\n",
+ abinfo.fragsize,abinfo.bytes,abinfo.fragstotal,
+ abinfo.fragments));
+ spin_unlock_irqrestore(&s->lock, flags);
+ return copy_to_user((void *) arg, &abinfo,
+ sizeof(abinfo)) ? -EFAULT : 0;
+
+ case SNDCTL_DSP_GETISPACE:
+ if (!(file->f_mode & FMODE_READ))
+ return -EINVAL;
+ if (!s->dma_adc.ready && (val = prog_dmabuf_adc(s)))
+ return val;
+ spin_lock_irqsave(&s->lock, flags);
+ cs4297a_update_ptr(s,CS_FALSE);
+ if (s->conversion) {
+ abinfo.fragsize = s->dma_adc.fragsize / 2;
+ abinfo.bytes = s->dma_adc.count / 2;
+ abinfo.fragstotal = s->dma_adc.numfrag;
+ abinfo.fragments =
+ abinfo.bytes >> (s->dma_adc.fragshift - 1);
+ } else {
+ abinfo.fragsize = s->dma_adc.fragsize;
+ abinfo.bytes = s->dma_adc.count;
+ abinfo.fragstotal = s->dma_adc.numfrag;
+ abinfo.fragments =
+ abinfo.bytes >> s->dma_adc.fragshift;
+ }
+ spin_unlock_irqrestore(&s->lock, flags);
+ return copy_to_user((void *) arg, &abinfo,
+ sizeof(abinfo)) ? -EFAULT : 0;
+
+ case SNDCTL_DSP_NONBLOCK:
+ spin_lock(&file->f_lock);
+ file->f_flags |= O_NONBLOCK;
+ spin_unlock(&file->f_lock);
+ return 0;
+
+ case SNDCTL_DSP_GETODELAY:
+ if (!(file->f_mode & FMODE_WRITE))
+ return -EINVAL;
+ if(!s->dma_dac.ready && prog_dmabuf_dac(s))
+ return 0;
+ spin_lock_irqsave(&s->lock, flags);
+ cs4297a_update_ptr(s,CS_FALSE);
+ val = s->dma_dac.count;
+ spin_unlock_irqrestore(&s->lock, flags);
+ return put_user(val, (int *) arg);
+
+ case SNDCTL_DSP_GETIPTR:
+ if (!(file->f_mode & FMODE_READ))
+ return -EINVAL;
+ if(!s->dma_adc.ready && prog_dmabuf_adc(s))
+ return 0;
+ spin_lock_irqsave(&s->lock, flags);
+ cs4297a_update_ptr(s,CS_FALSE);
+ cinfo.bytes = s->dma_adc.total_bytes;
+ if (s->dma_adc.mapped) {
+ cinfo.blocks =
+ (cinfo.bytes >> s->dma_adc.fragshift) -
+ s->dma_adc.blocks;
+ s->dma_adc.blocks =
+ cinfo.bytes >> s->dma_adc.fragshift;
+ } else {
+ if (s->conversion) {
+ cinfo.blocks =
+ s->dma_adc.count /
+ 2 >> (s->dma_adc.fragshift - 1);
+ } else
+ cinfo.blocks =
+ s->dma_adc.count >> s->dma_adc.
+ fragshift;
+ }
+ if (s->conversion)
+ cinfo.ptr = s->dma_adc.hwptr / 2;
+ else
+ cinfo.ptr = s->dma_adc.hwptr;
+ if (s->dma_adc.mapped)
+ s->dma_adc.count &= s->dma_adc.fragsize - 1;
+ spin_unlock_irqrestore(&s->lock, flags);
+ return copy_to_user((void *) arg, &cinfo, sizeof(cinfo)) ? -EFAULT : 0;
+
+ case SNDCTL_DSP_GETOPTR:
+ if (!(file->f_mode & FMODE_WRITE))
+ return -EINVAL;
+ if(!s->dma_dac.ready && prog_dmabuf_dac(s))
+ return 0;
+ spin_lock_irqsave(&s->lock, flags);
+ cs4297a_update_ptr(s,CS_FALSE);
+ cinfo.bytes = s->dma_dac.total_bytes;
+ if (s->dma_dac.mapped) {
+ cinfo.blocks =
+ (cinfo.bytes >> s->dma_dac.fragshift) -
+ s->dma_dac.blocks;
+ s->dma_dac.blocks =
+ cinfo.bytes >> s->dma_dac.fragshift;
+ } else {
+ cinfo.blocks =
+ s->dma_dac.count >> s->dma_dac.fragshift;
+ }
+ cinfo.ptr = s->dma_dac.hwptr;
+ if (s->dma_dac.mapped)
+ s->dma_dac.count &= s->dma_dac.fragsize - 1;
+ spin_unlock_irqrestore(&s->lock, flags);
+ return copy_to_user((void *) arg, &cinfo, sizeof(cinfo)) ? -EFAULT : 0;
+
+ case SNDCTL_DSP_GETBLKSIZE:
+ if (file->f_mode & FMODE_WRITE) {
+ if ((val = prog_dmabuf_dac(s)))
+ return val;
+ return put_user(s->dma_dac.fragsize, (int *) arg);
+ }
+ if ((val = prog_dmabuf_adc(s)))
+ return val;
+ if (s->conversion)
+ return put_user(s->dma_adc.fragsize / 2,
+ (int *) arg);
+ else
+ return put_user(s->dma_adc.fragsize, (int *) arg);
+
+ case SNDCTL_DSP_SETFRAGMENT:
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ return 0; // Say OK, but do nothing.
+
+ case SNDCTL_DSP_SUBDIVIDE:
+ if ((file->f_mode & FMODE_READ && s->dma_adc.subdivision)
+ || (file->f_mode & FMODE_WRITE
+ && s->dma_dac.subdivision)) return -EINVAL;
+ if (get_user(val, (int *) arg))
+ return -EFAULT;
+ if (val != 1 && val != 2 && val != 4)
+ return -EINVAL;
+ if (file->f_mode & FMODE_READ)
+ s->dma_adc.subdivision = val;
+ else if (file->f_mode & FMODE_WRITE)
+ s->dma_dac.subdivision = val;
+ return 0;
+
+ case SOUND_PCM_READ_RATE:
+ if (file->f_mode & FMODE_READ)
+ return put_user(s->prop_adc.rate, (int *) arg);
+ else if (file->f_mode & FMODE_WRITE)
+ return put_user(s->prop_dac.rate, (int *) arg);
+
+ case SOUND_PCM_READ_CHANNELS:
+ if (file->f_mode & FMODE_READ)
+ return put_user(s->prop_adc.channels, (int *) arg);
+ else if (file->f_mode & FMODE_WRITE)
+ return put_user(s->prop_dac.channels, (int *) arg);
+
+ case SOUND_PCM_READ_BITS:
+ if (file->f_mode & FMODE_READ)
+ return
+ put_user(
+ (s->prop_adc.
+ fmt & (AFMT_S8 | AFMT_U8)) ? 8 : 16,
+ (int *) arg);
+ else if (file->f_mode & FMODE_WRITE)
+ return
+ put_user(
+ (s->prop_dac.
+ fmt & (AFMT_S8 | AFMT_U8)) ? 8 : 16,
+ (int *) arg);
+
+ case SOUND_PCM_WRITE_FILTER:
+ case SNDCTL_DSP_SETSYNCRO:
+ case SOUND_PCM_READ_FILTER:
+ return -EINVAL;
+ }
+ return mixer_ioctl(s, cmd, arg);
+}
+
+static long cs4297a_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
+{
+ int ret;
+
+ mutex_lock(&swarm_cs4297a_mutex);
+ ret = cs4297a_ioctl(file, cmd, arg);
+ mutex_unlock(&swarm_cs4297a_mutex);
+
+ return ret;
+}
+
+static int cs4297a_release(struct inode *inode, struct file *file)
+{
+ struct cs4297a_state *s =
+ (struct cs4297a_state *) file->private_data;
+
+ CS_DBGOUT(CS_FUNCTION | CS_RELEASE, 2, printk(KERN_INFO
+ "cs4297a: cs4297a_release(): inode=0x%.8x file=0x%.8x f_mode=0x%x\n",
+ (unsigned) inode, (unsigned) file, file->f_mode));
+ VALIDATE_STATE(s);
+
+ if (file->f_mode & FMODE_WRITE) {
+ drain_dac(s, file->f_flags & O_NONBLOCK);
+ mutex_lock(&s->open_sem_dac);
+ stop_dac(s);
+ dealloc_dmabuf(s, &s->dma_dac);
+ s->open_mode &= ~FMODE_WRITE;
+ mutex_unlock(&s->open_sem_dac);
+ wake_up(&s->open_wait_dac);
+ }
+ if (file->f_mode & FMODE_READ) {
+ drain_adc(s, file->f_flags & O_NONBLOCK);
+ mutex_lock(&s->open_sem_adc);
+ stop_adc(s);
+ dealloc_dmabuf(s, &s->dma_adc);
+ s->open_mode &= ~FMODE_READ;
+ mutex_unlock(&s->open_sem_adc);
+ wake_up(&s->open_wait_adc);
+ }
+ return 0;
+}
+
+static int cs4297a_locked_open(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ struct cs4297a_state *s=NULL;
+ struct list_head *entry;
+
+ CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, printk(KERN_INFO
+ "cs4297a: cs4297a_open(): inode=0x%.8x file=0x%.8x f_mode=0x%x\n",
+ (unsigned) inode, (unsigned) file, file->f_mode));
+ CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, printk(KERN_INFO
+ "cs4297a: status = %08x\n", (int)__raw_readq(SS_CSR(R_SER_STATUS_DEBUG))));
+
+ list_for_each(entry, &cs4297a_devs)
+ {
+ s = list_entry(entry, struct cs4297a_state, list);
+
+ if (!((s->dev_audio ^ minor) & ~0xf))
+ break;
+ }
+ if (entry == &cs4297a_devs)
+ return -ENODEV;
+ if (!s) {
+ CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, printk(KERN_INFO
+ "cs4297a: cs4297a_open(): Error - unable to find audio state struct\n"));
+ return -ENODEV;
+ }
+ VALIDATE_STATE(s);
+ file->private_data = s;
+
+ // wait for device to become free
+ if (!(file->f_mode & (FMODE_WRITE | FMODE_READ))) {
+ CS_DBGOUT(CS_FUNCTION | CS_OPEN | CS_ERROR, 2, printk(KERN_INFO
+ "cs4297a: cs4297a_open(): Error - must open READ and/or WRITE\n"));
+ return -ENODEV;
+ }
+ if (file->f_mode & FMODE_WRITE) {
+ if (__raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_TX)) != 0) {
+ printk(KERN_ERR "cs4297a: TX pipe needs to drain\n");
+ while (__raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_TX)))
+ ;
+ }
+
+ mutex_lock(&s->open_sem_dac);
+ while (s->open_mode & FMODE_WRITE) {
+ if (file->f_flags & O_NONBLOCK) {
+ mutex_unlock(&s->open_sem_dac);
+ return -EBUSY;
+ }
+ mutex_unlock(&s->open_sem_dac);
+ interruptible_sleep_on(&s->open_wait_dac);
+
+ if (signal_pending(current)) {
+ printk("open - sig pending\n");
+ return -ERESTARTSYS;
+ }
+ mutex_lock(&s->open_sem_dac);
+ }
+ }
+ if (file->f_mode & FMODE_READ) {
+ mutex_lock(&s->open_sem_adc);
+ while (s->open_mode & FMODE_READ) {
+ if (file->f_flags & O_NONBLOCK) {
+ mutex_unlock(&s->open_sem_adc);
+ return -EBUSY;
+ }
+ mutex_unlock(&s->open_sem_adc);
+ interruptible_sleep_on(&s->open_wait_adc);
+
+ if (signal_pending(current)) {
+ printk("open - sig pending\n");
+ return -ERESTARTSYS;
+ }
+ mutex_lock(&s->open_sem_adc);
+ }
+ }
+ s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE);
+ if (file->f_mode & FMODE_READ) {
+ s->prop_adc.fmt = AFMT_S16_BE;
+ s->prop_adc.fmt_original = s->prop_adc.fmt;
+ s->prop_adc.channels = 2;
+ s->prop_adc.rate = 48000;
+ s->conversion = 0;
+ s->ena &= ~FMODE_READ;
+ s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags =
+ s->dma_adc.subdivision = 0;
+ mutex_unlock(&s->open_sem_adc);
+
+ if (prog_dmabuf_adc(s)) {
+ CS_DBGOUT(CS_OPEN | CS_ERROR, 2, printk(KERN_ERR
+ "cs4297a: adc Program dmabufs failed.\n"));
+ cs4297a_release(inode, file);
+ return -ENOMEM;
+ }
+ }
+ if (file->f_mode & FMODE_WRITE) {
+ s->prop_dac.fmt = AFMT_S16_BE;
+ s->prop_dac.fmt_original = s->prop_dac.fmt;
+ s->prop_dac.channels = 2;
+ s->prop_dac.rate = 48000;
+ s->conversion = 0;
+ s->ena &= ~FMODE_WRITE;
+ s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags =
+ s->dma_dac.subdivision = 0;
+ mutex_unlock(&s->open_sem_dac);
+
+ if (prog_dmabuf_dac(s)) {
+ CS_DBGOUT(CS_OPEN | CS_ERROR, 2, printk(KERN_ERR
+ "cs4297a: dac Program dmabufs failed.\n"));
+ cs4297a_release(inode, file);
+ return -ENOMEM;
+ }
+ }
+ CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2,
+ printk(KERN_INFO "cs4297a: cs4297a_open()- 0\n"));
+ return nonseekable_open(inode, file);
+}
+
+static int cs4297a_open(struct inode *inode, struct file *file)
+{
+ int ret;
+
+ mutex_lock(&swarm_cs4297a_mutex);
+ ret = cs4297a_open(inode, file);
+ mutex_unlock(&swarm_cs4297a_mutex);
+
+ return ret;
+}
+
+// ******************************************************************************************
+// Wave (audio) file operations struct.
+// ******************************************************************************************
+static const struct file_operations cs4297a_audio_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = cs4297a_read,
+ .write = cs4297a_write,
+ .poll = cs4297a_poll,
+ .unlocked_ioctl = cs4297a_unlocked_ioctl,
+ .mmap = cs4297a_mmap,
+ .open = cs4297a_open,
+ .release = cs4297a_release,
+};
+
+static void cs4297a_interrupt(int irq, void *dev_id)
+{
+ struct cs4297a_state *s = (struct cs4297a_state *) dev_id;
+ u32 status;
+
+ status = __raw_readq(SS_CSR(R_SER_STATUS_DEBUG));
+
+ CS_DBGOUT(CS_INTERRUPT, 6, printk(KERN_INFO
+ "cs4297a: cs4297a_interrupt() HISR=0x%.8x\n", status));
+
+#if 0
+ /* XXXKW what check *should* be done here? */
+ if (!(status & (M_SYNCSER_RX_EOP_COUNT | M_SYNCSER_RX_OVERRUN | M_SYNCSER_RX_SYNC_ERR))) {
+ status = __raw_readq(SS_CSR(R_SER_STATUS));
+ printk(KERN_ERR "cs4297a: unexpected interrupt (status %08x)\n", status);
+ return;
+ }
+#endif
+
+ if (status & M_SYNCSER_RX_SYNC_ERR) {
+ status = __raw_readq(SS_CSR(R_SER_STATUS));
+ printk(KERN_ERR "cs4297a: rx sync error (status %08x)\n", status);
+ return;
+ }
+
+ if (status & M_SYNCSER_RX_OVERRUN) {
+ int newptr, i;
+ s->stats.rx_ovrrn++;
+ printk(KERN_ERR "cs4297a: receive FIFO overrun\n");
+
+ /* Fix things up: get the receive descriptor pool
+ clean and give them back to the hardware */
+ while (__raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_RX)))
+ ;
+ newptr = (unsigned) (((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_RX)) & M_DMA_CURDSCR_ADDR) -
+ s->dma_adc.descrtab_phys) / sizeof(serdma_descr_t));
+ for (i=0; i<DMA_DESCR; i++) {
+ s->dma_adc.descrtab[i].descr_a &= ~M_DMA_SERRX_SOP;
+ }
+ s->dma_adc.swptr = s->dma_adc.hwptr = newptr;
+ s->dma_adc.count = 0;
+ s->dma_adc.sb_swptr = s->dma_adc.sb_hwptr = s->dma_adc.sample_buf;
+ __raw_writeq(DMA_DESCR, SS_CSR(R_SER_DMA_DSCR_COUNT_RX));
+ }
+
+ spin_lock(&s->lock);
+ cs4297a_update_ptr(s,CS_TRUE);
+ spin_unlock(&s->lock);
+
+ CS_DBGOUT(CS_INTERRUPT, 6, printk(KERN_INFO
+ "cs4297a: cs4297a_interrupt()-\n"));
+}
+
+#if 0
+static struct initvol {
+ int mixch;
+ int vol;
+} initvol[] __initdata = {
+
+ {SOUND_MIXER_WRITE_VOLUME, 0x4040},
+ {SOUND_MIXER_WRITE_PCM, 0x4040},
+ {SOUND_MIXER_WRITE_SYNTH, 0x4040},
+ {SOUND_MIXER_WRITE_CD, 0x4040},
+ {SOUND_MIXER_WRITE_LINE, 0x4040},
+ {SOUND_MIXER_WRITE_LINE1, 0x4040},
+ {SOUND_MIXER_WRITE_RECLEV, 0x0000},
+ {SOUND_MIXER_WRITE_SPEAKER, 0x4040},
+ {SOUND_MIXER_WRITE_MIC, 0x0000}
+};
+#endif
+
+static int __init cs4297a_init(void)
+{
+ struct cs4297a_state *s;
+ u32 pwr, id;
+ mm_segment_t fs;
+ int rval;
+#ifndef CONFIG_BCM_CS4297A_CSWARM
+ u64 cfg;
+ int mdio_val;
+#endif
+
+ CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, printk(KERN_INFO
+ "cs4297a: cs4297a_init_module()+ \n"));
+
+#ifndef CONFIG_BCM_CS4297A_CSWARM
+ mdio_val = __raw_readq(KSEG1 + A_MAC_REGISTER(2, R_MAC_MDIO)) &
+ (M_MAC_MDIO_DIR|M_MAC_MDIO_OUT);
+
+ /* Check syscfg for synchronous serial on port 1 */
+ cfg = __raw_readq(KSEG1 + A_SCD_SYSTEM_CFG);
+ if (!(cfg & M_SYS_SER1_ENABLE)) {
+ __raw_writeq(cfg | M_SYS_SER1_ENABLE, KSEG1+A_SCD_SYSTEM_CFG);
+ cfg = __raw_readq(KSEG1 + A_SCD_SYSTEM_CFG);
+ if (!(cfg & M_SYS_SER1_ENABLE)) {
+ printk(KERN_INFO "cs4297a: serial port 1 not configured for synchronous operation\n");
+ return -1;
+ }
+
+ printk(KERN_INFO "cs4297a: serial port 1 switching to synchronous operation\n");
+
+ /* Force the codec (on SWARM) to reset by clearing
+ GENO, preserving MDIO (no effect on CSWARM) */
+ __raw_writeq(mdio_val, KSEG1+A_MAC_REGISTER(2, R_MAC_MDIO));
+ udelay(10);
+ }
+
+ /* Now set GENO */
+ __raw_writeq(mdio_val | M_MAC_GENC, KSEG1+A_MAC_REGISTER(2, R_MAC_MDIO));
+ /* Give the codec some time to finish resetting (start the bit clock) */
+ udelay(100);
+#endif
+
+ if (!(s = kzalloc(sizeof(struct cs4297a_state), GFP_KERNEL))) {
+ CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR
+ "cs4297a: probe() no memory for state struct.\n"));
+ return -1;
+ }
+ s->magic = CS4297a_MAGIC;
+ init_waitqueue_head(&s->dma_adc.wait);
+ init_waitqueue_head(&s->dma_dac.wait);
+ init_waitqueue_head(&s->dma_adc.reg_wait);
+ init_waitqueue_head(&s->dma_dac.reg_wait);
+ init_waitqueue_head(&s->open_wait);
+ init_waitqueue_head(&s->open_wait_adc);
+ init_waitqueue_head(&s->open_wait_dac);
+ mutex_init(&s->open_sem_adc);
+ mutex_init(&s->open_sem_dac);
+ spin_lock_init(&s->lock);
+
+ s->irq = K_INT_SER_1;
+
+ if (request_irq
+ (s->irq, cs4297a_interrupt, 0, "Crystal CS4297a", s)) {
+ CS_DBGOUT(CS_INIT | CS_ERROR, 1,
+ printk(KERN_ERR "cs4297a: irq %u in use\n", s->irq));
+ goto err_irq;
+ }
+ if ((s->dev_audio = register_sound_dsp(&cs4297a_audio_fops, -1)) <
+ 0) {
+ CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_ERR
+ "cs4297a: probe() register_sound_dsp() failed.\n"));
+ goto err_dev1;
+ }
+ if ((s->dev_mixer = register_sound_mixer(&cs4297a_mixer_fops, -1)) <
+ 0) {
+ CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_ERR
+ "cs4297a: probe() register_sound_mixer() failed.\n"));
+ goto err_dev2;
+ }
+
+ if (ser_init(s) || dma_init(s)) {
+ CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_ERR
+ "cs4297a: ser_init failed.\n"));
+ goto err_dev3;
+ }
+
+ do {
+ udelay(4000);
+ rval = cs4297a_read_ac97(s, AC97_POWER_CONTROL, &pwr);
+ } while (!rval && (pwr != 0xf));
+
+ if (!rval) {
+ char *sb1250_duart_present;
+
+ fs = get_fs();
+ set_fs(KERNEL_DS);
+#if 0
+ val = SOUND_MASK_LINE;
+ mixer_ioctl(s, SOUND_MIXER_WRITE_RECSRC, (unsigned long) &val);
+ for (i = 0; i < ARRAY_SIZE(initvol); i++) {
+ val = initvol[i].vol;
+ mixer_ioctl(s, initvol[i].mixch, (unsigned long) &val);
+ }
+// cs4297a_write_ac97(s, 0x18, 0x0808);
+#else
+ // cs4297a_write_ac97(s, 0x5e, 0x180);
+ cs4297a_write_ac97(s, 0x02, 0x0808);
+ cs4297a_write_ac97(s, 0x18, 0x0808);
+#endif
+ set_fs(fs);
+
+ list_add(&s->list, &cs4297a_devs);
+
+ cs4297a_read_ac97(s, AC97_VENDOR_ID1, &id);
+
+ sb1250_duart_present = symbol_get(sb1250_duart_present);
+ if (sb1250_duart_present)
+ sb1250_duart_present[1] = 0;
+
+ printk(KERN_INFO "cs4297a: initialized (vendor id = %x)\n", id);
+
+ CS_DBGOUT(CS_INIT | CS_FUNCTION, 2,
+ printk(KERN_INFO "cs4297a: cs4297a_init_module()-\n"));
+
+ return 0;
+ }
+
+ err_dev3:
+ unregister_sound_mixer(s->dev_mixer);
+ err_dev2:
+ unregister_sound_dsp(s->dev_audio);
+ err_dev1:
+ free_irq(s->irq, s);
+ err_irq:
+ kfree(s);
+
+ printk(KERN_INFO "cs4297a: initialization failed\n");
+
+ return -1;
+}
+
+static void __exit cs4297a_cleanup(void)
+{
+ /*
+ XXXKW
+ disable_irq, free_irq
+ drain DMA queue
+ disable DMA
+ disable TX/RX
+ free memory
+ */
+ CS_DBGOUT(CS_INIT | CS_FUNCTION, 2,
+ printk(KERN_INFO "cs4297a: cleanup_cs4297a() finished\n"));
+}
+
+// ---------------------------------------------------------------------
+
+MODULE_AUTHOR("Kip Walker, Broadcom Corp.");
+MODULE_DESCRIPTION("Cirrus Logic CS4297a Driver for Broadcom SWARM board");
+
+// ---------------------------------------------------------------------
+
+module_init(cs4297a_init);
+module_exit(cs4297a_cleanup);
diff --git a/sound/oss/sys_timer.c b/sound/oss/sys_timer.c
new file mode 100644
index 00000000..8db6aefe
--- /dev/null
+++ b/sound/oss/sys_timer.c
@@ -0,0 +1,285 @@
+/*
+ * sound/oss/sys_timer.c
+ *
+ * The default timer for the Level 2 sequencer interface
+ * Uses the (1/HZ sec) timer of kernel.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ * Andrew Veliath : adapted tmr2ticks from level 1 sequencer (avoid overflow)
+ */
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+static volatile int opened, tmr_running;
+static volatile time_t tmr_offs, tmr_ctr;
+static volatile unsigned long ticks_offs;
+static volatile int curr_tempo, curr_timebase;
+static volatile unsigned long curr_ticks;
+static volatile unsigned long next_event_time;
+static unsigned long prev_event_time;
+
+static void poll_def_tmr(unsigned long dummy);
+static DEFINE_SPINLOCK(lock);
+static DEFINE_TIMER(def_tmr, poll_def_tmr, 0, 0);
+
+static unsigned long
+tmr2ticks(int tmr_value)
+{
+ /*
+ * Convert timer ticks to MIDI ticks
+ */
+
+ unsigned long tmp;
+ unsigned long scale;
+
+ /* tmr_value (ticks per sec) *
+ 1000000 (usecs per sec) / HZ (ticks per sec) -=> usecs */
+ tmp = tmr_value * (1000000 / HZ);
+ scale = (60 * 1000000) / (curr_tempo * curr_timebase); /* usecs per MIDI tick */
+ return (tmp + scale / 2) / scale;
+}
+
+static void
+poll_def_tmr(unsigned long dummy)
+{
+
+ if (opened)
+ {
+
+ {
+ def_tmr.expires = (1) + jiffies;
+ add_timer(&def_tmr);
+ };
+
+ if (tmr_running)
+ {
+ spin_lock(&lock);
+ tmr_ctr++;
+ curr_ticks = ticks_offs + tmr2ticks(tmr_ctr);
+
+ if (curr_ticks >= next_event_time)
+ {
+ next_event_time = (unsigned long) -1;
+ sequencer_timer(0);
+ }
+ spin_unlock(&lock);
+ }
+ }
+}
+
+static void
+tmr_reset(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&lock,flags);
+ tmr_offs = 0;
+ ticks_offs = 0;
+ tmr_ctr = 0;
+ next_event_time = (unsigned long) -1;
+ prev_event_time = 0;
+ curr_ticks = 0;
+ spin_unlock_irqrestore(&lock,flags);
+}
+
+static int
+def_tmr_open(int dev, int mode)
+{
+ if (opened)
+ return -EBUSY;
+
+ tmr_reset();
+ curr_tempo = 60;
+ curr_timebase = 100;
+ opened = 1;
+ {
+ def_tmr.expires = (1) + jiffies;
+ add_timer(&def_tmr);
+ };
+
+ return 0;
+}
+
+static void
+def_tmr_close(int dev)
+{
+ opened = tmr_running = 0;
+ del_timer(&def_tmr);
+}
+
+static int
+def_tmr_event(int dev, unsigned char *event)
+{
+ unsigned char cmd = event[1];
+ unsigned long parm = *(int *) &event[4];
+
+ switch (cmd)
+ {
+ case TMR_WAIT_REL:
+ parm += prev_event_time;
+ case TMR_WAIT_ABS:
+ if (parm > 0)
+ {
+ long time;
+
+ if (parm <= curr_ticks) /* It's the time */
+ return TIMER_NOT_ARMED;
+
+ time = parm;
+ next_event_time = prev_event_time = time;
+
+ return TIMER_ARMED;
+ }
+ break;
+
+ case TMR_START:
+ tmr_reset();
+ tmr_running = 1;
+ break;
+
+ case TMR_STOP:
+ tmr_running = 0;
+ break;
+
+ case TMR_CONTINUE:
+ tmr_running = 1;
+ break;
+
+ case TMR_TEMPO:
+ if (parm)
+ {
+ if (parm < 8)
+ parm = 8;
+ if (parm > 360)
+ parm = 360;
+ tmr_offs = tmr_ctr;
+ ticks_offs += tmr2ticks(tmr_ctr);
+ tmr_ctr = 0;
+ curr_tempo = parm;
+ }
+ break;
+
+ case TMR_ECHO:
+ seq_copy_to_input(event, 8);
+ break;
+
+ default:;
+ }
+
+ return TIMER_NOT_ARMED;
+}
+
+static unsigned long
+def_tmr_get_time(int dev)
+{
+ if (!opened)
+ return 0;
+
+ return curr_ticks;
+}
+
+/* same as sound_timer.c:timer_ioctl!? */
+static int def_tmr_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+ int __user *p = arg;
+ int val;
+
+ switch (cmd) {
+ case SNDCTL_TMR_SOURCE:
+ return __put_user(TMR_INTERNAL, p);
+
+ case SNDCTL_TMR_START:
+ tmr_reset();
+ tmr_running = 1;
+ return 0;
+
+ case SNDCTL_TMR_STOP:
+ tmr_running = 0;
+ return 0;
+
+ case SNDCTL_TMR_CONTINUE:
+ tmr_running = 1;
+ return 0;
+
+ case SNDCTL_TMR_TIMEBASE:
+ if (__get_user(val, p))
+ return -EFAULT;
+ if (val) {
+ if (val < 1)
+ val = 1;
+ if (val > 1000)
+ val = 1000;
+ curr_timebase = val;
+ }
+ return __put_user(curr_timebase, p);
+
+ case SNDCTL_TMR_TEMPO:
+ if (__get_user(val, p))
+ return -EFAULT;
+ if (val) {
+ if (val < 8)
+ val = 8;
+ if (val > 250)
+ val = 250;
+ tmr_offs = tmr_ctr;
+ ticks_offs += tmr2ticks(tmr_ctr);
+ tmr_ctr = 0;
+ curr_tempo = val;
+ reprogram_timer();
+ }
+ return __put_user(curr_tempo, p);
+
+ case SNDCTL_SEQ_CTRLRATE:
+ if (__get_user(val, p))
+ return -EFAULT;
+ if (val != 0) /* Can't change */
+ return -EINVAL;
+ val = ((curr_tempo * curr_timebase) + 30) / 60;
+ return __put_user(val, p);
+
+ case SNDCTL_SEQ_GETTIME:
+ return __put_user(curr_ticks, p);
+
+ case SNDCTL_TMR_METRONOME:
+ /* NOP */
+ break;
+
+ default:;
+ }
+ return -EINVAL;
+}
+
+static void
+def_tmr_arm(int dev, long time)
+{
+ if (time < 0)
+ time = curr_ticks + 1;
+ else if (time <= curr_ticks) /* It's the time */
+ return;
+
+ next_event_time = prev_event_time = time;
+
+ return;
+}
+
+struct sound_timer_operations default_sound_timer =
+{
+ .owner = THIS_MODULE,
+ .info = {"System clock", 0},
+ .priority = 0, /* Priority */
+ .devlink = 0, /* Local device link */
+ .open = def_tmr_open,
+ .close = def_tmr_close,
+ .event = def_tmr_event,
+ .get_time = def_tmr_get_time,
+ .ioctl = def_tmr_ioctl,
+ .arm_timer = def_tmr_arm
+};
diff --git a/sound/oss/trix.c b/sound/oss/trix.c
new file mode 100644
index 00000000..e04169e8
--- /dev/null
+++ b/sound/oss/trix.c
@@ -0,0 +1,525 @@
+/*
+ * sound/oss/trix.c
+ *
+ * Low level driver for the MediaTrix AudioTrix Pro
+ * (MT-0002-PC Control Chip)
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Changes
+ * Alan Cox Modularisation, cleanup.
+ * Christoph Hellwig Adapted to module_init/module_exit
+ * Arnaldo C. de Melo Got rid of attach_uart401
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include "sound_config.h"
+#include "sb.h"
+#include "sound_firmware.h"
+
+#include "ad1848.h"
+#include "mpu401.h"
+
+#include "trix_boot.h"
+
+static int mpu;
+
+static int joystick;
+
+static unsigned char trix_read(int addr)
+{
+ outb(((unsigned char) addr), 0x390); /* MT-0002-PC ASIC address */
+ return inb(0x391); /* MT-0002-PC ASIC data */
+}
+
+static void trix_write(int addr, int data)
+{
+ outb(((unsigned char) addr), 0x390); /* MT-0002-PC ASIC address */
+ outb(((unsigned char) data), 0x391); /* MT-0002-PC ASIC data */
+}
+
+static void download_boot(int base)
+{
+ int i = 0, n = trix_boot_len;
+
+ if (trix_boot_len == 0)
+ return;
+
+ trix_write(0xf8, 0x00); /* ??????? */
+ outb((0x01), base + 6); /* Clear the internal data pointer */
+ outb((0x00), base + 6); /* Restart */
+
+ /*
+ * Write the boot code to the RAM upload/download register.
+ * Each write increments the internal data pointer.
+ */
+ outb((0x01), base + 6); /* Clear the internal data pointer */
+ outb((0x1A), 0x390); /* Select RAM download/upload port */
+
+ for (i = 0; i < n; i++)
+ outb((trix_boot[i]), 0x391);
+ for (i = n; i < 10016; i++) /* Clear up to first 16 bytes of data RAM */
+ outb((0x00), 0x391);
+ outb((0x00), base + 6); /* Reset */
+ outb((0x50), 0x390); /* ?????? */
+
+}
+
+static int trix_set_wss_port(struct address_info *hw_config)
+{
+ unsigned char addr_bits;
+
+ if (trix_read(0x15) != 0x71) /* No ASIC signature */
+ {
+ MDB(printk(KERN_ERR "No AudioTrix ASIC signature found\n"));
+ return 0;
+ }
+
+ /*
+ * Reset some registers.
+ */
+
+ trix_write(0x13, 0);
+ trix_write(0x14, 0);
+
+ /*
+ * Configure the ASIC to place the codec to the proper I/O location
+ */
+
+ switch (hw_config->io_base)
+ {
+ case 0x530:
+ addr_bits = 0;
+ break;
+ case 0x604:
+ addr_bits = 1;
+ break;
+ case 0xE80:
+ addr_bits = 2;
+ break;
+ case 0xF40:
+ addr_bits = 3;
+ break;
+ default:
+ return 0;
+ }
+
+ trix_write(0x19, (trix_read(0x19) & 0x03) | addr_bits);
+ return 1;
+}
+
+/*
+ * Probe and attach routines for the Windows Sound System mode of
+ * AudioTrix Pro
+ */
+
+static int __init init_trix_wss(struct address_info *hw_config)
+{
+ static unsigned char dma_bits[4] = {
+ 1, 2, 0, 3
+ };
+ struct resource *ports;
+ int config_port = hw_config->io_base + 0;
+ int dma1 = hw_config->dma, dma2 = hw_config->dma2;
+ int old_num_mixers = num_mixers;
+ u8 config, bits;
+ int ret;
+
+ switch(hw_config->irq) {
+ case 7:
+ bits = 8;
+ break;
+ case 9:
+ bits = 0x10;
+ break;
+ case 10:
+ bits = 0x18;
+ break;
+ case 11:
+ bits = 0x20;
+ break;
+ default:
+ printk(KERN_ERR "AudioTrix: Bad WSS IRQ %d\n", hw_config->irq);
+ return 0;
+ }
+
+ switch (dma1) {
+ case 0:
+ case 1:
+ case 3:
+ break;
+ default:
+ printk(KERN_ERR "AudioTrix: Bad WSS DMA %d\n", dma1);
+ return 0;
+ }
+
+ switch (dma2) {
+ case -1:
+ case 0:
+ case 1:
+ case 3:
+ break;
+ default:
+ printk(KERN_ERR "AudioTrix: Bad capture DMA %d\n", dma2);
+ return 0;
+ }
+
+ /*
+ * Check if the IO port returns valid signature. The original MS Sound
+ * system returns 0x04 while some cards (AudioTrix Pro for example)
+ * return 0x00.
+ */
+ ports = request_region(hw_config->io_base + 4, 4, "ad1848");
+ if (!ports) {
+ printk(KERN_ERR "AudioTrix: MSS I/O port conflict (%x)\n", hw_config->io_base);
+ return 0;
+ }
+
+ if (!request_region(hw_config->io_base, 4, "MSS config")) {
+ printk(KERN_ERR "AudioTrix: MSS I/O port conflict (%x)\n", hw_config->io_base);
+ release_region(hw_config->io_base + 4, 4);
+ return 0;
+ }
+
+ if (!trix_set_wss_port(hw_config))
+ goto fail;
+
+ config = inb(hw_config->io_base + 3);
+
+ if ((config & 0x3f) != 0x00)
+ {
+ MDB(printk(KERN_ERR "No MSS signature detected on port 0x%x\n", hw_config->io_base));
+ goto fail;
+ }
+
+ /*
+ * Check that DMA0 is not in use with a 8 bit board.
+ */
+
+ if (dma1 == 0 && config & 0x80)
+ {
+ printk(KERN_ERR "AudioTrix: Can't use DMA0 with a 8 bit card slot\n");
+ goto fail;
+ }
+ if (hw_config->irq > 9 && config & 0x80)
+ {
+ printk(KERN_ERR "AudioTrix: Can't use IRQ%d with a 8 bit card slot\n", hw_config->irq);
+ goto fail;
+ }
+
+ ret = ad1848_detect(ports, NULL, hw_config->osp);
+ if (!ret)
+ goto fail;
+
+ if (joystick==1)
+ trix_write(0x15, 0x80);
+
+ /*
+ * Set the IRQ and DMA addresses.
+ */
+
+ outb((bits | 0x40), config_port);
+
+ if (dma2 == -1 || dma2 == dma1)
+ {
+ bits |= dma_bits[dma1];
+ dma2 = dma1;
+ }
+ else
+ {
+ unsigned char tmp;
+
+ tmp = trix_read(0x13) & ~30;
+ trix_write(0x13, tmp | 0x80 | (dma1 << 4));
+
+ tmp = trix_read(0x14) & ~30;
+ trix_write(0x14, tmp | 0x80 | (dma2 << 4));
+ }
+
+ outb((bits), config_port); /* Write IRQ+DMA setup */
+
+ hw_config->slots[0] = ad1848_init("AudioTrix Pro", ports,
+ hw_config->irq,
+ dma1,
+ dma2,
+ 0,
+ hw_config->osp,
+ THIS_MODULE);
+
+ if (num_mixers > old_num_mixers) /* Mixer got installed */
+ {
+ AD1848_REROUTE(SOUND_MIXER_LINE1, SOUND_MIXER_LINE); /* Line in */
+ AD1848_REROUTE(SOUND_MIXER_LINE2, SOUND_MIXER_CD);
+ AD1848_REROUTE(SOUND_MIXER_LINE3, SOUND_MIXER_SYNTH); /* OPL4 */
+ AD1848_REROUTE(SOUND_MIXER_SPEAKER, SOUND_MIXER_ALTPCM); /* SB */
+ }
+ return 1;
+
+fail:
+ release_region(hw_config->io_base, 4);
+ release_region(hw_config->io_base + 4, 4);
+ return 0;
+}
+
+static int __init probe_trix_sb(struct address_info *hw_config)
+{
+
+ int tmp;
+ unsigned char conf;
+ extern int sb_be_quiet;
+ int old_quiet;
+ static signed char irq_translate[] = {
+ -1, -1, -1, 0, 1, 2, -1, 3
+ };
+
+ if (trix_boot_len == 0)
+ return 0; /* No boot code -> no fun */
+
+ if ((hw_config->io_base & 0xffffff8f) != 0x200)
+ return 0;
+
+ tmp = hw_config->irq;
+ if (tmp > 7)
+ return 0;
+ if (irq_translate[tmp] == -1)
+ return 0;
+
+ tmp = hw_config->dma;
+ if (tmp != 1 && tmp != 3)
+ return 0;
+
+ if (!request_region(hw_config->io_base, 16, "soundblaster")) {
+ printk(KERN_ERR "AudioTrix: SB I/O port conflict (%x)\n", hw_config->io_base);
+ return 0;
+ }
+
+ conf = 0x84; /* DMA and IRQ enable */
+ conf |= hw_config->io_base & 0x70; /* I/O address bits */
+ conf |= irq_translate[hw_config->irq];
+ if (hw_config->dma == 3)
+ conf |= 0x08;
+ trix_write(0x1b, conf);
+
+ download_boot(hw_config->io_base);
+
+ hw_config->name = "AudioTrix SB";
+ if (!sb_dsp_detect(hw_config, 0, 0, NULL)) {
+ release_region(hw_config->io_base, 16);
+ return 0;
+ }
+
+ hw_config->driver_use_1 = SB_NO_MIDI | SB_NO_MIXER | SB_NO_RECORDING;
+
+ /* Prevent false alarms */
+ old_quiet = sb_be_quiet;
+ sb_be_quiet = 1;
+
+ sb_dsp_init(hw_config, THIS_MODULE);
+
+ sb_be_quiet = old_quiet;
+ return 1;
+}
+
+static int __init probe_trix_mpu(struct address_info *hw_config)
+{
+ unsigned char conf;
+ static int irq_bits[] = {
+ -1, -1, -1, 1, 2, 3, -1, 4, -1, 5
+ };
+
+ if (hw_config->irq > 9)
+ {
+ printk(KERN_ERR "AudioTrix: Bad MPU IRQ %d\n", hw_config->irq);
+ return 0;
+ }
+ if (irq_bits[hw_config->irq] == -1)
+ {
+ printk(KERN_ERR "AudioTrix: Bad MPU IRQ %d\n", hw_config->irq);
+ return 0;
+ }
+ switch (hw_config->io_base)
+ {
+ case 0x330:
+ conf = 0x00;
+ break;
+ case 0x370:
+ conf = 0x04;
+ break;
+ case 0x3b0:
+ conf = 0x08;
+ break;
+ case 0x3f0:
+ conf = 0x0c;
+ break;
+ default:
+ return 0; /* Invalid port */
+ }
+
+ conf |= irq_bits[hw_config->irq] << 4;
+ trix_write(0x19, (trix_read(0x19) & 0x83) | conf);
+ hw_config->name = "AudioTrix Pro";
+ return probe_uart401(hw_config, THIS_MODULE);
+}
+
+static void __exit unload_trix_wss(struct address_info *hw_config)
+{
+ int dma2 = hw_config->dma2;
+
+ if (dma2 == -1)
+ dma2 = hw_config->dma;
+
+ release_region(0x390, 2);
+ release_region(hw_config->io_base, 4);
+
+ ad1848_unload(hw_config->io_base + 4,
+ hw_config->irq,
+ hw_config->dma,
+ dma2,
+ 0);
+ sound_unload_audiodev(hw_config->slots[0]);
+}
+
+static inline void __exit unload_trix_mpu(struct address_info *hw_config)
+{
+ unload_uart401(hw_config);
+}
+
+static inline void __exit unload_trix_sb(struct address_info *hw_config)
+{
+ sb_dsp_unload(hw_config, mpu);
+}
+
+static struct address_info cfg;
+static struct address_info cfg2;
+static struct address_info cfg_mpu;
+
+static int sb;
+static int fw_load;
+
+static int __initdata io = -1;
+static int __initdata irq = -1;
+static int __initdata dma = -1;
+static int __initdata dma2 = -1; /* Set this for modules that need it */
+static int __initdata sb_io = -1;
+static int __initdata sb_dma = -1;
+static int __initdata sb_irq = -1;
+static int __initdata mpu_io = -1;
+static int __initdata mpu_irq = -1;
+
+module_param(io, int, 0);
+module_param(irq, int, 0);
+module_param(dma, int, 0);
+module_param(dma2, int, 0);
+module_param(sb_io, int, 0);
+module_param(sb_dma, int, 0);
+module_param(sb_irq, int, 0);
+module_param(mpu_io, int, 0);
+module_param(mpu_irq, int, 0);
+module_param(joystick, bool, 0);
+
+static int __init init_trix(void)
+{
+ printk(KERN_INFO "MediaTrix audio driver Copyright (C) by Hannu Savolainen 1993-1996\n");
+
+ cfg.io_base = io;
+ cfg.irq = irq;
+ cfg.dma = dma;
+ cfg.dma2 = dma2;
+
+ cfg2.io_base = sb_io;
+ cfg2.irq = sb_irq;
+ cfg2.dma = sb_dma;
+
+ cfg_mpu.io_base = mpu_io;
+ cfg_mpu.irq = mpu_irq;
+
+ if (cfg.io_base == -1 || cfg.dma == -1 || cfg.irq == -1) {
+ printk(KERN_INFO "I/O, IRQ, DMA and type are mandatory\n");
+ return -EINVAL;
+ }
+
+ if (cfg2.io_base != -1 && (cfg2.irq == -1 || cfg2.dma == -1)) {
+ printk(KERN_INFO "CONFIG_SB_IRQ and CONFIG_SB_DMA must be specified if SB_IO is set.\n");
+ return -EINVAL;
+ }
+ if (cfg_mpu.io_base != -1 && cfg_mpu.irq == -1) {
+ printk(KERN_INFO "CONFIG_MPU_IRQ must be specified if MPU_IO is set.\n");
+ return -EINVAL;
+ }
+ if (!trix_boot)
+ {
+ fw_load = 1;
+ trix_boot_len = mod_firmware_load("/etc/sound/trxpro.bin",
+ (char **) &trix_boot);
+ }
+
+ if (!request_region(0x390, 2, "AudioTrix")) {
+ printk(KERN_ERR "AudioTrix: Config port I/O conflict\n");
+ return -ENODEV;
+ }
+
+ if (!init_trix_wss(&cfg)) {
+ release_region(0x390, 2);
+ return -ENODEV;
+ }
+
+ /*
+ * We must attach in the right order to get the firmware
+ * loaded up in time.
+ */
+
+ if (cfg2.io_base != -1) {
+ sb = probe_trix_sb(&cfg2);
+ }
+
+ if (cfg_mpu.io_base != -1)
+ mpu = probe_trix_mpu(&cfg_mpu);
+
+ return 0;
+}
+
+static void __exit cleanup_trix(void)
+{
+ if (fw_load && trix_boot)
+ vfree(trix_boot);
+ if (sb)
+ unload_trix_sb(&cfg2);
+ if (mpu)
+ unload_trix_mpu(&cfg_mpu);
+ unload_trix_wss(&cfg);
+}
+
+module_init(init_trix);
+module_exit(cleanup_trix);
+
+#ifndef MODULE
+static int __init setup_trix (char *str)
+{
+ /* io, irq, dma, dma2, sb_io, sb_irq, sb_dma, mpu_io, mpu_irq */
+ int ints[9];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ io = ints[1];
+ irq = ints[2];
+ dma = ints[3];
+ dma2 = ints[4];
+ sb_io = ints[5];
+ sb_irq = ints[6];
+ sb_dma = ints[6];
+ mpu_io = ints[7];
+ mpu_irq = ints[8];
+
+ return 1;
+}
+
+__setup("trix=", setup_trix);
+#endif
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/tuning.h b/sound/oss/tuning.h
new file mode 100644
index 00000000..a73e3dd3
--- /dev/null
+++ b/sound/oss/tuning.h
@@ -0,0 +1,23 @@
+static unsigned short semitone_tuning[24] =
+{
+/* 0 */ 10000, 10595, 11225, 11892, 12599, 13348, 14142, 14983,
+/* 8 */ 15874, 16818, 17818, 18877, 20000, 21189, 22449, 23784,
+/* 16 */ 25198, 26697, 28284, 29966, 31748, 33636, 35636, 37755
+};
+
+static unsigned short cent_tuning[100] =
+{
+/* 0 */ 10000, 10006, 10012, 10017, 10023, 10029, 10035, 10041,
+/* 8 */ 10046, 10052, 10058, 10064, 10070, 10075, 10081, 10087,
+/* 16 */ 10093, 10099, 10105, 10110, 10116, 10122, 10128, 10134,
+/* 24 */ 10140, 10145, 10151, 10157, 10163, 10169, 10175, 10181,
+/* 32 */ 10187, 10192, 10198, 10204, 10210, 10216, 10222, 10228,
+/* 40 */ 10234, 10240, 10246, 10251, 10257, 10263, 10269, 10275,
+/* 48 */ 10281, 10287, 10293, 10299, 10305, 10311, 10317, 10323,
+/* 56 */ 10329, 10335, 10341, 10347, 10353, 10359, 10365, 10371,
+/* 64 */ 10377, 10383, 10389, 10395, 10401, 10407, 10413, 10419,
+/* 72 */ 10425, 10431, 10437, 10443, 10449, 10455, 10461, 10467,
+/* 80 */ 10473, 10479, 10485, 10491, 10497, 10503, 10509, 10515,
+/* 88 */ 10521, 10528, 10534, 10540, 10546, 10552, 10558, 10564,
+/* 96 */ 10570, 10576, 10582, 10589
+};
diff --git a/sound/oss/uart401.c b/sound/oss/uart401.c
new file mode 100644
index 00000000..8e514a67
--- /dev/null
+++ b/sound/oss/uart401.c
@@ -0,0 +1,482 @@
+/*
+ * sound/oss/uart401.c
+ *
+ * MPU-401 UART driver (formerly uart401_midi.c)
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Changes:
+ * Alan Cox Reformatted, removed sound_mem usage, use normal Linux
+ * interrupt allocation. Protect against bogus unload
+ * Fixed to allow IRQ > 15
+ * Christoph Hellwig Adapted to module_init/module_exit
+ * Arnaldo C. de Melo got rid of check_region
+ *
+ * Status:
+ * Untested
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+#include "mpu401.h"
+
+typedef struct uart401_devc
+{
+ int base;
+ int irq;
+ int *osp;
+ void (*midi_input_intr) (int dev, unsigned char data);
+ int opened, disabled;
+ volatile unsigned char input_byte;
+ int my_dev;
+ int share_irq;
+ spinlock_t lock;
+}
+uart401_devc;
+
+#define DATAPORT (devc->base)
+#define COMDPORT (devc->base+1)
+#define STATPORT (devc->base+1)
+
+static int uart401_status(uart401_devc * devc)
+{
+ return inb(STATPORT);
+}
+
+#define input_avail(devc) (!(uart401_status(devc)&INPUT_AVAIL))
+#define output_ready(devc) (!(uart401_status(devc)&OUTPUT_READY))
+
+static void uart401_cmd(uart401_devc * devc, unsigned char cmd)
+{
+ outb((cmd), COMDPORT);
+}
+
+static int uart401_read(uart401_devc * devc)
+{
+ return inb(DATAPORT);
+}
+
+static void uart401_write(uart401_devc * devc, unsigned char byte)
+{
+ outb((byte), DATAPORT);
+}
+
+#define OUTPUT_READY 0x40
+#define INPUT_AVAIL 0x80
+#define MPU_ACK 0xFE
+#define MPU_RESET 0xFF
+#define UART_MODE_ON 0x3F
+
+static int reset_uart401(uart401_devc * devc);
+static void enter_uart_mode(uart401_devc * devc);
+
+static void uart401_input_loop(uart401_devc * devc)
+{
+ int work_limit=30000;
+
+ while (input_avail(devc) && --work_limit)
+ {
+ unsigned char c = uart401_read(devc);
+
+ if (c == MPU_ACK)
+ devc->input_byte = c;
+ else if (devc->opened & OPEN_READ && devc->midi_input_intr)
+ devc->midi_input_intr(devc->my_dev, c);
+ }
+ if(work_limit==0)
+ printk(KERN_WARNING "Too much work in interrupt on uart401 (0x%X). UART jabbering ??\n", devc->base);
+}
+
+irqreturn_t uart401intr(int irq, void *dev_id)
+{
+ uart401_devc *devc = dev_id;
+
+ if (devc == NULL)
+ {
+ printk(KERN_ERR "uart401: bad devc\n");
+ return IRQ_NONE;
+ }
+
+ if (input_avail(devc))
+ uart401_input_loop(devc);
+ return IRQ_HANDLED;
+}
+
+static int
+uart401_open(int dev, int mode,
+ void (*input) (int dev, unsigned char data),
+ void (*output) (int dev)
+)
+{
+ uart401_devc *devc = (uart401_devc *) midi_devs[dev]->devc;
+
+ if (devc->opened)
+ return -EBUSY;
+
+ /* Flush the UART */
+
+ while (input_avail(devc))
+ uart401_read(devc);
+
+ devc->midi_input_intr = input;
+ devc->opened = mode;
+ enter_uart_mode(devc);
+ devc->disabled = 0;
+
+ return 0;
+}
+
+static void uart401_close(int dev)
+{
+ uart401_devc *devc = (uart401_devc *) midi_devs[dev]->devc;
+
+ reset_uart401(devc);
+ devc->opened = 0;
+}
+
+static int uart401_out(int dev, unsigned char midi_byte)
+{
+ int timeout;
+ unsigned long flags;
+ uart401_devc *devc = (uart401_devc *) midi_devs[dev]->devc;
+
+ if (devc->disabled)
+ return 1;
+ /*
+ * Test for input since pending input seems to block the output.
+ */
+
+ spin_lock_irqsave(&devc->lock,flags);
+ if (input_avail(devc))
+ uart401_input_loop(devc);
+
+ spin_unlock_irqrestore(&devc->lock,flags);
+
+ /*
+ * Sometimes it takes about 13000 loops before the output becomes ready
+ * (After reset). Normally it takes just about 10 loops.
+ */
+
+ for (timeout = 30000; timeout > 0 && !output_ready(devc); timeout--);
+
+ if (!output_ready(devc))
+ {
+ printk(KERN_WARNING "uart401: Timeout - Device not responding\n");
+ devc->disabled = 1;
+ reset_uart401(devc);
+ enter_uart_mode(devc);
+ return 1;
+ }
+ uart401_write(devc, midi_byte);
+ return 1;
+}
+
+static inline int uart401_start_read(int dev)
+{
+ return 0;
+}
+
+static inline int uart401_end_read(int dev)
+{
+ return 0;
+}
+
+static inline void uart401_kick(int dev)
+{
+}
+
+static inline int uart401_buffer_status(int dev)
+{
+ return 0;
+}
+
+#define MIDI_SYNTH_NAME "MPU-401 UART"
+#define MIDI_SYNTH_CAPS SYNTH_CAP_INPUT
+#include "midi_synth.h"
+
+static const struct midi_operations uart401_operations =
+{
+ .owner = THIS_MODULE,
+ .info = {"MPU-401 (UART) MIDI", 0, 0, SNDCARD_MPU401},
+ .converter = &std_midi_synth,
+ .in_info = {0},
+ .open = uart401_open,
+ .close = uart401_close,
+ .outputc = uart401_out,
+ .start_read = uart401_start_read,
+ .end_read = uart401_end_read,
+ .kick = uart401_kick,
+ .buffer_status = uart401_buffer_status,
+};
+
+static void enter_uart_mode(uart401_devc * devc)
+{
+ int ok, timeout;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devc->lock,flags);
+ for (timeout = 30000; timeout > 0 && !output_ready(devc); timeout--);
+
+ devc->input_byte = 0;
+ uart401_cmd(devc, UART_MODE_ON);
+
+ ok = 0;
+ for (timeout = 50000; timeout > 0 && !ok; timeout--)
+ if (devc->input_byte == MPU_ACK)
+ ok = 1;
+ else if (input_avail(devc))
+ if (uart401_read(devc) == MPU_ACK)
+ ok = 1;
+
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static int reset_uart401(uart401_devc * devc)
+{
+ int ok, timeout, n;
+
+ /*
+ * Send the RESET command. Try again if no success at the first time.
+ */
+
+ ok = 0;
+
+ for (n = 0; n < 2 && !ok; n++)
+ {
+ for (timeout = 30000; timeout > 0 && !output_ready(devc); timeout--);
+ devc->input_byte = 0;
+ uart401_cmd(devc, MPU_RESET);
+
+ /*
+ * Wait at least 25 msec. This method is not accurate so let's make the
+ * loop bit longer. Cannot sleep since this is called during boot.
+ */
+
+ for (timeout = 50000; timeout > 0 && !ok; timeout--)
+ {
+ if (devc->input_byte == MPU_ACK) /* Interrupt */
+ ok = 1;
+ else if (input_avail(devc))
+ {
+ if (uart401_read(devc) == MPU_ACK)
+ ok = 1;
+ }
+ }
+ }
+
+
+ if (ok)
+ {
+ DEB(printk("Reset UART401 OK\n"));
+ }
+ else
+ DDB(printk("Reset UART401 failed - No hardware detected.\n"));
+
+ if (ok)
+ uart401_input_loop(devc); /*
+ * Flush input before enabling interrupts
+ */
+
+ return ok;
+}
+
+int probe_uart401(struct address_info *hw_config, struct module *owner)
+{
+ uart401_devc *devc;
+ char *name = "MPU-401 (UART) MIDI";
+ int ok = 0;
+ unsigned long flags;
+
+ DDB(printk("Entered probe_uart401()\n"));
+
+ /* Default to "not found" */
+ hw_config->slots[4] = -1;
+
+ if (!request_region(hw_config->io_base, 4, "MPU-401 UART")) {
+ printk(KERN_INFO "uart401: could not request_region(%d, 4)\n", hw_config->io_base);
+ return 0;
+ }
+
+ devc = kmalloc(sizeof(uart401_devc), GFP_KERNEL);
+ if (!devc) {
+ printk(KERN_WARNING "uart401: Can't allocate memory\n");
+ goto cleanup_region;
+ }
+
+ devc->base = hw_config->io_base;
+ devc->irq = hw_config->irq;
+ devc->osp = hw_config->osp;
+ devc->midi_input_intr = NULL;
+ devc->opened = 0;
+ devc->input_byte = 0;
+ devc->my_dev = 0;
+ devc->share_irq = 0;
+ spin_lock_init(&devc->lock);
+
+ spin_lock_irqsave(&devc->lock,flags);
+ ok = reset_uart401(devc);
+ spin_unlock_irqrestore(&devc->lock,flags);
+
+ if (!ok)
+ goto cleanup_devc;
+
+ if (hw_config->name)
+ name = hw_config->name;
+
+ if (devc->irq < 0) {
+ devc->share_irq = 1;
+ devc->irq *= -1;
+ } else
+ devc->share_irq = 0;
+
+ if (!devc->share_irq)
+ if (request_irq(devc->irq, uart401intr, 0, "MPU-401 UART", devc) < 0) {
+ printk(KERN_WARNING "uart401: Failed to allocate IRQ%d\n", devc->irq);
+ devc->share_irq = 1;
+ }
+ devc->my_dev = sound_alloc_mididev();
+ enter_uart_mode(devc);
+
+ if (devc->my_dev == -1) {
+ printk(KERN_INFO "uart401: Too many midi devices detected\n");
+ goto cleanup_irq;
+ }
+ conf_printf(name, hw_config);
+ midi_devs[devc->my_dev] = kmalloc(sizeof(struct midi_operations), GFP_KERNEL);
+ if (!midi_devs[devc->my_dev]) {
+ printk(KERN_ERR "uart401: Failed to allocate memory\n");
+ goto cleanup_unload_mididev;
+ }
+ memcpy(midi_devs[devc->my_dev], &uart401_operations, sizeof(struct midi_operations));
+
+ if (owner)
+ midi_devs[devc->my_dev]->owner = owner;
+
+ midi_devs[devc->my_dev]->devc = devc;
+ midi_devs[devc->my_dev]->converter = kmalloc(sizeof(struct synth_operations), GFP_KERNEL);
+ if (!midi_devs[devc->my_dev]->converter) {
+ printk(KERN_WARNING "uart401: Failed to allocate memory\n");
+ goto cleanup_midi_devs;
+ }
+ memcpy(midi_devs[devc->my_dev]->converter, &std_midi_synth, sizeof(struct synth_operations));
+ strcpy(midi_devs[devc->my_dev]->info.name, name);
+ midi_devs[devc->my_dev]->converter->id = "UART401";
+ midi_devs[devc->my_dev]->converter->midi_dev = devc->my_dev;
+
+ if (owner)
+ midi_devs[devc->my_dev]->converter->owner = owner;
+
+ hw_config->slots[4] = devc->my_dev;
+ sequencer_init();
+ devc->opened = 0;
+ return 1;
+cleanup_midi_devs:
+ kfree(midi_devs[devc->my_dev]);
+cleanup_unload_mididev:
+ sound_unload_mididev(devc->my_dev);
+cleanup_irq:
+ if (!devc->share_irq)
+ free_irq(devc->irq, devc);
+cleanup_devc:
+ kfree(devc);
+cleanup_region:
+ release_region(hw_config->io_base, 4);
+ return 0;
+}
+
+void unload_uart401(struct address_info *hw_config)
+{
+ uart401_devc *devc;
+ int n=hw_config->slots[4];
+
+ /* Not set up */
+ if(n==-1 || midi_devs[n]==NULL)
+ return;
+
+ /* Not allocated (erm ??) */
+
+ devc = midi_devs[hw_config->slots[4]]->devc;
+ if (devc == NULL)
+ return;
+
+ reset_uart401(devc);
+ release_region(hw_config->io_base, 4);
+
+ if (!devc->share_irq)
+ free_irq(devc->irq, devc);
+ if (devc)
+ {
+ kfree(midi_devs[devc->my_dev]->converter);
+ kfree(midi_devs[devc->my_dev]);
+ kfree(devc);
+ devc = NULL;
+ }
+ /* This kills midi_devs[x] */
+ sound_unload_mididev(hw_config->slots[4]);
+}
+
+EXPORT_SYMBOL(probe_uart401);
+EXPORT_SYMBOL(unload_uart401);
+EXPORT_SYMBOL(uart401intr);
+
+static struct address_info cfg_mpu;
+
+static int io = -1;
+static int irq = -1;
+
+module_param(io, int, 0444);
+module_param(irq, int, 0444);
+
+
+static int __init init_uart401(void)
+{
+ cfg_mpu.irq = irq;
+ cfg_mpu.io_base = io;
+
+ /* Can be loaded either for module use or to provide functions
+ to others */
+ if (cfg_mpu.io_base != -1 && cfg_mpu.irq != -1) {
+ printk(KERN_INFO "MPU-401 UART driver Copyright (C) Hannu Savolainen 1993-1997");
+ if (!probe_uart401(&cfg_mpu, THIS_MODULE))
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void __exit cleanup_uart401(void)
+{
+ if (cfg_mpu.io_base != -1 && cfg_mpu.irq != -1)
+ unload_uart401(&cfg_mpu);
+}
+
+module_init(init_uart401);
+module_exit(cleanup_uart401);
+
+#ifndef MODULE
+static int __init setup_uart401(char *str)
+{
+ /* io, irq */
+ int ints[3];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ io = ints[1];
+ irq = ints[2];
+
+ return 1;
+}
+
+__setup("uart401=", setup_uart401);
+#endif
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/uart6850.c b/sound/oss/uart6850.c
new file mode 100644
index 00000000..f3f914aa
--- /dev/null
+++ b/sound/oss/uart6850.c
@@ -0,0 +1,361 @@
+/*
+ * sound/oss/uart6850.c
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ * Extended by Alan Cox for Red Hat Software. Now a loadable MIDI driver.
+ * 28/4/97 - (C) Copyright Alan Cox. Released under the GPL version 2.
+ *
+ * Alan Cox: Updated for new modular code. Removed snd_* irq handling. Now
+ * uses native linux resources
+ * Christoph Hellwig: Adapted to module_init/module_exit
+ * Jeff Garzik: Made it work again, in theory
+ * FIXME: If the request_irq() succeeds, the probe succeeds. Ug.
+ *
+ * Status: Testing required (no shit -jgarzik)
+ *
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+/* Mon Nov 22 22:38:35 MET 1993 marco@driq.home.usn.nl:
+ * added 6850 support, used with COVOX SoundMaster II and custom cards.
+ */
+
+#include "sound_config.h"
+
+static int uart6850_base = 0x330;
+
+static int *uart6850_osp;
+
+#define DATAPORT (uart6850_base)
+#define COMDPORT (uart6850_base+1)
+#define STATPORT (uart6850_base+1)
+
+static int uart6850_status(void)
+{
+ return inb(STATPORT);
+}
+
+#define input_avail() (uart6850_status()&INPUT_AVAIL)
+#define output_ready() (uart6850_status()&OUTPUT_READY)
+
+static void uart6850_cmd(unsigned char cmd)
+{
+ outb(cmd, COMDPORT);
+}
+
+static int uart6850_read(void)
+{
+ return inb(DATAPORT);
+}
+
+static void uart6850_write(unsigned char byte)
+{
+ outb(byte, DATAPORT);
+}
+
+#define OUTPUT_READY 0x02 /* Mask for data ready Bit */
+#define INPUT_AVAIL 0x01 /* Mask for Data Send Ready Bit */
+
+#define UART_RESET 0x95
+#define UART_MODE_ON 0x03
+
+static int uart6850_opened;
+static int uart6850_irq;
+static int uart6850_detected;
+static int my_dev;
+static DEFINE_SPINLOCK(lock);
+
+static void (*midi_input_intr) (int dev, unsigned char data);
+static void poll_uart6850(unsigned long dummy);
+
+
+static DEFINE_TIMER(uart6850_timer, poll_uart6850, 0, 0);
+
+static void uart6850_input_loop(void)
+{
+ int count = 10;
+
+ while (count)
+ {
+ /*
+ * Not timed out
+ */
+ if (input_avail())
+ {
+ unsigned char c = uart6850_read();
+ count = 100;
+ if (uart6850_opened & OPEN_READ)
+ midi_input_intr(my_dev, c);
+ }
+ else
+ {
+ while (!input_avail() && count)
+ count--;
+ }
+ }
+}
+
+static irqreturn_t m6850intr(int irq, void *dev_id)
+{
+ if (input_avail())
+ uart6850_input_loop();
+ return IRQ_HANDLED;
+}
+
+/*
+ * It looks like there is no input interrupts in the UART mode. Let's try
+ * polling.
+ */
+
+static void poll_uart6850(unsigned long dummy)
+{
+ unsigned long flags;
+
+ if (!(uart6850_opened & OPEN_READ))
+ return; /* Device has been closed */
+
+ spin_lock_irqsave(&lock,flags);
+ if (input_avail())
+ uart6850_input_loop();
+
+ uart6850_timer.expires = 1 + jiffies;
+ add_timer(&uart6850_timer);
+
+ /*
+ * Come back later
+ */
+
+ spin_unlock_irqrestore(&lock,flags);
+}
+
+static int uart6850_open(int dev, int mode,
+ void (*input) (int dev, unsigned char data),
+ void (*output) (int dev)
+)
+{
+ if (uart6850_opened)
+ {
+/* printk("Midi6850: Midi busy\n");*/
+ return -EBUSY;
+ };
+
+ uart6850_cmd(UART_RESET);
+ uart6850_input_loop();
+ midi_input_intr = input;
+ uart6850_opened = mode;
+ poll_uart6850(0); /*
+ * Enable input polling
+ */
+
+ return 0;
+}
+
+static void uart6850_close(int dev)
+{
+ uart6850_cmd(UART_MODE_ON);
+ del_timer(&uart6850_timer);
+ uart6850_opened = 0;
+}
+
+static int uart6850_out(int dev, unsigned char midi_byte)
+{
+ int timeout;
+ unsigned long flags;
+
+ /*
+ * Test for input since pending input seems to block the output.
+ */
+
+ spin_lock_irqsave(&lock,flags);
+
+ if (input_avail())
+ uart6850_input_loop();
+
+ spin_unlock_irqrestore(&lock,flags);
+
+ /*
+ * Sometimes it takes about 13000 loops before the output becomes ready
+ * (After reset). Normally it takes just about 10 loops.
+ */
+
+ for (timeout = 30000; timeout > 0 && !output_ready(); timeout--); /*
+ * Wait
+ */
+ if (!output_ready())
+ {
+ printk(KERN_WARNING "Midi6850: Timeout\n");
+ return 0;
+ }
+ uart6850_write(midi_byte);
+ return 1;
+}
+
+static inline int uart6850_command(int dev, unsigned char *midi_byte)
+{
+ return 1;
+}
+
+static inline int uart6850_start_read(int dev)
+{
+ return 0;
+}
+
+static inline int uart6850_end_read(int dev)
+{
+ return 0;
+}
+
+static inline void uart6850_kick(int dev)
+{
+}
+
+static inline int uart6850_buffer_status(int dev)
+{
+ return 0; /*
+ * No data in buffers
+ */
+}
+
+#define MIDI_SYNTH_NAME "6850 UART Midi"
+#define MIDI_SYNTH_CAPS SYNTH_CAP_INPUT
+#include "midi_synth.h"
+
+static struct midi_operations uart6850_operations =
+{
+ .owner = THIS_MODULE,
+ .info = {"6850 UART", 0, 0, SNDCARD_UART6850},
+ .converter = &std_midi_synth,
+ .in_info = {0},
+ .open = uart6850_open,
+ .close = uart6850_close,
+ .outputc = uart6850_out,
+ .start_read = uart6850_start_read,
+ .end_read = uart6850_end_read,
+ .kick = uart6850_kick,
+ .command = uart6850_command,
+ .buffer_status = uart6850_buffer_status
+};
+
+
+static void __init attach_uart6850(struct address_info *hw_config)
+{
+ int ok, timeout;
+ unsigned long flags;
+
+ if (!uart6850_detected)
+ return;
+
+ if ((my_dev = sound_alloc_mididev()) == -1)
+ {
+ printk(KERN_INFO "uart6850: Too many midi devices detected\n");
+ return;
+ }
+ uart6850_base = hw_config->io_base;
+ uart6850_osp = hw_config->osp;
+ uart6850_irq = hw_config->irq;
+
+ spin_lock_irqsave(&lock,flags);
+
+ for (timeout = 30000; timeout > 0 && !output_ready(); timeout--); /*
+ * Wait
+ */
+ uart6850_cmd(UART_MODE_ON);
+ ok = 1;
+ spin_unlock_irqrestore(&lock,flags);
+
+ conf_printf("6850 Midi Interface", hw_config);
+
+ std_midi_synth.midi_dev = my_dev;
+ hw_config->slots[4] = my_dev;
+ midi_devs[my_dev] = &uart6850_operations;
+ sequencer_init();
+}
+
+static inline int reset_uart6850(void)
+{
+ uart6850_read();
+ return 1; /*
+ * OK
+ */
+}
+
+static int __init probe_uart6850(struct address_info *hw_config)
+{
+ int ok;
+
+ uart6850_osp = hw_config->osp;
+ uart6850_base = hw_config->io_base;
+ uart6850_irq = hw_config->irq;
+
+ if (request_irq(uart6850_irq, m6850intr, 0, "MIDI6850", NULL) < 0)
+ return 0;
+
+ ok = reset_uart6850();
+ uart6850_detected = ok;
+ return ok;
+}
+
+static void __exit unload_uart6850(struct address_info *hw_config)
+{
+ free_irq(hw_config->irq, NULL);
+ sound_unload_mididev(hw_config->slots[4]);
+}
+
+static struct address_info cfg_mpu;
+
+static int __initdata io = -1;
+static int __initdata irq = -1;
+
+module_param(io, int, 0);
+module_param(irq, int, 0);
+
+static int __init init_uart6850(void)
+{
+ cfg_mpu.io_base = io;
+ cfg_mpu.irq = irq;
+
+ if (cfg_mpu.io_base == -1 || cfg_mpu.irq == -1) {
+ printk(KERN_INFO "uart6850: irq and io must be set.\n");
+ return -EINVAL;
+ }
+
+ if (probe_uart6850(&cfg_mpu))
+ return -ENODEV;
+ attach_uart6850(&cfg_mpu);
+
+ return 0;
+}
+
+static void __exit cleanup_uart6850(void)
+{
+ unload_uart6850(&cfg_mpu);
+}
+
+module_init(init_uart6850);
+module_exit(cleanup_uart6850);
+
+#ifndef MODULE
+static int __init setup_uart6850(char *str)
+{
+ /* io, irq */
+ int ints[3];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ io = ints[1];
+ irq = ints[2];
+
+ return 1;
+}
+__setup("uart6850=", setup_uart6850);
+#endif
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/ulaw.h b/sound/oss/ulaw.h
new file mode 100644
index 00000000..0ff8c0a3
--- /dev/null
+++ b/sound/oss/ulaw.h
@@ -0,0 +1,69 @@
+static unsigned char ulaw_dsp[] = {
+ 3, 7, 11, 15, 19, 23, 27, 31,
+ 35, 39, 43, 47, 51, 55, 59, 63,
+ 66, 68, 70, 72, 74, 76, 78, 80,
+ 82, 84, 86, 88, 90, 92, 94, 96,
+ 98, 99, 100, 101, 102, 103, 104, 105,
+ 106, 107, 108, 109, 110, 111, 112, 113,
+ 113, 114, 114, 115, 115, 116, 116, 117,
+ 117, 118, 118, 119, 119, 120, 120, 121,
+ 121, 121, 122, 122, 122, 122, 123, 123,
+ 123, 123, 124, 124, 124, 124, 125, 125,
+ 125, 125, 125, 125, 126, 126, 126, 126,
+ 126, 126, 126, 126, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128,
+ 253, 249, 245, 241, 237, 233, 229, 225,
+ 221, 217, 213, 209, 205, 201, 197, 193,
+ 190, 188, 186, 184, 182, 180, 178, 176,
+ 174, 172, 170, 168, 166, 164, 162, 160,
+ 158, 157, 156, 155, 154, 153, 152, 151,
+ 150, 149, 148, 147, 146, 145, 144, 143,
+ 143, 142, 142, 141, 141, 140, 140, 139,
+ 139, 138, 138, 137, 137, 136, 136, 135,
+ 135, 135, 134, 134, 134, 134, 133, 133,
+ 133, 133, 132, 132, 132, 132, 131, 131,
+ 131, 131, 131, 131, 130, 130, 130, 130,
+ 130, 130, 130, 130, 129, 129, 129, 129,
+ 129, 129, 129, 129, 129, 129, 129, 129,
+ 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128,
+};
+
+static unsigned char dsp_ulaw[] = {
+ 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 2, 2, 2, 2, 3, 3, 3,
+ 3, 4, 4, 4, 4, 5, 5, 5,
+ 5, 6, 6, 6, 6, 7, 7, 7,
+ 7, 8, 8, 8, 8, 9, 9, 9,
+ 9, 10, 10, 10, 10, 11, 11, 11,
+ 11, 12, 12, 12, 12, 13, 13, 13,
+ 13, 14, 14, 14, 14, 15, 15, 15,
+ 15, 16, 16, 17, 17, 18, 18, 19,
+ 19, 20, 20, 21, 21, 22, 22, 23,
+ 23, 24, 24, 25, 25, 26, 26, 27,
+ 27, 28, 28, 29, 29, 30, 30, 31,
+ 31, 32, 33, 34, 35, 36, 37, 38,
+ 39, 40, 41, 42, 43, 44, 45, 46,
+ 47, 49, 51, 53, 55, 57, 59, 61,
+ 63, 66, 70, 74, 78, 84, 92, 104,
+ 254, 231, 219, 211, 205, 201, 197, 193,
+ 190, 188, 186, 184, 182, 180, 178, 176,
+ 175, 174, 173, 172, 171, 170, 169, 168,
+ 167, 166, 165, 164, 163, 162, 161, 160,
+ 159, 159, 158, 158, 157, 157, 156, 156,
+ 155, 155, 154, 154, 153, 153, 152, 152,
+ 151, 151, 150, 150, 149, 149, 148, 148,
+ 147, 147, 146, 146, 145, 145, 144, 144,
+ 143, 143, 143, 143, 142, 142, 142, 142,
+ 141, 141, 141, 141, 140, 140, 140, 140,
+ 139, 139, 139, 139, 138, 138, 138, 138,
+ 137, 137, 137, 137, 136, 136, 136, 136,
+ 135, 135, 135, 135, 134, 134, 134, 134,
+ 133, 133, 133, 133, 132, 132, 132, 132,
+ 131, 131, 131, 131, 130, 130, 130, 130,
+ 129, 129, 129, 129, 128, 128, 128, 128,
+};
diff --git a/sound/oss/v_midi.c b/sound/oss/v_midi.c
new file mode 100644
index 00000000..f0b4151d
--- /dev/null
+++ b/sound/oss/v_midi.c
@@ -0,0 +1,290 @@
+/*
+ * sound/oss/v_midi.c
+ *
+ * The low level driver for the Sound Blaster DS chips.
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1996
+ *
+ * USS/Lite for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ * ??
+ *
+ * Changes
+ * Alan Cox Modularisation, changed memory allocations
+ * Christoph Hellwig Adapted to module_init/module_exit
+ *
+ * Status
+ * Untested
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+#include "v_midi.h"
+
+static vmidi_devc *v_devc[2] = { NULL, NULL};
+static int midi1,midi2;
+static void *midi_mem = NULL;
+
+/*
+ * The DSP channel can be used either for input or output. Variable
+ * 'sb_irq_mode' will be set when the program calls read or write first time
+ * after open. Current version doesn't support mode changes without closing
+ * and reopening the device. Support for this feature may be implemented in a
+ * future version of this driver.
+ */
+
+
+static int v_midi_open (int dev, int mode,
+ void (*input) (int dev, unsigned char data),
+ void (*output) (int dev)
+)
+{
+ vmidi_devc *devc = midi_devs[dev]->devc;
+ unsigned long flags;
+
+ if (devc == NULL)
+ return -(ENXIO);
+
+ spin_lock_irqsave(&devc->lock,flags);
+ if (devc->opened)
+ {
+ spin_unlock_irqrestore(&devc->lock,flags);
+ return -(EBUSY);
+ }
+ devc->opened = 1;
+ spin_unlock_irqrestore(&devc->lock,flags);
+
+ devc->intr_active = 1;
+
+ if (mode & OPEN_READ)
+ {
+ devc->input_opened = 1;
+ devc->midi_input_intr = input;
+ }
+
+ return 0;
+}
+
+static void v_midi_close (int dev)
+{
+ vmidi_devc *devc = midi_devs[dev]->devc;
+ unsigned long flags;
+
+ if (devc == NULL)
+ return;
+
+ spin_lock_irqsave(&devc->lock,flags);
+ devc->intr_active = 0;
+ devc->input_opened = 0;
+ devc->opened = 0;
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static int v_midi_out (int dev, unsigned char midi_byte)
+{
+ vmidi_devc *devc = midi_devs[dev]->devc;
+ vmidi_devc *pdevc;
+
+ if (devc == NULL)
+ return -ENXIO;
+
+ pdevc = midi_devs[devc->pair_mididev]->devc;
+ if (pdevc->input_opened > 0){
+ if (MIDIbuf_avail(pdevc->my_mididev) > 500)
+ return 0;
+ pdevc->midi_input_intr (pdevc->my_mididev, midi_byte);
+ }
+ return 1;
+}
+
+static inline int v_midi_start_read (int dev)
+{
+ return 0;
+}
+
+static int v_midi_end_read (int dev)
+{
+ vmidi_devc *devc = midi_devs[dev]->devc;
+ if (devc == NULL)
+ return -ENXIO;
+
+ devc->intr_active = 0;
+ return 0;
+}
+
+/* why -EPERM and not -EINVAL?? */
+
+static inline int v_midi_ioctl (int dev, unsigned cmd, void __user *arg)
+{
+ return -EPERM;
+}
+
+
+#define MIDI_SYNTH_NAME "Loopback MIDI"
+#define MIDI_SYNTH_CAPS SYNTH_CAP_INPUT
+
+#include "midi_synth.h"
+
+static struct midi_operations v_midi_operations =
+{
+ .owner = THIS_MODULE,
+ .info = {"Loopback MIDI Port 1", 0, 0, SNDCARD_VMIDI},
+ .converter = &std_midi_synth,
+ .in_info = {0},
+ .open = v_midi_open,
+ .close = v_midi_close,
+ .ioctl = v_midi_ioctl,
+ .outputc = v_midi_out,
+ .start_read = v_midi_start_read,
+ .end_read = v_midi_end_read,
+};
+
+static struct midi_operations v_midi_operations2 =
+{
+ .owner = THIS_MODULE,
+ .info = {"Loopback MIDI Port 2", 0, 0, SNDCARD_VMIDI},
+ .converter = &std_midi_synth,
+ .in_info = {0},
+ .open = v_midi_open,
+ .close = v_midi_close,
+ .ioctl = v_midi_ioctl,
+ .outputc = v_midi_out,
+ .start_read = v_midi_start_read,
+ .end_read = v_midi_end_read,
+};
+
+/*
+ * We kmalloc just one of these - it makes life simpler and the code
+ * cleaner and the memory handling far more efficient
+ */
+
+struct vmidi_memory
+{
+ /* Must be first */
+ struct midi_operations m_ops[2];
+ struct synth_operations s_ops[2];
+ struct vmidi_devc v_ops[2];
+};
+
+static void __init attach_v_midi (struct address_info *hw_config)
+{
+ struct vmidi_memory *m;
+ /* printk("Attaching v_midi device.....\n"); */
+
+ midi1 = sound_alloc_mididev();
+ if (midi1 == -1)
+ {
+ printk(KERN_ERR "v_midi: Too many midi devices detected\n");
+ return;
+ }
+
+ m = kmalloc(sizeof(struct vmidi_memory), GFP_KERNEL);
+ if (m == NULL)
+ {
+ printk(KERN_WARNING "Loopback MIDI: Failed to allocate memory\n");
+ sound_unload_mididev(midi1);
+ return;
+ }
+
+ midi_mem = m;
+
+ midi_devs[midi1] = &m->m_ops[0];
+
+
+ midi2 = sound_alloc_mididev();
+ if (midi2 == -1)
+ {
+ printk (KERN_ERR "v_midi: Too many midi devices detected\n");
+ kfree(m);
+ sound_unload_mididev(midi1);
+ return;
+ }
+
+ midi_devs[midi2] = &m->m_ops[1];
+
+ /* printk("VMIDI1: %d VMIDI2: %d\n",midi1,midi2); */
+
+ /* for MIDI-1 */
+ v_devc[0] = &m->v_ops[0];
+ memcpy ((char *) midi_devs[midi1], (char *) &v_midi_operations,
+ sizeof (struct midi_operations));
+
+ v_devc[0]->my_mididev = midi1;
+ v_devc[0]->pair_mididev = midi2;
+ v_devc[0]->opened = v_devc[0]->input_opened = 0;
+ v_devc[0]->intr_active = 0;
+ v_devc[0]->midi_input_intr = NULL;
+ spin_lock_init(&v_devc[0]->lock);
+
+ midi_devs[midi1]->devc = v_devc[0];
+
+ midi_devs[midi1]->converter = &m->s_ops[0];
+ std_midi_synth.midi_dev = midi1;
+ memcpy ((char *) midi_devs[midi1]->converter, (char *) &std_midi_synth,
+ sizeof (struct synth_operations));
+ midi_devs[midi1]->converter->id = "V_MIDI 1";
+
+ /* for MIDI-2 */
+ v_devc[1] = &m->v_ops[1];
+
+ memcpy ((char *) midi_devs[midi2], (char *) &v_midi_operations2,
+ sizeof (struct midi_operations));
+
+ v_devc[1]->my_mididev = midi2;
+ v_devc[1]->pair_mididev = midi1;
+ v_devc[1]->opened = v_devc[1]->input_opened = 0;
+ v_devc[1]->intr_active = 0;
+ v_devc[1]->midi_input_intr = NULL;
+ spin_lock_init(&v_devc[1]->lock);
+
+ midi_devs[midi2]->devc = v_devc[1];
+ midi_devs[midi2]->converter = &m->s_ops[1];
+
+ std_midi_synth.midi_dev = midi2;
+ memcpy ((char *) midi_devs[midi2]->converter, (char *) &std_midi_synth,
+ sizeof (struct synth_operations));
+ midi_devs[midi2]->converter->id = "V_MIDI 2";
+
+ sequencer_init();
+ /* printk("Attached v_midi device\n"); */
+}
+
+static inline int __init probe_v_midi(struct address_info *hw_config)
+{
+ return(1); /* always OK */
+}
+
+
+static void __exit unload_v_midi(struct address_info *hw_config)
+{
+ sound_unload_mididev(midi1);
+ sound_unload_mididev(midi2);
+ kfree(midi_mem);
+}
+
+static struct address_info cfg; /* dummy */
+
+static int __init init_vmidi(void)
+{
+ printk("MIDI Loopback device driver\n");
+ if (!probe_v_midi(&cfg))
+ return -ENODEV;
+ attach_v_midi(&cfg);
+
+ return 0;
+}
+
+static void __exit cleanup_vmidi(void)
+{
+ unload_v_midi(&cfg);
+}
+
+module_init(init_vmidi);
+module_exit(cleanup_vmidi);
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/v_midi.h b/sound/oss/v_midi.h
new file mode 100644
index 00000000..08e2185e
--- /dev/null
+++ b/sound/oss/v_midi.h
@@ -0,0 +1,14 @@
+typedef struct vmidi_devc {
+ int dev;
+
+ /* State variables */
+ int opened;
+ spinlock_t lock;
+
+ /* MIDI fields */
+ int my_mididev;
+ int pair_mididev;
+ int input_opened;
+ int intr_active;
+ void (*midi_input_intr) (int dev, unsigned char data);
+ } vmidi_devc;
diff --git a/sound/oss/vidc.c b/sound/oss/vidc.c
new file mode 100644
index 00000000..12ba28e7
--- /dev/null
+++ b/sound/oss/vidc.c
@@ -0,0 +1,558 @@
+/*
+ * linux/drivers/sound/vidc.c
+ *
+ * Copyright (C) 1997-2000 by Russell King <rmk@arm.linux.org.uk>
+ *
+ * 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.
+ *
+ * VIDC20 audio driver.
+ *
+ * The VIDC20 sound hardware consists of the VIDC20 itself, a DAC and a DMA
+ * engine. The DMA transfers fixed-format (16-bit little-endian linear)
+ * samples to the VIDC20, which then transfers this data serially to the
+ * DACs. The samplerate is controlled by the VIDC.
+ *
+ * We currently support a mixer device, but it is currently non-functional.
+ */
+
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+
+#include <mach/hardware.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/hardware/iomd.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+
+#include "sound_config.h"
+#include "vidc.h"
+
+#ifndef _SIOC_TYPE
+#define _SIOC_TYPE(x) _IOC_TYPE(x)
+#endif
+#ifndef _SIOC_NR
+#define _SIOC_NR(x) _IOC_NR(x)
+#endif
+
+#define VIDC_SOUND_CLOCK (250000)
+#define VIDC_SOUND_CLOCK_EXT (176400)
+
+/*
+ * When using SERIAL SOUND mode (external DAC), the number of physical
+ * channels is fixed at 2.
+ */
+static int vidc_busy;
+static int vidc_adev;
+static int vidc_audio_rate;
+static char vidc_audio_format;
+static char vidc_audio_channels;
+
+static unsigned char vidc_level_l[SOUND_MIXER_NRDEVICES] = {
+ 85, /* master */
+ 50, /* bass */
+ 50, /* treble */
+ 0, /* synth */
+ 75, /* pcm */
+ 0, /* speaker */
+ 100, /* ext line */
+ 0, /* mic */
+ 100, /* CD */
+ 0,
+};
+
+static unsigned char vidc_level_r[SOUND_MIXER_NRDEVICES] = {
+ 85, /* master */
+ 50, /* bass */
+ 50, /* treble */
+ 0, /* synth */
+ 75, /* pcm */
+ 0, /* speaker */
+ 100, /* ext line */
+ 0, /* mic */
+ 100, /* CD */
+ 0,
+};
+
+static unsigned int vidc_audio_volume_l; /* left PCM vol, 0 - 65536 */
+static unsigned int vidc_audio_volume_r; /* right PCM vol, 0 - 65536 */
+
+extern void vidc_update_filler(int bits, int channels);
+extern int softoss_dev;
+
+static void
+vidc_mixer_set(int mdev, unsigned int level)
+{
+ unsigned int lev_l = level & 0x007f;
+ unsigned int lev_r = (level & 0x7f00) >> 8;
+ unsigned int mlev_l, mlev_r;
+
+ if (lev_l > 100)
+ lev_l = 100;
+ if (lev_r > 100)
+ lev_r = 100;
+
+#define SCALE(lev,master) ((lev) * (master) * 65536 / 10000)
+
+ mlev_l = vidc_level_l[SOUND_MIXER_VOLUME];
+ mlev_r = vidc_level_r[SOUND_MIXER_VOLUME];
+
+ switch (mdev) {
+ case SOUND_MIXER_VOLUME:
+ case SOUND_MIXER_PCM:
+ vidc_level_l[mdev] = lev_l;
+ vidc_level_r[mdev] = lev_r;
+
+ vidc_audio_volume_l = SCALE(lev_l, mlev_l);
+ vidc_audio_volume_r = SCALE(lev_r, mlev_r);
+/*printk("VIDC: PCM vol %05X %05X\n", vidc_audio_volume_l, vidc_audio_volume_r);*/
+ break;
+ }
+#undef SCALE
+}
+
+static int vidc_mixer_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+ unsigned int val;
+ unsigned int mdev;
+
+ if (_SIOC_TYPE(cmd) != 'M')
+ return -EINVAL;
+
+ mdev = _SIOC_NR(cmd);
+
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+ if (get_user(val, (unsigned int __user *)arg))
+ return -EFAULT;
+
+ if (mdev < SOUND_MIXER_NRDEVICES)
+ vidc_mixer_set(mdev, val);
+ else
+ return -EINVAL;
+ }
+
+ /*
+ * Return parameters
+ */
+ switch (mdev) {
+ case SOUND_MIXER_RECSRC:
+ val = 0;
+ break;
+
+ case SOUND_MIXER_DEVMASK:
+ val = SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_SYNTH;
+ break;
+
+ case SOUND_MIXER_STEREODEVS:
+ val = SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_SYNTH;
+ break;
+
+ case SOUND_MIXER_RECMASK:
+ val = 0;
+ break;
+
+ case SOUND_MIXER_CAPS:
+ val = 0;
+ break;
+
+ default:
+ if (mdev < SOUND_MIXER_NRDEVICES)
+ val = vidc_level_l[mdev] | vidc_level_r[mdev] << 8;
+ else
+ return -EINVAL;
+ }
+
+ return put_user(val, (unsigned int __user *)arg) ? -EFAULT : 0;
+}
+
+static unsigned int vidc_audio_set_format(int dev, unsigned int fmt)
+{
+ switch (fmt) {
+ default:
+ fmt = AFMT_S16_LE;
+ case AFMT_U8:
+ case AFMT_S8:
+ case AFMT_S16_LE:
+ vidc_audio_format = fmt;
+ vidc_update_filler(vidc_audio_format, vidc_audio_channels);
+ case AFMT_QUERY:
+ break;
+ }
+ return vidc_audio_format;
+}
+
+#define my_abs(i) ((i)<0 ? -(i) : (i))
+
+static int vidc_audio_set_speed(int dev, int rate)
+{
+ if (rate) {
+ unsigned int hwctrl, hwrate, hwrate_ext, rate_int, rate_ext;
+ unsigned int diff_int, diff_ext;
+ unsigned int newsize, new2size;
+
+ hwctrl = 0x00000003;
+
+ /* Using internal clock */
+ hwrate = (((VIDC_SOUND_CLOCK * 2) / rate) + 1) >> 1;
+ if (hwrate < 3)
+ hwrate = 3;
+ if (hwrate > 255)
+ hwrate = 255;
+
+ /* Using exernal clock */
+ hwrate_ext = (((VIDC_SOUND_CLOCK_EXT * 2) / rate) + 1) >> 1;
+ if (hwrate_ext < 3)
+ hwrate_ext = 3;
+ if (hwrate_ext > 255)
+ hwrate_ext = 255;
+
+ rate_int = VIDC_SOUND_CLOCK / hwrate;
+ rate_ext = VIDC_SOUND_CLOCK_EXT / hwrate_ext;
+
+ /* Chose between external and internal clock */
+ diff_int = my_abs(rate_ext-rate);
+ diff_ext = my_abs(rate_int-rate);
+ if (diff_ext < diff_int) {
+ /*printk("VIDC: external %d %d %d\n", rate, rate_ext, hwrate_ext);*/
+ hwrate=hwrate_ext;
+ hwctrl=0x00000002;
+ /* Allow roughly 0.4% tolerance */
+ if (diff_ext > (rate/256))
+ rate=rate_ext;
+ } else {
+ /*printk("VIDC: internal %d %d %d\n", rate, rate_int, hwrate);*/
+ hwctrl=0x00000003;
+ /* Allow roughly 0.4% tolerance */
+ if (diff_int > (rate/256))
+ rate=rate_int;
+ }
+
+ vidc_writel(0xb0000000 | (hwrate - 2));
+ vidc_writel(0xb1000000 | hwctrl);
+
+ newsize = (10000 / hwrate) & ~3;
+ if (newsize < 208)
+ newsize = 208;
+ if (newsize > 4096)
+ newsize = 4096;
+ for (new2size = 128; new2size < newsize; new2size <<= 1);
+ if (new2size - newsize > newsize - (new2size >> 1))
+ new2size >>= 1;
+ if (new2size > 4096) {
+ printk(KERN_ERR "VIDC: error: dma buffer (%d) %d > 4K\n",
+ newsize, new2size);
+ new2size = 4096;
+ }
+ /*printk("VIDC: dma size %d\n", new2size);*/
+ dma_bufsize = new2size;
+ vidc_audio_rate = rate;
+ }
+ return vidc_audio_rate;
+}
+
+static short vidc_audio_set_channels(int dev, short channels)
+{
+ switch (channels) {
+ default:
+ channels = 2;
+ case 1:
+ case 2:
+ vidc_audio_channels = channels;
+ vidc_update_filler(vidc_audio_format, vidc_audio_channels);
+ case 0:
+ break;
+ }
+ return vidc_audio_channels;
+}
+
+/*
+ * Open the device
+ */
+static int vidc_audio_open(int dev, int mode)
+{
+ /* This audio device does not have recording capability */
+ if (mode == OPEN_READ)
+ return -EPERM;
+
+ if (vidc_busy)
+ return -EBUSY;
+
+ vidc_busy = 1;
+ return 0;
+}
+
+/*
+ * Close the device
+ */
+static void vidc_audio_close(int dev)
+{
+ vidc_busy = 0;
+}
+
+/*
+ * Output a block via DMA to sound device.
+ *
+ * We just set the DMA start and count; the DMA interrupt routine
+ * will take care of formatting the samples (via the appropriate
+ * vidc_filler routine), and flag via vidc_audio_dma_interrupt when
+ * more data is required.
+ */
+static void
+vidc_audio_output_block(int dev, unsigned long buf, int total_count, int one)
+{
+ struct dma_buffparms *dmap = audio_devs[dev]->dmap_out;
+ unsigned long flags;
+
+ local_irq_save(flags);
+ dma_start = buf - (unsigned long)dmap->raw_buf_phys + (unsigned long)dmap->raw_buf;
+ dma_count = total_count;
+ local_irq_restore(flags);
+}
+
+static void
+vidc_audio_start_input(int dev, unsigned long buf, int count, int intrflag)
+{
+}
+
+static int vidc_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+ return -EINVAL;
+}
+
+static irqreturn_t vidc_audio_dma_interrupt(void)
+{
+ DMAbuf_outputintr(vidc_adev, 1);
+ return IRQ_HANDLED;
+}
+
+/*
+ * Prepare for outputting samples.
+ *
+ * Each buffer that will be passed will be `bsize' bytes long,
+ * with a total of `bcount' buffers.
+ */
+static int vidc_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+ struct audio_operations *adev = audio_devs[dev];
+
+ dma_interrupt = NULL;
+ adev->dmap_out->flags |= DMA_NODMA;
+
+ return 0;
+}
+
+/*
+ * Stop our current operation.
+ */
+static void vidc_audio_reset(int dev)
+{
+ dma_interrupt = NULL;
+}
+
+static int vidc_audio_local_qlen(int dev)
+{
+ return /*dma_count !=*/ 0;
+}
+
+static void vidc_audio_trigger(int dev, int enable_bits)
+{
+ struct audio_operations *adev = audio_devs[dev];
+
+ if (enable_bits & PCM_ENABLE_OUTPUT) {
+ if (!(adev->dmap_out->flags & DMA_ACTIVE)) {
+ unsigned long flags;
+
+ local_irq_save(flags);
+
+ /* prevent recusion */
+ adev->dmap_out->flags |= DMA_ACTIVE;
+
+ dma_interrupt = vidc_audio_dma_interrupt;
+ vidc_sound_dma_irq(0, NULL);
+ iomd_writeb(DMA_CR_E | 0x10, IOMD_SD0CR);
+
+ local_irq_restore(flags);
+ }
+ }
+}
+
+static struct audio_driver vidc_audio_driver =
+{
+ .owner = THIS_MODULE,
+ .open = vidc_audio_open,
+ .close = vidc_audio_close,
+ .output_block = vidc_audio_output_block,
+ .start_input = vidc_audio_start_input,
+ .prepare_for_input = vidc_audio_prepare_for_input,
+ .prepare_for_output = vidc_audio_prepare_for_output,
+ .halt_io = vidc_audio_reset,
+ .local_qlen = vidc_audio_local_qlen,
+ .trigger = vidc_audio_trigger,
+ .set_speed = vidc_audio_set_speed,
+ .set_bits = vidc_audio_set_format,
+ .set_channels = vidc_audio_set_channels
+};
+
+static struct mixer_operations vidc_mixer_operations = {
+ .owner = THIS_MODULE,
+ .id = "VIDC",
+ .name = "VIDCsound",
+ .ioctl = vidc_mixer_ioctl
+};
+
+void vidc_update_filler(int format, int channels)
+{
+#define TYPE(fmt,ch) (((fmt)<<2) | ((ch)&3))
+
+ switch (TYPE(format, channels)) {
+ default:
+ case TYPE(AFMT_U8, 1):
+ vidc_filler = vidc_fill_1x8_u;
+ break;
+
+ case TYPE(AFMT_U8, 2):
+ vidc_filler = vidc_fill_2x8_u;
+ break;
+
+ case TYPE(AFMT_S8, 1):
+ vidc_filler = vidc_fill_1x8_s;
+ break;
+
+ case TYPE(AFMT_S8, 2):
+ vidc_filler = vidc_fill_2x8_s;
+ break;
+
+ case TYPE(AFMT_S16_LE, 1):
+ vidc_filler = vidc_fill_1x16_s;
+ break;
+
+ case TYPE(AFMT_S16_LE, 2):
+ vidc_filler = vidc_fill_2x16_s;
+ break;
+ }
+}
+
+static void __init attach_vidc(struct address_info *hw_config)
+{
+ char name[32];
+ int i, adev;
+
+ sprintf(name, "VIDC %d-bit sound", hw_config->card_subtype);
+ conf_printf(name, hw_config);
+ memset(dma_buf, 0, sizeof(dma_buf));
+
+ adev = sound_install_audiodrv(AUDIO_DRIVER_VERSION, name,
+ &vidc_audio_driver, sizeof(vidc_audio_driver),
+ DMA_AUTOMODE, AFMT_U8 | AFMT_S8 | AFMT_S16_LE,
+ NULL, hw_config->dma, hw_config->dma2);
+
+ if (adev < 0)
+ goto audio_failed;
+
+ /*
+ * 1024 bytes => 64 buffers
+ */
+ audio_devs[adev]->min_fragment = 10;
+ audio_devs[adev]->mixer_dev = num_mixers;
+
+ audio_devs[adev]->mixer_dev =
+ sound_install_mixer(MIXER_DRIVER_VERSION,
+ name, &vidc_mixer_operations,
+ sizeof(vidc_mixer_operations), NULL);
+
+ if (audio_devs[adev]->mixer_dev < 0)
+ goto mixer_failed;
+
+ for (i = 0; i < 2; i++) {
+ dma_buf[i] = get_zeroed_page(GFP_KERNEL);
+ if (!dma_buf[i]) {
+ printk(KERN_ERR "%s: can't allocate required buffers\n",
+ name);
+ goto mem_failed;
+ }
+ dma_pbuf[i] = virt_to_phys((void *)dma_buf[i]);
+ }
+
+ if (sound_alloc_dma(hw_config->dma, hw_config->name)) {
+ printk(KERN_ERR "%s: DMA %d is in use\n", name, hw_config->dma);
+ goto dma_failed;
+ }
+
+ if (request_irq(hw_config->irq, vidc_sound_dma_irq, 0,
+ hw_config->name, &dma_start)) {
+ printk(KERN_ERR "%s: IRQ %d is in use\n", name, hw_config->irq);
+ goto irq_failed;
+ }
+ vidc_adev = adev;
+ vidc_mixer_set(SOUND_MIXER_VOLUME, (85 | 85 << 8));
+
+ return;
+
+irq_failed:
+ sound_free_dma(hw_config->dma);
+dma_failed:
+mem_failed:
+ for (i = 0; i < 2; i++)
+ free_page(dma_buf[i]);
+ sound_unload_mixerdev(audio_devs[adev]->mixer_dev);
+mixer_failed:
+ sound_unload_audiodev(adev);
+audio_failed:
+ return;
+}
+
+static int __init probe_vidc(struct address_info *hw_config)
+{
+ hw_config->irq = IRQ_DMAS0;
+ hw_config->dma = DMA_VIRTUAL_SOUND;
+ hw_config->dma2 = -1;
+ hw_config->card_subtype = 16;
+ hw_config->name = "VIDC20";
+ return 1;
+}
+
+static void __exit unload_vidc(struct address_info *hw_config)
+{
+ int i, adev = vidc_adev;
+
+ vidc_adev = -1;
+
+ free_irq(hw_config->irq, &dma_start);
+ sound_free_dma(hw_config->dma);
+
+ if (adev >= 0) {
+ sound_unload_mixerdev(audio_devs[adev]->mixer_dev);
+ sound_unload_audiodev(adev);
+ for (i = 0; i < 2; i++)
+ free_page(dma_buf[i]);
+ }
+}
+
+static struct address_info cfg;
+
+static int __init init_vidc(void)
+{
+ if (probe_vidc(&cfg) == 0)
+ return -ENODEV;
+
+ attach_vidc(&cfg);
+
+ return 0;
+}
+
+static void __exit cleanup_vidc(void)
+{
+ unload_vidc(&cfg);
+}
+
+module_init(init_vidc);
+module_exit(cleanup_vidc);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("VIDC20 audio driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/vidc.h b/sound/oss/vidc.h
new file mode 100644
index 00000000..0d142475
--- /dev/null
+++ b/sound/oss/vidc.h
@@ -0,0 +1,63 @@
+/*
+ * linux/drivers/sound/vidc.h
+ *
+ * Copyright (C) 1997 Russell King <rmk@arm.linux.org.uk>
+ *
+ * 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.
+ *
+ * VIDC sound function prototypes
+ */
+
+/* vidc_fill.S */
+
+/*
+ * Filler routines for different channels and sample sizes
+ */
+
+extern unsigned long vidc_fill_1x8_u(unsigned long ibuf, unsigned long iend,
+ unsigned long obuf, int mask);
+extern unsigned long vidc_fill_2x8_u(unsigned long ibuf, unsigned long iend,
+ unsigned long obuf, int mask);
+extern unsigned long vidc_fill_1x8_s(unsigned long ibuf, unsigned long iend,
+ unsigned long obuf, int mask);
+extern unsigned long vidc_fill_2x8_s(unsigned long ibuf, unsigned long iend,
+ unsigned long obuf, int mask);
+extern unsigned long vidc_fill_1x16_s(unsigned long ibuf, unsigned long iend,
+ unsigned long obuf, int mask);
+extern unsigned long vidc_fill_2x16_s(unsigned long ibuf, unsigned long iend,
+ unsigned long obuf, int mask);
+
+/*
+ * DMA Interrupt handler
+ */
+
+extern irqreturn_t vidc_sound_dma_irq(int irqnr, void *ref);
+
+/*
+ * Filler routine pointer
+ */
+
+extern unsigned long (*vidc_filler) (unsigned long ibuf, unsigned long iend,
+ unsigned long obuf, int mask);
+
+/*
+ * Virtual DMA buffer exhausted
+ */
+
+extern irqreturn_t (*dma_interrupt) (void);
+
+/*
+ * Virtual DMA buffer addresses
+ */
+
+extern unsigned long dma_start, dma_count, dma_bufsize;
+extern unsigned long dma_buf[2], dma_pbuf[2];
+
+/* vidc_synth.c */
+
+extern void vidc_synth_init(struct address_info *hw_config);
+extern void vidc_synth_exit(struct address_info *hw_config);
+extern int vidc_synth_get_volume(void);
+extern int vidc_synth_set_volume(int vol);
diff --git a/sound/oss/vidc_fill.S b/sound/oss/vidc_fill.S
new file mode 100644
index 00000000..bed34921
--- /dev/null
+++ b/sound/oss/vidc_fill.S
@@ -0,0 +1,218 @@
+/*
+ * linux/drivers/sound/vidc_fill.S
+ *
+ * Copyright (C) 1997 Russell King
+ *
+ * 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.
+ *
+ * Filler routines for DMA buffers
+ */
+#include <linux/linkage.h>
+#include <asm/assembler.h>
+#include <mach/hardware.h>
+#include <asm/hardware/iomd.h>
+
+ .text
+
+ENTRY(vidc_fill_1x8_u)
+ mov ip, #0xff00
+1: cmp r0, r1
+ bge vidc_clear
+ ldrb r4, [r0], #1
+ eor r4, r4, #0x80
+ and r4, ip, r4, lsl #8
+ orr r4, r4, r4, lsl #16
+ str r4, [r2], #4
+ cmp r2, r3
+ blt 1b
+ mov pc, lr
+
+ENTRY(vidc_fill_2x8_u)
+ mov ip, #0xff00
+1: cmp r0, r1
+ bge vidc_clear
+ ldr r4, [r0], #2
+ and r5, r4, ip
+ and r4, ip, r4, lsl #8
+ orr r4, r4, r5, lsl #16
+ orr r4, r4, r4, lsr #8
+ str r4, [r2], #4
+ cmp r2, r3
+ blt 1b
+ mov pc, lr
+
+ENTRY(vidc_fill_1x8_s)
+ mov ip, #0xff00
+1: cmp r0, r1
+ bge vidc_clear
+ ldrb r4, [r0], #1
+ and r4, ip, r4, lsl #8
+ orr r4, r4, r4, lsl #16
+ str r4, [r2], #4
+ cmp r2, r3
+ blt 1b
+ mov pc, lr
+
+ENTRY(vidc_fill_2x8_s)
+ mov ip, #0xff00
+1: cmp r0, r1
+ bge vidc_clear
+ ldr r4, [r0], #2
+ and r5, r4, ip
+ and r4, ip, r4, lsl #8
+ orr r4, r4, r5, lsl #16
+ orr r4, r4, r4, lsr #8
+ str r4, [r2], #4
+ cmp r2, r3
+ blt 1b
+ mov pc, lr
+
+ENTRY(vidc_fill_1x16_s)
+ mov ip, #0xff00
+ orr ip, ip, ip, lsr #8
+1: cmp r0, r1
+ bge vidc_clear
+ ldr r5, [r0], #2
+ and r4, r5, ip
+ orr r4, r4, r4, lsl #16
+ str r4, [r2], #4
+ cmp r0, r1
+ addlt r0, r0, #2
+ andlt r4, r5, ip, lsl #16
+ orrlt r4, r4, r4, lsr #16
+ strlt r4, [r2], #4
+ cmp r2, r3
+ blt 1b
+ mov pc, lr
+
+ENTRY(vidc_fill_2x16_s)
+ mov ip, #0xff00
+ orr ip, ip, ip, lsr #8
+1: cmp r0, r1
+ bge vidc_clear
+ ldr r4, [r0], #4
+ str r4, [r2], #4
+ cmp r0, r1
+ ldrlt r4, [r0], #4
+ strlt r4, [r2], #4
+ cmp r2, r3
+ blt 1b
+ mov pc, lr
+
+ENTRY(vidc_fill_noaudio)
+ mov r0, #0
+ mov r1, #0
+2: mov r4, #0
+ mov r5, #0
+1: cmp r2, r3
+ stmltia r2!, {r0, r1, r4, r5}
+ blt 1b
+ mov pc, lr
+
+ENTRY(vidc_clear)
+ mov r0, #0
+ mov r1, #0
+ tst r2, #4
+ str r0, [r2], #4
+ tst r2, #8
+ stmia r2!, {r0, r1}
+ b 2b
+
+/*
+ * Call filler routines with:
+ * r0 = phys address
+ * r1 = phys end
+ * r2 = buffer
+ * Returns:
+ * r0 = new buffer address
+ * r2 = new buffer finish
+ * r4 = corrupted
+ * r5 = corrupted
+ * ip = corrupted
+ */
+
+ENTRY(vidc_sound_dma_irq)
+ stmfd sp!, {r4 - r8, lr}
+ ldr r8, =dma_start
+ ldmia r8, {r0, r1, r2, r3, r4, r5}
+ teq r1, #0
+ adreq r4, vidc_fill_noaudio
+ moveq r7, #1 << 31
+ movne r7, #0
+ mov ip, #IOMD_BASE & 0xff000000
+ orr ip, ip, #IOMD_BASE & 0x00ff0000
+ ldrb r6, [ip, #IOMD_SD0ST]
+ tst r6, #DMA_ST_OFL @ Check for overrun
+ eorne r6, r6, #DMA_ST_AB
+ tst r6, #DMA_ST_AB
+ moveq r2, r3 @ DMAing A, update B
+ add r3, r2, r5 @ End of DMA buffer
+ add r1, r1, r0 @ End of virtual DMA buffer
+ mov lr, pc
+ mov pc, r4 @ Call fill routine (uses r4, ip)
+ sub r1, r1, r0 @ Remaining length
+ stmia r8, {r0, r1}
+ mov r0, #0
+ tst r2, #4 @ Round buffer up to 4 words
+ strne r0, [r2], #4
+ tst r2, #8
+ strne r0, [r2], #4
+ strne r0, [r2], #4
+ sub r2, r2, #16
+ mov r2, r2, lsl #20
+ movs r2, r2, lsr #20
+ orreq r2, r2, #1 << 30 @ Set L bit
+ orr r2, r2, r7
+ ldmdb r8, {r3, r4, r5}
+ tst r6, #DMA_ST_AB
+ mov ip, #IOMD_BASE & 0xff000000
+ orr ip, ip, #IOMD_BASE & 0x00ff0000
+ streq r4, [ip, #IOMD_SD0CURB]
+ strne r5, [ip, #IOMD_SD0CURA]
+ streq r2, [ip, #IOMD_SD0ENDB]
+ strne r2, [ip, #IOMD_SD0ENDA]
+ ldr lr, [ip, #IOMD_SD0ST]
+ tst lr, #DMA_ST_OFL
+ bne 1f
+ tst r6, #DMA_ST_AB
+ strne r4, [ip, #IOMD_SD0CURB]
+ streq r5, [ip, #IOMD_SD0CURA]
+ strne r2, [ip, #IOMD_SD0ENDB]
+ streq r2, [ip, #IOMD_SD0ENDA]
+1: teq r7, #0
+ mov r0, #0x10
+ strneb r0, [ip, #IOMD_SD0CR]
+ ldmfd sp!, {r4 - r8, lr}
+ mov r0, #1 @ IRQ_HANDLED
+ teq r1, #0 @ If we have no more
+ movne pc, lr
+ teq r3, #0
+ movne pc, r3 @ Call interrupt routine
+ mov pc, lr
+
+ .data
+ .globl dma_interrupt
+dma_interrupt:
+ .long 0 @ r3
+ .globl dma_pbuf
+dma_pbuf:
+ .long 0 @ r4
+ .long 0 @ r5
+ .globl dma_start
+dma_start:
+ .long 0 @ r0
+ .globl dma_count
+dma_count:
+ .long 0 @ r1
+ .globl dma_buf
+dma_buf:
+ .long 0 @ r2
+ .long 0 @ r3
+ .globl vidc_filler
+vidc_filler:
+ .long vidc_fill_noaudio @ r4
+ .globl dma_bufsize
+dma_bufsize:
+ .long 0x1000 @ r5
diff --git a/sound/oss/vwsnd.c b/sound/oss/vwsnd.c
new file mode 100644
index 00000000..643f1113
--- /dev/null
+++ b/sound/oss/vwsnd.c
@@ -0,0 +1,3498 @@
+/*
+ * Sound driver for Silicon Graphics 320 and 540 Visual Workstations'
+ * onboard audio. See notes in Documentation/sound/oss/vwsnd .
+ *
+ * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#undef VWSND_DEBUG /* define for debugging */
+
+/*
+ * XXX to do -
+ *
+ * External sync.
+ * Rename swbuf, hwbuf, u&i, hwptr&swptr to something rational.
+ * Bug - if select() called before read(), pcm_setup() not called.
+ * Bug - output doesn't stop soon enough if process killed.
+ */
+
+/*
+ * Things to test -
+ *
+ * Will readv/writev work? Write a test.
+ *
+ * insmod/rmmod 100 million times.
+ *
+ * Run I/O until int ptrs wrap around (roughly 6.2 hours @ DAT
+ * rate).
+ *
+ * Concurrent threads banging on mixer simultaneously, both UP
+ * and SMP kernels. Especially, watch for thread A changing
+ * OUTSRC while thread B changes gain -- both write to the same
+ * ad1843 register.
+ *
+ * What happens if a client opens /dev/audio then forks?
+ * Do two procs have /dev/audio open? Test.
+ *
+ * Pump audio through the CD, MIC and line inputs and verify that
+ * they mix/mute into the output.
+ *
+ * Apps:
+ * amp
+ * mpg123
+ * x11amp
+ * mxv
+ * kmedia
+ * esound
+ * need more input apps
+ *
+ * Run tests while bombarding with signals. setitimer(2) will do it... */
+
+/*
+ * This driver is organized in nine sections.
+ * The nine sections are:
+ *
+ * debug stuff
+ * low level lithium access
+ * high level lithium access
+ * AD1843 access
+ * PCM I/O
+ * audio driver
+ * mixer driver
+ * probe/attach/unload
+ * initialization and loadable kernel module interface
+ *
+ * That is roughly the order of increasing abstraction, so forward
+ * dependencies are minimal.
+ */
+
+/*
+ * Locking Notes
+ *
+ * INC_USE_COUNT and DEC_USE_COUNT keep track of the number of
+ * open descriptors to this driver. They store it in vwsnd_use_count.
+ * The global device list, vwsnd_dev_list, is immutable when the IN_USE
+ * is true.
+ *
+ * devc->open_lock is a semaphore that is used to enforce the
+ * single reader/single writer rule for /dev/audio. The rule is
+ * that each device may have at most one reader and one writer.
+ * Open will block until the previous client has closed the
+ * device, unless O_NONBLOCK is specified.
+ *
+ * The semaphore devc->io_mutex serializes PCM I/O syscalls. This
+ * is unnecessary in Linux 2.2, because the kernel lock
+ * serializes read, write, and ioctl globally, but it's there,
+ * ready for the brave, new post-kernel-lock world.
+ *
+ * Locking between interrupt and baselevel is handled by the
+ * "lock" spinlock in vwsnd_port (one lock each for read and
+ * write). Each half holds the lock just long enough to see what
+ * area it owns and update its pointers. See pcm_output() and
+ * pcm_input() for most of the gory stuff.
+ *
+ * devc->mix_mutex serializes all mixer ioctls. This is also
+ * redundant because of the kernel lock.
+ *
+ * The lowest level lock is lith->lithium_lock. It is a
+ * spinlock which is held during the two-register tango of
+ * reading/writing an AD1843 register. See
+ * li_{read,write}_ad1843_reg().
+ */
+
+/*
+ * Sample Format Notes
+ *
+ * Lithium's DMA engine has two formats: 16-bit 2's complement
+ * and 8-bit unsigned . 16-bit transfers the data unmodified, 2
+ * bytes per sample. 8-bit unsigned transfers 1 byte per sample
+ * and XORs each byte with 0x80. Lithium can input or output
+ * either mono or stereo in either format.
+ *
+ * The AD1843 has four formats: 16-bit 2's complement, 8-bit
+ * unsigned, 8-bit mu-Law and 8-bit A-Law.
+ *
+ * This driver supports five formats: AFMT_S8, AFMT_U8,
+ * AFMT_MU_LAW, AFMT_A_LAW, and AFMT_S16_LE.
+ *
+ * For AFMT_U8 output, we keep the AD1843 in 16-bit mode, and
+ * rely on Lithium's XOR to translate between U8 and S8.
+ *
+ * For AFMT_S8, AFMT_MU_LAW and AFMT_A_LAW output, we have to XOR
+ * the 0x80 bit in software to compensate for Lithium's XOR.
+ * This happens in pcm_copy_{in,out}().
+ *
+ * Changes:
+ * 11-10-2000 Bartlomiej Zolnierkiewicz <bkz@linux-ide.org>
+ * Added some __init/__exit
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include <asm/visws/cobalt.h>
+
+#include "sound_config.h"
+
+/*****************************************************************************/
+/* debug stuff */
+
+#ifdef VWSND_DEBUG
+
+static DEFINE_MUTEX(vwsnd_mutex);
+static int shut_up = 1;
+
+/*
+ * dbgassert - called when an assertion fails.
+ */
+
+static void dbgassert(const char *fcn, int line, const char *expr)
+{
+ if (in_interrupt())
+ panic("ASSERTION FAILED IN INTERRUPT, %s:%s:%d %s\n",
+ __FILE__, fcn, line, expr);
+ else {
+ int x;
+ printk(KERN_ERR "ASSERTION FAILED, %s:%s:%d %s\n",
+ __FILE__, fcn, line, expr);
+ x = * (volatile int *) 0; /* force proc to exit */
+ }
+}
+
+/*
+ * Bunch of useful debug macros:
+ *
+ * ASSERT - print unless e nonzero (panic if in interrupt)
+ * DBGDO - include arbitrary code if debugging
+ * DBGX - debug print raw (w/o function name)
+ * DBGP - debug print w/ function name
+ * DBGE - debug print function entry
+ * DBGC - debug print function call
+ * DBGR - debug print function return
+ * DBGXV - debug print raw when verbose
+ * DBGPV - debug print when verbose
+ * DBGEV - debug print function entry when verbose
+ * DBGRV - debug print function return when verbose
+ */
+
+#define ASSERT(e) ((e) ? (void) 0 : dbgassert(__func__, __LINE__, #e))
+#define DBGDO(x) x
+#define DBGX(fmt, args...) (in_interrupt() ? 0 : printk(KERN_ERR fmt, ##args))
+#define DBGP(fmt, args...) (DBGX("%s: " fmt, __func__ , ##args))
+#define DBGE(fmt, args...) (DBGX("%s" fmt, __func__ , ##args))
+#define DBGC(rtn) (DBGP("calling %s\n", rtn))
+#define DBGR() (DBGP("returning\n"))
+#define DBGXV(fmt, args...) (shut_up ? 0 : DBGX(fmt, ##args))
+#define DBGPV(fmt, args...) (shut_up ? 0 : DBGP(fmt, ##args))
+#define DBGEV(fmt, args...) (shut_up ? 0 : DBGE(fmt, ##args))
+#define DBGCV(rtn) (shut_up ? 0 : DBGC(rtn))
+#define DBGRV() (shut_up ? 0 : DBGR())
+
+#else /* !VWSND_DEBUG */
+
+#define ASSERT(e) ((void) 0)
+#define DBGDO(x) /* don't */
+#define DBGX(fmt, args...) ((void) 0)
+#define DBGP(fmt, args...) ((void) 0)
+#define DBGE(fmt, args...) ((void) 0)
+#define DBGC(rtn) ((void) 0)
+#define DBGR() ((void) 0)
+#define DBGPV(fmt, args...) ((void) 0)
+#define DBGXV(fmt, args...) ((void) 0)
+#define DBGEV(fmt, args...) ((void) 0)
+#define DBGCV(rtn) ((void) 0)
+#define DBGRV() ((void) 0)
+
+#endif /* !VWSND_DEBUG */
+
+/*****************************************************************************/
+/* low level lithium access */
+
+/*
+ * We need to talk to Lithium registers on three pages. Here are
+ * the pages' offsets from the base address (0xFF001000).
+ */
+
+enum {
+ LI_PAGE0_OFFSET = 0x01000 - 0x1000, /* FF001000 */
+ LI_PAGE1_OFFSET = 0x0F000 - 0x1000, /* FF00F000 */
+ LI_PAGE2_OFFSET = 0x10000 - 0x1000, /* FF010000 */
+};
+
+/* low-level lithium data */
+
+typedef struct lithium {
+ void * page0; /* virtual addresses */
+ void * page1;
+ void * page2;
+ spinlock_t lock; /* protects codec and UST/MSC access */
+} lithium_t;
+
+/*
+ * li_destroy destroys the lithium_t structure and vm mappings.
+ */
+
+static void li_destroy(lithium_t *lith)
+{
+ if (lith->page0) {
+ iounmap(lith->page0);
+ lith->page0 = NULL;
+ }
+ if (lith->page1) {
+ iounmap(lith->page1);
+ lith->page1 = NULL;
+ }
+ if (lith->page2) {
+ iounmap(lith->page2);
+ lith->page2 = NULL;
+ }
+}
+
+/*
+ * li_create initializes the lithium_t structure and sets up vm mappings
+ * to access the registers.
+ * Returns 0 on success, -errno on failure.
+ */
+
+static int __init li_create(lithium_t *lith, unsigned long baseaddr)
+{
+ spin_lock_init(&lith->lock);
+ lith->page0 = ioremap_nocache(baseaddr + LI_PAGE0_OFFSET, PAGE_SIZE);
+ lith->page1 = ioremap_nocache(baseaddr + LI_PAGE1_OFFSET, PAGE_SIZE);
+ lith->page2 = ioremap_nocache(baseaddr + LI_PAGE2_OFFSET, PAGE_SIZE);
+ if (!lith->page0 || !lith->page1 || !lith->page2) {
+ li_destroy(lith);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/*
+ * basic register accessors - read/write long/byte
+ */
+
+static __inline__ unsigned long li_readl(lithium_t *lith, int off)
+{
+ return * (volatile unsigned long *) (lith->page0 + off);
+}
+
+static __inline__ unsigned char li_readb(lithium_t *lith, int off)
+{
+ return * (volatile unsigned char *) (lith->page0 + off);
+}
+
+static __inline__ void li_writel(lithium_t *lith, int off, unsigned long val)
+{
+ * (volatile unsigned long *) (lith->page0 + off) = val;
+}
+
+static __inline__ void li_writeb(lithium_t *lith, int off, unsigned char val)
+{
+ * (volatile unsigned char *) (lith->page0 + off) = val;
+}
+
+/*****************************************************************************/
+/* High Level Lithium Access */
+
+/*
+ * Lithium DMA Notes
+ *
+ * Lithium has two dedicated DMA channels for audio. They are known
+ * as comm1 and comm2 (communication areas 1 and 2). Comm1 is for
+ * input, and comm2 is for output. Each is controlled by three
+ * registers: BASE (base address), CFG (config) and CCTL
+ * (config/control).
+ *
+ * Each DMA channel points to a physically contiguous ring buffer in
+ * main memory of up to 8 Kbytes. (This driver always uses 8 Kb.)
+ * There are three pointers into the ring buffer: read, write, and
+ * trigger. The pointers are 8 bits each. Each pointer points to
+ * 32-byte "chunks" of data. The DMA engine moves 32 bytes at a time,
+ * so there is no finer-granularity control.
+ *
+ * In comm1, the hardware updates the write ptr, and software updates
+ * the read ptr. In comm2, it's the opposite: hardware updates the
+ * read ptr, and software updates the write ptr. I designate the
+ * hardware-updated ptr as the hwptr, and the software-updated ptr as
+ * the swptr.
+ *
+ * The trigger ptr and trigger mask are used to trigger interrupts.
+ * From the Lithium spec, section 5.6.8, revision of 12/15/1998:
+ *
+ * Trigger Mask Value
+ *
+ * A three bit wide field that represents a power of two mask
+ * that is used whenever the trigger pointer is compared to its
+ * respective read or write pointer. A value of zero here
+ * implies a mask of 0xFF and a value of seven implies a mask
+ * 0x01. This value can be used to sub-divide the ring buffer
+ * into pie sections so that interrupts monitor the progress of
+ * hardware from section to section.
+ *
+ * My interpretation of that is, whenever the hw ptr is updated, it is
+ * compared with the trigger ptr, and the result is masked by the
+ * trigger mask. (Actually, by the complement of the trigger mask.)
+ * If the result is zero, an interrupt is triggered. I.e., interrupt
+ * if ((hwptr & ~mask) == (trptr & ~mask)). The mask is formed from
+ * the trigger register value as mask = (1 << (8 - tmreg)) - 1.
+ *
+ * In yet different words, setting tmreg to 0 causes an interrupt after
+ * every 256 DMA chunks (8192 bytes) or once per traversal of the
+ * ring buffer. Setting it to 7 caues an interrupt every 2 DMA chunks
+ * (64 bytes) or 128 times per traversal of the ring buffer.
+ */
+
+/* Lithium register offsets and bit definitions */
+
+#define LI_HOST_CONTROLLER 0x000
+# define LI_HC_RESET 0x00008000
+# define LI_HC_LINK_ENABLE 0x00004000
+# define LI_HC_LINK_FAILURE 0x00000004
+# define LI_HC_LINK_CODEC 0x00000002
+# define LI_HC_LINK_READY 0x00000001
+
+#define LI_INTR_STATUS 0x010
+#define LI_INTR_MASK 0x014
+# define LI_INTR_LINK_ERR 0x00008000
+# define LI_INTR_COMM2_TRIG 0x00000008
+# define LI_INTR_COMM2_UNDERFLOW 0x00000004
+# define LI_INTR_COMM1_TRIG 0x00000002
+# define LI_INTR_COMM1_OVERFLOW 0x00000001
+
+#define LI_CODEC_COMMAND 0x018
+# define LI_CC_BUSY 0x00008000
+# define LI_CC_DIR 0x00000080
+# define LI_CC_DIR_RD LI_CC_DIR
+# define LI_CC_DIR_WR (!LI_CC_DIR)
+# define LI_CC_ADDR_MASK 0x0000007F
+
+#define LI_CODEC_DATA 0x01C
+
+#define LI_COMM1_BASE 0x100
+#define LI_COMM1_CTL 0x104
+# define LI_CCTL_RESET 0x80000000
+# define LI_CCTL_SIZE 0x70000000
+# define LI_CCTL_DMA_ENABLE 0x08000000
+# define LI_CCTL_TMASK 0x07000000 /* trigger mask */
+# define LI_CCTL_TPTR 0x00FF0000 /* trigger pointer */
+# define LI_CCTL_RPTR 0x0000FF00
+# define LI_CCTL_WPTR 0x000000FF
+#define LI_COMM1_CFG 0x108
+# define LI_CCFG_LOCK 0x00008000
+# define LI_CCFG_SLOT 0x00000070
+# define LI_CCFG_DIRECTION 0x00000008
+# define LI_CCFG_DIR_IN (!LI_CCFG_DIRECTION)
+# define LI_CCFG_DIR_OUT LI_CCFG_DIRECTION
+# define LI_CCFG_MODE 0x00000004
+# define LI_CCFG_MODE_MONO (!LI_CCFG_MODE)
+# define LI_CCFG_MODE_STEREO LI_CCFG_MODE
+# define LI_CCFG_FORMAT 0x00000003
+# define LI_CCFG_FMT_8BIT 0x00000000
+# define LI_CCFG_FMT_16BIT 0x00000001
+#define LI_COMM2_BASE 0x10C
+#define LI_COMM2_CTL 0x110
+ /* bit definitions are the same as LI_COMM1_CTL */
+#define LI_COMM2_CFG 0x114
+ /* bit definitions are the same as LI_COMM1_CFG */
+
+#define LI_UST_LOW 0x200 /* 64-bit Unadjusted System Time is */
+#define LI_UST_HIGH 0x204 /* microseconds since boot */
+
+#define LI_AUDIO1_UST 0x300 /* UST-MSC pairs */
+#define LI_AUDIO1_MSC 0x304 /* MSC (Media Stream Counter) */
+#define LI_AUDIO2_UST 0x308 /* counts samples actually */
+#define LI_AUDIO2_MSC 0x30C /* processed as of time UST */
+
+/*
+ * Lithium's DMA engine operates on chunks of 32 bytes. We call that
+ * a DMACHUNK.
+ */
+
+#define DMACHUNK_SHIFT 5
+#define DMACHUNK_SIZE (1 << DMACHUNK_SHIFT)
+#define BYTES_TO_CHUNKS(bytes) ((bytes) >> DMACHUNK_SHIFT)
+#define CHUNKS_TO_BYTES(chunks) ((chunks) << DMACHUNK_SHIFT)
+
+/*
+ * Two convenient macros to shift bitfields into/out of position.
+ *
+ * Observe that (mask & -mask) is (1 << low_set_bit_of(mask)).
+ * As long as mask is constant, we trust the compiler will change the
+ * multipy and divide into shifts.
+ */
+
+#define SHIFT_FIELD(val, mask) (((val) * ((mask) & -(mask))) & (mask))
+#define UNSHIFT_FIELD(val, mask) (((val) & (mask)) / ((mask) & -(mask)))
+
+/*
+ * dma_chan_desc is invariant information about a Lithium
+ * DMA channel. There are two instances, li_comm1 and li_comm2.
+ *
+ * Note that the CCTL register fields are write ptr and read ptr, but what
+ * we care about are which pointer is updated by software and which by
+ * hardware.
+ */
+
+typedef struct dma_chan_desc {
+ int basereg;
+ int cfgreg;
+ int ctlreg;
+ int hwptrreg;
+ int swptrreg;
+ int ustreg;
+ int mscreg;
+ unsigned long swptrmask;
+ int ad1843_slot;
+ int direction; /* LI_CCTL_DIR_IN/OUT */
+} dma_chan_desc_t;
+
+static const dma_chan_desc_t li_comm1 = {
+ LI_COMM1_BASE, /* base register offset */
+ LI_COMM1_CFG, /* config register offset */
+ LI_COMM1_CTL, /* control register offset */
+ LI_COMM1_CTL + 0, /* hw ptr reg offset (write ptr) */
+ LI_COMM1_CTL + 1, /* sw ptr reg offset (read ptr) */
+ LI_AUDIO1_UST, /* ust reg offset */
+ LI_AUDIO1_MSC, /* msc reg offset */
+ LI_CCTL_RPTR, /* sw ptr bitmask in ctlval */
+ 2, /* ad1843 serial slot */
+ LI_CCFG_DIR_IN /* direction */
+};
+
+static const dma_chan_desc_t li_comm2 = {
+ LI_COMM2_BASE, /* base register offset */
+ LI_COMM2_CFG, /* config register offset */
+ LI_COMM2_CTL, /* control register offset */
+ LI_COMM2_CTL + 1, /* hw ptr reg offset (read ptr) */
+ LI_COMM2_CTL + 0, /* sw ptr reg offset (writr ptr) */
+ LI_AUDIO2_UST, /* ust reg offset */
+ LI_AUDIO2_MSC, /* msc reg offset */
+ LI_CCTL_WPTR, /* sw ptr bitmask in ctlval */
+ 2, /* ad1843 serial slot */
+ LI_CCFG_DIR_OUT /* direction */
+};
+
+/*
+ * dma_chan is variable information about a Lithium DMA channel.
+ *
+ * The desc field points to invariant information.
+ * The lith field points to a lithium_t which is passed
+ * to li_read* and li_write* to access the registers.
+ * The *val fields shadow the lithium registers' contents.
+ */
+
+typedef struct dma_chan {
+ const dma_chan_desc_t *desc;
+ lithium_t *lith;
+ unsigned long baseval;
+ unsigned long cfgval;
+ unsigned long ctlval;
+} dma_chan_t;
+
+/*
+ * ustmsc is a UST/MSC pair (Unadjusted System Time/Media Stream Counter).
+ * UST is time in microseconds since the system booted, and MSC is a
+ * counter that increments with every audio sample.
+ */
+
+typedef struct ustmsc {
+ unsigned long long ust;
+ unsigned long msc;
+} ustmsc_t;
+
+/*
+ * li_ad1843_wait waits until lithium says the AD1843 register
+ * exchange is not busy. Returns 0 on success, -EBUSY on timeout.
+ *
+ * Locking: must be called with lithium_lock held.
+ */
+
+static int li_ad1843_wait(lithium_t *lith)
+{
+ unsigned long later = jiffies + 2;
+ while (li_readl(lith, LI_CODEC_COMMAND) & LI_CC_BUSY)
+ if (time_after_eq(jiffies, later))
+ return -EBUSY;
+ return 0;
+}
+
+/*
+ * li_read_ad1843_reg returns the current contents of a 16 bit AD1843 register.
+ *
+ * Returns unsigned register value on success, -errno on failure.
+ */
+
+static int li_read_ad1843_reg(lithium_t *lith, int reg)
+{
+ int val;
+
+ ASSERT(!in_interrupt());
+ spin_lock(&lith->lock);
+ {
+ val = li_ad1843_wait(lith);
+ if (val == 0) {
+ li_writel(lith, LI_CODEC_COMMAND, LI_CC_DIR_RD | reg);
+ val = li_ad1843_wait(lith);
+ }
+ if (val == 0)
+ val = li_readl(lith, LI_CODEC_DATA);
+ }
+ spin_unlock(&lith->lock);
+
+ DBGXV("li_read_ad1843_reg(lith=0x%p, reg=%d) returns 0x%04x\n",
+ lith, reg, val);
+
+ return val;
+}
+
+/*
+ * li_write_ad1843_reg writes the specified value to a 16 bit AD1843 register.
+ */
+
+static void li_write_ad1843_reg(lithium_t *lith, int reg, int newval)
+{
+ spin_lock(&lith->lock);
+ {
+ if (li_ad1843_wait(lith) == 0) {
+ li_writel(lith, LI_CODEC_DATA, newval);
+ li_writel(lith, LI_CODEC_COMMAND, LI_CC_DIR_WR | reg);
+ }
+ }
+ spin_unlock(&lith->lock);
+}
+
+/*
+ * li_setup_dma calculates all the register settings for DMA in a particular
+ * mode. It takes too many arguments.
+ */
+
+static void li_setup_dma(dma_chan_t *chan,
+ const dma_chan_desc_t *desc,
+ lithium_t *lith,
+ unsigned long buffer_paddr,
+ int bufshift,
+ int fragshift,
+ int channels,
+ int sampsize)
+{
+ unsigned long mode, format;
+ unsigned long size, tmask;
+
+ DBGEV("(chan=0x%p, desc=0x%p, lith=0x%p, buffer_paddr=0x%lx, "
+ "bufshift=%d, fragshift=%d, channels=%d, sampsize=%d)\n",
+ chan, desc, lith, buffer_paddr,
+ bufshift, fragshift, channels, sampsize);
+
+ /* Reset the channel first. */
+
+ li_writel(lith, desc->ctlreg, LI_CCTL_RESET);
+
+ ASSERT(channels == 1 || channels == 2);
+ if (channels == 2)
+ mode = LI_CCFG_MODE_STEREO;
+ else
+ mode = LI_CCFG_MODE_MONO;
+ ASSERT(sampsize == 1 || sampsize == 2);
+ if (sampsize == 2)
+ format = LI_CCFG_FMT_16BIT;
+ else
+ format = LI_CCFG_FMT_8BIT;
+ chan->desc = desc;
+ chan->lith = lith;
+
+ /*
+ * Lithium DMA address register takes a 40-bit physical
+ * address, right-shifted by 8 so it fits in 32 bits. Bit 37
+ * must be set -- it enables cache coherence.
+ */
+
+ ASSERT(!(buffer_paddr & 0xFF));
+ chan->baseval = (buffer_paddr >> 8) | 1 << (37 - 8);
+
+ chan->cfgval = ((chan->cfgval & ~LI_CCFG_LOCK) |
+ SHIFT_FIELD(desc->ad1843_slot, LI_CCFG_SLOT) |
+ desc->direction |
+ mode |
+ format);
+
+ size = bufshift - 6;
+ tmask = 13 - fragshift; /* See Lithium DMA Notes above. */
+ ASSERT(size >= 2 && size <= 7);
+ ASSERT(tmask >= 1 && tmask <= 7);
+ chan->ctlval = ((chan->ctlval & ~LI_CCTL_RESET) |
+ SHIFT_FIELD(size, LI_CCTL_SIZE) |
+ (chan->ctlval & ~LI_CCTL_DMA_ENABLE) |
+ SHIFT_FIELD(tmask, LI_CCTL_TMASK) |
+ SHIFT_FIELD(0, LI_CCTL_TPTR));
+
+ DBGPV("basereg 0x%x = 0x%lx\n", desc->basereg, chan->baseval);
+ DBGPV("cfgreg 0x%x = 0x%lx\n", desc->cfgreg, chan->cfgval);
+ DBGPV("ctlreg 0x%x = 0x%lx\n", desc->ctlreg, chan->ctlval);
+
+ li_writel(lith, desc->basereg, chan->baseval);
+ li_writel(lith, desc->cfgreg, chan->cfgval);
+ li_writel(lith, desc->ctlreg, chan->ctlval);
+
+ DBGRV();
+}
+
+static void li_shutdown_dma(dma_chan_t *chan)
+{
+ lithium_t *lith = chan->lith;
+ void * lith1 = lith->page1;
+
+ DBGEV("(chan=0x%p)\n", chan);
+
+ chan->ctlval &= ~LI_CCTL_DMA_ENABLE;
+ DBGPV("ctlreg 0x%x = 0x%lx\n", chan->desc->ctlreg, chan->ctlval);
+ li_writel(lith, chan->desc->ctlreg, chan->ctlval);
+
+ /*
+ * Offset 0x500 on Lithium page 1 is an undocumented,
+ * unsupported register that holds the zero sample value.
+ * Lithium is supposed to output zero samples when DMA is
+ * inactive, and repeat the last sample when DMA underflows.
+ * But it has a bug, where, after underflow occurs, the zero
+ * sample is not reset.
+ *
+ * I expect this to break in a future rev of Lithium.
+ */
+
+ if (lith1 && chan->desc->direction == LI_CCFG_DIR_OUT)
+ * (volatile unsigned long *) (lith1 + 0x500) = 0;
+}
+
+/*
+ * li_activate_dma always starts dma at the beginning of the buffer.
+ *
+ * N.B., these may be called from interrupt.
+ */
+
+static __inline__ void li_activate_dma(dma_chan_t *chan)
+{
+ chan->ctlval |= LI_CCTL_DMA_ENABLE;
+ DBGPV("ctlval = 0x%lx\n", chan->ctlval);
+ li_writel(chan->lith, chan->desc->ctlreg, chan->ctlval);
+}
+
+static void li_deactivate_dma(dma_chan_t *chan)
+{
+ lithium_t *lith = chan->lith;
+ void * lith2 = lith->page2;
+
+ chan->ctlval &= ~(LI_CCTL_DMA_ENABLE | LI_CCTL_RPTR | LI_CCTL_WPTR);
+ DBGPV("ctlval = 0x%lx\n", chan->ctlval);
+ DBGPV("ctlreg 0x%x = 0x%lx\n", chan->desc->ctlreg, chan->ctlval);
+ li_writel(lith, chan->desc->ctlreg, chan->ctlval);
+
+ /*
+ * Offsets 0x98 and 0x9C on Lithium page 2 are undocumented,
+ * unsupported registers that are internal copies of the DMA
+ * read and write pointers. Because of a Lithium bug, these
+ * registers aren't zeroed correctly when DMA is shut off. So
+ * we whack them directly.
+ *
+ * I expect this to break in a future rev of Lithium.
+ */
+
+ if (lith2 && chan->desc->direction == LI_CCFG_DIR_OUT) {
+ * (volatile unsigned long *) (lith2 + 0x98) = 0;
+ * (volatile unsigned long *) (lith2 + 0x9C) = 0;
+ }
+}
+
+/*
+ * read/write the ring buffer pointers. These routines' arguments and results
+ * are byte offsets from the beginning of the ring buffer.
+ */
+
+static __inline__ int li_read_swptr(dma_chan_t *chan)
+{
+ const unsigned long mask = chan->desc->swptrmask;
+
+ return CHUNKS_TO_BYTES(UNSHIFT_FIELD(chan->ctlval, mask));
+}
+
+static __inline__ int li_read_hwptr(dma_chan_t *chan)
+{
+ return CHUNKS_TO_BYTES(li_readb(chan->lith, chan->desc->hwptrreg));
+}
+
+static __inline__ void li_write_swptr(dma_chan_t *chan, int val)
+{
+ const unsigned long mask = chan->desc->swptrmask;
+
+ ASSERT(!(val & ~CHUNKS_TO_BYTES(0xFF)));
+ val = BYTES_TO_CHUNKS(val);
+ chan->ctlval = (chan->ctlval & ~mask) | SHIFT_FIELD(val, mask);
+ li_writeb(chan->lith, chan->desc->swptrreg, val);
+}
+
+/* li_read_USTMSC() returns a UST/MSC pair for the given channel. */
+
+static void li_read_USTMSC(dma_chan_t *chan, ustmsc_t *ustmsc)
+{
+ lithium_t *lith = chan->lith;
+ const dma_chan_desc_t *desc = chan->desc;
+ unsigned long now_low, now_high0, now_high1, chan_ust;
+
+ spin_lock(&lith->lock);
+ {
+ /*
+ * retry until we do all five reads without the
+ * high word changing. (High word increments
+ * every 2^32 microseconds, i.e., not often)
+ */
+ do {
+ now_high0 = li_readl(lith, LI_UST_HIGH);
+ now_low = li_readl(lith, LI_UST_LOW);
+
+ /*
+ * Lithium guarantees these two reads will be
+ * atomic -- ust will not increment after msc
+ * is read.
+ */
+
+ ustmsc->msc = li_readl(lith, desc->mscreg);
+ chan_ust = li_readl(lith, desc->ustreg);
+
+ now_high1 = li_readl(lith, LI_UST_HIGH);
+ } while (now_high0 != now_high1);
+ }
+ spin_unlock(&lith->lock);
+ ustmsc->ust = ((unsigned long long) now_high0 << 32 | chan_ust);
+}
+
+static void li_enable_interrupts(lithium_t *lith, unsigned int mask)
+{
+ DBGEV("(lith=0x%p, mask=0x%x)\n", lith, mask);
+
+ /* clear any already-pending interrupts. */
+
+ li_writel(lith, LI_INTR_STATUS, mask);
+
+ /* enable the interrupts. */
+
+ mask |= li_readl(lith, LI_INTR_MASK);
+ li_writel(lith, LI_INTR_MASK, mask);
+}
+
+static void li_disable_interrupts(lithium_t *lith, unsigned int mask)
+{
+ unsigned int keepmask;
+
+ DBGEV("(lith=0x%p, mask=0x%x)\n", lith, mask);
+
+ /* disable the interrupts */
+
+ keepmask = li_readl(lith, LI_INTR_MASK) & ~mask;
+ li_writel(lith, LI_INTR_MASK, keepmask);
+
+ /* clear any pending interrupts. */
+
+ li_writel(lith, LI_INTR_STATUS, mask);
+}
+
+/* Get the interrupt status and clear all pending interrupts. */
+
+static unsigned int li_get_clear_intr_status(lithium_t *lith)
+{
+ unsigned int status;
+
+ status = li_readl(lith, LI_INTR_STATUS);
+ li_writel(lith, LI_INTR_STATUS, ~0);
+ return status & li_readl(lith, LI_INTR_MASK);
+}
+
+static int li_init(lithium_t *lith)
+{
+ /* 1. System power supplies stabilize. */
+
+ /* 2. Assert the ~RESET signal. */
+
+ li_writel(lith, LI_HOST_CONTROLLER, LI_HC_RESET);
+ udelay(1);
+
+ /* 3. Deassert the ~RESET signal and enter a wait period to allow
+ the AD1843 internal clocks and the external crystal oscillator
+ to stabilize. */
+
+ li_writel(lith, LI_HOST_CONTROLLER, LI_HC_LINK_ENABLE);
+ udelay(1);
+
+ return 0;
+}
+
+/*****************************************************************************/
+/* AD1843 access */
+
+/*
+ * AD1843 bitfield definitions. All are named as in the AD1843 data
+ * sheet, with ad1843_ prepended and individual bit numbers removed.
+ *
+ * E.g., bits LSS0 through LSS2 become ad1843_LSS.
+ *
+ * Only the bitfields we need are defined.
+ */
+
+typedef struct ad1843_bitfield {
+ char reg;
+ char lo_bit;
+ char nbits;
+} ad1843_bitfield_t;
+
+static const ad1843_bitfield_t
+ ad1843_PDNO = { 0, 14, 1 }, /* Converter Power-Down Flag */
+ ad1843_INIT = { 0, 15, 1 }, /* Clock Initialization Flag */
+ ad1843_RIG = { 2, 0, 4 }, /* Right ADC Input Gain */
+ ad1843_RMGE = { 2, 4, 1 }, /* Right ADC Mic Gain Enable */
+ ad1843_RSS = { 2, 5, 3 }, /* Right ADC Source Select */
+ ad1843_LIG = { 2, 8, 4 }, /* Left ADC Input Gain */
+ ad1843_LMGE = { 2, 12, 1 }, /* Left ADC Mic Gain Enable */
+ ad1843_LSS = { 2, 13, 3 }, /* Left ADC Source Select */
+ ad1843_RX1M = { 4, 0, 5 }, /* Right Aux 1 Mix Gain/Atten */
+ ad1843_RX1MM = { 4, 7, 1 }, /* Right Aux 1 Mix Mute */
+ ad1843_LX1M = { 4, 8, 5 }, /* Left Aux 1 Mix Gain/Atten */
+ ad1843_LX1MM = { 4, 15, 1 }, /* Left Aux 1 Mix Mute */
+ ad1843_RX2M = { 5, 0, 5 }, /* Right Aux 2 Mix Gain/Atten */
+ ad1843_RX2MM = { 5, 7, 1 }, /* Right Aux 2 Mix Mute */
+ ad1843_LX2M = { 5, 8, 5 }, /* Left Aux 2 Mix Gain/Atten */
+ ad1843_LX2MM = { 5, 15, 1 }, /* Left Aux 2 Mix Mute */
+ ad1843_RMCM = { 7, 0, 5 }, /* Right Mic Mix Gain/Atten */
+ ad1843_RMCMM = { 7, 7, 1 }, /* Right Mic Mix Mute */
+ ad1843_LMCM = { 7, 8, 5 }, /* Left Mic Mix Gain/Atten */
+ ad1843_LMCMM = { 7, 15, 1 }, /* Left Mic Mix Mute */
+ ad1843_HPOS = { 8, 4, 1 }, /* Headphone Output Voltage Swing */
+ ad1843_HPOM = { 8, 5, 1 }, /* Headphone Output Mute */
+ ad1843_RDA1G = { 9, 0, 6 }, /* Right DAC1 Analog/Digital Gain */
+ ad1843_RDA1GM = { 9, 7, 1 }, /* Right DAC1 Analog Mute */
+ ad1843_LDA1G = { 9, 8, 6 }, /* Left DAC1 Analog/Digital Gain */
+ ad1843_LDA1GM = { 9, 15, 1 }, /* Left DAC1 Analog Mute */
+ ad1843_RDA1AM = { 11, 7, 1 }, /* Right DAC1 Digital Mute */
+ ad1843_LDA1AM = { 11, 15, 1 }, /* Left DAC1 Digital Mute */
+ ad1843_ADLC = { 15, 0, 2 }, /* ADC Left Sample Rate Source */
+ ad1843_ADRC = { 15, 2, 2 }, /* ADC Right Sample Rate Source */
+ ad1843_DA1C = { 15, 8, 2 }, /* DAC1 Sample Rate Source */
+ ad1843_C1C = { 17, 0, 16 }, /* Clock 1 Sample Rate Select */
+ ad1843_C2C = { 20, 0, 16 }, /* Clock 1 Sample Rate Select */
+ ad1843_DAADL = { 25, 4, 2 }, /* Digital ADC Left Source Select */
+ ad1843_DAADR = { 25, 6, 2 }, /* Digital ADC Right Source Select */
+ ad1843_DRSFLT = { 25, 15, 1 }, /* Digital Reampler Filter Mode */
+ ad1843_ADLF = { 26, 0, 2 }, /* ADC Left Channel Data Format */
+ ad1843_ADRF = { 26, 2, 2 }, /* ADC Right Channel Data Format */
+ ad1843_ADTLK = { 26, 4, 1 }, /* ADC Transmit Lock Mode Select */
+ ad1843_SCF = { 26, 7, 1 }, /* SCLK Frequency Select */
+ ad1843_DA1F = { 26, 8, 2 }, /* DAC1 Data Format Select */
+ ad1843_DA1SM = { 26, 14, 1 }, /* DAC1 Stereo/Mono Mode Select */
+ ad1843_ADLEN = { 27, 0, 1 }, /* ADC Left Channel Enable */
+ ad1843_ADREN = { 27, 1, 1 }, /* ADC Right Channel Enable */
+ ad1843_AAMEN = { 27, 4, 1 }, /* Analog to Analog Mix Enable */
+ ad1843_ANAEN = { 27, 7, 1 }, /* Analog Channel Enable */
+ ad1843_DA1EN = { 27, 8, 1 }, /* DAC1 Enable */
+ ad1843_DA2EN = { 27, 9, 1 }, /* DAC2 Enable */
+ ad1843_C1EN = { 28, 11, 1 }, /* Clock Generator 1 Enable */
+ ad1843_C2EN = { 28, 12, 1 }, /* Clock Generator 2 Enable */
+ ad1843_PDNI = { 28, 15, 1 }; /* Converter Power Down */
+
+/*
+ * The various registers of the AD1843 use three different formats for
+ * specifying gain. The ad1843_gain structure parameterizes the
+ * formats.
+ */
+
+typedef struct ad1843_gain {
+
+ int negative; /* nonzero if gain is negative. */
+ const ad1843_bitfield_t *lfield;
+ const ad1843_bitfield_t *rfield;
+
+} ad1843_gain_t;
+
+static const ad1843_gain_t ad1843_gain_RECLEV
+ = { 0, &ad1843_LIG, &ad1843_RIG };
+static const ad1843_gain_t ad1843_gain_LINE
+ = { 1, &ad1843_LX1M, &ad1843_RX1M };
+static const ad1843_gain_t ad1843_gain_CD
+ = { 1, &ad1843_LX2M, &ad1843_RX2M };
+static const ad1843_gain_t ad1843_gain_MIC
+ = { 1, &ad1843_LMCM, &ad1843_RMCM };
+static const ad1843_gain_t ad1843_gain_PCM
+ = { 1, &ad1843_LDA1G, &ad1843_RDA1G };
+
+/* read the current value of an AD1843 bitfield. */
+
+static int ad1843_read_bits(lithium_t *lith, const ad1843_bitfield_t *field)
+{
+ int w = li_read_ad1843_reg(lith, field->reg);
+ int val = w >> field->lo_bit & ((1 << field->nbits) - 1);
+
+ DBGXV("ad1843_read_bits(lith=0x%p, field->{%d %d %d}) returns 0x%x\n",
+ lith, field->reg, field->lo_bit, field->nbits, val);
+
+ return val;
+}
+
+/*
+ * write a new value to an AD1843 bitfield and return the old value.
+ */
+
+static int ad1843_write_bits(lithium_t *lith,
+ const ad1843_bitfield_t *field,
+ int newval)
+{
+ int w = li_read_ad1843_reg(lith, field->reg);
+ int mask = ((1 << field->nbits) - 1) << field->lo_bit;
+ int oldval = (w & mask) >> field->lo_bit;
+ int newbits = (newval << field->lo_bit) & mask;
+ w = (w & ~mask) | newbits;
+ (void) li_write_ad1843_reg(lith, field->reg, w);
+
+ DBGXV("ad1843_write_bits(lith=0x%p, field->{%d %d %d}, val=0x%x) "
+ "returns 0x%x\n",
+ lith, field->reg, field->lo_bit, field->nbits, newval,
+ oldval);
+
+ return oldval;
+}
+
+/*
+ * ad1843_read_multi reads multiple bitfields from the same AD1843
+ * register. It uses a single read cycle to do it. (Reading the
+ * ad1843 requires 256 bit times at 12.288 MHz, or nearly 20
+ * microseconds.)
+ *
+ * Called ike this.
+ *
+ * ad1843_read_multi(lith, nfields,
+ * &ad1843_FIELD1, &val1,
+ * &ad1843_FIELD2, &val2, ...);
+ */
+
+static void ad1843_read_multi(lithium_t *lith, int argcount, ...)
+{
+ va_list ap;
+ const ad1843_bitfield_t *fp;
+ int w = 0, mask, *value, reg = -1;
+
+ va_start(ap, argcount);
+ while (--argcount >= 0) {
+ fp = va_arg(ap, const ad1843_bitfield_t *);
+ value = va_arg(ap, int *);
+ if (reg == -1) {
+ reg = fp->reg;
+ w = li_read_ad1843_reg(lith, reg);
+ }
+ ASSERT(reg == fp->reg);
+ mask = (1 << fp->nbits) - 1;
+ *value = w >> fp->lo_bit & mask;
+ }
+ va_end(ap);
+}
+
+/*
+ * ad1843_write_multi stores multiple bitfields into the same AD1843
+ * register. It uses one read and one write cycle to do it.
+ *
+ * Called like this.
+ *
+ * ad1843_write_multi(lith, nfields,
+ * &ad1843_FIELD1, val1,
+ * &ad1843_FIELF2, val2, ...);
+ */
+
+static void ad1843_write_multi(lithium_t *lith, int argcount, ...)
+{
+ va_list ap;
+ int reg;
+ const ad1843_bitfield_t *fp;
+ int value;
+ int w, m, mask, bits;
+
+ mask = 0;
+ bits = 0;
+ reg = -1;
+
+ va_start(ap, argcount);
+ while (--argcount >= 0) {
+ fp = va_arg(ap, const ad1843_bitfield_t *);
+ value = va_arg(ap, int);
+ if (reg == -1)
+ reg = fp->reg;
+ ASSERT(fp->reg == reg);
+ m = ((1 << fp->nbits) - 1) << fp->lo_bit;
+ mask |= m;
+ bits |= (value << fp->lo_bit) & m;
+ }
+ va_end(ap);
+ ASSERT(!(bits & ~mask));
+ if (~mask & 0xFFFF)
+ w = li_read_ad1843_reg(lith, reg);
+ else
+ w = 0;
+ w = (w & ~mask) | bits;
+ (void) li_write_ad1843_reg(lith, reg, w);
+}
+
+/*
+ * ad1843_get_gain reads the specified register and extracts the gain value
+ * using the supplied gain type. It returns the gain in OSS format.
+ */
+
+static int ad1843_get_gain(lithium_t *lith, const ad1843_gain_t *gp)
+{
+ int lg, rg;
+ unsigned short mask = (1 << gp->lfield->nbits) - 1;
+
+ ad1843_read_multi(lith, 2, gp->lfield, &lg, gp->rfield, &rg);
+ if (gp->negative) {
+ lg = mask - lg;
+ rg = mask - rg;
+ }
+ lg = (lg * 100 + (mask >> 1)) / mask;
+ rg = (rg * 100 + (mask >> 1)) / mask;
+ return lg << 0 | rg << 8;
+}
+
+/*
+ * Set an audio channel's gain. Converts from OSS format to AD1843's
+ * format.
+ *
+ * Returns the new gain, which may be lower than the old gain.
+ */
+
+static int ad1843_set_gain(lithium_t *lith,
+ const ad1843_gain_t *gp,
+ int newval)
+{
+ unsigned short mask = (1 << gp->lfield->nbits) - 1;
+
+ int lg = newval >> 0 & 0xFF;
+ int rg = newval >> 8;
+ if (lg < 0 || lg > 100 || rg < 0 || rg > 100)
+ return -EINVAL;
+ lg = (lg * mask + (mask >> 1)) / 100;
+ rg = (rg * mask + (mask >> 1)) / 100;
+ if (gp->negative) {
+ lg = mask - lg;
+ rg = mask - rg;
+ }
+ ad1843_write_multi(lith, 2, gp->lfield, lg, gp->rfield, rg);
+ return ad1843_get_gain(lith, gp);
+}
+
+/* Returns the current recording source, in OSS format. */
+
+static int ad1843_get_recsrc(lithium_t *lith)
+{
+ int ls = ad1843_read_bits(lith, &ad1843_LSS);
+
+ switch (ls) {
+ case 1:
+ return SOUND_MASK_MIC;
+ case 2:
+ return SOUND_MASK_LINE;
+ case 3:
+ return SOUND_MASK_CD;
+ case 6:
+ return SOUND_MASK_PCM;
+ default:
+ ASSERT(0);
+ return -1;
+ }
+}
+
+/*
+ * Enable/disable digital resample mode in the AD1843.
+ *
+ * The AD1843 requires that ADL, ADR, DA1 and DA2 be powered down
+ * while switching modes. So we save DA1's state (DA2's state is not
+ * interesting), power them down, switch into/out of resample mode,
+ * power them up, and restore state.
+ *
+ * This will cause audible glitches if D/A or A/D is going on, so the
+ * driver disallows that (in mixer_write_ioctl()).
+ *
+ * The open question is, is this worth doing? I'm leaving it in,
+ * because it's written, but...
+ */
+
+static void ad1843_set_resample_mode(lithium_t *lith, int onoff)
+{
+ /* Save DA1 mute and gain (addr 9 is DA1 analog gain/attenuation) */
+ int save_da1 = li_read_ad1843_reg(lith, 9);
+
+ /* Power down A/D and D/A. */
+ ad1843_write_multi(lith, 4,
+ &ad1843_DA1EN, 0,
+ &ad1843_DA2EN, 0,
+ &ad1843_ADLEN, 0,
+ &ad1843_ADREN, 0);
+
+ /* Switch mode */
+ ASSERT(onoff == 0 || onoff == 1);
+ ad1843_write_bits(lith, &ad1843_DRSFLT, onoff);
+
+ /* Power up A/D and D/A. */
+ ad1843_write_multi(lith, 3,
+ &ad1843_DA1EN, 1,
+ &ad1843_ADLEN, 1,
+ &ad1843_ADREN, 1);
+
+ /* Restore DA1 mute and gain. */
+ li_write_ad1843_reg(lith, 9, save_da1);
+}
+
+/*
+ * Set recording source. Arg newsrc specifies an OSS channel mask.
+ *
+ * The complication is that when we switch into/out of loopback mode
+ * (i.e., src = SOUND_MASK_PCM), we change the AD1843 into/out of
+ * digital resampling mode.
+ *
+ * Returns newsrc on success, -errno on failure.
+ */
+
+static int ad1843_set_recsrc(lithium_t *lith, int newsrc)
+{
+ int bits;
+ int oldbits;
+
+ switch (newsrc) {
+ case SOUND_MASK_PCM:
+ bits = 6;
+ break;
+
+ case SOUND_MASK_MIC:
+ bits = 1;
+ break;
+
+ case SOUND_MASK_LINE:
+ bits = 2;
+ break;
+
+ case SOUND_MASK_CD:
+ bits = 3;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ oldbits = ad1843_read_bits(lith, &ad1843_LSS);
+ if (newsrc == SOUND_MASK_PCM && oldbits != 6) {
+ DBGP("enabling digital resample mode\n");
+ ad1843_set_resample_mode(lith, 1);
+ ad1843_write_multi(lith, 2,
+ &ad1843_DAADL, 2,
+ &ad1843_DAADR, 2);
+ } else if (newsrc != SOUND_MASK_PCM && oldbits == 6) {
+ DBGP("disabling digital resample mode\n");
+ ad1843_set_resample_mode(lith, 0);
+ ad1843_write_multi(lith, 2,
+ &ad1843_DAADL, 0,
+ &ad1843_DAADR, 0);
+ }
+ ad1843_write_multi(lith, 2, &ad1843_LSS, bits, &ad1843_RSS, bits);
+ return newsrc;
+}
+
+/*
+ * Return current output sources, in OSS format.
+ */
+
+static int ad1843_get_outsrc(lithium_t *lith)
+{
+ int pcm, line, mic, cd;
+
+ pcm = ad1843_read_bits(lith, &ad1843_LDA1GM) ? 0 : SOUND_MASK_PCM;
+ line = ad1843_read_bits(lith, &ad1843_LX1MM) ? 0 : SOUND_MASK_LINE;
+ cd = ad1843_read_bits(lith, &ad1843_LX2MM) ? 0 : SOUND_MASK_CD;
+ mic = ad1843_read_bits(lith, &ad1843_LMCMM) ? 0 : SOUND_MASK_MIC;
+
+ return pcm | line | cd | mic;
+}
+
+/*
+ * Set output sources. Arg is a mask of active sources in OSS format.
+ *
+ * Returns source mask on success, -errno on failure.
+ */
+
+static int ad1843_set_outsrc(lithium_t *lith, int mask)
+{
+ int pcm, line, mic, cd;
+
+ if (mask & ~(SOUND_MASK_PCM | SOUND_MASK_LINE |
+ SOUND_MASK_CD | SOUND_MASK_MIC))
+ return -EINVAL;
+ pcm = (mask & SOUND_MASK_PCM) ? 0 : 1;
+ line = (mask & SOUND_MASK_LINE) ? 0 : 1;
+ mic = (mask & SOUND_MASK_MIC) ? 0 : 1;
+ cd = (mask & SOUND_MASK_CD) ? 0 : 1;
+
+ ad1843_write_multi(lith, 2, &ad1843_LDA1GM, pcm, &ad1843_RDA1GM, pcm);
+ ad1843_write_multi(lith, 2, &ad1843_LX1MM, line, &ad1843_RX1MM, line);
+ ad1843_write_multi(lith, 2, &ad1843_LX2MM, cd, &ad1843_RX2MM, cd);
+ ad1843_write_multi(lith, 2, &ad1843_LMCMM, mic, &ad1843_RMCMM, mic);
+
+ return mask;
+}
+
+/* Setup ad1843 for D/A conversion. */
+
+static void ad1843_setup_dac(lithium_t *lith,
+ int framerate,
+ int fmt,
+ int channels)
+{
+ int ad_fmt = 0, ad_mode = 0;
+
+ DBGEV("(lith=0x%p, framerate=%d, fmt=%d, channels=%d)\n",
+ lith, framerate, fmt, channels);
+
+ switch (fmt) {
+ case AFMT_S8: ad_fmt = 1; break;
+ case AFMT_U8: ad_fmt = 1; break;
+ case AFMT_S16_LE: ad_fmt = 1; break;
+ case AFMT_MU_LAW: ad_fmt = 2; break;
+ case AFMT_A_LAW: ad_fmt = 3; break;
+ default: ASSERT(0);
+ }
+
+ switch (channels) {
+ case 2: ad_mode = 0; break;
+ case 1: ad_mode = 1; break;
+ default: ASSERT(0);
+ }
+
+ DBGPV("ad_mode = %d, ad_fmt = %d\n", ad_mode, ad_fmt);
+ ASSERT(framerate >= 4000 && framerate <= 49000);
+ ad1843_write_bits(lith, &ad1843_C1C, framerate);
+ ad1843_write_multi(lith, 2,
+ &ad1843_DA1SM, ad_mode, &ad1843_DA1F, ad_fmt);
+}
+
+static void ad1843_shutdown_dac(lithium_t *lith)
+{
+ ad1843_write_bits(lith, &ad1843_DA1F, 1);
+}
+
+static void ad1843_setup_adc(lithium_t *lith, int framerate, int fmt, int channels)
+{
+ int da_fmt = 0;
+
+ DBGEV("(lith=0x%p, framerate=%d, fmt=%d, channels=%d)\n",
+ lith, framerate, fmt, channels);
+
+ switch (fmt) {
+ case AFMT_S8: da_fmt = 1; break;
+ case AFMT_U8: da_fmt = 1; break;
+ case AFMT_S16_LE: da_fmt = 1; break;
+ case AFMT_MU_LAW: da_fmt = 2; break;
+ case AFMT_A_LAW: da_fmt = 3; break;
+ default: ASSERT(0);
+ }
+
+ DBGPV("da_fmt = %d\n", da_fmt);
+ ASSERT(framerate >= 4000 && framerate <= 49000);
+ ad1843_write_bits(lith, &ad1843_C2C, framerate);
+ ad1843_write_multi(lith, 2,
+ &ad1843_ADLF, da_fmt, &ad1843_ADRF, da_fmt);
+}
+
+static void ad1843_shutdown_adc(lithium_t *lith)
+{
+ /* nothing to do */
+}
+
+/*
+ * Fully initialize the ad1843. As described in the AD1843 data
+ * sheet, section "START-UP SEQUENCE". The numbered comments are
+ * subsection headings from the data sheet. See the data sheet, pages
+ * 52-54, for more info.
+ *
+ * return 0 on success, -errno on failure. */
+
+static int __init ad1843_init(lithium_t *lith)
+{
+ unsigned long later;
+ int err;
+
+ err = li_init(lith);
+ if (err)
+ return err;
+
+ if (ad1843_read_bits(lith, &ad1843_INIT) != 0) {
+ printk(KERN_ERR "vwsnd sound: AD1843 won't initialize\n");
+ return -EIO;
+ }
+
+ ad1843_write_bits(lith, &ad1843_SCF, 1);
+
+ /* 4. Put the conversion resources into standby. */
+
+ ad1843_write_bits(lith, &ad1843_PDNI, 0);
+ later = jiffies + HZ / 2; /* roughly half a second */
+ DBGDO(shut_up++);
+ while (ad1843_read_bits(lith, &ad1843_PDNO)) {
+ if (time_after(jiffies, later)) {
+ printk(KERN_ERR
+ "vwsnd audio: AD1843 won't power up\n");
+ return -EIO;
+ }
+ schedule();
+ }
+ DBGDO(shut_up--);
+
+ /* 5. Power up the clock generators and enable clock output pins. */
+
+ ad1843_write_multi(lith, 2, &ad1843_C1EN, 1, &ad1843_C2EN, 1);
+
+ /* 6. Configure conversion resources while they are in standby. */
+
+ /* DAC1 uses clock 1 as source, ADC uses clock 2. Always. */
+
+ ad1843_write_multi(lith, 3,
+ &ad1843_DA1C, 1,
+ &ad1843_ADLC, 2,
+ &ad1843_ADRC, 2);
+
+ /* 7. Enable conversion resources. */
+
+ ad1843_write_bits(lith, &ad1843_ADTLK, 1);
+ ad1843_write_multi(lith, 5,
+ &ad1843_ANAEN, 1,
+ &ad1843_AAMEN, 1,
+ &ad1843_DA1EN, 1,
+ &ad1843_ADLEN, 1,
+ &ad1843_ADREN, 1);
+
+ /* 8. Configure conversion resources while they are enabled. */
+
+ ad1843_write_bits(lith, &ad1843_DA1C, 1);
+
+ /* Unmute all channels. */
+
+ ad1843_set_outsrc(lith,
+ (SOUND_MASK_PCM | SOUND_MASK_LINE |
+ SOUND_MASK_MIC | SOUND_MASK_CD));
+ ad1843_write_multi(lith, 2, &ad1843_LDA1AM, 0, &ad1843_RDA1AM, 0);
+
+ /* Set default recording source to Line In and set
+ * mic gain to +20 dB.
+ */
+
+ ad1843_set_recsrc(lith, SOUND_MASK_LINE);
+ ad1843_write_multi(lith, 2, &ad1843_LMGE, 1, &ad1843_RMGE, 1);
+
+ /* Set Speaker Out level to +/- 4V and unmute it. */
+
+ ad1843_write_multi(lith, 2, &ad1843_HPOS, 1, &ad1843_HPOM, 0);
+
+ return 0;
+}
+
+/*****************************************************************************/
+/* PCM I/O */
+
+#define READ_INTR_MASK (LI_INTR_COMM1_TRIG | LI_INTR_COMM1_OVERFLOW)
+#define WRITE_INTR_MASK (LI_INTR_COMM2_TRIG | LI_INTR_COMM2_UNDERFLOW)
+
+typedef enum vwsnd_port_swstate { /* software state */
+ SW_OFF,
+ SW_INITIAL,
+ SW_RUN,
+ SW_DRAIN,
+} vwsnd_port_swstate_t;
+
+typedef enum vwsnd_port_hwstate { /* hardware state */
+ HW_STOPPED,
+ HW_RUNNING,
+} vwsnd_port_hwstate_t;
+
+/*
+ * These flags are read by ISR, but only written at baseline.
+ */
+
+typedef enum vwsnd_port_flags {
+ DISABLED = 1 << 0,
+ ERFLOWN = 1 << 1, /* overflown or underflown */
+ HW_BUSY = 1 << 2,
+} vwsnd_port_flags_t;
+
+/*
+ * vwsnd_port is the per-port data structure. Each device has two
+ * ports, one for input and one for output.
+ *
+ * Locking:
+ *
+ * port->lock protects: hwstate, flags, swb_[iu]_avail.
+ *
+ * devc->io_mutex protects: swstate, sw_*, swb_[iu]_idx.
+ *
+ * everything else is only written by open/release or
+ * pcm_{setup,shutdown}(), which are serialized by a
+ * combination of devc->open_mutex and devc->io_mutex.
+ */
+
+typedef struct vwsnd_port {
+
+ spinlock_t lock;
+ wait_queue_head_t queue;
+ vwsnd_port_swstate_t swstate;
+ vwsnd_port_hwstate_t hwstate;
+ vwsnd_port_flags_t flags;
+
+ int sw_channels;
+ int sw_samplefmt;
+ int sw_framerate;
+ int sample_size;
+ int frame_size;
+ unsigned int zero_word; /* zero for the sample format */
+
+ int sw_fragshift;
+ int sw_fragcount;
+ int sw_subdivshift;
+
+ unsigned int hw_fragshift;
+ unsigned int hw_fragsize;
+ unsigned int hw_fragcount;
+
+ int hwbuf_size;
+ unsigned long hwbuf_paddr;
+ unsigned long hwbuf_vaddr;
+ void * hwbuf; /* hwbuf == hwbuf_vaddr */
+ int hwbuf_max; /* max bytes to preload */
+
+ void * swbuf;
+ unsigned int swbuf_size; /* size in bytes */
+ unsigned int swb_u_idx; /* index of next user byte */
+ unsigned int swb_i_idx; /* index of next intr byte */
+ unsigned int swb_u_avail; /* # bytes avail to user */
+ unsigned int swb_i_avail; /* # bytes avail to intr */
+
+ dma_chan_t chan;
+
+ /* Accounting */
+
+ int byte_count;
+ int frag_count;
+ int MSC_offset;
+
+} vwsnd_port_t;
+
+/* vwsnd_dev is the per-device data structure. */
+
+typedef struct vwsnd_dev {
+ struct vwsnd_dev *next_dev;
+ int audio_minor; /* minor number of audio device */
+ int mixer_minor; /* minor number of mixer device */
+
+ struct mutex open_mutex;
+ struct mutex io_mutex;
+ struct mutex mix_mutex;
+ fmode_t open_mode;
+ wait_queue_head_t open_wait;
+
+ lithium_t lith;
+
+ vwsnd_port_t rport;
+ vwsnd_port_t wport;
+} vwsnd_dev_t;
+
+static vwsnd_dev_t *vwsnd_dev_list; /* linked list of all devices */
+
+static atomic_t vwsnd_use_count = ATOMIC_INIT(0);
+
+# define INC_USE_COUNT (atomic_inc(&vwsnd_use_count))
+# define DEC_USE_COUNT (atomic_dec(&vwsnd_use_count))
+# define IN_USE (atomic_read(&vwsnd_use_count) != 0)
+
+/*
+ * Lithium can only DMA multiples of 32 bytes. Its DMA buffer may
+ * be up to 8 Kb. This driver always uses 8 Kb.
+ *
+ * Memory bug workaround -- I'm not sure what's going on here, but
+ * somehow pcm_copy_out() was triggering segv's going on to the next
+ * page of the hw buffer. So, I make the hw buffer one size bigger
+ * than we actually use. That way, the following page is allocated
+ * and mapped, and no error. I suspect that something is broken
+ * in Cobalt, but haven't really investigated. HBO is the actual
+ * size of the buffer, and HWBUF_ORDER is what we allocate.
+ */
+
+#define HWBUF_SHIFT 13
+#define HWBUF_SIZE (1 << HWBUF_SHIFT)
+# define HBO (HWBUF_SHIFT > PAGE_SHIFT ? HWBUF_SHIFT - PAGE_SHIFT : 0)
+# define HWBUF_ORDER (HBO + 1) /* next size bigger */
+#define MIN_SPEED 4000
+#define MAX_SPEED 49000
+
+#define MIN_FRAGSHIFT (DMACHUNK_SHIFT + 1)
+#define MAX_FRAGSHIFT (PAGE_SHIFT)
+#define MIN_FRAGSIZE (1 << MIN_FRAGSHIFT)
+#define MAX_FRAGSIZE (1 << MAX_FRAGSHIFT)
+#define MIN_FRAGCOUNT(fragsize) 3
+#define MAX_FRAGCOUNT(fragsize) (32 * PAGE_SIZE / (fragsize))
+#define DEFAULT_FRAGSHIFT 12
+#define DEFAULT_FRAGCOUNT 16
+#define DEFAULT_SUBDIVSHIFT 0
+
+/*
+ * The software buffer (swbuf) is a ring buffer shared between user
+ * level and interrupt level. Each level owns some of the bytes in
+ * the buffer, and may give bytes away by calling swb_inc_{u,i}().
+ * User level calls _u for user, and interrupt level calls _i for
+ * interrupt.
+ *
+ * port->swb_{u,i}_avail is the number of bytes available to that level.
+ *
+ * port->swb_{u,i}_idx is the index of the first available byte in the
+ * buffer.
+ *
+ * Each level calls swb_inc_{u,i}() to atomically increment its index,
+ * recalculate the number of bytes available for both sides, and
+ * return the number of bytes available. Since each side can only
+ * give away bytes, the other side can only increase the number of
+ * bytes available to this side. Each side updates its own index
+ * variable, swb_{u,i}_idx, so no lock is needed to read it.
+ *
+ * To query the number of bytes available, call swb_inc_{u,i} with an
+ * increment of zero.
+ */
+
+static __inline__ unsigned int __swb_inc_u(vwsnd_port_t *port, int inc)
+{
+ if (inc) {
+ port->swb_u_idx += inc;
+ port->swb_u_idx %= port->swbuf_size;
+ port->swb_u_avail -= inc;
+ port->swb_i_avail += inc;
+ }
+ return port->swb_u_avail;
+}
+
+static __inline__ unsigned int swb_inc_u(vwsnd_port_t *port, int inc)
+{
+ unsigned long flags;
+ unsigned int ret;
+
+ spin_lock_irqsave(&port->lock, flags);
+ {
+ ret = __swb_inc_u(port, inc);
+ }
+ spin_unlock_irqrestore(&port->lock, flags);
+ return ret;
+}
+
+static __inline__ unsigned int __swb_inc_i(vwsnd_port_t *port, int inc)
+{
+ if (inc) {
+ port->swb_i_idx += inc;
+ port->swb_i_idx %= port->swbuf_size;
+ port->swb_i_avail -= inc;
+ port->swb_u_avail += inc;
+ }
+ return port->swb_i_avail;
+}
+
+static __inline__ unsigned int swb_inc_i(vwsnd_port_t *port, int inc)
+{
+ unsigned long flags;
+ unsigned int ret;
+
+ spin_lock_irqsave(&port->lock, flags);
+ {
+ ret = __swb_inc_i(port, inc);
+ }
+ spin_unlock_irqrestore(&port->lock, flags);
+ return ret;
+}
+
+/*
+ * pcm_setup - this routine initializes all port state after
+ * mode-setting ioctls have been done, but before the first I/O is
+ * done.
+ *
+ * Locking: called with devc->io_mutex held.
+ *
+ * Returns 0 on success, -errno on failure.
+ */
+
+static int pcm_setup(vwsnd_dev_t *devc,
+ vwsnd_port_t *rport,
+ vwsnd_port_t *wport)
+{
+ vwsnd_port_t *aport = rport ? rport : wport;
+ int sample_size;
+ unsigned int zero_word;
+
+ DBGEV("(devc=0x%p, rport=0x%p, wport=0x%p)\n", devc, rport, wport);
+
+ ASSERT(aport != NULL);
+ if (aport->swbuf != NULL)
+ return 0;
+ switch (aport->sw_samplefmt) {
+ case AFMT_MU_LAW:
+ sample_size = 1;
+ zero_word = 0xFFFFFFFF ^ 0x80808080;
+ break;
+
+ case AFMT_A_LAW:
+ sample_size = 1;
+ zero_word = 0xD5D5D5D5 ^ 0x80808080;
+ break;
+
+ case AFMT_U8:
+ sample_size = 1;
+ zero_word = 0x80808080;
+ break;
+
+ case AFMT_S8:
+ sample_size = 1;
+ zero_word = 0x00000000;
+ break;
+
+ case AFMT_S16_LE:
+ sample_size = 2;
+ zero_word = 0x00000000;
+ break;
+
+ default:
+ sample_size = 0; /* prevent compiler warning */
+ zero_word = 0;
+ ASSERT(0);
+ }
+ aport->sample_size = sample_size;
+ aport->zero_word = zero_word;
+ aport->frame_size = aport->sw_channels * aport->sample_size;
+ aport->hw_fragshift = aport->sw_fragshift - aport->sw_subdivshift;
+ aport->hw_fragsize = 1 << aport->hw_fragshift;
+ aport->hw_fragcount = aport->sw_fragcount << aport->sw_subdivshift;
+ ASSERT(aport->hw_fragsize >= MIN_FRAGSIZE);
+ ASSERT(aport->hw_fragsize <= MAX_FRAGSIZE);
+ ASSERT(aport->hw_fragcount >= MIN_FRAGCOUNT(aport->hw_fragsize));
+ ASSERT(aport->hw_fragcount <= MAX_FRAGCOUNT(aport->hw_fragsize));
+ if (rport) {
+ int hwfrags, swfrags;
+ rport->hwbuf_max = aport->hwbuf_size - DMACHUNK_SIZE;
+ hwfrags = rport->hwbuf_max >> aport->hw_fragshift;
+ swfrags = aport->hw_fragcount - hwfrags;
+ if (swfrags < 2)
+ swfrags = 2;
+ rport->swbuf_size = swfrags * aport->hw_fragsize;
+ DBGPV("hwfrags = %d, swfrags = %d\n", hwfrags, swfrags);
+ DBGPV("read hwbuf_max = %d, swbuf_size = %d\n",
+ rport->hwbuf_max, rport->swbuf_size);
+ }
+ if (wport) {
+ int hwfrags, swfrags;
+ int total_bytes = aport->hw_fragcount * aport->hw_fragsize;
+ wport->hwbuf_max = aport->hwbuf_size - DMACHUNK_SIZE;
+ if (wport->hwbuf_max > total_bytes)
+ wport->hwbuf_max = total_bytes;
+ hwfrags = wport->hwbuf_max >> aport->hw_fragshift;
+ DBGPV("hwfrags = %d\n", hwfrags);
+ swfrags = aport->hw_fragcount - hwfrags;
+ if (swfrags < 2)
+ swfrags = 2;
+ wport->swbuf_size = swfrags * aport->hw_fragsize;
+ DBGPV("hwfrags = %d, swfrags = %d\n", hwfrags, swfrags);
+ DBGPV("write hwbuf_max = %d, swbuf_size = %d\n",
+ wport->hwbuf_max, wport->swbuf_size);
+ }
+
+ aport->swb_u_idx = 0;
+ aport->swb_i_idx = 0;
+ aport->byte_count = 0;
+
+ /*
+ * Is this a Cobalt bug? We need to make this buffer extend
+ * one page further than we actually use -- somehow memcpy
+ * causes an exceptoin otherwise. I suspect there's a bug in
+ * Cobalt (or somewhere) where it's generating a fault on a
+ * speculative load or something. Obviously, I haven't taken
+ * the time to track it down.
+ */
+
+ aport->swbuf = vmalloc(aport->swbuf_size + PAGE_SIZE);
+ if (!aport->swbuf)
+ return -ENOMEM;
+ if (rport && wport) {
+ ASSERT(aport == rport);
+ ASSERT(wport->swbuf == NULL);
+ /* One extra page - see comment above. */
+ wport->swbuf = vmalloc(aport->swbuf_size + PAGE_SIZE);
+ if (!wport->swbuf) {
+ vfree(aport->swbuf);
+ aport->swbuf = NULL;
+ return -ENOMEM;
+ }
+ wport->sample_size = rport->sample_size;
+ wport->zero_word = rport->zero_word;
+ wport->frame_size = rport->frame_size;
+ wport->hw_fragshift = rport->hw_fragshift;
+ wport->hw_fragsize = rport->hw_fragsize;
+ wport->hw_fragcount = rport->hw_fragcount;
+ wport->swbuf_size = rport->swbuf_size;
+ wport->hwbuf_max = rport->hwbuf_max;
+ wport->swb_u_idx = rport->swb_u_idx;
+ wport->swb_i_idx = rport->swb_i_idx;
+ wport->byte_count = rport->byte_count;
+ }
+ if (rport) {
+ rport->swb_u_avail = 0;
+ rport->swb_i_avail = rport->swbuf_size;
+ rport->swstate = SW_RUN;
+ li_setup_dma(&rport->chan,
+ &li_comm1,
+ &devc->lith,
+ rport->hwbuf_paddr,
+ HWBUF_SHIFT,
+ rport->hw_fragshift,
+ rport->sw_channels,
+ rport->sample_size);
+ ad1843_setup_adc(&devc->lith,
+ rport->sw_framerate,
+ rport->sw_samplefmt,
+ rport->sw_channels);
+ li_enable_interrupts(&devc->lith, READ_INTR_MASK);
+ if (!(rport->flags & DISABLED)) {
+ ustmsc_t ustmsc;
+ rport->hwstate = HW_RUNNING;
+ li_activate_dma(&rport->chan);
+ li_read_USTMSC(&rport->chan, &ustmsc);
+ rport->MSC_offset = ustmsc.msc;
+ }
+ }
+ if (wport) {
+ if (wport->hwbuf_max > wport->swbuf_size)
+ wport->hwbuf_max = wport->swbuf_size;
+ wport->flags &= ~ERFLOWN;
+ wport->swb_u_avail = wport->swbuf_size;
+ wport->swb_i_avail = 0;
+ wport->swstate = SW_RUN;
+ li_setup_dma(&wport->chan,
+ &li_comm2,
+ &devc->lith,
+ wport->hwbuf_paddr,
+ HWBUF_SHIFT,
+ wport->hw_fragshift,
+ wport->sw_channels,
+ wport->sample_size);
+ ad1843_setup_dac(&devc->lith,
+ wport->sw_framerate,
+ wport->sw_samplefmt,
+ wport->sw_channels);
+ li_enable_interrupts(&devc->lith, WRITE_INTR_MASK);
+ }
+ DBGRV();
+ return 0;
+}
+
+/*
+ * pcm_shutdown_port - shut down one port (direction) for PCM I/O.
+ * Only called from pcm_shutdown.
+ */
+
+static void pcm_shutdown_port(vwsnd_dev_t *devc,
+ vwsnd_port_t *aport,
+ unsigned int mask)
+{
+ unsigned long flags;
+ vwsnd_port_hwstate_t hwstate;
+ DECLARE_WAITQUEUE(wait, current);
+
+ aport->swstate = SW_INITIAL;
+ add_wait_queue(&aport->queue, &wait);
+ while (1) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ spin_lock_irqsave(&aport->lock, flags);
+ {
+ hwstate = aport->hwstate;
+ }
+ spin_unlock_irqrestore(&aport->lock, flags);
+ if (hwstate == HW_STOPPED)
+ break;
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&aport->queue, &wait);
+ li_disable_interrupts(&devc->lith, mask);
+ if (aport == &devc->rport)
+ ad1843_shutdown_adc(&devc->lith);
+ else /* aport == &devc->wport) */
+ ad1843_shutdown_dac(&devc->lith);
+ li_shutdown_dma(&aport->chan);
+ vfree(aport->swbuf);
+ aport->swbuf = NULL;
+ aport->byte_count = 0;
+}
+
+/*
+ * pcm_shutdown undoes what pcm_setup did.
+ * Also sets the ports' swstate to newstate.
+ */
+
+static void pcm_shutdown(vwsnd_dev_t *devc,
+ vwsnd_port_t *rport,
+ vwsnd_port_t *wport)
+{
+ DBGEV("(devc=0x%p, rport=0x%p, wport=0x%p)\n", devc, rport, wport);
+
+ if (rport && rport->swbuf) {
+ DBGPV("shutting down rport\n");
+ pcm_shutdown_port(devc, rport, READ_INTR_MASK);
+ }
+ if (wport && wport->swbuf) {
+ DBGPV("shutting down wport\n");
+ pcm_shutdown_port(devc, wport, WRITE_INTR_MASK);
+ }
+ DBGRV();
+}
+
+static void pcm_copy_in(vwsnd_port_t *rport, int swidx, int hwidx, int nb)
+{
+ char *src = rport->hwbuf + hwidx;
+ char *dst = rport->swbuf + swidx;
+ int fmt = rport->sw_samplefmt;
+
+ DBGPV("swidx = %d, hwidx = %d\n", swidx, hwidx);
+ ASSERT(rport->hwbuf != NULL);
+ ASSERT(rport->swbuf != NULL);
+ ASSERT(nb > 0 && (nb % 32) == 0);
+ ASSERT(swidx % 32 == 0 && hwidx % 32 == 0);
+ ASSERT(swidx >= 0 && swidx + nb <= rport->swbuf_size);
+ ASSERT(hwidx >= 0 && hwidx + nb <= rport->hwbuf_size);
+
+ if (fmt == AFMT_MU_LAW || fmt == AFMT_A_LAW || fmt == AFMT_S8) {
+
+ /* See Sample Format Notes above. */
+
+ char *end = src + nb;
+ while (src < end)
+ *dst++ = *src++ ^ 0x80;
+ } else
+ memcpy(dst, src, nb);
+}
+
+static void pcm_copy_out(vwsnd_port_t *wport, int swidx, int hwidx, int nb)
+{
+ char *src = wport->swbuf + swidx;
+ char *dst = wport->hwbuf + hwidx;
+ int fmt = wport->sw_samplefmt;
+
+ ASSERT(nb > 0 && (nb % 32) == 0);
+ ASSERT(wport->hwbuf != NULL);
+ ASSERT(wport->swbuf != NULL);
+ ASSERT(swidx % 32 == 0 && hwidx % 32 == 0);
+ ASSERT(swidx >= 0 && swidx + nb <= wport->swbuf_size);
+ ASSERT(hwidx >= 0 && hwidx + nb <= wport->hwbuf_size);
+ if (fmt == AFMT_MU_LAW || fmt == AFMT_A_LAW || fmt == AFMT_S8) {
+
+ /* See Sample Format Notes above. */
+
+ char *end = src + nb;
+ while (src < end)
+ *dst++ = *src++ ^ 0x80;
+ } else
+ memcpy(dst, src, nb);
+}
+
+/*
+ * pcm_output() is called both from baselevel and from interrupt level.
+ * This is where audio frames are copied into the hardware-accessible
+ * ring buffer.
+ *
+ * Locking note: The part of this routine that figures out what to do
+ * holds wport->lock. The longer part releases wport->lock, but sets
+ * wport->flags & HW_BUSY. Afterward, it reacquires wport->lock, and
+ * checks for more work to do.
+ *
+ * If another thread calls pcm_output() while HW_BUSY is set, it
+ * returns immediately, knowing that the thread that set HW_BUSY will
+ * look for more work to do before returning.
+ *
+ * This has the advantage that port->lock is held for several short
+ * periods instead of one long period. Also, when pcm_output is
+ * called from base level, it reenables interrupts.
+ */
+
+static void pcm_output(vwsnd_dev_t *devc, int erflown, int nb)
+{
+ vwsnd_port_t *wport = &devc->wport;
+ const int hwmax = wport->hwbuf_max;
+ const int hwsize = wport->hwbuf_size;
+ const int swsize = wport->swbuf_size;
+ const int fragsize = wport->hw_fragsize;
+ unsigned long iflags;
+
+ DBGEV("(devc=0x%p, erflown=%d, nb=%d)\n", devc, erflown, nb);
+ spin_lock_irqsave(&wport->lock, iflags);
+ if (erflown)
+ wport->flags |= ERFLOWN;
+ (void) __swb_inc_u(wport, nb);
+ if (wport->flags & HW_BUSY) {
+ spin_unlock_irqrestore(&wport->lock, iflags);
+ DBGPV("returning: HW BUSY\n");
+ return;
+ }
+ if (wport->flags & DISABLED) {
+ spin_unlock_irqrestore(&wport->lock, iflags);
+ DBGPV("returning: DISABLED\n");
+ return;
+ }
+ wport->flags |= HW_BUSY;
+ while (1) {
+ int swptr, hwptr, hw_avail, sw_avail, swidx;
+ vwsnd_port_hwstate_t hwstate = wport->hwstate;
+ vwsnd_port_swstate_t swstate = wport->swstate;
+ int hw_unavail;
+ ustmsc_t ustmsc;
+
+ hwptr = li_read_hwptr(&wport->chan);
+ swptr = li_read_swptr(&wport->chan);
+ hw_unavail = (swptr - hwptr + hwsize) % hwsize;
+ hw_avail = (hwmax - hw_unavail) & -fragsize;
+ sw_avail = wport->swb_i_avail & -fragsize;
+ if (sw_avail && swstate == SW_RUN) {
+ if (wport->flags & ERFLOWN) {
+ wport->flags &= ~ERFLOWN;
+ }
+ } else if (swstate == SW_INITIAL ||
+ swstate == SW_OFF ||
+ (swstate == SW_DRAIN &&
+ !sw_avail &&
+ (wport->flags & ERFLOWN))) {
+ DBGP("stopping. hwstate = %d\n", hwstate);
+ if (hwstate != HW_STOPPED) {
+ li_deactivate_dma(&wport->chan);
+ wport->hwstate = HW_STOPPED;
+ }
+ wake_up(&wport->queue);
+ break;
+ }
+ if (!sw_avail || !hw_avail)
+ break;
+ spin_unlock_irqrestore(&wport->lock, iflags);
+
+ /*
+ * We gave up the port lock, but we have the HW_BUSY flag.
+ * Proceed without accessing any nonlocal state.
+ * Do not exit the loop -- must check for more work.
+ */
+
+ swidx = wport->swb_i_idx;
+ nb = hw_avail;
+ if (nb > sw_avail)
+ nb = sw_avail;
+ if (nb > hwsize - swptr)
+ nb = hwsize - swptr; /* don't overflow hwbuf */
+ if (nb > swsize - swidx)
+ nb = swsize - swidx; /* don't overflow swbuf */
+ ASSERT(nb > 0);
+ if (nb % fragsize) {
+ DBGP("nb = %d, fragsize = %d\n", nb, fragsize);
+ DBGP("hw_avail = %d\n", hw_avail);
+ DBGP("sw_avail = %d\n", sw_avail);
+ DBGP("hwsize = %d, swptr = %d\n", hwsize, swptr);
+ DBGP("swsize = %d, swidx = %d\n", swsize, swidx);
+ }
+ ASSERT(!(nb % fragsize));
+ DBGPV("copying swb[%d..%d] to hwb[%d..%d]\n",
+ swidx, swidx + nb, swptr, swptr + nb);
+ pcm_copy_out(wport, swidx, swptr, nb);
+ li_write_swptr(&wport->chan, (swptr + nb) % hwsize);
+ spin_lock_irqsave(&wport->lock, iflags);
+ if (hwstate == HW_STOPPED) {
+ DBGPV("starting\n");
+ li_activate_dma(&wport->chan);
+ wport->hwstate = HW_RUNNING;
+ li_read_USTMSC(&wport->chan, &ustmsc);
+ ASSERT(wport->byte_count % wport->frame_size == 0);
+ wport->MSC_offset = ustmsc.msc - wport->byte_count / wport->frame_size;
+ }
+ __swb_inc_i(wport, nb);
+ wport->byte_count += nb;
+ wport->frag_count += nb / fragsize;
+ ASSERT(nb % fragsize == 0);
+ wake_up(&wport->queue);
+ }
+ wport->flags &= ~HW_BUSY;
+ spin_unlock_irqrestore(&wport->lock, iflags);
+ DBGRV();
+}
+
+/*
+ * pcm_input() is called both from baselevel and from interrupt level.
+ * This is where audio frames are copied out of the hardware-accessible
+ * ring buffer.
+ *
+ * Locking note: The part of this routine that figures out what to do
+ * holds rport->lock. The longer part releases rport->lock, but sets
+ * rport->flags & HW_BUSY. Afterward, it reacquires rport->lock, and
+ * checks for more work to do.
+ *
+ * If another thread calls pcm_input() while HW_BUSY is set, it
+ * returns immediately, knowing that the thread that set HW_BUSY will
+ * look for more work to do before returning.
+ *
+ * This has the advantage that port->lock is held for several short
+ * periods instead of one long period. Also, when pcm_input is
+ * called from base level, it reenables interrupts.
+ */
+
+static void pcm_input(vwsnd_dev_t *devc, int erflown, int nb)
+{
+ vwsnd_port_t *rport = &devc->rport;
+ const int hwmax = rport->hwbuf_max;
+ const int hwsize = rport->hwbuf_size;
+ const int swsize = rport->swbuf_size;
+ const int fragsize = rport->hw_fragsize;
+ unsigned long iflags;
+
+ DBGEV("(devc=0x%p, erflown=%d, nb=%d)\n", devc, erflown, nb);
+
+ spin_lock_irqsave(&rport->lock, iflags);
+ if (erflown)
+ rport->flags |= ERFLOWN;
+ (void) __swb_inc_u(rport, nb);
+ if (rport->flags & HW_BUSY || !rport->swbuf) {
+ spin_unlock_irqrestore(&rport->lock, iflags);
+ DBGPV("returning: HW BUSY or !swbuf\n");
+ return;
+ }
+ if (rport->flags & DISABLED) {
+ spin_unlock_irqrestore(&rport->lock, iflags);
+ DBGPV("returning: DISABLED\n");
+ return;
+ }
+ rport->flags |= HW_BUSY;
+ while (1) {
+ int swptr, hwptr, hw_avail, sw_avail, swidx;
+ vwsnd_port_hwstate_t hwstate = rport->hwstate;
+ vwsnd_port_swstate_t swstate = rport->swstate;
+
+ hwptr = li_read_hwptr(&rport->chan);
+ swptr = li_read_swptr(&rport->chan);
+ hw_avail = (hwptr - swptr + hwsize) % hwsize & -fragsize;
+ if (hw_avail > hwmax)
+ hw_avail = hwmax;
+ sw_avail = rport->swb_i_avail & -fragsize;
+ if (swstate != SW_RUN) {
+ DBGP("stopping. hwstate = %d\n", hwstate);
+ if (hwstate != HW_STOPPED) {
+ li_deactivate_dma(&rport->chan);
+ rport->hwstate = HW_STOPPED;
+ }
+ wake_up(&rport->queue);
+ break;
+ }
+ if (!sw_avail || !hw_avail)
+ break;
+ spin_unlock_irqrestore(&rport->lock, iflags);
+
+ /*
+ * We gave up the port lock, but we have the HW_BUSY flag.
+ * Proceed without accessing any nonlocal state.
+ * Do not exit the loop -- must check for more work.
+ */
+
+ swidx = rport->swb_i_idx;
+ nb = hw_avail;
+ if (nb > sw_avail)
+ nb = sw_avail;
+ if (nb > hwsize - swptr)
+ nb = hwsize - swptr; /* don't overflow hwbuf */
+ if (nb > swsize - swidx)
+ nb = swsize - swidx; /* don't overflow swbuf */
+ ASSERT(nb > 0);
+ if (nb % fragsize) {
+ DBGP("nb = %d, fragsize = %d\n", nb, fragsize);
+ DBGP("hw_avail = %d\n", hw_avail);
+ DBGP("sw_avail = %d\n", sw_avail);
+ DBGP("hwsize = %d, swptr = %d\n", hwsize, swptr);
+ DBGP("swsize = %d, swidx = %d\n", swsize, swidx);
+ }
+ ASSERT(!(nb % fragsize));
+ DBGPV("copying hwb[%d..%d] to swb[%d..%d]\n",
+ swptr, swptr + nb, swidx, swidx + nb);
+ pcm_copy_in(rport, swidx, swptr, nb);
+ li_write_swptr(&rport->chan, (swptr + nb) % hwsize);
+ spin_lock_irqsave(&rport->lock, iflags);
+ __swb_inc_i(rport, nb);
+ rport->byte_count += nb;
+ rport->frag_count += nb / fragsize;
+ ASSERT(nb % fragsize == 0);
+ wake_up(&rport->queue);
+ }
+ rport->flags &= ~HW_BUSY;
+ spin_unlock_irqrestore(&rport->lock, iflags);
+ DBGRV();
+}
+
+/*
+ * pcm_flush_frag() writes zero samples to fill the current fragment,
+ * then flushes it to the hardware.
+ *
+ * It is only meaningful to flush output, not input.
+ */
+
+static void pcm_flush_frag(vwsnd_dev_t *devc)
+{
+ vwsnd_port_t *wport = &devc->wport;
+
+ DBGPV("swstate = %d\n", wport->swstate);
+ if (wport->swstate == SW_RUN) {
+ int idx = wport->swb_u_idx;
+ int end = (idx + wport->hw_fragsize - 1)
+ >> wport->hw_fragshift
+ << wport->hw_fragshift;
+ int nb = end - idx;
+ DBGPV("clearing %d bytes\n", nb);
+ if (nb)
+ memset(wport->swbuf + idx,
+ (char) wport->zero_word,
+ nb);
+ wport->swstate = SW_DRAIN;
+ pcm_output(devc, 0, nb);
+ }
+ DBGRV();
+}
+
+/*
+ * Wait for output to drain. This sleeps uninterruptibly because
+ * there is nothing intelligent we can do if interrupted. This
+ * means the process will be delayed in responding to the signal.
+ */
+
+static void pcm_write_sync(vwsnd_dev_t *devc)
+{
+ vwsnd_port_t *wport = &devc->wport;
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long flags;
+ vwsnd_port_hwstate_t hwstate;
+
+ DBGEV("(devc=0x%p)\n", devc);
+ add_wait_queue(&wport->queue, &wait);
+ while (1) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ spin_lock_irqsave(&wport->lock, flags);
+ {
+ hwstate = wport->hwstate;
+ }
+ spin_unlock_irqrestore(&wport->lock, flags);
+ if (hwstate == HW_STOPPED)
+ break;
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&wport->queue, &wait);
+ DBGPV("swstate = %d, hwstate = %d\n", wport->swstate, wport->hwstate);
+ DBGRV();
+}
+
+/*****************************************************************************/
+/* audio driver */
+
+/*
+ * seek on an audio device always fails.
+ */
+
+static void vwsnd_audio_read_intr(vwsnd_dev_t *devc, unsigned int status)
+{
+ int overflown = status & LI_INTR_COMM1_OVERFLOW;
+
+ if (status & READ_INTR_MASK)
+ pcm_input(devc, overflown, 0);
+}
+
+static void vwsnd_audio_write_intr(vwsnd_dev_t *devc, unsigned int status)
+{
+ int underflown = status & LI_INTR_COMM2_UNDERFLOW;
+
+ if (status & WRITE_INTR_MASK)
+ pcm_output(devc, underflown, 0);
+}
+
+static irqreturn_t vwsnd_audio_intr(int irq, void *dev_id)
+{
+ vwsnd_dev_t *devc = dev_id;
+ unsigned int status;
+
+ DBGEV("(irq=%d, dev_id=0x%p)\n", irq, dev_id);
+
+ status = li_get_clear_intr_status(&devc->lith);
+ vwsnd_audio_read_intr(devc, status);
+ vwsnd_audio_write_intr(devc, status);
+ return IRQ_HANDLED;
+}
+
+static ssize_t vwsnd_audio_do_read(struct file *file,
+ char *buffer,
+ size_t count,
+ loff_t *ppos)
+{
+ vwsnd_dev_t *devc = file->private_data;
+ vwsnd_port_t *rport = ((file->f_mode & FMODE_READ) ?
+ &devc->rport : NULL);
+ int ret, nb;
+
+ DBGEV("(file=0x%p, buffer=0x%p, count=%d, ppos=0x%p)\n",
+ file, buffer, count, ppos);
+
+ if (!rport)
+ return -EINVAL;
+
+ if (rport->swbuf == NULL) {
+ vwsnd_port_t *wport = (file->f_mode & FMODE_WRITE) ?
+ &devc->wport : NULL;
+ ret = pcm_setup(devc, rport, wport);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (!access_ok(VERIFY_READ, buffer, count))
+ return -EFAULT;
+ ret = 0;
+ while (count) {
+ DECLARE_WAITQUEUE(wait, current);
+ add_wait_queue(&rport->queue, &wait);
+ while ((nb = swb_inc_u(rport, 0)) == 0) {
+ DBGPV("blocking\n");
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (rport->flags & DISABLED ||
+ file->f_flags & O_NONBLOCK) {
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&rport->queue, &wait);
+ return ret ? ret : -EAGAIN;
+ }
+ schedule();
+ if (signal_pending(current)) {
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&rport->queue, &wait);
+ return ret ? ret : -ERESTARTSYS;
+ }
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&rport->queue, &wait);
+ pcm_input(devc, 0, 0);
+ /* nb bytes are available in userbuf. */
+ if (nb > count)
+ nb = count;
+ DBGPV("nb = %d\n", nb);
+ if (copy_to_user(buffer, rport->swbuf + rport->swb_u_idx, nb))
+ return -EFAULT;
+ (void) swb_inc_u(rport, nb);
+ buffer += nb;
+ count -= nb;
+ ret += nb;
+ }
+ DBGPV("returning %d\n", ret);
+ return ret;
+}
+
+static ssize_t vwsnd_audio_read(struct file *file,
+ char *buffer,
+ size_t count,
+ loff_t *ppos)
+{
+ vwsnd_dev_t *devc = file->private_data;
+ ssize_t ret;
+
+ mutex_lock(&devc->io_mutex);
+ ret = vwsnd_audio_do_read(file, buffer, count, ppos);
+ mutex_unlock(&devc->io_mutex);
+ return ret;
+}
+
+static ssize_t vwsnd_audio_do_write(struct file *file,
+ const char *buffer,
+ size_t count,
+ loff_t *ppos)
+{
+ vwsnd_dev_t *devc = file->private_data;
+ vwsnd_port_t *wport = ((file->f_mode & FMODE_WRITE) ?
+ &devc->wport : NULL);
+ int ret, nb;
+
+ DBGEV("(file=0x%p, buffer=0x%p, count=%d, ppos=0x%p)\n",
+ file, buffer, count, ppos);
+
+ if (!wport)
+ return -EINVAL;
+
+ if (wport->swbuf == NULL) {
+ vwsnd_port_t *rport = (file->f_mode & FMODE_READ) ?
+ &devc->rport : NULL;
+ ret = pcm_setup(devc, rport, wport);
+ if (ret < 0)
+ return ret;
+ }
+ if (!access_ok(VERIFY_WRITE, buffer, count))
+ return -EFAULT;
+ ret = 0;
+ while (count) {
+ DECLARE_WAITQUEUE(wait, current);
+ add_wait_queue(&wport->queue, &wait);
+ while ((nb = swb_inc_u(wport, 0)) == 0) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (wport->flags & DISABLED ||
+ file->f_flags & O_NONBLOCK) {
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&wport->queue, &wait);
+ return ret ? ret : -EAGAIN;
+ }
+ schedule();
+ if (signal_pending(current)) {
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&wport->queue, &wait);
+ return ret ? ret : -ERESTARTSYS;
+ }
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&wport->queue, &wait);
+ /* nb bytes are available in userbuf. */
+ if (nb > count)
+ nb = count;
+ DBGPV("nb = %d\n", nb);
+ if (copy_from_user(wport->swbuf + wport->swb_u_idx, buffer, nb))
+ return -EFAULT;
+ pcm_output(devc, 0, nb);
+ buffer += nb;
+ count -= nb;
+ ret += nb;
+ }
+ DBGPV("returning %d\n", ret);
+ return ret;
+}
+
+static ssize_t vwsnd_audio_write(struct file *file,
+ const char *buffer,
+ size_t count,
+ loff_t *ppos)
+{
+ vwsnd_dev_t *devc = file->private_data;
+ ssize_t ret;
+
+ mutex_lock(&devc->io_mutex);
+ ret = vwsnd_audio_do_write(file, buffer, count, ppos);
+ mutex_unlock(&devc->io_mutex);
+ return ret;
+}
+
+/* No kernel lock - fine */
+static unsigned int vwsnd_audio_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ vwsnd_dev_t *devc = (vwsnd_dev_t *) file->private_data;
+ vwsnd_port_t *rport = (file->f_mode & FMODE_READ) ?
+ &devc->rport : NULL;
+ vwsnd_port_t *wport = (file->f_mode & FMODE_WRITE) ?
+ &devc->wport : NULL;
+ unsigned int mask = 0;
+
+ DBGEV("(file=0x%p, wait=0x%p)\n", file, wait);
+
+ ASSERT(rport || wport);
+ if (rport) {
+ poll_wait(file, &rport->queue, wait);
+ if (swb_inc_u(rport, 0))
+ mask |= (POLLIN | POLLRDNORM);
+ }
+ if (wport) {
+ poll_wait(file, &wport->queue, wait);
+ if (wport->swbuf == NULL || swb_inc_u(wport, 0))
+ mask |= (POLLOUT | POLLWRNORM);
+ }
+
+ DBGPV("returning 0x%x\n", mask);
+ return mask;
+}
+
+static int vwsnd_audio_do_ioctl(struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ vwsnd_dev_t *devc = (vwsnd_dev_t *) file->private_data;
+ vwsnd_port_t *rport = (file->f_mode & FMODE_READ) ?
+ &devc->rport : NULL;
+ vwsnd_port_t *wport = (file->f_mode & FMODE_WRITE) ?
+ &devc->wport : NULL;
+ vwsnd_port_t *aport = rport ? rport : wport;
+ struct audio_buf_info buf_info;
+ struct count_info info;
+ unsigned long flags;
+ int ival;
+
+
+ DBGEV("(file=0x%p, cmd=0x%x, arg=0x%lx)\n",
+ file, cmd, arg);
+ switch (cmd) {
+ case OSS_GETVERSION: /* _SIOR ('M', 118, int) */
+ DBGX("OSS_GETVERSION\n");
+ ival = SOUND_VERSION;
+ return put_user(ival, (int *) arg);
+
+ case SNDCTL_DSP_GETCAPS: /* _SIOR ('P',15, int) */
+ DBGX("SNDCTL_DSP_GETCAPS\n");
+ ival = DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER;
+ return put_user(ival, (int *) arg);
+
+ case SNDCTL_DSP_GETFMTS: /* _SIOR ('P',11, int) */
+ DBGX("SNDCTL_DSP_GETFMTS\n");
+ ival = (AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW |
+ AFMT_U8 | AFMT_S8);
+ return put_user(ival, (int *) arg);
+ break;
+
+ case SOUND_PCM_READ_RATE: /* _SIOR ('P', 2, int) */
+ DBGX("SOUND_PCM_READ_RATE\n");
+ ival = aport->sw_framerate;
+ return put_user(ival, (int *) arg);
+
+ case SOUND_PCM_READ_CHANNELS: /* _SIOR ('P', 6, int) */
+ DBGX("SOUND_PCM_READ_CHANNELS\n");
+ ival = aport->sw_channels;
+ return put_user(ival, (int *) arg);
+
+ case SNDCTL_DSP_SPEED: /* _SIOWR('P', 2, int) */
+ if (get_user(ival, (int *) arg))
+ return -EFAULT;
+ DBGX("SNDCTL_DSP_SPEED %d\n", ival);
+ if (ival) {
+ if (aport->swstate != SW_INITIAL) {
+ DBGX("SNDCTL_DSP_SPEED failed: swstate = %d\n",
+ aport->swstate);
+ return -EINVAL;
+ }
+ if (ival < MIN_SPEED)
+ ival = MIN_SPEED;
+ if (ival > MAX_SPEED)
+ ival = MAX_SPEED;
+ if (rport)
+ rport->sw_framerate = ival;
+ if (wport)
+ wport->sw_framerate = ival;
+ } else
+ ival = aport->sw_framerate;
+ return put_user(ival, (int *) arg);
+
+ case SNDCTL_DSP_STEREO: /* _SIOWR('P', 3, int) */
+ if (get_user(ival, (int *) arg))
+ return -EFAULT;
+ DBGX("SNDCTL_DSP_STEREO %d\n", ival);
+ if (ival != 0 && ival != 1)
+ return -EINVAL;
+ if (aport->swstate != SW_INITIAL)
+ return -EINVAL;
+ if (rport)
+ rport->sw_channels = ival + 1;
+ if (wport)
+ wport->sw_channels = ival + 1;
+ return put_user(ival, (int *) arg);
+
+ case SNDCTL_DSP_CHANNELS: /* _SIOWR('P', 6, int) */
+ if (get_user(ival, (int *) arg))
+ return -EFAULT;
+ DBGX("SNDCTL_DSP_CHANNELS %d\n", ival);
+ if (ival != 1 && ival != 2)
+ return -EINVAL;
+ if (aport->swstate != SW_INITIAL)
+ return -EINVAL;
+ if (rport)
+ rport->sw_channels = ival;
+ if (wport)
+ wport->sw_channels = ival;
+ return put_user(ival, (int *) arg);
+
+ case SNDCTL_DSP_GETBLKSIZE: /* _SIOWR('P', 4, int) */
+ ival = pcm_setup(devc, rport, wport);
+ if (ival < 0) {
+ DBGX("SNDCTL_DSP_GETBLKSIZE failed, errno %d\n", ival);
+ return ival;
+ }
+ ival = 1 << aport->sw_fragshift;
+ DBGX("SNDCTL_DSP_GETBLKSIZE returning %d\n", ival);
+ return put_user(ival, (int *) arg);
+
+ case SNDCTL_DSP_SETFRAGMENT: /* _SIOWR('P',10, int) */
+ if (get_user(ival, (int *) arg))
+ return -EFAULT;
+ DBGX("SNDCTL_DSP_SETFRAGMENT %d:%d\n",
+ ival >> 16, ival & 0xFFFF);
+ if (aport->swstate != SW_INITIAL)
+ return -EINVAL;
+ {
+ int sw_fragshift = ival & 0xFFFF;
+ int sw_subdivshift = aport->sw_subdivshift;
+ int hw_fragshift = sw_fragshift - sw_subdivshift;
+ int sw_fragcount = (ival >> 16) & 0xFFFF;
+ int hw_fragsize;
+ if (hw_fragshift < MIN_FRAGSHIFT)
+ hw_fragshift = MIN_FRAGSHIFT;
+ if (hw_fragshift > MAX_FRAGSHIFT)
+ hw_fragshift = MAX_FRAGSHIFT;
+ sw_fragshift = hw_fragshift + aport->sw_subdivshift;
+ hw_fragsize = 1 << hw_fragshift;
+ if (sw_fragcount < MIN_FRAGCOUNT(hw_fragsize))
+ sw_fragcount = MIN_FRAGCOUNT(hw_fragsize);
+ if (sw_fragcount > MAX_FRAGCOUNT(hw_fragsize))
+ sw_fragcount = MAX_FRAGCOUNT(hw_fragsize);
+ DBGPV("sw_fragshift = %d\n", sw_fragshift);
+ DBGPV("rport = 0x%p, wport = 0x%p\n", rport, wport);
+ if (rport) {
+ rport->sw_fragshift = sw_fragshift;
+ rport->sw_fragcount = sw_fragcount;
+ }
+ if (wport) {
+ wport->sw_fragshift = sw_fragshift;
+ wport->sw_fragcount = sw_fragcount;
+ }
+ ival = sw_fragcount << 16 | sw_fragshift;
+ }
+ DBGX("SNDCTL_DSP_SETFRAGMENT returns %d:%d\n",
+ ival >> 16, ival & 0xFFFF);
+ return put_user(ival, (int *) arg);
+
+ case SNDCTL_DSP_SUBDIVIDE: /* _SIOWR('P', 9, int) */
+ if (get_user(ival, (int *) arg))
+ return -EFAULT;
+ DBGX("SNDCTL_DSP_SUBDIVIDE %d\n", ival);
+ if (aport->swstate != SW_INITIAL)
+ return -EINVAL;
+ {
+ int subdivshift;
+ int hw_fragshift, hw_fragsize, hw_fragcount;
+ switch (ival) {
+ case 1: subdivshift = 0; break;
+ case 2: subdivshift = 1; break;
+ case 4: subdivshift = 2; break;
+ default: return -EINVAL;
+ }
+ hw_fragshift = aport->sw_fragshift - subdivshift;
+ if (hw_fragshift < MIN_FRAGSHIFT ||
+ hw_fragshift > MAX_FRAGSHIFT)
+ return -EINVAL;
+ hw_fragsize = 1 << hw_fragshift;
+ hw_fragcount = aport->sw_fragcount >> subdivshift;
+ if (hw_fragcount < MIN_FRAGCOUNT(hw_fragsize) ||
+ hw_fragcount > MAX_FRAGCOUNT(hw_fragsize))
+ return -EINVAL;
+ if (rport)
+ rport->sw_subdivshift = subdivshift;
+ if (wport)
+ wport->sw_subdivshift = subdivshift;
+ }
+ return 0;
+
+ case SNDCTL_DSP_SETFMT: /* _SIOWR('P',5, int) */
+ if (get_user(ival, (int *) arg))
+ return -EFAULT;
+ DBGX("SNDCTL_DSP_SETFMT %d\n", ival);
+ if (ival != AFMT_QUERY) {
+ if (aport->swstate != SW_INITIAL) {
+ DBGP("SETFMT failed, swstate = %d\n",
+ aport->swstate);
+ return -EINVAL;
+ }
+ switch (ival) {
+ case AFMT_MU_LAW:
+ case AFMT_A_LAW:
+ case AFMT_U8:
+ case AFMT_S8:
+ case AFMT_S16_LE:
+ if (rport)
+ rport->sw_samplefmt = ival;
+ if (wport)
+ wport->sw_samplefmt = ival;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ ival = aport->sw_samplefmt;
+ return put_user(ival, (int *) arg);
+
+ case SNDCTL_DSP_GETOSPACE: /* _SIOR ('P',12, audio_buf_info) */
+ DBGXV("SNDCTL_DSP_GETOSPACE\n");
+ if (!wport)
+ return -EINVAL;
+ ival = pcm_setup(devc, rport, wport);
+ if (ival < 0)
+ return ival;
+ ival = swb_inc_u(wport, 0);
+ buf_info.fragments = ival >> wport->sw_fragshift;
+ buf_info.fragstotal = wport->sw_fragcount;
+ buf_info.fragsize = 1 << wport->sw_fragshift;
+ buf_info.bytes = ival;
+ DBGXV("SNDCTL_DSP_GETOSPACE returns { %d %d %d %d }\n",
+ buf_info.fragments, buf_info.fragstotal,
+ buf_info.fragsize, buf_info.bytes);
+ if (copy_to_user((void *) arg, &buf_info, sizeof buf_info))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_GETISPACE: /* _SIOR ('P',13, audio_buf_info) */
+ DBGX("SNDCTL_DSP_GETISPACE\n");
+ if (!rport)
+ return -EINVAL;
+ ival = pcm_setup(devc, rport, wport);
+ if (ival < 0)
+ return ival;
+ ival = swb_inc_u(rport, 0);
+ buf_info.fragments = ival >> rport->sw_fragshift;
+ buf_info.fragstotal = rport->sw_fragcount;
+ buf_info.fragsize = 1 << rport->sw_fragshift;
+ buf_info.bytes = ival;
+ DBGX("SNDCTL_DSP_GETISPACE returns { %d %d %d %d }\n",
+ buf_info.fragments, buf_info.fragstotal,
+ buf_info.fragsize, buf_info.bytes);
+ if (copy_to_user((void *) arg, &buf_info, sizeof buf_info))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_NONBLOCK: /* _SIO ('P',14) */
+ DBGX("SNDCTL_DSP_NONBLOCK\n");
+ spin_lock(&file->f_lock);
+ file->f_flags |= O_NONBLOCK;
+ spin_unlock(&file->f_lock);
+ return 0;
+
+ case SNDCTL_DSP_RESET: /* _SIO ('P', 0) */
+ DBGX("SNDCTL_DSP_RESET\n");
+ /*
+ * Nothing special needs to be done for input. Input
+ * samples sit in swbuf, but it will be reinitialized
+ * to empty when pcm_setup() is called.
+ */
+ if (wport && wport->swbuf) {
+ wport->swstate = SW_INITIAL;
+ pcm_output(devc, 0, 0);
+ pcm_write_sync(devc);
+ }
+ pcm_shutdown(devc, rport, wport);
+ return 0;
+
+ case SNDCTL_DSP_SYNC: /* _SIO ('P', 1) */
+ DBGX("SNDCTL_DSP_SYNC\n");
+ if (wport) {
+ pcm_flush_frag(devc);
+ pcm_write_sync(devc);
+ }
+ pcm_shutdown(devc, rport, wport);
+ return 0;
+
+ case SNDCTL_DSP_POST: /* _SIO ('P', 8) */
+ DBGX("SNDCTL_DSP_POST\n");
+ if (!wport)
+ return -EINVAL;
+ pcm_flush_frag(devc);
+ return 0;
+
+ case SNDCTL_DSP_GETIPTR: /* _SIOR ('P', 17, count_info) */
+ DBGX("SNDCTL_DSP_GETIPTR\n");
+ if (!rport)
+ return -EINVAL;
+ spin_lock_irqsave(&rport->lock, flags);
+ {
+ ustmsc_t ustmsc;
+ if (rport->hwstate == HW_RUNNING) {
+ ASSERT(rport->swstate == SW_RUN);
+ li_read_USTMSC(&rport->chan, &ustmsc);
+ info.bytes = ustmsc.msc - rport->MSC_offset;
+ info.bytes *= rport->frame_size;
+ } else {
+ info.bytes = rport->byte_count;
+ }
+ info.blocks = rport->frag_count;
+ info.ptr = 0; /* not implemented */
+ rport->frag_count = 0;
+ }
+ spin_unlock_irqrestore(&rport->lock, flags);
+ if (copy_to_user((void *) arg, &info, sizeof info))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_GETOPTR: /* _SIOR ('P',18, count_info) */
+ DBGX("SNDCTL_DSP_GETOPTR\n");
+ if (!wport)
+ return -EINVAL;
+ spin_lock_irqsave(&wport->lock, flags);
+ {
+ ustmsc_t ustmsc;
+ if (wport->hwstate == HW_RUNNING) {
+ ASSERT(wport->swstate == SW_RUN);
+ li_read_USTMSC(&wport->chan, &ustmsc);
+ info.bytes = ustmsc.msc - wport->MSC_offset;
+ info.bytes *= wport->frame_size;
+ } else {
+ info.bytes = wport->byte_count;
+ }
+ info.blocks = wport->frag_count;
+ info.ptr = 0; /* not implemented */
+ wport->frag_count = 0;
+ }
+ spin_unlock_irqrestore(&wport->lock, flags);
+ if (copy_to_user((void *) arg, &info, sizeof info))
+ return -EFAULT;
+ return 0;
+
+ case SNDCTL_DSP_GETODELAY: /* _SIOR ('P', 23, int) */
+ DBGX("SNDCTL_DSP_GETODELAY\n");
+ if (!wport)
+ return -EINVAL;
+ spin_lock_irqsave(&wport->lock, flags);
+ {
+ int fsize = wport->frame_size;
+ ival = wport->swb_i_avail / fsize;
+ if (wport->hwstate == HW_RUNNING) {
+ int swptr, hwptr, hwframes, hwbytes, hwsize;
+ int totalhwbytes;
+ ustmsc_t ustmsc;
+
+ hwsize = wport->hwbuf_size;
+ swptr = li_read_swptr(&wport->chan);
+ li_read_USTMSC(&wport->chan, &ustmsc);
+ hwframes = ustmsc.msc - wport->MSC_offset;
+ totalhwbytes = hwframes * fsize;
+ hwptr = totalhwbytes % hwsize;
+ hwbytes = (swptr - hwptr + hwsize) % hwsize;
+ ival += hwbytes / fsize;
+ }
+ }
+ spin_unlock_irqrestore(&wport->lock, flags);
+ return put_user(ival, (int *) arg);
+
+ case SNDCTL_DSP_PROFILE: /* _SIOW ('P', 23, int) */
+ DBGX("SNDCTL_DSP_PROFILE\n");
+
+ /*
+ * Thomas Sailer explains SNDCTL_DSP_PROFILE
+ * (private email, March 24, 1999):
+ *
+ * This gives the sound driver a hint on what it
+ * should do with partial fragments
+ * (i.e. fragments partially filled with write).
+ * This can direct the driver to zero them or
+ * leave them alone. But don't ask me what this
+ * is good for, my driver just zeroes the last
+ * fragment before the receiver stops, no idea
+ * what good for any other behaviour could
+ * be. Implementing it as NOP seems safe.
+ */
+
+ break;
+
+ case SNDCTL_DSP_GETTRIGGER: /* _SIOR ('P',16, int) */
+ DBGX("SNDCTL_DSP_GETTRIGGER\n");
+ ival = 0;
+ if (rport) {
+ spin_lock_irqsave(&rport->lock, flags);
+ {
+ if (!(rport->flags & DISABLED))
+ ival |= PCM_ENABLE_INPUT;
+ }
+ spin_unlock_irqrestore(&rport->lock, flags);
+ }
+ if (wport) {
+ spin_lock_irqsave(&wport->lock, flags);
+ {
+ if (!(wport->flags & DISABLED))
+ ival |= PCM_ENABLE_OUTPUT;
+ }
+ spin_unlock_irqrestore(&wport->lock, flags);
+ }
+ return put_user(ival, (int *) arg);
+
+ case SNDCTL_DSP_SETTRIGGER: /* _SIOW ('P',16, int) */
+ if (get_user(ival, (int *) arg))
+ return -EFAULT;
+ DBGX("SNDCTL_DSP_SETTRIGGER %d\n", ival);
+
+ /*
+ * If user is disabling I/O and port is not in initial
+ * state, fail with EINVAL.
+ */
+
+ if (((rport && !(ival & PCM_ENABLE_INPUT)) ||
+ (wport && !(ival & PCM_ENABLE_OUTPUT))) &&
+ aport->swstate != SW_INITIAL)
+ return -EINVAL;
+
+ if (rport) {
+ vwsnd_port_hwstate_t hwstate;
+ spin_lock_irqsave(&rport->lock, flags);
+ {
+ hwstate = rport->hwstate;
+ if (ival & PCM_ENABLE_INPUT)
+ rport->flags &= ~DISABLED;
+ else
+ rport->flags |= DISABLED;
+ }
+ spin_unlock_irqrestore(&rport->lock, flags);
+ if (hwstate != HW_RUNNING && ival & PCM_ENABLE_INPUT) {
+
+ if (rport->swstate == SW_INITIAL)
+ pcm_setup(devc, rport, wport);
+ else
+ li_activate_dma(&rport->chan);
+ }
+ }
+ if (wport) {
+ vwsnd_port_flags_t pflags;
+ spin_lock_irqsave(&wport->lock, flags);
+ {
+ pflags = wport->flags;
+ if (ival & PCM_ENABLE_OUTPUT)
+ wport->flags &= ~DISABLED;
+ else
+ wport->flags |= DISABLED;
+ }
+ spin_unlock_irqrestore(&wport->lock, flags);
+ if (pflags & DISABLED && ival & PCM_ENABLE_OUTPUT) {
+ if (wport->swstate == SW_RUN)
+ pcm_output(devc, 0, 0);
+ }
+ }
+ return 0;
+
+ default:
+ DBGP("unknown ioctl 0x%x\n", cmd);
+ return -EINVAL;
+ }
+ DBGP("unimplemented ioctl 0x%x\n", cmd);
+ return -EINVAL;
+}
+
+static long vwsnd_audio_ioctl(struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ vwsnd_dev_t *devc = (vwsnd_dev_t *) file->private_data;
+ int ret;
+
+ mutex_lock(&vwsnd_mutex);
+ mutex_lock(&devc->io_mutex);
+ ret = vwsnd_audio_do_ioctl(file, cmd, arg);
+ mutex_unlock(&devc->io_mutex);
+ mutex_unlock(&vwsnd_mutex);
+
+ return ret;
+}
+
+/* No mmap. */
+
+static int vwsnd_audio_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ DBGE("(file=0x%p, vma=0x%p)\n", file, vma);
+ return -ENODEV;
+}
+
+/*
+ * Open the audio device for read and/or write.
+ *
+ * Returns 0 on success, -errno on failure.
+ */
+
+static int vwsnd_audio_open(struct inode *inode, struct file *file)
+{
+ vwsnd_dev_t *devc;
+ int minor = iminor(inode);
+ int sw_samplefmt;
+
+ DBGE("(inode=0x%p, file=0x%p)\n", inode, file);
+
+ mutex_lock(&vwsnd_mutex);
+ INC_USE_COUNT;
+ for (devc = vwsnd_dev_list; devc; devc = devc->next_dev)
+ if ((devc->audio_minor & ~0x0F) == (minor & ~0x0F))
+ break;
+
+ if (devc == NULL) {
+ DEC_USE_COUNT;
+ mutex_unlock(&vwsnd_mutex);
+ return -ENODEV;
+ }
+
+ mutex_lock(&devc->open_mutex);
+ while (devc->open_mode & file->f_mode) {
+ mutex_unlock(&devc->open_mutex);
+ if (file->f_flags & O_NONBLOCK) {
+ DEC_USE_COUNT;
+ mutex_unlock(&vwsnd_mutex);
+ return -EBUSY;
+ }
+ interruptible_sleep_on(&devc->open_wait);
+ if (signal_pending(current)) {
+ DEC_USE_COUNT;
+ mutex_unlock(&vwsnd_mutex);
+ return -ERESTARTSYS;
+ }
+ mutex_lock(&devc->open_mutex);
+ }
+ devc->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE);
+ mutex_unlock(&devc->open_mutex);
+
+ /* get default sample format from minor number. */
+
+ sw_samplefmt = 0;
+ if ((minor & 0xF) == SND_DEV_DSP)
+ sw_samplefmt = AFMT_U8;
+ else if ((minor & 0xF) == SND_DEV_AUDIO)
+ sw_samplefmt = AFMT_MU_LAW;
+ else if ((minor & 0xF) == SND_DEV_DSP16)
+ sw_samplefmt = AFMT_S16_LE;
+ else
+ ASSERT(0);
+
+ /* Initialize vwsnd_ports. */
+
+ mutex_lock(&devc->io_mutex);
+ {
+ if (file->f_mode & FMODE_READ) {
+ devc->rport.swstate = SW_INITIAL;
+ devc->rport.flags = 0;
+ devc->rport.sw_channels = 1;
+ devc->rport.sw_samplefmt = sw_samplefmt;
+ devc->rport.sw_framerate = 8000;
+ devc->rport.sw_fragshift = DEFAULT_FRAGSHIFT;
+ devc->rport.sw_fragcount = DEFAULT_FRAGCOUNT;
+ devc->rport.sw_subdivshift = DEFAULT_SUBDIVSHIFT;
+ devc->rport.byte_count = 0;
+ devc->rport.frag_count = 0;
+ }
+ if (file->f_mode & FMODE_WRITE) {
+ devc->wport.swstate = SW_INITIAL;
+ devc->wport.flags = 0;
+ devc->wport.sw_channels = 1;
+ devc->wport.sw_samplefmt = sw_samplefmt;
+ devc->wport.sw_framerate = 8000;
+ devc->wport.sw_fragshift = DEFAULT_FRAGSHIFT;
+ devc->wport.sw_fragcount = DEFAULT_FRAGCOUNT;
+ devc->wport.sw_subdivshift = DEFAULT_SUBDIVSHIFT;
+ devc->wport.byte_count = 0;
+ devc->wport.frag_count = 0;
+ }
+ }
+ mutex_unlock(&devc->io_mutex);
+
+ file->private_data = devc;
+ DBGRV();
+ mutex_unlock(&vwsnd_mutex);
+ return 0;
+}
+
+/*
+ * Release (close) the audio device.
+ */
+
+static int vwsnd_audio_release(struct inode *inode, struct file *file)
+{
+ vwsnd_dev_t *devc = (vwsnd_dev_t *) file->private_data;
+ vwsnd_port_t *wport = NULL, *rport = NULL;
+ int err = 0;
+
+ mutex_lock(&vwsnd_mutex);
+ mutex_lock(&devc->io_mutex);
+ {
+ DBGEV("(inode=0x%p, file=0x%p)\n", inode, file);
+
+ if (file->f_mode & FMODE_READ)
+ rport = &devc->rport;
+ if (file->f_mode & FMODE_WRITE) {
+ wport = &devc->wport;
+ pcm_flush_frag(devc);
+ pcm_write_sync(devc);
+ }
+ pcm_shutdown(devc, rport, wport);
+ if (rport)
+ rport->swstate = SW_OFF;
+ if (wport)
+ wport->swstate = SW_OFF;
+ }
+ mutex_unlock(&devc->io_mutex);
+
+ mutex_lock(&devc->open_mutex);
+ {
+ devc->open_mode &= ~file->f_mode;
+ }
+ mutex_unlock(&devc->open_mutex);
+ wake_up(&devc->open_wait);
+ DEC_USE_COUNT;
+ DBGR();
+ mutex_unlock(&vwsnd_mutex);
+ return err;
+}
+
+static const struct file_operations vwsnd_audio_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = vwsnd_audio_read,
+ .write = vwsnd_audio_write,
+ .poll = vwsnd_audio_poll,
+ .unlocked_ioctl = vwsnd_audio_ioctl,
+ .mmap = vwsnd_audio_mmap,
+ .open = vwsnd_audio_open,
+ .release = vwsnd_audio_release,
+};
+
+/*****************************************************************************/
+/* mixer driver */
+
+/* open the mixer device. */
+
+static int vwsnd_mixer_open(struct inode *inode, struct file *file)
+{
+ vwsnd_dev_t *devc;
+
+ DBGEV("(inode=0x%p, file=0x%p)\n", inode, file);
+
+ INC_USE_COUNT;
+ mutex_lock(&vwsnd_mutex);
+ for (devc = vwsnd_dev_list; devc; devc = devc->next_dev)
+ if (devc->mixer_minor == iminor(inode))
+ break;
+
+ if (devc == NULL) {
+ DEC_USE_COUNT;
+ mutex_unlock(&vwsnd_mutex);
+ return -ENODEV;
+ }
+ file->private_data = devc;
+ mutex_unlock(&vwsnd_mutex);
+ return 0;
+}
+
+/* release (close) the mixer device. */
+
+static int vwsnd_mixer_release(struct inode *inode, struct file *file)
+{
+ DBGEV("(inode=0x%p, file=0x%p)\n", inode, file);
+ DEC_USE_COUNT;
+ return 0;
+}
+
+/* mixer_read_ioctl handles all read ioctls on the mixer device. */
+
+static int mixer_read_ioctl(vwsnd_dev_t *devc, unsigned int nr, void __user *arg)
+{
+ int val = -1;
+
+ DBGEV("(devc=0x%p, nr=0x%x, arg=0x%p)\n", devc, nr, arg);
+
+ switch (nr) {
+ case SOUND_MIXER_CAPS:
+ val = SOUND_CAP_EXCL_INPUT;
+ break;
+
+ case SOUND_MIXER_DEVMASK:
+ val = (SOUND_MASK_PCM | SOUND_MASK_LINE |
+ SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_RECLEV);
+ break;
+
+ case SOUND_MIXER_STEREODEVS:
+ val = (SOUND_MASK_PCM | SOUND_MASK_LINE |
+ SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_RECLEV);
+ break;
+
+ case SOUND_MIXER_OUTMASK:
+ val = (SOUND_MASK_PCM | SOUND_MASK_LINE |
+ SOUND_MASK_MIC | SOUND_MASK_CD);
+ break;
+
+ case SOUND_MIXER_RECMASK:
+ val = (SOUND_MASK_PCM | SOUND_MASK_LINE |
+ SOUND_MASK_MIC | SOUND_MASK_CD);
+ break;
+
+ case SOUND_MIXER_PCM:
+ val = ad1843_get_gain(&devc->lith, &ad1843_gain_PCM);
+ break;
+
+ case SOUND_MIXER_LINE:
+ val = ad1843_get_gain(&devc->lith, &ad1843_gain_LINE);
+ break;
+
+ case SOUND_MIXER_MIC:
+ val = ad1843_get_gain(&devc->lith, &ad1843_gain_MIC);
+ break;
+
+ case SOUND_MIXER_CD:
+ val = ad1843_get_gain(&devc->lith, &ad1843_gain_CD);
+ break;
+
+ case SOUND_MIXER_RECLEV:
+ val = ad1843_get_gain(&devc->lith, &ad1843_gain_RECLEV);
+ break;
+
+ case SOUND_MIXER_RECSRC:
+ val = ad1843_get_recsrc(&devc->lith);
+ break;
+
+ case SOUND_MIXER_OUTSRC:
+ val = ad1843_get_outsrc(&devc->lith);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return put_user(val, (int __user *) arg);
+}
+
+/* mixer_write_ioctl handles all write ioctls on the mixer device. */
+
+static int mixer_write_ioctl(vwsnd_dev_t *devc, unsigned int nr, void __user *arg)
+{
+ int val;
+ int err;
+
+ DBGEV("(devc=0x%p, nr=0x%x, arg=0x%p)\n", devc, nr, arg);
+
+ err = get_user(val, (int __user *) arg);
+ if (err)
+ return -EFAULT;
+ switch (nr) {
+ case SOUND_MIXER_PCM:
+ val = ad1843_set_gain(&devc->lith, &ad1843_gain_PCM, val);
+ break;
+
+ case SOUND_MIXER_LINE:
+ val = ad1843_set_gain(&devc->lith, &ad1843_gain_LINE, val);
+ break;
+
+ case SOUND_MIXER_MIC:
+ val = ad1843_set_gain(&devc->lith, &ad1843_gain_MIC, val);
+ break;
+
+ case SOUND_MIXER_CD:
+ val = ad1843_set_gain(&devc->lith, &ad1843_gain_CD, val);
+ break;
+
+ case SOUND_MIXER_RECLEV:
+ val = ad1843_set_gain(&devc->lith, &ad1843_gain_RECLEV, val);
+ break;
+
+ case SOUND_MIXER_RECSRC:
+ if (devc->rport.swbuf || devc->wport.swbuf)
+ return -EBUSY; /* can't change recsrc while running */
+ val = ad1843_set_recsrc(&devc->lith, val);
+ break;
+
+ case SOUND_MIXER_OUTSRC:
+ val = ad1843_set_outsrc(&devc->lith, val);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ if (val < 0)
+ return val;
+ return put_user(val, (int __user *) arg);
+}
+
+/* This is the ioctl entry to the mixer driver. */
+
+static long vwsnd_mixer_ioctl(struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ vwsnd_dev_t *devc = (vwsnd_dev_t *) file->private_data;
+ const unsigned int nrmask = _IOC_NRMASK << _IOC_NRSHIFT;
+ const unsigned int nr = (cmd & nrmask) >> _IOC_NRSHIFT;
+ int retval;
+
+ DBGEV("(devc=0x%p, cmd=0x%x, arg=0x%lx)\n", devc, cmd, arg);
+
+ mutex_lock(&vwsnd_mutex);
+ mutex_lock(&devc->mix_mutex);
+ {
+ if ((cmd & ~nrmask) == MIXER_READ(0))
+ retval = mixer_read_ioctl(devc, nr, (void __user *) arg);
+ else if ((cmd & ~nrmask) == MIXER_WRITE(0))
+ retval = mixer_write_ioctl(devc, nr, (void __user *) arg);
+ else
+ retval = -EINVAL;
+ }
+ mutex_unlock(&devc->mix_mutex);
+ mutex_unlock(&vwsnd_mutex);
+ return retval;
+}
+
+static const struct file_operations vwsnd_mixer_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .unlocked_ioctl = vwsnd_mixer_ioctl,
+ .open = vwsnd_mixer_open,
+ .release = vwsnd_mixer_release,
+};
+
+/*****************************************************************************/
+/* probe/attach/unload */
+
+/* driver probe routine. Return nonzero if hardware is found. */
+
+static int __init probe_vwsnd(struct address_info *hw_config)
+{
+ lithium_t lith;
+ int w;
+ unsigned long later;
+
+ DBGEV("(hw_config=0x%p)\n", hw_config);
+
+ /* XXX verify lithium present (to prevent crash on non-vw) */
+
+ if (li_create(&lith, hw_config->io_base) != 0) {
+ printk(KERN_WARNING "probe_vwsnd: can't map lithium\n");
+ return 0;
+ }
+ later = jiffies + 2;
+ li_writel(&lith, LI_HOST_CONTROLLER, LI_HC_LINK_ENABLE);
+ do {
+ w = li_readl(&lith, LI_HOST_CONTROLLER);
+ } while (w == LI_HC_LINK_ENABLE && time_before(jiffies, later));
+
+ li_destroy(&lith);
+
+ DBGPV("HC = 0x%04x\n", w);
+
+ if ((w == LI_HC_LINK_ENABLE) || (w & LI_HC_LINK_CODEC)) {
+
+ /* This may indicate a beta machine with no audio,
+ * or a future machine with different audio.
+ * On beta-release 320 w/ no audio, HC == 0x4000 */
+
+ printk(KERN_WARNING "probe_vwsnd: audio codec not found\n");
+ return 0;
+ }
+
+ if (w & LI_HC_LINK_FAILURE) {
+ printk(KERN_WARNING "probe_vwsnd: can't init audio codec\n");
+ return 0;
+ }
+
+ printk(KERN_INFO "vwsnd: lithium audio at mmio %#x irq %d\n",
+ hw_config->io_base, hw_config->irq);
+
+ return 1;
+}
+
+/*
+ * driver attach routine. Initialize driver data structures and
+ * initialize hardware. A new vwsnd_dev_t is allocated and put
+ * onto the global list, vwsnd_dev_list.
+ *
+ * Return +minor_dev on success, -errno on failure.
+ */
+
+static int __init attach_vwsnd(struct address_info *hw_config)
+{
+ vwsnd_dev_t *devc = NULL;
+ int err = -ENOMEM;
+
+ DBGEV("(hw_config=0x%p)\n", hw_config);
+
+ devc = kmalloc(sizeof (vwsnd_dev_t), GFP_KERNEL);
+ if (devc == NULL)
+ goto fail0;
+
+ err = li_create(&devc->lith, hw_config->io_base);
+ if (err)
+ goto fail1;
+
+ init_waitqueue_head(&devc->open_wait);
+
+ devc->rport.hwbuf_size = HWBUF_SIZE;
+ devc->rport.hwbuf_vaddr = __get_free_pages(GFP_KERNEL, HWBUF_ORDER);
+ if (!devc->rport.hwbuf_vaddr)
+ goto fail2;
+ devc->rport.hwbuf = (void *) devc->rport.hwbuf_vaddr;
+ devc->rport.hwbuf_paddr = virt_to_phys(devc->rport.hwbuf);
+
+ /*
+ * Quote from the NT driver:
+ *
+ * // WARNING!!! HACK to setup output dma!!!
+ * // This is required because even on output there is some data
+ * // trickling into the input DMA channel. This is a bug in the
+ * // Lithium microcode.
+ * // --sde
+ *
+ * We set the input side's DMA base address here. It will remain
+ * valid until the driver is unloaded.
+ */
+
+ li_writel(&devc->lith, LI_COMM1_BASE,
+ devc->rport.hwbuf_paddr >> 8 | 1 << (37 - 8));
+
+ devc->wport.hwbuf_size = HWBUF_SIZE;
+ devc->wport.hwbuf_vaddr = __get_free_pages(GFP_KERNEL, HWBUF_ORDER);
+ if (!devc->wport.hwbuf_vaddr)
+ goto fail3;
+ devc->wport.hwbuf = (void *) devc->wport.hwbuf_vaddr;
+ devc->wport.hwbuf_paddr = virt_to_phys(devc->wport.hwbuf);
+ DBGP("wport hwbuf = 0x%p\n", devc->wport.hwbuf);
+
+ DBGDO(shut_up++);
+ err = ad1843_init(&devc->lith);
+ DBGDO(shut_up--);
+ if (err)
+ goto fail4;
+
+ /* install interrupt handler */
+
+ err = request_irq(hw_config->irq, vwsnd_audio_intr, 0, "vwsnd", devc);
+ if (err)
+ goto fail5;
+
+ /* register this device's drivers. */
+
+ devc->audio_minor = register_sound_dsp(&vwsnd_audio_fops, -1);
+ if ((err = devc->audio_minor) < 0) {
+ DBGDO(printk(KERN_WARNING
+ "attach_vwsnd: register_sound_dsp error %d\n",
+ err));
+ goto fail6;
+ }
+ devc->mixer_minor = register_sound_mixer(&vwsnd_mixer_fops,
+ devc->audio_minor >> 4);
+ if ((err = devc->mixer_minor) < 0) {
+ DBGDO(printk(KERN_WARNING
+ "attach_vwsnd: register_sound_mixer error %d\n",
+ err));
+ goto fail7;
+ }
+
+ /* Squirrel away device indices for unload routine. */
+
+ hw_config->slots[0] = devc->audio_minor;
+
+ /* Initialize as much of *devc as possible */
+
+ mutex_init(&devc->open_mutex);
+ mutex_init(&devc->io_mutex);
+ mutex_init(&devc->mix_mutex);
+ devc->open_mode = 0;
+ spin_lock_init(&devc->rport.lock);
+ init_waitqueue_head(&devc->rport.queue);
+ devc->rport.swstate = SW_OFF;
+ devc->rport.hwstate = HW_STOPPED;
+ devc->rport.flags = 0;
+ devc->rport.swbuf = NULL;
+ spin_lock_init(&devc->wport.lock);
+ init_waitqueue_head(&devc->wport.queue);
+ devc->wport.swstate = SW_OFF;
+ devc->wport.hwstate = HW_STOPPED;
+ devc->wport.flags = 0;
+ devc->wport.swbuf = NULL;
+
+ /* Success. Link us onto the local device list. */
+
+ devc->next_dev = vwsnd_dev_list;
+ vwsnd_dev_list = devc;
+ return devc->audio_minor;
+
+ /* So many ways to fail. Undo what we did. */
+
+ fail7:
+ unregister_sound_dsp(devc->audio_minor);
+ fail6:
+ free_irq(hw_config->irq, devc);
+ fail5:
+ fail4:
+ free_pages(devc->wport.hwbuf_vaddr, HWBUF_ORDER);
+ fail3:
+ free_pages(devc->rport.hwbuf_vaddr, HWBUF_ORDER);
+ fail2:
+ li_destroy(&devc->lith);
+ fail1:
+ kfree(devc);
+ fail0:
+ return err;
+}
+
+static int __exit unload_vwsnd(struct address_info *hw_config)
+{
+ vwsnd_dev_t *devc, **devcp;
+
+ DBGE("()\n");
+
+ devcp = &vwsnd_dev_list;
+ while ((devc = *devcp)) {
+ if (devc->audio_minor == hw_config->slots[0]) {
+ *devcp = devc->next_dev;
+ break;
+ }
+ devcp = &devc->next_dev;
+ }
+
+ if (!devc)
+ return -ENODEV;
+
+ unregister_sound_mixer(devc->mixer_minor);
+ unregister_sound_dsp(devc->audio_minor);
+ free_irq(hw_config->irq, devc);
+ free_pages(devc->wport.hwbuf_vaddr, HWBUF_ORDER);
+ free_pages(devc->rport.hwbuf_vaddr, HWBUF_ORDER);
+ li_destroy(&devc->lith);
+ kfree(devc);
+
+ return 0;
+}
+
+/*****************************************************************************/
+/* initialization and loadable kernel module interface */
+
+static struct address_info the_hw_config = {
+ 0xFF001000, /* lithium phys addr */
+ CO_IRQ(CO_APIC_LI_AUDIO) /* irq */
+};
+
+MODULE_DESCRIPTION("SGI Visual Workstation sound module");
+MODULE_AUTHOR("Bob Miller <kbob@sgi.com>");
+MODULE_LICENSE("GPL");
+
+static int __init init_vwsnd(void)
+{
+ int err;
+
+ DBGXV("\n");
+ DBGXV("sound::vwsnd::init_module()\n");
+
+ if (!probe_vwsnd(&the_hw_config))
+ return -ENODEV;
+
+ err = attach_vwsnd(&the_hw_config);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static void __exit cleanup_vwsnd(void)
+{
+ DBGX("sound::vwsnd::cleanup_module()\n");
+
+ unload_vwsnd(&the_hw_config);
+}
+
+module_init(init_vwsnd);
+module_exit(cleanup_vwsnd);
diff --git a/sound/oss/waveartist.c b/sound/oss/waveartist.c
new file mode 100644
index 00000000..52468742
--- /dev/null
+++ b/sound/oss/waveartist.c
@@ -0,0 +1,2025 @@
+/*
+ * linux/sound/oss/waveartist.c
+ *
+ * The low level driver for the RWA010 Rockwell Wave Artist
+ * codec chip used in the Rebel.com NetWinder.
+ *
+ * Cleaned up and integrated into 2.1 by Russell King (rmk@arm.linux.org.uk)
+ * and Pat Beirne (patb@corel.ca)
+ *
+ *
+ * Copyright (C) by Rebel.com 1998-1999
+ *
+ * RWA010 specs received under NDA from Rockwell
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Changes:
+ * 11-10-2000 Bartlomiej Zolnierkiewicz <bkz@linux-ide.org>
+ * Added __init to waveartist_init()
+ */
+
+/* Debugging */
+#define DEBUG_CMD 1
+#define DEBUG_OUT 2
+#define DEBUG_IN 4
+#define DEBUG_INTR 8
+#define DEBUG_MIXER 16
+#define DEBUG_TRIGGER 32
+
+#define debug_flg (0)
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+
+#include "sound_config.h"
+#include "waveartist.h"
+
+#ifdef CONFIG_ARM
+#include <mach/hardware.h>
+#include <asm/mach-types.h>
+#endif
+
+#ifndef NO_DMA
+#define NO_DMA 255
+#endif
+
+#define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH |\
+ SOUND_MASK_PCM |\
+ SOUND_MASK_LINE |\
+ SOUND_MASK_MIC |\
+ SOUND_MASK_LINE1 |\
+ SOUND_MASK_RECLEV |\
+ SOUND_MASK_VOLUME |\
+ SOUND_MASK_IMIX)
+
+static unsigned short levels[SOUND_MIXER_NRDEVICES] = {
+ 0x5555, /* Master Volume */
+ 0x0000, /* Bass */
+ 0x0000, /* Treble */
+ 0x2323, /* Synth (FM) */
+ 0x4b4b, /* PCM */
+ 0x6464, /* PC Speaker */
+ 0x0000, /* Ext Line */
+ 0x0000, /* Mic */
+ 0x0000, /* CD */
+ 0x6464, /* Recording monitor */
+ 0x0000, /* SB PCM (ALT PCM) */
+ 0x0000, /* Recording level */
+ 0x6464, /* Input gain */
+ 0x6464, /* Output gain */
+ 0x0000, /* Line1 (Aux1) */
+ 0x0000, /* Line2 (Aux2) */
+ 0x0000, /* Line3 (Aux3) */
+ 0x0000, /* Digital1 */
+ 0x0000, /* Digital2 */
+ 0x0000, /* Digital3 */
+ 0x0000, /* Phone In */
+ 0x6464, /* Phone Out */
+ 0x0000, /* Video */
+ 0x0000, /* Radio */
+ 0x0000 /* Monitor */
+};
+
+typedef struct {
+ struct address_info hw; /* hardware */
+ char *chip_name;
+
+ int xfer_count;
+ int audio_mode;
+ int open_mode;
+ int audio_flags;
+ int record_dev;
+ int playback_dev;
+ int dev_no;
+
+ /* Mixer parameters */
+ const struct waveartist_mixer_info *mix;
+
+ unsigned short *levels; /* cache of volume settings */
+ int recmask; /* currently enabled recording device! */
+
+#ifdef CONFIG_ARCH_NETWINDER
+ signed int slider_vol; /* hardware slider volume */
+ unsigned int handset_detect :1;
+ unsigned int telephone_detect:1;
+ unsigned int no_autoselect :1;/* handset/telephone autoselects a path */
+ unsigned int spkr_mute_state :1;/* set by ioctl or autoselect */
+ unsigned int line_mute_state :1;/* set by ioctl or autoselect */
+ unsigned int use_slider :1;/* use slider setting for o/p vol */
+#endif
+} wavnc_info;
+
+/*
+ * This is the implementation specific mixer information.
+ */
+struct waveartist_mixer_info {
+ unsigned int supported_devs; /* Supported devices */
+ unsigned int recording_devs; /* Recordable devies */
+ unsigned int stereo_devs; /* Stereo devices */
+
+ unsigned int (*select_input)(wavnc_info *, unsigned int,
+ unsigned char *, unsigned char *);
+ int (*decode_mixer)(wavnc_info *, int,
+ unsigned char, unsigned char);
+ int (*get_mixer)(wavnc_info *, int);
+};
+
+typedef struct wavnc_port_info {
+ int open_mode;
+ int speed;
+ int channels;
+ int audio_format;
+} wavnc_port_info;
+
+static int nr_waveartist_devs;
+static wavnc_info adev_info[MAX_AUDIO_DEV];
+static DEFINE_SPINLOCK(waveartist_lock);
+
+#ifndef CONFIG_ARCH_NETWINDER
+#define machine_is_netwinder() 0
+#else
+static struct timer_list vnc_timer;
+static void vnc_configure_mixer(wavnc_info *devc, unsigned int input_mask);
+static int vnc_private_ioctl(int dev, unsigned int cmd, int __user *arg);
+static void vnc_slider_tick(unsigned long data);
+#endif
+
+static inline void
+waveartist_set_ctlr(struct address_info *hw, unsigned char clear, unsigned char set)
+{
+ unsigned int ctlr_port = hw->io_base + CTLR;
+
+ clear = ~clear & inb(ctlr_port);
+
+ outb(clear | set, ctlr_port);
+}
+
+/* Toggle IRQ acknowledge line
+ */
+static inline void
+waveartist_iack(wavnc_info *devc)
+{
+ unsigned int ctlr_port = devc->hw.io_base + CTLR;
+ int old_ctlr;
+
+ old_ctlr = inb(ctlr_port) & ~IRQ_ACK;
+
+ outb(old_ctlr | IRQ_ACK, ctlr_port);
+ outb(old_ctlr, ctlr_port);
+}
+
+static inline int
+waveartist_sleep(int timeout_ms)
+{
+ unsigned int timeout = msecs_to_jiffies(timeout_ms*100);
+ return schedule_timeout_interruptible(timeout);
+}
+
+static int
+waveartist_reset(wavnc_info *devc)
+{
+ struct address_info *hw = &devc->hw;
+ unsigned int timeout, res = -1;
+
+ waveartist_set_ctlr(hw, -1, RESET);
+ waveartist_sleep(2);
+ waveartist_set_ctlr(hw, RESET, 0);
+
+ timeout = 500;
+ do {
+ mdelay(2);
+
+ if (inb(hw->io_base + STATR) & CMD_RF) {
+ res = inw(hw->io_base + CMDR);
+ if (res == 0x55aa)
+ break;
+ }
+ } while (--timeout);
+
+ if (timeout == 0) {
+ printk(KERN_WARNING "WaveArtist: reset timeout ");
+ if (res != (unsigned int)-1)
+ printk("(res=%04X)", res);
+ printk("\n");
+ return 1;
+ }
+ return 0;
+}
+
+/* Helper function to send and receive words
+ * from WaveArtist. It handles all the handshaking
+ * and can send or receive multiple words.
+ */
+static int
+waveartist_cmd(wavnc_info *devc,
+ int nr_cmd, unsigned int *cmd,
+ int nr_resp, unsigned int *resp)
+{
+ unsigned int io_base = devc->hw.io_base;
+ unsigned int timed_out = 0;
+ unsigned int i;
+
+ if (debug_flg & DEBUG_CMD) {
+ printk("waveartist_cmd: cmd=");
+
+ for (i = 0; i < nr_cmd; i++)
+ printk("%04X ", cmd[i]);
+
+ printk("\n");
+ }
+
+ if (inb(io_base + STATR) & CMD_RF) {
+ int old_data;
+
+ /* flush the port
+ */
+
+ old_data = inw(io_base + CMDR);
+
+ if (debug_flg & DEBUG_CMD)
+ printk("flushed %04X...", old_data);
+
+ udelay(10);
+ }
+
+ for (i = 0; !timed_out && i < nr_cmd; i++) {
+ int count;
+
+ for (count = 5000; count; count--)
+ if (inb(io_base + STATR) & CMD_WE)
+ break;
+
+ if (!count)
+ timed_out = 1;
+ else
+ outw(cmd[i], io_base + CMDR);
+ }
+
+ for (i = 0; !timed_out && i < nr_resp; i++) {
+ int count;
+
+ for (count = 5000; count; count--)
+ if (inb(io_base + STATR) & CMD_RF)
+ break;
+
+ if (!count)
+ timed_out = 1;
+ else
+ resp[i] = inw(io_base + CMDR);
+ }
+
+ if (debug_flg & DEBUG_CMD) {
+ if (!timed_out) {
+ printk("waveartist_cmd: resp=");
+
+ for (i = 0; i < nr_resp; i++)
+ printk("%04X ", resp[i]);
+
+ printk("\n");
+ } else
+ printk("waveartist_cmd: timed out\n");
+ }
+
+ return timed_out ? 1 : 0;
+}
+
+/*
+ * Send one command word
+ */
+static inline int
+waveartist_cmd1(wavnc_info *devc, unsigned int cmd)
+{
+ return waveartist_cmd(devc, 1, &cmd, 0, NULL);
+}
+
+/*
+ * Send one command, receive one word
+ */
+static inline unsigned int
+waveartist_cmd1_r(wavnc_info *devc, unsigned int cmd)
+{
+ unsigned int ret;
+
+ waveartist_cmd(devc, 1, &cmd, 1, &ret);
+
+ return ret;
+}
+
+/*
+ * Send a double command, receive one
+ * word (and throw it away)
+ */
+static inline int
+waveartist_cmd2(wavnc_info *devc, unsigned int cmd, unsigned int arg)
+{
+ unsigned int vals[2];
+
+ vals[0] = cmd;
+ vals[1] = arg;
+
+ return waveartist_cmd(devc, 2, vals, 1, vals);
+}
+
+/*
+ * Send a triple command
+ */
+static inline int
+waveartist_cmd3(wavnc_info *devc, unsigned int cmd,
+ unsigned int arg1, unsigned int arg2)
+{
+ unsigned int vals[3];
+
+ vals[0] = cmd;
+ vals[1] = arg1;
+ vals[2] = arg2;
+
+ return waveartist_cmd(devc, 3, vals, 0, NULL);
+}
+
+static int
+waveartist_getrev(wavnc_info *devc, char *rev)
+{
+ unsigned int temp[2];
+ unsigned int cmd = WACMD_GETREV;
+
+ waveartist_cmd(devc, 1, &cmd, 2, temp);
+
+ rev[0] = temp[0] >> 8;
+ rev[1] = temp[0] & 255;
+ rev[2] = '\0';
+
+ return temp[0];
+}
+
+static void waveartist_halt_output(int dev);
+static void waveartist_halt_input(int dev);
+static void waveartist_halt(int dev);
+static void waveartist_trigger(int dev, int state);
+
+static int
+waveartist_open(int dev, int mode)
+{
+ wavnc_info *devc;
+ wavnc_port_info *portc;
+ unsigned long flags;
+
+ if (dev < 0 || dev >= num_audiodevs)
+ return -ENXIO;
+
+ devc = (wavnc_info *) audio_devs[dev]->devc;
+ portc = (wavnc_port_info *) audio_devs[dev]->portc;
+
+ spin_lock_irqsave(&waveartist_lock, flags);
+ if (portc->open_mode || (devc->open_mode & mode)) {
+ spin_unlock_irqrestore(&waveartist_lock, flags);
+ return -EBUSY;
+ }
+
+ devc->audio_mode = 0;
+ devc->open_mode |= mode;
+ portc->open_mode = mode;
+ waveartist_trigger(dev, 0);
+
+ if (mode & OPEN_READ)
+ devc->record_dev = dev;
+ if (mode & OPEN_WRITE)
+ devc->playback_dev = dev;
+ spin_unlock_irqrestore(&waveartist_lock, flags);
+
+ return 0;
+}
+
+static void
+waveartist_close(int dev)
+{
+ wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
+ wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&waveartist_lock, flags);
+
+ waveartist_halt(dev);
+
+ devc->audio_mode = 0;
+ devc->open_mode &= ~portc->open_mode;
+ portc->open_mode = 0;
+
+ spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static void
+waveartist_output_block(int dev, unsigned long buf, int __count, int intrflag)
+{
+ wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+ wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
+ unsigned long flags;
+ unsigned int count = __count;
+
+ if (debug_flg & DEBUG_OUT)
+ printk("waveartist: output block, buf=0x%lx, count=0x%x...\n",
+ buf, count);
+ /*
+ * 16 bit data
+ */
+ if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE))
+ count >>= 1;
+
+ if (portc->channels > 1)
+ count >>= 1;
+
+ count -= 1;
+
+ if (devc->audio_mode & PCM_ENABLE_OUTPUT &&
+ audio_devs[dev]->flags & DMA_AUTOMODE &&
+ intrflag &&
+ count == devc->xfer_count) {
+ devc->audio_mode |= PCM_ENABLE_OUTPUT;
+ return; /*
+ * Auto DMA mode on. No need to react
+ */
+ }
+
+ spin_lock_irqsave(&waveartist_lock, flags);
+
+ /*
+ * set sample count
+ */
+ waveartist_cmd2(devc, WACMD_OUTPUTSIZE, count);
+
+ devc->xfer_count = count;
+ devc->audio_mode |= PCM_ENABLE_OUTPUT;
+
+ spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static void
+waveartist_start_input(int dev, unsigned long buf, int __count, int intrflag)
+{
+ wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+ wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
+ unsigned long flags;
+ unsigned int count = __count;
+
+ if (debug_flg & DEBUG_IN)
+ printk("waveartist: start input, buf=0x%lx, count=0x%x...\n",
+ buf, count);
+
+ if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE)) /* 16 bit data */
+ count >>= 1;
+
+ if (portc->channels > 1)
+ count >>= 1;
+
+ count -= 1;
+
+ if (devc->audio_mode & PCM_ENABLE_INPUT &&
+ audio_devs[dev]->flags & DMA_AUTOMODE &&
+ intrflag &&
+ count == devc->xfer_count) {
+ devc->audio_mode |= PCM_ENABLE_INPUT;
+ return; /*
+ * Auto DMA mode on. No need to react
+ */
+ }
+
+ spin_lock_irqsave(&waveartist_lock, flags);
+
+ /*
+ * set sample count
+ */
+ waveartist_cmd2(devc, WACMD_INPUTSIZE, count);
+
+ devc->xfer_count = count;
+ devc->audio_mode |= PCM_ENABLE_INPUT;
+
+ spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static int
+waveartist_ioctl(int dev, unsigned int cmd, void __user * arg)
+{
+ return -EINVAL;
+}
+
+static unsigned int
+waveartist_get_speed(wavnc_port_info *portc)
+{
+ unsigned int speed;
+
+ /*
+ * program the speed, channels, bits
+ */
+ if (portc->speed == 8000)
+ speed = 0x2E71;
+ else if (portc->speed == 11025)
+ speed = 0x4000;
+ else if (portc->speed == 22050)
+ speed = 0x8000;
+ else if (portc->speed == 44100)
+ speed = 0x0;
+ else {
+ /*
+ * non-standard - just calculate
+ */
+ speed = portc->speed << 16;
+
+ speed = (speed / 44100) & 65535;
+ }
+
+ return speed;
+}
+
+static unsigned int
+waveartist_get_bits(wavnc_port_info *portc)
+{
+ unsigned int bits;
+
+ if (portc->audio_format == AFMT_S16_LE)
+ bits = 1;
+ else if (portc->audio_format == AFMT_S8)
+ bits = 0;
+ else
+ bits = 2; //default AFMT_U8
+
+ return bits;
+}
+
+static int
+waveartist_prepare_for_input(int dev, int bsize, int bcount)
+{
+ unsigned long flags;
+ wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
+ wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+ unsigned int speed, bits;
+
+ if (devc->audio_mode)
+ return 0;
+
+ speed = waveartist_get_speed(portc);
+ bits = waveartist_get_bits(portc);
+
+ spin_lock_irqsave(&waveartist_lock, flags);
+
+ if (waveartist_cmd2(devc, WACMD_INPUTFORMAT, bits))
+ printk(KERN_WARNING "waveartist: error setting the "
+ "record format to %d\n", portc->audio_format);
+
+ if (waveartist_cmd2(devc, WACMD_INPUTCHANNELS, portc->channels))
+ printk(KERN_WARNING "waveartist: error setting record "
+ "to %d channels\n", portc->channels);
+
+ /*
+ * write cmd SetSampleSpeedTimeConstant
+ */
+ if (waveartist_cmd2(devc, WACMD_INPUTSPEED, speed))
+ printk(KERN_WARNING "waveartist: error setting the record "
+ "speed to %dHz.\n", portc->speed);
+
+ if (waveartist_cmd2(devc, WACMD_INPUTDMA, 1))
+ printk(KERN_WARNING "waveartist: error setting the record "
+ "data path to 0x%X\n", 1);
+
+ if (waveartist_cmd2(devc, WACMD_INPUTFORMAT, bits))
+ printk(KERN_WARNING "waveartist: error setting the record "
+ "format to %d\n", portc->audio_format);
+
+ devc->xfer_count = 0;
+ spin_unlock_irqrestore(&waveartist_lock, flags);
+ waveartist_halt_input(dev);
+
+ if (debug_flg & DEBUG_INTR) {
+ printk("WA CTLR reg: 0x%02X.\n",
+ inb(devc->hw.io_base + CTLR));
+ printk("WA STAT reg: 0x%02X.\n",
+ inb(devc->hw.io_base + STATR));
+ printk("WA IRQS reg: 0x%02X.\n",
+ inb(devc->hw.io_base + IRQSTAT));
+ }
+
+ return 0;
+}
+
+static int
+waveartist_prepare_for_output(int dev, int bsize, int bcount)
+{
+ unsigned long flags;
+ wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
+ wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+ unsigned int speed, bits;
+
+ /*
+ * program the speed, channels, bits
+ */
+ speed = waveartist_get_speed(portc);
+ bits = waveartist_get_bits(portc);
+
+ spin_lock_irqsave(&waveartist_lock, flags);
+
+ if (waveartist_cmd2(devc, WACMD_OUTPUTSPEED, speed) &&
+ waveartist_cmd2(devc, WACMD_OUTPUTSPEED, speed))
+ printk(KERN_WARNING "waveartist: error setting the playback "
+ "speed to %dHz.\n", portc->speed);
+
+ if (waveartist_cmd2(devc, WACMD_OUTPUTCHANNELS, portc->channels))
+ printk(KERN_WARNING "waveartist: error setting the playback "
+ "to %d channels\n", portc->channels);
+
+ if (waveartist_cmd2(devc, WACMD_OUTPUTDMA, 0))
+ printk(KERN_WARNING "waveartist: error setting the playback "
+ "data path to 0x%X\n", 0);
+
+ if (waveartist_cmd2(devc, WACMD_OUTPUTFORMAT, bits))
+ printk(KERN_WARNING "waveartist: error setting the playback "
+ "format to %d\n", portc->audio_format);
+
+ devc->xfer_count = 0;
+ spin_unlock_irqrestore(&waveartist_lock, flags);
+ waveartist_halt_output(dev);
+
+ if (debug_flg & DEBUG_INTR) {
+ printk("WA CTLR reg: 0x%02X.\n",inb(devc->hw.io_base + CTLR));
+ printk("WA STAT reg: 0x%02X.\n",inb(devc->hw.io_base + STATR));
+ printk("WA IRQS reg: 0x%02X.\n",inb(devc->hw.io_base + IRQSTAT));
+ }
+
+ return 0;
+}
+
+static void
+waveartist_halt(int dev)
+{
+ wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+ wavnc_info *devc;
+
+ if (portc->open_mode & OPEN_WRITE)
+ waveartist_halt_output(dev);
+
+ if (portc->open_mode & OPEN_READ)
+ waveartist_halt_input(dev);
+
+ devc = (wavnc_info *) audio_devs[dev]->devc;
+ devc->audio_mode = 0;
+}
+
+static void
+waveartist_halt_input(int dev)
+{
+ wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&waveartist_lock, flags);
+
+ /*
+ * Stop capture
+ */
+ waveartist_cmd1(devc, WACMD_INPUTSTOP);
+
+ devc->audio_mode &= ~PCM_ENABLE_INPUT;
+
+ /*
+ * Clear interrupt by toggling
+ * the IRQ_ACK bit in CTRL
+ */
+ if (inb(devc->hw.io_base + STATR) & IRQ_REQ)
+ waveartist_iack(devc);
+
+// devc->audio_mode &= ~PCM_ENABLE_INPUT;
+
+ spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static void
+waveartist_halt_output(int dev)
+{
+ wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&waveartist_lock, flags);
+
+ waveartist_cmd1(devc, WACMD_OUTPUTSTOP);
+
+ devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
+
+ /*
+ * Clear interrupt by toggling
+ * the IRQ_ACK bit in CTRL
+ */
+ if (inb(devc->hw.io_base + STATR) & IRQ_REQ)
+ waveartist_iack(devc);
+
+// devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
+
+ spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static void
+waveartist_trigger(int dev, int state)
+{
+ wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
+ wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+ unsigned long flags;
+
+ if (debug_flg & DEBUG_TRIGGER) {
+ printk("wavnc: audio trigger ");
+ if (state & PCM_ENABLE_INPUT)
+ printk("in ");
+ if (state & PCM_ENABLE_OUTPUT)
+ printk("out");
+ printk("\n");
+ }
+
+ spin_lock_irqsave(&waveartist_lock, flags);
+
+ state &= devc->audio_mode;
+
+ if (portc->open_mode & OPEN_READ &&
+ state & PCM_ENABLE_INPUT)
+ /*
+ * enable ADC Data Transfer to PC
+ */
+ waveartist_cmd1(devc, WACMD_INPUTSTART);
+
+ if (portc->open_mode & OPEN_WRITE &&
+ state & PCM_ENABLE_OUTPUT)
+ /*
+ * enable DAC data transfer from PC
+ */
+ waveartist_cmd1(devc, WACMD_OUTPUTSTART);
+
+ spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static int
+waveartist_set_speed(int dev, int arg)
+{
+ wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+
+ if (arg <= 0)
+ return portc->speed;
+
+ if (arg < 5000)
+ arg = 5000;
+ if (arg > 44100)
+ arg = 44100;
+
+ portc->speed = arg;
+ return portc->speed;
+
+}
+
+static short
+waveartist_set_channels(int dev, short arg)
+{
+ wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+
+ if (arg != 1 && arg != 2)
+ return portc->channels;
+
+ portc->channels = arg;
+ return arg;
+}
+
+static unsigned int
+waveartist_set_bits(int dev, unsigned int arg)
+{
+ wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+
+ if (arg == 0)
+ return portc->audio_format;
+
+ if ((arg != AFMT_U8) && (arg != AFMT_S16_LE) && (arg != AFMT_S8))
+ arg = AFMT_U8;
+
+ portc->audio_format = arg;
+
+ return arg;
+}
+
+static struct audio_driver waveartist_audio_driver = {
+ .owner = THIS_MODULE,
+ .open = waveartist_open,
+ .close = waveartist_close,
+ .output_block = waveartist_output_block,
+ .start_input = waveartist_start_input,
+ .ioctl = waveartist_ioctl,
+ .prepare_for_input = waveartist_prepare_for_input,
+ .prepare_for_output = waveartist_prepare_for_output,
+ .halt_io = waveartist_halt,
+ .halt_input = waveartist_halt_input,
+ .halt_output = waveartist_halt_output,
+ .trigger = waveartist_trigger,
+ .set_speed = waveartist_set_speed,
+ .set_bits = waveartist_set_bits,
+ .set_channels = waveartist_set_channels
+};
+
+
+static irqreturn_t
+waveartist_intr(int irq, void *dev_id)
+{
+ wavnc_info *devc = dev_id;
+ int irqstatus, status;
+
+ spin_lock(&waveartist_lock);
+ irqstatus = inb(devc->hw.io_base + IRQSTAT);
+ status = inb(devc->hw.io_base + STATR);
+
+ if (debug_flg & DEBUG_INTR)
+ printk("waveartist_intr: stat=%02x, irqstat=%02x\n",
+ status, irqstatus);
+
+ if (status & IRQ_REQ) /* Clear interrupt */
+ waveartist_iack(devc);
+ else
+ printk(KERN_WARNING "waveartist: unexpected interrupt\n");
+
+ if (irqstatus & 0x01) {
+ int temp = 1;
+
+ /* PCM buffer done
+ */
+ if ((status & DMA0) && (devc->audio_mode & PCM_ENABLE_OUTPUT)) {
+ DMAbuf_outputintr(devc->playback_dev, 1);
+ temp = 0;
+ }
+ if ((status & DMA1) && (devc->audio_mode & PCM_ENABLE_INPUT)) {
+ DMAbuf_inputintr(devc->record_dev);
+ temp = 0;
+ }
+ if (temp) //default:
+ printk(KERN_WARNING "waveartist: Unknown interrupt\n");
+ }
+ if (irqstatus & 0x2)
+ // We do not use SB mode natively...
+ printk(KERN_WARNING "waveartist: Unexpected SB interrupt...\n");
+ spin_unlock(&waveartist_lock);
+ return IRQ_HANDLED;
+}
+
+/* -------------------------------------------------------------------------
+ * Mixer stuff
+ */
+struct mix_ent {
+ unsigned char reg_l;
+ unsigned char reg_r;
+ unsigned char shift;
+ unsigned char max;
+};
+
+static const struct mix_ent mix_devs[SOUND_MIXER_NRDEVICES] = {
+ { 2, 6, 1, 7 }, /* SOUND_MIXER_VOLUME */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_BASS */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_TREBLE */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_SYNTH */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_PCM */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_SPEAKER */
+ { 0, 4, 6, 31 }, /* SOUND_MIXER_LINE */
+ { 2, 6, 4, 3 }, /* SOUND_MIXER_MIC */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_CD */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_IMIX */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_ALTPCM */
+#if 0
+ { 3, 7, 0, 10 }, /* SOUND_MIXER_RECLEV */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_IGAIN */
+#else
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_RECLEV */
+ { 3, 7, 0, 7 }, /* SOUND_MIXER_IGAIN */
+#endif
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_OGAIN */
+ { 0, 4, 1, 31 }, /* SOUND_MIXER_LINE1 */
+ { 1, 5, 6, 31 }, /* SOUND_MIXER_LINE2 */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_LINE3 */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_DIGITAL1 */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_DIGITAL2 */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_DIGITAL3 */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_PHONEIN */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_PHONEOUT */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_VIDEO */
+ { 0, 0, 0, 0 }, /* SOUND_MIXER_RADIO */
+ { 0, 0, 0, 0 } /* SOUND_MIXER_MONITOR */
+};
+
+static void
+waveartist_mixer_update(wavnc_info *devc, int whichDev)
+{
+ unsigned int lev_left, lev_right;
+
+ lev_left = devc->levels[whichDev] & 0xff;
+ lev_right = devc->levels[whichDev] >> 8;
+
+ if (lev_left > 100)
+ lev_left = 100;
+ if (lev_right > 100)
+ lev_right = 100;
+
+#define SCALE(lev,max) ((lev) * (max) / 100)
+
+ if (machine_is_netwinder() && whichDev == SOUND_MIXER_PHONEOUT)
+ whichDev = SOUND_MIXER_VOLUME;
+
+ if (mix_devs[whichDev].reg_l || mix_devs[whichDev].reg_r) {
+ const struct mix_ent *mix = mix_devs + whichDev;
+ unsigned int mask, left, right;
+
+ mask = mix->max << mix->shift;
+ lev_left = SCALE(lev_left, mix->max) << mix->shift;
+ lev_right = SCALE(lev_right, mix->max) << mix->shift;
+
+ /* read left setting */
+ left = waveartist_cmd1_r(devc, WACMD_GET_LEVEL |
+ mix->reg_l << 8);
+
+ /* read right setting */
+ right = waveartist_cmd1_r(devc, WACMD_GET_LEVEL |
+ mix->reg_r << 8);
+
+ left = (left & ~mask) | (lev_left & mask);
+ right = (right & ~mask) | (lev_right & mask);
+
+ /* write left,right back */
+ waveartist_cmd3(devc, WACMD_SET_MIXER, left, right);
+ } else {
+ switch(whichDev) {
+ case SOUND_MIXER_PCM:
+ waveartist_cmd3(devc, WACMD_SET_LEVEL,
+ SCALE(lev_left, 32767),
+ SCALE(lev_right, 32767));
+ break;
+
+ case SOUND_MIXER_SYNTH:
+ waveartist_cmd3(devc, 0x0100 | WACMD_SET_LEVEL,
+ SCALE(lev_left, 32767),
+ SCALE(lev_right, 32767));
+ break;
+ }
+ }
+}
+
+/*
+ * Set the ADC MUX to the specified values. We do NOT do any
+ * checking of the values passed, since we assume that the
+ * relevant *_select_input function has done that for us.
+ */
+static void
+waveartist_set_adc_mux(wavnc_info *devc, char left_dev, char right_dev)
+{
+ unsigned int reg_08, reg_09;
+
+ reg_08 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x0800);
+ reg_09 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x0900);
+
+ reg_08 = (reg_08 & ~0x3f) | right_dev << 3 | left_dev;
+
+ waveartist_cmd3(devc, WACMD_SET_MIXER, reg_08, reg_09);
+}
+
+/*
+ * Decode a recording mask into a mixer selection as follows:
+ *
+ * OSS Source WA Source Actual source
+ * SOUND_MASK_IMIX Mixer Mixer output (same as AD1848)
+ * SOUND_MASK_LINE Line Line in
+ * SOUND_MASK_LINE1 Aux 1 Aux 1 in
+ * SOUND_MASK_LINE2 Aux 2 Aux 2 in
+ * SOUND_MASK_MIC Mic Microphone
+ */
+static unsigned int
+waveartist_select_input(wavnc_info *devc, unsigned int recmask,
+ unsigned char *dev_l, unsigned char *dev_r)
+{
+ unsigned int recdev = ADC_MUX_NONE;
+
+ if (recmask & SOUND_MASK_IMIX) {
+ recmask = SOUND_MASK_IMIX;
+ recdev = ADC_MUX_MIXER;
+ } else if (recmask & SOUND_MASK_LINE2) {
+ recmask = SOUND_MASK_LINE2;
+ recdev = ADC_MUX_AUX2;
+ } else if (recmask & SOUND_MASK_LINE1) {
+ recmask = SOUND_MASK_LINE1;
+ recdev = ADC_MUX_AUX1;
+ } else if (recmask & SOUND_MASK_LINE) {
+ recmask = SOUND_MASK_LINE;
+ recdev = ADC_MUX_LINE;
+ } else if (recmask & SOUND_MASK_MIC) {
+ recmask = SOUND_MASK_MIC;
+ recdev = ADC_MUX_MIC;
+ }
+
+ *dev_l = *dev_r = recdev;
+
+ return recmask;
+}
+
+static int
+waveartist_decode_mixer(wavnc_info *devc, int dev, unsigned char lev_l,
+ unsigned char lev_r)
+{
+ switch (dev) {
+ case SOUND_MIXER_VOLUME:
+ case SOUND_MIXER_SYNTH:
+ case SOUND_MIXER_PCM:
+ case SOUND_MIXER_LINE:
+ case SOUND_MIXER_MIC:
+ case SOUND_MIXER_IGAIN:
+ case SOUND_MIXER_LINE1:
+ case SOUND_MIXER_LINE2:
+ devc->levels[dev] = lev_l | lev_r << 8;
+ break;
+
+ case SOUND_MIXER_IMIX:
+ break;
+
+ default:
+ dev = -EINVAL;
+ break;
+ }
+
+ return dev;
+}
+
+static int waveartist_get_mixer(wavnc_info *devc, int dev)
+{
+ return devc->levels[dev];
+}
+
+static const struct waveartist_mixer_info waveartist_mixer = {
+ .supported_devs = SUPPORTED_MIXER_DEVICES | SOUND_MASK_IGAIN,
+ .recording_devs = SOUND_MASK_LINE | SOUND_MASK_MIC |
+ SOUND_MASK_LINE1 | SOUND_MASK_LINE2 |
+ SOUND_MASK_IMIX,
+ .stereo_devs = (SUPPORTED_MIXER_DEVICES | SOUND_MASK_IGAIN) & ~
+ (SOUND_MASK_SPEAKER | SOUND_MASK_IMIX),
+ .select_input = waveartist_select_input,
+ .decode_mixer = waveartist_decode_mixer,
+ .get_mixer = waveartist_get_mixer,
+};
+
+static void
+waveartist_set_recmask(wavnc_info *devc, unsigned int recmask)
+{
+ unsigned char dev_l, dev_r;
+
+ recmask &= devc->mix->recording_devs;
+
+ /*
+ * If more than one recording device selected,
+ * disable the device that is currently in use.
+ */
+ if (hweight32(recmask) > 1)
+ recmask &= ~devc->recmask;
+
+ /*
+ * Translate the recording device mask into
+ * the ADC multiplexer settings.
+ */
+ devc->recmask = devc->mix->select_input(devc, recmask,
+ &dev_l, &dev_r);
+
+ waveartist_set_adc_mux(devc, dev_l, dev_r);
+}
+
+static int
+waveartist_set_mixer(wavnc_info *devc, int dev, unsigned int level)
+{
+ unsigned int lev_left = level & 0x00ff;
+ unsigned int lev_right = (level & 0xff00) >> 8;
+
+ if (lev_left > 100)
+ lev_left = 100;
+ if (lev_right > 100)
+ lev_right = 100;
+
+ /*
+ * Mono devices have their right volume forced to their
+ * left volume. (from ALSA driver OSS emulation).
+ */
+ if (!(devc->mix->stereo_devs & (1 << dev)))
+ lev_right = lev_left;
+
+ dev = devc->mix->decode_mixer(devc, dev, lev_left, lev_right);
+
+ if (dev >= 0)
+ waveartist_mixer_update(devc, dev);
+
+ return dev < 0 ? dev : 0;
+}
+
+static int
+waveartist_mixer_ioctl(int dev, unsigned int cmd, void __user * arg)
+{
+ wavnc_info *devc = (wavnc_info *)audio_devs[dev]->devc;
+ int ret = 0, val, nr;
+
+ /*
+ * All SOUND_MIXER_* ioctls use type 'M'
+ */
+ if (((cmd >> 8) & 255) != 'M')
+ return -ENOIOCTLCMD;
+
+#ifdef CONFIG_ARCH_NETWINDER
+ if (machine_is_netwinder()) {
+ ret = vnc_private_ioctl(dev, cmd, arg);
+ if (ret != -ENOIOCTLCMD)
+ return ret;
+ else
+ ret = 0;
+ }
+#endif
+
+ nr = cmd & 0xff;
+
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+
+ switch (nr) {
+ case SOUND_MIXER_RECSRC:
+ waveartist_set_recmask(devc, val);
+ break;
+
+ default:
+ ret = -EINVAL;
+ if (nr < SOUND_MIXER_NRDEVICES &&
+ devc->mix->supported_devs & (1 << nr))
+ ret = waveartist_set_mixer(devc, nr, val);
+ }
+ }
+
+ if (ret == 0 && _SIOC_DIR(cmd) & _SIOC_READ) {
+ ret = -EINVAL;
+
+ switch (nr) {
+ case SOUND_MIXER_RECSRC:
+ ret = devc->recmask;
+ break;
+
+ case SOUND_MIXER_DEVMASK:
+ ret = devc->mix->supported_devs;
+ break;
+
+ case SOUND_MIXER_STEREODEVS:
+ ret = devc->mix->stereo_devs;
+ break;
+
+ case SOUND_MIXER_RECMASK:
+ ret = devc->mix->recording_devs;
+ break;
+
+ case SOUND_MIXER_CAPS:
+ ret = SOUND_CAP_EXCL_INPUT;
+ break;
+
+ default:
+ if (nr < SOUND_MIXER_NRDEVICES)
+ ret = devc->mix->get_mixer(devc, nr);
+ break;
+ }
+
+ if (ret >= 0)
+ ret = put_user(ret, (int __user *)arg) ? -EFAULT : 0;
+ }
+
+ return ret;
+}
+
+static struct mixer_operations waveartist_mixer_operations =
+{
+ .owner = THIS_MODULE,
+ .id = "WaveArtist",
+ .name = "WaveArtist",
+ .ioctl = waveartist_mixer_ioctl
+};
+
+static void
+waveartist_mixer_reset(wavnc_info *devc)
+{
+ int i;
+
+ if (debug_flg & DEBUG_MIXER)
+ printk("%s: mixer_reset\n", devc->hw.name);
+
+ /*
+ * reset mixer cmd
+ */
+ waveartist_cmd1(devc, WACMD_RST_MIXER);
+
+ /*
+ * set input for ADC to come from 'quiet'
+ * turn on default modes
+ */
+ waveartist_cmd3(devc, WACMD_SET_MIXER, 0x9800, 0xa836);
+
+ /*
+ * set mixer input select to none, RX filter gains 0 dB
+ */
+ waveartist_cmd3(devc, WACMD_SET_MIXER, 0x4c00, 0x8c00);
+
+ /*
+ * set bit 0 reg 2 to 1 - unmute MonoOut
+ */
+ waveartist_cmd3(devc, WACMD_SET_MIXER, 0x2801, 0x6800);
+
+ /* set default input device = internal mic
+ * current recording device = none
+ */
+ waveartist_set_recmask(devc, 0);
+
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ waveartist_mixer_update(devc, i);
+}
+
+static int __init waveartist_init(wavnc_info *devc)
+{
+ wavnc_port_info *portc;
+ char rev[3], dev_name[64];
+ int my_dev;
+
+ if (waveartist_reset(devc))
+ return -ENODEV;
+
+ sprintf(dev_name, "%s (%s", devc->hw.name, devc->chip_name);
+
+ if (waveartist_getrev(devc, rev)) {
+ strcat(dev_name, " rev. ");
+ strcat(dev_name, rev);
+ }
+ strcat(dev_name, ")");
+
+ conf_printf2(dev_name, devc->hw.io_base, devc->hw.irq,
+ devc->hw.dma, devc->hw.dma2);
+
+ portc = kzalloc(sizeof(wavnc_port_info), GFP_KERNEL);
+ if (portc == NULL)
+ goto nomem;
+
+ my_dev = sound_install_audiodrv(AUDIO_DRIVER_VERSION, dev_name,
+ &waveartist_audio_driver, sizeof(struct audio_driver),
+ devc->audio_flags, AFMT_U8 | AFMT_S16_LE | AFMT_S8,
+ devc, devc->hw.dma, devc->hw.dma2);
+
+ if (my_dev < 0)
+ goto free;
+
+ audio_devs[my_dev]->portc = portc;
+
+ waveartist_mixer_reset(devc);
+
+ /*
+ * clear any pending interrupt
+ */
+ waveartist_iack(devc);
+
+ if (request_irq(devc->hw.irq, waveartist_intr, 0, devc->hw.name, devc) < 0) {
+ printk(KERN_ERR "%s: IRQ %d in use\n",
+ devc->hw.name, devc->hw.irq);
+ goto uninstall;
+ }
+
+ if (sound_alloc_dma(devc->hw.dma, devc->hw.name)) {
+ printk(KERN_ERR "%s: Can't allocate DMA%d\n",
+ devc->hw.name, devc->hw.dma);
+ goto uninstall_irq;
+ }
+
+ if (devc->hw.dma != devc->hw.dma2 && devc->hw.dma2 != NO_DMA)
+ if (sound_alloc_dma(devc->hw.dma2, devc->hw.name)) {
+ printk(KERN_ERR "%s: can't allocate DMA%d\n",
+ devc->hw.name, devc->hw.dma2);
+ goto uninstall_dma;
+ }
+
+ waveartist_set_ctlr(&devc->hw, 0, DMA1_IE | DMA0_IE);
+
+ audio_devs[my_dev]->mixer_dev =
+ sound_install_mixer(MIXER_DRIVER_VERSION,
+ dev_name,
+ &waveartist_mixer_operations,
+ sizeof(struct mixer_operations),
+ devc);
+
+ return my_dev;
+
+uninstall_dma:
+ sound_free_dma(devc->hw.dma);
+
+uninstall_irq:
+ free_irq(devc->hw.irq, devc);
+
+uninstall:
+ sound_unload_audiodev(my_dev);
+
+free:
+ kfree(portc);
+
+nomem:
+ return -1;
+}
+
+static int __init probe_waveartist(struct address_info *hw_config)
+{
+ wavnc_info *devc = &adev_info[nr_waveartist_devs];
+
+ if (nr_waveartist_devs >= MAX_AUDIO_DEV) {
+ printk(KERN_WARNING "waveartist: too many audio devices\n");
+ return 0;
+ }
+
+ if (!request_region(hw_config->io_base, 15, hw_config->name)) {
+ printk(KERN_WARNING "WaveArtist: I/O port conflict\n");
+ return 0;
+ }
+
+ if (hw_config->irq > 15 || hw_config->irq < 0) {
+ release_region(hw_config->io_base, 15);
+ printk(KERN_WARNING "WaveArtist: Bad IRQ %d\n",
+ hw_config->irq);
+ return 0;
+ }
+
+ if (hw_config->dma != 3) {
+ release_region(hw_config->io_base, 15);
+ printk(KERN_WARNING "WaveArtist: Bad DMA %d\n",
+ hw_config->dma);
+ return 0;
+ }
+
+ hw_config->name = "WaveArtist";
+ devc->hw = *hw_config;
+ devc->open_mode = 0;
+ devc->chip_name = "RWA-010";
+
+ return 1;
+}
+
+static void __init
+attach_waveartist(struct address_info *hw, const struct waveartist_mixer_info *mix)
+{
+ wavnc_info *devc = &adev_info[nr_waveartist_devs];
+
+ /*
+ * NOTE! If irq < 0, there is another driver which has allocated the
+ * IRQ so that this driver doesn't need to allocate/deallocate it.
+ * The actually used IRQ is ABS(irq).
+ */
+ devc->hw = *hw;
+ devc->hw.irq = (hw->irq > 0) ? hw->irq : 0;
+ devc->open_mode = 0;
+ devc->playback_dev = 0;
+ devc->record_dev = 0;
+ devc->audio_flags = DMA_AUTOMODE;
+ devc->levels = levels;
+
+ if (hw->dma != hw->dma2 && hw->dma2 != NO_DMA)
+ devc->audio_flags |= DMA_DUPLEX;
+
+ devc->mix = mix;
+ devc->dev_no = waveartist_init(devc);
+
+ if (devc->dev_no < 0)
+ release_region(hw->io_base, 15);
+ else {
+#ifdef CONFIG_ARCH_NETWINDER
+ if (machine_is_netwinder()) {
+ init_timer(&vnc_timer);
+ vnc_timer.function = vnc_slider_tick;
+ vnc_timer.expires = jiffies;
+ vnc_timer.data = nr_waveartist_devs;
+ add_timer(&vnc_timer);
+
+ vnc_configure_mixer(devc, 0);
+
+ devc->no_autoselect = 1;
+ }
+#endif
+ nr_waveartist_devs += 1;
+ }
+}
+
+static void __exit unload_waveartist(struct address_info *hw)
+{
+ wavnc_info *devc = NULL;
+ int i;
+
+ for (i = 0; i < nr_waveartist_devs; i++)
+ if (hw->io_base == adev_info[i].hw.io_base) {
+ devc = adev_info + i;
+ break;
+ }
+
+ if (devc != NULL) {
+ int mixer;
+
+#ifdef CONFIG_ARCH_NETWINDER
+ if (machine_is_netwinder())
+ del_timer(&vnc_timer);
+#endif
+
+ release_region(devc->hw.io_base, 15);
+
+ waveartist_set_ctlr(&devc->hw, DMA1_IE|DMA0_IE, 0);
+
+ if (devc->hw.irq >= 0)
+ free_irq(devc->hw.irq, devc);
+
+ sound_free_dma(devc->hw.dma);
+
+ if (devc->hw.dma != devc->hw.dma2 &&
+ devc->hw.dma2 != NO_DMA)
+ sound_free_dma(devc->hw.dma2);
+
+ mixer = audio_devs[devc->dev_no]->mixer_dev;
+
+ if (mixer >= 0)
+ sound_unload_mixerdev(mixer);
+
+ if (devc->dev_no >= 0)
+ sound_unload_audiodev(devc->dev_no);
+
+ nr_waveartist_devs -= 1;
+
+ for (; i < nr_waveartist_devs; i++)
+ adev_info[i] = adev_info[i + 1];
+ } else
+ printk(KERN_WARNING "waveartist: can't find device "
+ "to unload\n");
+}
+
+#ifdef CONFIG_ARCH_NETWINDER
+
+/*
+ * Rebel.com Netwinder specifics...
+ */
+
+#include <asm/hardware/dec21285.h>
+
+#define VNC_TIMER_PERIOD (HZ/4) //check slider 4 times/sec
+
+#define MIXER_PRIVATE3_RESET 0x53570000
+#define MIXER_PRIVATE3_READ 0x53570001
+#define MIXER_PRIVATE3_WRITE 0x53570002
+
+#define VNC_MUTE_INTERNAL_SPKR 0x01 //the sw mute on/off control bit
+#define VNC_MUTE_LINE_OUT 0x10
+#define VNC_PHONE_DETECT 0x20
+#define VNC_HANDSET_DETECT 0x40
+#define VNC_DISABLE_AUTOSWITCH 0x80
+
+static inline void
+vnc_mute_spkr(wavnc_info *devc)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&nw_gpio_lock, flags);
+ nw_cpld_modify(CPLD_UNMUTE, devc->spkr_mute_state ? 0 : CPLD_UNMUTE);
+ spin_unlock_irqrestore(&nw_gpio_lock, flags);
+}
+
+static void
+vnc_mute_lout(wavnc_info *devc)
+{
+ unsigned int left, right;
+
+ left = waveartist_cmd1_r(devc, WACMD_GET_LEVEL);
+ right = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x400);
+
+ if (devc->line_mute_state) {
+ left &= ~1;
+ right &= ~1;
+ } else {
+ left |= 1;
+ right |= 1;
+ }
+ waveartist_cmd3(devc, WACMD_SET_MIXER, left, right);
+
+}
+
+static int
+vnc_volume_slider(wavnc_info *devc)
+{
+ static signed int old_slider_volume;
+ unsigned long flags;
+ signed int volume = 255;
+
+ *CSR_TIMER1_LOAD = 0x00ffffff;
+
+ spin_lock_irqsave(&waveartist_lock, flags);
+
+ outb(0xFF, 0x201);
+ *CSR_TIMER1_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_DIV1;
+
+ while (volume && (inb(0x201) & 0x01))
+ volume--;
+
+ *CSR_TIMER1_CNTL = 0;
+
+ spin_unlock_irqrestore(&waveartist_lock,flags);
+
+ volume = 0x00ffffff - *CSR_TIMER1_VALUE;
+
+
+#ifndef REVERSE
+ volume = 150 - (volume >> 5);
+#else
+ volume = (volume >> 6) - 25;
+#endif
+
+ if (volume < 0)
+ volume = 0;
+
+ if (volume > 100)
+ volume = 100;
+
+ /*
+ * slider quite often reads +-8, so debounce this random noise
+ */
+ if (abs(volume - old_slider_volume) > 7) {
+ old_slider_volume = volume;
+
+ if (debug_flg & DEBUG_MIXER)
+ printk(KERN_DEBUG "Slider volume: %d.\n", volume);
+ }
+
+ return old_slider_volume;
+}
+
+/*
+ * Decode a recording mask into a mixer selection on the NetWinder
+ * as follows:
+ *
+ * OSS Source WA Source Actual source
+ * SOUND_MASK_IMIX Mixer Mixer output (same as AD1848)
+ * SOUND_MASK_LINE Line Line in
+ * SOUND_MASK_LINE1 Left Mic Handset
+ * SOUND_MASK_PHONEIN Left Aux Telephone microphone
+ * SOUND_MASK_MIC Right Mic Builtin microphone
+ */
+static unsigned int
+netwinder_select_input(wavnc_info *devc, unsigned int recmask,
+ unsigned char *dev_l, unsigned char *dev_r)
+{
+ unsigned int recdev_l = ADC_MUX_NONE, recdev_r = ADC_MUX_NONE;
+
+ if (recmask & SOUND_MASK_IMIX) {
+ recmask = SOUND_MASK_IMIX;
+ recdev_l = ADC_MUX_MIXER;
+ recdev_r = ADC_MUX_MIXER;
+ } else if (recmask & SOUND_MASK_LINE) {
+ recmask = SOUND_MASK_LINE;
+ recdev_l = ADC_MUX_LINE;
+ recdev_r = ADC_MUX_LINE;
+ } else if (recmask & SOUND_MASK_LINE1) {
+ recmask = SOUND_MASK_LINE1;
+ waveartist_cmd1(devc, WACMD_SET_MONO); /* left */
+ recdev_l = ADC_MUX_MIC;
+ recdev_r = ADC_MUX_NONE;
+ } else if (recmask & SOUND_MASK_PHONEIN) {
+ recmask = SOUND_MASK_PHONEIN;
+ waveartist_cmd1(devc, WACMD_SET_MONO); /* left */
+ recdev_l = ADC_MUX_AUX1;
+ recdev_r = ADC_MUX_NONE;
+ } else if (recmask & SOUND_MASK_MIC) {
+ recmask = SOUND_MASK_MIC;
+ waveartist_cmd1(devc, WACMD_SET_MONO | 0x100); /* right */
+ recdev_l = ADC_MUX_NONE;
+ recdev_r = ADC_MUX_MIC;
+ }
+
+ *dev_l = recdev_l;
+ *dev_r = recdev_r;
+
+ return recmask;
+}
+
+static int
+netwinder_decode_mixer(wavnc_info *devc, int dev, unsigned char lev_l,
+ unsigned char lev_r)
+{
+ switch (dev) {
+ case SOUND_MIXER_VOLUME:
+ case SOUND_MIXER_SYNTH:
+ case SOUND_MIXER_PCM:
+ case SOUND_MIXER_LINE:
+ case SOUND_MIXER_IGAIN:
+ devc->levels[dev] = lev_l | lev_r << 8;
+ break;
+
+ case SOUND_MIXER_MIC: /* right mic only */
+ devc->levels[SOUND_MIXER_MIC] &= 0xff;
+ devc->levels[SOUND_MIXER_MIC] |= lev_l << 8;
+ break;
+
+ case SOUND_MIXER_LINE1: /* left mic only */
+ devc->levels[SOUND_MIXER_MIC] &= 0xff00;
+ devc->levels[SOUND_MIXER_MIC] |= lev_l;
+ dev = SOUND_MIXER_MIC;
+ break;
+
+ case SOUND_MIXER_PHONEIN: /* left aux only */
+ devc->levels[SOUND_MIXER_LINE1] = lev_l;
+ dev = SOUND_MIXER_LINE1;
+ break;
+
+ case SOUND_MIXER_IMIX:
+ case SOUND_MIXER_PHONEOUT:
+ break;
+
+ default:
+ dev = -EINVAL;
+ break;
+ }
+ return dev;
+}
+
+static int netwinder_get_mixer(wavnc_info *devc, int dev)
+{
+ int levels;
+
+ switch (dev) {
+ case SOUND_MIXER_VOLUME:
+ case SOUND_MIXER_SYNTH:
+ case SOUND_MIXER_PCM:
+ case SOUND_MIXER_LINE:
+ case SOUND_MIXER_IGAIN:
+ levels = devc->levels[dev];
+ break;
+
+ case SOUND_MIXER_MIC: /* builtin mic: right mic only */
+ levels = devc->levels[SOUND_MIXER_MIC] >> 8;
+ levels |= levels << 8;
+ break;
+
+ case SOUND_MIXER_LINE1: /* handset mic: left mic only */
+ levels = devc->levels[SOUND_MIXER_MIC] & 0xff;
+ levels |= levels << 8;
+ break;
+
+ case SOUND_MIXER_PHONEIN: /* phone mic: left aux1 only */
+ levels = devc->levels[SOUND_MIXER_LINE1] & 0xff;
+ levels |= levels << 8;
+ break;
+
+ default:
+ levels = 0;
+ }
+
+ return levels;
+}
+
+/*
+ * Waveartist specific mixer information.
+ */
+static const struct waveartist_mixer_info netwinder_mixer = {
+ .supported_devs = SOUND_MASK_VOLUME | SOUND_MASK_SYNTH |
+ SOUND_MASK_PCM | SOUND_MASK_SPEAKER |
+ SOUND_MASK_LINE | SOUND_MASK_MIC |
+ SOUND_MASK_IMIX | SOUND_MASK_LINE1 |
+ SOUND_MASK_PHONEIN | SOUND_MASK_PHONEOUT|
+ SOUND_MASK_IGAIN,
+
+ .recording_devs = SOUND_MASK_LINE | SOUND_MASK_MIC |
+ SOUND_MASK_IMIX | SOUND_MASK_LINE1 |
+ SOUND_MASK_PHONEIN,
+
+ .stereo_devs = SOUND_MASK_VOLUME | SOUND_MASK_SYNTH |
+ SOUND_MASK_PCM | SOUND_MASK_LINE |
+ SOUND_MASK_IMIX | SOUND_MASK_IGAIN,
+
+ .select_input = netwinder_select_input,
+ .decode_mixer = netwinder_decode_mixer,
+ .get_mixer = netwinder_get_mixer,
+};
+
+static void
+vnc_configure_mixer(wavnc_info *devc, unsigned int recmask)
+{
+ if (!devc->no_autoselect) {
+ if (devc->handset_detect) {
+ recmask = SOUND_MASK_LINE1;
+ devc->spkr_mute_state = devc->line_mute_state = 1;
+ } else if (devc->telephone_detect) {
+ recmask = SOUND_MASK_PHONEIN;
+ devc->spkr_mute_state = devc->line_mute_state = 1;
+ } else {
+ /* unless someone has asked for LINE-IN,
+ * we default to MIC
+ */
+ if ((devc->recmask & SOUND_MASK_LINE) == 0)
+ devc->recmask = SOUND_MASK_MIC;
+ devc->spkr_mute_state = devc->line_mute_state = 0;
+ }
+ vnc_mute_spkr(devc);
+ vnc_mute_lout(devc);
+
+ if (recmask != devc->recmask)
+ waveartist_set_recmask(devc, recmask);
+ }
+}
+
+static int
+vnc_slider(wavnc_info *devc)
+{
+ signed int slider_volume;
+ unsigned int temp, old_hs, old_td;
+
+ /*
+ * read the "buttons" state.
+ * Bit 4 = 0 means handset present
+ * Bit 5 = 1 means phone offhook
+ */
+ temp = inb(0x201);
+
+ old_hs = devc->handset_detect;
+ old_td = devc->telephone_detect;
+
+ devc->handset_detect = !(temp & 0x10);
+ devc->telephone_detect = !!(temp & 0x20);
+
+ if (!devc->no_autoselect &&
+ (old_hs != devc->handset_detect ||
+ old_td != devc->telephone_detect))
+ vnc_configure_mixer(devc, devc->recmask);
+
+ slider_volume = vnc_volume_slider(devc);
+
+ /*
+ * If we're using software controlled volume, and
+ * the slider moves by more than 20%, then we
+ * switch back to slider controlled volume.
+ */
+ if (abs(devc->slider_vol - slider_volume) > 20)
+ devc->use_slider = 1;
+
+ /*
+ * use only left channel
+ */
+ temp = levels[SOUND_MIXER_VOLUME] & 0xFF;
+
+ if (slider_volume != temp && devc->use_slider) {
+ devc->slider_vol = slider_volume;
+
+ waveartist_set_mixer(devc, SOUND_MIXER_VOLUME,
+ slider_volume | slider_volume << 8);
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+vnc_slider_tick(unsigned long data)
+{
+ int next_timeout;
+
+ if (vnc_slider(adev_info + data))
+ next_timeout = 5; // mixer reported change
+ else
+ next_timeout = VNC_TIMER_PERIOD;
+
+ mod_timer(&vnc_timer, jiffies + next_timeout);
+}
+
+static int
+vnc_private_ioctl(int dev, unsigned int cmd, int __user * arg)
+{
+ wavnc_info *devc = (wavnc_info *)audio_devs[dev]->devc;
+ int val;
+
+ switch (cmd) {
+ case SOUND_MIXER_PRIVATE1:
+ {
+ u_int prev_spkr_mute, prev_line_mute, prev_auto_state;
+ int val;
+
+ if (get_user(val, arg))
+ return -EFAULT;
+
+ /* check if parameter is logical */
+ if (val & ~(VNC_MUTE_INTERNAL_SPKR |
+ VNC_MUTE_LINE_OUT |
+ VNC_DISABLE_AUTOSWITCH))
+ return -EINVAL;
+
+ prev_auto_state = devc->no_autoselect;
+ prev_spkr_mute = devc->spkr_mute_state;
+ prev_line_mute = devc->line_mute_state;
+
+ devc->no_autoselect = (val & VNC_DISABLE_AUTOSWITCH) ? 1 : 0;
+ devc->spkr_mute_state = (val & VNC_MUTE_INTERNAL_SPKR) ? 1 : 0;
+ devc->line_mute_state = (val & VNC_MUTE_LINE_OUT) ? 1 : 0;
+
+ if (prev_spkr_mute != devc->spkr_mute_state)
+ vnc_mute_spkr(devc);
+
+ if (prev_line_mute != devc->line_mute_state)
+ vnc_mute_lout(devc);
+
+ if (prev_auto_state != devc->no_autoselect)
+ vnc_configure_mixer(devc, devc->recmask);
+
+ return 0;
+ }
+
+ case SOUND_MIXER_PRIVATE2:
+ if (get_user(val, arg))
+ return -EFAULT;
+
+ switch (val) {
+#define VNC_SOUND_PAUSE 0x53 //to pause the DSP
+#define VNC_SOUND_RESUME 0x57 //to unpause the DSP
+ case VNC_SOUND_PAUSE:
+ waveartist_cmd1(devc, 0x16);
+ break;
+
+ case VNC_SOUND_RESUME:
+ waveartist_cmd1(devc, 0x18);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+
+ /* private ioctl to allow bulk access to waveartist */
+ case SOUND_MIXER_PRIVATE3:
+ {
+ unsigned long flags;
+ int mixer_reg[15], i, val;
+
+ if (get_user(val, arg))
+ return -EFAULT;
+ if (copy_from_user(mixer_reg, (void *)val, sizeof(mixer_reg)))
+ return -EFAULT;
+
+ switch (mixer_reg[14]) {
+ case MIXER_PRIVATE3_RESET:
+ waveartist_mixer_reset(devc);
+ break;
+
+ case MIXER_PRIVATE3_WRITE:
+ waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[0], mixer_reg[4]);
+ waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[1], mixer_reg[5]);
+ waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[2], mixer_reg[6]);
+ waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[3], mixer_reg[7]);
+ waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[8], mixer_reg[9]);
+
+ waveartist_cmd3(devc, WACMD_SET_LEVEL, mixer_reg[10], mixer_reg[11]);
+ waveartist_cmd3(devc, WACMD_SET_LEVEL, mixer_reg[12], mixer_reg[13]);
+ break;
+
+ case MIXER_PRIVATE3_READ:
+ spin_lock_irqsave(&waveartist_lock, flags);
+
+ for (i = 0x30; i < 14 << 8; i += 1 << 8)
+ waveartist_cmd(devc, 1, &i, 1, mixer_reg + (i >> 8));
+
+ spin_unlock_irqrestore(&waveartist_lock, flags);
+
+ if (copy_to_user((void *)val, mixer_reg, sizeof(mixer_reg)))
+ return -EFAULT;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ /* read back the state from PRIVATE1 */
+ case SOUND_MIXER_PRIVATE4:
+ val = (devc->spkr_mute_state ? VNC_MUTE_INTERNAL_SPKR : 0) |
+ (devc->line_mute_state ? VNC_MUTE_LINE_OUT : 0) |
+ (devc->handset_detect ? VNC_HANDSET_DETECT : 0) |
+ (devc->telephone_detect ? VNC_PHONE_DETECT : 0) |
+ (devc->no_autoselect ? VNC_DISABLE_AUTOSWITCH : 0);
+
+ return put_user(val, arg) ? -EFAULT : 0;
+ }
+
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+ /*
+ * special case for master volume: if we
+ * received this call - switch from hw
+ * volume control to a software volume
+ * control, till the hw volume is modified
+ * to signal that user wants to be back in
+ * hardware...
+ */
+ if ((cmd & 0xff) == SOUND_MIXER_VOLUME)
+ devc->use_slider = 0;
+
+ /* speaker output */
+ if ((cmd & 0xff) == SOUND_MIXER_SPEAKER) {
+ unsigned int val, l, r;
+
+ if (get_user(val, arg))
+ return -EFAULT;
+
+ l = val & 0x7f;
+ r = (val & 0x7f00) >> 8;
+ val = (l + r) / 2;
+ devc->levels[SOUND_MIXER_SPEAKER] = val | (val << 8);
+ devc->spkr_mute_state = (val <= 50);
+ vnc_mute_spkr(devc);
+ return 0;
+ }
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+#endif
+
+static struct address_info cfg;
+
+static int attached;
+
+static int __initdata io = 0;
+static int __initdata irq = 0;
+static int __initdata dma = 0;
+static int __initdata dma2 = 0;
+
+
+static int __init init_waveartist(void)
+{
+ const struct waveartist_mixer_info *mix;
+
+ if (!io && machine_is_netwinder()) {
+ /*
+ * The NetWinder WaveArtist is at a fixed address.
+ * If the user does not supply an address, use the
+ * well-known parameters.
+ */
+ io = 0x250;
+ irq = 12;
+ dma = 3;
+ dma2 = 7;
+ }
+
+ mix = &waveartist_mixer;
+#ifdef CONFIG_ARCH_NETWINDER
+ if (machine_is_netwinder())
+ mix = &netwinder_mixer;
+#endif
+
+ cfg.io_base = io;
+ cfg.irq = irq;
+ cfg.dma = dma;
+ cfg.dma2 = dma2;
+
+ if (!probe_waveartist(&cfg))
+ return -ENODEV;
+
+ attach_waveartist(&cfg, mix);
+ attached = 1;
+
+ return 0;
+}
+
+static void __exit cleanup_waveartist(void)
+{
+ if (attached)
+ unload_waveartist(&cfg);
+}
+
+module_init(init_waveartist);
+module_exit(cleanup_waveartist);
+
+#ifndef MODULE
+static int __init setup_waveartist(char *str)
+{
+ /* io, irq, dma, dma2 */
+ int ints[5];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ io = ints[1];
+ irq = ints[2];
+ dma = ints[3];
+ dma2 = ints[4];
+
+ return 1;
+}
+__setup("waveartist=", setup_waveartist);
+#endif
+
+MODULE_DESCRIPTION("Rockwell WaveArtist RWA-010 sound driver");
+module_param(io, int, 0); /* IO base */
+module_param(irq, int, 0); /* IRQ */
+module_param(dma, int, 0); /* DMA */
+module_param(dma2, int, 0); /* DMA2 */
+MODULE_LICENSE("GPL");
diff --git a/sound/oss/waveartist.h b/sound/oss/waveartist.h
new file mode 100644
index 00000000..dac4ca91
--- /dev/null
+++ b/sound/oss/waveartist.h
@@ -0,0 +1,92 @@
+/*
+ * linux/sound/oss/waveartist.h
+ *
+ * def file for Rockwell RWA010 chip set, as installed in Rebel.com NetWinder
+ */
+
+//registers
+#define CMDR 0
+#define DATR 2
+#define CTLR 4
+#define STATR 5
+#define IRQSTAT 12
+
+//bit defs
+//reg STATR
+#define CMD_WE 0x80
+#define CMD_RF 0x40
+#define DAT_WE 0x20
+#define DAT_RF 0x10
+
+#define IRQ_REQ 0x08
+#define DMA1 0x04
+#define DMA0 0x02
+
+//bit defs
+//reg CTLR
+#define CMD_WEIE 0x80
+#define CMD_RFIE 0x40
+#define DAT_WEIE 0x20
+#define DAT_RFIE 0x10
+
+#define RESET 0x08
+#define DMA1_IE 0x04
+#define DMA0_IE 0x02
+#define IRQ_ACK 0x01
+
+//commands
+
+#define WACMD_SYSTEMID 0x00
+#define WACMD_GETREV 0x00
+#define WACMD_INPUTFORMAT 0x10 //0-8S, 1-16S, 2-8U
+#define WACMD_INPUTCHANNELS 0x11 //1-Mono, 2-Stereo
+#define WACMD_INPUTSPEED 0x12 //sampling rate
+#define WACMD_INPUTDMA 0x13 //0-8bit, 1-16bit, 2-PIO
+#define WACMD_INPUTSIZE 0x14 //samples to interrupt
+#define WACMD_INPUTSTART 0x15 //start ADC
+#define WACMD_INPUTPAUSE 0x16 //pause ADC
+#define WACMD_INPUTSTOP 0x17 //stop ADC
+#define WACMD_INPUTRESUME 0x18 //resume ADC
+#define WACMD_INPUTPIO 0x19 //PIO ADC
+
+#define WACMD_OUTPUTFORMAT 0x20 //0-8S, 1-16S, 2-8U
+#define WACMD_OUTPUTCHANNELS 0x21 //1-Mono, 2-Stereo
+#define WACMD_OUTPUTSPEED 0x22 //sampling rate
+#define WACMD_OUTPUTDMA 0x23 //0-8bit, 1-16bit, 2-PIO
+#define WACMD_OUTPUTSIZE 0x24 //samples to interrupt
+#define WACMD_OUTPUTSTART 0x25 //start ADC
+#define WACMD_OUTPUTPAUSE 0x26 //pause ADC
+#define WACMD_OUTPUTSTOP 0x27 //stop ADC
+#define WACMD_OUTPUTRESUME 0x28 //resume ADC
+#define WACMD_OUTPUTPIO 0x29 //PIO ADC
+
+#define WACMD_GET_LEVEL 0x30
+#define WACMD_SET_LEVEL 0x31
+#define WACMD_SET_MIXER 0x32
+#define WACMD_RST_MIXER 0x33
+#define WACMD_SET_MONO 0x34
+
+/*
+ * Definitions for left/right recording input mux
+ */
+#define ADC_MUX_NONE 0
+#define ADC_MUX_MIXER 1
+#define ADC_MUX_LINE 2
+#define ADC_MUX_AUX2 3
+#define ADC_MUX_AUX1 4
+#define ADC_MUX_MIC 5
+
+/*
+ * Definitions for mixer gain settings
+ */
+#define MIX_GAIN_LINE 0 /* line in */
+#define MIX_GAIN_AUX1 1 /* aux1 */
+#define MIX_GAIN_AUX2 2 /* aux2 */
+#define MIX_GAIN_XMIC 3 /* crossover mic */
+#define MIX_GAIN_MIC 4 /* normal mic */
+#define MIX_GAIN_PREMIC 5 /* preamp mic */
+#define MIX_GAIN_OUT 6 /* output */
+#define MIX_GAIN_MONO 7 /* mono in */
+
+int wa_sendcmd(unsigned int cmd);
+int wa_writecmd(unsigned int cmd, unsigned int arg);