From 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 16 Apr 2005 15:20:36 -0700 Subject: Linux-2.6.12-rc2 Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip! --- sound/oss/CHANGELOG | 369 ++ sound/oss/COPYING | 339 ++ sound/oss/Kconfig | 1120 +++++++ sound/oss/Makefile | 187 ++ sound/oss/README.FIRST | 6 + sound/oss/ac97.c | 452 +++ sound/oss/ac97.h | 204 ++ sound/oss/ac97_codec.c | 1576 +++++++++ sound/oss/ac97_plugin_ad1980.c | 126 + sound/oss/aci.c | 711 ++++ sound/oss/aci.h | 57 + sound/oss/ad1816.c | 1369 ++++++++ sound/oss/ad1848.c | 3159 +++++++++++++++++ sound/oss/ad1848.h | 25 + sound/oss/ad1848_mixer.h | 253 ++ sound/oss/ad1889.c | 1103 ++++++ sound/oss/ad1889.h | 134 + sound/oss/adlib_card.c | 73 + sound/oss/aedsp16.c | 1381 ++++++++ sound/oss/ali5455.c | 3733 +++++++++++++++++++++ sound/oss/au1000.c | 2214 ++++++++++++ sound/oss/au1550_ac97.c | 2119 ++++++++++++ sound/oss/audio.c | 983 ++++++ sound/oss/audio_syms.c | 16 + sound/oss/awe_hw.h | 99 + sound/oss/awe_wave.c | 6147 ++++++++++++++++++++++++++++++++++ sound/oss/awe_wave.h | 77 + sound/oss/bin2hex.c | 39 + sound/oss/btaudio.c | 1136 +++++++ sound/oss/cmpci.c | 3378 +++++++++++++++++++ sound/oss/coproc.h | 12 + sound/oss/cs4232.c | 520 +++ sound/oss/cs4281/Makefile | 6 + sound/oss/cs4281/cs4281_hwdefs.h | 1234 +++++++ sound/oss/cs4281/cs4281_wrapper-24.c | 41 + sound/oss/cs4281/cs4281m.c | 4505 +++++++++++++++++++++++++ sound/oss/cs4281/cs4281pm-24.c | 84 + sound/oss/cs4281/cs4281pm.h | 74 + sound/oss/cs461x.h | 1691 ++++++++++ sound/oss/cs461x_image.h | 322 ++ sound/oss/cs46xx.c | 5794 ++++++++++++++++++++++++++++++++ sound/oss/cs46xx_wrapper-24.h | 56 + sound/oss/cs46xxpm-24.h | 52 + sound/oss/cs46xxpm.h | 70 + sound/oss/dev_table.c | 214 ++ sound/oss/dev_table.h | 405 +++ sound/oss/dm.h | 79 + sound/oss/dmabuf.c | 1298 +++++++ sound/oss/dmasound/Kconfig | 58 + sound/oss/dmasound/Makefile | 13 + sound/oss/dmasound/awacs_defs.h | 251 ++ sound/oss/dmasound/dac3550a.c | 210 ++ sound/oss/dmasound/dmasound.h | 277 ++ sound/oss/dmasound/dmasound_atari.c | 1600 +++++++++ sound/oss/dmasound/dmasound_awacs.c | 3176 ++++++++++++++++++ sound/oss/dmasound/dmasound_core.c | 1829 ++++++++++ sound/oss/dmasound/dmasound_paula.c | 743 ++++ sound/oss/dmasound/dmasound_q40.c | 634 ++++ sound/oss/dmasound/tas3001c.c | 850 +++++ sound/oss/dmasound/tas3001c.h | 64 + sound/oss/dmasound/tas3001c_tables.c | 375 +++ sound/oss/dmasound/tas3004.c | 1140 +++++++ sound/oss/dmasound/tas3004.h | 77 + sound/oss/dmasound/tas3004_tables.c | 301 ++ sound/oss/dmasound/tas_common.c | 214 ++ sound/oss/dmasound/tas_common.h | 284 ++ sound/oss/dmasound/tas_eq_prefs.h | 24 + sound/oss/dmasound/tas_ioctl.h | 24 + sound/oss/dmasound/trans_16.c | 897 +++++ sound/oss/emu10k1/8010.h | 737 ++++ sound/oss/emu10k1/Makefile | 17 + sound/oss/emu10k1/audio.c | 1588 +++++++++ sound/oss/emu10k1/audio.h | 44 + sound/oss/emu10k1/cardmi.c | 832 +++++ sound/oss/emu10k1/cardmi.h | 97 + sound/oss/emu10k1/cardmo.c | 229 ++ sound/oss/emu10k1/cardmo.h | 62 + sound/oss/emu10k1/cardwi.c | 373 +++ sound/oss/emu10k1/cardwi.h | 91 + sound/oss/emu10k1/cardwo.c | 643 ++++ sound/oss/emu10k1/cardwo.h | 90 + sound/oss/emu10k1/ecard.c | 157 + sound/oss/emu10k1/ecard.h | 113 + sound/oss/emu10k1/efxmgr.c | 220 ++ sound/oss/emu10k1/efxmgr.h | 270 ++ sound/oss/emu10k1/emuadxmg.c | 104 + sound/oss/emu10k1/hwaccess.c | 507 +++ sound/oss/emu10k1/hwaccess.h | 247 ++ sound/oss/emu10k1/icardmid.h | 163 + sound/oss/emu10k1/icardwav.h | 53 + sound/oss/emu10k1/irqmgr.c | 113 + sound/oss/emu10k1/irqmgr.h | 52 + sound/oss/emu10k1/main.c | 1475 ++++++++ sound/oss/emu10k1/midi.c | 613 ++++ sound/oss/emu10k1/midi.h | 78 + sound/oss/emu10k1/mixer.c | 690 ++++ sound/oss/emu10k1/passthrough.c | 236 ++ sound/oss/emu10k1/passthrough.h | 99 + sound/oss/emu10k1/recmgr.c | 147 + sound/oss/emu10k1/recmgr.h | 48 + sound/oss/emu10k1/timer.c | 176 + sound/oss/emu10k1/timer.h | 54 + sound/oss/emu10k1/voicemgr.c | 398 +++ sound/oss/emu10k1/voicemgr.h | 103 + sound/oss/es1370.c | 2789 +++++++++++++++ sound/oss/es1371.c | 3097 +++++++++++++++++ sound/oss/esssolo1.c | 2497 ++++++++++++++ sound/oss/forte.c | 2137 ++++++++++++ sound/oss/gus.h | 24 + sound/oss/gus_card.c | 293 ++ sound/oss/gus_hw.h | 50 + sound/oss/gus_linearvol.h | 18 + sound/oss/gus_midi.c | 256 ++ sound/oss/gus_vol.c | 153 + sound/oss/gus_wave.c | 3464 +++++++++++++++++++ sound/oss/hal2.c | 1557 +++++++++ sound/oss/hal2.h | 248 ++ sound/oss/harmony.c | 1330 ++++++++ sound/oss/hex2hex.c | 101 + sound/oss/i810_audio.c | 3658 ++++++++++++++++++++ sound/oss/ics2101.c | 247 ++ sound/oss/ite8172.c | 2259 +++++++++++++ sound/oss/iwmem.h | 36 + sound/oss/kahlua.c | 232 ++ sound/oss/mad16.c | 1097 ++++++ sound/oss/maestro.c | 3832 +++++++++++++++++++++ sound/oss/maestro.h | 60 + sound/oss/maestro3.c | 2973 ++++++++++++++++ sound/oss/maestro3.h | 821 +++++ sound/oss/maui.c | 478 +++ sound/oss/midi_ctrl.h | 22 + sound/oss/midi_syms.c | 29 + sound/oss/midi_synth.c | 697 ++++ sound/oss/midi_synth.h | 47 + sound/oss/midibuf.c | 431 +++ sound/oss/mpu401.c | 1826 ++++++++++ sound/oss/mpu401.h | 14 + sound/oss/msnd.c | 419 +++ sound/oss/msnd.h | 280 ++ sound/oss/msnd_classic.c | 3 + sound/oss/msnd_classic.h | 188 ++ sound/oss/msnd_pinnacle.c | 1922 +++++++++++ sound/oss/msnd_pinnacle.h | 249 ++ sound/oss/nec_vrc5477.c | 2059 ++++++++++++ sound/oss/nm256.h | 295 ++ sound/oss/nm256_audio.c | 1707 ++++++++++ sound/oss/nm256_coeff.h | 4697 ++++++++++++++++++++++++++ sound/oss/opl3.c | 1257 +++++++ sound/oss/opl3.h | 5 + sound/oss/opl3_hw.h | 246 ++ sound/oss/opl3sa.c | 329 ++ sound/oss/opl3sa2.c | 1129 +++++++ sound/oss/os.h | 51 + sound/oss/pas2.h | 17 + sound/oss/pas2_card.c | 458 +++ sound/oss/pas2_midi.c | 262 ++ sound/oss/pas2_mixer.c | 336 ++ sound/oss/pas2_pcm.c | 437 +++ sound/oss/pss.c | 1283 +++++++ sound/oss/rme96xx.c | 1861 ++++++++++ sound/oss/rme96xx.h | 78 + sound/oss/sb.h | 185 + sound/oss/sb_audio.c | 1098 ++++++ sound/oss/sb_card.c | 347 ++ sound/oss/sb_card.h | 149 + sound/oss/sb_common.c | 1291 +++++++ sound/oss/sb_ess.c | 1832 ++++++++++ sound/oss/sb_ess.h | 34 + sound/oss/sb_midi.c | 205 ++ sound/oss/sb_mixer.c | 768 +++++ sound/oss/sb_mixer.h | 105 + sound/oss/sequencer.c | 1684 ++++++++++ sound/oss/sequencer_syms.c | 30 + sound/oss/sgalaxy.c | 207 ++ sound/oss/sh_dac_audio.c | 325 ++ sound/oss/skeleton.c | 219 ++ sound/oss/sonicvibes.c | 2792 +++++++++++++++ sound/oss/sound_calls.h | 90 + sound/oss/sound_config.h | 154 + sound/oss/sound_firmware.h | 2 + sound/oss/sound_syms.c | 50 + sound/oss/sound_timer.c | 323 ++ sound/oss/soundcard.c | 751 +++++ sound/oss/soundvers.h | 2 + sound/oss/sscape.c | 1485 ++++++++ sound/oss/swarm_cs4297a.c | 2742 +++++++++++++++ sound/oss/sys_timer.c | 289 ++ sound/oss/trident.c | 4628 +++++++++++++++++++++++++ sound/oss/trident.h | 358 ++ sound/oss/trix.c | 525 +++ sound/oss/tuning.h | 29 + sound/oss/uart401.c | 481 +++ sound/oss/uart6850.c | 362 ++ sound/oss/ulaw.h | 69 + sound/oss/v_midi.c | 291 ++ sound/oss/v_midi.h | 15 + sound/oss/via82cxxx_audio.c | 3615 ++++++++++++++++++++ sound/oss/vidc.c | 561 ++++ sound/oss/vidc.h | 67 + sound/oss/vidc_fill.S | 218 ++ sound/oss/vwsnd.c | 3486 +++++++++++++++++++ sound/oss/waveartist.c | 2035 +++++++++++ sound/oss/waveartist.h | 92 + sound/oss/wavfront.c | 3538 +++++++++++++++++++ sound/oss/wf_midi.c | 880 +++++ sound/oss/ymfpci.c | 2691 +++++++++++++++ sound/oss/ymfpci.h | 360 ++ sound/oss/ymfpci_image.h | 1565 +++++++++ sound/oss/yss225.c | 319 ++ sound/oss/yss225.h | 24 + 210 files changed, 172711 insertions(+) create mode 100644 sound/oss/CHANGELOG create mode 100644 sound/oss/COPYING create mode 100644 sound/oss/Kconfig create mode 100644 sound/oss/Makefile create mode 100644 sound/oss/README.FIRST create mode 100644 sound/oss/ac97.c create mode 100644 sound/oss/ac97.h create mode 100644 sound/oss/ac97_codec.c create mode 100644 sound/oss/ac97_plugin_ad1980.c create mode 100644 sound/oss/aci.c create mode 100644 sound/oss/aci.h create mode 100644 sound/oss/ad1816.c create mode 100644 sound/oss/ad1848.c create mode 100644 sound/oss/ad1848.h create mode 100644 sound/oss/ad1848_mixer.h create mode 100644 sound/oss/ad1889.c create mode 100644 sound/oss/ad1889.h create mode 100644 sound/oss/adlib_card.c create mode 100644 sound/oss/aedsp16.c create mode 100644 sound/oss/ali5455.c create mode 100644 sound/oss/au1000.c create mode 100644 sound/oss/au1550_ac97.c create mode 100644 sound/oss/audio.c create mode 100644 sound/oss/audio_syms.c create mode 100644 sound/oss/awe_hw.h create mode 100644 sound/oss/awe_wave.c create mode 100644 sound/oss/awe_wave.h create mode 100644 sound/oss/bin2hex.c create mode 100644 sound/oss/btaudio.c create mode 100644 sound/oss/cmpci.c create mode 100644 sound/oss/coproc.h create mode 100644 sound/oss/cs4232.c create mode 100644 sound/oss/cs4281/Makefile create mode 100644 sound/oss/cs4281/cs4281_hwdefs.h create mode 100644 sound/oss/cs4281/cs4281_wrapper-24.c create mode 100644 sound/oss/cs4281/cs4281m.c create mode 100644 sound/oss/cs4281/cs4281pm-24.c create mode 100644 sound/oss/cs4281/cs4281pm.h create mode 100644 sound/oss/cs461x.h create mode 100644 sound/oss/cs461x_image.h create mode 100644 sound/oss/cs46xx.c create mode 100644 sound/oss/cs46xx_wrapper-24.h create mode 100644 sound/oss/cs46xxpm-24.h create mode 100644 sound/oss/cs46xxpm.h create mode 100644 sound/oss/dev_table.c create mode 100644 sound/oss/dev_table.h create mode 100644 sound/oss/dm.h create mode 100644 sound/oss/dmabuf.c create mode 100644 sound/oss/dmasound/Kconfig create mode 100644 sound/oss/dmasound/Makefile create mode 100644 sound/oss/dmasound/awacs_defs.h create mode 100644 sound/oss/dmasound/dac3550a.c create mode 100644 sound/oss/dmasound/dmasound.h create mode 100644 sound/oss/dmasound/dmasound_atari.c create mode 100644 sound/oss/dmasound/dmasound_awacs.c create mode 100644 sound/oss/dmasound/dmasound_core.c create mode 100644 sound/oss/dmasound/dmasound_paula.c create mode 100644 sound/oss/dmasound/dmasound_q40.c create mode 100644 sound/oss/dmasound/tas3001c.c create mode 100644 sound/oss/dmasound/tas3001c.h create mode 100644 sound/oss/dmasound/tas3001c_tables.c create mode 100644 sound/oss/dmasound/tas3004.c create mode 100644 sound/oss/dmasound/tas3004.h create mode 100644 sound/oss/dmasound/tas3004_tables.c create mode 100644 sound/oss/dmasound/tas_common.c create mode 100644 sound/oss/dmasound/tas_common.h create mode 100644 sound/oss/dmasound/tas_eq_prefs.h create mode 100644 sound/oss/dmasound/tas_ioctl.h create mode 100644 sound/oss/dmasound/trans_16.c create mode 100644 sound/oss/emu10k1/8010.h create mode 100644 sound/oss/emu10k1/Makefile create mode 100644 sound/oss/emu10k1/audio.c create mode 100644 sound/oss/emu10k1/audio.h create mode 100644 sound/oss/emu10k1/cardmi.c create mode 100644 sound/oss/emu10k1/cardmi.h create mode 100644 sound/oss/emu10k1/cardmo.c create mode 100644 sound/oss/emu10k1/cardmo.h create mode 100644 sound/oss/emu10k1/cardwi.c create mode 100644 sound/oss/emu10k1/cardwi.h create mode 100644 sound/oss/emu10k1/cardwo.c create mode 100644 sound/oss/emu10k1/cardwo.h create mode 100644 sound/oss/emu10k1/ecard.c create mode 100644 sound/oss/emu10k1/ecard.h create mode 100644 sound/oss/emu10k1/efxmgr.c create mode 100644 sound/oss/emu10k1/efxmgr.h create mode 100644 sound/oss/emu10k1/emuadxmg.c create mode 100644 sound/oss/emu10k1/hwaccess.c create mode 100644 sound/oss/emu10k1/hwaccess.h create mode 100644 sound/oss/emu10k1/icardmid.h create mode 100644 sound/oss/emu10k1/icardwav.h create mode 100644 sound/oss/emu10k1/irqmgr.c create mode 100644 sound/oss/emu10k1/irqmgr.h create mode 100644 sound/oss/emu10k1/main.c create mode 100644 sound/oss/emu10k1/midi.c create mode 100644 sound/oss/emu10k1/midi.h create mode 100644 sound/oss/emu10k1/mixer.c create mode 100644 sound/oss/emu10k1/passthrough.c create mode 100644 sound/oss/emu10k1/passthrough.h create mode 100644 sound/oss/emu10k1/recmgr.c create mode 100644 sound/oss/emu10k1/recmgr.h create mode 100644 sound/oss/emu10k1/timer.c create mode 100644 sound/oss/emu10k1/timer.h create mode 100644 sound/oss/emu10k1/voicemgr.c create mode 100644 sound/oss/emu10k1/voicemgr.h create mode 100644 sound/oss/es1370.c create mode 100644 sound/oss/es1371.c create mode 100644 sound/oss/esssolo1.c create mode 100644 sound/oss/forte.c create mode 100644 sound/oss/gus.h create mode 100644 sound/oss/gus_card.c create mode 100644 sound/oss/gus_hw.h create mode 100644 sound/oss/gus_linearvol.h create mode 100644 sound/oss/gus_midi.c create mode 100644 sound/oss/gus_vol.c create mode 100644 sound/oss/gus_wave.c create mode 100644 sound/oss/hal2.c create mode 100644 sound/oss/hal2.h create mode 100644 sound/oss/harmony.c create mode 100644 sound/oss/hex2hex.c create mode 100644 sound/oss/i810_audio.c create mode 100644 sound/oss/ics2101.c create mode 100644 sound/oss/ite8172.c create mode 100644 sound/oss/iwmem.h create mode 100644 sound/oss/kahlua.c create mode 100644 sound/oss/mad16.c create mode 100644 sound/oss/maestro.c create mode 100644 sound/oss/maestro.h create mode 100644 sound/oss/maestro3.c create mode 100644 sound/oss/maestro3.h create mode 100644 sound/oss/maui.c create mode 100644 sound/oss/midi_ctrl.h create mode 100644 sound/oss/midi_syms.c create mode 100644 sound/oss/midi_synth.c create mode 100644 sound/oss/midi_synth.h create mode 100644 sound/oss/midibuf.c create mode 100644 sound/oss/mpu401.c create mode 100644 sound/oss/mpu401.h create mode 100644 sound/oss/msnd.c create mode 100644 sound/oss/msnd.h create mode 100644 sound/oss/msnd_classic.c create mode 100644 sound/oss/msnd_classic.h create mode 100644 sound/oss/msnd_pinnacle.c create mode 100644 sound/oss/msnd_pinnacle.h create mode 100644 sound/oss/nec_vrc5477.c create mode 100644 sound/oss/nm256.h create mode 100644 sound/oss/nm256_audio.c create mode 100644 sound/oss/nm256_coeff.h create mode 100644 sound/oss/opl3.c create mode 100644 sound/oss/opl3.h create mode 100644 sound/oss/opl3_hw.h create mode 100644 sound/oss/opl3sa.c create mode 100644 sound/oss/opl3sa2.c create mode 100644 sound/oss/os.h create mode 100644 sound/oss/pas2.h create mode 100644 sound/oss/pas2_card.c create mode 100644 sound/oss/pas2_midi.c create mode 100644 sound/oss/pas2_mixer.c create mode 100644 sound/oss/pas2_pcm.c create mode 100644 sound/oss/pss.c create mode 100644 sound/oss/rme96xx.c create mode 100644 sound/oss/rme96xx.h create mode 100644 sound/oss/sb.h create mode 100644 sound/oss/sb_audio.c create mode 100644 sound/oss/sb_card.c create mode 100644 sound/oss/sb_card.h create mode 100644 sound/oss/sb_common.c create mode 100644 sound/oss/sb_ess.c create mode 100644 sound/oss/sb_ess.h create mode 100644 sound/oss/sb_midi.c create mode 100644 sound/oss/sb_mixer.c create mode 100644 sound/oss/sb_mixer.h create mode 100644 sound/oss/sequencer.c create mode 100644 sound/oss/sequencer_syms.c create mode 100644 sound/oss/sgalaxy.c create mode 100644 sound/oss/sh_dac_audio.c create mode 100644 sound/oss/skeleton.c create mode 100644 sound/oss/sonicvibes.c create mode 100644 sound/oss/sound_calls.h create mode 100644 sound/oss/sound_config.h create mode 100644 sound/oss/sound_firmware.h create mode 100644 sound/oss/sound_syms.c create mode 100644 sound/oss/sound_timer.c create mode 100644 sound/oss/soundcard.c create mode 100644 sound/oss/soundvers.h create mode 100644 sound/oss/sscape.c create mode 100644 sound/oss/swarm_cs4297a.c create mode 100644 sound/oss/sys_timer.c create mode 100644 sound/oss/trident.c create mode 100644 sound/oss/trident.h create mode 100644 sound/oss/trix.c create mode 100644 sound/oss/tuning.h create mode 100644 sound/oss/uart401.c create mode 100644 sound/oss/uart6850.c create mode 100644 sound/oss/ulaw.h create mode 100644 sound/oss/v_midi.c create mode 100644 sound/oss/v_midi.h create mode 100644 sound/oss/via82cxxx_audio.c create mode 100644 sound/oss/vidc.c create mode 100644 sound/oss/vidc.h create mode 100644 sound/oss/vidc_fill.S create mode 100644 sound/oss/vwsnd.c create mode 100644 sound/oss/waveartist.c create mode 100644 sound/oss/waveartist.h create mode 100644 sound/oss/wavfront.c create mode 100644 sound/oss/wf_midi.c create mode 100644 sound/oss/ymfpci.c create mode 100644 sound/oss/ymfpci.h create mode 100644 sound/oss/ymfpci_image.h create mode 100644 sound/oss/yss225.c create mode 100644 sound/oss/yss225.h (limited to 'sound/oss') diff --git a/sound/oss/CHANGELOG b/sound/oss/CHANGELOG new file mode 100644 index 000000000000..8706cd66ca1f --- /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/COPYING b/sound/oss/COPYING new file mode 100644 index 000000000000..916d1f0f2842 --- /dev/null +++ b/sound/oss/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/sound/oss/Kconfig b/sound/oss/Kconfig new file mode 100644 index 000000000000..d303c2ed6e5a --- /dev/null +++ b/sound/oss/Kconfig @@ -0,0 +1,1120 @@ +# drivers/sound/Config.in +# +# 18 Apr 1998, Michael Elizabeth Chastain, +# More hacking for modularisation. +# +# Prompt user for primary drivers. +config SOUND_BT878 + tristate "BT878 audio dma" + depends on SOUND_PRIME!=n && SOUND + ---help--- + Audio DMA support for bt878 based grabber boards. As you might have + already noticed, bt878 is listed with two functions in /proc/pci. + Function 0 does the video stuff (bt848 compatible), function 1 does + the same for audio data. This is a driver for the audio part of + the chip. If you say 'Y' here you get a oss-compatible dsp device + where you can record from. If you want just watch TV you probably + don't need this driver as most TV cards handle sound with a short + cable from the TV card to your sound card's line-in. + + To compile this driver as a module, choose M here: the module will + be called btaudio. + +config SOUND_CMPCI + tristate "C-Media PCI (CMI8338/8738)" + depends on SOUND_PRIME!=n && SOUND && PCI + help + Say Y or M if you have a PCI sound card using the CMI8338 + or the CMI8738 chipset. Data on these chips are available at + . + + A userspace utility to control some internal registers of these + chips is available at + . + +config SOUND_CMPCI_FM + bool "Enable legacy FM" + depends on SOUND_CMPCI && X86 + help + Say Y here to enable the legacy FM (frequency-modulation) synthesizer + support on a card using the CMI8338 or CMI8378 chipset. Even it is + enabled, you need to set fmio as proper value to enable it. + Say N here if you don't need this. + +config SOUND_CMPCI_MIDI + bool "Enable legacy MPU-401" + depends on SOUND_CMPCI && X86 + help + Say Y here to enable the legacy MPU401 MIDI synthesizer support on a + card using the CMI8338 or CMI8378 chipset. Even it is enabled, + you need to set mpuio as proper value to enable it. + Say N here if you don't need this. + +config SOUND_CMPCI_JOYSTICK + bool "Enable joystick" + depends on SOUND_CMPCI && X86 + help + Say Y here in order to enable the joystick port on a sound card using + the CMI8338 or the CMI8738 chipset. You need to config the + gameport support and set joystick parameter as 1 to use it. + Say N here if you don't need this. + +config SOUND_EMU10K1 + tristate "Creative SBLive! (EMU10K1)" + depends on SOUND_PRIME!=n && SOUND && PCI + ---help--- + Say Y or M if you have a PCI sound card using the EMU10K1 chipset, + such as the Creative SBLive!, SB PCI512 or Emu-APS. + + For more information on this driver and the degree of support for + the different card models please check: + + + + It is now possible to load dsp microcode patches into the EMU10K1 + chip. These patches are used to implement real time sound + processing effects which include for example: signal routing, + bass/treble control, AC3 passthrough, ... + Userspace tools to create new patches and load/unload them can be + found in the emu-tools package at the above URL. + +config MIDI_EMU10K1 + bool "Creative SBLive! MIDI (EXPERIMENTAL)" + depends on SOUND_EMU10K1 && EXPERIMENTAL + help + Say Y if you want to be able to use the OSS /dev/sequencer + interface. This code is still experimental. + +config SOUND_FUSION + tristate "Crystal SoundFusion (CS4280/461x)" + depends on SOUND_PRIME!=n && SOUND + help + This module drives the Crystal SoundFusion devices (CS4280/46xx + series) when wired as native sound drivers with AC97 codecs. If + this driver does not work try the CS4232 driver. + +config SOUND_CS4281 + tristate "Crystal Sound CS4281" + depends on SOUND_PRIME!=n && SOUND + help + Picture and feature list at + . + +config SOUND_BCM_CS4297A + tristate "Crystal Sound CS4297a (for Swarm)" + depends on SOUND_PRIME!=n && SIBYTE_SWARM && SOUND + 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_ES1370 + tristate "Ensoniq AudioPCI (ES1370)" + depends on SOUND_PRIME!=n && SOUND && PCI && SOUND_GAMEPORT + help + Say Y or M if you have a PCI sound card utilizing the Ensoniq + ES1370 chipset, such as Ensoniq's AudioPCI (non-97). To find + out if your sound card uses an ES1370 without removing your + computer's cover, use lspci -n and look for the PCI ID + 1274:5000. Since Ensoniq was bought by Creative Labs, + Sound Blaster 64/PCI models are either ES1370 or ES1371 based. + This driver differs slightly from OSS/Free, so PLEASE READ + . + +config SOUND_ES1371 + tristate "Creative Ensoniq AudioPCI 97 (ES1371)" + depends on SOUND_PRIME!=n && SOUND && PCI && SOUND_GAMEPORT + help + Say Y or M if you have a PCI sound card utilizing the Ensoniq + ES1371 chipset, such as Ensoniq's AudioPCI97. To find out if + your sound card uses an ES1371 without removing your computer's + cover, use lspci -n and look for the PCI ID 1274:1371. Since + Ensoniq was bought by Creative Labs, Sound Blaster 64/PCI + models are either ES1370 or ES1371 based. This driver differs + slightly from OSS/Free, so PLEASE READ + . + +config SOUND_ESSSOLO1 + tristate "ESS Technology Solo1" + depends on SOUND_PRIME!=n && SOUND && SOUND_GAMEPORT && PCI + help + Say Y or M if you have a PCI sound card utilizing the ESS Technology + Solo1 chip. To find out if your sound card uses a + Solo1 chip without removing your computer's cover, use + lspci -n and look for the PCI ID 125D:1969. This driver + differs slightly from OSS/Free, so PLEASE READ + . + +config SOUND_MAESTRO + tristate "ESS Maestro, Maestro2, Maestro2E driver" + depends on SOUND_PRIME!=n && SOUND && PCI + help + Say Y or M if you have a sound system driven by ESS's Maestro line + of PCI sound chips. These include the Maestro 1, Maestro 2, and + Maestro 2E. See for more + details. + +config SOUND_MAESTRO3 + tristate "ESS Maestro3/Allegro driver (EXPERIMENTAL)" + depends on SOUND_PRIME!=n && SOUND && PCI && EXPERIMENTAL + help + Say Y or M if you have a sound system driven by ESS's Maestro 3 + PCI sound chip. + +config SOUND_ICH + tristate "Intel ICH (i8xx) audio support" + depends on SOUND_PRIME!=n && PCI + help + Support for integral audio in Intel's I/O Controller Hub (ICH) + chipset, as used on the 810/820/840 motherboards. + +config SOUND_HARMONY + tristate "PA Harmony audio driver" + depends on GSC_LASI && SOUND_PRIME!=n + help + Say 'Y' or 'M' to include support for Harmony soundchip + on HP 712, 715/new and many other GSC based machines. + +config SOUND_SONICVIBES + tristate "S3 SonicVibes" + depends on SOUND_PRIME!=n && SOUND && SOUND_GAMEPORT + help + Say Y or M if you have a PCI sound card utilizing the S3 + SonicVibes chipset. To find out if your sound card uses a + SonicVibes chip without removing your computer's cover, use + lspci -n and look for the PCI ID 5333:CA00. This driver + differs slightly from OSS/Free, so PLEASE READ + . + +config SOUND_VWSND + tristate "SGI Visual Workstation Sound" + depends on SOUND_PRIME!=n && X86_VISWS && SOUND + 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 + for more info on this driver's + capabilities. + +config SOUND_HAL2 + tristate "SGI HAL2 sound (EXPERIMENTAL)" + depends on SOUND_PRIME!=n && SOUND && SGI_IP22 && EXPERIMENTAL + help + Say Y or M if you have an SGI Indy system and want to be able to + use it's on-board A2 audio system. + +config SOUND_IT8172 + tristate "IT8172G Sound" + depends on SOUND_PRIME!=n && (MIPS_ITE8172 || MIPS_IVR) && SOUND + +config SOUND_VRC5477 + tristate "NEC Vrc5477 AC97 sound" + depends on SOUND_PRIME!=n && DDB5477 && SOUND + help + Say Y here to enable sound support for the NEC Vrc5477 chip, an + integrated, multi-function controller chip for MIPS CPUs. Works + with the AC97 codec. + +config SOUND_AU1000 + tristate "Au1000 Sound" + depends on SOUND_PRIME!=n && (SOC_AU1000 || SOC_AU1100 || SOC_AU1500) && SOUND + +config SOUND_AU1550_AC97 + tristate "Au1550 AC97 Sound" + depends on SOUND_PRIME!=n && SOC_AU1550 && SOUND + +config SOUND_TRIDENT + tristate "Trident 4DWave DX/NX, SiS 7018 or ALi 5451 PCI Audio Core" + depends on SOUND_PRIME!=n && SOUND && SOUND_GAMEPORT + ---help--- + Say Y or M if you have a PCI sound card utilizing the Trident + 4DWave-DX/NX chipset or your mother board chipset has SiS 7018 + or ALi 5451 built-in. The SiS 7018 PCI Audio Core is embedded + in SiS960 Super South Bridge and SiS540/630 Single Chipset. + The ALi 5451 PCI Audio Core is embedded in ALi M1535, M1535D, + M1535+ or M1535D+ South Bridge. + + Use lspci -n to find out if your sound card or chipset uses + Trident 4DWave or SiS 7018. PCI ID 1023:2000 or 1023:2001 stands + for Trident 4Dwave. PCI ID 1039:7018 stands for SiS7018. PCI ID + 10B9:5451 stands for ALi5451. + + This driver supports S/PDIF in/out (record/playback) for ALi 5451 + embedded in ALi M1535+ and M1535D+. Note that they aren't all + enabled by default; you can enable them by saying Y to "/proc file + system support" and "Sysctl support", and after the /proc file + system has been mounted, executing the command + + command what is enabled + + echo 0>/proc/ALi5451 pcm out is also set to S/PDIF out. (Default). + + echo 1>/proc/ALi5451 use S/PDIF out to output pcm data. + + echo 2>/proc/ALi5451 use S/PDIF out to output non-pcm data. + (AC3...). + + echo 3>/proc/ALi5451 record from Ac97 in(MIC, Line in...). + (Default). + + echo 4>/proc/ALi5451 no matter Ac97 settings, record from S/PDIF + in. + + + This driver differs slightly from OSS/Free, so PLEASE READ the + comments at the top of . + +config SOUND_MSNDCLAS + tristate "Support for Turtle Beach MultiSound Classic, Tahiti, Monterey" + depends on SOUND_PRIME!=n && SOUND && (m || !STANDALONE) + help + Say M here if you have a Turtle Beach MultiSound Classic, Tahiti or + Monterey (not for the Pinnacle or Fiji). + + See 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 . + +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 + 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 + 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 SOUND_PRIME!=n && SOUND && (m || !STANDALONE) + help + Say M here if you have a Turtle Beach MultiSound Pinnacle or Fiji. + See 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 . + +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 + 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 + 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 + 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_PRIME && 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_PRIME && 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_PRIME && (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. + +config SOUND_VIA82CXXX + tristate "VIA 82C686 Audio Codec" + depends on SOUND_PRIME!=n && PCI + help + Say Y here to include support for the audio codec found on VIA + 82Cxxx-based chips. Typically these are built into a motherboard. + + DO NOT select Sound Blaster or Adlib with this driver, unless + you have a Sound Blaster or Adlib card in addition to your VIA + audio chip. + +config MIDI_VIA82CXXX + bool "VIA 82C686 MIDI" + depends on SOUND_VIA82CXXX + help + Answer Y to use the MIDI interface of the Via686. You may need to + enable this in the BIOS before it will work. This is for connection + to external MIDI hardware, and is not required for software playback + of MIDI files. + +config SOUND_OSS + tristate "OSS sound modules" + depends on SOUND_PRIME!=n && SOUND + 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. + +config SOUND_TRACEINIT + bool "Verbose initialisation" + depends on SOUND_OSS + help + Verbose soundcard initialization -- affects the format of autoprobe + and initialization messages at boot time. + +config SOUND_DMAP + bool "Persistent DMA buffers" + depends on SOUND_OSS + ---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_AD1816 + tristate "AD1816(A) based cards (EXPERIMENTAL)" + depends on EXPERIMENTAL && SOUND_OSS + help + Say M here if you have a sound card based on the Analog Devices + AD1816(A) chip. + + If you compile the driver into the kernel, you have to add + "ad1816=,,," to the kernel command line. + +config SOUND_AD1889 + tristate "AD1889 based cards (AD1819 codec) (EXPERIMENTAL)" + depends on EXPERIMENTAL && SOUND_OSS + help + Say M here if you have a sound card based on the Analog Devices + AD1889 chip. + +config SOUND_SGALAXY + tristate "Aztech Sound Galaxy (non-PnP) cards" + depends on SOUND_OSS + help + This module initializes the older non Plug and Play sound galaxy + cards from Aztech. It supports the Waverider Pro 32 - 3D and the + Galaxy Washington 16. + + If you compile the driver into the kernel, you have to add + "sgalaxy=,,,," to the kernel command + line. + +config SOUND_ADLIB + tristate "Adlib Cards" + depends on SOUND_OSS + help + Includes ASB 64 4D. Information on programming AdLib cards is + available at . + +config SOUND_ACI_MIXER + tristate "ACI mixer (miroSOUND PCM1-pro/PCM12/PCM20)" + depends on SOUND_OSS + ---help--- + ACI (Audio Command Interface) is a protocol used to communicate with + the microcontroller on some sound cards produced by miro and + Cardinal Technologies. The main function of the ACI is to control + the mixer and to get a product identification. + + This VoxWare ACI driver currently supports the ACI functions on the + miroSOUND PCM1-pro, PCM12 and PCM20 radio. On the PCM20 radio, ACI + also controls the radio tuner. This is supported in the video4linux + miropcm20 driver (say M or Y here and go back to "Multimedia + devices" -> "Radio Adapters"). + + This driver is also available as a module and will be called aci. + +config SOUND_CS4232 + tristate "Crystal CS4232 based (PnP) cards" + depends on SOUND_OSS + help + Say Y here if you have a card based on the Crystal CS4232 chip set, + which uses its own Plug and Play protocol. + + If you compile the driver into the kernel, you have to add + "cs4232=,,,,," to the kernel + command line. + + See for more information on + configuring this card. + +config SOUND_SSCAPE + tristate "Ensoniq SoundScape support" + depends on SOUND_OSS + help + Answer Y if you have a sound card based on the Ensoniq SoundScape + chipset. Such cards are being manufactured at least by Ensoniq, Spea + and Reveal (Reveal makes also other cards). + + If you compile the driver into the kernel, you have to add + "sscape=,,,," to the kernel command + line. + +config SOUND_GUS + tristate "Gravis Ultrasound support" + depends on SOUND_OSS + help + Say Y here for any type of Gravis Ultrasound card, including the GUS + or GUS MAX. See also for more + information on configuring this card with modules. + + If you compile the driver into the kernel, you have to add + "gus=,,," to the kernel command line. + +config SOUND_GUS16 + bool "16 bit sampling option of GUS (_NOT_ GUS MAX)" + depends on SOUND_GUS + help + Support for Gravis Ulstrasound (GUS) cards (other than the GUS), + sampling at 16-bit width. + +config SOUND_GUSMAX + bool "GUS MAX support" + depends on SOUND_GUS + help + Support for Gravis Ulstrasound MAX. + +config SOUND_VMIDI + tristate "Loopback MIDI device support" + depends on SOUND_OSS + help + Support for MIDI loopback on port 1 or 2. + +config SOUND_TRIX + tristate "MediaTrix AudioTrix Pro support" + depends on SOUND_OSS + 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" + depends on SOUND_OSS + ---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 . + 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=,,,[,]" to the kernel command + line. + +config SOUND_MPU401 + tristate "MPU-401 support (NOT for SB16)" + depends on SOUND_OSS + ---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. 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=," to the kernel command line. + +config SOUND_NM256 + tristate "NM256AV/NM256ZX audio support" + depends on SOUND_OSS + help + Say M here to include audio support for the NeoMagic 256AV/256ZX + chipsets. These are the audio chipsets found in the Sony + Z505S/SX/DX, some Sony F-series, and the Dell Latitude CPi and CPt + laptops. It includes support for an AC97-compatible mixer and an + apparently proprietary sound engine. + + See for further information. + +config SOUND_MAD16 + tristate "OPTi MAD16 and/or Mozart based cards" + depends on SOUND_OSS && SOUND_GAMEPORT + ---help--- + Answer Y if your card has a Mozart (OAK OTI-601) or MAD16 (OPTi + 82C928 or 82C929 or 82C931) audio interface chip. These chips are + quite common so it's possible that many no-name cards have one of + them. In addition the MAD16 chip is used in some cards made by known + manufacturers such as Turtle Beach (Tropez), Reveal (some models) + and Diamond (latest ones). Note however that the Tropez sound cards + have their own driver; if you have one of those, say N here and Y or + M to "Full support for Turtle Beach WaveFront", below. + + If you compile the driver into the kernel, you have to add + "mad16=,,,,," to the + kernel command line. + + See also and + for more information on setting + these cards up as modules. + +config MAD16_OLDCARD + bool "Support MIDI in older MAD16 based cards (requires SB)" + depends on SOUND_MAD16 + help + Answer Y (or M) if you have an older card based on the C928 or + Mozart chipset and you want to have MIDI support. If you enable this + option you also need to enable support for Sound Blaster. + +config SOUND_PAS + tristate "ProAudioSpectrum 16 support" + depends on SOUND_OSS + ---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 . + 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=,,,,,,, + 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" + depends on SOUND_OSS + 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 + . + + If you compile the driver into the kernel, you have to add + "pss=,,,,," to the kernel + command line. + +config PSS_MIXER + bool "Enable PSS mixer (Beethoven ADSP-16 and other compatibile)" + 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 . + +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" + depends on SOUND_OSS + ---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 . + + You should also say Y here for cards based on the Avance Logic + ALS-007 and ALS-1X0 chips (read ) and + for cards based on ESS chips (read + and + ). If you have an SB AWE 32 or SB AWE + 64, say Y here and also to "AWE32 synth" below and read + . If you have an IBM Mwave + card, say Y here and read . + + If you compile the driver into the kernel and don't want to use + isapnp, you have to add "sb=,,," to the kernel + command line. + + You can say M here to compile this driver as a module; the module is + called sb. + +config SOUND_AWE32_SYNTH + tristate "AWE32 synth" + depends on SOUND_OSS + help + Say Y here if you have a Sound Blaster SB32, AWE32-PnP, SB AWE64 or + similar sound card. See , + and the Soundblaster-AWE + mini-HOWTO, available from + for more info. + +config SOUND_WAVEFRONT + tristate "Full support for Turtle Beach WaveFront (Tropez Plus, Tropez, Maui) synth/soundcards" + depends on SOUND_OSS && m + help + Answer Y or M if you have a Tropez Plus, Tropez or Maui sound card + and read the files and + . + +config SOUND_MAUI + tristate "Limited support for Turtle Beach Wave Front (Maui, Tropez) synthesizers" + depends on SOUND_OSS + help + Say Y here if you have a Turtle Beach Wave Front, Maui, or Tropez + sound card. + + If you compile the driver into the kernel, you have to add + "maui=," to the kernel command line. + +config MAUI_HAVE_BOOT + bool "Have OSWF.MOT firmware file" + depends on SOUND_MAUI=y && !STANDALONE + help + Turtle Beach Maui and Tropez sound cards have a microcontroller + which needs to be initialized prior to use. OSWF.MOT is a file + distributed with the card's DOS/Windows drivers. Answer Y if you + have this file. + +config MAUI_BOOT_FILE + string "Full pathname of OSWF.MOT firmware file" + depends on MAUI_HAVE_BOOT + default "/etc/sound/oswf.mot" + help + Enter the full pathname of your OSWF.MOT file, starting from /. + +config SOUND_YM3812 + tristate "Yamaha FM synthesizer (YM3812/OPL-3) support" + depends on SOUND_OSS + ---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 + if your card has an OPL3 chip. + + If you compile the driver into the kernel, you have to add + "opl3=" to the kernel command line. + + If unsure, say Y. + +config SOUND_OPL3SA1 + tristate "Yamaha OPL3-SA1 audio controller" + depends on SOUND_OSS + help + Say Y or M if you have a Yamaha OPL3-SA1 sound chip, which is + usually built into motherboards. Read + for details. + + If you compile the driver into the kernel, you have to add + "opl3sa=,,,,," to the kernel + command line. + +config SOUND_OPL3SA2 + tristate "Yamaha OPL3-SA2 and SA3 based PnP cards" + depends on SOUND_OSS + help + Say Y or M if you have a card based on one of these Yamaha sound + chipsets or the "SAx", which is actually a SA3. Read + for more information on + configuring these cards. + + If you compile the driver into the kernel and do not also + configure in the optional ISA PnP support, you will have to add + "opl3sa2=,,,,," to the kernel + command line. + +config SOUND_YMFPCI + tristate "Yamaha YMF7xx PCI audio (native mode)" + depends on SOUND_OSS && PCI + help + Support for Yamaha cards including the YMF711, YMF715, YMF718, + YMF719, YMF724, Waveforce 192XG, and Waveforce 192 Digital. + +config SOUND_YMFPCI_LEGACY + bool "Yamaha PCI legacy ports support" + depends on SOUND_YMFPCI + help + Support for YMF7xx PCI cards emulating an MP401. + +config SOUND_UART6850 + tristate "6850 UART support" + depends on SOUND_OSS + 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=," to the kernel command line. + +config SOUND_AEDSP16 + tristate "Gallant Audio Cards (SC-6000 and SC-6600 based)" + depends on SOUND_OSS + ---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 and the head of + as well as + 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. + +choice + prompt "Audio Excel DSP 16" + optional + depends on SOUND_AEDSP16 + +config AEDSP16_MSS + bool "MSS emulation" + depends on SOUND_MSS + help + Answer Y if you want your audio card to emulate Microsoft Sound + System. You should then say Y to "Microsoft Sound System support" + and say N to "Audio Excel DSP 16 (SBPro emulation)". + +config AEDSP16_SBPRO + bool "SBPro emulation" + depends on SOUND_SB + help + Answer Y if you want your audio card to emulate Sound Blaster Pro. + You should then say Y to "100% Sound Blaster compatibles + (SB16/32/64, ESS, Jazz16) support" and N to "Audio Excel DSP 16 (MSS + emulation)". + + If you compile the driver into the kernel, you have to add + "aedsp16=,,,,," to the kernel + command line. + +endchoice + +config AEDSP16_MPU401 + bool "Audio Excel DSP 16 (MPU401 emulation)" + depends on SOUND_AEDSP16 && SOUND_MPU401 + help + Answer Y if you want your audio card to emulate the MPU-401 midi + interface. You should then also say Y to "MPU-401 support". + + Note that the I/O base for MPU-401 support of aedsp16 is the same + you have selected for "MPU-401 support". If you are using this + driver as a module you have to specify the MPU I/O base address with + the parameter 'mpu_base=0xNNN'. + +config SOUND_VIDC + tristate "VIDC 16-bit sound" + depends on ARM && (ARCH_ACORN || ARCH_CLPS7500) && SOUND_OSS + help + 16-bit support for the VIDC onboard sound hardware found on Acorn + machines. + +config SOUND_WAVEARTIST + tristate "Netwinder WaveArtist" + depends on ARM && SOUND_OSS && ARCH_NETWINDER + help + Say Y here to include support for the Rockwell WaveArtist sound + system. This driver is mainly for the NetWinder. + +config SOUND_TVMIXER + tristate "TV card (bt848) mixer support" + depends on SOUND_PRIME!=n && SOUND && I2C + help + Support for audio mixer facilities on the BT848 TV frame-grabber + card. + +config SOUND_KAHLUA + tristate "XpressAudio Sound Blaster emulation" + depends on SOUND_SB + +config SOUND_ALI5455 + tristate "ALi5455 audio support" + depends on SOUND_PRIME!=n && PCI + +config SOUND_FORTE + tristate "ForteMedia FM801 driver" + depends on SOUND_PRIME!=n && PCI + help + Say Y or M if you want driver support for the ForteMedia FM801 PCI + audio controller (Abit AU10, Genius Sound Maker, HP Workstation + zx2000, and others). + +config SOUND_RME96XX + tristate "RME Hammerfall (RME96XX) support" + depends on SOUND_PRIME!=n && PCI + help + Say Y or M if you have a Hammerfall or Hammerfall light + multichannel card from RME. If you want to access advanced + features of the card, read . + +config SOUND_AD1980 + tristate "AD1980 front/back switch plugin" + depends on SOUND_PRIME!=n + +config SOUND_SH_DAC_AUDIO + tristate "SuperH DAC audio support" + depends on SOUND_PRIME!=n && SOUND && CPU_SH3 + +config SOUND_SH_DAC_AUDIO_CHANNEL + int " DAC channel" + default "1" + depends on SOUND_SH_DAC_AUDIO diff --git a/sound/oss/Makefile b/sound/oss/Makefile new file mode 100644 index 000000000000..db9afb61d6ff --- /dev/null +++ b/sound/oss/Makefile @@ -0,0 +1,187 @@ +# Makefile for the Linux sound card driver +# +# 18 Apr 1998, Michael Elizabeth Chastain, +# Rewritten to use lists instead of if-statements. + +# Each configuration option enables a list of files. + +obj-$(CONFIG_SOUND_OSS) += sound.o +obj-$(CONFIG_SOUND_CS4232) += cs4232.o ad1848.o + +# Please leave it as is, cause the link order is significant ! + +obj-$(CONFIG_SOUND_SH_DAC_AUDIO) += sh_dac_audio.o +obj-$(CONFIG_SOUND_HAL2) += hal2.o +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_OPL3SA1) += opl3sa.o ad1848.o uart401.o +obj-$(CONFIG_SOUND_SSCAPE) += sscape.o ad1848.o mpu401.o +obj-$(CONFIG_SOUND_MAD16) += mad16.o ad1848.o sb_lib.o uart401.o +obj-$(CONFIG_SOUND_CS4232) += cs4232.o uart401.o +obj-$(CONFIG_SOUND_MSS) += ad1848.o +obj-$(CONFIG_SOUND_OPL3SA2) += opl3sa2.o ad1848.o mpu401.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_WAVEFRONT) += wavefront.o +obj-$(CONFIG_SOUND_MAUI) += maui.o mpu401.o +obj-$(CONFIG_SOUND_MPU401) += mpu401.o +obj-$(CONFIG_SOUND_UART6850) += uart6850.o +obj-$(CONFIG_SOUND_GUS) += gus.o ad1848.o +obj-$(CONFIG_SOUND_ADLIB) += adlib_card.o opl3.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_SGALAXY) += sgalaxy.o ad1848.o +obj-$(CONFIG_SOUND_AD1816) += ad1816.o +obj-$(CONFIG_SOUND_AD1889) += ad1889.o ac97_codec.o +obj-$(CONFIG_SOUND_ACI_MIXER) += aci.o +obj-$(CONFIG_SOUND_AWE32_SYNTH) += awe_wave.o + +obj-$(CONFIG_SOUND_VIA82CXXX) += via82cxxx_audio.o ac97_codec.o +ifeq ($(CONFIG_MIDI_VIA82CXXX),y) + obj-$(CONFIG_SOUND_VIA82CXXX) += sound.o uart401.o +endif +obj-$(CONFIG_SOUND_YMFPCI) += ymfpci.o ac97_codec.o +ifeq ($(CONFIG_SOUND_YMFPCI_LEGACY),y) + obj-$(CONFIG_SOUND_YMFPCI) += opl3.o uart401.o +endif +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_NM256) += nm256_audio.o ac97.o +obj-$(CONFIG_SOUND_ICH) += i810_audio.o ac97_codec.o +obj-$(CONFIG_SOUND_SONICVIBES) += sonicvibes.o +obj-$(CONFIG_SOUND_CMPCI) += cmpci.o +ifeq ($(CONFIG_SOUND_CMPCI_FM),y) + obj-$(CONFIG_SOUND_CMPCI) += sound.o opl3.o +endif +ifeq ($(CONFIG_SOUND_CMPCI_MIDI),y) + obj-$(CONFIG_SOUND_CMPCI) += sound.o mpu401.o +endif +obj-$(CONFIG_SOUND_ES1370) += es1370.o +obj-$(CONFIG_SOUND_ES1371) += es1371.o ac97_codec.o +obj-$(CONFIG_SOUND_VRC5477) += nec_vrc5477.o ac97_codec.o +obj-$(CONFIG_SOUND_AU1000) += au1000.o ac97_codec.o +obj-$(CONFIG_SOUND_AU1550_AC97) += au1550_ac97.o ac97_codec.o +obj-$(CONFIG_SOUND_ESSSOLO1) += esssolo1.o +obj-$(CONFIG_SOUND_FUSION) += cs46xx.o ac97_codec.o +obj-$(CONFIG_SOUND_MAESTRO) += maestro.o +obj-$(CONFIG_SOUND_MAESTRO3) += maestro3.o ac97_codec.o +obj-$(CONFIG_SOUND_TRIDENT) += trident.o ac97_codec.o +obj-$(CONFIG_SOUND_HARMONY) += harmony.o +obj-$(CONFIG_SOUND_EMU10K1) += ac97_codec.o +obj-$(CONFIG_SOUND_BCM_CS4297A) += swarm_cs4297a.o +obj-$(CONFIG_SOUND_RME96XX) += rme96xx.o +obj-$(CONFIG_SOUND_BT878) += btaudio.o +obj-$(CONFIG_SOUND_ALI5455) += ali5455.o ac97_codec.o +obj-$(CONFIG_SOUND_IT8172) += ite8172.o ac97_codec.o +obj-$(CONFIG_SOUND_FORTE) += forte.o ac97_codec.o + +obj-$(CONFIG_SOUND_AD1980) += ac97_plugin_ad1980.o +obj-$(CONFIG_SOUND_WM97XX) += ac97_plugin_wm97xx.o + +ifeq ($(CONFIG_MIDI_EMU10K1),y) + obj-$(CONFIG_SOUND_EMU10K1) += sound.o +endif + +obj-$(CONFIG_SOUND_EMU10K1) += emu10k1/ +obj-$(CONFIG_SOUND_CS4281) += cs4281/ +obj-$(CONFIG_DMASOUND) += dmasound/ + +# Declare multi-part drivers. + +sound-objs := \ + dev_table.o soundcard.o sound_syms.o \ + audio.o audio_syms.o dmabuf.o \ + midi_syms.o midi_synth.o midibuf.o \ + sequencer.o sequencer_syms.o sound_timer.o sys_timer.o + +gus-objs := gus_card.o gus_midi.o gus_vol.o gus_wave.o ics2101.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 +wavefront-objs := wavfront.o wf_midi.o yss225.o + +hostprogs-y := bin2hex hex2hex + +# Files generated that shall be removed upon make clean +clean-files := maui_boot.h 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 Maui / Tropez + +$(obj)/maui.o: $(obj)/maui_boot.h + +ifeq ($(CONFIG_MAUI_HAVE_BOOT),y) + $(obj)/maui_boot.h: $(patsubst "%", %, $(CONFIG_MAUI_BOOT_FILE)) $(obj)/bin2hex + $(obj)/bin2hex -i maui_os < $< > $@ +else + $(obj)/maui_boot.h: + ( \ + echo 'static unsigned char * maui_os = NULL;'; \ + echo 'static int maui_osLen = 0;'; \ + ) > $@ +endif + +# 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: + ( \ + 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: + ( \ + 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 000000000000..90fdcf063d2d --- /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/ac97.c b/sound/oss/ac97.c new file mode 100644 index 000000000000..3ba6d91e891d --- /dev/null +++ b/sound/oss/ac97.c @@ -0,0 +1,452 @@ +#include +#include +#include +#include "ac97.h" + +/* Flag for mono controls. */ +#define MO 0 +/* And for stereo. */ +#define ST 1 + +/* Whether or not the bits in the channel are inverted. */ +#define INV 1 +#define NINV 0 + +static struct ac97_chn_desc { + int ac97_regnum; + int oss_channel; + int maxval; + int is_stereo; + int oss_mask; + int recordNum; + u16 regmask; + int is_inverted; +} mixerRegs[] = { + { AC97_MASTER_VOL_STEREO, SOUND_MIXER_VOLUME, 0x3f, ST, SOUND_MASK_VOLUME, 5, 0x0000, INV }, + { AC97_MASTER_VOL_MONO, SOUND_MIXER_PHONEOUT, 0x3f, MO, SOUND_MASK_PHONEOUT, 6, 0x0000, INV }, + { AC97_MASTER_TONE, SOUND_MIXER_TREBLE, 0x0f, MO, SOUND_MASK_TREBLE, -1, 0x00ff, INV }, + { AC97_MASTER_TONE, SOUND_MIXER_BASS, 0x0f, MO, SOUND_MASK_BASS, -1, 0xff00, INV }, + { AC97_PCBEEP_VOL, SOUND_MIXER_SPEAKER, 0x0f, MO, SOUND_MASK_SPEAKER, -1, 0x001e, INV }, + { AC97_PHONE_VOL, SOUND_MIXER_PHONEIN, 0x1f, MO, SOUND_MASK_PHONEIN, 7, 0x0000, INV }, + { AC97_MIC_VOL, SOUND_MIXER_MIC, 0x1f, MO, SOUND_MASK_MIC, 0, 0x0000, INV }, + { AC97_LINEIN_VOL, SOUND_MIXER_LINE, 0x1f, ST, SOUND_MASK_LINE, 4, 0x0000, INV }, + { AC97_CD_VOL, SOUND_MIXER_CD, 0x1f, ST, SOUND_MASK_CD, 1, 0x0000, INV }, + { AC97_VIDEO_VOL, SOUND_MIXER_VIDEO, 0x1f, ST, SOUND_MASK_VIDEO, 2, 0x0000, INV }, + { AC97_AUX_VOL, SOUND_MIXER_LINE1, 0x1f, ST, SOUND_MASK_LINE1, 3, 0x0000, INV }, + { AC97_PCMOUT_VOL, SOUND_MIXER_PCM, 0x1f, ST, SOUND_MASK_PCM, -1, 0x0000, INV }, + { AC97_RECORD_GAIN, SOUND_MIXER_IGAIN, 0x0f, ST, SOUND_MASK_IGAIN, -1, 0x0000, NINV }, + { -1, -1, 0xff, 0, 0, -1, 0x0000, 0 }, +}; + +static struct ac97_chn_desc * +ac97_find_chndesc (struct ac97_hwint *dev, int oss_channel) +{ + int x; + + for (x = 0; mixerRegs[x].oss_channel != -1; x++) { + if (mixerRegs[x].oss_channel == oss_channel) + return mixerRegs + x; + } + + return NULL; +} + +static inline int +ac97_is_valid_channel (struct ac97_hwint *dev, struct ac97_chn_desc *chn) +{ + return (dev->last_written_mixer_values[chn->ac97_regnum / 2] + != AC97_REG_UNSUPPORTED); +} + +int +ac97_init (struct ac97_hwint *dev) +{ + int x; + int reg0; + + /* Clear out the arrays of cached values. */ + for (x = 0; x < AC97_REG_CNT; x++) + dev->last_written_mixer_values[x] = AC97_REGVAL_UNKNOWN; + + for (x = 0; x < SOUND_MIXER_NRDEVICES; x++) + dev->last_written_OSS_values[x] = AC97_REGVAL_UNKNOWN; + + /* Clear the device masks. */ + dev->mixer_devmask = 0; + dev->mixer_stereomask = 0; + dev->mixer_recmask = 0; + + /* ??? Do a "standard reset" via register 0? */ + + /* Hardware-dependent reset. */ + if (dev->reset_device (dev)) + return -1; + + /* Check the mixer device capabilities. */ + reg0 = dev->read_reg (dev, AC97_RESET); + + if (reg0 < 0) + return -1; + + /* Check for support for treble/bass controls. */ + if (! (reg0 & 4)) { + dev->last_written_mixer_values[AC97_MASTER_TONE / 2] + = AC97_REG_UNSUPPORTED; + } + + /* ??? There may be other tests here? */ + + /* Fill in the device masks. */ + for (x = 0; mixerRegs[x].ac97_regnum != -1; x++) { + if (ac97_is_valid_channel (dev, mixerRegs + x)) { + dev->mixer_devmask |= mixerRegs[x].oss_mask; + + if (mixerRegs[x].is_stereo) + dev->mixer_stereomask |= mixerRegs[x].oss_mask; + + if (mixerRegs[x].recordNum != -1) + dev->mixer_recmask |= mixerRegs[x].oss_mask; + } + } + + return 0; +} + +/* Reset the mixer to the currently saved settings. */ +int +ac97_reset (struct ac97_hwint *dev) +{ + int x; + + if (dev->reset_device (dev)) + return -1; + + /* Now set the registers back to their last-written values. */ + for (x = 0; mixerRegs[x].ac97_regnum != -1; x++) { + int regnum = mixerRegs[x].ac97_regnum; + int value = dev->last_written_mixer_values [regnum / 2]; + if (value >= 0) + ac97_put_register (dev, regnum, value); + } + return 0; +} + +/* Return the contents of register REG; use the cache if the value in it + is valid. Returns a negative error code on failure. */ +static int +ac97_get_register (struct ac97_hwint *dev, u8 reg) +{ + if (reg > 127 || (reg & 1)) + return -EINVAL; + + /* See if it's in the cache, or if it's just plain invalid. */ + switch (dev->last_written_mixer_values[reg / 2]) { + case AC97_REG_UNSUPPORTED: + return -EINVAL; + break; + case AC97_REGVAL_UNKNOWN: + dev->last_written_mixer_values[reg / 2] = dev->read_reg (dev, reg); + break; + default: + break; + } + return dev->last_written_mixer_values[reg / 2]; +} + +/* Write VALUE to AC97 register REG, and cache its value in the last-written + cache. Returns a negative error code on failure, or 0 on success. */ +int +ac97_put_register (struct ac97_hwint *dev, u8 reg, u16 value) +{ + if (reg > 127 || (reg & 1)) + return -EINVAL; + + if (dev->last_written_mixer_values[reg / 2] == AC97_REG_UNSUPPORTED) + return -EINVAL; + else { + int res = dev->write_reg (dev, reg, value); + if (res >= 0) { + dev->last_written_mixer_values[reg / 2] = value; + return 0; + } + else + return res; + } +} + +/* Scale VALUE (a value fro 0 to MAXVAL) to a value from 0-100. If + IS_STEREO is set, VALUE is a stereo value; the left channel value + is in the lower 8 bits, and the right channel value is in the upper + 8 bits. + + A negative error code is returned on failure, or the unsigned + scaled value on success. */ + +static int +ac97_scale_to_oss_val (int value, int maxval, int is_stereo, int inv) +{ + /* Muted? */ + if (value & AC97_MUTE) + return 0; + + if (is_stereo) + return (ac97_scale_to_oss_val (value & 255, maxval, 0, inv) << 8) + | (ac97_scale_to_oss_val ((value >> 8) & 255, maxval, 0, inv) << 0); + else { + int i; + + /* Inverted. */ + if (inv) + value = maxval - value; + + i = (value * 100 + (maxval / 2)) / maxval; + if (i > 100) + i = 100; + if (i < 0) + i = 0; + return i; + } +} + +static int +ac97_scale_from_oss_val (int value, int maxval, int is_stereo, int inv) +{ + if (is_stereo) + return (ac97_scale_from_oss_val (value & 255, maxval, 0, inv) << 8) + | (ac97_scale_from_oss_val ((value >> 8) & 255, maxval, 0, inv) << 0); + else { + int i = ((value & 255) * maxval + 50) / 100; + if (inv) + i = maxval - i; + if (i < 0) + i = 0; + if (i > maxval) + i = maxval; + return i; + } +} + +static int +ac97_set_mixer (struct ac97_hwint *dev, int oss_channel, u16 oss_value) +{ + int scaled_value; + struct ac97_chn_desc *channel = ac97_find_chndesc (dev, oss_channel); + int result; + + if (channel == NULL) + return -ENODEV; + if (! ac97_is_valid_channel (dev, channel)) + return -ENODEV; + scaled_value = ac97_scale_from_oss_val (oss_value, channel->maxval, + channel->is_stereo, + channel->is_inverted); + if (scaled_value < 0) + return scaled_value; + + if (channel->regmask != 0) { + int mv; + + int oldval = ac97_get_register (dev, channel->ac97_regnum); + if (oldval < 0) + return oldval; + + for (mv = channel->regmask; ! (mv & 1); mv >>= 1) + scaled_value <<= 1; + + scaled_value &= channel->regmask; + scaled_value |= (oldval & ~channel->regmask); + } + result = ac97_put_register (dev, channel->ac97_regnum, scaled_value); + if (result == 0) + dev->last_written_OSS_values[oss_channel] = oss_value; + return result; +} + +static int +ac97_get_mixer_scaled (struct ac97_hwint *dev, int oss_channel) +{ + struct ac97_chn_desc *channel = ac97_find_chndesc (dev, oss_channel); + int regval; + + if (channel == NULL) + return -ENODEV; + + if (! ac97_is_valid_channel (dev, channel)) + return -ENODEV; + + regval = ac97_get_register (dev, channel->ac97_regnum); + + if (regval < 0) + return regval; + + if (channel->regmask != 0) { + int mv; + + regval &= channel->regmask; + + for (mv = channel->regmask; ! (mv & 1); mv >>= 1) + regval >>= 1; + } + return ac97_scale_to_oss_val (regval, channel->maxval, + channel->is_stereo, + channel->is_inverted); +} + +static int +ac97_get_recmask (struct ac97_hwint *dev) +{ + int recReg = ac97_get_register (dev, AC97_RECORD_SELECT); + + if (recReg < 0) + return recReg; + else { + int x; + for (x = 0; mixerRegs[x].ac97_regnum >= 0; x++) { + if (mixerRegs[x].recordNum == (recReg & 7)) + return mixerRegs[x].oss_mask; + } + return -ENODEV; + } +} + +static int +ac97_set_recmask (struct ac97_hwint *dev, int oss_recmask) +{ + int x; + + if (oss_recmask == 0) + oss_recmask = SOUND_MIXER_MIC; + + for (x = 0; mixerRegs[x].ac97_regnum >= 0; x++) { + if ((mixerRegs[x].recordNum >= 0) + && (oss_recmask & mixerRegs[x].oss_mask)) + break; + } + if (mixerRegs[x].ac97_regnum < 0) + return -ENODEV; + else { + int regval = (mixerRegs[x].recordNum << 8) | mixerRegs[x].recordNum; + int res = ac97_put_register (dev, AC97_RECORD_SELECT, regval); + if (res == 0) + return ac97_get_recmask (dev); + else + return res; + } +} + +/* Set the mixer DEV to the list of values in VALUE_LIST. Return 0 on + success, or a negative error code. */ +int +ac97_set_values (struct ac97_hwint *dev, + struct ac97_mixer_value_list *value_list) +{ + int x; + + for (x = 0; value_list[x].oss_channel != -1; x++) { + int chnum = value_list[x].oss_channel; + struct ac97_chn_desc *chent = ac97_find_chndesc (dev, chnum); + if (chent != NULL) { + u16 val; + int res; + + if (chent->is_stereo) + val = (value_list[x].value.stereo.right << 8) + | value_list[x].value.stereo.left; + else { + /* We do this so the returned value looks OK in the + mixer app. It's not necessary otherwise. */ + val = (value_list[x].value.mono << 8) + | value_list[x].value.mono; + } + res = ac97_set_mixer (dev, chnum, val); + if (res < 0) + return res; + } + else + return -ENODEV; + } + return 0; +} + +int +ac97_mixer_ioctl (struct ac97_hwint *dev, unsigned int cmd, void __user *arg) +{ + int ret; + + switch (cmd) { + case SOUND_MIXER_READ_RECSRC: + ret = ac97_get_recmask (dev); + break; + + case SOUND_MIXER_WRITE_RECSRC: + { + if (get_user (ret, (int __user *) arg)) + ret = -EFAULT; + else + ret = ac97_set_recmask (dev, ret); + } + break; + + case SOUND_MIXER_READ_CAPS: + ret = SOUND_CAP_EXCL_INPUT; + break; + + case SOUND_MIXER_READ_DEVMASK: + ret = dev->mixer_devmask; + break; + + case SOUND_MIXER_READ_RECMASK: + ret = dev->mixer_recmask; + break; + + case SOUND_MIXER_READ_STEREODEVS: + ret = dev->mixer_stereomask; + break; + + default: + /* Read or write request. */ + ret = -EINVAL; + if (_IOC_TYPE (cmd) == 'M') { + int dir = _SIOC_DIR (cmd); + int channel = _IOC_NR (cmd); + + if (channel >= 0 && channel < SOUND_MIXER_NRDEVICES) { + ret = 0; + if (dir & _SIOC_WRITE) { + int val; + if (get_user (val, (int __user *) arg) == 0) + ret = ac97_set_mixer (dev, channel, val); + else + ret = -EFAULT; + } + if (ret >= 0 && (dir & _SIOC_READ)) { + if (dev->last_written_OSS_values[channel] + == AC97_REGVAL_UNKNOWN) + dev->last_written_OSS_values[channel] + = ac97_get_mixer_scaled (dev, channel); + ret = dev->last_written_OSS_values[channel]; + } + } + } + break; + } + + if (ret < 0) + return ret; + else + return put_user(ret, (int __user *) arg); +} + +EXPORT_SYMBOL(ac97_init); +EXPORT_SYMBOL(ac97_set_values); +EXPORT_SYMBOL(ac97_put_register); +EXPORT_SYMBOL(ac97_mixer_ioctl); +EXPORT_SYMBOL(ac97_reset); +MODULE_LICENSE("GPL"); + + +/* + * Local variables: + * c-basic-offset: 4 + * End: + */ diff --git a/sound/oss/ac97.h b/sound/oss/ac97.h new file mode 100644 index 000000000000..77d454ea3202 --- /dev/null +++ b/sound/oss/ac97.h @@ -0,0 +1,204 @@ +/* + * ac97.h + * + * definitions for the AC97, Intel's Audio Codec 97 Spec + * also includes support for a generic AC97 interface + */ + +#ifndef _AC97_H_ +#define _AC97_H_ +#include "sound_config.h" +#include "sound_calls.h" + +#define AC97_RESET 0x0000 // +#define AC97_MASTER_VOL_STEREO 0x0002 // Line Out +#define AC97_HEADPHONE_VOL 0x0004 // +#define AC97_MASTER_VOL_MONO 0x0006 // TAD Output +#define AC97_MASTER_TONE 0x0008 // +#define AC97_PCBEEP_VOL 0x000a // none +#define AC97_PHONE_VOL 0x000c // TAD Input (mono) +#define AC97_MIC_VOL 0x000e // MIC Input (mono) +#define AC97_LINEIN_VOL 0x0010 // Line Input (stereo) +#define AC97_CD_VOL 0x0012 // CD Input (stereo) +#define AC97_VIDEO_VOL 0x0014 // none +#define AC97_AUX_VOL 0x0016 // Aux Input (stereo) +#define AC97_PCMOUT_VOL 0x0018 // Wave Output (stereo) +#define AC97_RECORD_SELECT 0x001a // +#define AC97_RECORD_GAIN 0x001c +#define AC97_RECORD_GAIN_MIC 0x001e +#define AC97_GENERAL_PURPOSE 0x0020 +#define AC97_3D_CONTROL 0x0022 +#define AC97_MODEM_RATE 0x0024 +#define AC97_POWER_CONTROL 0x0026 + +/* registers 0x0028 - 0x0058 are reserved */ + +/* AC'97 2.0 */ +#define AC97_EXTENDED_ID 0x0028 /* Extended Audio ID */ +#define AC97_EXTENDED_STATUS 0x002A /* Extended Audio Status */ +#define AC97_PCM_FRONT_DAC_RATE 0x002C /* PCM Front DAC Rate */ +#define AC97_PCM_SURR_DAC_RATE 0x002E /* PCM Surround DAC Rate */ +#define AC97_PCM_LFE_DAC_RATE 0x0030 /* PCM LFE DAC Rate */ +#define AC97_PCM_LR_ADC_RATE 0x0032 /* PCM LR DAC Rate */ +#define AC97_PCM_MIC_ADC_RATE 0x0034 /* PCM MIC ADC Rate */ +#define AC97_CENTER_LFE_MASTER 0x0036 /* Center + LFE Master Volume */ +#define AC97_SURROUND_MASTER 0x0038 /* Surround (Rear) Master Volume */ +#define AC97_RESERVED_3A 0x003A /* Reserved */ +/* range 0x3c-0x58 - MODEM */ + +/* registers 0x005a - 0x007a are vendor reserved */ + +#define AC97_VENDOR_ID1 0x007c +#define AC97_VENDOR_ID2 0x007e + +/* volume control bit defines */ + +#define AC97_MUTE 0x8000 +#define AC97_MICBOOST 0x0040 +#define AC97_LEFTVOL 0x3f00 +#define AC97_RIGHTVOL 0x003f + +/* record mux defines */ + +#define AC97_RECMUX_MIC 0x0000 +#define AC97_RECMUX_CD 0x0101 +#define AC97_RECMUX_VIDEO 0x0202 /* not used */ +#define AC97_RECMUX_AUX 0x0303 +#define AC97_RECMUX_LINE 0x0404 +#define AC97_RECMUX_STEREO_MIX 0x0505 +#define AC97_RECMUX_MONO_MIX 0x0606 +#define AC97_RECMUX_PHONE 0x0707 + + +/* general purpose register bit defines */ + +#define AC97_GP_LPBK 0x0080 /* Loopback mode */ +#define AC97_GP_MS 0x0100 /* Mic Select 0=Mic1, 1=Mic2 */ +#define AC97_GP_MIX 0x0200 /* Mono output select 0=Mix, 1=Mic */ +#define AC97_GP_RLBK 0x0400 /* Remote Loopback - Modem line codec */ +#define AC97_GP_LLBK 0x0800 /* Local Loopback - Modem Line codec */ +#define AC97_GP_LD 0x1000 /* Loudness 1=on */ +#define AC97_GP_3D 0x2000 /* 3D Enhancement 1=on */ +#define AC97_GP_ST 0x4000 /* Stereo Enhancement 1=on */ +#define AC97_GP_POP 0x8000 /* Pcm Out Path, 0=pre 3D, 1=post 3D */ + + +/* powerdown control and status bit defines */ + +/* status */ +#define AC97_PWR_MDM 0x0010 /* Modem section ready */ +#define AC97_PWR_REF 0x0008 /* Vref nominal */ +#define AC97_PWR_ANL 0x0004 /* Analog section ready */ +#define AC97_PWR_DAC 0x0002 /* DAC section ready */ +#define AC97_PWR_ADC 0x0001 /* ADC section ready */ + +/* control */ +#define AC97_PWR_PR0 0x0100 /* ADC and Mux powerdown */ +#define AC97_PWR_PR1 0x0200 /* DAC powerdown */ +#define AC97_PWR_PR2 0x0400 /* Output mixer powerdown (Vref on) */ +#define AC97_PWR_PR3 0x0800 /* Output mixer powerdown (Vref off) */ +#define AC97_PWR_PR4 0x1000 /* AC-link powerdown */ +#define AC97_PWR_PR5 0x2000 /* Internal Clk disable */ +#define AC97_PWR_PR6 0x4000 /* HP amp powerdown */ +#define AC97_PWR_PR7 0x8000 /* Modem off - if supported */ + +/* useful power states */ +#define AC97_PWR_D0 0x0000 /* everything on */ +#define AC97_PWR_D1 AC97_PWR_PR0|AC97_PWR_PR1|AC97_PWR_PR4 +#define AC97_PWR_D2 AC97_PWR_PR0|AC97_PWR_PR1|AC97_PWR_PR2|AC97_PWR_PR3|AC97_PWR_PR4 +#define AC97_PWR_D3 AC97_PWR_PR0|AC97_PWR_PR1|AC97_PWR_PR2|AC97_PWR_PR3|AC97_PWR_PR4 +#define AC97_PWR_ANLOFF AC97_PWR_PR2|AC97_PWR_PR3 /* analog section off */ + +/* Total number of defined registers. */ +#define AC97_REG_CNT 64 + +/* Generic AC97 mixer interface. */ + +/* Structure describing access to the hardware. */ +struct ac97_hwint +{ + /* Perform any hardware-specific reset and initialization. Returns + 0 on success, or a negative error code. */ + int (*reset_device) (struct ac97_hwint *dev); + + /* Returns the contents of the specified register REG. The caller + should check to see if the desired contents are available in + the cache first, if applicable. Returns a positive unsigned value + representing the contents of the register, or a negative error + code. */ + int (*read_reg) (struct ac97_hwint *dev, u8 reg); + + /* Writes VALUE to register REG. Returns 0 on success, or a + negative error code. */ + int (*write_reg) (struct ac97_hwint *dev, u8 reg, u16 value); + + /* Hardware-specific information. */ + void *driver_private; + + /* Three OSS masks. */ + int mixer_devmask; + int mixer_stereomask; + int mixer_recmask; + + /* The mixer cache. The indices correspond to the AC97 hardware register + number / 2, since the register numbers are always an even number. + + Unknown values are set to -1; unsupported registers contain a + -2. */ + int last_written_mixer_values[AC97_REG_CNT]; + + /* A cache of values written via OSS; we need these so we can return + the values originally written by the user. + + Why the original user values? Because the real-world hardware + has less precision, and some existing applications assume that + they will get back the exact value that they wrote (aumix). + + A -1 value indicates that no value has been written to this mixer + channel via OSS. */ + int last_written_OSS_values[SOUND_MIXER_NRDEVICES]; +}; + +/* Values stored in the register cache. */ +#define AC97_REGVAL_UNKNOWN -1 +#define AC97_REG_UNSUPPORTED -2 + +struct ac97_mixer_value_list +{ + /* Mixer channel to set. List is terminated by a value of -1. */ + int oss_channel; + /* The scaled value to set it to; values generally range from 0-100. */ + union { + struct { + u8 left, right; + } stereo; + u8 mono; + } value; +}; + +/* Initialize the ac97 mixer by resetting it. */ +extern int ac97_init (struct ac97_hwint *dev); + +/* Sets the mixer DEV to the values in VALUE_LIST. Returns 0 on success, + or a negative error code. */ +extern int ac97_set_values (struct ac97_hwint *dev, + struct ac97_mixer_value_list *value_list); + +/* Writes the specified VALUE to the AC97 register REG in the mixer. + Takes care of setting the last-written cache as well. */ +extern int ac97_put_register (struct ac97_hwint *dev, u8 reg, u16 value); + +/* Default ioctl. */ +extern int ac97_mixer_ioctl (struct ac97_hwint *dev, unsigned int cmd, + void __user * arg); + +/* Do a complete reset on the AC97 mixer, restoring all mixer registers to + the current values. Normally used after an APM resume event. */ +extern int ac97_reset (struct ac97_hwint *dev); +#endif + +/* + * Local variables: + * c-basic-offset: 4 + * End: + */ diff --git a/sound/oss/ac97_codec.c b/sound/oss/ac97_codec.c new file mode 100644 index 000000000000..124b1e10a13d --- /dev/null +++ b/sound/oss/ac97_codec.c @@ -0,0 +1,1576 @@ +/* + * ac97_codec.c: Generic AC97 mixer/modem module + * + * Derived from ac97 mixer in maestro and trident driver. + * + * Copyright 2000 Silicon Integrated System Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; 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. + * + ************************************************************************** + * + * The Intel Audio Codec '97 specification is available at the Intel + * audio homepage: http://developer.intel.com/ial/scalableplatforms/audio/ + * + * The specification itself is currently available at: + * ftp://download.intel.com/ial/scalableplatforms/ac97r22.pdf + * + ************************************************************************** + * + * History + * May 02, 2003 Liam Girdwood + * Removed non existant WM9700 + * Added support for WM9705, WM9708, WM9709, WM9710, WM9711 + * WM9712 and WM9717 + * Mar 28, 2002 Randolph Bentson + * corrections to support WM9707 in ViewPad 1000 + * v0.4 Mar 15 2000 Ollie Lho + * dual codecs support verified with 4 channels output + * v0.3 Feb 22 2000 Ollie Lho + * bug fix for record mask setting + * v0.2 Feb 10 2000 Ollie Lho + * add ac97_read_proc for /proc/driver/{vendor}/ac97 + * v0.1 Jan 14 2000 Ollie Lho + * Isolated from trident.c to support multiple ac97 codec + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CODEC_ID_BUFSZ 14 + +static int ac97_read_mixer(struct ac97_codec *codec, int oss_channel); +static void ac97_write_mixer(struct ac97_codec *codec, int oss_channel, + unsigned int left, unsigned int right); +static void ac97_set_mixer(struct ac97_codec *codec, unsigned int oss_mixer, unsigned int val ); +static int ac97_recmask_io(struct ac97_codec *codec, int rw, int mask); +static int ac97_mixer_ioctl(struct ac97_codec *codec, unsigned int cmd, unsigned long arg); + +static int ac97_init_mixer(struct ac97_codec *codec); + +static int wolfson_init03(struct ac97_codec * codec); +static int wolfson_init04(struct ac97_codec * codec); +static int wolfson_init05(struct ac97_codec * codec); +static int wolfson_init11(struct ac97_codec * codec); +static int wolfson_init13(struct ac97_codec * codec); +static int tritech_init(struct ac97_codec * codec); +static int tritech_maestro_init(struct ac97_codec * codec); +static int sigmatel_9708_init(struct ac97_codec *codec); +static int sigmatel_9721_init(struct ac97_codec *codec); +static int sigmatel_9744_init(struct ac97_codec *codec); +static int ad1886_init(struct ac97_codec *codec); +static int eapd_control(struct ac97_codec *codec, int); +static int crystal_digital_control(struct ac97_codec *codec, int slots, int rate, int mode); +static int cmedia_init(struct ac97_codec * codec); +static int cmedia_digital_control(struct ac97_codec *codec, int slots, int rate, int mode); +static int generic_digital_control(struct ac97_codec *codec, int slots, int rate, int mode); + + +/* + * AC97 operations. + * + * If you are adding a codec then you should be able to use + * eapd_ops - any codec that supports EAPD amp control (most) + * null_ops - any ancient codec that supports nothing + * + * The three functions are + * init - used for non AC97 standard initialisation + * amplifier - used to do amplifier control (1=on 0=off) + * digital - switch to digital modes (0 = analog) + * + * Not all codecs support all features, not all drivers use all the + * operations yet + */ + +static struct ac97_ops null_ops = { NULL, NULL, NULL }; +static struct ac97_ops default_ops = { NULL, eapd_control, NULL }; +static struct ac97_ops default_digital_ops = { NULL, eapd_control, generic_digital_control}; +static struct ac97_ops wolfson_ops03 = { wolfson_init03, NULL, NULL }; +static struct ac97_ops wolfson_ops04 = { wolfson_init04, NULL, NULL }; +static struct ac97_ops wolfson_ops05 = { wolfson_init05, NULL, NULL }; +static struct ac97_ops wolfson_ops11 = { wolfson_init11, NULL, NULL }; +static struct ac97_ops wolfson_ops13 = { wolfson_init13, NULL, NULL }; +static struct ac97_ops tritech_ops = { tritech_init, NULL, NULL }; +static struct ac97_ops tritech_m_ops = { tritech_maestro_init, NULL, NULL }; +static struct ac97_ops sigmatel_9708_ops = { sigmatel_9708_init, NULL, NULL }; +static struct ac97_ops sigmatel_9721_ops = { sigmatel_9721_init, NULL, NULL }; +static struct ac97_ops sigmatel_9744_ops = { sigmatel_9744_init, NULL, NULL }; +static struct ac97_ops crystal_digital_ops = { NULL, eapd_control, crystal_digital_control }; +static struct ac97_ops ad1886_ops = { ad1886_init, eapd_control, NULL }; +static struct ac97_ops cmedia_ops = { NULL, eapd_control, NULL}; +static struct ac97_ops cmedia_digital_ops = { cmedia_init, eapd_control, cmedia_digital_control}; + +/* sorted by vendor/device id */ +static const struct { + u32 id; + char *name; + struct ac97_ops *ops; + int flags; +} ac97_codec_ids[] = { + {0x41445303, "Analog Devices AD1819", &null_ops}, + {0x41445340, "Analog Devices AD1881", &null_ops}, + {0x41445348, "Analog Devices AD1881A", &null_ops}, + {0x41445360, "Analog Devices AD1885", &default_ops}, + {0x41445361, "Analog Devices AD1886", &ad1886_ops}, + {0x41445370, "Analog Devices AD1981", &null_ops}, + {0x41445372, "Analog Devices AD1981A", &null_ops}, + {0x41445374, "Analog Devices AD1981B", &null_ops}, + {0x41445460, "Analog Devices AD1885", &default_ops}, + {0x41445461, "Analog Devices AD1886", &ad1886_ops}, + {0x414B4D00, "Asahi Kasei AK4540", &null_ops}, + {0x414B4D01, "Asahi Kasei AK4542", &null_ops}, + {0x414B4D02, "Asahi Kasei AK4543", &null_ops}, + {0x414C4326, "ALC100P", &null_ops}, + {0x414C4710, "ALC200/200P", &null_ops}, + {0x414C4720, "ALC650", &default_digital_ops}, + {0x434D4941, "CMedia", &cmedia_ops, AC97_NO_PCM_VOLUME }, + {0x434D4942, "CMedia", &cmedia_ops, AC97_NO_PCM_VOLUME }, + {0x434D4961, "CMedia", &cmedia_digital_ops, AC97_NO_PCM_VOLUME }, + {0x43525900, "Cirrus Logic CS4297", &default_ops}, + {0x43525903, "Cirrus Logic CS4297", &default_ops}, + {0x43525913, "Cirrus Logic CS4297A rev A", &default_ops}, + {0x43525914, "Cirrus Logic CS4297A rev B", &default_ops}, + {0x43525923, "Cirrus Logic CS4298", &null_ops}, + {0x4352592B, "Cirrus Logic CS4294", &null_ops}, + {0x4352592D, "Cirrus Logic CS4294", &null_ops}, + {0x43525931, "Cirrus Logic CS4299 rev A", &crystal_digital_ops}, + {0x43525933, "Cirrus Logic CS4299 rev C", &crystal_digital_ops}, + {0x43525934, "Cirrus Logic CS4299 rev D", &crystal_digital_ops}, + {0x43585442, "CXT66", &default_ops, AC97_DELUDED_MODEM }, + {0x44543031, "Diamond Technology DT0893", &default_ops}, + {0x45838308, "ESS Allegro ES1988", &null_ops}, + {0x49434511, "ICE1232", &null_ops}, /* I hope --jk */ + {0x4e534331, "National Semiconductor LM4549", &null_ops}, + {0x53494c22, "Silicon Laboratory Si3036", &null_ops}, + {0x53494c23, "Silicon Laboratory Si3038", &null_ops}, + {0x545200FF, "TriTech TR?????", &tritech_m_ops}, + {0x54524102, "TriTech TR28022", &null_ops}, + {0x54524103, "TriTech TR28023", &null_ops}, + {0x54524106, "TriTech TR28026", &null_ops}, + {0x54524108, "TriTech TR28028", &tritech_ops}, + {0x54524123, "TriTech TR A5", &null_ops}, + {0x574D4C03, "Wolfson WM9703/07/08/17", &wolfson_ops03}, + {0x574D4C04, "Wolfson WM9704M/WM9704Q", &wolfson_ops04}, + {0x574D4C05, "Wolfson WM9705/WM9710", &wolfson_ops05}, + {0x574D4C09, "Wolfson WM9709", &null_ops}, + {0x574D4C12, "Wolfson WM9711/9712", &wolfson_ops11}, + {0x574D4C13, "Wolfson WM9713", &wolfson_ops13, AC97_DEFAULT_POWER_OFF}, + {0x83847600, "SigmaTel STAC????", &null_ops}, + {0x83847604, "SigmaTel STAC9701/3/4/5", &null_ops}, + {0x83847605, "SigmaTel STAC9704", &null_ops}, + {0x83847608, "SigmaTel STAC9708", &sigmatel_9708_ops}, + {0x83847609, "SigmaTel STAC9721/23", &sigmatel_9721_ops}, + {0x83847644, "SigmaTel STAC9744/45", &sigmatel_9744_ops}, + {0x83847652, "SigmaTel STAC9752/53", &default_ops}, + {0x83847656, "SigmaTel STAC9756/57", &sigmatel_9744_ops}, + {0x83847666, "SigmaTel STAC9750T", &sigmatel_9744_ops}, + {0x83847684, "SigmaTel STAC9783/84?", &null_ops}, + {0x57454301, "Winbond 83971D", &null_ops}, +}; + +static const char *ac97_stereo_enhancements[] = +{ + /* 0 */ "No 3D Stereo Enhancement", + /* 1 */ "Analog Devices Phat Stereo", + /* 2 */ "Creative Stereo Enhancement", + /* 3 */ "National Semi 3D Stereo Enhancement", + /* 4 */ "YAMAHA Ymersion", + /* 5 */ "BBE 3D Stereo Enhancement", + /* 6 */ "Crystal Semi 3D Stereo Enhancement", + /* 7 */ "Qsound QXpander", + /* 8 */ "Spatializer 3D Stereo Enhancement", + /* 9 */ "SRS 3D Stereo Enhancement", + /* 10 */ "Platform Tech 3D Stereo Enhancement", + /* 11 */ "AKM 3D Audio", + /* 12 */ "Aureal Stereo Enhancement", + /* 13 */ "Aztech 3D Enhancement", + /* 14 */ "Binaura 3D Audio Enhancement", + /* 15 */ "ESS Technology Stereo Enhancement", + /* 16 */ "Harman International VMAx", + /* 17 */ "Nvidea 3D Stereo Enhancement", + /* 18 */ "Philips Incredible Sound", + /* 19 */ "Texas Instruments 3D Stereo Enhancement", + /* 20 */ "VLSI Technology 3D Stereo Enhancement", + /* 21 */ "TriTech 3D Stereo Enhancement", + /* 22 */ "Realtek 3D Stereo Enhancement", + /* 23 */ "Samsung 3D Stereo Enhancement", + /* 24 */ "Wolfson Microelectronics 3D Enhancement", + /* 25 */ "Delta Integration 3D Enhancement", + /* 26 */ "SigmaTel 3D Enhancement", + /* 27 */ "Winbond 3D Stereo Enhancement", + /* 28 */ "Rockwell 3D Stereo Enhancement", + /* 29 */ "Reserved 29", + /* 30 */ "Reserved 30", + /* 31 */ "Reserved 31" +}; + +/* this table has default mixer values for all OSS mixers. */ +static struct mixer_defaults { + int mixer; + unsigned int value; +} mixer_defaults[SOUND_MIXER_NRDEVICES] = { + /* all values 0 -> 100 in bytes */ + {SOUND_MIXER_VOLUME, 0x4343}, + {SOUND_MIXER_BASS, 0x4343}, + {SOUND_MIXER_TREBLE, 0x4343}, + {SOUND_MIXER_PCM, 0x4343}, + {SOUND_MIXER_SPEAKER, 0x4343}, + {SOUND_MIXER_LINE, 0x4343}, + {SOUND_MIXER_MIC, 0x0000}, + {SOUND_MIXER_CD, 0x4343}, + {SOUND_MIXER_ALTPCM, 0x4343}, + {SOUND_MIXER_IGAIN, 0x4343}, + {SOUND_MIXER_LINE1, 0x4343}, + {SOUND_MIXER_PHONEIN, 0x4343}, + {SOUND_MIXER_PHONEOUT, 0x4343}, + {SOUND_MIXER_VIDEO, 0x4343}, + {-1,0} +}; + +/* table to scale scale from OSS mixer value to AC97 mixer register value */ +static struct ac97_mixer_hw { + unsigned char offset; + int scale; +} ac97_hw[SOUND_MIXER_NRDEVICES]= { + [SOUND_MIXER_VOLUME] = {AC97_MASTER_VOL_STEREO,64}, + [SOUND_MIXER_BASS] = {AC97_MASTER_TONE, 16}, + [SOUND_MIXER_TREBLE] = {AC97_MASTER_TONE, 16}, + [SOUND_MIXER_PCM] = {AC97_PCMOUT_VOL, 32}, + [SOUND_MIXER_SPEAKER] = {AC97_PCBEEP_VOL, 16}, + [SOUND_MIXER_LINE] = {AC97_LINEIN_VOL, 32}, + [SOUND_MIXER_MIC] = {AC97_MIC_VOL, 32}, + [SOUND_MIXER_CD] = {AC97_CD_VOL, 32}, + [SOUND_MIXER_ALTPCM] = {AC97_HEADPHONE_VOL, 64}, + [SOUND_MIXER_IGAIN] = {AC97_RECORD_GAIN, 16}, + [SOUND_MIXER_LINE1] = {AC97_AUX_VOL, 32}, + [SOUND_MIXER_PHONEIN] = {AC97_PHONE_VOL, 32}, + [SOUND_MIXER_PHONEOUT] = {AC97_MASTER_VOL_MONO, 64}, + [SOUND_MIXER_VIDEO] = {AC97_VIDEO_VOL, 32}, +}; + +/* the following tables allow us to go from OSS <-> ac97 quickly. */ +enum ac97_recsettings { + AC97_REC_MIC=0, + AC97_REC_CD, + AC97_REC_VIDEO, + AC97_REC_AUX, + AC97_REC_LINE, + AC97_REC_STEREO, /* combination of all enabled outputs.. */ + AC97_REC_MONO, /*.. or the mono equivalent */ + AC97_REC_PHONE +}; + +static const unsigned int ac97_rm2oss[] = { + [AC97_REC_MIC] = SOUND_MIXER_MIC, + [AC97_REC_CD] = SOUND_MIXER_CD, + [AC97_REC_VIDEO] = SOUND_MIXER_VIDEO, + [AC97_REC_AUX] = SOUND_MIXER_LINE1, + [AC97_REC_LINE] = SOUND_MIXER_LINE, + [AC97_REC_STEREO]= SOUND_MIXER_IGAIN, + [AC97_REC_PHONE] = SOUND_MIXER_PHONEIN +}; + +/* indexed by bit position */ +static const unsigned int ac97_oss_rm[] = { + [SOUND_MIXER_MIC] = AC97_REC_MIC, + [SOUND_MIXER_CD] = AC97_REC_CD, + [SOUND_MIXER_VIDEO] = AC97_REC_VIDEO, + [SOUND_MIXER_LINE1] = AC97_REC_AUX, + [SOUND_MIXER_LINE] = AC97_REC_LINE, + [SOUND_MIXER_IGAIN] = AC97_REC_STEREO, + [SOUND_MIXER_PHONEIN] = AC97_REC_PHONE +}; + +static LIST_HEAD(codecs); +static LIST_HEAD(codec_drivers); +static DECLARE_MUTEX(codec_sem); + +/* reads the given OSS mixer from the ac97 the caller must have insured that the ac97 knows + about that given mixer, and should be holding a spinlock for the card */ +static int ac97_read_mixer(struct ac97_codec *codec, int oss_channel) +{ + u16 val; + int ret = 0; + int scale; + struct ac97_mixer_hw *mh = &ac97_hw[oss_channel]; + + val = codec->codec_read(codec , mh->offset); + + if (val & AC97_MUTE) { + ret = 0; + } else if (AC97_STEREO_MASK & (1 << oss_channel)) { + /* nice stereo mixers .. */ + int left,right; + + left = (val >> 8) & 0x7f; + right = val & 0x7f; + + if (oss_channel == SOUND_MIXER_IGAIN) { + right = (right * 100) / mh->scale; + left = (left * 100) / mh->scale; + } else { + /* these may have 5 or 6 bit resolution */ + if(oss_channel == SOUND_MIXER_VOLUME || oss_channel == SOUND_MIXER_ALTPCM) + scale = (1 << codec->bit_resolution); + else + scale = mh->scale; + + right = 100 - ((right * 100) / scale); + left = 100 - ((left * 100) / scale); + } + ret = left | (right << 8); + } else if (oss_channel == SOUND_MIXER_SPEAKER) { + ret = 100 - ((((val & 0x1e)>>1) * 100) / mh->scale); + } else if (oss_channel == SOUND_MIXER_PHONEIN) { + ret = 100 - (((val & 0x1f) * 100) / mh->scale); + } else if (oss_channel == SOUND_MIXER_PHONEOUT) { + scale = (1 << codec->bit_resolution); + ret = 100 - (((val & 0x1f) * 100) / scale); + } else if (oss_channel == SOUND_MIXER_MIC) { + ret = 100 - (((val & 0x1f) * 100) / mh->scale); + /* the low bit is optional in the tone sliders and masking + it lets us avoid the 0xf 'bypass'.. */ + } else if (oss_channel == SOUND_MIXER_BASS) { + ret = 100 - ((((val >> 8) & 0xe) * 100) / mh->scale); + } else if (oss_channel == SOUND_MIXER_TREBLE) { + ret = 100 - (((val & 0xe) * 100) / mh->scale); + } + +#ifdef DEBUG + printk("ac97_codec: read OSS mixer %2d (%s ac97 register 0x%02x), " + "0x%04x -> 0x%04x\n", + oss_channel, codec->id ? "Secondary" : "Primary", + mh->offset, val, ret); +#endif + + return ret; +} + +/* write the OSS encoded volume to the given OSS encoded mixer, again caller's job to + make sure all is well in arg land, call with spinlock held */ +static void ac97_write_mixer(struct ac97_codec *codec, int oss_channel, + unsigned int left, unsigned int right) +{ + u16 val = 0; + int scale; + struct ac97_mixer_hw *mh = &ac97_hw[oss_channel]; + +#ifdef DEBUG + printk("ac97_codec: wrote OSS mixer %2d (%s ac97 register 0x%02x), " + "left vol:%2d, right vol:%2d:", + oss_channel, codec->id ? "Secondary" : "Primary", + mh->offset, left, right); +#endif + + if (AC97_STEREO_MASK & (1 << oss_channel)) { + /* stereo mixers */ + if (left == 0 && right == 0) { + val = AC97_MUTE; + } else { + if (oss_channel == SOUND_MIXER_IGAIN) { + right = (right * mh->scale) / 100; + left = (left * mh->scale) / 100; + if (right >= mh->scale) + right = mh->scale-1; + if (left >= mh->scale) + left = mh->scale-1; + } else { + /* these may have 5 or 6 bit resolution */ + if (oss_channel == SOUND_MIXER_VOLUME || + oss_channel == SOUND_MIXER_ALTPCM) + scale = (1 << codec->bit_resolution); + else + scale = mh->scale; + + right = ((100 - right) * scale) / 100; + left = ((100 - left) * scale) / 100; + if (right >= scale) + right = scale-1; + if (left >= scale) + left = scale-1; + } + val = (left << 8) | right; + } + } else if (oss_channel == SOUND_MIXER_BASS) { + val = codec->codec_read(codec , mh->offset) & ~0x0f00; + left = ((100 - left) * mh->scale) / 100; + if (left >= mh->scale) + left = mh->scale-1; + val |= (left << 8) & 0x0e00; + } else if (oss_channel == SOUND_MIXER_TREBLE) { + val = codec->codec_read(codec , mh->offset) & ~0x000f; + left = ((100 - left) * mh->scale) / 100; + if (left >= mh->scale) + left = mh->scale-1; + val |= left & 0x000e; + } else if(left == 0) { + val = AC97_MUTE; + } else if (oss_channel == SOUND_MIXER_SPEAKER) { + left = ((100 - left) * mh->scale) / 100; + if (left >= mh->scale) + left = mh->scale-1; + val = left << 1; + } else if (oss_channel == SOUND_MIXER_PHONEIN) { + left = ((100 - left) * mh->scale) / 100; + if (left >= mh->scale) + left = mh->scale-1; + val = left; + } else if (oss_channel == SOUND_MIXER_PHONEOUT) { + scale = (1 << codec->bit_resolution); + left = ((100 - left) * scale) / 100; + if (left >= mh->scale) + left = mh->scale-1; + val = left; + } else if (oss_channel == SOUND_MIXER_MIC) { + val = codec->codec_read(codec , mh->offset) & ~0x801f; + left = ((100 - left) * mh->scale) / 100; + if (left >= mh->scale) + left = mh->scale-1; + val |= left; + /* the low bit is optional in the tone sliders and masking + it lets us avoid the 0xf 'bypass'.. */ + } +#ifdef DEBUG + printk(" 0x%04x", val); +#endif + + codec->codec_write(codec, mh->offset, val); + +#ifdef DEBUG + val = codec->codec_read(codec, mh->offset); + printk(" -> 0x%04x\n", val); +#endif +} + +/* a thin wrapper for write_mixer */ +static void ac97_set_mixer(struct ac97_codec *codec, unsigned int oss_mixer, unsigned int val ) +{ + unsigned int left,right; + + /* cleanse input a little */ + right = ((val >> 8) & 0xff) ; + left = (val & 0xff) ; + + if (right > 100) right = 100; + if (left > 100) left = 100; + + codec->mixer_state[oss_mixer] = (right << 8) | left; + codec->write_mixer(codec, oss_mixer, left, right); +} + +/* read or write the recmask, the ac97 can really have left and right recording + inputs independantly set, but OSS doesn't seem to want us to express that to + the user. the caller guarantees that we have a supported bit set, and they + must be holding the card's spinlock */ +static int ac97_recmask_io(struct ac97_codec *codec, int rw, int mask) +{ + unsigned int val; + + if (rw) { + /* read it from the card */ + val = codec->codec_read(codec, AC97_RECORD_SELECT); +#ifdef DEBUG + printk("ac97_codec: ac97 recmask to set to 0x%04x\n", val); +#endif + return (1 << ac97_rm2oss[val & 0x07]); + } + + /* else, write the first set in the mask as the + output */ + /* clear out current set value first (AC97 supports only 1 input!) */ + val = (1 << ac97_rm2oss[codec->codec_read(codec, AC97_RECORD_SELECT) & 0x07]); + if (mask != val) + mask &= ~val; + + val = ffs(mask); + val = ac97_oss_rm[val-1]; + val |= val << 8; /* set both channels */ + +#ifdef DEBUG + printk("ac97_codec: setting ac97 recmask to 0x%04x\n", val); +#endif + + codec->codec_write(codec, AC97_RECORD_SELECT, val); + + return 0; +}; + +static int ac97_mixer_ioctl(struct ac97_codec *codec, unsigned int cmd, unsigned long arg) +{ + int i, val = 0; + + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + memset(&info, 0, sizeof(info)); + strlcpy(info.id, codec->name, sizeof(info.id)); + strlcpy(info.name, codec->name, sizeof(info.name)); + info.modify_counter = codec->modcnt; + if (copy_to_user((void __user *)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, codec->name, sizeof(info.id)); + strlcpy(info.name, codec->name, sizeof(info.name)); + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + + if (_IOC_TYPE(cmd) != 'M' || _SIOC_SIZE(cmd) != sizeof(int)) + return -EINVAL; + + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, (int __user *)arg); + + if (_SIOC_DIR(cmd) == _SIOC_READ) { + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* give them the current record source */ + if (!codec->recmask_io) { + val = 0; + } else { + val = codec->recmask_io(codec, 1, 0); + } + break; + + case SOUND_MIXER_DEVMASK: /* give them the supported mixers */ + val = codec->supported_mixers; + break; + + case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */ + val = codec->record_sources; + break; + + case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */ + val = codec->stereo_mixers; + break; + + case SOUND_MIXER_CAPS: + val = SOUND_CAP_EXCL_INPUT; + break; + + default: /* read a specific mixer */ + i = _IOC_NR(cmd); + + if (!supported_mixer(codec, i)) + return -EINVAL; + + /* do we ever want to touch the hardware? */ + /* val = codec->read_mixer(codec, i); */ + val = codec->mixer_state[i]; + break; + } + return put_user(val, (int __user *)arg); + } + + if (_SIOC_DIR(cmd) == (_SIOC_WRITE|_SIOC_READ)) { + codec->modcnt++; + if (get_user(val, (int __user *)arg)) + return -EFAULT; + + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + if (!codec->recmask_io) return -EINVAL; + if (!val) return 0; + if (!(val &= codec->record_sources)) return -EINVAL; + + codec->recmask_io(codec, 0, val); + + return 0; + default: /* write a specific mixer */ + i = _IOC_NR(cmd); + + if (!supported_mixer(codec, i)) + return -EINVAL; + + ac97_set_mixer(codec, i, val); + + return 0; + } + } + return -EINVAL; +} + +/* entry point for /proc/driver/controller_vendor/ac97/%d */ +int ac97_read_proc (char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0, cap, extid, val, id1, id2; + struct ac97_codec *codec; + int is_ac97_20 = 0; + + if ((codec = data) == NULL) + return -ENODEV; + + id1 = codec->codec_read(codec, AC97_VENDOR_ID1); + id2 = codec->codec_read(codec, AC97_VENDOR_ID2); + len += sprintf (page+len, "Vendor name : %s\n", codec->name); + len += sprintf (page+len, "Vendor id : %04X %04X\n", id1, id2); + + extid = codec->codec_read(codec, AC97_EXTENDED_ID); + extid &= ~((1<<2)|(1<<4)|(1<<5)|(1<<10)|(1<<11)|(1<<12)|(1<<13)); + len += sprintf (page+len, "AC97 Version : %s\n", + extid ? "2.0 or later" : "1.0"); + if (extid) is_ac97_20 = 1; + + cap = codec->codec_read(codec, AC97_RESET); + len += sprintf (page+len, "Capabilities :%s%s%s%s%s%s\n", + cap & 0x0001 ? " -dedicated MIC PCM IN channel-" : "", + cap & 0x0002 ? " -reserved1-" : "", + cap & 0x0004 ? " -bass & treble-" : "", + cap & 0x0008 ? " -simulated stereo-" : "", + cap & 0x0010 ? " -headphone out-" : "", + cap & 0x0020 ? " -loudness-" : ""); + val = cap & 0x00c0; + len += sprintf (page+len, "DAC resolutions :%s%s%s\n", + " -16-bit-", + val & 0x0040 ? " -18-bit-" : "", + val & 0x0080 ? " -20-bit-" : ""); + val = cap & 0x0300; + len += sprintf (page+len, "ADC resolutions :%s%s%s\n", + " -16-bit-", + val & 0x0100 ? " -18-bit-" : "", + val & 0x0200 ? " -20-bit-" : ""); + len += sprintf (page+len, "3D enhancement : %s\n", + ac97_stereo_enhancements[(cap >> 10) & 0x1f]); + + val = codec->codec_read(codec, AC97_GENERAL_PURPOSE); + len += sprintf (page+len, "POP path : %s 3D\n" + "Sim. stereo : %s\n" + "3D enhancement : %s\n" + "Loudness : %s\n" + "Mono output : %s\n" + "MIC select : %s\n" + "ADC/DAC loopback : %s\n", + val & 0x8000 ? "post" : "pre", + val & 0x4000 ? "on" : "off", + val & 0x2000 ? "on" : "off", + val & 0x1000 ? "on" : "off", + val & 0x0200 ? "MIC" : "MIX", + val & 0x0100 ? "MIC2" : "MIC1", + val & 0x0080 ? "on" : "off"); + + extid = codec->codec_read(codec, AC97_EXTENDED_ID); + cap = extid; + len += sprintf (page+len, "Ext Capabilities :%s%s%s%s%s%s%s\n", + cap & 0x0001 ? " -var rate PCM audio-" : "", + cap & 0x0002 ? " -2x PCM audio out-" : "", + cap & 0x0008 ? " -var rate MIC in-" : "", + cap & 0x0040 ? " -PCM center DAC-" : "", + cap & 0x0080 ? " -PCM surround DAC-" : "", + cap & 0x0100 ? " -PCM LFE DAC-" : "", + cap & 0x0200 ? " -slot/DAC mappings-" : ""); + if (is_ac97_20) { + len += sprintf (page+len, "Front DAC rate : %d\n", + codec->codec_read(codec, AC97_PCM_FRONT_DAC_RATE)); + } + + return len; +} + +/** + * codec_id - Turn id1/id2 into a PnP string + * @id1: Vendor ID1 + * @id2: Vendor ID2 + * @buf: CODEC_ID_BUFSZ byte buffer + * + * Fills buf with a zero terminated PnP ident string for the id1/id2 + * pair. For convenience the return is the passed in buffer pointer. + */ + +static char *codec_id(u16 id1, u16 id2, char *buf) +{ + if(id1&0x8080) { + snprintf(buf, CODEC_ID_BUFSZ, "0x%04x:0x%04x", id1, id2); + } else { + buf[0] = (id1 >> 8); + buf[1] = (id1 & 0xFF); + buf[2] = (id2 >> 8); + snprintf(buf+3, CODEC_ID_BUFSZ - 3, "%d", id2&0xFF); + } + return buf; +} + +/** + * ac97_check_modem - Check if the Codec is a modem + * @codec: codec to check + * + * Return true if the device is an AC97 1.0 or AC97 2.0 modem + */ + +static int ac97_check_modem(struct ac97_codec *codec) +{ + /* Check for an AC97 1.0 soft modem (ID1) */ + if(codec->codec_read(codec, AC97_RESET) & 2) + return 1; + /* Check for an AC97 2.x soft modem */ + codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0L); + if(codec->codec_read(codec, AC97_EXTENDED_MODEM_ID) & 1) + return 1; + return 0; +} + + +/** + * ac97_alloc_codec - Allocate an AC97 codec + * + * Returns a new AC97 codec structure. AC97 codecs may become + * refcounted soon so this interface is needed. Returns with + * one reference taken. + */ + +struct ac97_codec *ac97_alloc_codec(void) +{ + struct ac97_codec *codec = kmalloc(sizeof(struct ac97_codec), GFP_KERNEL); + if(!codec) + return NULL; + + memset(codec, 0, sizeof(*codec)); + spin_lock_init(&codec->lock); + INIT_LIST_HEAD(&codec->list); + return codec; +} + +EXPORT_SYMBOL(ac97_alloc_codec); + +/** + * ac97_release_codec - Release an AC97 codec + * @codec: codec to release + * + * Release an allocated AC97 codec. This will be refcounted in + * time but for the moment is trivial. Calls the unregister + * handler if the codec is now defunct. + */ + +void ac97_release_codec(struct ac97_codec *codec) +{ + /* Remove from the list first, we don't want to be + "rediscovered" */ + down(&codec_sem); + list_del(&codec->list); + up(&codec_sem); + /* + * The driver needs to deal with internal + * locking to avoid accidents here. + */ + if(codec->driver) + codec->driver->remove(codec, codec->driver); + kfree(codec); +} + +EXPORT_SYMBOL(ac97_release_codec); + +/** + * ac97_probe_codec - Initialize and setup AC97-compatible codec + * @codec: (in/out) Kernel info for a single AC97 codec + * + * Reset the AC97 codec, then initialize the mixer and + * the rest of the @codec structure. + * + * The codec_read and codec_write fields of @codec are + * required to be setup and working when this function + * is called. All other fields are set by this function. + * + * codec_wait field of @codec can optionally be provided + * when calling this function. If codec_wait is not %NULL, + * this function will call codec_wait any time it is + * necessary to wait for the audio chip to reach the + * codec-ready state. If codec_wait is %NULL, then + * the default behavior is to call schedule_timeout. + * Currently codec_wait is used to wait for AC97 codec + * reset to complete. + * + * Some codecs will power down when a register reset is + * performed. We now check for such codecs. + * + * Returns 1 (true) on success, or 0 (false) on failure. + */ + +int ac97_probe_codec(struct ac97_codec *codec) +{ + u16 id1, id2; + u16 audio; + int i; + char cidbuf[CODEC_ID_BUFSZ]; + u16 f; + struct list_head *l; + struct ac97_driver *d; + + /* wait for codec-ready state */ + if (codec->codec_wait) + codec->codec_wait(codec); + else + udelay(10); + + /* will the codec power down if register reset ? */ + id1 = codec->codec_read(codec, AC97_VENDOR_ID1); + id2 = codec->codec_read(codec, AC97_VENDOR_ID2); + codec->name = NULL; + codec->codec_ops = &null_ops; + for (i = 0; i < ARRAY_SIZE(ac97_codec_ids); i++) { + if (ac97_codec_ids[i].id == ((id1 << 16) | id2)) { + codec->type = ac97_codec_ids[i].id; + codec->name = ac97_codec_ids[i].name; + codec->codec_ops = ac97_codec_ids[i].ops; + codec->flags = ac97_codec_ids[i].flags; + break; + } + } + + codec->model = (id1 << 16) | id2; + if ((codec->flags & AC97_DEFAULT_POWER_OFF) == 0) { + /* reset codec and wait for the ready bit before we continue */ + codec->codec_write(codec, AC97_RESET, 0L); + if (codec->codec_wait) + codec->codec_wait(codec); + else + udelay(10); + } + + /* probing AC97 codec, AC97 2.0 says that bit 15 of register 0x00 (reset) should + * be read zero. + * + * FIXME: is the following comment outdated? -jgarzik + * Probing of AC97 in this way is not reliable, it is not even SAFE !! + */ + if ((audio = codec->codec_read(codec, AC97_RESET)) & 0x8000) { + printk(KERN_ERR "ac97_codec: %s ac97 codec not present\n", + (codec->id & 0x2) ? (codec->id&1 ? "4th" : "Tertiary") + : (codec->id&1 ? "Secondary": "Primary")); + return 0; + } + + /* probe for Modem Codec */ + codec->modem = ac97_check_modem(codec); + + /* enable SPDIF */ + f = codec->codec_read(codec, AC97_EXTENDED_STATUS); + if((codec->codec_ops == &null_ops) && (f & 4)) + codec->codec_ops = &default_digital_ops; + + /* A device which thinks its a modem but isnt */ + if(codec->flags & AC97_DELUDED_MODEM) + codec->modem = 0; + + if (codec->name == NULL) + codec->name = "Unknown"; + printk(KERN_INFO "ac97_codec: AC97 %s codec, id: %s (%s)\n", + codec->modem ? "Modem" : (audio ? "Audio" : ""), + codec_id(id1, id2, cidbuf), codec->name); + + if(!ac97_init_mixer(codec)) + return 0; + + /* + * Attach last so the caller can override the mixer + * callbacks. + */ + + down(&codec_sem); + list_add(&codec->list, &codecs); + + list_for_each(l, &codec_drivers) { + d = list_entry(l, struct ac97_driver, list); + if ((codec->model ^ d->codec_id) & d->codec_mask) + continue; + if(d->probe(codec, d) == 0) + { + codec->driver = d; + break; + } + } + + up(&codec_sem); + return 1; +} + +static int ac97_init_mixer(struct ac97_codec *codec) +{ + u16 cap; + int i; + + cap = codec->codec_read(codec, AC97_RESET); + + /* mixer masks */ + codec->supported_mixers = AC97_SUPPORTED_MASK; + codec->stereo_mixers = AC97_STEREO_MASK; + codec->record_sources = AC97_RECORD_MASK; + if (!(cap & 0x04)) + codec->supported_mixers &= ~(SOUND_MASK_BASS|SOUND_MASK_TREBLE); + if (!(cap & 0x10)) + codec->supported_mixers &= ~SOUND_MASK_ALTPCM; + + + /* detect bit resolution */ + codec->codec_write(codec, AC97_MASTER_VOL_STEREO, 0x2020); + if(codec->codec_read(codec, AC97_MASTER_VOL_STEREO) == 0x2020) + codec->bit_resolution = 6; + else + codec->bit_resolution = 5; + + /* generic OSS to AC97 wrapper */ + codec->read_mixer = ac97_read_mixer; + codec->write_mixer = ac97_write_mixer; + codec->recmask_io = ac97_recmask_io; + codec->mixer_ioctl = ac97_mixer_ioctl; + + /* initialize mixer channel volumes */ + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + struct mixer_defaults *md = &mixer_defaults[i]; + if (md->mixer == -1) + break; + if (!supported_mixer(codec, md->mixer)) + continue; + ac97_set_mixer(codec, md->mixer, md->value); + } + + /* codec specific initialization for 4-6 channel output or secondary codec stuff */ + if (codec->codec_ops->init != NULL) { + codec->codec_ops->init(codec); + } + + /* + * Volume is MUTE only on this device. We have to initialise + * it but its useless beyond that. + */ + if(codec->flags & AC97_NO_PCM_VOLUME) + { + codec->supported_mixers &= ~SOUND_MASK_PCM; + printk(KERN_WARNING "AC97 codec does not have proper volume support.\n"); + } + return 1; +} + +#define AC97_SIGMATEL_ANALOG 0x6c /* Analog Special */ +#define AC97_SIGMATEL_DAC2INVERT 0x6e +#define AC97_SIGMATEL_BIAS1 0x70 +#define AC97_SIGMATEL_BIAS2 0x72 +#define AC97_SIGMATEL_MULTICHN 0x74 /* Multi-Channel programming */ +#define AC97_SIGMATEL_CIC1 0x76 +#define AC97_SIGMATEL_CIC2 0x78 + + +static int sigmatel_9708_init(struct ac97_codec * codec) +{ + u16 codec72, codec6c; + + codec72 = codec->codec_read(codec, AC97_SIGMATEL_BIAS2) & 0x8000; + codec6c = codec->codec_read(codec, AC97_SIGMATEL_ANALOG); + + if ((codec72==0) && (codec6c==0)) { + codec->codec_write(codec, AC97_SIGMATEL_CIC1, 0xabba); + codec->codec_write(codec, AC97_SIGMATEL_CIC2, 0x1000); + codec->codec_write(codec, AC97_SIGMATEL_BIAS1, 0xabba); + codec->codec_write(codec, AC97_SIGMATEL_BIAS2, 0x0007); + } else if ((codec72==0x8000) && (codec6c==0)) { + codec->codec_write(codec, AC97_SIGMATEL_CIC1, 0xabba); + codec->codec_write(codec, AC97_SIGMATEL_CIC2, 0x1001); + codec->codec_write(codec, AC97_SIGMATEL_DAC2INVERT, 0x0008); + } else if ((codec72==0x8000) && (codec6c==0x0080)) { + /* nothing */ + } + codec->codec_write(codec, AC97_SIGMATEL_MULTICHN, 0x0000); + return 0; +} + + +static int sigmatel_9721_init(struct ac97_codec * codec) +{ + /* Only set up secondary codec */ + if (codec->id == 0) + return 0; + + codec->codec_write(codec, AC97_SURROUND_MASTER, 0L); + + /* initialize SigmaTel STAC9721/23 as secondary codec, decoding AC link + sloc 3,4 = 0x01, slot 7,8 = 0x00, */ + codec->codec_write(codec, AC97_SIGMATEL_MULTICHN, 0x00); + + /* we don't have the crystal when we are on an AMR card, so use + BIT_CLK as our clock source. Write the magic word ABBA and read + back to enable register 0x78 */ + codec->codec_write(codec, AC97_SIGMATEL_CIC1, 0xabba); + codec->codec_read(codec, AC97_SIGMATEL_CIC1); + + /* sync all the clocks*/ + codec->codec_write(codec, AC97_SIGMATEL_CIC2, 0x3802); + + return 0; +} + + +static int sigmatel_9744_init(struct ac97_codec * codec) +{ + // patch for SigmaTel + codec->codec_write(codec, AC97_SIGMATEL_CIC1, 0xabba); + codec->codec_write(codec, AC97_SIGMATEL_CIC2, 0x0000); // is this correct? --jk + codec->codec_write(codec, AC97_SIGMATEL_BIAS1, 0xabba); + codec->codec_write(codec, AC97_SIGMATEL_BIAS2, 0x0002); + codec->codec_write(codec, AC97_SIGMATEL_MULTICHN, 0x0000); + return 0; +} + +static int cmedia_init(struct ac97_codec *codec) +{ + /* Initialise the CMedia 9739 */ + /* + We could set various options here + Register 0x20 bit 0x100 sets mic as center bass + Also do multi_channel_ctrl &=~0x3000 |=0x1000 + + For now we set up the GPIO and PC beep + */ + + u16 v; + + /* MIC */ + codec->codec_write(codec, 0x64, 0x3000); + v = codec->codec_read(codec, 0x64); + v &= ~0x8000; + codec->codec_write(codec, 0x64, v); + codec->codec_write(codec, 0x70, 0x0100); + codec->codec_write(codec, 0x72, 0x0020); + return 0; +} + +#define AC97_WM97XX_FMIXER_VOL 0x72 +#define AC97_WM97XX_RMIXER_VOL 0x74 +#define AC97_WM97XX_TEST 0x5a +#define AC97_WM9704_RPCM_VOL 0x70 +#define AC97_WM9711_OUT3VOL 0x16 + +static int wolfson_init03(struct ac97_codec * codec) +{ + /* this is known to work for the ViewSonic ViewPad 1000 */ + codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808); + codec->codec_write(codec, AC97_GENERAL_PURPOSE, 0x8000); + return 0; +} + +static int wolfson_init04(struct ac97_codec * codec) +{ + codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808); + codec->codec_write(codec, AC97_WM97XX_RMIXER_VOL, 0x0808); + + // patch for DVD noise + codec->codec_write(codec, AC97_WM97XX_TEST, 0x0200); + + // init vol as PCM vol + codec->codec_write(codec, AC97_WM9704_RPCM_VOL, + codec->codec_read(codec, AC97_PCMOUT_VOL)); + + /* set rear surround volume */ + codec->codec_write(codec, AC97_SURROUND_MASTER, 0x0000); + return 0; +} + +/* WM9705, WM9710 */ +static int wolfson_init05(struct ac97_codec * codec) +{ + /* set front mixer volume */ + codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808); + return 0; +} + +/* WM9711, WM9712 */ +static int wolfson_init11(struct ac97_codec * codec) +{ + /* stop pop's during suspend/resume */ + codec->codec_write(codec, AC97_WM97XX_TEST, + codec->codec_read(codec, AC97_WM97XX_TEST) & 0xffbf); + + /* set out3 volume */ + codec->codec_write(codec, AC97_WM9711_OUT3VOL, 0x0808); + return 0; +} + +/* WM9713 */ +static int wolfson_init13(struct ac97_codec * codec) +{ + codec->codec_write(codec, AC97_RECORD_GAIN, 0x00a0); + codec->codec_write(codec, AC97_POWER_CONTROL, 0x0000); + codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0xDA00); + codec->codec_write(codec, AC97_EXTEND_MODEM_STAT, 0x3810); + codec->codec_write(codec, AC97_PHONE_VOL, 0x0808); + codec->codec_write(codec, AC97_PCBEEP_VOL, 0x0808); + + return 0; +} + +static int tritech_init(struct ac97_codec * codec) +{ + codec->codec_write(codec, 0x26, 0x0300); + codec->codec_write(codec, 0x26, 0x0000); + codec->codec_write(codec, AC97_SURROUND_MASTER, 0x0000); + codec->codec_write(codec, AC97_RESERVED_3A, 0x0000); + return 0; +} + + +/* copied from drivers/sound/maestro.c */ +static int tritech_maestro_init(struct ac97_codec * codec) +{ + /* no idea what this does */ + codec->codec_write(codec, 0x2A, 0x0001); + codec->codec_write(codec, 0x2C, 0x0000); + codec->codec_write(codec, 0x2C, 0XFFFF); + return 0; +} + + + +/* + * Presario700 workaround + * for Jack Sense/SPDIF Register mis-setting causing + * no audible output + * by Santiago Nullo 04/05/2002 + */ + +#define AC97_AD1886_JACK_SENSE 0x72 + +static int ad1886_init(struct ac97_codec * codec) +{ + /* from AD1886 Specs */ + codec->codec_write(codec, AC97_AD1886_JACK_SENSE, 0x0010); + return 0; +} + + + + +/* + * This is basically standard AC97. It should work as a default for + * almost all modern codecs. Note that some cards wire EAPD *backwards* + * That side of it is up to the card driver not us to cope with. + * + */ + +static int eapd_control(struct ac97_codec * codec, int on) +{ + if(on) + codec->codec_write(codec, AC97_POWER_CONTROL, + codec->codec_read(codec, AC97_POWER_CONTROL)|0x8000); + else + codec->codec_write(codec, AC97_POWER_CONTROL, + codec->codec_read(codec, AC97_POWER_CONTROL)&~0x8000); + return 0; +} + +static int generic_digital_control(struct ac97_codec *codec, int slots, int rate, int mode) +{ + u16 reg; + + reg = codec->codec_read(codec, AC97_SPDIF_CONTROL); + + switch(rate) + { + /* Off by default */ + default: + case 0: + reg = codec->codec_read(codec, AC97_EXTENDED_STATUS); + codec->codec_write(codec, AC97_EXTENDED_STATUS, (reg & ~AC97_EA_SPDIF)); + if(rate == 0) + return 0; + return -EINVAL; + case 1: + reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_48K; + break; + case 2: + reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_44K; + break; + case 3: + reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_32K; + break; + } + + reg &= ~AC97_SC_CC_MASK; + reg |= (mode & AUDIO_CCMASK) << 6; + + if(mode & AUDIO_DIGITAL) + reg |= 2; + if(mode & AUDIO_PRO) + reg |= 1; + if(mode & AUDIO_DRS) + reg |= 0x4000; + + codec->codec_write(codec, AC97_SPDIF_CONTROL, reg); + + reg = codec->codec_read(codec, AC97_EXTENDED_STATUS); + reg &= (AC97_EA_SLOT_MASK); + reg |= AC97_EA_VRA | AC97_EA_SPDIF | slots; + codec->codec_write(codec, AC97_EXTENDED_STATUS, reg); + + reg = codec->codec_read(codec, AC97_EXTENDED_STATUS); + if(!(reg & 0x0400)) + { + codec->codec_write(codec, AC97_EXTENDED_STATUS, reg & ~ AC97_EA_SPDIF); + return -EINVAL; + } + return 0; +} + +/* + * Crystal digital audio control (CS4299) + */ + +static int crystal_digital_control(struct ac97_codec *codec, int slots, int rate, int mode) +{ + u16 cv; + + if(mode & AUDIO_DIGITAL) + return -EINVAL; + + switch(rate) + { + case 0: cv = 0x0; break; /* SPEN off */ + case 48000: cv = 0x8004; break; /* 48KHz digital */ + case 44100: cv = 0x8104; break; /* 44.1KHz digital */ + case 32768: /* 32Khz */ + default: + return -EINVAL; + } + codec->codec_write(codec, 0x68, cv); + return 0; +} + +/* + * CMedia digital audio control + * Needs more work. + */ + +static int cmedia_digital_control(struct ac97_codec *codec, int slots, int rate, int mode) +{ + u16 cv; + + if(mode & AUDIO_DIGITAL) + return -EINVAL; + + switch(rate) + { + case 0: cv = 0x0001; break; /* SPEN off */ + case 48000: cv = 0x0009; break; /* 48KHz digital */ + default: + return -EINVAL; + } + codec->codec_write(codec, 0x2A, 0x05c4); + codec->codec_write(codec, 0x6C, cv); + + /* Switch on mix to surround */ + cv = codec->codec_read(codec, 0x64); + cv &= ~0x0200; + if(mode) + cv |= 0x0200; + codec->codec_write(codec, 0x64, cv); + return 0; +} + + +/* copied from drivers/sound/maestro.c */ +#if 0 /* there has been 1 person on the planet with a pt101 that we + know of. If they care, they can put this back in :) */ +static int pt101_init(struct ac97_codec * codec) +{ + printk(KERN_INFO "ac97_codec: PT101 Codec detected, initializing but _not_ installing mixer device.\n"); + /* who knows.. */ + codec->codec_write(codec, 0x2A, 0x0001); + codec->codec_write(codec, 0x2C, 0x0000); + codec->codec_write(codec, 0x2C, 0xFFFF); + codec->codec_write(codec, 0x10, 0x9F1F); + codec->codec_write(codec, 0x12, 0x0808); + codec->codec_write(codec, 0x14, 0x9F1F); + codec->codec_write(codec, 0x16, 0x9F1F); + codec->codec_write(codec, 0x18, 0x0404); + codec->codec_write(codec, 0x1A, 0x0000); + codec->codec_write(codec, 0x1C, 0x0000); + codec->codec_write(codec, 0x02, 0x0404); + codec->codec_write(codec, 0x04, 0x0808); + codec->codec_write(codec, 0x0C, 0x801F); + codec->codec_write(codec, 0x0E, 0x801F); + return 0; +} +#endif + + +EXPORT_SYMBOL(ac97_read_proc); +EXPORT_SYMBOL(ac97_probe_codec); + +/* + * AC97 library support routines + */ + +/** + * ac97_set_dac_rate - set codec rate adaption + * @codec: ac97 code + * @rate: rate in hertz + * + * Set the DAC rate. Assumes the codec supports VRA. The caller is + * expected to have checked this little detail. + */ + +unsigned int ac97_set_dac_rate(struct ac97_codec *codec, unsigned int rate) +{ + unsigned int new_rate = rate; + u32 dacp; + u32 mast_vol, phone_vol, mono_vol, pcm_vol; + u32 mute_vol = 0x8000; /* The mute volume? */ + + if(rate != codec->codec_read(codec, AC97_PCM_FRONT_DAC_RATE)) + { + /* Mute several registers */ + mast_vol = codec->codec_read(codec, AC97_MASTER_VOL_STEREO); + mono_vol = codec->codec_read(codec, AC97_MASTER_VOL_MONO); + phone_vol = codec->codec_read(codec, AC97_HEADPHONE_VOL); + pcm_vol = codec->codec_read(codec, AC97_PCMOUT_VOL); + codec->codec_write(codec, AC97_MASTER_VOL_STEREO, mute_vol); + codec->codec_write(codec, AC97_MASTER_VOL_MONO, mute_vol); + codec->codec_write(codec, AC97_HEADPHONE_VOL, mute_vol); + codec->codec_write(codec, AC97_PCMOUT_VOL, mute_vol); + + /* Power down the DAC */ + dacp=codec->codec_read(codec, AC97_POWER_CONTROL); + codec->codec_write(codec, AC97_POWER_CONTROL, dacp|0x0200); + /* Load the rate and read the effective rate */ + codec->codec_write(codec, AC97_PCM_FRONT_DAC_RATE, rate); + new_rate=codec->codec_read(codec, AC97_PCM_FRONT_DAC_RATE); + /* Power it back up */ + codec->codec_write(codec, AC97_POWER_CONTROL, dacp); + + /* Restore volumes */ + codec->codec_write(codec, AC97_MASTER_VOL_STEREO, mast_vol); + codec->codec_write(codec, AC97_MASTER_VOL_MONO, mono_vol); + codec->codec_write(codec, AC97_HEADPHONE_VOL, phone_vol); + codec->codec_write(codec, AC97_PCMOUT_VOL, pcm_vol); + } + return new_rate; +} + +EXPORT_SYMBOL(ac97_set_dac_rate); + +/** + * ac97_set_adc_rate - set codec rate adaption + * @codec: ac97 code + * @rate: rate in hertz + * + * Set the ADC rate. Assumes the codec supports VRA. The caller is + * expected to have checked this little detail. + */ + +unsigned int ac97_set_adc_rate(struct ac97_codec *codec, unsigned int rate) +{ + unsigned int new_rate = rate; + u32 dacp; + + if(rate != codec->codec_read(codec, AC97_PCM_LR_ADC_RATE)) + { + /* Power down the ADC */ + dacp=codec->codec_read(codec, AC97_POWER_CONTROL); + codec->codec_write(codec, AC97_POWER_CONTROL, dacp|0x0100); + /* Load the rate and read the effective rate */ + codec->codec_write(codec, AC97_PCM_LR_ADC_RATE, rate); + new_rate=codec->codec_read(codec, AC97_PCM_LR_ADC_RATE); + /* Power it back up */ + codec->codec_write(codec, AC97_POWER_CONTROL, dacp); + } + return new_rate; +} + +EXPORT_SYMBOL(ac97_set_adc_rate); + +int ac97_save_state(struct ac97_codec *codec) +{ + return 0; +} + +EXPORT_SYMBOL(ac97_save_state); + +int ac97_restore_state(struct ac97_codec *codec) +{ + int i; + unsigned int left, right, val; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!supported_mixer(codec, i)) + continue; + + val = codec->mixer_state[i]; + right = val >> 8; + left = val & 0xff; + codec->write_mixer(codec, i, left, right); + } + return 0; +} + +EXPORT_SYMBOL(ac97_restore_state); + +/** + * ac97_register_driver - register a codec helper + * @driver: Driver handler + * + * Register a handler for codecs matching the codec id. The handler + * attach function is called for all present codecs and will be + * called when new codecs are discovered. + */ + +int ac97_register_driver(struct ac97_driver *driver) +{ + struct list_head *l; + struct ac97_codec *c; + + down(&codec_sem); + INIT_LIST_HEAD(&driver->list); + list_add(&driver->list, &codec_drivers); + + list_for_each(l, &codecs) + { + c = list_entry(l, struct ac97_codec, list); + if(c->driver != NULL || ((c->model ^ driver->codec_id) & driver->codec_mask)) + continue; + if(driver->probe(c, driver)) + continue; + c->driver = driver; + } + up(&codec_sem); + return 0; +} + +EXPORT_SYMBOL_GPL(ac97_register_driver); + +/** + * ac97_unregister_driver - unregister a codec helper + * @driver: Driver handler + * + * Unregister a handler for codecs matching the codec id. The handler + * remove function is called for all matching codecs. + */ + +void ac97_unregister_driver(struct ac97_driver *driver) +{ + struct list_head *l; + struct ac97_codec *c; + + down(&codec_sem); + list_del_init(&driver->list); + + list_for_each(l, &codecs) + { + c = list_entry(l, struct ac97_codec, list); + if (c->driver == driver) { + driver->remove(c, driver); + c->driver = NULL; + } + } + + up(&codec_sem); +} + +EXPORT_SYMBOL_GPL(ac97_unregister_driver); + +static int swap_headphone(int remove_master) +{ + struct list_head *l; + struct ac97_codec *c; + + if (remove_master) { + down(&codec_sem); + list_for_each(l, &codecs) + { + c = list_entry(l, struct ac97_codec, list); + if (supported_mixer(c, SOUND_MIXER_PHONEOUT)) + c->supported_mixers &= ~SOUND_MASK_PHONEOUT; + } + up(&codec_sem); + } else + ac97_hw[SOUND_MIXER_PHONEOUT].offset = AC97_MASTER_VOL_STEREO; + + /* Scale values already match */ + ac97_hw[SOUND_MIXER_VOLUME].offset = AC97_MASTER_VOL_MONO; + return 0; +} + +static int apply_quirk(int quirk) +{ + switch (quirk) { + case AC97_TUNE_NONE: + return 0; + case AC97_TUNE_HP_ONLY: + return swap_headphone(1); + case AC97_TUNE_SWAP_HP: + return swap_headphone(0); + case AC97_TUNE_SWAP_SURROUND: + return -ENOSYS; /* not yet implemented */ + case AC97_TUNE_AD_SHARING: + return -ENOSYS; /* not yet implemented */ + case AC97_TUNE_ALC_JACK: + return -ENOSYS; /* not yet implemented */ + } + return -EINVAL; +} + +/** + * ac97_tune_hardware - tune up the hardware + * @pdev: pci_dev pointer + * @quirk: quirk list + * @override: explicit quirk value (overrides if not AC97_TUNE_DEFAULT) + * + * Do some workaround for each pci device, such as renaming of the + * headphone (true line-out) control as "Master". + * The quirk-list must be terminated with a zero-filled entry. + * + * Returns zero if successful, or a negative error code on failure. + */ + +int ac97_tune_hardware(struct pci_dev *pdev, struct ac97_quirk *quirk, int override) +{ + int result; + + if (!quirk) + return -EINVAL; + + if (override != AC97_TUNE_DEFAULT) { + result = apply_quirk(override); + if (result < 0) + printk(KERN_ERR "applying quirk type %d failed (%d)\n", override, result); + return result; + } + + for (; quirk->vendor; quirk++) { + if (quirk->vendor != pdev->subsystem_vendor) + continue; + if ((! quirk->mask && quirk->device == pdev->subsystem_device) || + quirk->device == (quirk->mask & pdev->subsystem_device)) { +#ifdef DEBUG + printk("ac97 quirk for %s (%04x:%04x)\n", quirk->name, ac97->subsystem_vendor, pdev->subsystem_device); +#endif + result = apply_quirk(quirk->type); + if (result < 0) + printk(KERN_ERR "applying quirk type %d for %s failed (%d)\n", quirk->type, quirk->name, result); + return result; + } + } + return 0; +} + +EXPORT_SYMBOL_GPL(ac97_tune_hardware); + +MODULE_LICENSE("GPL"); diff --git a/sound/oss/ac97_plugin_ad1980.c b/sound/oss/ac97_plugin_ad1980.c new file mode 100644 index 000000000000..24a9acd28160 --- /dev/null +++ b/sound/oss/ac97_plugin_ad1980.c @@ -0,0 +1,126 @@ +/* + ac97_plugin_ad1980.c Copyright (C) 2003 Red Hat, Inc. All rights reserved. + + The contents of this file are subject to the Open Software License version 1.1 + that can be found at http://www.opensource.org/licenses/osl-1.1.txt and is + included herein by reference. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL") as + distributed in the kernel source COPYING file, in which + case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the OSL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the OSL or the GPL. + + Authors: Alan Cox + + This is an example codec plugin. This one switches the connections + around to match the setups some vendors use with audio switched to + non standard front connectors not the normal rear ones + + This code primarily exists to demonstrate how to use the codec + interface + +*/ + +#include +#include +#include +#include +#include + +/** + * ad1980_remove - codec remove callback + * @codec: The codec that is being removed + * + * This callback occurs when an AC97 codec is being removed. A + * codec remove call will not occur for a codec during that codec + * probe callback. + * + * Most drivers will need to lock their remove versus their + * use of the codec after the probe function. + */ + +static void __devexit ad1980_remove(struct ac97_codec *codec, struct ac97_driver *driver) +{ + /* Nothing to do in the simple example */ +} + + +/** + * ad1980_probe - codec found callback + * @codec: ac97 codec matching the idents + * @driver: ac97_driver it matched + * + * This entry point is called when a codec is found which matches + * the driver. At the point it is called the codec is basically + * operational, mixer operations have been initialised and can + * be overriden. Called in process context. The field driver_private + * is available for the driver to use to store stuff. + * + * The caller can claim the device by returning zero, or return + * a negative error code. + */ + +static int ad1980_probe(struct ac97_codec *codec, struct ac97_driver *driver) +{ + u16 control; + +#define AC97_AD_MISC 0x76 + + /* Switch the inputs/outputs over (from Dell code) */ + control = codec->codec_read(codec, AC97_AD_MISC); + codec->codec_write(codec, AC97_AD_MISC, control | 0x4420); + + /* We could refuse the device since we dont need to hang around, + but we will claim it */ + return 0; +} + + +static struct ac97_driver ad1980_driver = { + .codec_id = 0x41445370, + .codec_mask = 0xFFFFFFFF, + .name = "AD1980 example", + .probe = ad1980_probe, + .remove = __devexit_p(ad1980_remove), +}; + +/** + * ad1980_exit - module exit path + * + * Our module is being unloaded. At this point unregister_driver + * will call back our remove handler for any existing codecs. You + * may not unregister_driver from interrupt context or from a + * probe/remove callback. + */ + +static void ad1980_exit(void) +{ + ac97_unregister_driver(&ad1980_driver); +} + +/** + * ad1980_init - set up ad1980 handlers + * + * After we call the register function it will call our probe + * function for each existing matching device before returning to us. + * Any devices appearing afterwards whose id's match the codec_id + * will also cause the probe function to be called. + * You may not register_driver from interrupt context or from a + * probe/remove callback. + */ + +static int ad1980_init(void) +{ + return ac97_register_driver(&ad1980_driver); +} + +module_init(ad1980_init); +module_exit(ad1980_exit); +MODULE_LICENSE("GPL"); diff --git a/sound/oss/aci.c b/sound/oss/aci.c new file mode 100644 index 000000000000..3928c2802cc4 --- /dev/null +++ b/sound/oss/aci.c @@ -0,0 +1,711 @@ +/* + * Audio Command Interface (ACI) driver (sound/aci.c) + * + * ACI is a protocol used to communicate with the microcontroller on + * some sound cards produced by miro, e.g. the miroSOUND PCM12 and + * PCM20. The ACI has been developed for miro by Norberto Pellicci + * . Special thanks to both him and miro for + * providing the ACI specification. + * + * The main function of the ACI is to control the mixer and to get a + * product identification. On the PCM20, ACI also controls the radio + * tuner on this card, this is supported in the Video for Linux + * miropcm20 driver. + * - + * This is a fullfeatured implementation. Unsupported features + * are bugs... (: + * + * It is not longer necessary to load the mad16 module first. The + * user is currently responsible to set the mad16 mixer correctly. + * + * To toggle the solo mode for full duplex operation just use the OSS + * record switch for the pcm ('wave') controller. Robert + * - + * + * Revision history: + * + * 1995-11-10 Markus Kuhn + * First version written. + * 1995-12-31 Markus Kuhn + * Second revision, general code cleanup. + * 1996-05-16 Hannu Savolainen + * Integrated with other parts of the driver. + * 1996-05-28 Markus Kuhn + * Initialize CS4231A mixer, make ACI first mixer, + * use new private mixer API for solo mode. + * 1998-08-18 Ruurd Reitsma + * Small modification to export ACI functions and + * complete modularisation. + * 2000-06-20 Robert Siemer + * Don't initialize the CS4231A mixer anymore, so the code is + * working again, and other small changes to fit in todays + * kernels. + * 2000-08-26 Robert Siemer + * Clean up and rewrite for 2.4.x. Maybe it's SMP safe now... (: + * ioctl bugfix, and integration of solo-mode into OSS-API, + * added (OSS-limited) equalizer support, return value bugfix, + * changed param aci_reset to reset, new params: ide, wss. + * 2001-04-20 Robert Siemer + * even more cleanups... + * 2001-10-08 Arnaldo Carvalho de Melo + * Get rid of check_region, .bss optimizations, use set_current_state + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sound_config.h" + +int aci_port; /* as determined by bit 4 in the OPTi 929 MC4 register */ +static int aci_idcode[2]; /* manufacturer and product ID */ +int aci_version; /* ACI firmware version */ + +EXPORT_SYMBOL(aci_port); +EXPORT_SYMBOL(aci_version); + +#include "aci.h" + + +static int aci_solo; /* status bit of the card that can't be * + * checked with ACI versions prior to 0xb0 */ +static int aci_amp; /* status bit for power-amp/line-out level + but I have no docs about what is what... */ +static int aci_micpreamp=3; /* microphone preamp-level that can't be * + * checked with ACI versions prior to 0xb0 */ + +static int mixer_device; +static struct semaphore aci_sem; + +#ifdef MODULE +static int reset; +module_param(reset, bool, 0); +MODULE_PARM_DESC(reset,"When set to 1, reset aci mixer."); +#else +static int reset = 1; +#endif + +static int ide=-1; +module_param(ide, int, 0); +MODULE_PARM_DESC(ide,"1 enable, 0 disable ide-port - untested" + " default: do nothing"); +static int wss=-1; +module_param(wss, int, 0); +MODULE_PARM_DESC(wss,"change between ACI/WSS-mixer; use 0 and 1 - untested" + " default: do nothing; for PCM1-pro only"); + +#ifdef DEBUG +static void print_bits(unsigned char c) +{ + int j; + printk(KERN_DEBUG "aci: "); + + for (j=7; j>=0; j--) { + printk("%d", (c >> j) & 0x1); + } + + printk("\n"); +} +#endif + +/* + * This busy wait code normally requires less than 15 loops and + * practically always less than 100 loops on my i486/DX2 66 MHz. + * + * Warning: Waiting on the general status flag after reseting the MUTE + * function can take a VERY long time, because the PCM12 does some kind + * of fade-in effect. For this reason, access to the MUTE function has + * not been implemented at all. + * + * - The OSS interface has no mute option. It takes about 3 seconds to + * fade-in on my PCM20. busy_wait() handles it great now... Robert + */ + +static int busy_wait(void) +{ + #define MINTIME 500 + long timeout; + unsigned char byte; + + for (timeout = 1; timeout <= MINTIME+30; timeout++) { + if (((byte=inb(BUSY_REGISTER)) & 1) == 0) { + if (timeout >= MINTIME) + printk(KERN_DEBUG "aci: Got READYFLAG in round %ld.\n", timeout-MINTIME); + return byte; + } + if (timeout >= MINTIME) { + long out=10*HZ; + switch (timeout-MINTIME) { + case 0 ... 9: + out /= 10; + case 10 ... 19: + out /= 10; + case 20 ... 30: + out /= 10; + default: + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(out); + break; + } + } + } + printk(KERN_WARNING "aci: busy_wait() time out.\n"); + return -EBUSY; +} + +/* The four ACI command types are fucked up. [-: + * implied is: 1w - special case for INIT + * write is: 2w1r + * read is: x(1w1r) where x is 1 or 2 (1 CHECK_SIG, 1 CHECK_STER, + * 1 VERSION, 2 IDCODE) + * the command is only in the first write, rest is protocol overhead + * + * indexed is technically a write and used for STATUS + * and the special case for TUNE is: 3w1r + * + * Here the new general sheme: TUNE --> aci_rw_cmd(x, y, z) + * indexed and write --> aci_rw_cmd(x, y, -1) + * implied and read (x=1) --> aci_rw_cmd(x, -1, -1) + * + * Read (x>=2) is not implemented (only used during initialization). + * Use aci_idcode[2] and aci_version... Robert + */ + +/* Some notes for error detection: theoretically it is possible. + * But it doubles the I/O-traffic from ww(r) to wwwrw(r) in the normal + * case and doesn't seem to be designed for that... Robert + */ + +static inline int aci_rawwrite(unsigned char byte) +{ + if (busy_wait() >= 0) { +#ifdef DEBUG + printk(KERN_DEBUG "aci_rawwrite(%d)\n", byte); +#endif + outb(byte, COMMAND_REGISTER); + return 0; + } else + return -EBUSY; +} + +static inline int aci_rawread(void) +{ + unsigned char byte; + + if (busy_wait() >= 0) { + byte=inb(STATUS_REGISTER); +#ifdef DEBUG + printk(KERN_DEBUG "%d = aci_rawread()\n", byte); +#endif + return byte; + } else + return -EBUSY; +} + + +int aci_rw_cmd(int write1, int write2, int write3) +{ + int write[] = {write1, write2, write3}; + int read = -EINTR, i; + + if (down_interruptible(&aci_sem)) + goto out; + + for (i=0; i<3; i++) { + if (write[i]< 0 || write[i] > 255) + break; + else { + read = aci_rawwrite(write[i]); + if (read < 0) + goto out_up; + } + + } + + read = aci_rawread(); +out_up: up(&aci_sem); +out: return read; +} + +EXPORT_SYMBOL(aci_rw_cmd); + +static int setvolume(int __user *arg, + unsigned char left_index, unsigned char right_index) +{ + int vol, ret, uservol, buf; + + __get_user(uservol, arg); + + /* left channel */ + vol = uservol & 0xff; + if (vol > 100) + vol = 100; + vol = SCALE(100, 0x20, vol); + if ((buf=aci_write_cmd(left_index, 0x20 - vol))<0) + return buf; + ret = SCALE(0x20, 100, vol); + + + /* right channel */ + vol = (uservol >> 8) & 0xff; + if (vol > 100) + vol = 100; + vol = SCALE(100, 0x20, vol); + if ((buf=aci_write_cmd(right_index, 0x20 - vol))<0) + return buf; + ret |= SCALE(0x20, 100, vol) << 8; + + __put_user(ret, arg); + + return 0; +} + +static int getvolume(int __user *arg, + unsigned char left_index, unsigned char right_index) +{ + int vol; + int buf; + + /* left channel */ + if ((buf=aci_indexed_cmd(ACI_STATUS, left_index))<0) + return buf; + vol = SCALE(0x20, 100, buf < 0x20 ? 0x20-buf : 0); + + /* right channel */ + if ((buf=aci_indexed_cmd(ACI_STATUS, right_index))<0) + return buf; + vol |= SCALE(0x20, 100, buf < 0x20 ? 0x20-buf : 0) << 8; + + __put_user(vol, arg); + + return 0; +} + + +/* The equalizer is somewhat strange on the ACI. From -12dB to +12dB + * write: 0xff..down.to..0x80==0x00..up.to..0x7f + */ + +static inline unsigned int eq_oss2aci(unsigned int vol) +{ + int boost=0; + unsigned int ret; + + if (vol > 100) + vol = 100; + if (vol > 50) { + vol -= 51; + boost=1; + } + if (boost) + ret=SCALE(49, 0x7e, vol)+1; + else + ret=0xff - SCALE(50, 0x7f, vol); + return ret; +} + +static inline unsigned int eq_aci2oss(unsigned int vol) +{ + if (vol < 0x80) + return SCALE(0x7f, 50, vol) + 50; + else + return SCALE(0x7f, 50, 0xff-vol); +} + + +static int setequalizer(int __user *arg, + unsigned char left_index, unsigned char right_index) +{ + int buf; + unsigned int vol; + + __get_user(vol, arg); + + /* left channel */ + if ((buf=aci_write_cmd(left_index, eq_oss2aci(vol & 0xff)))<0) + return buf; + + /* right channel */ + if ((buf=aci_write_cmd(right_index, eq_oss2aci((vol>>8) & 0xff)))<0) + return buf; + + /* the ACI equalizer is more precise */ + return 0; +} + +static int getequalizer(int __user *arg, + unsigned char left_index, unsigned char right_index) +{ + int buf; + unsigned int vol; + + /* left channel */ + if ((buf=aci_indexed_cmd(ACI_STATUS, left_index))<0) + return buf; + vol = eq_aci2oss(buf); + + /* right channel */ + if ((buf=aci_indexed_cmd(ACI_STATUS, right_index))<0) + return buf; + vol |= eq_aci2oss(buf) << 8; + + __put_user(vol, arg); + + return 0; +} + +static int aci_mixer_ioctl (int dev, unsigned int cmd, void __user * arg) +{ + int vol, buf; + int __user *p = arg; + + switch (cmd) { + case SOUND_MIXER_WRITE_VOLUME: + return setvolume(p, 0x01, 0x00); + case SOUND_MIXER_WRITE_CD: + return setvolume(p, 0x3c, 0x34); + case SOUND_MIXER_WRITE_MIC: + return setvolume(p, 0x38, 0x30); + case SOUND_MIXER_WRITE_LINE: + return setvolume(p, 0x39, 0x31); + case SOUND_MIXER_WRITE_SYNTH: + return setvolume(p, 0x3b, 0x33); + case SOUND_MIXER_WRITE_PCM: + return setvolume(p, 0x3a, 0x32); + case MIXER_WRITE(SOUND_MIXER_RADIO): /* fall through */ + case SOUND_MIXER_WRITE_LINE1: /* AUX1 or radio */ + return setvolume(p, 0x3d, 0x35); + case SOUND_MIXER_WRITE_LINE2: /* AUX2 */ + return setvolume(p, 0x3e, 0x36); + case SOUND_MIXER_WRITE_BASS: /* set band one and two */ + if (aci_idcode[1]=='C') { + if ((buf=setequalizer(p, 0x48, 0x40)) || + (buf=setequalizer(p, 0x49, 0x41))); + return buf; + } + break; + case SOUND_MIXER_WRITE_TREBLE: /* set band six and seven */ + if (aci_idcode[1]=='C') { + if ((buf=setequalizer(p, 0x4d, 0x45)) || + (buf=setequalizer(p, 0x4e, 0x46))); + return buf; + } + break; + case SOUND_MIXER_WRITE_IGAIN: /* MIC pre-amp */ + if (aci_idcode[1]=='B' || aci_idcode[1]=='C') { + __get_user(vol, p); + vol = vol & 0xff; + if (vol > 100) + vol = 100; + vol = SCALE(100, 3, vol); + if ((buf=aci_write_cmd(ACI_WRITE_IGAIN, vol))<0) + return buf; + aci_micpreamp = vol; + vol = SCALE(3, 100, vol); + vol |= (vol << 8); + __put_user(vol, p); + return 0; + } + break; + case SOUND_MIXER_WRITE_OGAIN: /* Power-amp/line-out level */ + if (aci_idcode[1]=='A' || aci_idcode[1]=='B') { + __get_user(buf, p); + buf = buf & 0xff; + if (buf > 50) + vol = 1; + else + vol = 0; + if ((buf=aci_write_cmd(ACI_SET_POWERAMP, vol))<0) + return buf; + aci_amp = vol; + if (aci_amp) + buf = (100 || 100<<8); + else + buf = 0; + __put_user(buf, p); + return 0; + } + break; + case SOUND_MIXER_WRITE_RECSRC: + /* handle solo mode control */ + __get_user(buf, p); + /* unset solo when RECSRC for PCM is requested */ + if (aci_idcode[1]=='B' || aci_idcode[1]=='C') { + vol = !(buf & SOUND_MASK_PCM); + if ((buf=aci_write_cmd(ACI_SET_SOLOMODE, vol))<0) + return buf; + aci_solo = vol; + } + buf = (SOUND_MASK_CD| SOUND_MASK_MIC| SOUND_MASK_LINE| + SOUND_MASK_SYNTH| SOUND_MASK_LINE2); + if (aci_idcode[1] == 'C') /* PCM20 radio */ + buf |= SOUND_MASK_RADIO; + else + buf |= SOUND_MASK_LINE1; + if (!aci_solo) + buf |= SOUND_MASK_PCM; + __put_user(buf, p); + return 0; + case SOUND_MIXER_READ_DEVMASK: + buf = (SOUND_MASK_VOLUME | SOUND_MASK_CD | + SOUND_MASK_MIC | SOUND_MASK_LINE | + SOUND_MASK_SYNTH | SOUND_MASK_PCM | + SOUND_MASK_LINE2); + switch (aci_idcode[1]) { + case 'C': /* PCM20 radio */ + buf |= (SOUND_MASK_RADIO | SOUND_MASK_IGAIN | + SOUND_MASK_BASS | SOUND_MASK_TREBLE); + break; + case 'B': /* PCM12 */ + buf |= (SOUND_MASK_LINE1 | SOUND_MASK_IGAIN | + SOUND_MASK_OGAIN); + break; + case 'A': /* PCM1-pro */ + buf |= (SOUND_MASK_LINE1 | SOUND_MASK_OGAIN); + break; + default: + buf |= SOUND_MASK_LINE1; + } + __put_user(buf, p); + return 0; + case SOUND_MIXER_READ_STEREODEVS: + buf = (SOUND_MASK_VOLUME | SOUND_MASK_CD | + SOUND_MASK_MIC | SOUND_MASK_LINE | + SOUND_MASK_SYNTH | SOUND_MASK_PCM | + SOUND_MASK_LINE2); + switch (aci_idcode[1]) { + case 'C': /* PCM20 radio */ + buf |= (SOUND_MASK_RADIO | + SOUND_MASK_BASS | SOUND_MASK_TREBLE); + break; + default: + buf |= SOUND_MASK_LINE1; + } + __put_user(buf, p); + return 0; + case SOUND_MIXER_READ_RECMASK: + buf = (SOUND_MASK_CD| SOUND_MASK_MIC| SOUND_MASK_LINE| + SOUND_MASK_SYNTH| SOUND_MASK_LINE2| SOUND_MASK_PCM); + if (aci_idcode[1] == 'C') /* PCM20 radio */ + buf |= SOUND_MASK_RADIO; + else + buf |= SOUND_MASK_LINE1; + + __put_user(buf, p); + return 0; + case SOUND_MIXER_READ_RECSRC: + buf = (SOUND_MASK_CD | SOUND_MASK_MIC | SOUND_MASK_LINE | + SOUND_MASK_SYNTH | SOUND_MASK_LINE2); + /* do we need aci_solo or can I get it from the ACI? */ + switch (aci_idcode[1]) { + case 'B': /* PCM12 */ + case 'C': /* PCM20 radio */ + if (aci_version >= 0xb0) { + if ((vol=aci_rw_cmd(ACI_STATUS, + ACI_S_GENERAL, -1))<0) + return vol; + if (vol & 0x20) + buf |= SOUND_MASK_PCM; + } + else + if (!aci_solo) + buf |= SOUND_MASK_PCM; + break; + default: + buf |= SOUND_MASK_PCM; + } + if (aci_idcode[1] == 'C') /* PCM20 radio */ + buf |= SOUND_MASK_RADIO; + else + buf |= SOUND_MASK_LINE1; + + __put_user(buf, p); + return 0; + case SOUND_MIXER_READ_CAPS: + __put_user(0, p); + return 0; + case SOUND_MIXER_READ_VOLUME: + return getvolume(p, 0x04, 0x03); + case SOUND_MIXER_READ_CD: + return getvolume(p, 0x0a, 0x09); + case SOUND_MIXER_READ_MIC: + return getvolume(p, 0x06, 0x05); + case SOUND_MIXER_READ_LINE: + return getvolume(p, 0x08, 0x07); + case SOUND_MIXER_READ_SYNTH: + return getvolume(p, 0x0c, 0x0b); + case SOUND_MIXER_READ_PCM: + return getvolume(p, 0x0e, 0x0d); + case MIXER_READ(SOUND_MIXER_RADIO): /* fall through */ + case SOUND_MIXER_READ_LINE1: /* AUX1 */ + return getvolume(p, 0x11, 0x10); + case SOUND_MIXER_READ_LINE2: /* AUX2 */ + return getvolume(p, 0x13, 0x12); + case SOUND_MIXER_READ_BASS: /* get band one */ + if (aci_idcode[1]=='C') { + return getequalizer(p, 0x23, 0x22); + } + break; + case SOUND_MIXER_READ_TREBLE: /* get band seven */ + if (aci_idcode[1]=='C') { + return getequalizer(p, 0x2f, 0x2e); + } + break; + case SOUND_MIXER_READ_IGAIN: /* MIC pre-amp */ + if (aci_idcode[1]=='B' || aci_idcode[1]=='C') { + /* aci_micpreamp or ACI? */ + if (aci_version >= 0xb0) { + if ((buf=aci_indexed_cmd(ACI_STATUS, + ACI_S_READ_IGAIN))<0) + return buf; + } + else + buf=aci_micpreamp; + vol = SCALE(3, 100, buf <= 3 ? buf : 3); + vol |= vol << 8; + __put_user(vol, p); + return 0; + } + break; + case SOUND_MIXER_READ_OGAIN: + if (aci_amp) + buf = (100 || 100<<8); + else + buf = 0; + __put_user(buf, p); + return 0; + } + return -EINVAL; +} + +static struct mixer_operations aci_mixer_operations = +{ + .owner = THIS_MODULE, + .id = "ACI", + .ioctl = aci_mixer_ioctl +}; + +/* + * There is also an internal mixer in the codec (CS4231A or AD1845), + * that deserves no purpose in an ACI based system which uses an + * external ACI controlled stereo mixer. Make sure that this codec + * mixer has the AUX1 input selected as the recording source, that the + * input gain is set near maximum and that the other channels going + * from the inputs to the codec output are muted. + */ + +static int __init attach_aci(void) +{ + char *boardname; + int i, rc = -EBUSY; + + init_MUTEX(&aci_sem); + + outb(0xE3, 0xf8f); /* Write MAD16 password */ + aci_port = (inb(0xf90) & 0x10) ? + 0x344: 0x354; /* Get aci_port from MC4_PORT */ + + if (!request_region(aci_port, 3, "sound mixer (ACI)")) { + printk(KERN_NOTICE + "aci: I/O area 0x%03x-0x%03x already used.\n", + aci_port, aci_port+2); + goto out; + } + + /* force ACI into a known state */ + rc = -EFAULT; + for (i=0; i<3; i++) + if (aci_rw_cmd(ACI_ERROR_OP, -1, -1)<0) + goto out_release_region; + + /* official this is one aci read call: */ + rc = -EFAULT; + if ((aci_idcode[0]=aci_rw_cmd(ACI_READ_IDCODE, -1, -1))<0 || + (aci_idcode[1]=aci_rw_cmd(ACI_READ_IDCODE, -1, -1))<0) { + printk(KERN_ERR "aci: Failed to read idcode on 0x%03x.\n", + aci_port); + goto out_release_region; + } + + if ((aci_version=aci_rw_cmd(ACI_READ_VERSION, -1, -1))<0) { + printk(KERN_ERR "aci: Failed to read version on 0x%03x.\n", + aci_port); + goto out_release_region; + } + + if (aci_idcode[0] == 'm') { + /* It looks like a miro sound card. */ + switch (aci_idcode[1]) { + case 'A': + boardname = "PCM1 pro / early PCM12"; + break; + case 'B': + boardname = "PCM12"; + break; + case 'C': + boardname = "PCM20 radio"; + break; + default: + boardname = "unknown miro"; + } + } else { + printk(KERN_WARNING "aci: Warning: unsupported card! - " + "no hardware, no specs...\n"); + boardname = "unknown Cardinal Technologies"; + } + + printk(KERN_INFO " at 0x%03x\n", + aci_version, + aci_idcode[0], aci_idcode[1], + aci_idcode[0], aci_idcode[1], + boardname, aci_port); + + rc = -EBUSY; + if (reset) { + /* first write()s after reset fail with my PCM20 */ + if (aci_rw_cmd(ACI_INIT, -1, -1)<0 || + aci_rw_cmd(ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP)<0 || + aci_rw_cmd(ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP)<0) + goto out_release_region; + } + + /* the PCM20 is muted after reset (and reboot) */ + if (aci_rw_cmd(ACI_SET_MUTE, 0x00, -1)<0) + goto out_release_region; + + if (ide>=0) + if (aci_rw_cmd(ACI_SET_IDE, !ide, -1)<0) + goto out_release_region; + + if (wss>=0 && aci_idcode[1]=='A') + if (aci_rw_cmd(ACI_SET_WSS, !!wss, -1)<0) + goto out_release_region; + + mixer_device = sound_install_mixer(MIXER_DRIVER_VERSION, boardname, + &aci_mixer_operations, + sizeof(aci_mixer_operations), NULL); + rc = 0; + if (mixer_device < 0) { + printk(KERN_ERR "aci: Failed to install mixer.\n"); + rc = mixer_device; + goto out_release_region; + } /* else Maybe initialize the CS4231A mixer here... */ +out: return rc; +out_release_region: + release_region(aci_port, 3); + goto out; +} + +static void __exit unload_aci(void) +{ + sound_unload_mixerdev(mixer_device); + release_region(aci_port, 3); +} + +module_init(attach_aci); +module_exit(unload_aci); +MODULE_LICENSE("GPL"); diff --git a/sound/oss/aci.h b/sound/oss/aci.h new file mode 100644 index 000000000000..20102ee088eb --- /dev/null +++ b/sound/oss/aci.h @@ -0,0 +1,57 @@ +#ifndef _ACI_H_ +#define _ACI_H_ + +extern int aci_port; +extern int aci_version; /* ACI firmware version */ +extern int aci_rw_cmd(int write1, int write2, int write3); + +#define aci_indexed_cmd(a, b) aci_rw_cmd(a, b, -1) +#define aci_write_cmd(a, b) aci_rw_cmd(a, b, -1) +#define aci_read_cmd(a) aci_rw_cmd(a,-1, -1) + +#define COMMAND_REGISTER (aci_port) /* write register */ +#define STATUS_REGISTER (aci_port + 1) /* read register */ +#define BUSY_REGISTER (aci_port + 2) /* also used for rds */ + +#define RDS_REGISTER BUSY_REGISTER + +#define ACI_SET_MUTE 0x0d +#define ACI_SET_POWERAMP 0x0f +#define ACI_SET_TUNERMUTE 0xa3 +#define ACI_SET_TUNERMONO 0xa4 +#define ACI_SET_IDE 0xd0 +#define ACI_SET_WSS 0xd1 +#define ACI_SET_SOLOMODE 0xd2 +#define ACI_WRITE_IGAIN 0x03 +#define ACI_WRITE_TUNE 0xa7 +#define ACI_READ_TUNERSTEREO 0xa8 +#define ACI_READ_TUNERSTATION 0xa9 +#define ACI_READ_VERSION 0xf1 +#define ACI_READ_IDCODE 0xf2 +#define ACI_INIT 0xff +#define ACI_STATUS 0xf0 +#define ACI_S_GENERAL 0x00 +#define ACI_S_READ_IGAIN 0x21 +#define ACI_ERROR_OP 0xdf + +/* + * The following macro SCALE can be used to scale one integer volume + * value into another one using only integer arithmetic. If the input + * value x is in the range 0 <= x <= xmax, then the result will be in + * the range 0 <= SCALE(xmax,ymax,x) <= ymax. + * + * This macro has for all xmax, ymax > 0 and all 0 <= x <= xmax the + * following nice properties: + * + * - SCALE(xmax,ymax,xmax) = ymax + * - SCALE(xmax,ymax,0) = 0 + * - SCALE(xmax,ymax,SCALE(ymax,xmax,SCALE(xmax,ymax,x))) = SCALE(xmax,ymax,x) + * + * In addition, the rounding error is minimal and nicely distributed. + * The proofs are left as an exercise to the reader. + */ + +#define SCALE(xmax,ymax,x) (((x)*(ymax)+(xmax)/2)/(xmax)) + + +#endif /* _ACI_H_ */ diff --git a/sound/oss/ad1816.c b/sound/oss/ad1816.c new file mode 100644 index 000000000000..22dae5d0fda3 --- /dev/null +++ b/sound/oss/ad1816.c @@ -0,0 +1,1369 @@ +/* + * + * AD1816 lowlevel sound driver for Linux 2.6.0 and above + * + * Copyright (C) 1998-2003 by Thorsten Knabe + * + * Based on the CS4232/AD1848 driver Copyright (C) by Hannu Savolainen 1993-1996 + * + * + * version: 1.5 + * status: beta + * date: 2003/07/15 + * + * Changes: + * Oleg Drokin: Some cleanup of load/unload functions. 1998/11/24 + * + * Thorsten Knabe: attach and unload rewritten, + * some argument checks added 1998/11/30 + * + * Thorsten Knabe: Buggy isa bridge workaround added 1999/01/16 + * + * David Moews/Thorsten Knabe: Introduced options + * parameter. Added slightly modified patch from + * David Moews to disable dsp audio sources by setting + * bit 0 of options parameter. This seems to be + * required by some Aztech/Newcom SC-16 cards. 1999/04/18 + * + * Christoph Hellwig: Adapted to module_init/module_exit. 2000/03/03 + * + * Christoph Hellwig: Added isapnp support 2000/03/15 + * + * Arnaldo Carvalho de Melo: get rid of check_region 2001/10/07 + * + * Thorsten Knabe: Compiling with CONFIG_PNP enabled + * works again. It is now possible to use more than one + * AD1816 sound card. Sample rate now may be changed during + * playback/capture. printk() uses log levels everywhere. + * SMP fixes. DMA handling fixes. + * Other minor code cleanup. 2003/07/15 + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include "sound_config.h" + +#define DEBUGNOISE(x) + +#define CHECK_FOR_POWER { int timeout=100; \ + while (timeout > 0 && (inb(devc->base)&0x80)!= 0x80) {\ + timeout--; \ + } \ + if (timeout==0) {\ + printk(KERN_WARNING "ad1816: Check for power failed in %s line: %d\n",__FILE__,__LINE__); \ + } \ +} + +/* structure to hold device specific information */ +typedef struct +{ + int base; /* set in attach */ + int irq; + int dma_playback; + int dma_capture; + + int opened; /* open */ + int speed; + int channels; + int audio_format; + int audio_mode; + + int recmask; /* setup */ + unsigned char format_bits; + int supported_devices; + int supported_rec_devices; + unsigned short levels[SOUND_MIXER_NRDEVICES]; + /* misc */ + struct pnp_dev *pnpdev; /* configured via pnp */ + int dev_no; /* this is the # in audio_devs and NOT + in ad1816_info */ + spinlock_t lock; +} ad1816_info; + +static int nr_ad1816_devs; +static int ad1816_clockfreq = 33000; +static int options; + +/* supported audio formats */ +static int ad_format_mask = +AFMT_U8 | AFMT_S16_LE | AFMT_S16_BE | AFMT_MU_LAW | AFMT_A_LAW; + +/* array of device info structures */ +static ad1816_info dev_info[MAX_AUDIO_DEV]; + + +/* ------------------------------------------------------------------- */ + +/* functions for easier access to inderect registers */ + +static int ad_read (ad1816_info * devc, int reg) +{ + int result; + + CHECK_FOR_POWER; + outb ((unsigned char) (reg & 0x3f), devc->base+0); + result = inb(devc->base+2); + result+= inb(devc->base+3)<<8; + return (result); +} + + +static void ad_write (ad1816_info * devc, int reg, int data) +{ + CHECK_FOR_POWER; + outb ((unsigned char) (reg & 0xff), devc->base+0); + outb ((unsigned char) (data & 0xff),devc->base+2); + outb ((unsigned char) ((data>>8)&0xff),devc->base+3); +} + +/* ------------------------------------------------------------------- */ + +/* function interface required by struct audio_driver */ + +static void ad1816_halt_input (int dev) +{ + unsigned long flags; + ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc; + unsigned char buffer; + + DEBUGNOISE(printk(KERN_DEBUG "ad1816: halt_input called\n")); + + spin_lock_irqsave(&devc->lock,flags); + + if(!isa_dma_bridge_buggy) { + disable_dma(audio_devs[dev]->dmap_in->dma); + } + + buffer=inb(devc->base+9); + if (buffer & 0x01) { + /* disable capture */ + outb(buffer & ~0x01,devc->base+9); + } + + if(!isa_dma_bridge_buggy) { + enable_dma(audio_devs[dev]->dmap_in->dma); + } + + /* Clear interrupt status */ + outb (~0x40, devc->base+1); + + devc->audio_mode &= ~PCM_ENABLE_INPUT; + spin_unlock_irqrestore(&devc->lock,flags); +} + +static void ad1816_halt_output (int dev) +{ + unsigned long flags; + ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc; + + unsigned char buffer; + + DEBUGNOISE(printk(KERN_DEBUG "ad1816: halt_output called!\n")); + + spin_lock_irqsave(&devc->lock,flags); + /* Mute pcm output */ + ad_write(devc, 4, ad_read(devc,4)|0x8080); + + if(!isa_dma_bridge_buggy) { + disable_dma(audio_devs[dev]->dmap_out->dma); + } + + buffer=inb(devc->base+8); + if (buffer & 0x01) { + /* disable capture */ + outb(buffer & ~0x01,devc->base+8); + } + + if(!isa_dma_bridge_buggy) { + enable_dma(audio_devs[dev]->dmap_out->dma); + } + + /* Clear interrupt status */ + outb ((unsigned char)~0x80, devc->base+1); + + devc->audio_mode &= ~PCM_ENABLE_OUTPUT; + spin_unlock_irqrestore(&devc->lock,flags); +} + +static void ad1816_output_block (int dev, unsigned long buf, + int count, int intrflag) +{ + unsigned long flags; + unsigned long cnt; + ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc; + + DEBUGNOISE(printk(KERN_DEBUG "ad1816: output_block called buf=%ld count=%d flags=%d\n",buf,count,intrflag)); + + cnt = count/4 - 1; + + spin_lock_irqsave(&devc->lock,flags); + + /* set transfer count */ + ad_write (devc, 8, cnt & 0xffff); + + devc->audio_mode |= PCM_ENABLE_OUTPUT; + spin_unlock_irqrestore(&devc->lock,flags); +} + + +static void ad1816_start_input (int dev, unsigned long buf, int count, + int intrflag) +{ + unsigned long flags; + unsigned long cnt; + ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc; + + DEBUGNOISE(printk(KERN_DEBUG "ad1816: start_input called buf=%ld count=%d flags=%d\n",buf,count,intrflag)); + + cnt = count/4 - 1; + + spin_lock_irqsave(&devc->lock,flags); + + /* set transfer count */ + ad_write (devc, 10, cnt & 0xffff); + devc->audio_mode |= PCM_ENABLE_INPUT; + spin_unlock_irqrestore(&devc->lock,flags); +} + +static int ad1816_prepare_for_input (int dev, int bsize, int bcount) +{ + unsigned long flags; + unsigned int freq; + ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc; + unsigned char fmt_bits; + + DEBUGNOISE(printk(KERN_DEBUG "ad1816: prepare_for_input called: bsize=%d bcount=%d\n",bsize,bcount)); + + spin_lock_irqsave(&devc->lock,flags); + fmt_bits= (devc->format_bits&0x7)<<3; + + /* set mono/stereo mode */ + if (devc->channels > 1) { + fmt_bits |=0x4; + } + /* set Mono/Stereo in playback/capture register */ + outb( (inb(devc->base+8) & ~0x3C)|fmt_bits, devc->base+8); + outb( (inb(devc->base+9) & ~0x3C)|fmt_bits, devc->base+9); + + freq=((unsigned int)devc->speed*33000)/ad1816_clockfreq; + + /* write playback/capture speeds */ + ad_write (devc, 2, freq & 0xffff); + ad_write (devc, 3, freq & 0xffff); + + spin_unlock_irqrestore(&devc->lock,flags); + + ad1816_halt_input(dev); + return 0; +} + +static int ad1816_prepare_for_output (int dev, int bsize, int bcount) +{ + unsigned long flags; + unsigned int freq; + ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc; + unsigned char fmt_bits; + + DEBUGNOISE(printk(KERN_DEBUG "ad1816: prepare_for_output called: bsize=%d bcount=%d\n",bsize,bcount)); + + spin_lock_irqsave(&devc->lock,flags); + + fmt_bits= (devc->format_bits&0x7)<<3; + /* set mono/stereo mode */ + if (devc->channels > 1) { + fmt_bits |=0x4; + } + + /* write format bits to playback/capture registers */ + outb( (inb(devc->base+8) & ~0x3C)|fmt_bits, devc->base+8); + outb( (inb(devc->base+9) & ~0x3C)|fmt_bits, devc->base+9); + + freq=((unsigned int)devc->speed*33000)/ad1816_clockfreq; + + /* write playback/capture speeds */ + ad_write (devc, 2, freq & 0xffff); + ad_write (devc, 3, freq & 0xffff); + + spin_unlock_irqrestore(&devc->lock,flags); + + ad1816_halt_output(dev); + return 0; + +} + +static void ad1816_trigger (int dev, int state) +{ + unsigned long flags; + ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc; + + DEBUGNOISE(printk(KERN_DEBUG "ad1816: trigger called! (devc=%d,devc->base=%d\n", devc, devc->base)); + + /* mode may have changed */ + + spin_lock_irqsave(&devc->lock,flags); + + /* mask out modes not specified on open call */ + state &= devc->audio_mode; + + /* setup soundchip to new io-mode */ + if (state & PCM_ENABLE_INPUT) { + /* enable capture */ + outb(inb(devc->base+9)|0x01, devc->base+9); + } else { + /* disable capture */ + outb(inb(devc->base+9)&~0x01, devc->base+9); + } + + if (state & PCM_ENABLE_OUTPUT) { + /* enable playback */ + outb(inb(devc->base+8)|0x01, devc->base+8); + /* unmute pcm output */ + ad_write(devc, 4, ad_read(devc,4)&~0x8080); + } else { + /* mute pcm output */ + ad_write(devc, 4, ad_read(devc,4)|0x8080); + /* disable capture */ + outb(inb(devc->base+8)&~0x01, devc->base+8); + } + spin_unlock_irqrestore(&devc->lock,flags); +} + + +/* halt input & output */ +static void ad1816_halt (int dev) +{ + ad1816_halt_input(dev); + ad1816_halt_output(dev); +} + +static void ad1816_reset (int dev) +{ + ad1816_halt (dev); +} + +/* set playback speed */ +static int ad1816_set_speed (int dev, int arg) +{ + unsigned long flags; + unsigned int freq; + int ret; + + ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc; + + spin_lock_irqsave(&devc->lock, flags); + if (arg == 0) { + ret = devc->speed; + spin_unlock_irqrestore(&devc->lock, flags); + return ret; + } + /* range checking */ + if (arg < 4000) { + arg = 4000; + } + if (arg > 55000) { + arg = 55000; + } + devc->speed = arg; + + /* change speed during playback */ + freq=((unsigned int)devc->speed*33000)/ad1816_clockfreq; + /* write playback/capture speeds */ + ad_write (devc, 2, freq & 0xffff); + ad_write (devc, 3, freq & 0xffff); + + ret = devc->speed; + spin_unlock_irqrestore(&devc->lock, flags); + return ret; + +} + +static unsigned int ad1816_set_bits (int dev, unsigned int arg) +{ + unsigned long flags; + ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc; + + static struct format_tbl { + int format; + unsigned char bits; + } format2bits[] = { + { 0, 0 }, + { AFMT_MU_LAW, 1 }, + { AFMT_A_LAW, 3 }, + { AFMT_IMA_ADPCM, 0 }, + { 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); + + spin_lock_irqsave(&devc->lock, flags); + /* return current format */ + if (arg == 0) { + arg = devc->audio_format; + spin_unlock_irqrestore(&devc->lock, flags); + return arg; + } + devc->audio_format = arg; + + /* search matching format bits */ + for (i = 0; i < n; i++) + if (format2bits[i].format == arg) { + devc->format_bits = format2bits[i].bits; + devc->audio_format = arg; + spin_unlock_irqrestore(&devc->lock, flags); + return arg; + } + + /* Still hanging here. Something must be terribly wrong */ + devc->format_bits = 0; + devc->audio_format = AFMT_U8; + spin_unlock_irqrestore(&devc->lock, flags); + return(AFMT_U8); +} + +static short ad1816_set_channels (int dev, short arg) +{ + ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc; + + if (arg != 1 && arg != 2) + return devc->channels; + + devc->channels = arg; + return arg; +} + +/* open device */ +static int ad1816_open (int dev, int mode) +{ + ad1816_info *devc = NULL; + unsigned long flags; + + /* is device number valid ? */ + if (dev < 0 || dev >= num_audiodevs) + return -(ENXIO); + + /* get device info of this dev */ + devc = (ad1816_info *) audio_devs[dev]->devc; + + /* make check if device already open atomic */ + spin_lock_irqsave(&devc->lock,flags); + + if (devc->opened) { + spin_unlock_irqrestore(&devc->lock,flags); + return -(EBUSY); + } + + /* mark device as open */ + devc->opened = 1; + + devc->audio_mode = 0; + devc->speed = 8000; + devc->audio_format=AFMT_U8; + devc->channels=1; + spin_unlock_irqrestore(&devc->lock,flags); + ad1816_reset(devc->dev_no); /* halt all pending output */ + return 0; +} + +static void ad1816_close (int dev) /* close device */ +{ + unsigned long flags; + ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc; + + /* halt all pending output */ + ad1816_reset(devc->dev_no); + + spin_lock_irqsave(&devc->lock,flags); + devc->opened = 0; + devc->audio_mode = 0; + devc->speed = 8000; + devc->audio_format=AFMT_U8; + devc->format_bits = 0; + spin_unlock_irqrestore(&devc->lock,flags); +} + + +/* ------------------------------------------------------------------- */ + +/* Audio driver structure */ + +static struct audio_driver ad1816_audio_driver = +{ + .owner = THIS_MODULE, + .open = ad1816_open, + .close = ad1816_close, + .output_block = ad1816_output_block, + .start_input = ad1816_start_input, + .prepare_for_input = ad1816_prepare_for_input, + .prepare_for_output = ad1816_prepare_for_output, + .halt_io = ad1816_halt, + .halt_input = ad1816_halt_input, + .halt_output = ad1816_halt_output, + .trigger = ad1816_trigger, + .set_speed = ad1816_set_speed, + .set_bits = ad1816_set_bits, + .set_channels = ad1816_set_channels, +}; + + +/* ------------------------------------------------------------------- */ + +/* Interrupt handler */ + + +static irqreturn_t ad1816_interrupt (int irq, void *dev_id, struct pt_regs *dummy) +{ + unsigned char status; + ad1816_info *devc = (ad1816_info *)dev_id; + + if (irq < 0 || irq > 15) { + printk(KERN_WARNING "ad1816: Got bogus interrupt %d\n", irq); + return IRQ_NONE; + } + + spin_lock(&devc->lock); + + /* read interrupt register */ + status = inb (devc->base+1); + /* Clear all interrupt */ + outb (~status, devc->base+1); + + DEBUGNOISE(printk(KERN_DEBUG "ad1816: Got interrupt subclass %d\n",status)); + + if (status == 0) { + DEBUGNOISE(printk(KERN_DEBUG "ad1816: interrupt: Got interrupt, but no source.\n")); + spin_unlock(&devc->lock); + return IRQ_NONE; + } + + if (devc->opened && (devc->audio_mode & PCM_ENABLE_INPUT) && (status&64)) + DMAbuf_inputintr (devc->dev_no); + + if (devc->opened && (devc->audio_mode & PCM_ENABLE_OUTPUT) && (status & 128)) + DMAbuf_outputintr (devc->dev_no, 1); + + spin_unlock(&devc->lock); + return IRQ_HANDLED; +} + +/* ------------------------------------------------------------------- */ + +/* Mixer stuff */ + +struct mixer_def { + unsigned int regno: 7; + unsigned int polarity:1; /* 0=normal, 1=reversed */ + unsigned int bitpos:4; + unsigned int nbits:4; +}; + +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; + +/* + * Most of the mixer entries work in backwards. Setting the polarity field + * makes them to work correctly. + * + * The channel numbering used by individual soundcards 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) \ + {{reg_l, pola_l, pos_l, len_l}, {reg_r, pola_r, pos_r, len_r}} + + +mixer_ent mix_devices[SOUND_MIXER_NRDEVICES][2] = { +MIX_ENT(SOUND_MIXER_VOLUME, 14, 1, 8, 5, 14, 1, 0, 5), +MIX_ENT(SOUND_MIXER_BASS, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_TREBLE, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_SYNTH, 5, 1, 8, 6, 5, 1, 0, 6), +MIX_ENT(SOUND_MIXER_PCM, 4, 1, 8, 6, 4, 1, 0, 6), +MIX_ENT(SOUND_MIXER_SPEAKER, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 18, 1, 8, 5, 18, 1, 0, 5), +MIX_ENT(SOUND_MIXER_MIC, 19, 1, 8, 5, 19, 1, 0, 5), +MIX_ENT(SOUND_MIXER_CD, 15, 1, 8, 5, 15, 1, 0, 5), +MIX_ENT(SOUND_MIXER_IMIX, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_ALTPCM, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_RECLEV, 20, 0, 8, 4, 20, 0, 0, 4), +MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_OGAIN, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_LINE1, 17, 1, 8, 5, 17, 1, 0, 5), +MIX_ENT(SOUND_MIXER_LINE2, 16, 1, 8, 5, 16, 1, 0, 5), +MIX_ENT(SOUND_MIXER_LINE3, 39, 0, 9, 4, 39, 1, 0, 5) +}; + + +static unsigned short default_mixer_levels[SOUND_MIXER_NRDEVICES] = +{ + 0x4343, /* Master Volume */ + 0x3232, /* Bass */ + 0x3232, /* Treble */ + 0x0000, /* FM */ + 0x4343, /* PCM */ + 0x0000, /* PC Speaker */ + 0x0000, /* Ext Line */ + 0x0000, /* Mic */ + 0x0000, /* CD */ + 0x0000, /* Recording monitor */ + 0x0000, /* SB PCM */ + 0x0000, /* Recording level */ + 0x0000, /* Input gain */ + 0x0000, /* Output gain */ + 0x0000, /* Line1 */ + 0x0000, /* Line2 */ + 0x0000 /* Line3 (usually line in)*/ +}; + +#define LEFT_CHN 0 +#define RIGHT_CHN 1 + + + +static int +ad1816_set_recmask (ad1816_info * devc, int mask) +{ + unsigned long flags; + unsigned char recdev; + int i, n; + + spin_lock_irqsave(&devc->lock, flags); + mask &= devc->supported_rec_devices; + + n = 0; + /* Count selected device bits */ + for (i = 0; i < 32; i++) + if (mask & (1 << i)) + n++; + + if (n == 0) + mask = SOUND_MASK_MIC; + else if (n != 1) { /* Too many devices selected */ + /* Filter out active settings */ + mask &= ~devc->recmask; + + n = 0; + /* Count selected device bits */ + for (i = 0; i < 32; i++) + if (mask & (1 << i)) + n++; + + if (n != 1) + mask = SOUND_MASK_MIC; + } + + switch (mask) { + case SOUND_MASK_MIC: + recdev = 5; + break; + + case SOUND_MASK_LINE: + recdev = 0; + break; + + case SOUND_MASK_CD: + recdev = 2; + break; + + case SOUND_MASK_LINE1: + recdev = 4; + break; + + case SOUND_MASK_LINE2: + recdev = 3; + break; + + case SOUND_MASK_VOLUME: + recdev = 1; + break; + + default: + mask = SOUND_MASK_MIC; + recdev = 5; + } + + recdev <<= 4; + ad_write (devc, 20, + (ad_read (devc, 20) & 0x8f8f) | recdev | (recdev<<8)); + + devc->recmask = mask; + spin_unlock_irqrestore(&devc->lock, flags); + return mask; +} + +static void +change_bits (int *regval, int dev, int chn, int newval) +{ + unsigned char mask; + int shift; + + /* Reverse polarity*/ + + if (mix_devices[dev][chn].polarity == 1) + newval = 100 - newval; + + mask = (1 << mix_devices[dev][chn].nbits) - 1; + shift = mix_devices[dev][chn].bitpos; + /* Scale it */ + newval = (int) ((newval * mask) + 50) / 100; + /* Clear bits */ + *regval &= ~(mask << shift); + /* Set new value */ + *regval |= (newval & mask) << shift; +} + +static int +ad1816_mixer_get (ad1816_info * devc, int dev) +{ + DEBUGNOISE(printk(KERN_DEBUG "ad1816: mixer_get called!\n")); + + /* range check + supported mixer check */ + if (dev < 0 || dev >= SOUND_MIXER_NRDEVICES ) + return (-(EINVAL)); + if (!((1 << dev) & devc->supported_devices)) + return -(EINVAL); + + return devc->levels[dev]; +} + +static int +ad1816_mixer_set (ad1816_info * devc, int dev, int value) +{ + int left = value & 0x000000ff; + int right = (value & 0x0000ff00) >> 8; + int retvol; + + int regoffs; + int val; + int valmute; + unsigned long flags; + + DEBUGNOISE(printk(KERN_DEBUG "ad1816: mixer_set called!\n")); + + if (dev < 0 || dev >= SOUND_MIXER_NRDEVICES ) + return -(EINVAL); + + if (left > 100) + left = 100; + if (left < 0) + left = 0; + if (right > 100) + right = 100; + if (right < 0) + right = 0; + + /* Mono control */ + if (mix_devices[dev][RIGHT_CHN].nbits == 0) + right = left; + retvol = left | (right << 8); + + /* Scale it */ + + left = mix_cvt[left]; + right = mix_cvt[right]; + + /* reject all mixers that are not supported */ + if (!(devc->supported_devices & (1 << dev))) + return -(EINVAL); + + /* sanity check */ + if (mix_devices[dev][LEFT_CHN].nbits == 0) + return -(EINVAL); + spin_lock_irqsave(&devc->lock, flags); + + /* keep precise volume internal */ + devc->levels[dev] = retvol; + + /* Set the left channel */ + regoffs = mix_devices[dev][LEFT_CHN].regno; + val = ad_read (devc, regoffs); + change_bits (&val, dev, LEFT_CHN, left); + + valmute=val; + + /* Mute bit masking on some registers */ + if ( regoffs==5 || regoffs==14 || regoffs==15 || + regoffs==16 || regoffs==17 || regoffs==18 || + regoffs==19 || regoffs==39) { + if (left==0) + valmute |= 0x8000; + else + valmute &= ~0x8000; + } + ad_write (devc, regoffs, valmute); /* mute */ + + /* + * Set the right channel + */ + + /* Was just a mono channel */ + if (mix_devices[dev][RIGHT_CHN].nbits == 0) { + spin_unlock_irqrestore(&devc->lock, flags); + return retvol; + } + + regoffs = mix_devices[dev][RIGHT_CHN].regno; + val = ad_read (devc, regoffs); + change_bits (&val, dev, RIGHT_CHN, right); + + valmute=val; + if ( regoffs==5 || regoffs==14 || regoffs==15 || + regoffs==16 || regoffs==17 || regoffs==18 || + regoffs==19 || regoffs==39) { + if (right==0) + valmute |= 0x80; + else + valmute &= ~0x80; + } + ad_write (devc, regoffs, valmute); /* mute */ + spin_unlock_irqrestore(&devc->lock, flags); + return retvol; +} + +#define MIXER_DEVICES ( SOUND_MASK_VOLUME | \ + SOUND_MASK_SYNTH | \ + SOUND_MASK_PCM | \ + SOUND_MASK_LINE | \ + SOUND_MASK_LINE1 | \ + SOUND_MASK_LINE2 | \ + SOUND_MASK_LINE3 | \ + SOUND_MASK_MIC | \ + SOUND_MASK_CD | \ + SOUND_MASK_RECLEV \ + ) +#define REC_DEVICES ( SOUND_MASK_LINE2 |\ + SOUND_MASK_LINE |\ + SOUND_MASK_LINE1 |\ + SOUND_MASK_MIC |\ + SOUND_MASK_CD |\ + SOUND_MASK_VOLUME \ + ) + +static void +ad1816_mixer_reset (ad1816_info * devc) +{ + int i; + + devc->supported_devices = MIXER_DEVICES; + + devc->supported_rec_devices = REC_DEVICES; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (devc->supported_devices & (1 << i)) + ad1816_mixer_set (devc, i, default_mixer_levels[i]); + ad1816_set_recmask (devc, SOUND_MASK_MIC); +} + +static int +ad1816_mixer_ioctl (int dev, unsigned int cmd, void __user * arg) +{ + ad1816_info *devc = mixer_devs[dev]->devc; + int val; + int __user *p = arg; + + DEBUGNOISE(printk(KERN_DEBUG "ad1816: mixer_ioctl called!\n")); + + /* Mixer ioctl */ + if (((cmd >> 8) & 0xff) == 'M') { + + /* set ioctl */ + if (_SIOC_DIR (cmd) & _SIOC_WRITE) { + switch (cmd & 0xff){ + case SOUND_MIXER_RECSRC: + + if (get_user(val, p)) + return -EFAULT; + val=ad1816_set_recmask (devc, val); + return put_user(val, p); + break; + + default: + if (get_user(val, p)) + return -EFAULT; + if ((val=ad1816_mixer_set (devc, cmd & 0xff, val))<0) + return val; + else + return put_user(val, p); + } + } else { + /* read ioctl */ + switch (cmd & 0xff) { + + case SOUND_MIXER_RECSRC: + val=devc->recmask; + return put_user(val, p); + break; + + case SOUND_MIXER_DEVMASK: + val=devc->supported_devices; + return put_user(val, p); + break; + + case SOUND_MIXER_STEREODEVS: + val=devc->supported_devices & ~(SOUND_MASK_SPEAKER | SOUND_MASK_IMIX); + return put_user(val, p); + break; + + case SOUND_MIXER_RECMASK: + val=devc->supported_rec_devices; + return put_user(val, p); + break; + + case SOUND_MIXER_CAPS: + val=SOUND_CAP_EXCL_INPUT; + return put_user(val, p); + break; + + default: + if ((val=ad1816_mixer_get (devc, cmd & 0xff))<0) + return val; + else + return put_user(val, p); + } + } + } else + /* not for mixer */ + return -(EINVAL); +} + +/* ------------------------------------------------------------------- */ + +/* Mixer structure */ + +static struct mixer_operations ad1816_mixer_operations = { + .owner = THIS_MODULE, + .id = "AD1816", + .name = "AD1816 Mixer", + .ioctl = ad1816_mixer_ioctl +}; + + +/* ------------------------------------------------------------------- */ + +/* stuff for card recognition, init and unloading PNP ...*/ + + +/* check if AD1816 present at specified hw_config and register device with OS + * return 1 if initialization was successful, 0 otherwise + */ +static int __init ad1816_init_card (struct address_info *hw_config, + struct pnp_dev *pnp) +{ + ad1816_info *devc = NULL; + int tmp; + int oss_devno = -1; + + printk(KERN_INFO "ad1816: initializing card: io=0x%x, irq=%d, dma=%d, " + "dma2=%d, clockfreq=%d, options=%d isadmabug=%d " + "%s\n", + hw_config->io_base, + hw_config->irq, + hw_config->dma, + hw_config->dma2, + ad1816_clockfreq, + options, + isa_dma_bridge_buggy, + pnp?"(PNP)":""); + + /* ad1816_info structure remaining ? */ + if (nr_ad1816_devs >= MAX_AUDIO_DEV) { + printk(KERN_WARNING "ad1816: no more ad1816_info structures " + "left\n"); + goto out; + } + + devc = &dev_info[nr_ad1816_devs]; + devc->base = hw_config->io_base; + devc->irq = hw_config->irq; + devc->dma_playback=hw_config->dma; + devc->dma_capture=hw_config->dma2; + devc->opened = 0; + devc->pnpdev = pnp; + spin_lock_init(&devc->lock); + + if (!request_region(devc->base, 16, "AD1816 Sound")) { + printk(KERN_WARNING "ad1816: I/O port 0x%03x not free\n", + devc->base); + goto out; + } + + printk(KERN_INFO "ad1816: Examining AD1816 at address 0x%03x.\n", + devc->base); + + + /* tests for ad1816 */ + /* base+0: bit 1 must be set but not 255 */ + tmp=inb(devc->base); + if ( (tmp&0x80)==0 || tmp==255 ) { + printk (KERN_INFO "ad1816: Chip is not an AD1816 or chip " + "is not active (Test 0)\n"); + goto out_release_region; + } + + /* writes to ireg 8 are copied to ireg 9 */ + ad_write(devc,8,12345); + if (ad_read(devc,9)!=12345) { + printk(KERN_INFO "ad1816: Chip is not an AD1816 (Test 1)\n"); + goto out_release_region; + } + + /* writes to ireg 8 are copied to ireg 9 */ + ad_write(devc,8,54321); + if (ad_read(devc,9)!=54321) { + printk(KERN_INFO "ad1816: Chip is not an AD1816 (Test 2)\n"); + goto out_release_region; + } + + /* writes to ireg 10 are copied to ireg 11 */ + ad_write(devc,10,54321); + if (ad_read(devc,11)!=54321) { + printk (KERN_INFO "ad1816: Chip is not an AD1816 (Test 3)\n"); + goto out_release_region; + } + + /* writes to ireg 10 are copied to ireg 11 */ + ad_write(devc,10,12345); + if (ad_read(devc,11)!=12345) { + printk (KERN_INFO "ad1816: Chip is not an AD1816 (Test 4)\n"); + goto out_release_region; + } + + /* bit in base +1 cannot be set to 1 */ + tmp=inb(devc->base+1); + outb(0xff,devc->base+1); + if (inb(devc->base+1)!=tmp) { + printk(KERN_INFO "ad1816: Chip is not an AD1816 (Test 5)\n"); + goto out_release_region; + } + + printk(KERN_INFO "ad1816: AD1816 (version %d) successfully detected!\n", + ad_read(devc,45)); + + /* disable all interrupts */ + ad_write(devc,1,0); + + /* Clear pending interrupts */ + outb (0, devc->base+1); + + /* allocate irq */ + if (devc->irq < 0 || devc->irq > 15) + goto out_release_region; + if (request_irq(devc->irq, ad1816_interrupt,0, + "SoundPort", devc) < 0) { + printk(KERN_WARNING "ad1816: IRQ in use\n"); + goto out_release_region; + } + + /* DMA stuff */ + if (sound_alloc_dma (devc->dma_playback, "Sound System")) { + printk(KERN_WARNING "ad1816: Can't allocate DMA%d\n", + devc->dma_playback); + goto out_free_irq; + } + + if ( devc->dma_capture >= 0 && + devc->dma_capture != devc->dma_playback) { + if (sound_alloc_dma(devc->dma_capture, + "Sound System (capture)")) { + printk(KERN_WARNING "ad1816: Can't allocate DMA%d\n", + devc->dma_capture); + goto out_free_dma; + } + devc->audio_mode=DMA_AUTOMODE|DMA_DUPLEX; + } else { + printk(KERN_WARNING "ad1816: Only one DMA channel " + "available/configured. No duplex operation possible\n"); + devc->audio_mode=DMA_AUTOMODE; + } + + conf_printf2 ("AD1816 audio driver", + devc->base, devc->irq, devc->dma_playback, + devc->dma_capture); + + /* register device */ + if ((oss_devno = sound_install_audiodrv (AUDIO_DRIVER_VERSION, + "AD1816 audio driver", + &ad1816_audio_driver, + sizeof (struct audio_driver), + devc->audio_mode, + ad_format_mask, + devc, + devc->dma_playback, + devc->dma_capture)) < 0) { + printk(KERN_WARNING "ad1816: Can't install sound driver\n"); + goto out_free_dma_2; + } + + + ad_write(devc,32,0x80f0); /* sound system mode */ + if (options&1) { + ad_write(devc,33,0); /* disable all audiosources for dsp */ + } else { + ad_write(devc,33,0x03f8); /* enable all audiosources for dsp */ + } + ad_write(devc,4,0x8080); /* default values for volumes (muted)*/ + ad_write(devc,5,0x8080); + ad_write(devc,6,0x8080); + ad_write(devc,7,0x8080); + ad_write(devc,15,0x8888); + ad_write(devc,16,0x8888); + ad_write(devc,17,0x8888); + ad_write(devc,18,0x8888); + ad_write(devc,19,0xc888); /* +20db mic active */ + ad_write(devc,14,0x0000); /* Master volume unmuted */ + ad_write(devc,39,0x009f); /* 3D effect on 0% phone out muted */ + ad_write(devc,44,0x0080); /* everything on power, 3d enabled for d/a */ + outb(0x10,devc->base+8); /* set dma mode */ + outb(0x10,devc->base+9); + + /* enable capture + playback interrupt */ + ad_write(devc,1,0xc000); + + /* set mixer defaults */ + ad1816_mixer_reset (devc); + + /* register mixer */ + if ((audio_devs[oss_devno]->mixer_dev=sound_install_mixer( + MIXER_DRIVER_VERSION, + "AD1816 audio driver", + &ad1816_mixer_operations, + sizeof (struct mixer_operations), + devc)) < 0) { + printk(KERN_WARNING "Can't install mixer\n"); + } + /* make ad1816_info active */ + nr_ad1816_devs++; + printk(KERN_INFO "ad1816: card successfully installed!\n"); + return 1; + /* error handling */ +out_free_dma_2: + if (devc->dma_capture >= 0 && devc->dma_capture != devc->dma_playback) + sound_free_dma(devc->dma_capture); +out_free_dma: + sound_free_dma(devc->dma_playback); +out_free_irq: + free_irq(devc->irq, devc); +out_release_region: + release_region(devc->base, 16); +out: + return 0; +} + +static void __exit unload_card(ad1816_info *devc) +{ + int mixer, dev = 0; + + if (devc != NULL) { + printk("ad1816: Unloading card at address 0x%03x\n",devc->base); + + dev = devc->dev_no; + mixer = audio_devs[dev]->mixer_dev; + + /* unreg mixer*/ + if(mixer>=0) { + sound_unload_mixerdev(mixer); + } + /* unreg audiodev */ + sound_unload_audiodev(dev); + + /* free dma channels */ + if (devc->dma_capture>=0 && + devc->dma_capture != devc->dma_playback) { + sound_free_dma(devc->dma_capture); + } + sound_free_dma (devc->dma_playback); + /* free irq */ + free_irq(devc->irq, devc); + /* free io */ + release_region (devc->base, 16); +#ifdef __ISAPNP__ + if (devc->pnpdev) { + pnp_disable_dev(devc->pnpdev); + pnp_device_detach(devc->pnpdev); + } +#endif + + } else + printk(KERN_WARNING "ad1816: no device/card specified\n"); +} + +static int __initdata io = -1; +static int __initdata irq = -1; +static int __initdata dma = -1; +static int __initdata dma2 = -1; + +#ifdef __ISAPNP__ +/* use isapnp for configuration */ +static int isapnp = 1; +static int isapnpjump; +module_param(isapnp, bool, 0); +module_param(isapnpjump, int, 0); +#endif + +module_param(io, int, 0); +module_param(irq, int, 0); +module_param(dma, int, 0); +module_param(dma2, int, 0); +module_param(ad1816_clockfreq, int, 0); +module_param(options, int, 0); + +#ifdef __ISAPNP__ +static struct { + unsigned short card_vendor, card_device; + unsigned short vendor; + unsigned short function; + struct ad1816_data *data; +} isapnp_ad1816_list[] __initdata = { + { ISAPNP_ANY_ID, ISAPNP_ANY_ID, + ISAPNP_VENDOR('A','D','S'), ISAPNP_FUNCTION(0x7150), + NULL }, + { ISAPNP_ANY_ID, ISAPNP_ANY_ID, + ISAPNP_VENDOR('A','D','S'), ISAPNP_FUNCTION(0x7180), + NULL }, + {0} +}; + +MODULE_DEVICE_TABLE(isapnp, isapnp_ad1816_list); + + +static void __init ad1816_config_pnp_card(struct pnp_card *card, + unsigned short vendor, + unsigned short function) +{ + struct address_info cfg; + struct pnp_dev *card_dev = pnp_find_dev(card, vendor, function, NULL); + if (!card_dev) return; + if (pnp_device_attach(card_dev) < 0) { + printk(KERN_WARNING "ad1816: Failed to attach PnP device\n"); + return; + } + if (pnp_activate_dev(card_dev) < 0) { + printk(KERN_WARNING "ad1816: Failed to activate PnP device\n"); + pnp_device_detach(card_dev); + return; + } + cfg.io_base = pnp_port_start(card_dev, 2); + cfg.irq = pnp_irq(card_dev, 0); + cfg.dma = pnp_irq(card_dev, 0); + cfg.dma2 = pnp_irq(card_dev, 1); + if (!ad1816_init_card(&cfg, card_dev)) { + pnp_disable_dev(card_dev); + pnp_device_detach(card_dev); + } +} + +static void __init ad1816_config_pnp_cards(void) +{ + int nr_pnp_cfg; + int i; + + /* Count entries in isapnp_ad1816_list */ + for (nr_pnp_cfg = 0; isapnp_ad1816_list[nr_pnp_cfg].card_vendor != 0; + nr_pnp_cfg++); + /* Check and adjust isapnpjump */ + if( isapnpjump < 0 || isapnpjump >= nr_pnp_cfg) { + printk(KERN_WARNING + "ad1816: Valid range for isapnpjump is 0-%d. " + "Adjusted to 0.\n", nr_pnp_cfg-1); + isapnpjump = 0; + } + for (i = isapnpjump; isapnp_ad1816_list[i].card_vendor != 0; i++) { + struct pnp_card *card = NULL; + /* iterate over all pnp cards */ + while ((card = pnp_find_card(isapnp_ad1816_list[i].card_vendor, + isapnp_ad1816_list[i].card_device, card))) + ad1816_config_pnp_card(card, + isapnp_ad1816_list[i].vendor, + isapnp_ad1816_list[i].function); + } +} +#endif + +/* module initialization */ +static int __init init_ad1816(void) +{ + printk(KERN_INFO "ad1816: AD1816 sounddriver " + "Copyright (C) 1998-2003 by Thorsten Knabe and " + "others\n"); +#ifdef AD1816_CLOCK + /* set ad1816_clockfreq if set during compilation */ + ad1816_clockfreq=AD1816_CLOCK; +#endif + if (ad1816_clockfreq<5000 || ad1816_clockfreq>100000) { + ad1816_clockfreq=33000; + } + +#ifdef __ISAPNP__ + /* configure PnP cards */ + if(isapnp) ad1816_config_pnp_cards(); +#endif + /* configure card by module params */ + if (io != -1 && irq != -1 && dma != -1) { + struct address_info cfg; + cfg.io_base = io; + cfg.irq = irq; + cfg.dma = dma; + cfg.dma2 = dma2; + ad1816_init_card(&cfg, NULL); + } + if (nr_ad1816_devs <= 0) + return -ENODEV; + return 0; +} + +/* module cleanup */ +static void __exit cleanup_ad1816 (void) +{ + int i; + ad1816_info *devc = NULL; + + /* remove any soundcard */ + for (i = 0; i < nr_ad1816_devs; i++) { + devc = &dev_info[i]; + unload_card(devc); + } + nr_ad1816_devs=0; + printk(KERN_INFO "ad1816: driver unloaded!\n"); +} + +module_init(init_ad1816); +module_exit(cleanup_ad1816); + +#ifndef MODULE +/* kernel command line parameter evaluation */ +static int __init setup_ad1816(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("ad1816=", setup_ad1816); +#endif +MODULE_LICENSE("GPL"); diff --git a/sound/oss/ad1848.c b/sound/oss/ad1848.c new file mode 100644 index 000000000000..4384dac3f794 --- /dev/null +++ b/sound/oss/ad1848.c @@ -0,0 +1,3159 @@ +/* + * sound/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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + + /* Power management */ + struct pm_dev *pmdev; +} 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 int ad1848_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data); + +#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 = (ad1848_port_info *) 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++; + + devc->pmdev = pm_register(PM_ISA_DEV, my_dev, ad1848_pm_callback); + if (devc->pmdev) + devc->pmdev->data = devc; + + ad1848_init_hw(devc); + + if (irq > 0) + { + devc->dev_no = my_dev; + if (request_irq(devc->irq, adintr, 0, devc->name, (void *)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) + { + if(audio_devs[dev]->portc!=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 *)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); + + if (devc->pmdev) + pm_unregister(devc->pmdev); + + 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); +} + +irqreturn_t adintr(int irq, void *dev_id, struct pt_regs *dummy) +{ + unsigned char status; + ad1848_info *devc; + int dev; + int alt_stat = 0xff; + unsigned char c930_stat = 0; + int cnt = 0; + + dev = (int)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 */ + +static int ad1848_suspend(ad1848_info *devc) +{ + unsigned long flags; + + spin_lock_irqsave(&devc->lock,flags); + + ad_mute(devc); + + spin_unlock_irqrestore(&devc->lock,flags); + return 0; +} + +static int ad1848_resume(ad1848_info *devc) +{ + int mixer_levels[32], i; + + /* Thinkpad is a bit more of PITA than normal. The BIOS tends to + restore it in a different config to the one we use. Need to + fix this somehow */ + + /* store old mixer levels */ + memcpy(mixer_levels, devc->levels, sizeof (mixer_levels)); + ad1848_init_hw(devc); + + /* restore mixer levels */ + for (i = 0; i < 32; i++) + ad1848_mixer_set(devc, devc->dev_no, mixer_levels[i]); + + if (!devc->subtype) { + static signed char interrupt_bits[12] = { -1, -1, -1, -1, -1, 0x00, -1, 0x08, -1, 0x10, 0x18, 0x20 }; + static char dma_bits[4] = { 1, 2, 0, 3 }; + unsigned long flags; + signed char bits; + char dma2_bit = 0; + + int config_port = devc->base + 0; + + bits = interrupt_bits[devc->irq]; + if (bits == -1) { + printk(KERN_ERR "MSS: Bad IRQ %d\n", devc->irq); + return -1; + } + + spin_lock_irqsave(&devc->lock,flags); + + outb((bits | 0x40), config_port); + + if (devc->dma2 != -1 && devc->dma2 != devc->dma1) + if ( (devc->dma1 == 0 && devc->dma2 == 1) || + (devc->dma1 == 1 && devc->dma2 == 0) || + (devc->dma1 == 3 && devc->dma2 == 0)) + dma2_bit = 0x04; + + outb((bits | dma_bits[devc->dma1] | dma2_bit), config_port); + spin_unlock_irqrestore(&devc->lock,flags); + } + + return 0; +} + +static int ad1848_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data) +{ + ad1848_info *devc = dev->data; + if (devc) { + DEB(printk("ad1848: pm event received: 0x%x\n", rqst)); + + switch (rqst) { + case PM_SUSPEND: + ad1848_suspend(devc); + break; + case PM_RESUME: + ad1848_resume(devc); + break; + } + } + return 0; +} + + +EXPORT_SYMBOL(ad1848_detect); +EXPORT_SYMBOL(ad1848_init); +EXPORT_SYMBOL(ad1848_unload); +EXPORT_SYMBOL(ad1848_control); +EXPORT_SYMBOL(adintr); +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 *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 000000000000..d0573b023973 --- /dev/null +++ b/sound/oss/ad1848.h @@ -0,0 +1,25 @@ + +#include + +#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); + +irqreturn_t adintr(int irq, void *dev_id, struct pt_regs * dummy); +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 000000000000..f9231c6cd4e1 --- /dev/null +++ b/sound/oss/ad1848_mixer.h @@ -0,0 +1,253 @@ +/* + * sound/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/ad1889.c b/sound/oss/ad1889.c new file mode 100644 index 000000000000..b767c621fd09 --- /dev/null +++ b/sound/oss/ad1889.c @@ -0,0 +1,1103 @@ +/* + * Copyright 2001-2004 Randolph Chung + * + * Analog Devices 1889 PCI audio driver (AD1819 AC97-compatible codec) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Notes: + * 1. Only flat DMA is supported; s-g is not supported right now + * + * + tausq: Anyway, to set up sample rates for D to A, you just use the sample rate on the codec. For A to D, you need to set the codec always to 48K (using the split sample rate feature on the codec) and then set the resampler on the AD1889 to the sample rate you want. + Also, when changing the sample rate on the codec you need to power it down and re power it up for the change to take effect! + * + * $Id: ad1889.c,v 1.3 2002/10/19 21:31:44 grundler Exp $ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ad1889.h" + +#define DBG(fmt, arg...) printk(fmt, ##arg) +#define DEVNAME "ad1889" + +#define NR_HW_CH 4 +#define DAC_RUNNING 1 +#define ADC_RUNNING 2 + +#define UNDERRUN(dev) (0) + +#define AD1889_READW(dev,reg) readw(dev->regbase + reg) +#define AD1889_WRITEW(dev,reg,val) writew((val), dev->regbase + reg) +#define AD1889_READL(dev,reg) readl(dev->regbase + reg) +#define AD1889_WRITEL(dev,reg,val) writel((val), dev->regbase + reg) + +//now 100ms +/* #define WAIT_10MS() schedule_timeout(HZ/10) */ +#define WAIT_10MS() do { int __i; for (__i = 0; __i < 100; __i++) udelay(1000); } while(0) + +/* currently only support a single device */ +static ad1889_dev_t *ad1889_dev = NULL; + +/************************* helper routines ***************************** */ +static inline void ad1889_set_wav_rate(ad1889_dev_t *dev, int rate) +{ + struct ac97_codec *ac97_codec = dev->ac97_codec; + + DBG("Setting WAV rate to %d\n", rate); + dev->state[AD_WAV_STATE].dmabuf.rate = rate; + AD1889_WRITEW(dev, AD_DSWAS, rate); + + /* Cycle the DAC to enable the new rate */ + ac97_codec->codec_write(dev->ac97_codec, AC97_POWER_CONTROL, 0x0200); + WAIT_10MS(); + ac97_codec->codec_write(dev->ac97_codec, AC97_POWER_CONTROL, 0); +} + +static inline void ad1889_set_wav_fmt(ad1889_dev_t *dev, int fmt) +{ + u16 tmp; + + DBG("Setting WAV format to 0x%x\n", fmt); + + tmp = AD1889_READW(ad1889_dev, AD_DSWSMC); + if (fmt & AFMT_S16_LE) { + //tmp |= 0x0100; /* set WA16 */ + tmp |= 0x0300; /* set WA16 stereo */ + } else if (fmt & AFMT_U8) { + tmp &= ~0x0100; /* clear WA16 */ + } + AD1889_WRITEW(ad1889_dev, AD_DSWSMC, tmp); +} + +static inline void ad1889_set_adc_fmt(ad1889_dev_t *dev, int fmt) +{ + u16 tmp; + + DBG("Setting ADC format to 0x%x\n", fmt); + + tmp = AD1889_READW(ad1889_dev, AD_DSRAMC); + if (fmt & AFMT_S16_LE) { + tmp |= 0x0100; /* set WA16 */ + } else if (fmt & AFMT_U8) { + tmp &= ~0x0100; /* clear WA16 */ + } + AD1889_WRITEW(ad1889_dev, AD_DSRAMC, tmp); +} + +static void ad1889_start_wav(ad1889_state_t *state) +{ + unsigned long flags; + struct dmabuf *dmabuf = &state->dmabuf; + int cnt; + u16 tmp; + + spin_lock_irqsave(&state->card->lock, flags); + + if (dmabuf->dma_len) /* DMA already in flight */ + goto skip_dma; + + /* setup dma */ + cnt = dmabuf->wr_ptr - dmabuf->rd_ptr; + if (cnt == 0) /* done - don't need to do anything */ + goto skip_dma; + + /* If the wr_ptr has wrapped, only map to the end */ + if (cnt < 0) + cnt = DMA_SIZE - dmabuf->rd_ptr; + + dmabuf->dma_handle = pci_map_single(ad1889_dev->pci, + dmabuf->rawbuf + dmabuf->rd_ptr, + cnt, PCI_DMA_TODEVICE); + dmabuf->dma_len = cnt; + dmabuf->ready = 1; + + DBG("Starting playback at 0x%p for %ld bytes\n", dmabuf->rawbuf + + dmabuf->rd_ptr, dmabuf->dma_len); + + /* load up the current register set */ + AD1889_WRITEL(ad1889_dev, AD_DMAWAVCC, cnt); + AD1889_WRITEL(ad1889_dev, AD_DMAWAVICC, cnt); + AD1889_WRITEL(ad1889_dev, AD_DMAWAVCA, dmabuf->dma_handle); + + /* TODO: for now we load the base registers with the same thing */ + AD1889_WRITEL(ad1889_dev, AD_DMAWAVBC, cnt); + AD1889_WRITEL(ad1889_dev, AD_DMAWAVIBC, cnt); + AD1889_WRITEL(ad1889_dev, AD_DMAWAVBA, dmabuf->dma_handle); + + /* and we're off to the races... */ + AD1889_WRITEL(ad1889_dev, AD_DMACHSS, 0x8); + tmp = AD1889_READW(ad1889_dev, AD_DSWSMC); + tmp |= 0x0400; /* set WAEN */ + AD1889_WRITEW(ad1889_dev, AD_DSWSMC, tmp); + (void) AD1889_READW(ad1889_dev, AD_DSWSMC); /* flush posted PCI write */ + + dmabuf->enable |= DAC_RUNNING; + +skip_dma: + spin_unlock_irqrestore(&state->card->lock, flags); +} + + +static void ad1889_stop_wav(ad1889_state_t *state) +{ + unsigned long flags; + struct dmabuf *dmabuf = &state->dmabuf; + + spin_lock_irqsave(&state->card->lock, flags); + + if (dmabuf->enable & DAC_RUNNING) { + u16 tmp; + unsigned long cnt = dmabuf->dma_len; + + tmp = AD1889_READW(ad1889_dev, AD_DSWSMC); + tmp &= ~0x0400; /* clear WAEN */ + AD1889_WRITEW(ad1889_dev, AD_DSWSMC, tmp); + (void) AD1889_READW(ad1889_dev, AD_DSWSMC); /* flush posted PCI write */ + pci_unmap_single(ad1889_dev->pci, dmabuf->dma_handle, + cnt, PCI_DMA_TODEVICE); + + dmabuf->enable &= ~DAC_RUNNING; + + /* update dma pointers */ + dmabuf->rd_ptr += cnt; + dmabuf->rd_ptr &= (DMA_SIZE - 1); + + dmabuf->dma_handle = 0; + dmabuf->dma_len = 0; + dmabuf->ready = 0; + + wake_up(&dmabuf->wait); + } + + spin_unlock_irqrestore(&state->card->lock, flags); +} + + +#if 0 +static void ad1889_startstop_adc(ad1889_state_t *state, int start) +{ + u16 tmp; + unsigned long flags; + + spin_lock_irqsave(&state->card->lock, flags); + + tmp = AD1889_READW(ad1889_dev, AD_DSRAMC); + if (start) { + state->dmabuf.enable |= ADC_RUNNING; + tmp |= 0x0004; /* set ADEN */ + } else { + state->dmabuf.enable &= ~ADC_RUNNING; + tmp &= ~0x0004; /* clear ADEN */ + } + AD1889_WRITEW(ad1889_dev, AD_DSRAMC, tmp); + + spin_unlock_irqrestore(&state->card->lock, flags); +} +#endif + +static ad1889_dev_t *ad1889_alloc_dev(struct pci_dev *pci) +{ + ad1889_dev_t *dev; + struct dmabuf *dmabuf; + int i; + + if ((dev = kmalloc(sizeof(ad1889_dev_t), GFP_KERNEL)) == NULL) + return NULL; + memset(dev, 0, sizeof(ad1889_dev_t)); + spin_lock_init(&dev->lock); + dev->pci = pci; + + for (i = 0; i < AD_MAX_STATES; i++) { + dev->state[i].card = dev; + init_MUTEX(&dev->state[i].sem); + init_waitqueue_head(&dev->state[i].dmabuf.wait); + } + + /* allocate dma buffer */ + + for (i = 0; i < AD_MAX_STATES; i++) { + dmabuf = &dev->state[i].dmabuf; + dmabuf->rawbuf = kmalloc(DMA_SIZE, GFP_KERNEL|GFP_DMA); + if (!dmabuf->rawbuf) + goto err_free_dmabuf; + dmabuf->rawbuf_size = DMA_SIZE; + dmabuf->dma_handle = 0; + dmabuf->rd_ptr = dmabuf->wr_ptr = dmabuf->dma_len = 0UL; + dmabuf->ready = 0; + dmabuf->rate = 48000; + } + return dev; + +err_free_dmabuf: + while (--i >= 0) + kfree(dev->state[i].dmabuf.rawbuf); + kfree(dev); + return NULL; +} + +static void ad1889_free_dev(ad1889_dev_t *dev) +{ + int j; + struct dmabuf *dmabuf; + + if (dev == NULL) + return; + + if (dev->ac97_codec) + ac97_release_codec(dev->ac97_codec); + + for (j = 0; j < AD_MAX_STATES; j++) { + dmabuf = &dev->state[j].dmabuf; + if (dmabuf->rawbuf != NULL) + kfree(dmabuf->rawbuf); + } + + kfree(dev); +} + +static inline void ad1889_trigger_playback(ad1889_dev_t *dev) +{ +#if 0 + u32 val; + struct dmabuf *dmabuf = &dev->state[AD_WAV_STATE].dmabuf; +#endif + + ad1889_start_wav(&dev->state[AD_WAV_STATE]); +} + +static int ad1889_read_proc (char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char *out = page; + int len, i; + ad1889_dev_t *dev = data; + ad1889_reg_t regs[] = { + { "WSMC", AD_DSWSMC, 16 }, + { "RAMC", AD_DSRAMC, 16 }, + { "WADA", AD_DSWADA, 16 }, + { "SYDA", AD_DSSYDA, 16 }, + { "WAS", AD_DSWAS, 16 }, + { "RES", AD_DSRES, 16 }, + { "CCS", AD_DSCCS, 16 }, + { "ADCBA", AD_DMAADCBA, 32 }, + { "ADCCA", AD_DMAADCCA, 32 }, + { "ADCBC", AD_DMAADCBC, 32 }, + { "ADCCC", AD_DMAADCCC, 32 }, + { "ADCIBC", AD_DMAADCIBC, 32 }, + { "ADCICC", AD_DMAADCICC, 32 }, + { "ADCCTRL", AD_DMAADCCTRL, 16 }, + { "WAVBA", AD_DMAWAVBA, 32 }, + { "WAVCA", AD_DMAWAVCA, 32 }, + { "WAVBC", AD_DMAWAVBC, 32 }, + { "WAVCC", AD_DMAWAVCC, 32 }, + { "WAVIBC", AD_DMAWAVIBC, 32 }, + { "WAVICC", AD_DMAWAVICC, 32 }, + { "WAVCTRL", AD_DMAWAVCTRL, 16 }, + { "DISR", AD_DMADISR, 32 }, + { "CHSS", AD_DMACHSS, 32 }, + { "IPC", AD_GPIOIPC, 16 }, + { "OP", AD_GPIOOP, 16 }, + { "IP", AD_GPIOIP, 16 }, + { "ACIC", AD_ACIC, 16 }, + { "AC97_RESET", 0x100 + AC97_RESET, 16 }, + { "AC97_MASTER_VOL_STEREO", 0x100 + AC97_MASTER_VOL_STEREO, 16 }, + { "AC97_HEADPHONE_VOL", 0x100 + AC97_HEADPHONE_VOL, 16 }, + { "AC97_MASTER_VOL_MONO", 0x100 + AC97_MASTER_VOL_MONO, 16 }, + { "AC97_MASTER_TONE", 0x100 + AC97_MASTER_TONE, 16 }, + { "AC97_PCBEEP_VOL", 0x100 + AC97_PCBEEP_VOL, 16 }, + { "AC97_PHONE_VOL", 0x100 + AC97_PHONE_VOL, 16 }, + { "AC97_MIC_VOL", 0x100 + AC97_MIC_VOL, 16 }, + { "AC97_LINEIN_VOL", 0x100 + AC97_LINEIN_VOL, 16 }, + { "AC97_CD_VOL", 0x100 + AC97_CD_VOL, 16 }, + { "AC97_VIDEO_VOL", 0x100 + AC97_VIDEO_VOL, 16 }, + { "AC97_AUX_VOL", 0x100 + AC97_AUX_VOL, 16 }, + { "AC97_PCMOUT_VOL", 0x100 + AC97_PCMOUT_VOL, 16 }, + { "AC97_RECORD_SELECT", 0x100 + AC97_RECORD_SELECT, 16 }, + { "AC97_RECORD_GAIN", 0x100 + AC97_RECORD_GAIN, 16 }, + { "AC97_RECORD_GAIN_MIC", 0x100 + AC97_RECORD_GAIN_MIC, 16 }, + { "AC97_GENERAL_PURPOSE", 0x100 + AC97_GENERAL_PURPOSE, 16 }, + { "AC97_3D_CONTROL", 0x100 + AC97_3D_CONTROL, 16 }, + { "AC97_MODEM_RATE", 0x100 + AC97_MODEM_RATE, 16 }, + { "AC97_POWER_CONTROL", 0x100 + AC97_POWER_CONTROL, 16 }, + { NULL } + }; + + if (dev == NULL) + return -ENODEV; + + for (i = 0; regs[i].name != 0; i++) + out += sprintf(out, "%s: 0x%0*x\n", regs[i].name, + regs[i].width >> 2, + (regs[i].width == 16 + ? AD1889_READW(dev, regs[i].offset) + : AD1889_READL(dev, regs[i].offset))); + + for (i = 0; i < AD_MAX_STATES; i++) { + out += sprintf(out, "DMA status for %s:\n", + (i == AD_WAV_STATE ? "WAV" : "ADC")); + out += sprintf(out, "\t\t0x%p (IOVA: 0x%llu)\n", + dev->state[i].dmabuf.rawbuf, + (unsigned long long)dev->state[i].dmabuf.dma_handle); + + out += sprintf(out, "\tread ptr: offset %u\n", + (unsigned int)dev->state[i].dmabuf.rd_ptr); + out += sprintf(out, "\twrite ptr: offset %u\n", + (unsigned int)dev->state[i].dmabuf.wr_ptr); + out += sprintf(out, "\tdma len: offset %u\n", + (unsigned int)dev->state[i].dmabuf.dma_len); + } + + len = out - page - off; + if (len < count) { + *eof = 1; + if (len <= 0) return 0; + } else { + len = count; + } + *start = page + off; + return len; +} + +/***************************** DMA interfaces ************************** */ +#if 0 +static inline unsigned long ad1889_get_dma_addr(ad1889_state_t *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + u32 offset; + + if (!(dmabuf->enable & (DAC_RUNNING | ADC_RUNNING))) { + printk(KERN_ERR DEVNAME ": get_dma_addr called without dma enabled\n"); + return 0; + } + + if (dmabuf->enable & DAC_RUNNING) + offset = le32_to_cpu(AD1889_READL(state->card, AD_DMAWAVBA)); + else + offset = le32_to_cpu(AD1889_READL(state->card, AD_DMAADCBA)); + + return (unsigned long)bus_to_virt((unsigned long)offset) - (unsigned long)dmabuf->rawbuf; +} + +static void ad1889_update_ptr(ad1889_dev_t *dev, int wake) +{ + ad1889_state_t *state; + struct dmabuf *dmabuf; + unsigned long hwptr; + int diff; + + /* check ADC first */ + state = &dev->adc_state; + dmabuf = &state->dmabuf; + if (dmabuf->enable & ADC_RUNNING) { + hwptr = ad1889_get_dma_addr(state); + diff = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize; + + dmabuf->hwptr = hwptr; + dmabuf->total_bytes += diff; + dmabuf->count += diff; + if (dmabuf->count > dmabuf->dmasize) + dmabuf->count = dmabuf->dmasize; + + if (dmabuf->mapped) { + if (wake & dmabuf->count >= dmabuf->fragsize) + wake_up(&dmabuf->wait); + } else { + if (wake & dmabuf->count > 0) + wake_up(&dmabuf->wait); + } + } + + /* check DAC */ + state = &dev->wav_state; + dmabuf = &state->dmabuf; + if (dmabuf->enable & DAC_RUNNING) { +XXX + +} +#endif + +/************************* /dev/dsp interfaces ************************* */ + +static ssize_t ad1889_read(struct file *file, char __user *buffer, size_t count, + loff_t *ppos) +{ + return 0; +} + +static ssize_t ad1889_write(struct file *file, const char __user *buffer, size_t count, + loff_t *ppos) +{ + ad1889_dev_t *dev = (ad1889_dev_t *)file->private_data; + ad1889_state_t *state = &dev->state[AD_WAV_STATE]; + volatile struct dmabuf *dmabuf = &state->dmabuf; + ssize_t ret = 0; + DECLARE_WAITQUEUE(wait, current); + + down(&state->sem); +#if 0 + if (dmabuf->mapped) { + ret = -ENXIO; + goto err1; + } +#endif + if (!access_ok(VERIFY_READ, buffer, count)) { + ret = -EFAULT; + goto err1; + } + + add_wait_queue(&state->dmabuf.wait, &wait); + + /* start filling dma buffer.... */ + while (count > 0) { + long rem; + long cnt = count; + unsigned long flags; + + for (;;) { + long used_bytes; + long timeout; /* max time for DMA in jiffies */ + + /* buffer is full if wr catches up to rd */ + spin_lock_irqsave(&state->card->lock, flags); + used_bytes = dmabuf->wr_ptr - dmabuf->rd_ptr; + timeout = (dmabuf->dma_len * HZ) / dmabuf->rate; + spin_unlock_irqrestore(&state->card->lock, flags); + + /* adjust for buffer wrap around */ + used_bytes = (used_bytes + DMA_SIZE) & (DMA_SIZE - 1); + + /* If at least one page unused */ + if (used_bytes < (DMA_SIZE - 0x1000)) + break; + + /* dma buffer full */ + + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + goto err2; + } + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(timeout + 1); + if (signal_pending(current)) { + ret = -ERESTARTSYS; + goto err2; + } + } + + /* watch out for wrapping around static buffer */ + spin_lock_irqsave(&state->card->lock, flags); + rem = DMA_SIZE - dmabuf->wr_ptr; + if (cnt > rem) + cnt = rem; + + rem = dmabuf->wr_ptr; + + /* update dma pointers */ + dmabuf->wr_ptr += cnt; + dmabuf->wr_ptr &= DMA_SIZE - 1; /* wrap ptr if necessary */ + spin_unlock_irqrestore(&state->card->lock, flags); + + /* transfer unwrapped chunk */ + if (copy_from_user(dmabuf->rawbuf + rem, buffer, cnt)) { + ret = -EFAULT; + goto err2; + } + + DBG("Writing 0x%lx bytes to +0x%lx\n", cnt, rem); + + /* update counters */ + count -= cnt; + buffer += cnt; + ret += cnt; + + /* we have something to play - go play it! */ + ad1889_trigger_playback(dev); + } + +err2: + remove_wait_queue(&state->dmabuf.wait, &wait); +err1: + up(&state->sem); + return ret; +} + +static unsigned int ad1889_poll(struct file *file, struct poll_table_struct *wait) +{ + unsigned int mask = 0; +#if 0 + ad1889_dev_t *dev = (ad1889_dev_t *)file->private_data; + ad1889_state_t *state = NULL; + struct dmabuf *dmabuf; + unsigned long flags; + + if (!(file->f_mode & (FMODE_READ | FMODE_WRITE))) + return -EINVAL; + + if (file->f_mode & FMODE_WRITE) { + state = &dev->state[AD_WAV_STATE]; + if (!state) return 0; + dmabuf = &state->dmabuf; + poll_wait(file, &dmabuf->wait, wait); + } + + if (file->f_mode & FMODE_READ) { + state = &dev->state[AD_ADC_STATE]; + if (!state) return 0; + dmabuf = &state->dmabuf; + poll_wait(file, &dmabuf->wait, wait); + } + + spin_lock_irqsave(&dev->lock, flags); + ad1889_update_ptr(dev, 0); + + if (file->f_mode & FMODE_WRITE) { + state = &dev->state[WAV_STATE]; + dmabuf = &state->dmabuf; + if (dmabuf->mapped) { + if (dmabuf->count >= (int)dmabuf->fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((int)dmabuf->dmasize >= dmabuf->count + + (int)dmabuf->fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + + if (file ->f_mode & FMODE_READ) { + state = &dev->state[AD_ADC_STATE]; + dmabuf = &state->dmabuf; + if (dmabuf->count >= (int)dmabuf->fragsize) + mask |= POLLIN | POLLRDNORM; + } + spin_unlock_irqrestore(&dev->lock, flags); + +#endif + return mask; +} + +static int ad1889_mmap(struct file *file, struct vm_area_struct *vma) +{ + return 0; +} + +static int ad1889_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + int val = 0; + ad1889_dev_t *dev = (ad1889_dev_t *)file->private_data; + struct dmabuf *dmabuf; + audio_buf_info abinfo; + int __user *p = (int __user *)arg; + + DBG("ad1889_ioctl cmd 0x%x arg %lu\n", cmd, arg); + + switch (cmd) + { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_RESET: + break; + + case SNDCTL_DSP_SYNC: + break; + + case SNDCTL_DSP_SPEED: + /* set sampling rate */ + if (get_user(val, p)) + return -EFAULT; + if (val > 5400 && val < 48000) + { + if (file->f_mode & FMODE_WRITE) + AD1889_WRITEW(ad1889_dev, AD_DSWAS, val); + if (file->f_mode & FMODE_READ) + AD1889_WRITEW(ad1889_dev, AD_DSRES, val); + } + return 0; + + case SNDCTL_DSP_STEREO: /* undocumented? */ + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + val = AD1889_READW(ad1889_dev, AD_DSWSMC); + if (val) { + val |= 0x0200; /* set WAST */ + } else { + val &= ~0x0200; /* clear WAST */ + } + AD1889_WRITEW(ad1889_dev, AD_DSWSMC, val); + } + if (file->f_mode & FMODE_WRITE) { + val = AD1889_READW(ad1889_dev, AD_DSRAMC); + if (val) { + val |= 0x0002; /* set ADST */ + } else { + val &= ~0x0002; /* clear ADST */ + } + AD1889_WRITEW(ad1889_dev, AD_DSRAMC, val); + } + + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + return put_user(DMA_SIZE, p); + + case SNDCTL_DSP_GETFMTS: + return put_user(AFMT_S16_LE|AFMT_U8, p); + + case SNDCTL_DSP_SETFMT: + if (get_user(val, p)) + return -EFAULT; + + if (val == 0) { + if (file->f_mode & FMODE_READ) + ad1889_set_adc_fmt(dev, val); + + if (file->f_mode & FMODE_WRITE) + ad1889_set_wav_fmt(dev, val); + } else { + val = AFMT_S16_LE | AFMT_U8; + } + + return put_user(val, p); + + case SNDCTL_DSP_CHANNELS: + break; + + case SNDCTL_DSP_POST: + /* send all data to device */ + break; + + case SNDCTL_DSP_SUBDIVIDE: + break; + + case SNDCTL_DSP_SETFRAGMENT: + /* not supported; uses fixed fragment sizes */ + return put_user(DMA_SIZE, p); + + case SNDCTL_DSP_GETOSPACE: + case SNDCTL_DSP_GETISPACE: + /* space left in dma buffers */ + if (cmd == SNDCTL_DSP_GETOSPACE) + dmabuf = &dev->state[AD_WAV_STATE].dmabuf; + else + dmabuf = &dev->state[AD_ADC_STATE].dmabuf; + abinfo.fragments = 1; + abinfo.fragstotal = 1; + abinfo.fragsize = DMA_SIZE; + abinfo.bytes = DMA_SIZE; + return copy_to_user(p, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETCAPS: + return put_user(0, p); + + case SNDCTL_DSP_GETTRIGGER: + case SNDCTL_DSP_SETTRIGGER: + break; + + case SNDCTL_DSP_GETIPTR: + case SNDCTL_DSP_GETOPTR: + break; + + case SNDCTL_DSP_SETDUPLEX: + break; + + case SNDCTL_DSP_GETODELAY: + break; + + case SOUND_PCM_READ_RATE: + return put_user(AD1889_READW(ad1889_dev, AD_DSWAS), p); + + case SOUND_PCM_READ_CHANNELS: + case SOUND_PCM_READ_BITS: + break; + + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + break; + + default: + break; + } + + return -ENOTTY; +} + +static int ad1889_open(struct inode *inode, struct file *file) +{ + /* check minor; only support /dev/dsp atm */ + if (iminor(inode) != 3) + return -ENXIO; + + file->private_data = ad1889_dev; + + ad1889_set_wav_rate(ad1889_dev, 48000); + ad1889_set_wav_fmt(ad1889_dev, AFMT_S16_LE); + AD1889_WRITEW(ad1889_dev, AD_DSWADA, 0x0404); /* attenuation */ + return nonseekable_open(inode, file); +} + +static int ad1889_release(struct inode *inode, struct file *file) +{ + /* if we have state free it here */ + return 0; +} + +static struct file_operations ad1889_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = ad1889_read, + .write = ad1889_write, + .poll = ad1889_poll, + .ioctl = ad1889_ioctl, + .mmap = ad1889_mmap, + .open = ad1889_open, + .release = ad1889_release, +}; + +/************************* /dev/mixer interfaces ************************ */ +static int ad1889_mixer_open(struct inode *inode, struct file *file) +{ + if (ad1889_dev->ac97_codec->dev_mixer != iminor(inode)) + return -ENODEV; + + file->private_data = ad1889_dev->ac97_codec; + return 0; +} + +static int ad1889_mixer_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int ad1889_mixer_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct ac97_codec *codec = (struct ac97_codec *)file->private_data; + return codec->mixer_ioctl(codec, cmd, arg); +} + +static struct file_operations ad1889_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = ad1889_mixer_ioctl, + .open = ad1889_mixer_open, + .release = ad1889_mixer_release, +}; + +/************************* AC97 interfaces ****************************** */ +static void ad1889_codec_write(struct ac97_codec *ac97, u8 reg, u16 val) +{ + ad1889_dev_t *dev = ac97->private_data; + + //DBG("Writing 0x%x to 0x%lx\n", val, dev->regbase + 0x100 + reg); + AD1889_WRITEW(dev, 0x100 + reg, val); +} + +static u16 ad1889_codec_read(struct ac97_codec *ac97, u8 reg) +{ + ad1889_dev_t *dev = ac97->private_data; + //DBG("Reading from 0x%lx\n", dev->regbase + 0x100 + reg); + return AD1889_READW(dev, 0x100 + reg); +} + +static int ad1889_ac97_init(ad1889_dev_t *dev, int id) +{ + struct ac97_codec *ac97; + u16 eid; + + if ((ac97 = ac97_alloc_codec()) == NULL) + return -ENOMEM; + + ac97->private_data = dev; + ac97->id = id; + + ac97->codec_read = ad1889_codec_read; + ac97->codec_write = ad1889_codec_write; + + if (ac97_probe_codec(ac97) == 0) { + printk(DEVNAME ": ac97_probe_codec failed\n"); + goto out_free; + } + + eid = ad1889_codec_read(ac97, AC97_EXTENDED_ID); + if (eid == 0xffff) { + printk(KERN_WARNING DEVNAME ": no codec attached?\n"); + goto out_free; + } + + dev->ac97_features = eid; + + if ((ac97->dev_mixer = register_sound_mixer(&ad1889_mixer_fops, -1)) < 0) { + printk(KERN_ERR DEVNAME ": cannot register mixer\n"); + goto out_free; + } + + dev->ac97_codec = ac97; + return 0; + +out_free: + ac97_release_codec(ac97); + return -ENODEV; +} + +static int ad1889_aclink_reset(struct pci_dev * pcidev) +{ + u16 stat; + int retry = 200; + ad1889_dev_t *dev = pci_get_drvdata(pcidev); + + AD1889_WRITEW(dev, AD_DSCCS, 0x8000); /* turn on clock */ + AD1889_READW(dev, AD_DSCCS); + + WAIT_10MS(); + + stat = AD1889_READW(dev, AD_ACIC); + stat |= 0x0002; /* Reset Disable */ + AD1889_WRITEW(dev, AD_ACIC, stat); + (void) AD1889_READW(dev, AD_ACIC); /* flush posted write */ + + udelay(10); + + stat = AD1889_READW(dev, AD_ACIC); + stat |= 0x0001; /* Interface Enable */ + AD1889_WRITEW(dev, AD_ACIC, stat); + + do { + if (AD1889_READW(dev, AD_ACIC) & 0x8000) /* Ready */ + break; + WAIT_10MS(); + retry--; + } while (retry > 0); + + if (!retry) { + printk(KERN_ERR "ad1889_aclink_reset: codec is not ready [0x%x]\n", + AD1889_READW(dev, AD_ACIC)); + return -EBUSY; + } + + /* TODO reset AC97 codec */ + /* TODO set wave/adc pci ctrl status */ + + stat = AD1889_READW(dev, AD_ACIC); + stat |= 0x0004; /* Audio Stream Output Enable */ + AD1889_WRITEW(dev, AD_ACIC, stat); + return 0; +} + +/************************* PCI interfaces ****************************** */ +/* PCI device table */ +static struct pci_device_id ad1889_id_tbl[] = { + { PCI_VENDOR_ID_ANALOG_DEVICES, PCI_DEVICE_ID_AD1889JS, PCI_ANY_ID, + PCI_ANY_ID, 0, 0, (unsigned long)DEVNAME }, + { }, +}; +MODULE_DEVICE_TABLE(pci, ad1889_id_tbl); + +static irqreturn_t ad1889_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + u32 stat; + ad1889_dev_t *dev = (ad1889_dev_t *)dev_id; + + stat = AD1889_READL(dev, AD_DMADISR); + + /* clear ISR */ + AD1889_WRITEL(dev, AD_DMADISR, stat); + + if (stat & 0x8) { /* WAVI */ + DBG("WAV interrupt\n"); + dev->stats.wav_intrs++; + if (dev->state[AD_WAV_STATE].dmabuf.ready) { + ad1889_stop_wav(&dev->state[AD_WAV_STATE]); /* clean up */ + ad1889_start_wav(&dev->state[AD_WAV_STATE]); /* start new */ + } + } + + if ((stat & 0x2) && dev->state[AD_ADC_STATE].dmabuf.ready) { /* ADCI */ + DBG("ADC interrupt\n"); + dev->stats.adc_intrs++; + } + if(stat) + return IRQ_HANDLED; + return IRQ_NONE; +} + +static void ad1889_initcfg(ad1889_dev_t *dev) +{ + u16 tmp16; + u32 tmp32; + + /* make sure the interrupt bits are setup the way we want */ + tmp32 = AD1889_READL(dev, AD_DMAWAVCTRL); + tmp32 &= ~0xff; /* flat dma, no sg, mask out the intr bits */ + tmp32 |= 0x6; /* intr on count, loop */ + AD1889_WRITEL(dev, AD_DMAWAVCTRL, tmp32); + + /* unmute... */ + tmp16 = AD1889_READW(dev, AD_DSWADA); + tmp16 &= ~0x8080; + AD1889_WRITEW(dev, AD_DSWADA, tmp16); +} + +static int __devinit ad1889_probe(struct pci_dev *pcidev, const struct pci_device_id *ent) +{ + int err; + ad1889_dev_t *dev; + unsigned long bar; + struct proc_dir_entry *proc_root = NULL; + + if ((err = pci_enable_device(pcidev)) != 0) { + printk(KERN_ERR DEVNAME ": pci_enable_device failed\n"); + return err; + } + + pci_set_master(pcidev); + if ((dev = ad1889_alloc_dev(pcidev)) == NULL) { + printk(KERN_ERR DEVNAME ": cannot allocate memory for device\n"); + return -ENOMEM; + } + pci_set_drvdata(pcidev, dev); + bar = pci_resource_start(pcidev, 0); + + if (!(pci_resource_flags(pcidev, 0) & IORESOURCE_MEM)) { + printk(KERN_ERR DEVNAME ": memory region not assigned\n"); + goto out1; + } + + if (pci_request_region(pcidev, 0, DEVNAME)) { + printk(KERN_ERR DEVNAME ": unable to request memory region\n"); + goto out1; + } + + dev->regbase = ioremap_nocache(bar, AD_DSIOMEMSIZE); + if (!dev->regbase) { + printk(KERN_ERR DEVNAME ": unable to remap iomem\n"); + goto out2; + } + + if (request_irq(pcidev->irq, ad1889_interrupt, SA_SHIRQ, DEVNAME, dev) != 0) { + printk(KERN_ERR DEVNAME ": unable to request interrupt\n"); + goto out3; + } + + printk(KERN_INFO DEVNAME ": %s at %p IRQ %d\n", + (char *)ent->driver_data, dev->regbase, pcidev->irq); + + if (ad1889_aclink_reset(pcidev) != 0) + goto out4; + + /* register /dev/dsp */ + if ((dev->dev_audio = register_sound_dsp(&ad1889_fops, -1)) < 0) { + printk(KERN_ERR DEVNAME ": cannot register /dev/dsp\n"); + goto out4; + } + + if ((err = ad1889_ac97_init(dev, 0)) != 0) + goto out5; + + /* XXX: cleanups */ + if (((proc_root = proc_mkdir("driver/ad1889", NULL)) == NULL) || + create_proc_read_entry("ac97", S_IFREG|S_IRUGO, proc_root, ac97_read_proc, dev->ac97_codec) == NULL || + create_proc_read_entry("info", S_IFREG|S_IRUGO, proc_root, ad1889_read_proc, dev) == NULL) + goto out5; + + ad1889_initcfg(dev); + + //DBG(DEVNAME ": Driver initialization done!\n"); + + ad1889_dev = dev; + + return 0; + +out5: + unregister_sound_dsp(dev->dev_audio); +out4: + free_irq(pcidev->irq, dev); +out3: + iounmap(dev->regbase); +out2: + pci_release_region(pcidev, 0); +out1: + ad1889_free_dev(dev); + pci_set_drvdata(pcidev, NULL); + + return -ENODEV; +} + +static void __devexit ad1889_remove(struct pci_dev *pcidev) +{ + ad1889_dev_t *dev = pci_get_drvdata(pcidev); + + if (dev == NULL) return; + + unregister_sound_mixer(dev->ac97_codec->dev_mixer); + unregister_sound_dsp(dev->dev_audio); + free_irq(pcidev->irq, dev); + iounmap(dev->regbase); + pci_release_region(pcidev, 0); + + /* any hw programming needed? */ + ad1889_free_dev(dev); + pci_set_drvdata(pcidev, NULL); +} + +MODULE_AUTHOR("Randolph Chung"); +MODULE_DESCRIPTION("Analog Devices AD1889 PCI Audio"); +MODULE_LICENSE("GPL"); + +static struct pci_driver ad1889_driver = { + .name = DEVNAME, + .id_table = ad1889_id_tbl, + .probe = ad1889_probe, + .remove = __devexit_p(ad1889_remove), +}; + +static int __init ad1889_init_module(void) +{ + return pci_module_init(&ad1889_driver); +} + +static void ad1889_exit_module(void) +{ + pci_unregister_driver(&ad1889_driver); + return; +} + +module_init(ad1889_init_module); +module_exit(ad1889_exit_module); diff --git a/sound/oss/ad1889.h b/sound/oss/ad1889.h new file mode 100644 index 000000000000..e04affce1dd1 --- /dev/null +++ b/sound/oss/ad1889.h @@ -0,0 +1,134 @@ +#ifndef _AD1889_H_ +#define _AD1889_H_ + +#define AD_DSWSMC 0x00 /* DMA input wave/syn mixer control */ +#define AD_DSRAMC 0x02 /* DMA output resamp/ADC mixer control */ +#define AD_DSWADA 0x04 /* DMA input wave attenuation */ +#define AD_DSSYDA 0x06 /* DMA input syn attentuation */ +#define AD_DSWAS 0x08 /* wave input sample rate */ +#define AD_DSRES 0x0a /* resampler output sample rate */ +#define AD_DSCCS 0x0c /* chip control/status */ + +#define AD_DMARESBA 0x40 /* RES base addr */ +#define AD_DMARESCA 0x44 /* RES current addr */ +#define AD_DMARESBC 0x48 /* RES base cnt */ +#define AD_DMARESCC 0x4c /* RES current count */ +#define AD_DMAADCBA 0x50 /* ADC */ +#define AD_DMAADCCA 0x54 +#define AD_DMAADCBC 0x58 +#define AD_DMAADCCC 0x5c +#define AD_DMASYNBA 0x60 /* SYN */ +#define AD_DMASYNCA 0x64 +#define AD_DMASYNBC 0x68 +#define AD_DMASYNCC 0x6c +#define AD_DMAWAVBA 0x70 /* WAV */ +#define AD_DMAWAVCA 0x74 +#define AD_DMAWAVBC 0x78 +#define AD_DMAWAVCC 0x7c +#define AD_DMARESICC 0x80 /* RES interrupt current count */ +#define AD_DMARESIBC 0x84 /* RES interrupt base count */ +#define AD_DMAADCICC 0x88 /* ADC interrupt current count */ +#define AD_DMAADCIBC 0x8c /* ADC interrupt base count */ +#define AD_DMASYNICC 0x90 /* SYN interrupt current count */ +#define AD_DMASYNIBC 0x94 /* SYN interrupt base count */ +#define AD_DMAWAVICC 0x98 /* WAV interrupt current count */ +#define AD_DMAWAVIBC 0x9c /* WAV interrupt base count */ +#define AD_DMARESCTRL 0xa0 /* RES PCI control/status */ +#define AD_DMAADCCTRL 0xa8 /* ADC PCI control/status */ +#define AD_DMASYNCTRL 0xb0 /* SYN PCI control/status */ +#define AD_DMAWAVCTRL 0xb8 /* WAV PCI control/status */ +#define AD_DMADISR 0xc0 /* PCI DMA intr status */ +#define AD_DMACHSS 0xc4 /* PCI DMA channel stop status */ + +#define AD_GPIOIPC 0xc8 /* IO port ctrl */ +#define AD_GPIOOP 0xca /* IO output status */ +#define AD_GPIOIP 0xcc /* IO input status */ + +/* AC97 registers, 0x100 - 0x17f; see ac97.h */ +#define AD_ACIC 0x180 /* AC Link interface ctrl */ + +/* OPL3; BAR1 */ +#define AD_OPLM0AS 0x00 /* Music0 address/status */ +#define AD_OPLM0DATA 0x01 /* Music0 data */ +#define AD_OPLM1A 0x02 /* Music1 address */ +#define AD_OPLM1DATA 0x03 /* Music1 data */ +/* 0x04-0x0f reserved */ + +/* MIDI; BAR2 */ +#define AD_MIDA 0x00 /* MIDI data */ +#define AD_MISC 0x01 /* MIDI status/cmd */ +/* 0x02-0xff reserved */ + +#define AD_DSIOMEMSIZE 512 +#define AD_OPLMEMSIZE 16 +#define AD_MIDIMEMSIZE 16 + +#define AD_WAV_STATE 0 +#define AD_ADC_STATE 1 +#define AD_MAX_STATES 2 + +#define DMA_SIZE (128*1024) + +#define DMA_FLAG_MAPPED 1 + +struct ad1889_dev; + +typedef struct ad1889_state { + struct ad1889_dev *card; + + mode_t open_mode; + struct dmabuf { + unsigned int rate; + unsigned char fmt, enable; + + /* buf management */ + size_t rawbuf_size; + void *rawbuf; + dma_addr_t dma_handle; /* mapped address */ + unsigned long dma_len; /* number of bytes mapped */ + + /* indexes into rawbuf for setting up DMA engine */ + volatile unsigned long rd_ptr, wr_ptr; + + wait_queue_head_t wait; /* to wait for buf servicing */ + + /* OSS bits */ + unsigned int mapped:1; + unsigned int ready:1; + unsigned int ossfragshift; + int ossmaxfrags; + unsigned int subdivision; + } dmabuf; + + struct semaphore sem; +} ad1889_state_t; + +typedef struct ad1889_dev { + void __iomem *regbase; + struct pci_dev *pci; + + spinlock_t lock; + + int dev_audio; + + /* states; one per channel; right now only WAV and ADC */ + struct ad1889_state state[AD_MAX_STATES]; + + /* AC97 codec */ + struct ac97_codec *ac97_codec; + u16 ac97_features; + + /* debugging stuff */ + struct stats { + unsigned int wav_intrs, adc_intrs; + unsigned int blocks, underrun, error; + } stats; +} ad1889_dev_t; + +typedef struct ad1889_reg { + const char *name; + int offset; + int width; +} ad1889_reg_t; + +#endif diff --git a/sound/oss/adlib_card.c b/sound/oss/adlib_card.c new file mode 100644 index 000000000000..6414ceb8f072 --- /dev/null +++ b/sound/oss/adlib_card.c @@ -0,0 +1,73 @@ +/* + * sound/adlib_card.c + * + * Detection routine for the AdLib card. + * + * 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 +#include + +#include "sound_config.h" + +#include "opl3.h" + +static void __init attach_adlib_card(struct address_info *hw_config) +{ + hw_config->slots[0] = opl3_init(hw_config->io_base, hw_config->osp, THIS_MODULE); +} + +static int __init probe_adlib(struct address_info *hw_config) +{ + return opl3_detect(hw_config->io_base, hw_config->osp); +} + +static struct address_info cfg; + +static int __initdata io = -1; + +module_param(io, int, 0); + +static int __init init_adlib(void) +{ + cfg.io_base = io; + + if (cfg.io_base == -1) { + printk(KERN_ERR "adlib: must specify I/O address.\n"); + return -EINVAL; + } + if (probe_adlib(&cfg) == 0) + return -ENODEV; + attach_adlib_card(&cfg); + + return 0; +} + +static void __exit cleanup_adlib(void) +{ + sound_unload_synthdev(cfg.slots[0]); + +} + +module_init(init_adlib); +module_exit(cleanup_adlib); + +#ifndef MODULE +static int __init setup_adlib(char *str) +{ + /* io */ + int ints[2]; + str = get_options(str, ARRAY_SIZE(ints), ints); + + io = ints[1]; + + return 1; +} +__setup("adlib=", setup_adlib); +#endif +MODULE_LICENSE("GPL"); diff --git a/sound/oss/aedsp16.c b/sound/oss/aedsp16.c new file mode 100644 index 000000000000..b556263a57f5 --- /dev/null +++ b/sound/oss/aedsp16.c @@ -0,0 +1,1381 @@ +/* + 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 +#include +#include +#include +#include "sound_config.h" + +/* + * Sanity checks + */ + +#if defined(CONFIG_SOUND_AEDSP16_SBPRO) && defined(CONFIG_SOUND_AEDSP16_MSS) +#error You have to enable only one of the MSS and SBPRO emulations. +#endif + +/* + + 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 + + [...] + 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 an 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 - 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 . 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 2 /* Size of the card's version in chars */ + +#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[CARDVERLEN] __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 < CARDVERLEN); + 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 "); +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/ali5455.c b/sound/oss/ali5455.c new file mode 100644 index 000000000000..9c9e6c0410f2 --- /dev/null +++ b/sound/oss/ali5455.c @@ -0,0 +1,3733 @@ +/* + * ALI ali5455 and friends ICH driver for Linux + * LEI HU + * + * Built from: + * drivers/sound/i810_audio + * + * The ALi 5455 is similar but not quite identical to the Intel ICH + * series of controllers. Its easier to keep the driver separated from + * the i810 driver. + * + * 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. + * + * + * ALi 5455 theory of operation + * + * The chipset provides three DMA channels that talk to an AC97 + * CODEC (AC97 is a digital/analog mixer standard). At its simplest + * you get 48Khz audio with basic volume and mixer controls. At the + * best you get rate adaption in the codec. We set the card up so + * that we never take completion interrupts but instead keep the card + * chasing its tail around a ring buffer. This is needed for mmap + * mode audio and happens to work rather well for non-mmap modes too. + * + * The board has one output channel for PCM audio (supported) and + * a stereo line in and mono microphone input. Again these are normally + * locked to 48Khz only. Right now recording is not finished. + * + * There is no midi support, no synth support. Use timidity. To get + * esd working you need to use esd -r 48000 as it won't probe 48KHz + * by default. mpg123 can't handle 48Khz only audio so use xmms. + * + * If you need to force a specific rate set the clocking= option + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PCI_DEVICE_ID_ALI_5455 +#define PCI_DEVICE_ID_ALI_5455 0x5455 +#endif + +#ifndef PCI_VENDOR_ID_ALI +#define PCI_VENDOR_ID_ALI 0x10b9 +#endif + +static int strict_clocking = 0; +static unsigned int clocking = 0; +static unsigned int codec_pcmout_share_spdif_locked = 0; +static unsigned int codec_independent_spdif_locked = 0; +static unsigned int controller_pcmout_share_spdif_locked = 0; +static unsigned int controller_independent_spdif_locked = 0; +static unsigned int globel = 0; + +#define ADC_RUNNING 1 +#define DAC_RUNNING 2 +#define CODEC_SPDIFOUT_RUNNING 8 +#define CONTROLLER_SPDIFOUT_RUNNING 4 + +#define SPDIF_ENABLE_OUTPUT 4 /* bits 0,1 are PCM */ + +#define ALI5455_FMT_16BIT 1 +#define ALI5455_FMT_STEREO 2 +#define ALI5455_FMT_MASK 3 + +#define SPDIF_ON 0x0004 +#define SURR_ON 0x0010 +#define CENTER_LFE_ON 0x0020 +#define VOL_MUTED 0x8000 + + +#define ALI_SPDIF_OUT_CH_STATUS 0xbf +/* the 810's array of pointers to data buffers */ + +struct sg_item { +#define BUSADDR_MASK 0xFFFFFFFE + u32 busaddr; +#define CON_IOC 0x80000000 /* interrupt on completion */ +#define CON_BUFPAD 0x40000000 /* pad underrun with last sample, else 0 */ +#define CON_BUFLEN_MASK 0x0000ffff /* buffer length in samples */ + u32 control; +}; + +/* an instance of the ali channel */ +#define SG_LEN 32 +struct ali_channel { + /* these sg guys should probably be allocated + separately as nocache. Must be 8 byte aligned */ + struct sg_item sg[SG_LEN]; /* 32*8 */ + u32 offset; /* 4 */ + u32 port; /* 4 */ + u32 used; + u32 num; +}; + +/* + * we have 3 separate dma engines. pcm in, pcm out, and mic. + * each dma engine has controlling registers. These goofy + * names are from the datasheet, but make it easy to write + * code while leafing through it. + */ + +#define ENUM_ENGINE(PRE,DIG) \ +enum { \ + PRE##_BDBAR = 0x##DIG##0, /* Buffer Descriptor list Base Address */ \ + PRE##_CIV = 0x##DIG##4, /* Current Index Value */ \ + PRE##_LVI = 0x##DIG##5, /* Last Valid Index */ \ + PRE##_SR = 0x##DIG##6, /* Status Register */ \ + PRE##_PICB = 0x##DIG##8, /* Position In Current Buffer */ \ + PRE##_CR = 0x##DIG##b /* Control Register */ \ +} + +ENUM_ENGINE(OFF, 0); /* Offsets */ +ENUM_ENGINE(PI, 4); /* PCM In */ +ENUM_ENGINE(PO, 5); /* PCM Out */ +ENUM_ENGINE(MC, 6); /* Mic In */ +ENUM_ENGINE(CODECSPDIFOUT, 7); /* CODEC SPDIF OUT */ +ENUM_ENGINE(CONTROLLERSPDIFIN, A); /* CONTROLLER SPDIF In */ +ENUM_ENGINE(CONTROLLERSPDIFOUT, B); /* CONTROLLER SPDIF OUT */ + + +enum { + ALI_SCR = 0x00, /* System Control Register */ + ALI_SSR = 0x04, /* System Status Register */ + ALI_DMACR = 0x08, /* DMA Control Register */ + ALI_FIFOCR1 = 0x0c, /* FIFO Control Register 1 */ + ALI_INTERFACECR = 0x10, /* Interface Control Register */ + ALI_INTERRUPTCR = 0x14, /* Interrupt control Register */ + ALI_INTERRUPTSR = 0x18, /* Interrupt Status Register */ + ALI_FIFOCR2 = 0x1c, /* FIFO Control Register 2 */ + ALI_CPR = 0x20, /* Command Port Register */ + ALI_SPR = 0x24, /* Status Port Register */ + ALI_FIFOCR3 = 0x2c, /* FIFO Control Register 3 */ + ALI_TTSR = 0x30, /* Transmit Tag Slot Register */ + ALI_RTSR = 0x34, /* Receive Tag Slot Register */ + ALI_CSPSR = 0x38, /* Command/Status Port Status Register */ + ALI_CAS = 0x3c, /* Codec Write Semaphore Register */ + ALI_SPDIFCSR = 0xf8, /* spdif channel status register */ + ALI_SPDIFICS = 0xfc /* spdif interface control/status */ +}; + +// x-status register(x:pcm in ,pcm out, mic in,) +/* interrupts for a dma engine */ +#define DMA_INT_FIFO (1<<4) /* fifo under/over flow */ +#define DMA_INT_COMPLETE (1<<3) /* buffer read/write complete and ioc set */ +#define DMA_INT_LVI (1<<2) /* last valid done */ +#define DMA_INT_CELV (1<<1) /* last valid is current */ +#define DMA_INT_DCH (1) /* DMA Controller Halted (happens on LVI interrupts) */ //not eqult intel +#define DMA_INT_MASK (DMA_INT_FIFO|DMA_INT_COMPLETE|DMA_INT_LVI) + +/* interrupts for the whole chip */// by interrupt status register finish + +#define INT_SPDIFOUT (1<<23) /* controller spdif out INTERRUPT */ +#define INT_SPDIFIN (1<<22) +#define INT_CODECSPDIFOUT (1<<19) +#define INT_MICIN (1<<18) +#define INT_PCMOUT (1<<17) +#define INT_PCMIN (1<<16) +#define INT_CPRAIS (1<<7) +#define INT_SPRAIS (1<<5) +#define INT_GPIO (1<<1) +#define INT_MASK (INT_SPDIFOUT|INT_CODECSPDIFOUT|INT_MICIN|INT_PCMOUT|INT_PCMIN) + +#define DRIVER_VERSION "0.02ac" + +/* magic numbers to protect our data structures */ +#define ALI5455_CARD_MAGIC 0x5072696E /* "Prin" */ +#define ALI5455_STATE_MAGIC 0x63657373 /* "cess" */ +#define ALI5455_DMA_MASK 0xffffffff /* DMA buffer mask for pci_alloc_consist */ +#define NR_HW_CH 5 //I think 5 channel + +/* maxinum number of AC97 codecs connected, AC97 2.0 defined 4 */ +#define NR_AC97 2 + +/* Please note that an 8bit mono stream is not valid on this card, you must have a 16bit */ +/* stream at a minimum for this card to be happy */ +static const unsigned sample_size[] = { 1, 2, 2, 4 }; +/* Samples are 16bit values, so we are shifting to a word, not to a byte, hence shift */ +/* values are one less than might be expected */ +static const unsigned sample_shift[] = { -1, 0, 0, 1 }; + +#define ALI5455 +static char *card_names[] = { + "ALI 5455" +}; + +static struct pci_device_id ali_pci_tbl[] = { + {PCI_VENDOR_ID_ALI, PCI_DEVICE_ID_ALI_5455, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, ALI5455}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, ali_pci_tbl); + +#ifdef CONFIG_PM +#define PM_SUSPENDED(card) (card->pm_suspended) +#else +#define PM_SUSPENDED(card) (0) +#endif + +/* "software" or virtual channel, an instance of opened /dev/dsp */ +struct ali_state { + unsigned int magic; + struct ali_card *card; /* Card info */ + + /* single open lock mechanism, only used for recording */ + struct semaphore open_sem; + wait_queue_head_t open_wait; + + /* file mode */ + mode_t open_mode; + + /* virtual channel number */ + int virt; + +#ifdef CONFIG_PM + unsigned int pm_saved_dac_rate, pm_saved_adc_rate; +#endif + struct dmabuf { + /* wave sample stuff */ + unsigned int rate; + unsigned char fmt, enable, trigger; + + /* hardware channel */ + struct ali_channel *read_channel; + struct ali_channel *write_channel; + struct ali_channel *codec_spdifout_channel; + struct ali_channel *controller_spdifout_channel; + + /* OSS buffer management stuff */ + void *rawbuf; + dma_addr_t dma_handle; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + + /* our buffer acts like a circular ring */ + unsigned hwptr; /* where dma last started, updated by update_ptr */ + unsigned swptr; /* where driver last clear/filled, updated by read/write */ + int count; /* bytes to be consumed or been generated by dma machine */ + unsigned total_bytes; /* total bytes dmaed by hardware */ + + unsigned error; /* number of over/underruns */ + wait_queue_head_t wait; /* put process on wait queue when no more space in buffer */ + + /* redundant, but makes calculations easier */ + /* what the hardware uses */ + unsigned dmasize; + unsigned fragsize; + unsigned fragsamples; + + /* what we tell the user to expect */ + unsigned userfrags; + unsigned userfragsize; + + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned update_flag; + unsigned ossfragsize; + unsigned ossmaxfrags; + unsigned subdivision; + } dmabuf; +}; + + +struct ali_card { + struct ali_channel channel[5]; + unsigned int magic; + + /* We keep ali5455 cards in a linked list */ + struct ali_card *next; + + /* The ali has a certain amount of cross channel interaction + so we use a single per card lock */ + spinlock_t lock; + spinlock_t ac97_lock; + + /* PCI device stuff */ + struct pci_dev *pci_dev; + u16 pci_id; +#ifdef CONFIG_PM + u16 pm_suspended; + int pm_saved_mixer_settings[SOUND_MIXER_NRDEVICES][NR_AC97]; +#endif + /* soundcore stuff */ + int dev_audio; + + /* structures for abstraction of hardware facilities, codecs, banks and channels */ + struct ac97_codec *ac97_codec[NR_AC97]; + struct ali_state *states[NR_HW_CH]; + + u16 ac97_features; + u16 ac97_status; + u16 channels; + + /* hardware resources */ + unsigned long iobase; + + u32 irq; + + /* Function support */ + struct ali_channel *(*alloc_pcm_channel) (struct ali_card *); + struct ali_channel *(*alloc_rec_pcm_channel) (struct ali_card *); + struct ali_channel *(*alloc_rec_mic_channel) (struct ali_card *); + struct ali_channel *(*alloc_codec_spdifout_channel) (struct ali_card *); + struct ali_channel *(*alloc_controller_spdifout_channel) (struct ali_card *); + void (*free_pcm_channel) (struct ali_card *, int chan); + + /* We have a *very* long init time possibly, so use this to block */ + /* attempts to open our devices before we are ready (stops oops'es) */ + int initializing; +}; + + +static struct ali_card *devs = NULL; + +static int ali_open_mixdev(struct inode *inode, struct file *file); +static int ali_ioctl_mixdev(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); +static u16 ali_ac97_get(struct ac97_codec *dev, u8 reg); +static void ali_ac97_set(struct ac97_codec *dev, u8 reg, u16 data); + +static struct ali_channel *ali_alloc_pcm_channel(struct ali_card *card) +{ + if (card->channel[1].used == 1) + return NULL; + card->channel[1].used = 1; + return &card->channel[1]; +} + +static struct ali_channel *ali_alloc_rec_pcm_channel(struct ali_card *card) +{ + if (card->channel[0].used == 1) + return NULL; + card->channel[0].used = 1; + return &card->channel[0]; +} + +static struct ali_channel *ali_alloc_rec_mic_channel(struct ali_card *card) +{ + if (card->channel[2].used == 1) + return NULL; + card->channel[2].used = 1; + return &card->channel[2]; +} + +static struct ali_channel *ali_alloc_codec_spdifout_channel(struct ali_card *card) +{ + if (card->channel[3].used == 1) + return NULL; + card->channel[3].used = 1; + return &card->channel[3]; +} + +static struct ali_channel *ali_alloc_controller_spdifout_channel(struct ali_card *card) +{ + if (card->channel[4].used == 1) + return NULL; + card->channel[4].used = 1; + return &card->channel[4]; +} +static void ali_free_pcm_channel(struct ali_card *card, int channel) +{ + card->channel[channel].used = 0; +} + + +//add support codec spdif out +static int ali_valid_spdif_rate(struct ac97_codec *codec, int rate) +{ + unsigned long id = 0L; + + id = (ali_ac97_get(codec, AC97_VENDOR_ID1) << 16); + id |= ali_ac97_get(codec, AC97_VENDOR_ID2) & 0xffff; + switch (id) { + case 0x41445361: /* AD1886 */ + if (rate == 48000) { + return 1; + } + break; + case 0x414c4720: /* ALC650 */ + if (rate == 48000) { + return 1; + } + break; + default: /* all other codecs, until we know otherwiae */ + if (rate == 48000 || rate == 44100 || rate == 32000) { + return 1; + } + break; + } + return (0); +} + +/* ali_set_spdif_output + * + * Configure the S/PDIF output transmitter. When we turn on + * S/PDIF, we turn off the analog output. This may not be + * the right thing to do. + * + * Assumptions: + * The DSP sample rate must already be set to a supported + * S/PDIF rate (32kHz, 44.1kHz, or 48kHz) or we abort. + */ +static void ali_set_spdif_output(struct ali_state *state, int slots, + int rate) +{ + int vol; + int aud_reg; + struct ac97_codec *codec = state->card->ac97_codec[0]; + + if (!(state->card->ac97_features & 4)) { + state->card->ac97_status &= ~SPDIF_ON; + } else { + if (slots == -1) { /* Turn off S/PDIF */ + aud_reg = ali_ac97_get(codec, AC97_EXTENDED_STATUS); + ali_ac97_set(codec, AC97_EXTENDED_STATUS, (aud_reg & ~AC97_EA_SPDIF)); + + /* If the volume wasn't muted before we turned on S/PDIF, unmute it */ + if (!(state->card->ac97_status & VOL_MUTED)) { + aud_reg = ali_ac97_get(codec, AC97_MASTER_VOL_STEREO); + ali_ac97_set(codec, AC97_MASTER_VOL_STEREO, + (aud_reg & ~VOL_MUTED)); + } + state->card->ac97_status &= ~(VOL_MUTED | SPDIF_ON); + return; + } + + vol = ali_ac97_get(codec, AC97_MASTER_VOL_STEREO); + state->card->ac97_status = vol & VOL_MUTED; + + /* Set S/PDIF transmitter sample rate */ + aud_reg = ali_ac97_get(codec, AC97_SPDIF_CONTROL); + switch (rate) { + case 32000: + aud_reg = (aud_reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_32K; + break; + case 44100: + aud_reg = (aud_reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_44K; + break; + case 48000: + aud_reg = (aud_reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_48K; + break; + default: + /* turn off S/PDIF */ + aud_reg = ali_ac97_get(codec, AC97_EXTENDED_STATUS); + ali_ac97_set(codec, AC97_EXTENDED_STATUS, (aud_reg & ~AC97_EA_SPDIF)); + state->card->ac97_status &= ~SPDIF_ON; + return; + } + + ali_ac97_set(codec, AC97_SPDIF_CONTROL, aud_reg); + + aud_reg = ali_ac97_get(codec, AC97_EXTENDED_STATUS); + aud_reg = (aud_reg & AC97_EA_SLOT_MASK) | slots | AC97_EA_SPDIF; + ali_ac97_set(codec, AC97_EXTENDED_STATUS, aud_reg); + + aud_reg = ali_ac97_get(codec, AC97_POWER_CONTROL); + aud_reg |= 0x0002; + ali_ac97_set(codec, AC97_POWER_CONTROL, aud_reg); + udelay(1); + + state->card->ac97_status |= SPDIF_ON; + + /* Check to make sure the configuration is valid */ + aud_reg = ali_ac97_get(codec, AC97_EXTENDED_STATUS); + if (!(aud_reg & 0x0400)) { + /* turn off S/PDIF */ + ali_ac97_set(codec, AC97_EXTENDED_STATUS, (aud_reg & ~AC97_EA_SPDIF)); + state->card->ac97_status &= ~SPDIF_ON; + return; + } + if (codec_independent_spdif_locked > 0) { + aud_reg = ali_ac97_get(codec, 0x6a); + ali_ac97_set(codec, 0x6a, (aud_reg & 0xefff)); + } + /* Mute the analog output */ + /* Should this only mute the PCM volume??? */ + } +} + +/* ali_set_dac_channels + * + * Configure the codec's multi-channel DACs + * + * The logic is backwards. Setting the bit to 1 turns off the DAC. + * + * What about the ICH? We currently configure it using the + * SNDCTL_DSP_CHANNELS ioctl. If we're turnning on the DAC, + * does that imply that we want the ICH set to support + * these channels? + * + * TODO: + * vailidate that the codec really supports these DACs + * before turning them on. + */ +static void ali_set_dac_channels(struct ali_state *state, int channel) +{ + int aud_reg; + struct ac97_codec *codec = state->card->ac97_codec[0]; + + aud_reg = ali_ac97_get(codec, AC97_EXTENDED_STATUS); + aud_reg |= AC97_EA_PRI | AC97_EA_PRJ | AC97_EA_PRK; + state->card->ac97_status &= ~(SURR_ON | CENTER_LFE_ON); + + switch (channel) { + case 2: /* always enabled */ + break; + case 4: + aud_reg &= ~AC97_EA_PRJ; + state->card->ac97_status |= SURR_ON; + break; + case 6: + aud_reg &= ~(AC97_EA_PRJ | AC97_EA_PRI | AC97_EA_PRK); + state->card->ac97_status |= SURR_ON | CENTER_LFE_ON; + break; + default: + break; + } + ali_ac97_set(codec, AC97_EXTENDED_STATUS, aud_reg); + +} + +/* set playback sample rate */ +static unsigned int ali_set_dac_rate(struct ali_state *state, + unsigned int rate) +{ + struct dmabuf *dmabuf = &state->dmabuf; + u32 new_rate; + struct ac97_codec *codec = state->card->ac97_codec[0]; + + if (!(state->card->ac97_features & 0x0001)) { + dmabuf->rate = clocking; + return clocking; + } + + if (rate > 48000) + rate = 48000; + if (rate < 8000) + rate = 8000; + dmabuf->rate = rate; + + /* + * Adjust for misclocked crap + */ + + rate = (rate * clocking) / 48000; + + if (strict_clocking && rate < 8000) { + rate = 8000; + dmabuf->rate = (rate * 48000) / clocking; + } + + new_rate = ac97_set_dac_rate(codec, rate); + if (new_rate != rate) { + dmabuf->rate = (new_rate * 48000) / clocking; + } + rate = new_rate; + return dmabuf->rate; +} + +/* set recording sample rate */ +static unsigned int ali_set_adc_rate(struct ali_state *state, + unsigned int rate) +{ + struct dmabuf *dmabuf = &state->dmabuf; + u32 new_rate; + struct ac97_codec *codec = state->card->ac97_codec[0]; + + if (!(state->card->ac97_features & 0x0001)) { + dmabuf->rate = clocking; + return clocking; + } + + if (rate > 48000) + rate = 48000; + if (rate < 8000) + rate = 8000; + dmabuf->rate = rate; + + /* + * Adjust for misclocked crap + */ + + rate = (rate * clocking) / 48000; + if (strict_clocking && rate < 8000) { + rate = 8000; + dmabuf->rate = (rate * 48000) / clocking; + } + + new_rate = ac97_set_adc_rate(codec, rate); + + if (new_rate != rate) { + dmabuf->rate = (new_rate * 48000) / clocking; + rate = new_rate; + } + return dmabuf->rate; +} + +/* set codec independent spdifout sample rate */ +static unsigned int ali_set_codecspdifout_rate(struct ali_state *state, + unsigned int rate) +{ + struct dmabuf *dmabuf = &state->dmabuf; + + if (!(state->card->ac97_features & 0x0001)) { + dmabuf->rate = clocking; + return clocking; + } + + if (rate > 48000) + rate = 48000; + if (rate < 8000) + rate = 8000; + dmabuf->rate = rate; + + return dmabuf->rate; +} + +/* set controller independent spdif out function sample rate */ +static void ali_set_spdifout_rate(struct ali_state *state, + unsigned int rate) +{ + unsigned char ch_st_sel; + unsigned short status_rate; + + switch (rate) { + case 44100: + status_rate = 0; + break; + case 32000: + status_rate = 0x300; + break; + case 48000: + default: + status_rate = 0x200; + break; + } + + ch_st_sel = inb(state->card->iobase + ALI_SPDIFICS) & ALI_SPDIF_OUT_CH_STATUS; //select spdif_out + + ch_st_sel |= 0x80; //select right + outb(ch_st_sel, (state->card->iobase + ALI_SPDIFICS)); + outb(status_rate | 0x20, (state->card->iobase + ALI_SPDIFCSR + 2)); + + ch_st_sel &= (~0x80); //select left + outb(ch_st_sel, (state->card->iobase + ALI_SPDIFICS)); + outw(status_rate | 0x10, (state->card->iobase + ALI_SPDIFCSR + 2)); +} + +/* get current playback/recording dma buffer pointer (byte offset from LBA), + called with spinlock held! */ + +static inline unsigned ali_get_dma_addr(struct ali_state *state, int rec) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned int civ, offset, port, port_picb; + unsigned int data; + + if (!dmabuf->enable) + return 0; + + if (rec == 1) + port = state->card->iobase + dmabuf->read_channel->port; + else if (rec == 2) + port = state->card->iobase + dmabuf->codec_spdifout_channel->port; + else if (rec == 3) + port = state->card->iobase + dmabuf->controller_spdifout_channel->port; + else + port = state->card->iobase + dmabuf->write_channel->port; + + port_picb = port + OFF_PICB; + + do { + civ = inb(port + OFF_CIV) & 31; + offset = inw(port_picb); + /* Must have a delay here! */ + if (offset == 0) + udelay(1); + + /* Reread both registers and make sure that that total + * offset from the first reading to the second is 0. + * There is an issue with SiS hardware where it will count + * picb down to 0, then update civ to the next value, + * then set the new picb to fragsize bytes. We can catch + * it between the civ update and the picb update, making + * it look as though we are 1 fragsize ahead of where we + * are. The next to we get the address though, it will + * be back in thdelay is more than long enough + * that we won't have to worry about the chip still being + * out of sync with reality ;-) + */ + } while (civ != (inb(port + OFF_CIV) & 31) || offset != inw(port_picb)); + + data = ((civ + 1) * dmabuf->fragsize - (2 * offset)) % dmabuf->dmasize; + if (inw(port_picb) == 0) + data -= 2048; + + return data; +} + +/* Stop recording (lock held) */ +static inline void __stop_adc(struct ali_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct ali_card *card = state->card; + + dmabuf->enable &= ~ADC_RUNNING; + + outl((1 << 18) | (1 << 16), card->iobase + ALI_DMACR); + udelay(1); + + outb(0, card->iobase + PI_CR); + while (inb(card->iobase + PI_CR) != 0); + + // now clear any latent interrupt bits (like the halt bit) + outb(inb(card->iobase + PI_SR) | 0x001e, card->iobase + PI_SR); + outl(inl(card->iobase + ALI_INTERRUPTSR) & INT_PCMIN, card->iobase + ALI_INTERRUPTSR); +} + +static void stop_adc(struct ali_state *state) +{ + struct ali_card *card = state->card; + unsigned long flags; + spin_lock_irqsave(&card->lock, flags); + __stop_adc(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +static inline void __start_adc(struct ali_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + + if (dmabuf->count < dmabuf->dmasize && dmabuf->ready + && !dmabuf->enable && (dmabuf->trigger & PCM_ENABLE_INPUT)) { + dmabuf->enable |= ADC_RUNNING; + outb((1 << 4) | (1 << 2), state->card->iobase + PI_CR); + if (state->card->channel[0].used == 1) + outl(1, state->card->iobase + ALI_DMACR); // DMA CONTROL REGISTRER + udelay(100); + if (state->card->channel[2].used == 1) + outl((1 << 2), state->card->iobase + ALI_DMACR); //DMA CONTROL REGISTER + udelay(100); + } +} + +static void start_adc(struct ali_state *state) +{ + struct ali_card *card = state->card; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + __start_adc(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +/* stop playback (lock held) */ +static inline void __stop_dac(struct ali_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct ali_card *card = state->card; + + dmabuf->enable &= ~DAC_RUNNING; + outl(0x00020000, card->iobase + 0x08); + outb(0, card->iobase + PO_CR); + while (inb(card->iobase + PO_CR) != 0) + cpu_relax(); + + outb(inb(card->iobase + PO_SR) | 0x001e, card->iobase + PO_SR); + + outl(inl(card->iobase + ALI_INTERRUPTSR) & INT_PCMOUT, card->iobase + ALI_INTERRUPTSR); +} + +static void stop_dac(struct ali_state *state) +{ + struct ali_card *card = state->card; + unsigned long flags; + spin_lock_irqsave(&card->lock, flags); + __stop_dac(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +static inline void __start_dac(struct ali_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + if (dmabuf->count > 0 && dmabuf->ready && !dmabuf->enable && + (dmabuf->trigger & PCM_ENABLE_OUTPUT)) { + dmabuf->enable |= DAC_RUNNING; + outb((1 << 4) | (1 << 2), state->card->iobase + PO_CR); + outl((1 << 1), state->card->iobase + 0x08); //dma control register + } +} + +static void start_dac(struct ali_state *state) +{ + struct ali_card *card = state->card; + unsigned long flags; + spin_lock_irqsave(&card->lock, flags); + __start_dac(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +/* stop codec and controller spdif out (lock held) */ +static inline void __stop_spdifout(struct ali_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct ali_card *card = state->card; + + if (codec_independent_spdif_locked > 0) { + dmabuf->enable &= ~CODEC_SPDIFOUT_RUNNING; + outl((1 << 19), card->iobase + 0x08); + outb(0, card->iobase + CODECSPDIFOUT_CR); + + while (inb(card->iobase + CODECSPDIFOUT_CR) != 0) + cpu_relax(); + + outb(inb(card->iobase + CODECSPDIFOUT_SR) | 0x001e, card->iobase + CODECSPDIFOUT_SR); + outl(inl(card->iobase + ALI_INTERRUPTSR) & INT_CODECSPDIFOUT, card->iobase + ALI_INTERRUPTSR); + } else { + if (controller_independent_spdif_locked > 0) { + dmabuf->enable &= ~CONTROLLER_SPDIFOUT_RUNNING; + outl((1 << 23), card->iobase + 0x08); + outb(0, card->iobase + CONTROLLERSPDIFOUT_CR); + while (inb(card->iobase + CONTROLLERSPDIFOUT_CR) != 0) + cpu_relax(); + outb(inb(card->iobase + CONTROLLERSPDIFOUT_SR) | 0x001e, card->iobase + CONTROLLERSPDIFOUT_SR); + outl(inl(card->iobase + ALI_INTERRUPTSR) & INT_SPDIFOUT, card->iobase + ALI_INTERRUPTSR); + } + } +} + +static void stop_spdifout(struct ali_state *state) +{ + struct ali_card *card = state->card; + unsigned long flags; + spin_lock_irqsave(&card->lock, flags); + __stop_spdifout(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +static inline void __start_spdifout(struct ali_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + if (dmabuf->count > 0 && dmabuf->ready && !dmabuf->enable && + (dmabuf->trigger & SPDIF_ENABLE_OUTPUT)) { + if (codec_independent_spdif_locked > 0) { + dmabuf->enable |= CODEC_SPDIFOUT_RUNNING; + outb((1 << 4) | (1 << 2), state->card->iobase + CODECSPDIFOUT_CR); + outl((1 << 3), state->card->iobase + 0x08); //dma control register + } else { + if (controller_independent_spdif_locked > 0) { + dmabuf->enable |= CONTROLLER_SPDIFOUT_RUNNING; + outb((1 << 4) | (1 << 2), state->card->iobase + CONTROLLERSPDIFOUT_CR); + outl((1 << 7), state->card->iobase + 0x08); //dma control register + } + } + } +} + +static void start_spdifout(struct ali_state *state) +{ + struct ali_card *card = state->card; + unsigned long flags; + spin_lock_irqsave(&card->lock, flags); + __start_spdifout(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +#define DMABUF_DEFAULTORDER (16-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +/* allocate DMA buffer, playback , recording,spdif out buffer should be allocated separately */ +static int alloc_dmabuf(struct ali_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + void *rawbuf = NULL; + int order, size; + struct page *page, *pend; + + /* If we don't have any oss frag params, then use our default ones */ + if (dmabuf->ossmaxfrags == 0) + dmabuf->ossmaxfrags = 4; + if (dmabuf->ossfragsize == 0) + dmabuf->ossfragsize = (PAGE_SIZE << DMABUF_DEFAULTORDER) / dmabuf->ossmaxfrags; + size = dmabuf->ossfragsize * dmabuf->ossmaxfrags; + + if (dmabuf->rawbuf && (PAGE_SIZE << dmabuf->buforder) == size) + return 0; + /* alloc enough to satisfy the oss params */ + for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--) { + if ((PAGE_SIZE << order) > size) + continue; + if ((rawbuf = pci_alloc_consistent(state->card->pci_dev, + PAGE_SIZE << order, + &dmabuf->dma_handle))) + break; + } + if (!rawbuf) + return -ENOMEM; + + dmabuf->ready = dmabuf->mapped = 0; + dmabuf->rawbuf = rawbuf; + dmabuf->buforder = order; + + /* now mark the pages as reserved; otherwise remap_pfn_range doesn't do what we want */ + pend = virt_to_page(rawbuf + (PAGE_SIZE << order) - 1); + for (page = virt_to_page(rawbuf); page <= pend; page++) + SetPageReserved(page); + return 0; +} + +/* free DMA buffer */ +static void dealloc_dmabuf(struct ali_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct page *page, *pend; + + if (dmabuf->rawbuf) { + /* undo marking the pages as reserved */ + pend = virt_to_page(dmabuf->rawbuf + (PAGE_SIZE << dmabuf->buforder) - 1); + for (page = virt_to_page(dmabuf->rawbuf); page <= pend; page++) + ClearPageReserved(page); + pci_free_consistent(state->card->pci_dev, + PAGE_SIZE << dmabuf->buforder, + dmabuf->rawbuf, dmabuf->dma_handle); + } + dmabuf->rawbuf = NULL; + dmabuf->mapped = dmabuf->ready = 0; +} + +static int prog_dmabuf(struct ali_state *state, unsigned rec) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct ali_channel *c = NULL; + struct sg_item *sg; + unsigned long flags; + int ret; + unsigned fragint; + int i; + + spin_lock_irqsave(&state->card->lock, flags); + if (dmabuf->enable & DAC_RUNNING) + __stop_dac(state); + if (dmabuf->enable & ADC_RUNNING) + __stop_adc(state); + if (dmabuf->enable & CODEC_SPDIFOUT_RUNNING) + __stop_spdifout(state); + if (dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING) + __stop_spdifout(state); + + dmabuf->total_bytes = 0; + dmabuf->count = dmabuf->error = 0; + dmabuf->swptr = dmabuf->hwptr = 0; + spin_unlock_irqrestore(&state->card->lock, flags); + + /* allocate DMA buffer, let alloc_dmabuf determine if we are already + * allocated well enough or if we should replace the current buffer + * (assuming one is already allocated, if it isn't, then allocate it). + */ + if ((ret = alloc_dmabuf(state))) + return ret; + + /* FIXME: figure out all this OSS fragment stuff */ + /* I did, it now does what it should according to the OSS API. DL */ + /* We may not have realloced our dmabuf, but the fragment size to + * fragment number ratio may have changed, so go ahead and reprogram + * things + */ + + dmabuf->dmasize = PAGE_SIZE << dmabuf->buforder; + dmabuf->numfrag = SG_LEN; + dmabuf->fragsize = dmabuf->dmasize / dmabuf->numfrag; + dmabuf->fragsamples = dmabuf->fragsize >> 1; + dmabuf->userfragsize = dmabuf->ossfragsize; + dmabuf->userfrags = dmabuf->dmasize / dmabuf->ossfragsize; + + memset(dmabuf->rawbuf, 0, dmabuf->dmasize); + + if (dmabuf->ossmaxfrags == 4) { + fragint = 8; + dmabuf->fragshift = 2; + } else if (dmabuf->ossmaxfrags == 8) { + fragint = 4; + dmabuf->fragshift = 3; + } else if (dmabuf->ossmaxfrags == 16) { + fragint = 2; + dmabuf->fragshift = 4; + } else { + fragint = 1; + dmabuf->fragshift = 5; + } + /* + * Now set up the ring + */ + + if (rec == 1) + c = dmabuf->read_channel; + else if (rec == 2) + c = dmabuf->codec_spdifout_channel; + else if (rec == 3) + c = dmabuf->controller_spdifout_channel; + else if (rec == 0) + c = dmabuf->write_channel; + if (c != NULL) { + sg = &c->sg[0]; + /* + * Load up 32 sg entries and take an interrupt at half + * way (we might want more interrupts later..) + */ + for (i = 0; i < dmabuf->numfrag; i++) { + sg->busaddr = + virt_to_bus(dmabuf->rawbuf + + dmabuf->fragsize * i); + // the card will always be doing 16bit stereo + sg->control = dmabuf->fragsamples; + sg->control |= CON_BUFPAD; //I modify + // set us up to get IOC interrupts as often as needed to + // satisfy numfrag requirements, no more + if (((i + 1) % fragint) == 0) { + sg->control |= CON_IOC; + } + sg++; + } + spin_lock_irqsave(&state->card->lock, flags); + outb(2, state->card->iobase + c->port + OFF_CR); /* reset DMA machine */ + outl(virt_to_bus(&c->sg[0]), state->card->iobase + c->port + OFF_BDBAR); + outb(0, state->card->iobase + c->port + OFF_CIV); + outb(0, state->card->iobase + c->port + OFF_LVI); + spin_unlock_irqrestore(&state->card->lock, flags); + } + /* set the ready flag for the dma buffer */ + dmabuf->ready = 1; + return 0; +} + +static void __ali_update_lvi(struct ali_state *state, int rec) +{ + struct dmabuf *dmabuf = &state->dmabuf; + int x, port; + port = state->card->iobase; + if (rec == 1) + port += dmabuf->read_channel->port; + else if (rec == 2) + port += dmabuf->codec_spdifout_channel->port; + else if (rec == 3) + port += dmabuf->controller_spdifout_channel->port; + else if (rec == 0) + port += dmabuf->write_channel->port; + /* if we are currently stopped, then our CIV is actually set to our + * *last* sg segment and we are ready to wrap to the next. However, + * if we set our LVI to the last sg segment, then it won't wrap to + * the next sg segment, it won't even get a start. So, instead, when + * we are stopped, we set both the LVI value and also we increment + * the CIV value to the next sg segment to be played so that when + * we call start_{dac,adc}, things will operate properly + */ + if (!dmabuf->enable && dmabuf->ready) { + if (rec && dmabuf->count < dmabuf->dmasize && (dmabuf->trigger & PCM_ENABLE_INPUT)) { + outb((inb(port + OFF_CIV) + 1) & 31, port + OFF_LVI); + __start_adc(state); + while (! (inb(port + OFF_CR) & ((1 << 4) | (1 << 2)))) + cpu_relax(); + } else if (!rec && dmabuf->count && (dmabuf->trigger & PCM_ENABLE_OUTPUT)) { + outb((inb(port + OFF_CIV) + 1) & 31, port + OFF_LVI); + __start_dac(state); + while (!(inb(port + OFF_CR) & ((1 << 4) | (1 << 2)))) + cpu_relax(); + } else if (rec && dmabuf->count && (dmabuf->trigger & SPDIF_ENABLE_OUTPUT)) { + if (codec_independent_spdif_locked > 0) { + // outb((inb(port+OFF_CIV))&31, port+OFF_LVI); + outb((inb(port + OFF_CIV) + 1) & 31, port + OFF_LVI); + __start_spdifout(state); + while (!(inb(port + OFF_CR) & ((1 << 4) | (1 << 2)))) + cpu_relax(); + } else { + if (controller_independent_spdif_locked > 0) { + outb((inb(port + OFF_CIV) + 1) & 31, port + OFF_LVI); + __start_spdifout(state); + while (!(inb(port + OFF_CR) & ((1 << 4) | (1 << 2)))) + cpu_relax(); + } + } + } + } + + /* swptr - 1 is the tail of our transfer */ + x = (dmabuf->dmasize + dmabuf->swptr - 1) % dmabuf->dmasize; + x /= dmabuf->fragsize; + outb(x, port + OFF_LVI); +} + +static void ali_update_lvi(struct ali_state *state, int rec) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + if (!dmabuf->ready) + return; + spin_lock_irqsave(&state->card->lock, flags); + __ali_update_lvi(state, rec); + spin_unlock_irqrestore(&state->card->lock, flags); +} + +/* update buffer manangement pointers, especially, dmabuf->count and dmabuf->hwptr */ +static void ali_update_ptr(struct ali_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned hwptr; + int diff; + + /* error handling and process wake up for DAC */ + if (dmabuf->enable == ADC_RUNNING) { + /* update hardware pointer */ + hwptr = ali_get_dma_addr(state, 1); + diff = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize; + dmabuf->hwptr = hwptr; + dmabuf->total_bytes += diff; + dmabuf->count += diff; + if (dmabuf->count > dmabuf->dmasize) { + /* buffer underrun or buffer overrun */ + /* this is normal for the end of a read */ + /* only give an error if we went past the */ + /* last valid sg entry */ + if ((inb(state->card->iobase + PI_CIV) & 31) != (inb(state->card->iobase + PI_LVI) & 31)) { + printk(KERN_WARNING "ali_audio: DMA overrun on read\n"); + dmabuf->error++; + } + } + if (dmabuf->count > dmabuf->userfragsize) + wake_up(&dmabuf->wait); + } + /* error handling and process wake up for DAC */ + if (dmabuf->enable == DAC_RUNNING) { + /* update hardware pointer */ + hwptr = ali_get_dma_addr(state, 0); + diff = + (dmabuf->dmasize + hwptr - + dmabuf->hwptr) % dmabuf->dmasize; +#if defined(DEBUG_INTERRUPTS) || defined(DEBUG_MMAP) + printk("DAC HWP %d,%d,%d\n", hwptr, dmabuf->hwptr, diff); +#endif + dmabuf->hwptr = hwptr; + dmabuf->total_bytes += diff; + dmabuf->count -= diff; + if (dmabuf->count < 0) { + /* buffer underrun or buffer overrun */ + /* this is normal for the end of a write */ + /* only give an error if we went past the */ + /* last valid sg entry */ + if ((inb(state->card->iobase + PO_CIV) & 31) != (inb(state->card->iobase + PO_LVI) & 31)) { + printk(KERN_WARNING "ali_audio: DMA overrun on write\n"); + printk(KERN_DEBUG "ali_audio: CIV %d, LVI %d, hwptr %x, count %d\n", + inb(state->card->iobase + PO_CIV) & 31, + inb(state->card->iobase + PO_LVI) & 31, + dmabuf->hwptr, + dmabuf->count); + dmabuf->error++; + } + } + if (dmabuf->count < (dmabuf->dmasize - dmabuf->userfragsize)) + wake_up(&dmabuf->wait); + } + + /* error handling and process wake up for CODEC SPDIF OUT */ + if (dmabuf->enable == CODEC_SPDIFOUT_RUNNING) { + /* update hardware pointer */ + hwptr = ali_get_dma_addr(state, 2); + diff = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize; + dmabuf->hwptr = hwptr; + dmabuf->total_bytes += diff; + dmabuf->count -= diff; + if (dmabuf->count < 0) { + /* buffer underrun or buffer overrun */ + /* this is normal for the end of a write */ + /* only give an error if we went past the */ + /* last valid sg entry */ + if ((inb(state->card->iobase + CODECSPDIFOUT_CIV) & 31) != (inb(state->card->iobase + CODECSPDIFOUT_LVI) & 31)) { + printk(KERN_WARNING "ali_audio: DMA overrun on write\n"); + printk(KERN_DEBUG "ali_audio: CIV %d, LVI %d, hwptr %x, count %d\n", + inb(state->card->iobase + CODECSPDIFOUT_CIV) & 31, + inb(state->card->iobase + CODECSPDIFOUT_LVI) & 31, + dmabuf->hwptr, dmabuf->count); + dmabuf->error++; + } + } + if (dmabuf->count < (dmabuf->dmasize - dmabuf->userfragsize)) + wake_up(&dmabuf->wait); + } + /* error handling and process wake up for CONTROLLER SPDIF OUT */ + if (dmabuf->enable == CONTROLLER_SPDIFOUT_RUNNING) { + /* update hardware pointer */ + hwptr = ali_get_dma_addr(state, 3); + diff = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize; + dmabuf->hwptr = hwptr; + dmabuf->total_bytes += diff; + dmabuf->count -= diff; + if (dmabuf->count < 0) { + /* buffer underrun or buffer overrun */ + /* this is normal for the end of a write */ + /* only give an error if we went past the */ + /* last valid sg entry */ + if ((inb(state->card->iobase + CONTROLLERSPDIFOUT_CIV) & 31) != (inb(state->card->iobase + CONTROLLERSPDIFOUT_LVI) & 31)) { + printk(KERN_WARNING + "ali_audio: DMA overrun on write\n"); + printk("ali_audio: CIV %d, LVI %d, hwptr %x, " + "count %d\n", + inb(state->card->iobase + CONTROLLERSPDIFOUT_CIV) & 31, + inb(state->card->iobase + CONTROLLERSPDIFOUT_LVI) & 31, + dmabuf->hwptr, dmabuf->count); + dmabuf->error++; + } + } + if (dmabuf->count < (dmabuf->dmasize - dmabuf->userfragsize)) + wake_up(&dmabuf->wait); + } +} + +static inline int ali_get_free_write_space(struct + ali_state + *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + int free; + + if (dmabuf->count < 0) { + dmabuf->count = 0; + dmabuf->swptr = dmabuf->hwptr; + } + free = dmabuf->dmasize - dmabuf->swptr; + if ((dmabuf->count + free) > dmabuf->dmasize){ + free = dmabuf->dmasize - dmabuf->count; + } + return free; +} + +static inline int ali_get_available_read_data(struct + ali_state + *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + int avail; + ali_update_ptr(state); + // catch overruns during record + if (dmabuf->count > dmabuf->dmasize) { + dmabuf->count = dmabuf->dmasize; + dmabuf->swptr = dmabuf->hwptr; + } + avail = dmabuf->count; + avail -= (dmabuf->hwptr % dmabuf->fragsize); + if (avail < 0) + return (0); + return (avail); +} + +static int drain_dac(struct ali_state *state, int signals_allowed) +{ + + DECLARE_WAITQUEUE(wait, current); + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + unsigned long tmo; + int count; + if (!dmabuf->ready) + return 0; + if (dmabuf->mapped) { + stop_dac(state); + return 0; + } + add_wait_queue(&dmabuf->wait, &wait); + for (;;) { + + spin_lock_irqsave(&state->card->lock, flags); + ali_update_ptr(state); + count = dmabuf->count; + spin_unlock_irqrestore(&state->card->lock, flags); + if (count <= 0) + break; + /* + * This will make sure that our LVI is correct, that our + * pointer is updated, and that the DAC is running. We + * have to force the setting of dmabuf->trigger to avoid + * any possible deadlocks. + */ + if (!dmabuf->enable) { + dmabuf->trigger = PCM_ENABLE_OUTPUT; + ali_update_lvi(state, 0); + } + if (signal_pending(current) && signals_allowed) { + break; + } + + /* It seems that we have to set the current state to + * TASK_INTERRUPTIBLE every time to make the process + * really go to sleep. This also has to be *after* the + * update_ptr() call because update_ptr is likely to + * do a wake_up() which will unset this before we ever + * try to sleep, resuling in a tight loop in this code + * instead of actually sleeping and waiting for an + * interrupt to wake us up! + */ + set_current_state(TASK_INTERRUPTIBLE); + /* + * set the timeout to significantly longer than it *should* + * take for the DAC to drain the DMA buffer + */ + tmo = (count * HZ) / (dmabuf->rate); + if (!schedule_timeout(tmo >= 2 ? tmo : 2)) { + printk(KERN_ERR "ali_audio: drain_dac, dma timeout?\n"); + count = 0; + break; + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&dmabuf->wait, &wait); + if (count > 0 && signal_pending(current) && signals_allowed) + return -ERESTARTSYS; + stop_dac(state); + return 0; +} + + +static int drain_spdifout(struct ali_state *state, int signals_allowed) +{ + + DECLARE_WAITQUEUE(wait, current); + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + unsigned long tmo; + int count; + if (!dmabuf->ready) + return 0; + if (dmabuf->mapped) { + stop_spdifout(state); + return 0; + } + add_wait_queue(&dmabuf->wait, &wait); + for (;;) { + + spin_lock_irqsave(&state->card->lock, flags); + ali_update_ptr(state); + count = dmabuf->count; + spin_unlock_irqrestore(&state->card->lock, flags); + if (count <= 0) + break; + /* + * This will make sure that our LVI is correct, that our + * pointer is updated, and that the DAC is running. We + * have to force the setting of dmabuf->trigger to avoid + * any possible deadlocks. + */ + if (!dmabuf->enable) { + if (codec_independent_spdif_locked > 0) { + dmabuf->trigger = SPDIF_ENABLE_OUTPUT; + ali_update_lvi(state, 2); + } else { + if (controller_independent_spdif_locked > 0) { + dmabuf->trigger = SPDIF_ENABLE_OUTPUT; + ali_update_lvi(state, 3); + } + } + } + if (signal_pending(current) && signals_allowed) { + break; + } + + /* It seems that we have to set the current state to + * TASK_INTERRUPTIBLE every time to make the process + * really go to sleep. This also has to be *after* the + * update_ptr() call because update_ptr is likely to + * do a wake_up() which will unset this before we ever + * try to sleep, resuling in a tight loop in this code + * instead of actually sleeping and waiting for an + * interrupt to wake us up! + */ + set_current_state(TASK_INTERRUPTIBLE); + /* + * set the timeout to significantly longer than it *should* + * take for the DAC to drain the DMA buffer + */ + tmo = (count * HZ) / (dmabuf->rate); + if (!schedule_timeout(tmo >= 2 ? tmo : 2)) { + printk(KERN_ERR "ali_audio: drain_spdifout, dma timeout?\n"); + count = 0; + break; + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&dmabuf->wait, &wait); + if (count > 0 && signal_pending(current) && signals_allowed) + return -ERESTARTSYS; + stop_spdifout(state); + return 0; +} + +static void ali_channel_interrupt(struct ali_card *card) +{ + int i, count; + + for (i = 0; i < NR_HW_CH; i++) { + struct ali_state *state = card->states[i]; + struct ali_channel *c = NULL; + struct dmabuf *dmabuf; + unsigned long port = card->iobase; + u16 status; + if (!state) + continue; + if (!state->dmabuf.ready) + continue; + dmabuf = &state->dmabuf; + if (codec_independent_spdif_locked > 0) { + if (dmabuf->enable & CODEC_SPDIFOUT_RUNNING) { + c = dmabuf->codec_spdifout_channel; + } + } else { + if (controller_independent_spdif_locked > 0) { + if (dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING) + c = dmabuf->controller_spdifout_channel; + } else { + if (dmabuf->enable & DAC_RUNNING) { + c = dmabuf->write_channel; + } else if (dmabuf->enable & ADC_RUNNING) { + c = dmabuf->read_channel; + } else + continue; + } + } + port += c->port; + + status = inw(port + OFF_SR); + + if (status & DMA_INT_COMPLETE) { + /* only wake_up() waiters if this interrupt signals + * us being beyond a userfragsize of data open or + * available, and ali_update_ptr() does that for + * us + */ + ali_update_ptr(state); + } + + if (status & DMA_INT_LVI) { + ali_update_ptr(state); + wake_up(&dmabuf->wait); + + if (dmabuf->enable & DAC_RUNNING) + count = dmabuf->count; + else if (dmabuf->enable & ADC_RUNNING) + count = dmabuf->dmasize - dmabuf->count; + else if (dmabuf->enable & CODEC_SPDIFOUT_RUNNING) + count = dmabuf->count; + else if (dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING) + count = dmabuf->count; + else count = 0; + + if (count > 0) { + if (dmabuf->enable & DAC_RUNNING) + outl((1 << 1), state->card->iobase + ALI_DMACR); + else if (dmabuf->enable & CODEC_SPDIFOUT_RUNNING) + outl((1 << 3), state->card->iobase + ALI_DMACR); + else if (dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING) + outl((1 << 7), state->card->iobase + ALI_DMACR); + } else { + if (dmabuf->enable & DAC_RUNNING) + __stop_dac(state); + if (dmabuf->enable & ADC_RUNNING) + __stop_adc(state); + if (dmabuf->enable & CODEC_SPDIFOUT_RUNNING) + __stop_spdifout(state); + if (dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING) + __stop_spdifout(state); + dmabuf->enable = 0; + wake_up(&dmabuf->wait); + } + + } + if (!(status & DMA_INT_DCH)) { + ali_update_ptr(state); + wake_up(&dmabuf->wait); + if (dmabuf->enable & DAC_RUNNING) + count = dmabuf->count; + else if (dmabuf->enable & ADC_RUNNING) + count = dmabuf->dmasize - dmabuf->count; + else if (dmabuf->enable & CODEC_SPDIFOUT_RUNNING) + count = dmabuf->count; + else if (dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING) + count = dmabuf->count; + else + count = 0; + + if (count > 0) { + if (dmabuf->enable & DAC_RUNNING) + outl((1 << 1), state->card->iobase + ALI_DMACR); + else if (dmabuf->enable & CODEC_SPDIFOUT_RUNNING) + outl((1 << 3), state->card->iobase + ALI_DMACR); + else if (dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING) + outl((1 << 7), state->card->iobase + ALI_DMACR); + } else { + if (dmabuf->enable & DAC_RUNNING) + __stop_dac(state); + if (dmabuf->enable & ADC_RUNNING) + __stop_adc(state); + if (dmabuf->enable & CODEC_SPDIFOUT_RUNNING) + __stop_spdifout(state); + if (dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING) + __stop_spdifout(state); + dmabuf->enable = 0; + wake_up(&dmabuf->wait); + } + } + outw(status & DMA_INT_MASK, port + OFF_SR); + } +} + +static irqreturn_t ali_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct ali_card *card = (struct ali_card *) dev_id; + u32 status; + u16 status2; + + spin_lock(&card->lock); + status = inl(card->iobase + ALI_INTERRUPTSR); + if (!(status & INT_MASK)) { + spin_unlock(&card->lock); + return IRQ_NONE; /* not for us */ + } + + if (codec_independent_spdif_locked > 0) { + if (globel == 0) { + globel += 1; + status2 = inw(card->iobase + 0x76); + outw(status2 | 0x000c, card->iobase + 0x76); + } else { + if (status & (INT_PCMOUT | INT_PCMIN | INT_MICIN | INT_SPDIFOUT | INT_CODECSPDIFOUT)) + ali_channel_interrupt(card); + } + } else { + if (status & (INT_PCMOUT | INT_PCMIN | INT_MICIN | INT_SPDIFOUT | INT_CODECSPDIFOUT)) + ali_channel_interrupt(card); + } + + /* clear 'em */ + outl(status & INT_MASK, card->iobase + ALI_INTERRUPTSR); + spin_unlock(&card->lock); + return IRQ_HANDLED; +} + +/* in this loop, dmabuf.count signifies the amount of data that is + waiting to be copied to the user's buffer. It is filled by the dma + machine and drained by this loop. */ + +static ssize_t ali_read(struct file *file, char __user *buffer, + size_t count, loff_t * ppos) +{ + struct ali_state *state = (struct ali_state *) file->private_data; + struct ali_card *card = state ? state->card : NULL; + struct dmabuf *dmabuf = &state->dmabuf; + ssize_t ret; + unsigned long flags; + unsigned int swptr; + int cnt; + DECLARE_WAITQUEUE(waita, current); +#ifdef DEBUG2 + printk("ali_audio: ali_read called, count = %d\n", count); +#endif + if (dmabuf->mapped) + return -ENXIO; + if (dmabuf->enable & DAC_RUNNING) + return -ENODEV; + if (!dmabuf->read_channel) { + dmabuf->ready = 0; + dmabuf->read_channel = card->alloc_rec_pcm_channel(card); + if (!dmabuf->read_channel) { + return -EBUSY; + } + } + if (!dmabuf->ready && (ret = prog_dmabuf(state, 1))) + return ret; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; + add_wait_queue(&dmabuf->wait, &waita); + while (count > 0) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&card->lock, flags); + if (PM_SUSPENDED(card)) { + spin_unlock_irqrestore(&card->lock, flags); + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -EAGAIN; + break; + } + continue; + } + swptr = dmabuf->swptr; + cnt = ali_get_available_read_data(state); + // this is to make the copy_to_user simpler below + if (cnt > (dmabuf->dmasize - swptr)) + cnt = dmabuf->dmasize - swptr; + spin_unlock_irqrestore(&card->lock, flags); + if (cnt > count) + cnt = count; + /* Lop off the last two bits to force the code to always + * write in full samples. This keeps software that sets + * O_NONBLOCK but doesn't check the return value of the + * write call from getting things out of state where they + * think a full 4 byte sample was written when really only + * a portion was, resulting in odd sound and stereo + * hysteresis. + */ + cnt &= ~0x3; + if (cnt <= 0) { + unsigned long tmo; + /* + * Don't let us deadlock. The ADC won't start if + * dmabuf->trigger isn't set. A call to SETTRIGGER + * could have turned it off after we set it to on + * previously. + */ + dmabuf->trigger = PCM_ENABLE_INPUT; + /* + * This does three things. Updates LVI to be correct, + * makes sure the ADC is running, and updates the + * hwptr. + */ + ali_update_lvi(state, 1); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto done; + } + /* Set the timeout to how long it would take to fill + * two of our buffers. If we haven't been woke up + * by then, then we know something is wrong. + */ + tmo = (dmabuf->dmasize * HZ * 2) / (dmabuf->rate * 4); + + /* There are two situations when sleep_on_timeout returns, one is when + the interrupt is serviced correctly and the process is waked up by + ISR ON TIME. Another is when timeout is expired, which means that + either interrupt is NOT serviced correctly (pending interrupt) or it + is TOO LATE for the process to be scheduled to run (scheduler latency) + which results in a (potential) buffer overrun. And worse, there is + NOTHING we can do to prevent it. */ + if (!schedule_timeout(tmo >= 2 ? tmo : 2)) { + printk(KERN_ERR + "ali_audio: recording schedule timeout, " + "dmasz %u fragsz %u count %i hwptr %u swptr %u\n", + dmabuf->dmasize, dmabuf->fragsize, + dmabuf->count, dmabuf->hwptr, + dmabuf->swptr); + /* a buffer overrun, we delay the recovery until next time the + while loop begin and we REALLY have space to record */ + } + if (signal_pending(current)) { + ret = ret ? ret : -ERESTARTSYS; + goto done; + } + continue; + } + + if (copy_to_user(buffer, dmabuf->rawbuf + swptr, cnt)) { + if (!ret) + ret = -EFAULT; + goto done; + } + + swptr = (swptr + cnt) % dmabuf->dmasize; + spin_lock_irqsave(&card->lock, flags); + if (PM_SUSPENDED(card)) { + spin_unlock_irqrestore(&card->lock, flags); + continue; + } + dmabuf->swptr = swptr; + dmabuf->count -= cnt; + spin_unlock_irqrestore(&card->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + } +done: + ali_update_lvi(state, 1); + set_current_state(TASK_RUNNING); + remove_wait_queue(&dmabuf->wait, &waita); + return ret; +} + +/* in this loop, dmabuf.count signifies the amount of data that is waiting to be dma to + the soundcard. it is drained by the dma machine and filled by this loop. */ +static ssize_t ali_write(struct file *file, + const char __user *buffer, size_t count, loff_t * ppos) +{ + struct ali_state *state = (struct ali_state *) file->private_data; + struct ali_card *card = state ? state->card : NULL; + struct dmabuf *dmabuf = &state->dmabuf; + ssize_t ret; + unsigned long flags; + unsigned int swptr = 0; + int cnt, x; + DECLARE_WAITQUEUE(waita, current); +#ifdef DEBUG2 + printk("ali_audio: ali_write called, count = %d\n", count); +#endif + if (dmabuf->mapped) + return -ENXIO; + if (dmabuf->enable & ADC_RUNNING) + return -ENODEV; + if (codec_independent_spdif_locked > 0) { + if (!dmabuf->codec_spdifout_channel) { + dmabuf->ready = 0; + dmabuf->codec_spdifout_channel = card->alloc_codec_spdifout_channel(card); + if (!dmabuf->codec_spdifout_channel) + return -EBUSY; + } + } else { + if (controller_independent_spdif_locked > 0) { + if (!dmabuf->controller_spdifout_channel) { + dmabuf->ready = 0; + dmabuf->controller_spdifout_channel = card->alloc_controller_spdifout_channel(card); + if (!dmabuf->controller_spdifout_channel) + return -EBUSY; + } + } else { + if (!dmabuf->write_channel) { + dmabuf->ready = 0; + dmabuf->write_channel = + card->alloc_pcm_channel(card); + if (!dmabuf->write_channel) + return -EBUSY; + } + } + } + + if (codec_independent_spdif_locked > 0) { + if (!dmabuf->ready && (ret = prog_dmabuf(state, 2))) + return ret; + } else { + if (controller_independent_spdif_locked > 0) { + if (!dmabuf->ready && (ret = prog_dmabuf(state, 3))) + return ret; + } else { + + if (!dmabuf->ready && (ret = prog_dmabuf(state, 0))) + return ret; + } + } + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; + add_wait_queue(&dmabuf->wait, &waita); + while (count > 0) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&state->card->lock, flags); + if (PM_SUSPENDED(card)) { + spin_unlock_irqrestore(&card->lock, flags); + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -EAGAIN; + break; + } + continue; + } + + swptr = dmabuf->swptr; + cnt = ali_get_free_write_space(state); + /* Bound the maximum size to how much we can copy to the + * dma buffer before we hit the end. If we have more to + * copy then it will get done in a second pass of this + * loop starting from the beginning of the buffer. + */ + if (cnt > (dmabuf->dmasize - swptr)) + cnt = dmabuf->dmasize - swptr; + spin_unlock_irqrestore(&state->card->lock, flags); +#ifdef DEBUG2 + printk(KERN_INFO + "ali_audio: ali_write: %d bytes available space\n", + cnt); +#endif + if (cnt > count) + cnt = count; + /* Lop off the last two bits to force the code to always + * write in full samples. This keeps software that sets + * O_NONBLOCK but doesn't check the return value of the + * write call from getting things out of state where they + * think a full 4 byte sample was written when really only + * a portion was, resulting in odd sound and stereo + * hysteresis. + */ + cnt &= ~0x3; + if (cnt <= 0) { + unsigned long tmo; + // There is data waiting to be played + /* + * Force the trigger setting since we would + * deadlock with it set any other way + */ + if (codec_independent_spdif_locked > 0) { + dmabuf->trigger = SPDIF_ENABLE_OUTPUT; + ali_update_lvi(state, 2); + } else { + if (controller_independent_spdif_locked > 0) { + dmabuf->trigger = SPDIF_ENABLE_OUTPUT; + ali_update_lvi(state, 3); + } else { + + dmabuf->trigger = PCM_ENABLE_OUTPUT; + ali_update_lvi(state, 0); + } + } + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto ret; + } + /* Not strictly correct but works */ + tmo = (dmabuf->dmasize * HZ * 2) / (dmabuf->rate * 4); + /* There are two situations when sleep_on_timeout returns, one is when + the interrupt is serviced correctly and the process is waked up by + ISR ON TIME. Another is when timeout is expired, which means that + either interrupt is NOT serviced correctly (pending interrupt) or it + is TOO LATE for the process to be scheduled to run (scheduler latency) + which results in a (potential) buffer underrun. And worse, there is + NOTHING we can do to prevent it. */ + + /* FIXME - do timeout handling here !! */ + schedule_timeout(tmo >= 2 ? tmo : 2); + + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto ret; + } + continue; + } + if (copy_from_user(dmabuf->rawbuf + swptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + goto ret; + } + + swptr = (swptr + cnt) % dmabuf->dmasize; + spin_lock_irqsave(&state->card->lock, flags); + if (PM_SUSPENDED(card)) { + spin_unlock_irqrestore(&card->lock, flags); + continue; + } + + dmabuf->swptr = swptr; + dmabuf->count += cnt; + count -= cnt; + buffer += cnt; + ret += cnt; + spin_unlock_irqrestore(&state->card->lock, flags); + } + if (swptr % dmabuf->fragsize) { + x = dmabuf->fragsize - (swptr % dmabuf->fragsize); + memset(dmabuf->rawbuf + swptr, '\0', x); + } +ret: + if (codec_independent_spdif_locked > 0) { + ali_update_lvi(state, 2); + } else { + if (controller_independent_spdif_locked > 0) { + ali_update_lvi(state, 3); + } else { + ali_update_lvi(state, 0); + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&dmabuf->wait, &waita); + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int ali_poll(struct file *file, struct poll_table_struct + *wait) +{ + struct ali_state *state = (struct ali_state *) file->private_data; + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + unsigned int mask = 0; + if (!dmabuf->ready) + return 0; + poll_wait(file, &dmabuf->wait, wait); + spin_lock_irqsave(&state->card->lock, flags); + ali_update_ptr(state); + if (file->f_mode & FMODE_READ && dmabuf->enable & ADC_RUNNING) { + if (dmabuf->count >= (signed) dmabuf->fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE && (dmabuf->enable & (DAC_RUNNING|CODEC_SPDIFOUT_RUNNING|CONTROLLER_SPDIFOUT_RUNNING))) { + if ((signed) dmabuf->dmasize >= dmabuf->count + (signed) dmabuf->fragsize) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&state->card->lock, flags); + return mask; +} + +static int ali_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct ali_state *state = (struct ali_state *) file->private_data; + struct dmabuf *dmabuf = &state->dmabuf; + int ret = -EINVAL; + unsigned long size; + lock_kernel(); + if (vma->vm_flags & VM_WRITE) { + if (!dmabuf->write_channel && (dmabuf->write_channel = state->card->alloc_pcm_channel(state->card)) == NULL) { + ret = -EBUSY; + goto out; + } + } + if (vma->vm_flags & VM_READ) { + if (!dmabuf->read_channel && (dmabuf->read_channel = state->card->alloc_rec_pcm_channel(state->card)) == NULL) { + ret = -EBUSY; + goto out; + } + } + if ((ret = prog_dmabuf(state, 0)) != 0) + goto out; + ret = -EINVAL; + if (vma->vm_pgoff != 0) + goto out; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << dmabuf->buforder)) + goto out; + ret = -EAGAIN; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(dmabuf->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + goto out; + dmabuf->mapped = 1; + dmabuf->trigger = 0; + ret = 0; +out: + unlock_kernel(); + return ret; +} + +static int ali_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct ali_state *state = (struct ali_state *) file->private_data; + struct ali_channel *c = NULL; + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + unsigned int i_scr; + int val = 0, ret; + struct ac97_codec *codec = state->card->ac97_codec[0]; + void __user *argp = (void __user *)arg; + int __user *p = argp; + +#ifdef DEBUG + printk("ali_audio: ali_ioctl, arg=0x%x, cmd=", + arg ? *p : 0); +#endif + switch (cmd) { + case OSS_GETVERSION: +#ifdef DEBUG + printk("OSS_GETVERSION\n"); +#endif + return put_user(SOUND_VERSION, p); + case SNDCTL_DSP_RESET: +#ifdef DEBUG + printk("SNDCTL_DSP_RESET\n"); +#endif + spin_lock_irqsave(&state->card->lock, flags); + if (dmabuf->enable == DAC_RUNNING) { + c = dmabuf->write_channel; + __stop_dac(state); + } + if (dmabuf->enable == ADC_RUNNING) { + c = dmabuf->read_channel; + __stop_adc(state); + } + if (dmabuf->enable == CODEC_SPDIFOUT_RUNNING) { + c = dmabuf->codec_spdifout_channel; + __stop_spdifout(state); + } + if (dmabuf->enable == CONTROLLER_SPDIFOUT_RUNNING) { + c = dmabuf->controller_spdifout_channel; + __stop_spdifout(state); + } + if (c != NULL) { + outb(2, state->card->iobase + c->port + OFF_CR); /* reset DMA machine */ + outl(virt_to_bus(&c->sg[0]), + state->card->iobase + c->port + OFF_BDBAR); + outb(0, state->card->iobase + c->port + OFF_CIV); + outb(0, state->card->iobase + c->port + OFF_LVI); + } + + spin_unlock_irqrestore(&state->card->lock, flags); + synchronize_irq(state->card->pci_dev->irq); + dmabuf->ready = 0; + dmabuf->swptr = dmabuf->hwptr = 0; + dmabuf->count = dmabuf->total_bytes = 0; + return 0; + case SNDCTL_DSP_SYNC: +#ifdef DEBUG + printk("SNDCTL_DSP_SYNC\n"); +#endif + if (codec_independent_spdif_locked > 0) { + if (dmabuf->enable != CODEC_SPDIFOUT_RUNNING + || file->f_flags & O_NONBLOCK) + return 0; + if ((val = drain_spdifout(state, 1))) + return val; + } else { + if (controller_independent_spdif_locked > 0) { + if (dmabuf->enable != + CONTROLLER_SPDIFOUT_RUNNING + || file->f_flags & O_NONBLOCK) + return 0; + if ((val = drain_spdifout(state, 1))) + return val; + } else { + if (dmabuf->enable != DAC_RUNNING + || file->f_flags & O_NONBLOCK) + return 0; + if ((val = drain_dac(state, 1))) + return val; + } + } + dmabuf->total_bytes = 0; + return 0; + case SNDCTL_DSP_SPEED: /* set smaple rate */ +#ifdef DEBUG + printk("SNDCTL_DSP_SPEED\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { + if (file->f_mode & FMODE_WRITE) { + if ((state->card->ac97_status & SPDIF_ON)) { /* S/PDIF Enabled */ + /* RELTEK ALC650 only support 48000, need to check that */ + if (ali_valid_spdif_rate(codec, val)) { + if (codec_independent_spdif_locked > 0) { + ali_set_spdif_output(state, -1, 0); + stop_spdifout(state); + dmabuf->ready = 0; + /* I add test codec independent spdif out */ + spin_lock_irqsave(&state->card->lock, flags); + ali_set_codecspdifout_rate(state, val); // I modified + spin_unlock_irqrestore(&state->card->lock, flags); + /* Set S/PDIF transmitter rate. */ + i_scr = inl(state->card->iobase + ALI_SCR); + if ((i_scr & 0x00300000) == 0x00100000) { + ali_set_spdif_output(state, AC97_EA_SPSA_7_8, codec_independent_spdif_locked); + } else { + if ((i_scr&0x00300000) == 0x00200000) + { + ali_set_spdif_output(state, AC97_EA_SPSA_6_9, codec_independent_spdif_locked); + } else { + if ((i_scr & 0x00300000) == 0x00300000) { + ali_set_spdif_output(state, AC97_EA_SPSA_10_11, codec_independent_spdif_locked); + } else { + ali_set_spdif_output(state, AC97_EA_SPSA_7_8, codec_independent_spdif_locked); + } + } + } + + if (!(state->card->ac97_status & SPDIF_ON)) { + val = dmabuf->rate; + } + } else { + if (controller_independent_spdif_locked > 0) + { + stop_spdifout(state); + dmabuf->ready = 0; + spin_lock_irqsave(&state->card->lock, flags); + ali_set_spdifout_rate(state, controller_independent_spdif_locked); + spin_unlock_irqrestore(&state->card->lock, flags); + } else { + /* Set DAC rate */ + ali_set_spdif_output(state, -1, 0); + stop_dac(state); + dmabuf->ready = 0; + spin_lock_irqsave(&state->card->lock, flags); + ali_set_dac_rate(state, val); + spin_unlock_irqrestore(&state->card->lock, flags); + /* Set S/PDIF transmitter rate. */ + ali_set_spdif_output(state, AC97_EA_SPSA_3_4, val); + if (!(state->card->ac97_status & SPDIF_ON)) + { + val = dmabuf->rate; + } + } + } + } else { /* Not a valid rate for S/PDIF, ignore it */ + val = dmabuf->rate; + } + } else { + stop_dac(state); + dmabuf->ready = 0; + spin_lock_irqsave(&state->card->lock, flags); + ali_set_dac_rate(state, val); + spin_unlock_irqrestore(&state->card->lock, flags); + } + } + if (file->f_mode & FMODE_READ) { + stop_adc(state); + dmabuf->ready = 0; + spin_lock_irqsave(&state->card->lock, flags); + ali_set_adc_rate(state, val); + spin_unlock_irqrestore(&state->card->lock, flags); + } + } + return put_user(dmabuf->rate, p); + case SNDCTL_DSP_STEREO: /* set stereo or mono channel */ +#ifdef DEBUG + printk("SNDCTL_DSP_STEREO\n"); +#endif + if (dmabuf->enable & DAC_RUNNING) { + stop_dac(state); + } + if (dmabuf->enable & ADC_RUNNING) { + stop_adc(state); + } + if (dmabuf->enable & CODEC_SPDIFOUT_RUNNING) { + stop_spdifout(state); + } + if (dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING) { + stop_spdifout(state); + } + return put_user(1, p); + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + if (codec_independent_spdif_locked > 0) { + if (!dmabuf->ready && (val = prog_dmabuf(state, 2))) + return val; + } else { + if (controller_independent_spdif_locked > 0) { + if (!dmabuf->ready && (val = prog_dmabuf(state, 3))) + return val; + } else { + if (!dmabuf->ready && (val = prog_dmabuf(state, 0))) + return val; + } + } + } + + if (file->f_mode & FMODE_READ) { + if (!dmabuf->ready && (val = prog_dmabuf(state, 1))) + return val; + } +#ifdef DEBUG + printk("SNDCTL_DSP_GETBLKSIZE %d\n", dmabuf->userfragsize); +#endif + return put_user(dmabuf->userfragsize, p); + case SNDCTL_DSP_GETFMTS: /* Returns a mask of supported sample format */ +#ifdef DEBUG + printk("SNDCTL_DSP_GETFMTS\n"); +#endif + return put_user(AFMT_S16_LE, p); + case SNDCTL_DSP_SETFMT: /* Select sample format */ +#ifdef DEBUG + printk("SNDCTL_DSP_SETFMT\n"); +#endif + return put_user(AFMT_S16_LE, p); + case SNDCTL_DSP_CHANNELS: // add support 4,6 channel +#ifdef DEBUG + printk("SNDCTL_DSP_CHANNELS\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + if (val > 0) { + if (dmabuf->enable & DAC_RUNNING) { + stop_dac(state); + } + if (dmabuf->enable & CODEC_SPDIFOUT_RUNNING) { + stop_spdifout(state); + } + if (dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING) { + stop_spdifout(state); + } + if (dmabuf->enable & ADC_RUNNING) { + stop_adc(state); + } + } else { + return put_user(state->card->channels, p); + } + + i_scr = inl(state->card->iobase + ALI_SCR); + /* Current # of channels enabled */ + if (i_scr & 0x00000100) + ret = 4; + else if (i_scr & 0x00000200) + ret = 6; + else + ret = 2; + switch (val) { + case 2: /* 2 channels is always supported */ + if (codec_independent_spdif_locked > 0) { + outl(((i_scr & 0xfffffcff) | 0x00100000), (state->card->iobase + ALI_SCR)); + } else + outl((i_scr & 0xfffffcff), (state->card->iobase + ALI_SCR)); + /* Do we need to change mixer settings???? */ + break; + case 4: /* Supported on some chipsets, better check first */ + if (codec_independent_spdif_locked > 0) { + outl(((i_scr & 0xfffffcff) | 0x00000100 | 0x00200000), (state->card->iobase + ALI_SCR)); + } else + outl(((i_scr & 0xfffffcff) | 0x00000100), (state->card->iobase + ALI_SCR)); + break; + case 6: /* Supported on some chipsets, better check first */ + if (codec_independent_spdif_locked > 0) { + outl(((i_scr & 0xfffffcff) | 0x00000200 | 0x00008000 | 0x00300000), (state->card->iobase + ALI_SCR)); + } else + outl(((i_scr & 0xfffffcff) | 0x00000200 | 0x00008000), (state->card->iobase + ALI_SCR)); + break; + default: /* nothing else is ever supported by the chipset */ + val = ret; + break; + } + return put_user(val, p); + case SNDCTL_DSP_POST: /* the user has sent all data and is notifying us */ + /* we update the swptr to the end of the last sg segment then return */ +#ifdef DEBUG + printk("SNDCTL_DSP_POST\n"); +#endif + if (codec_independent_spdif_locked > 0) { + if (!dmabuf->ready || (dmabuf->enable != CODEC_SPDIFOUT_RUNNING)) + return 0; + } else { + if (controller_independent_spdif_locked > 0) { + if (!dmabuf->ready || (dmabuf->enable != CONTROLLER_SPDIFOUT_RUNNING)) + return 0; + } else { + if (!dmabuf->ready || (dmabuf->enable != DAC_RUNNING)) + return 0; + } + } + if ((dmabuf->swptr % dmabuf->fragsize) != 0) { + val = dmabuf->fragsize - (dmabuf->swptr % dmabuf->fragsize); + dmabuf->swptr += val; + dmabuf->count += val; + } + return 0; + case SNDCTL_DSP_SUBDIVIDE: + if (dmabuf->subdivision) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; +#ifdef DEBUG + printk("SNDCTL_DSP_SUBDIVIDE %d\n", val); +#endif + dmabuf->subdivision = val; + dmabuf->ready = 0; + return 0; + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + dmabuf->ossfragsize = 1 << (val & 0xffff); + dmabuf->ossmaxfrags = (val >> 16) & 0xffff; + if (!dmabuf->ossfragsize || !dmabuf->ossmaxfrags) + return -EINVAL; + /* + * Bound the frag size into our allowed range of 256 - 4096 + */ + if (dmabuf->ossfragsize < 256) + dmabuf->ossfragsize = 256; + else if (dmabuf->ossfragsize > 4096) + dmabuf->ossfragsize = 4096; + /* + * The numfrags could be something reasonable, or it could + * be 0xffff meaning "Give me as much as possible". So, + * we check the numfrags * fragsize doesn't exceed our + * 64k buffer limit, nor is it less than our 8k minimum. + * If it fails either one of these checks, then adjust the + * number of fragments, not the size of them. It's OK if + * our number of fragments doesn't equal 32 or anything + * like our hardware based number now since we are using + * a different frag count for the hardware. Before we get + * into this though, bound the maxfrags to avoid overflow + * issues. A reasonable bound would be 64k / 256 since our + * maximum buffer size is 64k and our minimum frag size is + * 256. On the other end, our minimum buffer size is 8k and + * our maximum frag size is 4k, so the lower bound should + * be 2. + */ + if (dmabuf->ossmaxfrags > 256) + dmabuf->ossmaxfrags = 256; + else if (dmabuf->ossmaxfrags < 2) + dmabuf->ossmaxfrags = 2; + val = dmabuf->ossfragsize * dmabuf->ossmaxfrags; + while (val < 8192) { + val <<= 1; + dmabuf->ossmaxfrags <<= 1; + } + while (val > 65536) { + val >>= 1; + dmabuf->ossmaxfrags >>= 1; + } + dmabuf->ready = 0; +#ifdef DEBUG + printk("SNDCTL_DSP_SETFRAGMENT 0x%x, %d, %d\n", val, + dmabuf->ossfragsize, dmabuf->ossmaxfrags); +#endif + return 0; + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (codec_independent_spdif_locked > 0) { + if (!dmabuf->ready && (val = prog_dmabuf(state, 2)) != 0) + return val; + } else { + if (controller_independent_spdif_locked > 0) { + if (!dmabuf->ready && (val = prog_dmabuf(state, 3)) != 0) + return val; + } else { + if (!dmabuf->ready && (val = prog_dmabuf(state, 0)) != 0) + return val; + } + } + spin_lock_irqsave(&state->card->lock, flags); + ali_update_ptr(state); + abinfo.fragsize = dmabuf->userfragsize; + abinfo.fragstotal = dmabuf->userfrags; + if (dmabuf->mapped) + abinfo.bytes = dmabuf->dmasize; + else + abinfo.bytes = ali_get_free_write_space(state); + abinfo.fragments = abinfo.bytes / dmabuf->userfragsize; + spin_unlock_irqrestore(&state->card->lock, flags); +#if defined(DEBUG) || defined(DEBUG_MMAP) + printk("SNDCTL_DSP_GETOSPACE %d, %d, %d, %d\n", + abinfo.bytes, abinfo.fragsize, abinfo.fragments, + abinfo.fragstotal); +#endif + return copy_to_user(argp, &abinfo, + sizeof(abinfo)) ? -EFAULT : 0; + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (codec_independent_spdif_locked > 0) { + if (!dmabuf->ready && (val = prog_dmabuf(state, 2)) != 0) + return val; + } else { + if (controller_independent_spdif_locked > 0) { + if (!dmabuf->ready && (val = prog_dmabuf(state, 3)) != 0) + return val; + } else { + if (!dmabuf->ready && (val = prog_dmabuf(state, 0)) != 0) + return val; + } + } + spin_lock_irqsave(&state->card->lock, flags); + val = ali_get_free_write_space(state); + cinfo.bytes = dmabuf->total_bytes; + cinfo.ptr = dmabuf->hwptr; + cinfo.blocks = val / dmabuf->userfragsize; + if (codec_independent_spdif_locked > 0) { + if (dmabuf->mapped && (dmabuf->trigger & SPDIF_ENABLE_OUTPUT)) { + dmabuf->count += val; + dmabuf->swptr = (dmabuf->swptr + val) % dmabuf->dmasize; + __ali_update_lvi(state, 2); + } + } else { + if (controller_independent_spdif_locked > 0) { + if (dmabuf->mapped && (dmabuf->trigger & SPDIF_ENABLE_OUTPUT)) { + dmabuf->count += val; + dmabuf->swptr = (dmabuf->swptr + val) % dmabuf->dmasize; + __ali_update_lvi(state, 3); + } + } else { + if (dmabuf->mapped && (dmabuf->trigger & PCM_ENABLE_OUTPUT)) { + dmabuf->count += val; + dmabuf->swptr = (dmabuf->swptr + val) % dmabuf->dmasize; + __ali_update_lvi(state, 0); + } + } + } + spin_unlock_irqrestore(&state->card->lock, flags); +#if defined(DEBUG) || defined(DEBUG_MMAP) + printk("SNDCTL_DSP_GETOPTR %d, %d, %d, %d\n", cinfo.bytes, + cinfo.blocks, cinfo.ptr, dmabuf->count); +#endif + return copy_to_user(argp, &cinfo, sizeof(cinfo))? -EFAULT : 0; + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!dmabuf->ready && (val = prog_dmabuf(state, 1)) != 0) + return val; + spin_lock_irqsave(&state->card->lock, flags); + abinfo.bytes = ali_get_available_read_data(state); + abinfo.fragsize = dmabuf->userfragsize; + abinfo.fragstotal = dmabuf->userfrags; + abinfo.fragments = abinfo.bytes / dmabuf->userfragsize; + spin_unlock_irqrestore(&state->card->lock, flags); +#if defined(DEBUG) || defined(DEBUG_MMAP) + printk("SNDCTL_DSP_GETISPACE %d, %d, %d, %d\n", + abinfo.bytes, abinfo.fragsize, abinfo.fragments, + abinfo.fragstotal); +#endif + return copy_to_user(argp, &abinfo, + sizeof(abinfo)) ? -EFAULT : 0; + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!dmabuf->ready && (val = prog_dmabuf(state, 0)) != 0) + return val; + spin_lock_irqsave(&state->card->lock, flags); + val = ali_get_available_read_data(state); + cinfo.bytes = dmabuf->total_bytes; + cinfo.blocks = val / dmabuf->userfragsize; + cinfo.ptr = dmabuf->hwptr; + if (dmabuf->mapped && (dmabuf->trigger & PCM_ENABLE_INPUT)) { + dmabuf->count -= val; + dmabuf->swptr = (dmabuf->swptr + val) % dmabuf->dmasize; + __ali_update_lvi(state, 1); + } + spin_unlock_irqrestore(&state->card->lock, flags); +#if defined(DEBUG) || defined(DEBUG_MMAP) + printk("SNDCTL_DSP_GETIPTR %d, %d, %d, %d\n", cinfo.bytes, + cinfo.blocks, cinfo.ptr, dmabuf->count); +#endif + return copy_to_user(argp, &cinfo, sizeof(cinfo))? -EFAULT: 0; + case SNDCTL_DSP_NONBLOCK: +#ifdef DEBUG + printk("SNDCTL_DSP_NONBLOCK\n"); +#endif + file->f_flags |= O_NONBLOCK; + return 0; + case SNDCTL_DSP_GETCAPS: +#ifdef DEBUG + printk("SNDCTL_DSP_GETCAPS\n"); +#endif + return put_user(DSP_CAP_REALTIME | DSP_CAP_TRIGGER | + DSP_CAP_MMAP | DSP_CAP_BIND, p); + case SNDCTL_DSP_GETTRIGGER: + val = 0; +#ifdef DEBUG + printk("SNDCTL_DSP_GETTRIGGER 0x%x\n", dmabuf->trigger); +#endif + return put_user(dmabuf->trigger, p); + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + return -EFAULT; +#if defined(DEBUG) || defined(DEBUG_MMAP) + printk("SNDCTL_DSP_SETTRIGGER 0x%x\n", val); +#endif + if (!(val & PCM_ENABLE_INPUT) && dmabuf->enable == ADC_RUNNING) { + stop_adc(state); + } + if (!(val & PCM_ENABLE_OUTPUT) && dmabuf->enable == DAC_RUNNING) { + stop_dac(state); + } + if (!(val & SPDIF_ENABLE_OUTPUT) && dmabuf->enable == CODEC_SPDIFOUT_RUNNING) { + stop_spdifout(state); + } + if (!(val & SPDIF_ENABLE_OUTPUT) && dmabuf->enable == CONTROLLER_SPDIFOUT_RUNNING) { + stop_spdifout(state); + } + dmabuf->trigger = val; + if (val & PCM_ENABLE_OUTPUT && !(dmabuf->enable & DAC_RUNNING)) { + if (!dmabuf->write_channel) { + dmabuf->ready = 0; + dmabuf->write_channel = state->card->alloc_pcm_channel(state->card); + if (!dmabuf->write_channel) + return -EBUSY; + } + if (!dmabuf->ready && (ret = prog_dmabuf(state, 0))) + return ret; + if (dmabuf->mapped) { + spin_lock_irqsave(&state->card->lock, flags); + ali_update_ptr(state); + dmabuf->count = 0; + dmabuf->swptr = dmabuf->hwptr; + dmabuf->count = ali_get_free_write_space(state); + dmabuf->swptr = (dmabuf->swptr + dmabuf->count) % dmabuf->dmasize; + __ali_update_lvi(state, 0); + spin_unlock_irqrestore(&state->card->lock, + flags); + } else + start_dac(state); + } + if (val & SPDIF_ENABLE_OUTPUT && !(dmabuf->enable & CODEC_SPDIFOUT_RUNNING)) { + if (!dmabuf->codec_spdifout_channel) { + dmabuf->ready = 0; + dmabuf->codec_spdifout_channel = state->card->alloc_codec_spdifout_channel(state->card); + if (!dmabuf->codec_spdifout_channel) + return -EBUSY; + } + if (!dmabuf->ready && (ret = prog_dmabuf(state, 2))) + return ret; + if (dmabuf->mapped) { + spin_lock_irqsave(&state->card->lock, flags); + ali_update_ptr(state); + dmabuf->count = 0; + dmabuf->swptr = dmabuf->hwptr; + dmabuf->count = ali_get_free_write_space(state); + dmabuf->swptr = (dmabuf->swptr + dmabuf->count) % dmabuf->dmasize; + __ali_update_lvi(state, 2); + spin_unlock_irqrestore(&state->card->lock, + flags); + } else + start_spdifout(state); + } + if (val & SPDIF_ENABLE_OUTPUT && !(dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING)) { + if (!dmabuf->controller_spdifout_channel) { + dmabuf->ready = 0; + dmabuf->controller_spdifout_channel = state->card->alloc_controller_spdifout_channel(state->card); + if (!dmabuf->controller_spdifout_channel) + return -EBUSY; + } + if (!dmabuf->ready && (ret = prog_dmabuf(state, 3))) + return ret; + if (dmabuf->mapped) { + spin_lock_irqsave(&state->card->lock, flags); + ali_update_ptr(state); + dmabuf->count = 0; + dmabuf->swptr = dmabuf->hwptr; + dmabuf->count = ali_get_free_write_space(state); + dmabuf->swptr = (dmabuf->swptr + dmabuf->count) % dmabuf->dmasize; + __ali_update_lvi(state, 3); + spin_unlock_irqrestore(&state->card->lock, flags); + } else + start_spdifout(state); + } + if (val & PCM_ENABLE_INPUT && !(dmabuf->enable & ADC_RUNNING)) { + if (!dmabuf->read_channel) { + dmabuf->ready = 0; + dmabuf->read_channel = state->card->alloc_rec_pcm_channel(state->card); + if (!dmabuf->read_channel) + return -EBUSY; + } + if (!dmabuf->ready && (ret = prog_dmabuf(state, 1))) + return ret; + if (dmabuf->mapped) { + spin_lock_irqsave(&state->card->lock, + flags); + ali_update_ptr(state); + dmabuf->swptr = dmabuf->hwptr; + dmabuf->count = 0; + spin_unlock_irqrestore(&state->card->lock, flags); + } + ali_update_lvi(state, 1); + start_adc(state); + } + return 0; + case SNDCTL_DSP_SETDUPLEX: +#ifdef DEBUG + printk("SNDCTL_DSP_SETDUPLEX\n"); +#endif + return -EINVAL; + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&state->card->lock, flags); + ali_update_ptr(state); + val = dmabuf->count; + spin_unlock_irqrestore(&state->card->lock, flags); +#ifdef DEBUG + printk("SNDCTL_DSP_GETODELAY %d\n", dmabuf->count); +#endif + return put_user(val, p); + case SOUND_PCM_READ_RATE: +#ifdef DEBUG + printk("SOUND_PCM_READ_RATE %d\n", dmabuf->rate); +#endif + return put_user(dmabuf->rate, p); + case SOUND_PCM_READ_CHANNELS: +#ifdef DEBUG + printk("SOUND_PCM_READ_CHANNELS\n"); +#endif + return put_user(2, p); + case SOUND_PCM_READ_BITS: +#ifdef DEBUG + printk("SOUND_PCM_READ_BITS\n"); +#endif + return put_user(AFMT_S16_LE, p); + case SNDCTL_DSP_SETSPDIF: /* Set S/PDIF Control register */ +#ifdef DEBUG + printk("SNDCTL_DSP_SETSPDIF\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + /* Check to make sure the codec supports S/PDIF transmitter */ + if ((state->card->ac97_features & 4)) { + /* mask out the transmitter speed bits so the user can't set them */ + val &= ~0x3000; + /* Add the current transmitter speed bits to the passed value */ + ret = ali_ac97_get(codec, AC97_SPDIF_CONTROL); + val |= (ret & 0x3000); + ali_ac97_set(codec, AC97_SPDIF_CONTROL, val); + if (ali_ac97_get(codec, AC97_SPDIF_CONTROL) != val) { + printk(KERN_ERR "ali_audio: Unable to set S/PDIF configuration to 0x%04x.\n", val); + return -EFAULT; + } + } +#ifdef DEBUG + else + printk(KERN_WARNING "ali_audio: S/PDIF transmitter not avalible.\n"); +#endif + return put_user(val, p); + case SNDCTL_DSP_GETSPDIF: /* Get S/PDIF Control register */ +#ifdef DEBUG + printk("SNDCTL_DSP_GETSPDIF\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + /* Check to make sure the codec supports S/PDIF transmitter */ + if (!(state->card->ac97_features & 4)) { +#ifdef DEBUG + printk(KERN_WARNING "ali_audio: S/PDIF transmitter not avalible.\n"); +#endif + val = 0; + } else { + val = ali_ac97_get(codec, AC97_SPDIF_CONTROL); + } + + return put_user(val, p); +//end add support spdif out +//add support 4,6 channel + case SNDCTL_DSP_GETCHANNELMASK: +#ifdef DEBUG + printk("SNDCTL_DSP_GETCHANNELMASK\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + /* Based on AC'97 DAC support, not ICH hardware */ + val = DSP_BIND_FRONT; + if (state->card->ac97_features & 0x0004) + val |= DSP_BIND_SPDIF; + if (state->card->ac97_features & 0x0080) + val |= DSP_BIND_SURR; + if (state->card->ac97_features & 0x0140) + val |= DSP_BIND_CENTER_LFE; + return put_user(val, p); + case SNDCTL_DSP_BIND_CHANNEL: +#ifdef DEBUG + printk("SNDCTL_DSP_BIND_CHANNEL\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + if (val == DSP_BIND_QUERY) { + val = DSP_BIND_FRONT; /* Always report this as being enabled */ + if (state->card->ac97_status & SPDIF_ON) + val |= DSP_BIND_SPDIF; + else { + if (state->card->ac97_status & SURR_ON) + val |= DSP_BIND_SURR; + if (state->card-> + ac97_status & CENTER_LFE_ON) + val |= DSP_BIND_CENTER_LFE; + } + } else { /* Not a query, set it */ + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (dmabuf->enable == DAC_RUNNING) { + stop_dac(state); + } + if (val & DSP_BIND_SPDIF) { /* Turn on SPDIF */ + /* Ok, this should probably define what slots + * to use. For now, we'll only set it to the + * defaults: + * + * non multichannel codec maps to slots 3&4 + * 2 channel codec maps to slots 7&8 + * 4 channel codec maps to slots 6&9 + * 6 channel codec maps to slots 10&11 + * + * there should be some way for the app to + * select the slot assignment. + */ + i_scr = inl(state->card->iobase + ALI_SCR); + if (codec_independent_spdif_locked > 0) { + + if ((i_scr & 0x00300000) == 0x00100000) { + ali_set_spdif_output(state, AC97_EA_SPSA_7_8, codec_independent_spdif_locked); + } else { + if ((i_scr & 0x00300000) == 0x00200000) { + ali_set_spdif_output(state, AC97_EA_SPSA_6_9, codec_independent_spdif_locked); + } else { + if ((i_scr & 0x00300000) == 0x00300000) { + ali_set_spdif_output(state, AC97_EA_SPSA_10_11, codec_independent_spdif_locked); + } + } + } + } else { /* codec spdif out (pcm out share ) */ + ali_set_spdif_output(state, AC97_EA_SPSA_3_4, dmabuf->rate); //I do not modify + } + + if (!(state->card->ac97_status & SPDIF_ON)) + val &= ~DSP_BIND_SPDIF; + } else { + int mask; + int channels; + /* Turn off S/PDIF if it was on */ + if (state->card->ac97_status & SPDIF_ON) + ali_set_spdif_output(state, -1, 0); + mask = + val & (DSP_BIND_FRONT | DSP_BIND_SURR | + DSP_BIND_CENTER_LFE); + switch (mask) { + case DSP_BIND_FRONT: + channels = 2; + break; + case DSP_BIND_FRONT | DSP_BIND_SURR: + channels = 4; + break; + case DSP_BIND_FRONT | DSP_BIND_SURR | DSP_BIND_CENTER_LFE: + channels = 6; + break; + default: + val = DSP_BIND_FRONT; + channels = 2; + break; + } + ali_set_dac_channels(state, channels); + /* check that they really got turned on */ + if (!state->card->ac97_status & SURR_ON) + val &= ~DSP_BIND_SURR; + if (!state->card-> + ac97_status & CENTER_LFE_ON) + val &= ~DSP_BIND_CENTER_LFE; + } + } + return put_user(val, p); + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + } + return -EINVAL; +} + +static int ali_open(struct inode *inode, struct file *file) +{ + int i = 0; + struct ali_card *card = devs; + struct ali_state *state = NULL; + struct dmabuf *dmabuf = NULL; + unsigned int i_scr; + + /* find an available virtual channel (instance of /dev/dsp) */ + + while (card != NULL) { + + /* + * If we are initializing and then fail, card could go + * away unuexpectedly while we are in the for() loop. + * So, check for card on each iteration before we check + * for card->initializing to avoid a possible oops. + * This usually only matters for times when the driver is + * autoloaded by kmod. + */ + for (i = 0; i < 50 && card && card->initializing; i++) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ / 20); + } + + for (i = 0; i < NR_HW_CH && card && !card->initializing; i++) { + if (card->states[i] == NULL) { + state = card->states[i] = (struct ali_state *) kmalloc(sizeof(struct ali_state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + memset(state, 0, sizeof(struct ali_state)); + dmabuf = &state->dmabuf; + goto found_virt; + } + } + card = card->next; + } + + /* no more virtual channel avaiable */ + if (!state) + return -ENODEV; +found_virt: + /* initialize the virtual channel */ + + state->virt = i; + state->card = card; + state->magic = ALI5455_STATE_MAGIC; + init_waitqueue_head(&dmabuf->wait); + init_MUTEX(&state->open_sem); + file->private_data = state; + dmabuf->trigger = 0; + /* allocate hardware channels */ + if (file->f_mode & FMODE_READ) { + if ((dmabuf->read_channel = + card->alloc_rec_pcm_channel(card)) == NULL) { + kfree(card->states[i]); + card->states[i] = NULL; + return -EBUSY; + } + dmabuf->trigger |= PCM_ENABLE_INPUT; + ali_set_adc_rate(state, 8000); + } + if (file->f_mode & FMODE_WRITE) { + if (codec_independent_spdif_locked > 0) { + if ((dmabuf->codec_spdifout_channel = card->alloc_codec_spdifout_channel(card)) == NULL) { + kfree(card->states[i]); + card->states[i] = NULL; + return -EBUSY; + } + dmabuf->trigger |= SPDIF_ENABLE_OUTPUT; + ali_set_codecspdifout_rate(state, codec_independent_spdif_locked); //It must add + i_scr = inl(state->card->iobase + ALI_SCR); + if ((i_scr & 0x00300000) == 0x00100000) { + ali_set_spdif_output(state, AC97_EA_SPSA_7_8, codec_independent_spdif_locked); + } else { + if ((i_scr & 0x00300000) == 0x00200000) { + ali_set_spdif_output(state, AC97_EA_SPSA_6_9, codec_independent_spdif_locked); + } else { + if ((i_scr & 0x00300000) == 0x00300000) { + ali_set_spdif_output(state, AC97_EA_SPSA_10_11, codec_independent_spdif_locked); + } else { + ali_set_spdif_output(state, AC97_EA_SPSA_7_8, codec_independent_spdif_locked); + } + } + + } + } else { + if (controller_independent_spdif_locked > 0) { + if ((dmabuf->controller_spdifout_channel = card->alloc_controller_spdifout_channel(card)) == NULL) { + kfree(card->states[i]); + card->states[i] = NULL; + return -EBUSY; + } + dmabuf->trigger |= SPDIF_ENABLE_OUTPUT; + ali_set_spdifout_rate(state, controller_independent_spdif_locked); + } else { + if ((dmabuf->write_channel = card->alloc_pcm_channel(card)) == NULL) { + kfree(card->states[i]); + card->states[i] = NULL; + return -EBUSY; + } + /* Initialize to 8kHz? What if we don't support 8kHz? */ + /* Let's change this to check for S/PDIF stuff */ + + dmabuf->trigger |= PCM_ENABLE_OUTPUT; + if (codec_pcmout_share_spdif_locked) { + ali_set_dac_rate(state, codec_pcmout_share_spdif_locked); + ali_set_spdif_output(state, AC97_EA_SPSA_3_4, codec_pcmout_share_spdif_locked); + } else { + ali_set_dac_rate(state, 8000); + } + } + + } + } + + /* set default sample format. According to OSS Programmer's Guide /dev/dsp + should be default to unsigned 8-bits, mono, with sample rate 8kHz and + /dev/dspW will accept 16-bits sample, but we don't support those so we + set it immediately to stereo and 16bit, which is all we do support */ + dmabuf->fmt |= ALI5455_FMT_16BIT | ALI5455_FMT_STEREO; + dmabuf->ossfragsize = 0; + dmabuf->ossmaxfrags = 0; + dmabuf->subdivision = 0; + state->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + outl(0x00000000, card->iobase + ALI_INTERRUPTCR); + outl(0x00000000, card->iobase + ALI_INTERRUPTSR); + return nonseekable_open(inode, file); +} + +static int ali_release(struct inode *inode, struct file *file) +{ + struct ali_state *state = (struct ali_state *) file->private_data; + struct ali_card *card = state->card; + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + lock_kernel(); + + /* stop DMA state machine and free DMA buffers/channels */ + if (dmabuf->trigger & PCM_ENABLE_OUTPUT) + drain_dac(state, 0); + + if (dmabuf->trigger & SPDIF_ENABLE_OUTPUT) + drain_spdifout(state, 0); + + if (dmabuf->trigger & PCM_ENABLE_INPUT) + stop_adc(state); + + spin_lock_irqsave(&card->lock, flags); + dealloc_dmabuf(state); + if (file->f_mode & FMODE_WRITE) { + if (codec_independent_spdif_locked > 0) { + state->card->free_pcm_channel(state->card, dmabuf->codec_spdifout_channel->num); + } else { + if (controller_independent_spdif_locked > 0) + state->card->free_pcm_channel(state->card, + dmabuf->controller_spdifout_channel->num); + else state->card->free_pcm_channel(state->card, + dmabuf->write_channel->num); + } + } + if (file->f_mode & FMODE_READ) + state->card->free_pcm_channel(state->card, dmabuf->read_channel->num); + + state->card->states[state->virt] = NULL; + kfree(state); + spin_unlock_irqrestore(&card->lock, flags); + unlock_kernel(); + return 0; +} + +static /*const */ struct file_operations ali_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = ali_read, + .write = ali_write, + .poll = ali_poll, + .ioctl = ali_ioctl, + .mmap = ali_mmap, + .open = ali_open, + .release = ali_release, +}; + +/* Read AC97 codec registers */ +static u16 ali_ac97_get(struct ac97_codec *dev, u8 reg) +{ + struct ali_card *card = dev->private_data; + int count1 = 100; + char val; + unsigned short int data = 0, count, addr1, addr2 = 0; + + spin_lock(&card->ac97_lock); + while (count1-- && (inl(card->iobase + ALI_CAS) & 0x80000000)) + udelay(1); + + addr1 = reg; + reg |= 0x0080; + for (count = 0; count < 0x7f; count++) { + val = inb(card->iobase + ALI_CSPSR); + if (val & 0x08) + break; + } + if (count == 0x7f) + { + spin_unlock(&card->ac97_lock); + return -1; + } + outw(reg, (card->iobase + ALI_CPR) + 2); + for (count = 0; count < 0x7f; count++) { + val = inb(card->iobase + ALI_CSPSR); + if (val & 0x02) { + data = inw(card->iobase + ALI_SPR); + addr2 = inw((card->iobase + ALI_SPR) + 2); + break; + } + } + spin_unlock(&card->ac97_lock); + if (count == 0x7f) + return -1; + if (addr2 != addr1) + return -1; + return ((u16) data); +} + +/* write ac97 codec register */ + +static void ali_ac97_set(struct ac97_codec *dev, u8 reg, u16 data) +{ + struct ali_card *card = dev->private_data; + int count1 = 100; + char val; + unsigned short int count; + + spin_lock(&card->ac97_lock); + while (count1-- && (inl(card->iobase + ALI_CAS) & 0x80000000)) + udelay(1); + + for (count = 0; count < 0x7f; count++) { + val = inb(card->iobase + ALI_CSPSR); + if (val & 0x08) + break; + } + if (count == 0x7f) { + printk(KERN_WARNING "ali_ac97_set: AC97 codec register access timed out. \n"); + spin_unlock(&card->ac97_lock); + return; + } + outw(data, (card->iobase + ALI_CPR)); + outb(reg, (card->iobase + ALI_CPR) + 2); + for (count = 0; count < 0x7f; count++) { + val = inb(card->iobase + ALI_CSPSR); + if (val & 0x01) + break; + } + spin_unlock(&card->ac97_lock); + if (count == 0x7f) + printk(KERN_WARNING "ali_ac97_set: AC97 codec register access timed out. \n"); + return; +} + +/* OSS /dev/mixer file operation methods */ + +static int ali_open_mixdev(struct inode *inode, struct file *file) +{ + int i; + int minor = iminor(inode); + struct ali_card *card = devs; + for (card = devs; card != NULL; card = card->next) { + /* + * If we are initializing and then fail, card could go + * away unuexpectedly while we are in the for() loop. + * So, check for card on each iteration before we check + * for card->initializing to avoid a possible oops. + * This usually only matters for times when the driver is + * autoloaded by kmod. + */ + for (i = 0; i < 50 && card && card->initializing; i++) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ / 20); + } + for (i = 0; i < NR_AC97 && card && !card->initializing; i++) + if (card->ac97_codec[i] != NULL + && card->ac97_codec[i]->dev_mixer == minor) { + file->private_data = card->ac97_codec[i]; + return nonseekable_open(inode, file); + } + } + return -ENODEV; +} + +static int ali_ioctl_mixdev(struct inode *inode, + struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct ac97_codec *codec = (struct ac97_codec *) file->private_data; + return codec->mixer_ioctl(codec, cmd, arg); +} + +static /*const */ struct file_operations ali_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = ali_ioctl_mixdev, + .open = ali_open_mixdev, +}; + +/* AC97 codec initialisation. These small functions exist so we don't + duplicate code between module init and apm resume */ + +static inline int ali_ac97_exists(struct ali_card *card, int ac97_number) +{ + unsigned int i = 1; + u32 reg = inl(card->iobase + ALI_RTSR); + if (ac97_number) { + while (i < 100) { + + reg = inl(card->iobase + ALI_RTSR); + if (reg & 0x40) { + break; + } else { + outl(reg | 0x00000040, + card->iobase + 0x34); + udelay(1); + } + i++; + } + + } else { + while (i < 100) { + reg = inl(card->iobase + ALI_RTSR); + if (reg & 0x80) { + break; + } else { + outl(reg | 0x00000080, + card->iobase + 0x34); + udelay(1); + } + i++; + } + } + + if (ac97_number) + return reg & 0x40; + else + return reg & 0x80; +} + +static inline int ali_ac97_enable_variable_rate(struct ac97_codec *codec) +{ + ali_ac97_set(codec, AC97_EXTENDED_STATUS, 9); + ali_ac97_set(codec, AC97_EXTENDED_STATUS, ali_ac97_get(codec, AC97_EXTENDED_STATUS) | 0xE800); + return (ali_ac97_get(codec, AC97_EXTENDED_STATUS) & 1); +} + + +static int ali_ac97_probe_and_powerup(struct ali_card *card, struct ac97_codec *codec) +{ + /* Returns 0 on failure */ + int i; + u16 addr; + if (ac97_probe_codec(codec) == 0) + return 0; + /* ac97_probe_codec is success ,then begin to init codec */ + ali_ac97_set(codec, AC97_RESET, 0xffff); + if (card->channel[0].used == 1) { + ali_ac97_set(codec, AC97_RECORD_SELECT, 0x0000); + ali_ac97_set(codec, AC97_LINEIN_VOL, 0x0808); + ali_ac97_set(codec, AC97_RECORD_GAIN, 0x0F0F); + } + + if (card->channel[2].used == 1) //if MICin then init codec + { + ali_ac97_set(codec, AC97_RECORD_SELECT, 0x0000); + ali_ac97_set(codec, AC97_MIC_VOL, 0x8808); + ali_ac97_set(codec, AC97_RECORD_GAIN, 0x0F0F); + ali_ac97_set(codec, AC97_RECORD_GAIN_MIC, 0x0000); + } + + ali_ac97_set(codec, AC97_MASTER_VOL_STEREO, 0x0000); + ali_ac97_set(codec, AC97_HEADPHONE_VOL, 0x0000); + ali_ac97_set(codec, AC97_PCMOUT_VOL, 0x0000); + ali_ac97_set(codec, AC97_CD_VOL, 0x0808); + ali_ac97_set(codec, AC97_VIDEO_VOL, 0x0808); + ali_ac97_set(codec, AC97_AUX_VOL, 0x0808); + ali_ac97_set(codec, AC97_PHONE_VOL, 0x8048); + ali_ac97_set(codec, AC97_PCBEEP_VOL, 0x0000); + ali_ac97_set(codec, AC97_GENERAL_PURPOSE, AC97_GP_MIX); + ali_ac97_set(codec, AC97_MASTER_VOL_MONO, 0x0000); + ali_ac97_set(codec, 0x38, 0x0000); + addr = ali_ac97_get(codec, 0x2a); + ali_ac97_set(codec, 0x2a, addr | 0x0001); + addr = ali_ac97_get(codec, 0x2a); + addr = ali_ac97_get(codec, 0x28); + ali_ac97_set(codec, 0x2c, 0xbb80); + addr = ali_ac97_get(codec, 0x2c); + /* power it all up */ + ali_ac97_set(codec, AC97_POWER_CONTROL, + ali_ac97_get(codec, AC97_POWER_CONTROL) & ~0x7f00); + /* wait for analog ready */ + for (i = 10; i && ((ali_ac97_get(codec, AC97_POWER_CONTROL) & 0xf) != 0xf); i--) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ / 20); + } + /* FIXME !! */ + i++; + return i; +} + + +/* I clone ali5455(2.4.7 ) not clone i810_audio(2.4.18) */ + +static int ali_reset_5455(struct ali_card *card) +{ + outl(0x80000003, card->iobase + ALI_SCR); + outl(0x83838383, card->iobase + ALI_FIFOCR1); + outl(0x83838383, card->iobase + ALI_FIFOCR2); + if (controller_pcmout_share_spdif_locked > 0) { + outl((inl(card->iobase + ALI_SPDIFICS) | 0x00000001), + card->iobase + ALI_SPDIFICS); + outl(0x0408000a, card->iobase + ALI_INTERFACECR); + } else { + if (codec_independent_spdif_locked > 0) { + outl((inl(card->iobase + ALI_SCR) | 0x00100000), card->iobase + ALI_SCR); // now I select slot 7 & 8 + outl(0x00200000, card->iobase + ALI_INTERFACECR); //enable codec independent spdifout + } else + outl(0x04080002, card->iobase + ALI_INTERFACECR); + } + + outl(0x00000000, card->iobase + ALI_INTERRUPTCR); + outl(0x00000000, card->iobase + ALI_INTERRUPTSR); + if (controller_independent_spdif_locked > 0) + outl((inl(card->iobase + ALI_SPDIFICS) | 0x00000001), + card->iobase + ALI_SPDIFICS); + return 1; +} + + +static int ali_ac97_random_init_stuff(struct ali_card + *card) +{ + u32 reg = inl(card->iobase + ALI_SCR); + int i = 0; + reg = inl(card->iobase + ALI_SCR); + if ((reg & 2) == 0) /* Cold required */ + reg |= 2; + else + reg |= 1; /* Warm */ + reg &= ~0x80000000; /* ACLink on */ + outl(reg, card->iobase + ALI_SCR); + + while (i < 10) { + if ((inl(card->iobase + 0x18) & (1 << 1)) == 0) + break; + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(HZ / 20); + i++; + } + if (i == 10) { + printk(KERN_ERR "ali_audio: AC'97 reset failed.\n"); + return 0; + } + + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ / 2); + return 1; +} + +/* AC97 codec initialisation. */ + +static int __devinit ali_ac97_init(struct ali_card *card) +{ + int num_ac97 = 0; + int total_channels = 0; + struct ac97_codec *codec; + u16 eid; + + if (!ali_ac97_random_init_stuff(card)) + return 0; + + /* Number of channels supported */ + /* What about the codec? Just because the ICH supports */ + /* multiple channels doesn't mean the codec does. */ + /* we'll have to modify this in the codec section below */ + /* to reflect what the codec has. */ + /* ICH and ICH0 only support 2 channels so don't bother */ + /* to check.... */ + inl(card->iobase + ALI_CPR); + card->channels = 2; + + for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) { + + /* Assume codec isn't available until we go through the + * gauntlet below */ + card->ac97_codec[num_ac97] = NULL; + /* The ICH programmer's reference says you should */ + /* check the ready status before probing. So we chk */ + /* What do we do if it's not ready? Wait and try */ + /* again, or abort? */ + if (!ali_ac97_exists(card, num_ac97)) { + if (num_ac97 == 0) + printk(KERN_ERR "ali_audio: Primary codec not ready.\n"); + break; + } + + if ((codec = ac97_alloc_codec()) == NULL) + return -ENOMEM; + /* initialize some basic codec information, other fields will be filled + in ac97_probe_codec */ + codec->private_data = card; + codec->id = num_ac97; + codec->codec_read = ali_ac97_get; + codec->codec_write = ali_ac97_set; + if (!ali_ac97_probe_and_powerup(card, codec)) { + printk(KERN_ERR "ali_audio: timed out waiting for codec %d analog ready", + num_ac97); + kfree(codec); + break; /* it didn't work */ + } + + /* Store state information about S/PDIF transmitter */ + card->ac97_status = 0; + /* Don't attempt to get eid until powerup is complete */ + eid = ali_ac97_get(codec, AC97_EXTENDED_ID); + if (eid == 0xFFFF) { + printk(KERN_ERR "ali_audio: no codec attached ?\n"); + kfree(codec); + break; + } + + card->ac97_features = eid; + /* Now check the codec for useful features to make up for + the dumbness of the ali5455 hardware engine */ + if (!(eid & 0x0001)) + printk(KERN_WARNING + "ali_audio: only 48Khz playback available.\n"); + else { + if (!ali_ac97_enable_variable_rate(codec)) { + printk(KERN_WARNING + "ali_audio: Codec refused to allow VRA, using 48Khz only.\n"); + card->ac97_features &= ~1; + } + } + + /* Determine how many channels the codec(s) support */ + /* - The primary codec always supports 2 */ + /* - If the codec supports AMAP, surround DACs will */ + /* automaticlly get assigned to slots. */ + /* * Check for surround DACs and increment if */ + /* found. */ + /* - Else check if the codec is revision 2.2 */ + /* * If surround DACs exist, assign them to slots */ + /* and increment channel count. */ + + /* All of this only applies to ICH2 and above. ICH */ + /* and ICH0 only support 2 channels. ICH2 will only */ + /* support multiple codecs in a "split audio" config. */ + /* as described above. */ + + /* TODO: Remove all the debugging messages! */ + + if ((eid & 0xc000) == 0) /* primary codec */ + total_channels += 2; + if ((codec->dev_mixer = register_sound_mixer(&ali_mixer_fops, -1)) < 0) { + printk(KERN_ERR "ali_audio: couldn't register mixer!\n"); + kfree(codec); + break; + } + card->ac97_codec[num_ac97] = codec; + } + /* pick the minimum of channels supported by ICHx or codec(s) */ + card->channels = (card->channels > total_channels) ? total_channels : card->channels; + return num_ac97; +} + +static void __devinit ali_configure_clocking(void) +{ + struct ali_card *card; + struct ali_state *state; + struct dmabuf *dmabuf; + unsigned int i, offset, new_offset; + unsigned long flags; + card = devs; + + /* We could try to set the clocking for multiple cards, but can you even have + * more than one ali in a machine? Besides, clocking is global, so unless + * someone actually thinks more than one ali in a machine is possible and + * decides to rewrite that little bit, setting the rate for more than one card + * is a waste of time. + */ + if (card != NULL) { + state = card->states[0] = (struct ali_state *) + kmalloc(sizeof(struct ali_state), GFP_KERNEL); + if (state == NULL) + return; + memset(state, 0, sizeof(struct ali_state)); + dmabuf = &state->dmabuf; + dmabuf->write_channel = card->alloc_pcm_channel(card); + state->virt = 0; + state->card = card; + state->magic = ALI5455_STATE_MAGIC; + init_waitqueue_head(&dmabuf->wait); + init_MUTEX(&state->open_sem); + dmabuf->fmt = ALI5455_FMT_STEREO | ALI5455_FMT_16BIT; + dmabuf->trigger = PCM_ENABLE_OUTPUT; + ali_set_dac_rate(state, 48000); + if (prog_dmabuf(state, 0) != 0) + goto config_out_nodmabuf; + + if (dmabuf->dmasize < 16384) + goto config_out; + + dmabuf->count = dmabuf->dmasize; + outb(31, card->iobase + dmabuf->write_channel->port + OFF_LVI); + + local_irq_save(flags); + start_dac(state); + offset = ali_get_dma_addr(state, 0); + mdelay(50); + new_offset = ali_get_dma_addr(state, 0); + stop_dac(state); + + outb(2, card->iobase + dmabuf->write_channel->port + OFF_CR); + local_irq_restore(flags); + + i = new_offset - offset; + + if (i == 0) + goto config_out; + i = i / 4 * 20; + if (i > 48500 || i < 47500) { + clocking = clocking * clocking / i; + } +config_out: + dealloc_dmabuf(state); +config_out_nodmabuf: + state->card->free_pcm_channel(state->card, state->dmabuf. write_channel->num); + kfree(state); + card->states[0] = NULL; + } +} + +/* install the driver, we do not allocate hardware channel nor DMA buffer now, they are defered + until "ACCESS" time (in prog_dmabuf called by open/read/write/ioctl/mmap) */ + +static int __devinit ali_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct ali_card *card; + if (pci_enable_device(pci_dev)) + return -EIO; + if (pci_set_dma_mask(pci_dev, ALI5455_DMA_MASK)) { + printk(KERN_ERR "ali5455: architecture does not support" + " 32bit PCI busmaster DMA\n"); + return -ENODEV; + } + + if ((card = kmalloc(sizeof(struct ali_card), GFP_KERNEL)) == NULL) { + printk(KERN_ERR "ali_audio: out of memory\n"); + return -ENOMEM; + } + memset(card, 0, sizeof(*card)); + card->initializing = 1; + card->iobase = pci_resource_start(pci_dev, 0); + card->pci_dev = pci_dev; + card->pci_id = pci_id->device; + card->irq = pci_dev->irq; + card->next = devs; + card->magic = ALI5455_CARD_MAGIC; +#ifdef CONFIG_PM + card->pm_suspended = 0; +#endif + spin_lock_init(&card->lock); + spin_lock_init(&card->ac97_lock); + devs = card; + pci_set_master(pci_dev); + printk(KERN_INFO "ali: %s found at IO 0x%04lx, IRQ %d\n", + card_names[pci_id->driver_data], card->iobase, card->irq); + card->alloc_pcm_channel = ali_alloc_pcm_channel; + card->alloc_rec_pcm_channel = ali_alloc_rec_pcm_channel; + card->alloc_rec_mic_channel = ali_alloc_rec_mic_channel; + card->alloc_codec_spdifout_channel = ali_alloc_codec_spdifout_channel; + card->alloc_controller_spdifout_channel = ali_alloc_controller_spdifout_channel; + card->free_pcm_channel = ali_free_pcm_channel; + card->channel[0].offset = 0; + card->channel[0].port = 0x40; + card->channel[0].num = 0; + card->channel[1].offset = 0; + card->channel[1].port = 0x50; + card->channel[1].num = 1; + card->channel[2].offset = 0; + card->channel[2].port = 0x60; + card->channel[2].num = 2; + card->channel[3].offset = 0; + card->channel[3].port = 0x70; + card->channel[3].num = 3; + card->channel[4].offset = 0; + card->channel[4].port = 0xb0; + card->channel[4].num = 4; + /* claim our iospace and irq */ + request_region(card->iobase, 256, card_names[pci_id->driver_data]); + if (request_irq(card->irq, &ali_interrupt, SA_SHIRQ, + card_names[pci_id->driver_data], card)) { + printk(KERN_ERR "ali_audio: unable to allocate irq %d\n", + card->irq); + release_region(card->iobase, 256); + kfree(card); + return -ENODEV; + } + + if (ali_reset_5455(card) <= 0) { + unregister_sound_dsp(card->dev_audio); + release_region(card->iobase, 256); + free_irq(card->irq, card); + kfree(card); + return -ENODEV; + } + + /* initialize AC97 codec and register /dev/mixer */ + if (ali_ac97_init(card) < 0) { + release_region(card->iobase, 256); + free_irq(card->irq, card); + kfree(card); + return -ENODEV; + } + + pci_set_drvdata(pci_dev, card); + + if (clocking == 0) { + clocking = 48000; + ali_configure_clocking(); + } + + /* register /dev/dsp */ + if ((card->dev_audio = register_sound_dsp(&ali_audio_fops, -1)) < 0) { + int i; + printk(KERN_ERR"ali_audio: couldn't register DSP device!\n"); + release_region(card->iobase, 256); + free_irq(card->irq, card); + for (i = 0; i < NR_AC97; i++) + if (card->ac97_codec[i] != NULL) { + unregister_sound_mixer(card->ac97_codec[i]->dev_mixer); + kfree(card->ac97_codec[i]); + } + kfree(card); + return -ENODEV; + } + card->initializing = 0; + return 0; +} + +static void __devexit ali_remove(struct pci_dev *pci_dev) +{ + int i; + struct ali_card *card = pci_get_drvdata(pci_dev); + /* free hardware resources */ + free_irq(card->irq, devs); + release_region(card->iobase, 256); + /* unregister audio devices */ + for (i = 0; i < NR_AC97; i++) + if (card->ac97_codec[i] != NULL) { + unregister_sound_mixer(card->ac97_codec[i]-> + dev_mixer); + ac97_release_codec(card->ac97_codec[i]); + card->ac97_codec[i] = NULL; + } + unregister_sound_dsp(card->dev_audio); + kfree(card); +} + +#ifdef CONFIG_PM +static int ali_pm_suspend(struct pci_dev *dev, pm_message_t pm_state) +{ + struct ali_card *card = pci_get_drvdata(dev); + struct ali_state *state; + unsigned long flags; + struct dmabuf *dmabuf; + int i, num_ac97; + + if (!card) + return 0; + spin_lock_irqsave(&card->lock, flags); + card->pm_suspended = 1; + for (i = 0; i < NR_HW_CH; i++) { + state = card->states[i]; + if (!state) + continue; + /* this happens only if there are open files */ + dmabuf = &state->dmabuf; + if (dmabuf->enable & DAC_RUNNING || + (dmabuf->count + && (dmabuf->trigger & PCM_ENABLE_OUTPUT))) { + state->pm_saved_dac_rate = dmabuf->rate; + stop_dac(state); + } else { + state->pm_saved_dac_rate = 0; + } + if (dmabuf->enable & ADC_RUNNING) { + state->pm_saved_adc_rate = dmabuf->rate; + stop_adc(state); + } else { + state->pm_saved_adc_rate = 0; + } + dmabuf->ready = 0; + dmabuf->swptr = dmabuf->hwptr = 0; + dmabuf->count = dmabuf->total_bytes = 0; + } + + spin_unlock_irqrestore(&card->lock, flags); + /* save mixer settings */ + for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) { + struct ac97_codec *codec = card->ac97_codec[num_ac97]; + if (!codec) + continue; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if ((supported_mixer(codec, i)) && (codec->read_mixer)) { + card->pm_saved_mixer_settings[i][num_ac97] = codec->read_mixer(codec, i); + } + } + } + pci_save_state(dev); /* XXX do we need this? */ + pci_disable_device(dev); /* disable busmastering */ + pci_set_power_state(dev, 3); /* Zzz. */ + return 0; +} + + +static int ali_pm_resume(struct pci_dev *dev) +{ + int num_ac97, i = 0; + struct ali_card *card = pci_get_drvdata(dev); + pci_enable_device(dev); + pci_restore_state(dev); + /* observation of a toshiba portege 3440ct suggests that the + hardware has to be more or less completely reinitialized from + scratch after an apm suspend. Works For Me. -dan */ + ali_ac97_random_init_stuff(card); + for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) { + struct ac97_codec *codec = card->ac97_codec[num_ac97]; + /* check they haven't stolen the hardware while we were + away */ + if (!codec || !ali_ac97_exists(card, num_ac97)) { + if (num_ac97) + continue; + else + BUG(); + } + if (!ali_ac97_probe_and_powerup(card, codec)) + BUG(); + if ((card->ac97_features & 0x0001)) { + /* at probe time we found we could do variable + rates, but APM suspend has made it forget + its magical powers */ + if (!ali_ac97_enable_variable_rate(codec)) + BUG(); + } + /* we lost our mixer settings, so restore them */ + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (supported_mixer(codec, i)) { + int val = card->pm_saved_mixer_settings[i][num_ac97]; + codec->mixer_state[i] = val; + codec->write_mixer(codec, i, + (val & 0xff), + ((val >> 8) & 0xff)); + } + } + } + + /* we need to restore the sample rate from whatever it was */ + for (i = 0; i < NR_HW_CH; i++) { + struct ali_state *state = card->states[i]; + if (state) { + if (state->pm_saved_adc_rate) + ali_set_adc_rate(state, state->pm_saved_adc_rate); + if (state->pm_saved_dac_rate) + ali_set_dac_rate(state, state->pm_saved_dac_rate); + } + } + + card->pm_suspended = 0; + /* any processes that were reading/writing during the suspend + probably ended up here */ + for (i = 0; i < NR_HW_CH; i++) { + struct ali_state *state = card->states[i]; + if (state) + wake_up(&state->dmabuf.wait); + } + return 0; +} +#endif /* CONFIG_PM */ + +MODULE_AUTHOR(""); +MODULE_DESCRIPTION("ALI 5455 audio support"); +MODULE_LICENSE("GPL"); +module_param(clocking, int, 0); +/* FIXME: bool? */ +module_param(strict_clocking, uint, 0); +module_param(codec_pcmout_share_spdif_locked, uint, 0); +module_param(codec_independent_spdif_locked, uint, 0); +module_param(controller_pcmout_share_spdif_locked, uint, 0); +module_param(controller_independent_spdif_locked, uint, 0); +#define ALI5455_MODULE_NAME "ali5455" +static struct pci_driver ali_pci_driver = { + .name = ALI5455_MODULE_NAME, + .id_table = ali_pci_tbl, + .probe = ali_probe, + .remove = __devexit_p(ali_remove), +#ifdef CONFIG_PM + .suspend = ali_pm_suspend, + .resume = ali_pm_resume, +#endif /* CONFIG_PM */ +}; + +static int __init ali_init_module(void) +{ + printk(KERN_INFO "ALI 5455 + AC97 Audio, version " + DRIVER_VERSION ", " __TIME__ " " __DATE__ "\n"); + + if (codec_independent_spdif_locked > 0) { + if (codec_independent_spdif_locked == 32000 + || codec_independent_spdif_locked == 44100 + || codec_independent_spdif_locked == 48000) { + printk(KERN_INFO "ali_audio: Enabling S/PDIF at sample rate %dHz.\n", codec_independent_spdif_locked); + } else { + printk(KERN_INFO "ali_audio: S/PDIF can only be locked to 32000, 44100, or 48000Hz.\n"); + codec_independent_spdif_locked = 0; + } + } + if (controller_independent_spdif_locked > 0) { + if (controller_independent_spdif_locked == 32000 + || controller_independent_spdif_locked == 44100 + || controller_independent_spdif_locked == 48000) { + printk(KERN_INFO "ali_audio: Enabling S/PDIF at sample rate %dHz.\n", controller_independent_spdif_locked); + } else { + printk(KERN_INFO "ali_audio: S/PDIF can only be locked to 32000, 44100, or 48000Hz.\n"); + controller_independent_spdif_locked = 0; + } + } + + if (codec_pcmout_share_spdif_locked > 0) { + if (codec_pcmout_share_spdif_locked == 32000 + || codec_pcmout_share_spdif_locked == 44100 + || codec_pcmout_share_spdif_locked == 48000) { + printk(KERN_INFO "ali_audio: Enabling S/PDIF at sample rate %dHz.\n", codec_pcmout_share_spdif_locked); + } else { + printk(KERN_INFO "ali_audio: S/PDIF can only be locked to 32000, 44100, or 48000Hz.\n"); + codec_pcmout_share_spdif_locked = 0; + } + } + if (controller_pcmout_share_spdif_locked > 0) { + if (controller_pcmout_share_spdif_locked == 32000 + || controller_pcmout_share_spdif_locked == 44100 + || controller_pcmout_share_spdif_locked == 48000) { + printk(KERN_INFO "ali_audio: Enabling controller S/PDIF at sample rate %dHz.\n", controller_pcmout_share_spdif_locked); + } else { + printk(KERN_INFO "ali_audio: S/PDIF can only be locked to 32000, 44100, or 48000Hz.\n"); + controller_pcmout_share_spdif_locked = 0; + } + } + return pci_register_driver(&ali_pci_driver); +} + +static void __exit ali_cleanup_module(void) +{ + pci_unregister_driver(&ali_pci_driver); +} + +module_init(ali_init_module); +module_exit(ali_cleanup_module); +/* +Local Variables: +c-basic-offset: 8 +End: +*/ diff --git a/sound/oss/au1000.c b/sound/oss/au1000.c new file mode 100644 index 000000000000..4491733c9e4e --- /dev/null +++ b/sound/oss/au1000.c @@ -0,0 +1,2214 @@ +/* + * au1000.c -- Sound driver for Alchemy Au1000 MIPS Internet Edge + * Processor. + * + * Copyright 2001 MontaVista Software Inc. + * Author: MontaVista Software, Inc. + * stevel@mvista.com or source@mvista.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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: + * + * Supported devices: + * /dev/dsp standard OSS /dev/dsp device + * /dev/mixer standard OSS /dev/mixer device + * + * Notes: + * + * 1. Much of the OSS buffer allocation, ioctl's, and mmap'ing are + * taken, slightly modified or not at all, from the ES1371 driver, + * so refer to the credits in es1371.c for those. The rest of the + * code (probe, open, read, write, the ISR, etc.) is new. + * + * Revision history + * 06.27.2001 Initial version + * 03.20.2002 Added mutex locks around read/write methods, to prevent + * simultaneous access on SMP or preemptible kernels. Also + * removed the counter/pointer fragment aligning at the end + * of read/write methods [stevel]. + * 03.21.2002 Add support for coherent DMA on the audio read/write DMA + * channels [stevel]. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* --------------------------------------------------------------------- */ + +#undef OSS_DOCUMENTED_MIXER_SEMANTICS +#undef AU1000_DEBUG +#undef AU1000_VERBOSE_DEBUG + +#define AU1000_MODULE_NAME "Au1000 audio" +#define PFX AU1000_MODULE_NAME + +#ifdef AU1000_DEBUG +#define dbg(format, arg...) printk(KERN_DEBUG PFX ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif +#define err(format, arg...) printk(KERN_ERR PFX ": " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO PFX ": " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING PFX ": " format "\n" , ## arg) + + +/* misc stuff */ +#define POLL_COUNT 0x5000 +#define AC97_EXT_DACS (AC97_EXTID_SDAC | AC97_EXTID_CDAC | AC97_EXTID_LDAC) + +/* Boot options */ +static int vra = 0; // 0 = no VRA, 1 = use VRA if codec supports it +MODULE_PARM(vra, "i"); +MODULE_PARM_DESC(vra, "if 1 use VRA if codec supports it"); + + +/* --------------------------------------------------------------------- */ + +struct au1000_state { + /* soundcore stuff */ + int dev_audio; + +#ifdef AU1000_DEBUG + /* debug /proc entry */ + struct proc_dir_entry *ps; + struct proc_dir_entry *ac97_ps; +#endif /* AU1000_DEBUG */ + + struct ac97_codec codec; + unsigned codec_base_caps;// AC'97 reg 00h, "Reset Register" + unsigned codec_ext_caps; // AC'97 reg 28h, "Extended Audio ID" + int no_vra; // do not use VRA + + spinlock_t lock; + struct semaphore open_sem; + struct semaphore sem; + mode_t open_mode; + wait_queue_head_t open_wait; + + struct dmabuf { + unsigned int dmanr; // DMA Channel number + unsigned sample_rate; // Hz + unsigned src_factor; // SRC interp/decimation (no vra) + unsigned sample_size; // 8 or 16 + int num_channels; // 1 = mono, 2 = stereo, 4, 6 + int dma_bytes_per_sample;// DMA bytes per audio sample frame + int user_bytes_per_sample;// User bytes per audio sample frame + int cnt_factor; // user-to-DMA bytes per audio + // sample frame + void *rawbuf; + dma_addr_t dmaaddr; + unsigned buforder; + unsigned numfrag; // # of DMA fragments in DMA buffer + unsigned fragshift; + void *nextIn; // ptr to next-in to DMA buffer + void *nextOut;// ptr to next-out from DMA buffer + int count; // current byte count in DMA buffer + unsigned total_bytes; // total bytes written or read + unsigned error; // over/underrun + wait_queue_head_t wait; + /* redundant, but makes calculations easier */ + unsigned fragsize; // user perception of fragment size + unsigned dma_fragsize; // DMA (real) fragment size + unsigned dmasize; // Total DMA buffer size + // (mult. of DMA fragsize) + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned stopped:1; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + } dma_dac , dma_adc; +} au1000_state; + +/* --------------------------------------------------------------------- */ + + +static inline unsigned ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +/* --------------------------------------------------------------------- */ + +static void au1000_delay(int msec) +{ + unsigned long tmo; + signed long tmo2; + + if (in_interrupt()) + return; + + tmo = jiffies + (msec * HZ) / 1000; + for (;;) { + tmo2 = tmo - jiffies; + if (tmo2 <= 0) + break; + schedule_timeout(tmo2); + } +} + + +/* --------------------------------------------------------------------- */ + +static u16 rdcodec(struct ac97_codec *codec, u8 addr) +{ + struct au1000_state *s = (struct au1000_state *)codec->private_data; + unsigned long flags; + u32 cmd; + u16 data; + int i; + + spin_lock_irqsave(&s->lock, flags); + + for (i = 0; i < POLL_COUNT; i++) + if (!(au_readl(AC97C_STATUS) & AC97C_CP)) + break; + if (i == POLL_COUNT) + err("rdcodec: codec cmd pending expired!"); + + cmd = (u32) addr & AC97C_INDEX_MASK; + cmd |= AC97C_READ; // read command + au_writel(cmd, AC97C_CMD); + + /* now wait for the data */ + for (i = 0; i < POLL_COUNT; i++) + if (!(au_readl(AC97C_STATUS) & AC97C_CP)) + break; + if (i == POLL_COUNT) { + err("rdcodec: read poll expired!"); + return 0; + } + + data = au_readl(AC97C_CMD) & 0xffff; + + spin_unlock_irqrestore(&s->lock, flags); + + return data; +} + + +static void wrcodec(struct ac97_codec *codec, u8 addr, u16 data) +{ + struct au1000_state *s = (struct au1000_state *)codec->private_data; + unsigned long flags; + u32 cmd; + int i; + + spin_lock_irqsave(&s->lock, flags); + + for (i = 0; i < POLL_COUNT; i++) + if (!(au_readl(AC97C_STATUS) & AC97C_CP)) + break; + if (i == POLL_COUNT) + err("wrcodec: codec cmd pending expired!"); + + cmd = (u32) addr & AC97C_INDEX_MASK; + cmd &= ~AC97C_READ; // write command + cmd |= ((u32) data << AC97C_WD_BIT); // OR in the data word + au_writel(cmd, AC97C_CMD); + + spin_unlock_irqrestore(&s->lock, flags); +} + +static void waitcodec(struct ac97_codec *codec) +{ + u16 temp; + int i; + + /* codec_wait is used to wait for a ready state after + an AC97C_RESET. */ + au1000_delay(10); + + // first poll the CODEC_READY tag bit + for (i = 0; i < POLL_COUNT; i++) + if (au_readl(AC97C_STATUS) & AC97C_READY) + break; + if (i == POLL_COUNT) { + err("waitcodec: CODEC_READY poll expired!"); + return; + } + // get AC'97 powerdown control/status register + temp = rdcodec(codec, AC97_POWER_CONTROL); + + // If anything is powered down, power'em up + if (temp & 0x7f00) { + // Power on + wrcodec(codec, AC97_POWER_CONTROL, 0); + au1000_delay(100); + // Reread + temp = rdcodec(codec, AC97_POWER_CONTROL); + } + + // Check if Codec REF,ANL,DAC,ADC ready + if ((temp & 0x7f0f) != 0x000f) + err("codec reg 26 status (0x%x) not ready!!", temp); +} + + +/* --------------------------------------------------------------------- */ + +/* stop the ADC before calling */ +static void set_adc_rate(struct au1000_state *s, unsigned rate) +{ + struct dmabuf *adc = &s->dma_adc; + struct dmabuf *dac = &s->dma_dac; + unsigned adc_rate, dac_rate; + u16 ac97_extstat; + + if (s->no_vra) { + // calc SRC factor + adc->src_factor = ((96000 / rate) + 1) >> 1; + adc->sample_rate = 48000 / adc->src_factor; + return; + } + + adc->src_factor = 1; + + ac97_extstat = rdcodec(&s->codec, AC97_EXTENDED_STATUS); + + rate = rate > 48000 ? 48000 : rate; + + // enable VRA + wrcodec(&s->codec, AC97_EXTENDED_STATUS, + ac97_extstat | AC97_EXTSTAT_VRA); + // now write the sample rate + wrcodec(&s->codec, AC97_PCM_LR_ADC_RATE, (u16) rate); + // read it back for actual supported rate + adc_rate = rdcodec(&s->codec, AC97_PCM_LR_ADC_RATE); + +#ifdef AU1000_VERBOSE_DEBUG + dbg("%s: set to %d Hz", __FUNCTION__, adc_rate); +#endif + + // some codec's don't allow unequal DAC and ADC rates, in which case + // writing one rate reg actually changes both. + dac_rate = rdcodec(&s->codec, AC97_PCM_FRONT_DAC_RATE); + if (dac->num_channels > 2) + wrcodec(&s->codec, AC97_PCM_SURR_DAC_RATE, dac_rate); + if (dac->num_channels > 4) + wrcodec(&s->codec, AC97_PCM_LFE_DAC_RATE, dac_rate); + + adc->sample_rate = adc_rate; + dac->sample_rate = dac_rate; +} + +/* stop the DAC before calling */ +static void set_dac_rate(struct au1000_state *s, unsigned rate) +{ + struct dmabuf *dac = &s->dma_dac; + struct dmabuf *adc = &s->dma_adc; + unsigned adc_rate, dac_rate; + u16 ac97_extstat; + + if (s->no_vra) { + // calc SRC factor + dac->src_factor = ((96000 / rate) + 1) >> 1; + dac->sample_rate = 48000 / dac->src_factor; + return; + } + + dac->src_factor = 1; + + ac97_extstat = rdcodec(&s->codec, AC97_EXTENDED_STATUS); + + rate = rate > 48000 ? 48000 : rate; + + // enable VRA + wrcodec(&s->codec, AC97_EXTENDED_STATUS, + ac97_extstat | AC97_EXTSTAT_VRA); + // now write the sample rate + wrcodec(&s->codec, AC97_PCM_FRONT_DAC_RATE, (u16) rate); + // I don't support different sample rates for multichannel, + // so make these channels the same. + if (dac->num_channels > 2) + wrcodec(&s->codec, AC97_PCM_SURR_DAC_RATE, (u16) rate); + if (dac->num_channels > 4) + wrcodec(&s->codec, AC97_PCM_LFE_DAC_RATE, (u16) rate); + // read it back for actual supported rate + dac_rate = rdcodec(&s->codec, AC97_PCM_FRONT_DAC_RATE); + +#ifdef AU1000_VERBOSE_DEBUG + dbg("%s: set to %d Hz", __FUNCTION__, dac_rate); +#endif + + // some codec's don't allow unequal DAC and ADC rates, in which case + // writing one rate reg actually changes both. + adc_rate = rdcodec(&s->codec, AC97_PCM_LR_ADC_RATE); + + dac->sample_rate = dac_rate; + adc->sample_rate = adc_rate; +} + +static void stop_dac(struct au1000_state *s) +{ + struct dmabuf *db = &s->dma_dac; + unsigned long flags; + + if (db->stopped) + return; + + spin_lock_irqsave(&s->lock, flags); + + disable_dma(db->dmanr); + + db->stopped = 1; + + spin_unlock_irqrestore(&s->lock, flags); +} + +static void stop_adc(struct au1000_state *s) +{ + struct dmabuf *db = &s->dma_adc; + unsigned long flags; + + if (db->stopped) + return; + + spin_lock_irqsave(&s->lock, flags); + + disable_dma(db->dmanr); + + db->stopped = 1; + + spin_unlock_irqrestore(&s->lock, flags); +} + + +static void set_xmit_slots(int num_channels) +{ + u32 ac97_config = au_readl(AC97C_CONFIG) & ~AC97C_XMIT_SLOTS_MASK; + + switch (num_channels) { + case 1: // mono + case 2: // stereo, slots 3,4 + ac97_config |= (0x3 << AC97C_XMIT_SLOTS_BIT); + break; + case 4: // stereo with surround, slots 3,4,7,8 + ac97_config |= (0x33 << AC97C_XMIT_SLOTS_BIT); + break; + case 6: // stereo with surround and center/LFE, slots 3,4,6,7,8,9 + ac97_config |= (0x7b << AC97C_XMIT_SLOTS_BIT); + break; + } + + au_writel(ac97_config, AC97C_CONFIG); +} + +static void set_recv_slots(int num_channels) +{ + u32 ac97_config = au_readl(AC97C_CONFIG) & ~AC97C_RECV_SLOTS_MASK; + + /* + * Always enable slots 3 and 4 (stereo). Slot 6 is + * optional Mic ADC, which I don't support yet. + */ + ac97_config |= (0x3 << AC97C_RECV_SLOTS_BIT); + + au_writel(ac97_config, AC97C_CONFIG); +} + +static void start_dac(struct au1000_state *s) +{ + struct dmabuf *db = &s->dma_dac; + unsigned long flags; + unsigned long buf1, buf2; + + if (!db->stopped) + return; + + spin_lock_irqsave(&s->lock, flags); + + au_readl(AC97C_STATUS); // read status to clear sticky bits + + // reset Buffer 1 and 2 pointers to nextOut and nextOut+dma_fragsize + buf1 = virt_to_phys(db->nextOut); + buf2 = buf1 + db->dma_fragsize; + if (buf2 >= db->dmaaddr + db->dmasize) + buf2 -= db->dmasize; + + set_xmit_slots(db->num_channels); + + init_dma(db->dmanr); + if (get_dma_active_buffer(db->dmanr) == 0) { + clear_dma_done0(db->dmanr); // clear DMA done bit + set_dma_addr0(db->dmanr, buf1); + set_dma_addr1(db->dmanr, buf2); + } else { + clear_dma_done1(db->dmanr); // clear DMA done bit + set_dma_addr1(db->dmanr, buf1); + set_dma_addr0(db->dmanr, buf2); + } + set_dma_count(db->dmanr, db->dma_fragsize>>1); + enable_dma_buffers(db->dmanr); + + start_dma(db->dmanr); + +#ifdef AU1000_VERBOSE_DEBUG + dump_au1000_dma_channel(db->dmanr); +#endif + + db->stopped = 0; + + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_adc(struct au1000_state *s) +{ + struct dmabuf *db = &s->dma_adc; + unsigned long flags; + unsigned long buf1, buf2; + + if (!db->stopped) + return; + + spin_lock_irqsave(&s->lock, flags); + + au_readl(AC97C_STATUS); // read status to clear sticky bits + + // reset Buffer 1 and 2 pointers to nextIn and nextIn+dma_fragsize + buf1 = virt_to_phys(db->nextIn); + buf2 = buf1 + db->dma_fragsize; + if (buf2 >= db->dmaaddr + db->dmasize) + buf2 -= db->dmasize; + + set_recv_slots(db->num_channels); + + init_dma(db->dmanr); + if (get_dma_active_buffer(db->dmanr) == 0) { + clear_dma_done0(db->dmanr); // clear DMA done bit + set_dma_addr0(db->dmanr, buf1); + set_dma_addr1(db->dmanr, buf2); + } else { + clear_dma_done1(db->dmanr); // clear DMA done bit + set_dma_addr1(db->dmanr, buf1); + set_dma_addr0(db->dmanr, buf2); + } + set_dma_count(db->dmanr, db->dma_fragsize>>1); + enable_dma_buffers(db->dmanr); + + start_dma(db->dmanr); + +#ifdef AU1000_VERBOSE_DEBUG + dump_au1000_dma_channel(db->dmanr); +#endif + + db->stopped = 0; + + spin_unlock_irqrestore(&s->lock, flags); +} + +/* --------------------------------------------------------------------- */ + +#define DMABUF_DEFAULTORDER (17-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +extern inline void dealloc_dmabuf(struct au1000_state *s, struct dmabuf *db) +{ + struct page *page, *pend; + + if (db->rawbuf) { + /* undo marking the pages as reserved */ + pend = virt_to_page(db->rawbuf + + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + ClearPageReserved(page); + dma_free_noncoherent(NULL, + PAGE_SIZE << db->buforder, + db->rawbuf, + db->dmaaddr); + } + db->rawbuf = db->nextIn = db->nextOut = NULL; + db->mapped = db->ready = 0; +} + +static int prog_dmabuf(struct au1000_state *s, struct dmabuf *db) +{ + int order; + unsigned user_bytes_per_sec; + unsigned bufs; + struct page *page, *pend; + unsigned rate = db->sample_rate; + + if (!db->rawbuf) { + db->ready = db->mapped = 0; + for (order = DMABUF_DEFAULTORDER; + order >= DMABUF_MINORDER; order--) + if ((db->rawbuf = dma_alloc_noncoherent(NULL, + PAGE_SIZE << order, + &db->dmaaddr, + 0))) + break; + if (!db->rawbuf) + return -ENOMEM; + db->buforder = order; + /* now mark the pages as reserved; + otherwise remap_pfn_range doesn't do what we want */ + pend = virt_to_page(db->rawbuf + + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + SetPageReserved(page); + } + + db->cnt_factor = 1; + if (db->sample_size == 8) + db->cnt_factor *= 2; + if (db->num_channels == 1) + db->cnt_factor *= 2; + db->cnt_factor *= db->src_factor; + + db->count = 0; + db->nextIn = db->nextOut = db->rawbuf; + + db->user_bytes_per_sample = (db->sample_size>>3) * db->num_channels; + db->dma_bytes_per_sample = 2 * ((db->num_channels == 1) ? + 2 : db->num_channels); + + user_bytes_per_sec = rate * db->user_bytes_per_sample; + bufs = PAGE_SIZE << db->buforder; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < user_bytes_per_sec) + db->fragshift = ld2(user_bytes_per_sec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(user_bytes_per_sec / 100 / + (db->subdivision ? db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + + db->fragsize = 1 << db->fragshift; + db->dma_fragsize = db->fragsize * db->cnt_factor; + db->numfrag = bufs / db->dma_fragsize; + + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->fragsize = 1 << db->fragshift; + db->dma_fragsize = db->fragsize * db->cnt_factor; + db->numfrag = bufs / db->dma_fragsize; + } + + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; + + db->dmasize = db->dma_fragsize * db->numfrag; + memset(db->rawbuf, 0, bufs); + +#ifdef AU1000_VERBOSE_DEBUG + dbg("rate=%d, samplesize=%d, channels=%d", + rate, db->sample_size, db->num_channels); + dbg("fragsize=%d, cnt_factor=%d, dma_fragsize=%d", + db->fragsize, db->cnt_factor, db->dma_fragsize); + dbg("numfrag=%d, dmasize=%d", db->numfrag, db->dmasize); +#endif + + db->ready = 1; + return 0; +} + +extern inline int prog_dmabuf_adc(struct au1000_state *s) +{ + stop_adc(s); + return prog_dmabuf(s, &s->dma_adc); + +} + +extern inline int prog_dmabuf_dac(struct au1000_state *s) +{ + stop_dac(s); + return prog_dmabuf(s, &s->dma_dac); +} + + +/* hold spinlock for the following */ +static irqreturn_t dac_dma_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct au1000_state *s = (struct au1000_state *) dev_id; + struct dmabuf *dac = &s->dma_dac; + unsigned long newptr; + u32 ac97c_stat, buff_done; + + ac97c_stat = au_readl(AC97C_STATUS); +#ifdef AU1000_VERBOSE_DEBUG + if (ac97c_stat & (AC97C_XU | AC97C_XO | AC97C_TE)) + dbg("AC97C status = 0x%08x", ac97c_stat); +#endif + + if ((buff_done = get_dma_buffer_done(dac->dmanr)) == 0) { + /* fastpath out, to ease interrupt sharing */ + return IRQ_HANDLED; + } + + spin_lock(&s->lock); + + if (buff_done != (DMA_D0 | DMA_D1)) { + dac->nextOut += dac->dma_fragsize; + if (dac->nextOut >= dac->rawbuf + dac->dmasize) + dac->nextOut -= dac->dmasize; + + /* update playback pointers */ + newptr = virt_to_phys(dac->nextOut) + dac->dma_fragsize; + if (newptr >= dac->dmaaddr + dac->dmasize) + newptr -= dac->dmasize; + + dac->count -= dac->dma_fragsize; + dac->total_bytes += dac->dma_fragsize; + + if (dac->count <= 0) { +#ifdef AU1000_VERBOSE_DEBUG + dbg("dac underrun"); +#endif + spin_unlock(&s->lock); + stop_dac(s); + spin_lock(&s->lock); + dac->count = 0; + dac->nextIn = dac->nextOut; + } else if (buff_done == DMA_D0) { + clear_dma_done0(dac->dmanr); // clear DMA done bit + set_dma_count0(dac->dmanr, dac->dma_fragsize>>1); + set_dma_addr0(dac->dmanr, newptr); + enable_dma_buffer0(dac->dmanr); // reenable + } else { + clear_dma_done1(dac->dmanr); // clear DMA done bit + set_dma_count1(dac->dmanr, dac->dma_fragsize>>1); + set_dma_addr1(dac->dmanr, newptr); + enable_dma_buffer1(dac->dmanr); // reenable + } + } else { + // both done bits set, we missed an interrupt + spin_unlock(&s->lock); + stop_dac(s); + spin_lock(&s->lock); + + dac->nextOut += 2*dac->dma_fragsize; + if (dac->nextOut >= dac->rawbuf + dac->dmasize) + dac->nextOut -= dac->dmasize; + + dac->count -= 2*dac->dma_fragsize; + dac->total_bytes += 2*dac->dma_fragsize; + + if (dac->count > 0) { + spin_unlock(&s->lock); + start_dac(s); + spin_lock(&s->lock); + } + } + + /* wake up anybody listening */ + if (waitqueue_active(&dac->wait)) + wake_up(&dac->wait); + + spin_unlock(&s->lock); + + return IRQ_HANDLED; +} + + +static irqreturn_t adc_dma_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct au1000_state *s = (struct au1000_state *) dev_id; + struct dmabuf *adc = &s->dma_adc; + unsigned long newptr; + u32 ac97c_stat, buff_done; + + ac97c_stat = au_readl(AC97C_STATUS); +#ifdef AU1000_VERBOSE_DEBUG + if (ac97c_stat & (AC97C_RU | AC97C_RO)) + dbg("AC97C status = 0x%08x", ac97c_stat); +#endif + + if ((buff_done = get_dma_buffer_done(adc->dmanr)) == 0) { + /* fastpath out, to ease interrupt sharing */ + return IRQ_HANDLED; + } + + spin_lock(&s->lock); + + if (buff_done != (DMA_D0 | DMA_D1)) { + if (adc->count + adc->dma_fragsize > adc->dmasize) { + // Overrun. Stop ADC and log the error + spin_unlock(&s->lock); + stop_adc(s); + adc->error++; + err("adc overrun"); + return IRQ_NONE; + } + + adc->nextIn += adc->dma_fragsize; + if (adc->nextIn >= adc->rawbuf + adc->dmasize) + adc->nextIn -= adc->dmasize; + + /* update capture pointers */ + newptr = virt_to_phys(adc->nextIn) + adc->dma_fragsize; + if (newptr >= adc->dmaaddr + adc->dmasize) + newptr -= adc->dmasize; + + adc->count += adc->dma_fragsize; + adc->total_bytes += adc->dma_fragsize; + + if (buff_done == DMA_D0) { + clear_dma_done0(adc->dmanr); // clear DMA done bit + set_dma_count0(adc->dmanr, adc->dma_fragsize>>1); + set_dma_addr0(adc->dmanr, newptr); + enable_dma_buffer0(adc->dmanr); // reenable + } else { + clear_dma_done1(adc->dmanr); // clear DMA done bit + set_dma_count1(adc->dmanr, adc->dma_fragsize>>1); + set_dma_addr1(adc->dmanr, newptr); + enable_dma_buffer1(adc->dmanr); // reenable + } + } else { + // both done bits set, we missed an interrupt + spin_unlock(&s->lock); + stop_adc(s); + spin_lock(&s->lock); + + if (adc->count + 2*adc->dma_fragsize > adc->dmasize) { + // Overrun. Log the error + adc->error++; + err("adc overrun"); + spin_unlock(&s->lock); + return IRQ_NONE; + } + + adc->nextIn += 2*adc->dma_fragsize; + if (adc->nextIn >= adc->rawbuf + adc->dmasize) + adc->nextIn -= adc->dmasize; + + adc->count += 2*adc->dma_fragsize; + adc->total_bytes += 2*adc->dma_fragsize; + + spin_unlock(&s->lock); + start_adc(s); + spin_lock(&s->lock); + } + + /* wake up anybody listening */ + if (waitqueue_active(&adc->wait)) + wake_up(&adc->wait); + + spin_unlock(&s->lock); + + return IRQ_HANDLED; +} + +/* --------------------------------------------------------------------- */ + +static loff_t au1000_llseek(struct file *file, loff_t offset, int origin) +{ + return -ESPIPE; +} + + +static int au1000_open_mixdev(struct inode *inode, struct file *file) +{ + file->private_data = &au1000_state; + return nonseekable_open(inode, file); +} + +static int au1000_release_mixdev(struct inode *inode, struct file *file) +{ + return 0; +} + +static int mixdev_ioctl(struct ac97_codec *codec, unsigned int cmd, + unsigned long arg) +{ + return codec->mixer_ioctl(codec, cmd, arg); +} + +static int au1000_ioctl_mixdev(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct au1000_state *s = (struct au1000_state *)file->private_data; + struct ac97_codec *codec = &s->codec; + + return mixdev_ioctl(codec, cmd, arg); +} + +static /*const */ struct file_operations au1000_mixer_fops = { + .owner = THIS_MODULE, + .llseek = au1000_llseek, + .ioctl = au1000_ioctl_mixdev, + .open = au1000_open_mixdev, + .release = au1000_release_mixdev, +}; + +/* --------------------------------------------------------------------- */ + +static int drain_dac(struct au1000_state *s, int nonblock) +{ + unsigned long flags; + int count, tmo; + + if (s->dma_dac.mapped || !s->dma_dac.ready || s->dma_dac.stopped) + return 0; + + for (;;) { + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) + return -EBUSY; + tmo = 1000 * count / (s->no_vra ? + 48000 : s->dma_dac.sample_rate); + tmo /= s->dma_dac.dma_bytes_per_sample; + au1000_delay(tmo); + } + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static inline u8 S16_TO_U8(s16 ch) +{ + return (u8) (ch >> 8) + 0x80; +} +static inline s16 U8_TO_S16(u8 ch) +{ + return (s16) (ch - 0x80) << 8; +} + +/* + * Translates user samples to dma buffer suitable for AC'97 DAC data: + * If mono, copy left channel to right channel in dma buffer. + * If 8 bit samples, cvt to 16-bit before writing to dma buffer. + * If interpolating (no VRA), duplicate every audio frame src_factor times. + */ +static int translate_from_user(struct dmabuf *db, + char* dmabuf, + char* userbuf, + int dmacount) +{ + int sample, i; + int interp_bytes_per_sample; + int num_samples; + int mono = (db->num_channels == 1); + char usersample[12]; + s16 ch, dmasample[6]; + + if (db->sample_size == 16 && !mono && db->src_factor == 1) { + // no translation necessary, just copy + if (copy_from_user(dmabuf, userbuf, dmacount)) + return -EFAULT; + return dmacount; + } + + interp_bytes_per_sample = db->dma_bytes_per_sample * db->src_factor; + num_samples = dmacount / interp_bytes_per_sample; + + for (sample = 0; sample < num_samples; sample++) { + if (copy_from_user(usersample, userbuf, + db->user_bytes_per_sample)) { + dbg("%s: fault", __FUNCTION__); + return -EFAULT; + } + + for (i = 0; i < db->num_channels; i++) { + if (db->sample_size == 8) + ch = U8_TO_S16(usersample[i]); + else + ch = *((s16 *) (&usersample[i * 2])); + dmasample[i] = ch; + if (mono) + dmasample[i + 1] = ch; // right channel + } + + // duplicate every audio frame src_factor times + for (i = 0; i < db->src_factor; i++) + memcpy(dmabuf, dmasample, db->dma_bytes_per_sample); + + userbuf += db->user_bytes_per_sample; + dmabuf += interp_bytes_per_sample; + } + + return num_samples * interp_bytes_per_sample; +} + +/* + * Translates AC'97 ADC samples to user buffer: + * If mono, send only left channel to user buffer. + * If 8 bit samples, cvt from 16 to 8 bit before writing to user buffer. + * If decimating (no VRA), skip over src_factor audio frames. + */ +static int translate_to_user(struct dmabuf *db, + char* userbuf, + char* dmabuf, + int dmacount) +{ + int sample, i; + int interp_bytes_per_sample; + int num_samples; + int mono = (db->num_channels == 1); + char usersample[12]; + + if (db->sample_size == 16 && !mono && db->src_factor == 1) { + // no translation necessary, just copy + if (copy_to_user(userbuf, dmabuf, dmacount)) + return -EFAULT; + return dmacount; + } + + interp_bytes_per_sample = db->dma_bytes_per_sample * db->src_factor; + num_samples = dmacount / interp_bytes_per_sample; + + for (sample = 0; sample < num_samples; sample++) { + for (i = 0; i < db->num_channels; i++) { + if (db->sample_size == 8) + usersample[i] = + S16_TO_U8(*((s16 *) (&dmabuf[i * 2]))); + else + *((s16 *) (&usersample[i * 2])) = + *((s16 *) (&dmabuf[i * 2])); + } + + if (copy_to_user(userbuf, usersample, + db->user_bytes_per_sample)) { + dbg("%s: fault", __FUNCTION__); + return -EFAULT; + } + + userbuf += db->user_bytes_per_sample; + dmabuf += interp_bytes_per_sample; + } + + return num_samples * interp_bytes_per_sample; +} + +/* + * Copy audio data to/from user buffer from/to dma buffer, taking care + * that we wrap when reading/writing the dma buffer. Returns actual byte + * count written to or read from the dma buffer. + */ +static int copy_dmabuf_user(struct dmabuf *db, char* userbuf, + int count, int to_user) +{ + char *bufptr = to_user ? db->nextOut : db->nextIn; + char *bufend = db->rawbuf + db->dmasize; + int cnt, ret; + + if (bufptr + count > bufend) { + int partial = (int) (bufend - bufptr); + if (to_user) { + if ((cnt = translate_to_user(db, userbuf, + bufptr, partial)) < 0) + return cnt; + ret = cnt; + if ((cnt = translate_to_user(db, userbuf + partial, + db->rawbuf, + count - partial)) < 0) + return cnt; + ret += cnt; + } else { + if ((cnt = translate_from_user(db, bufptr, userbuf, + partial)) < 0) + return cnt; + ret = cnt; + if ((cnt = translate_from_user(db, db->rawbuf, + userbuf + partial, + count - partial)) < 0) + return cnt; + ret += cnt; + } + } else { + if (to_user) + ret = translate_to_user(db, userbuf, bufptr, count); + else + ret = translate_from_user(db, bufptr, userbuf, count); + } + + return ret; +} + + +static ssize_t au1000_read(struct file *file, char *buffer, + size_t count, loff_t *ppos) +{ + struct au1000_state *s = (struct au1000_state *)file->private_data; + struct dmabuf *db = &s->dma_adc; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + int cnt, usercnt, avail; + + if (db->mapped) + return -ENXIO; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; + + count *= db->cnt_factor; + + down(&s->sem); + add_wait_queue(&db->wait, &wait); + + while (count > 0) { + // wait for samples in ADC dma buffer + do { + if (db->stopped) + start_adc(s); + spin_lock_irqsave(&s->lock, flags); + avail = db->count; + if (avail <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (avail <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + up(&s->sem); + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out2; + } + down(&s->sem); + } + } while (avail <= 0); + + // copy from nextOut to user + if ((cnt = copy_dmabuf_user(db, buffer, + count > avail ? + avail : count, 1)) < 0) { + if (!ret) + ret = -EFAULT; + goto out; + } + + spin_lock_irqsave(&s->lock, flags); + db->count -= cnt; + db->nextOut += cnt; + if (db->nextOut >= db->rawbuf + db->dmasize) + db->nextOut -= db->dmasize; + spin_unlock_irqrestore(&s->lock, flags); + + count -= cnt; + usercnt = cnt / db->cnt_factor; + buffer += usercnt; + ret += usercnt; + } // while (count > 0) + +out: + up(&s->sem); +out2: + remove_wait_queue(&db->wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +static ssize_t au1000_write(struct file *file, const char *buffer, + size_t count, loff_t * ppos) +{ + struct au1000_state *s = (struct au1000_state *)file->private_data; + struct dmabuf *db = &s->dma_dac; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret = 0; + unsigned long flags; + int cnt, usercnt, avail; + +#ifdef AU1000_VERBOSE_DEBUG + dbg("write: count=%d", count); +#endif + + if (db->mapped) + return -ENXIO; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + + count *= db->cnt_factor; + + down(&s->sem); + add_wait_queue(&db->wait, &wait); + + while (count > 0) { + // wait for space in playback buffer + do { + spin_lock_irqsave(&s->lock, flags); + avail = (int) db->dmasize - db->count; + if (avail <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (avail <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + up(&s->sem); + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out2; + } + down(&s->sem); + } + } while (avail <= 0); + + // copy from user to nextIn + if ((cnt = copy_dmabuf_user(db, (char *) buffer, + count > avail ? + avail : count, 0)) < 0) { + if (!ret) + ret = -EFAULT; + goto out; + } + + spin_lock_irqsave(&s->lock, flags); + db->count += cnt; + db->nextIn += cnt; + if (db->nextIn >= db->rawbuf + db->dmasize) + db->nextIn -= db->dmasize; + spin_unlock_irqrestore(&s->lock, flags); + if (db->stopped) + start_dac(s); + + count -= cnt; + usercnt = cnt / db->cnt_factor; + buffer += usercnt; + ret += usercnt; + } // while (count > 0) + +out: + up(&s->sem); +out2: + remove_wait_queue(&db->wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + + +/* No kernel lock - we have our own spinlock */ +static unsigned int au1000_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct au1000_state *s = (struct au1000_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + if (file->f_mode & FMODE_WRITE) { + if (!s->dma_dac.ready) + return 0; + poll_wait(file, &s->dma_dac.wait, wait); + } + if (file->f_mode & FMODE_READ) { + if (!s->dma_adc.ready) + return 0; + poll_wait(file, &s->dma_adc.wait, wait); + } + + spin_lock_irqsave(&s->lock, flags); + + if (file->f_mode & FMODE_READ) { + if (s->dma_adc.count >= (signed)s->dma_adc.dma_fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->dma_dac.mapped) { + if (s->dma_dac.count >= + (signed)s->dma_dac.dma_fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed) s->dma_dac.dmasize >= + s->dma_dac.count + (signed)s->dma_dac.dma_fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int au1000_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct au1000_state *s = (struct au1000_state *)file->private_data; + struct dmabuf *db; + unsigned long size; + int ret = 0; + + dbg(__FUNCTION__); + + lock_kernel(); + down(&s->sem); + if (vma->vm_flags & VM_WRITE) + db = &s->dma_dac; + else if (vma->vm_flags & VM_READ) + db = &s->dma_adc; + else { + ret = -EINVAL; + goto out; + } + if (vma->vm_pgoff != 0) { + ret = -EINVAL; + goto out; + } + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << db->buforder)) { + ret = -EINVAL; + goto out; + } + if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(db->rawbuf), + size, vma->vm_page_prot)) { + ret = -EAGAIN; + goto out; + } + vma->vm_flags &= ~VM_IO; + db->mapped = 1; +out: + up(&s->sem); + unlock_kernel(); + return ret; +} + + +#ifdef AU1000_VERBOSE_DEBUG +static struct ioctl_str_t { + unsigned int cmd; + const char *str; +} ioctl_str[] = { + {SNDCTL_DSP_RESET, "SNDCTL_DSP_RESET"}, + {SNDCTL_DSP_SYNC, "SNDCTL_DSP_SYNC"}, + {SNDCTL_DSP_SPEED, "SNDCTL_DSP_SPEED"}, + {SNDCTL_DSP_STEREO, "SNDCTL_DSP_STEREO"}, + {SNDCTL_DSP_GETBLKSIZE, "SNDCTL_DSP_GETBLKSIZE"}, + {SNDCTL_DSP_SAMPLESIZE, "SNDCTL_DSP_SAMPLESIZE"}, + {SNDCTL_DSP_CHANNELS, "SNDCTL_DSP_CHANNELS"}, + {SOUND_PCM_WRITE_CHANNELS, "SOUND_PCM_WRITE_CHANNELS"}, + {SOUND_PCM_WRITE_FILTER, "SOUND_PCM_WRITE_FILTER"}, + {SNDCTL_DSP_POST, "SNDCTL_DSP_POST"}, + {SNDCTL_DSP_SUBDIVIDE, "SNDCTL_DSP_SUBDIVIDE"}, + {SNDCTL_DSP_SETFRAGMENT, "SNDCTL_DSP_SETFRAGMENT"}, + {SNDCTL_DSP_GETFMTS, "SNDCTL_DSP_GETFMTS"}, + {SNDCTL_DSP_SETFMT, "SNDCTL_DSP_SETFMT"}, + {SNDCTL_DSP_GETOSPACE, "SNDCTL_DSP_GETOSPACE"}, + {SNDCTL_DSP_GETISPACE, "SNDCTL_DSP_GETISPACE"}, + {SNDCTL_DSP_NONBLOCK, "SNDCTL_DSP_NONBLOCK"}, + {SNDCTL_DSP_GETCAPS, "SNDCTL_DSP_GETCAPS"}, + {SNDCTL_DSP_GETTRIGGER, "SNDCTL_DSP_GETTRIGGER"}, + {SNDCTL_DSP_SETTRIGGER, "SNDCTL_DSP_SETTRIGGER"}, + {SNDCTL_DSP_GETIPTR, "SNDCTL_DSP_GETIPTR"}, + {SNDCTL_DSP_GETOPTR, "SNDCTL_DSP_GETOPTR"}, + {SNDCTL_DSP_MAPINBUF, "SNDCTL_DSP_MAPINBUF"}, + {SNDCTL_DSP_MAPOUTBUF, "SNDCTL_DSP_MAPOUTBUF"}, + {SNDCTL_DSP_SETSYNCRO, "SNDCTL_DSP_SETSYNCRO"}, + {SNDCTL_DSP_SETDUPLEX, "SNDCTL_DSP_SETDUPLEX"}, + {SNDCTL_DSP_GETODELAY, "SNDCTL_DSP_GETODELAY"}, + {SNDCTL_DSP_GETCHANNELMASK, "SNDCTL_DSP_GETCHANNELMASK"}, + {SNDCTL_DSP_BIND_CHANNEL, "SNDCTL_DSP_BIND_CHANNEL"}, + {OSS_GETVERSION, "OSS_GETVERSION"}, + {SOUND_PCM_READ_RATE, "SOUND_PCM_READ_RATE"}, + {SOUND_PCM_READ_CHANNELS, "SOUND_PCM_READ_CHANNELS"}, + {SOUND_PCM_READ_BITS, "SOUND_PCM_READ_BITS"}, + {SOUND_PCM_READ_FILTER, "SOUND_PCM_READ_FILTER"} +}; +#endif + +// Need to hold a spin-lock before calling this! +static int dma_count_done(struct dmabuf *db) +{ + if (db->stopped) + return 0; + + return db->dma_fragsize - get_dma_residue(db->dmanr); +} + + +static int au1000_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct au1000_state *s = (struct au1000_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int count; + int val, mapped, ret, diff; + + mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac.mapped) || + ((file->f_mode & FMODE_READ) && s->dma_adc.mapped); + +#ifdef AU1000_VERBOSE_DEBUG + for (count=0; countf_mode & FMODE_WRITE) + return drain_dac(s, 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: + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + synchronize_irq(); + s->dma_dac.count = s->dma_dac.total_bytes = 0; + s->dma_dac.nextIn = s->dma_dac.nextOut = + s->dma_dac.rawbuf; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(); + s->dma_adc.count = s->dma_adc.total_bytes = 0; + s->dma_adc.nextIn = s->dma_adc.nextOut = + s->dma_adc.rawbuf; + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, (int *) arg)) + return -EFAULT; + if (val >= 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + set_adc_rate(s, val); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + set_dac_rate(s, val); + } + if (s->open_mode & FMODE_READ) + if ((ret = prog_dmabuf_adc(s))) + return ret; + if (s->open_mode & FMODE_WRITE) + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return put_user((file->f_mode & FMODE_READ) ? + s->dma_adc.sample_rate : + s->dma_dac.sample_rate, + (int *)arg); + + case SNDCTL_DSP_STEREO: + if (get_user(val, (int *) arg)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.num_channels = val ? 2 : 1; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.num_channels = val ? 2 : 1; + if (s->codec_ext_caps & AC97_EXT_DACS) { + // disable surround and center/lfe in AC'97 + u16 ext_stat = rdcodec(&s->codec, + AC97_EXTENDED_STATUS); + wrcodec(&s->codec, AC97_EXTENDED_STATUS, + ext_stat | (AC97_EXTSTAT_PRI | + AC97_EXTSTAT_PRJ | + AC97_EXTSTAT_PRK)); + } + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, (int *) arg)) + return -EFAULT; + if (val != 0) { + if (file->f_mode & FMODE_READ) { + if (val < 0 || val > 2) + return -EINVAL; + stop_adc(s); + s->dma_adc.num_channels = val; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + switch (val) { + case 1: + case 2: + break; + case 3: + case 5: + return -EINVAL; + case 4: + if (!(s->codec_ext_caps & + AC97_EXTID_SDAC)) + return -EINVAL; + break; + case 6: + if ((s->codec_ext_caps & + AC97_EXT_DACS) != AC97_EXT_DACS) + return -EINVAL; + break; + default: + return -EINVAL; + } + + stop_dac(s); + if (val <= 2 && + (s->codec_ext_caps & AC97_EXT_DACS)) { + // disable surround and center/lfe + // channels in AC'97 + u16 ext_stat = + rdcodec(&s->codec, + AC97_EXTENDED_STATUS); + wrcodec(&s->codec, + AC97_EXTENDED_STATUS, + ext_stat | (AC97_EXTSTAT_PRI | + AC97_EXTSTAT_PRJ | + AC97_EXTSTAT_PRK)); + } else if (val >= 4) { + // enable surround, center/lfe + // channels in AC'97 + u16 ext_stat = + rdcodec(&s->codec, + AC97_EXTENDED_STATUS); + ext_stat &= ~AC97_EXTSTAT_PRJ; + if (val == 6) + ext_stat &= + ~(AC97_EXTSTAT_PRI | + AC97_EXTSTAT_PRK); + wrcodec(&s->codec, + AC97_EXTENDED_STATUS, + ext_stat); + } + + s->dma_dac.num_channels = val; + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + } + return put_user(val, (int *) arg); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_LE | AFMT_U8, (int *) arg); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt */ + if (get_user(val, (int *) arg)) + return -EFAULT; + if (val != AFMT_QUERY) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + if (val == AFMT_S16_LE) + s->dma_adc.sample_size = 16; + else { + val = AFMT_U8; + s->dma_adc.sample_size = 8; + } + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + if (val == AFMT_S16_LE) + s->dma_dac.sample_size = 16; + else { + val = AFMT_U8; + s->dma_dac.sample_size = 8; + } + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + } else { + if (file->f_mode & FMODE_READ) + val = (s->dma_adc.sample_size == 16) ? + AFMT_S16_LE : AFMT_U8; + else + val = (s->dma_dac.sample_size == 16) ? + AFMT_S16_LE : AFMT_U8; + } + return put_user(val, (int *) arg); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + spin_lock_irqsave(&s->lock, flags); + if (file->f_mode & FMODE_READ && !s->dma_adc.stopped) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && !s->dma_dac.stopped) + val |= PCM_ENABLE_OUTPUT; + spin_unlock_irqrestore(&s->lock, flags); + 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) + start_adc(s); + else + stop_adc(s); + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) + start_dac(s); + else + stop_dac(s); + } + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + abinfo.fragsize = s->dma_dac.fragsize; + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + count -= dma_count_done(&s->dma_dac); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + abinfo.bytes = (s->dma_dac.dmasize - count) / + s->dma_dac.cnt_factor; + abinfo.fragstotal = s->dma_dac.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift; +#ifdef AU1000_VERBOSE_DEBUG + dbg("bytes=%d, fragments=%d", abinfo.bytes, abinfo.fragments); +#endif + return copy_to_user((void *) arg, &abinfo, + sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + abinfo.fragsize = s->dma_adc.fragsize; + spin_lock_irqsave(&s->lock, flags); + count = s->dma_adc.count; + count += dma_count_done(&s->dma_adc); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + abinfo.bytes = count / s->dma_adc.cnt_factor; + abinfo.fragstotal = s->dma_adc.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_adc.fragshift; + return copy_to_user((void *) arg, &abinfo, + sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + count -= dma_count_done(&s->dma_dac); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + count /= s->dma_dac.cnt_factor; + return put_user(count, (int *) arg); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cinfo.bytes = s->dma_adc.total_bytes; + count = s->dma_adc.count; + if (!s->dma_adc.stopped) { + diff = dma_count_done(&s->dma_adc); + count += diff; + cinfo.bytes += diff; + cinfo.ptr = virt_to_phys(s->dma_adc.nextIn) + diff - + s->dma_adc.dmaaddr; + } else + cinfo.ptr = virt_to_phys(s->dma_adc.nextIn) - + s->dma_adc.dmaaddr; + if (s->dma_adc.mapped) + s->dma_adc.count &= (s->dma_adc.dma_fragsize-1); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_adc.fragshift; + return copy_to_user((void *) arg, &cinfo, sizeof(cinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cinfo.bytes = s->dma_dac.total_bytes; + count = s->dma_dac.count; + if (!s->dma_dac.stopped) { + diff = dma_count_done(&s->dma_dac); + count -= diff; + cinfo.bytes += diff; + cinfo.ptr = virt_to_phys(s->dma_dac.nextOut) + diff - + s->dma_dac.dmaaddr; + } else + cinfo.ptr = virt_to_phys(s->dma_dac.nextOut) - + s->dma_dac.dmaaddr; + if (s->dma_dac.mapped) + s->dma_dac.count &= (s->dma_dac.dma_fragsize-1); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_dac.fragshift; + return copy_to_user((void *) arg, &cinfo, sizeof(cinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) + return put_user(s->dma_dac.fragsize, (int *) arg); + else + return put_user(s->dma_adc.fragsize, (int *) arg); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, (int *) arg)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ossfragshift = val & 0xffff; + s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_adc.ossfragshift < 4) + s->dma_adc.ossfragshift = 4; + if (s->dma_adc.ossfragshift > 15) + s->dma_adc.ossfragshift = 15; + if (s->dma_adc.ossmaxfrags < 4) + s->dma_adc.ossmaxfrags = 4; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ossfragshift = val & 0xffff; + s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac.ossfragshift < 4) + s->dma_dac.ossfragshift = 4; + if (s->dma_dac.ossfragshift > 15) + s->dma_dac.ossfragshift = 15; + if (s->dma_dac.ossmaxfrags < 4) + s->dma_dac.ossmaxfrags = 4; + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return 0; + + 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) { + stop_adc(s); + s->dma_adc.subdivision = val; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.subdivision = val; + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return 0; + + case SOUND_PCM_READ_RATE: + return put_user((file->f_mode & FMODE_READ) ? + s->dma_adc.sample_rate : + s->dma_dac.sample_rate, + (int *)arg); + + case SOUND_PCM_READ_CHANNELS: + if (file->f_mode & FMODE_READ) + return put_user(s->dma_adc.num_channels, (int *)arg); + else + return put_user(s->dma_dac.num_channels, (int *)arg); + + case SOUND_PCM_READ_BITS: + if (file->f_mode & FMODE_READ) + return put_user(s->dma_adc.sample_size, (int *)arg); + else + return put_user(s->dma_dac.sample_size, (int *)arg); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + } + + return mixdev_ioctl(&s->codec, cmd, arg); +} + + +static int au1000_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + struct au1000_state *s = &au1000_state; + int ret; + +#ifdef AU1000_VERBOSE_DEBUG + if (file->f_flags & O_NONBLOCK) + dbg("%s: non-blocking", __FUNCTION__); + else + dbg("%s: blocking", __FUNCTION__); +#endif + + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & file->f_mode) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + + stop_dac(s); + stop_adc(s); + + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = + s->dma_adc.subdivision = s->dma_adc.total_bytes = 0; + s->dma_adc.num_channels = 1; + s->dma_adc.sample_size = 8; + set_adc_rate(s, 8000); + if ((minor & 0xf) == SND_DEV_DSP16) + s->dma_adc.sample_size = 16; + } + + if (file->f_mode & FMODE_WRITE) { + s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = + s->dma_dac.subdivision = s->dma_dac.total_bytes = 0; + s->dma_dac.num_channels = 1; + s->dma_dac.sample_size = 8; + set_dac_rate(s, 8000); + if ((minor & 0xf) == SND_DEV_DSP16) + s->dma_dac.sample_size = 16; + } + + if (file->f_mode & FMODE_READ) { + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + + s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + up(&s->open_sem); + init_MUTEX(&s->sem); + return nonseekable_open(inode, file); +} + +static int au1000_release(struct inode *inode, struct file *file) +{ + struct au1000_state *s = (struct au1000_state *)file->private_data; + + lock_kernel(); + + if (file->f_mode & FMODE_WRITE) { + unlock_kernel(); + drain_dac(s, file->f_flags & O_NONBLOCK); + lock_kernel(); + } + + down(&s->open_sem); + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + dealloc_dmabuf(s, &s->dma_dac); + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + dealloc_dmabuf(s, &s->dma_adc); + } + s->open_mode &= ((~file->f_mode) & (FMODE_READ|FMODE_WRITE)); + up(&s->open_sem); + wake_up(&s->open_wait); + unlock_kernel(); + return 0; +} + +static /*const */ struct file_operations au1000_audio_fops = { + .owner = THIS_MODULE, + .llseek = au1000_llseek, + .read = au1000_read, + .write = au1000_write, + .poll = au1000_poll, + .ioctl = au1000_ioctl, + .mmap = au1000_mmap, + .open = au1000_open, + .release = au1000_release, +}; + + +/* --------------------------------------------------------------------- */ + + +/* --------------------------------------------------------------------- */ + +/* + * for debugging purposes, we'll create a proc device that dumps the + * CODEC chipstate + */ + +#ifdef AU1000_DEBUG +static int proc_au1000_dump(char *buf, char **start, off_t fpos, + int length, int *eof, void *data) +{ + struct au1000_state *s = &au1000_state; + int cnt, len = 0; + + /* print out header */ + len += sprintf(buf + len, "\n\t\tAU1000 Audio Debug\n\n"); + + // print out digital controller state + len += sprintf(buf + len, "AU1000 Audio Controller registers\n"); + len += sprintf(buf + len, "---------------------------------\n"); + len += sprintf (buf + len, "AC97C_CONFIG = %08x\n", + au_readl(AC97C_CONFIG)); + len += sprintf (buf + len, "AC97C_STATUS = %08x\n", + au_readl(AC97C_STATUS)); + len += sprintf (buf + len, "AC97C_CNTRL = %08x\n", + au_readl(AC97C_CNTRL)); + + /* print out CODEC state */ + len += sprintf(buf + len, "\nAC97 CODEC registers\n"); + len += sprintf(buf + len, "----------------------\n"); + for (cnt = 0; cnt <= 0x7e; cnt += 2) + len += sprintf(buf + len, "reg %02x = %04x\n", + cnt, rdcodec(&s->codec, cnt)); + + if (fpos >= len) { + *start = buf; + *eof = 1; + return 0; + } + *start = buf + fpos; + if ((len -= fpos) > length) + return length; + *eof = 1; + return len; + +} +#endif /* AU1000_DEBUG */ + +/* --------------------------------------------------------------------- */ + +MODULE_AUTHOR("Monta Vista Software, stevel@mvista.com"); +MODULE_DESCRIPTION("Au1000 Audio Driver"); + +/* --------------------------------------------------------------------- */ + +static int __devinit au1000_probe(void) +{ + struct au1000_state *s = &au1000_state; + int val; +#ifdef AU1000_DEBUG + char proc_str[80]; +#endif + + memset(s, 0, sizeof(struct au1000_state)); + + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac.wait); + init_waitqueue_head(&s->open_wait); + init_MUTEX(&s->open_sem); + spin_lock_init(&s->lock); + s->codec.private_data = s; + s->codec.id = 0; + s->codec.codec_read = rdcodec; + s->codec.codec_write = wrcodec; + s->codec.codec_wait = waitcodec; + + if (!request_mem_region(CPHYSADDR(AC97C_CONFIG), + 0x14, AU1000_MODULE_NAME)) { + err("AC'97 ports in use"); + return -1; + } + // Allocate the DMA Channels + if ((s->dma_dac.dmanr = request_au1000_dma(DMA_ID_AC97C_TX, + "audio DAC", + dac_dma_interrupt, + SA_INTERRUPT, s)) < 0) { + err("Can't get DAC DMA"); + goto err_dma1; + } + if ((s->dma_adc.dmanr = request_au1000_dma(DMA_ID_AC97C_RX, + "audio ADC", + adc_dma_interrupt, + SA_INTERRUPT, s)) < 0) { + err("Can't get ADC DMA"); + goto err_dma2; + } + + info("DAC: DMA%d/IRQ%d, ADC: DMA%d/IRQ%d", + s->dma_dac.dmanr, get_dma_done_irq(s->dma_dac.dmanr), + s->dma_adc.dmanr, get_dma_done_irq(s->dma_adc.dmanr)); + + // enable DMA coherency in read/write DMA channels + set_dma_mode(s->dma_dac.dmanr, + get_dma_mode(s->dma_dac.dmanr) & ~DMA_NC); + set_dma_mode(s->dma_adc.dmanr, + get_dma_mode(s->dma_adc.dmanr) & ~DMA_NC); + + /* register devices */ + + if ((s->dev_audio = register_sound_dsp(&au1000_audio_fops, -1)) < 0) + goto err_dev1; + if ((s->codec.dev_mixer = + register_sound_mixer(&au1000_mixer_fops, -1)) < 0) + goto err_dev2; + +#ifdef AU1000_DEBUG + /* intialize the debug proc device */ + s->ps = create_proc_read_entry(AU1000_MODULE_NAME, 0, NULL, + proc_au1000_dump, NULL); +#endif /* AU1000_DEBUG */ + + // configure pins for AC'97 + au_writel(au_readl(SYS_PINFUNC) & ~0x02, SYS_PINFUNC); + + // Assert reset for 10msec to the AC'97 controller, and enable clock + au_writel(AC97C_RS | AC97C_CE, AC97C_CNTRL); + au1000_delay(10); + au_writel(AC97C_CE, AC97C_CNTRL); + au1000_delay(10); // wait for clock to stabilize + + /* cold reset the AC'97 */ + au_writel(AC97C_RESET, AC97C_CONFIG); + au1000_delay(10); + au_writel(0, AC97C_CONFIG); + /* need to delay around 500msec(bleech) to give + some CODECs enough time to wakeup */ + au1000_delay(500); + + /* warm reset the AC'97 to start the bitclk */ + au_writel(AC97C_SG | AC97C_SYNC, AC97C_CONFIG); + udelay(100); + au_writel(0, AC97C_CONFIG); + + /* codec init */ + if (!ac97_probe_codec(&s->codec)) + goto err_dev3; + + s->codec_base_caps = rdcodec(&s->codec, AC97_RESET); + s->codec_ext_caps = rdcodec(&s->codec, AC97_EXTENDED_ID); + info("AC'97 Base/Extended ID = %04x/%04x", + s->codec_base_caps, s->codec_ext_caps); + + /* + * On the Pb1000, audio playback is on the AUX_OUT + * channel (which defaults to LNLVL_OUT in AC'97 + * rev 2.2) so make sure this channel is listed + * as supported (soundcard.h calls this channel + * ALTPCM). ac97_codec.c does not handle detection + * of this channel correctly. + */ + s->codec.supported_mixers |= SOUND_MASK_ALTPCM; + /* + * Now set AUX_OUT's default volume. + */ + val = 0x4343; + mixdev_ioctl(&s->codec, SOUND_MIXER_WRITE_ALTPCM, + (unsigned long) &val); + + if (!(s->codec_ext_caps & AC97_EXTID_VRA)) { + // codec does not support VRA + s->no_vra = 1; + } else if (!vra) { + // Boot option says disable VRA + u16 ac97_extstat = rdcodec(&s->codec, AC97_EXTENDED_STATUS); + wrcodec(&s->codec, AC97_EXTENDED_STATUS, + ac97_extstat & ~AC97_EXTSTAT_VRA); + s->no_vra = 1; + } + if (s->no_vra) + info("no VRA, interpolating and decimating"); + + /* set mic to be the recording source */ + val = SOUND_MASK_MIC; + mixdev_ioctl(&s->codec, SOUND_MIXER_WRITE_RECSRC, + (unsigned long) &val); + +#ifdef AU1000_DEBUG + sprintf(proc_str, "driver/%s/%d/ac97", AU1000_MODULE_NAME, + s->codec.id); + s->ac97_ps = create_proc_read_entry (proc_str, 0, NULL, + ac97_read_proc, &s->codec); +#endif + +#ifdef CONFIG_MIPS_XXS1500 + /* deassert eapd */ + wrcodec(&s->codec, AC97_POWER_CONTROL, + rdcodec(&s->codec, AC97_POWER_CONTROL) & ~0x8000); + /* mute a number of signals which seem to be causing problems + * if not muted. + */ + wrcodec(&s->codec, AC97_PCBEEP_VOL, 0x8000); + wrcodec(&s->codec, AC97_PHONE_VOL, 0x8008); + wrcodec(&s->codec, AC97_MIC_VOL, 0x8008); + wrcodec(&s->codec, AC97_LINEIN_VOL, 0x8808); + wrcodec(&s->codec, AC97_CD_VOL, 0x8808); + wrcodec(&s->codec, AC97_VIDEO_VOL, 0x8808); + wrcodec(&s->codec, AC97_AUX_VOL, 0x8808); + wrcodec(&s->codec, AC97_PCMOUT_VOL, 0x0808); + wrcodec(&s->codec, AC97_GENERAL_PURPOSE, 0x2000); +#endif + + return 0; + + err_dev3: + unregister_sound_mixer(s->codec.dev_mixer); + err_dev2: + unregister_sound_dsp(s->dev_audio); + err_dev1: + free_au1000_dma(s->dma_adc.dmanr); + err_dma2: + free_au1000_dma(s->dma_dac.dmanr); + err_dma1: + release_mem_region(CPHYSADDR(AC97C_CONFIG), 0x14); + return -1; +} + +static void au1000_remove(void) +{ + struct au1000_state *s = &au1000_state; + + if (!s) + return; +#ifdef AU1000_DEBUG + if (s->ps) + remove_proc_entry(AU1000_MODULE_NAME, NULL); +#endif /* AU1000_DEBUG */ + synchronize_irq(); + free_au1000_dma(s->dma_adc.dmanr); + free_au1000_dma(s->dma_dac.dmanr); + release_mem_region(CPHYSADDR(AC97C_CONFIG), 0x14); + unregister_sound_dsp(s->dev_audio); + unregister_sound_mixer(s->codec.dev_mixer); +} + +static int __init init_au1000(void) +{ + info("stevel@mvista.com, built " __TIME__ " on " __DATE__); + return au1000_probe(); +} + +static void __exit cleanup_au1000(void) +{ + info("unloading"); + au1000_remove(); +} + +module_init(init_au1000); +module_exit(cleanup_au1000); + +/* --------------------------------------------------------------------- */ + +#ifndef MODULE + +static int __init au1000_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ","))) { + if (!*this_opt) + continue; + if (!strncmp(this_opt, "vra", 3)) { + vra = 1; + } + } + + return 1; +} + +__setup("au1000_audio=", au1000_setup); + +#endif /* MODULE */ diff --git a/sound/oss/au1550_ac97.c b/sound/oss/au1550_ac97.c new file mode 100644 index 000000000000..a78e48d412d2 --- /dev/null +++ b/sound/oss/au1550_ac97.c @@ -0,0 +1,2119 @@ +/* + * au1550_ac97.c -- Sound driver for Alchemy Au1550 MIPS Internet Edge + * Processor. + * + * Copyright 2004 Embedded Edge, LLC + * dan@embeddededge.com + * + * Mostly copied from the au1000.c driver and some from the + * PowerMac dbdma driver. + * We assume the processor can do memory coherent DMA. + * + * Ported to 2.6 by Matt Porter + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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 DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef OSS_DOCUMENTED_MIXER_SEMANTICS + +/* misc stuff */ +#define POLL_COUNT 0x50000 +#define AC97_EXT_DACS (AC97_EXTID_SDAC | AC97_EXTID_CDAC | AC97_EXTID_LDAC) + +/* The number of DBDMA ring descriptors to allocate. No sense making + * this too large....if you can't keep up with a few you aren't likely + * to be able to with lots of them, either. + */ +#define NUM_DBDMA_DESCRIPTORS 4 + +#define err(format, arg...) printk(KERN_ERR format "\n" , ## arg) + +/* Boot options + * 0 = no VRA, 1 = use VRA if codec supports it + */ +static int vra = 1; +MODULE_PARM(vra, "i"); +MODULE_PARM_DESC(vra, "if 1 use VRA if codec supports it"); + +static struct au1550_state { + /* soundcore stuff */ + int dev_audio; + + struct ac97_codec *codec; + unsigned codec_base_caps; /* AC'97 reg 00h, "Reset Register" */ + unsigned codec_ext_caps; /* AC'97 reg 28h, "Extended Audio ID" */ + int no_vra; /* do not use VRA */ + + spinlock_t lock; + struct semaphore open_sem; + struct semaphore sem; + mode_t open_mode; + wait_queue_head_t open_wait; + + struct dmabuf { + u32 dmanr; + unsigned sample_rate; + unsigned src_factor; + unsigned sample_size; + int num_channels; + int dma_bytes_per_sample; + int user_bytes_per_sample; + int cnt_factor; + + void *rawbuf; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + void *nextIn; + void *nextOut; + int count; + unsigned total_bytes; + unsigned error; + wait_queue_head_t wait; + + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dma_fragsize; + unsigned dmasize; + unsigned dma_qcount; + + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned stopped:1; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + } dma_dac, dma_adc; +} au1550_state; + +static unsigned +ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +static void +au1550_delay(int msec) +{ + unsigned long tmo; + signed long tmo2; + + if (in_interrupt()) + return; + + tmo = jiffies + (msec * HZ) / 1000; + for (;;) { + tmo2 = tmo - jiffies; + if (tmo2 <= 0) + break; + schedule_timeout(tmo2); + } +} + +static u16 +rdcodec(struct ac97_codec *codec, u8 addr) +{ + struct au1550_state *s = (struct au1550_state *)codec->private_data; + unsigned long flags; + u32 cmd, val; + u16 data; + int i; + + spin_lock_irqsave(&s->lock, flags); + + for (i = 0; i < POLL_COUNT; i++) { + val = au_readl(PSC_AC97STAT); + au_sync(); + if (!(val & PSC_AC97STAT_CP)) + break; + } + if (i == POLL_COUNT) + err("rdcodec: codec cmd pending expired!"); + + cmd = (u32)PSC_AC97CDC_INDX(addr); + cmd |= PSC_AC97CDC_RD; /* read command */ + au_writel(cmd, PSC_AC97CDC); + au_sync(); + + /* now wait for the data + */ + for (i = 0; i < POLL_COUNT; i++) { + val = au_readl(PSC_AC97STAT); + au_sync(); + if (!(val & PSC_AC97STAT_CP)) + break; + } + if (i == POLL_COUNT) { + err("rdcodec: read poll expired!"); + return 0; + } + + /* wait for command done? + */ + for (i = 0; i < POLL_COUNT; i++) { + val = au_readl(PSC_AC97EVNT); + au_sync(); + if (val & PSC_AC97EVNT_CD) + break; + } + if (i == POLL_COUNT) { + err("rdcodec: read cmdwait expired!"); + return 0; + } + + data = au_readl(PSC_AC97CDC) & 0xffff; + au_sync(); + + /* Clear command done event. + */ + au_writel(PSC_AC97EVNT_CD, PSC_AC97EVNT); + au_sync(); + + spin_unlock_irqrestore(&s->lock, flags); + + return data; +} + + +static void +wrcodec(struct ac97_codec *codec, u8 addr, u16 data) +{ + struct au1550_state *s = (struct au1550_state *)codec->private_data; + unsigned long flags; + u32 cmd, val; + int i; + + spin_lock_irqsave(&s->lock, flags); + + for (i = 0; i < POLL_COUNT; i++) { + val = au_readl(PSC_AC97STAT); + au_sync(); + if (!(val & PSC_AC97STAT_CP)) + break; + } + if (i == POLL_COUNT) + err("wrcodec: codec cmd pending expired!"); + + cmd = (u32)PSC_AC97CDC_INDX(addr); + cmd |= (u32)data; + au_writel(cmd, PSC_AC97CDC); + au_sync(); + + for (i = 0; i < POLL_COUNT; i++) { + val = au_readl(PSC_AC97STAT); + au_sync(); + if (!(val & PSC_AC97STAT_CP)) + break; + } + if (i == POLL_COUNT) + err("wrcodec: codec cmd pending expired!"); + + for (i = 0; i < POLL_COUNT; i++) { + val = au_readl(PSC_AC97EVNT); + au_sync(); + if (val & PSC_AC97EVNT_CD) + break; + } + if (i == POLL_COUNT) + err("wrcodec: read cmdwait expired!"); + + /* Clear command done event. + */ + au_writel(PSC_AC97EVNT_CD, PSC_AC97EVNT); + au_sync(); + + spin_unlock_irqrestore(&s->lock, flags); +} + +static void +waitcodec(struct ac97_codec *codec) +{ + u16 temp; + u32 val; + int i; + + /* codec_wait is used to wait for a ready state after + * an AC97C_RESET. + */ + au1550_delay(10); + + /* first poll the CODEC_READY tag bit + */ + for (i = 0; i < POLL_COUNT; i++) { + val = au_readl(PSC_AC97STAT); + au_sync(); + if (val & PSC_AC97STAT_CR) + break; + } + if (i == POLL_COUNT) { + err("waitcodec: CODEC_READY poll expired!"); + return; + } + + /* get AC'97 powerdown control/status register + */ + temp = rdcodec(codec, AC97_POWER_CONTROL); + + /* If anything is powered down, power'em up + */ + if (temp & 0x7f00) { + /* Power on + */ + wrcodec(codec, AC97_POWER_CONTROL, 0); + au1550_delay(100); + + /* Reread + */ + temp = rdcodec(codec, AC97_POWER_CONTROL); + } + + /* Check if Codec REF,ANL,DAC,ADC ready + */ + if ((temp & 0x7f0f) != 0x000f) + err("codec reg 26 status (0x%x) not ready!!", temp); +} + +/* stop the ADC before calling */ +static void +set_adc_rate(struct au1550_state *s, unsigned rate) +{ + struct dmabuf *adc = &s->dma_adc; + struct dmabuf *dac = &s->dma_dac; + unsigned adc_rate, dac_rate; + u16 ac97_extstat; + + if (s->no_vra) { + /* calc SRC factor + */ + adc->src_factor = ((96000 / rate) + 1) >> 1; + adc->sample_rate = 48000 / adc->src_factor; + return; + } + + adc->src_factor = 1; + + ac97_extstat = rdcodec(s->codec, AC97_EXTENDED_STATUS); + + rate = rate > 48000 ? 48000 : rate; + + /* enable VRA + */ + wrcodec(s->codec, AC97_EXTENDED_STATUS, + ac97_extstat | AC97_EXTSTAT_VRA); + + /* now write the sample rate + */ + wrcodec(s->codec, AC97_PCM_LR_ADC_RATE, (u16) rate); + + /* read it back for actual supported rate + */ + adc_rate = rdcodec(s->codec, AC97_PCM_LR_ADC_RATE); + + pr_debug("set_adc_rate: set to %d Hz\n", adc_rate); + + /* some codec's don't allow unequal DAC and ADC rates, in which case + * writing one rate reg actually changes both. + */ + dac_rate = rdcodec(s->codec, AC97_PCM_FRONT_DAC_RATE); + if (dac->num_channels > 2) + wrcodec(s->codec, AC97_PCM_SURR_DAC_RATE, dac_rate); + if (dac->num_channels > 4) + wrcodec(s->codec, AC97_PCM_LFE_DAC_RATE, dac_rate); + + adc->sample_rate = adc_rate; + dac->sample_rate = dac_rate; +} + +/* stop the DAC before calling */ +static void +set_dac_rate(struct au1550_state *s, unsigned rate) +{ + struct dmabuf *dac = &s->dma_dac; + struct dmabuf *adc = &s->dma_adc; + unsigned adc_rate, dac_rate; + u16 ac97_extstat; + + if (s->no_vra) { + /* calc SRC factor + */ + dac->src_factor = ((96000 / rate) + 1) >> 1; + dac->sample_rate = 48000 / dac->src_factor; + return; + } + + dac->src_factor = 1; + + ac97_extstat = rdcodec(s->codec, AC97_EXTENDED_STATUS); + + rate = rate > 48000 ? 48000 : rate; + + /* enable VRA + */ + wrcodec(s->codec, AC97_EXTENDED_STATUS, + ac97_extstat | AC97_EXTSTAT_VRA); + + /* now write the sample rate + */ + wrcodec(s->codec, AC97_PCM_FRONT_DAC_RATE, (u16) rate); + + /* I don't support different sample rates for multichannel, + * so make these channels the same. + */ + if (dac->num_channels > 2) + wrcodec(s->codec, AC97_PCM_SURR_DAC_RATE, (u16) rate); + if (dac->num_channels > 4) + wrcodec(s->codec, AC97_PCM_LFE_DAC_RATE, (u16) rate); + /* read it back for actual supported rate + */ + dac_rate = rdcodec(s->codec, AC97_PCM_FRONT_DAC_RATE); + + pr_debug("set_dac_rate: set to %d Hz\n", dac_rate); + + /* some codec's don't allow unequal DAC and ADC rates, in which case + * writing one rate reg actually changes both. + */ + adc_rate = rdcodec(s->codec, AC97_PCM_LR_ADC_RATE); + + dac->sample_rate = dac_rate; + adc->sample_rate = adc_rate; +} + +static void +stop_dac(struct au1550_state *s) +{ + struct dmabuf *db = &s->dma_dac; + u32 stat; + unsigned long flags; + + if (db->stopped) + return; + + spin_lock_irqsave(&s->lock, flags); + + au_writel(PSC_AC97PCR_TP, PSC_AC97PCR); + au_sync(); + + /* Wait for Transmit Busy to show disabled. + */ + do { + stat = readl((void *)PSC_AC97STAT); + au_sync(); + } while ((stat & PSC_AC97STAT_TB) != 0); + + au1xxx_dbdma_reset(db->dmanr); + + db->stopped = 1; + + spin_unlock_irqrestore(&s->lock, flags); +} + +static void +stop_adc(struct au1550_state *s) +{ + struct dmabuf *db = &s->dma_adc; + unsigned long flags; + u32 stat; + + if (db->stopped) + return; + + spin_lock_irqsave(&s->lock, flags); + + au_writel(PSC_AC97PCR_RP, PSC_AC97PCR); + au_sync(); + + /* Wait for Receive Busy to show disabled. + */ + do { + stat = readl((void *)PSC_AC97STAT); + au_sync(); + } while ((stat & PSC_AC97STAT_RB) != 0); + + au1xxx_dbdma_reset(db->dmanr); + + db->stopped = 1; + + spin_unlock_irqrestore(&s->lock, flags); +} + + +static void +set_xmit_slots(int num_channels) +{ + u32 ac97_config, stat; + + ac97_config = au_readl(PSC_AC97CFG); + au_sync(); + ac97_config &= ~(PSC_AC97CFG_TXSLOT_MASK | PSC_AC97CFG_DE_ENABLE); + au_writel(ac97_config, PSC_AC97CFG); + au_sync(); + + switch (num_channels) { + case 6: /* stereo with surround and center/LFE, + * slots 3,4,6,7,8,9 + */ + ac97_config |= PSC_AC97CFG_TXSLOT_ENA(6); + ac97_config |= PSC_AC97CFG_TXSLOT_ENA(9); + + case 4: /* stereo with surround, slots 3,4,7,8 */ + ac97_config |= PSC_AC97CFG_TXSLOT_ENA(7); + ac97_config |= PSC_AC97CFG_TXSLOT_ENA(8); + + case 2: /* stereo, slots 3,4 */ + case 1: /* mono */ + ac97_config |= PSC_AC97CFG_TXSLOT_ENA(3); + ac97_config |= PSC_AC97CFG_TXSLOT_ENA(4); + } + + au_writel(ac97_config, PSC_AC97CFG); + au_sync(); + + ac97_config |= PSC_AC97CFG_DE_ENABLE; + au_writel(ac97_config, PSC_AC97CFG); + au_sync(); + + /* Wait for Device ready. + */ + do { + stat = readl((void *)PSC_AC97STAT); + au_sync(); + } while ((stat & PSC_AC97STAT_DR) == 0); +} + +static void +set_recv_slots(int num_channels) +{ + u32 ac97_config, stat; + + ac97_config = au_readl(PSC_AC97CFG); + au_sync(); + ac97_config &= ~(PSC_AC97CFG_RXSLOT_MASK | PSC_AC97CFG_DE_ENABLE); + au_writel(ac97_config, PSC_AC97CFG); + au_sync(); + + /* Always enable slots 3 and 4 (stereo). Slot 6 is + * optional Mic ADC, which we don't support yet. + */ + ac97_config |= PSC_AC97CFG_RXSLOT_ENA(3); + ac97_config |= PSC_AC97CFG_RXSLOT_ENA(4); + + au_writel(ac97_config, PSC_AC97CFG); + au_sync(); + + ac97_config |= PSC_AC97CFG_DE_ENABLE; + au_writel(ac97_config, PSC_AC97CFG); + au_sync(); + + /* Wait for Device ready. + */ + do { + stat = readl((void *)PSC_AC97STAT); + au_sync(); + } while ((stat & PSC_AC97STAT_DR) == 0); +} + +static void +start_dac(struct au1550_state *s) +{ + struct dmabuf *db = &s->dma_dac; + unsigned long flags; + + if (!db->stopped) + return; + + spin_lock_irqsave(&s->lock, flags); + + set_xmit_slots(db->num_channels); + au_writel(PSC_AC97PCR_TC, PSC_AC97PCR); + au_sync(); + au_writel(PSC_AC97PCR_TS, PSC_AC97PCR); + au_sync(); + + au1xxx_dbdma_start(db->dmanr); + + db->stopped = 0; + + spin_unlock_irqrestore(&s->lock, flags); +} + +static void +start_adc(struct au1550_state *s) +{ + struct dmabuf *db = &s->dma_adc; + int i; + + if (!db->stopped) + return; + + /* Put two buffers on the ring to get things started. + */ + for (i=0; i<2; i++) { + au1xxx_dbdma_put_dest(db->dmanr, db->nextIn, db->dma_fragsize); + + db->nextIn += db->dma_fragsize; + if (db->nextIn >= db->rawbuf + db->dmasize) + db->nextIn -= db->dmasize; + } + + set_recv_slots(db->num_channels); + au1xxx_dbdma_start(db->dmanr); + au_writel(PSC_AC97PCR_RC, PSC_AC97PCR); + au_sync(); + au_writel(PSC_AC97PCR_RS, PSC_AC97PCR); + au_sync(); + + db->stopped = 0; +} + +static int +prog_dmabuf(struct au1550_state *s, struct dmabuf *db) +{ + unsigned user_bytes_per_sec; + unsigned bufs; + unsigned rate = db->sample_rate; + + if (!db->rawbuf) { + db->ready = db->mapped = 0; + db->buforder = 5; /* 32 * PAGE_SIZE */ + db->rawbuf = kmalloc((PAGE_SIZE << db->buforder), GFP_KERNEL); + if (!db->rawbuf) + return -ENOMEM; + } + + db->cnt_factor = 1; + if (db->sample_size == 8) + db->cnt_factor *= 2; + if (db->num_channels == 1) + db->cnt_factor *= 2; + db->cnt_factor *= db->src_factor; + + db->count = 0; + db->dma_qcount = 0; + db->nextIn = db->nextOut = db->rawbuf; + + db->user_bytes_per_sample = (db->sample_size>>3) * db->num_channels; + db->dma_bytes_per_sample = 2 * ((db->num_channels == 1) ? + 2 : db->num_channels); + + user_bytes_per_sec = rate * db->user_bytes_per_sample; + bufs = PAGE_SIZE << db->buforder; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < user_bytes_per_sec) + db->fragshift = ld2(user_bytes_per_sec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(user_bytes_per_sec / 100 / + (db->subdivision ? db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + + db->fragsize = 1 << db->fragshift; + db->dma_fragsize = db->fragsize * db->cnt_factor; + db->numfrag = bufs / db->dma_fragsize; + + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->fragsize = 1 << db->fragshift; + db->dma_fragsize = db->fragsize * db->cnt_factor; + db->numfrag = bufs / db->dma_fragsize; + } + + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; + + db->dmasize = db->dma_fragsize * db->numfrag; + memset(db->rawbuf, 0, bufs); + + pr_debug("prog_dmabuf: rate=%d, samplesize=%d, channels=%d\n", + rate, db->sample_size, db->num_channels); + pr_debug("prog_dmabuf: fragsize=%d, cnt_factor=%d, dma_fragsize=%d\n", + db->fragsize, db->cnt_factor, db->dma_fragsize); + pr_debug("prog_dmabuf: numfrag=%d, dmasize=%d\n", db->numfrag, db->dmasize); + + db->ready = 1; + return 0; +} + +static int +prog_dmabuf_adc(struct au1550_state *s) +{ + stop_adc(s); + return prog_dmabuf(s, &s->dma_adc); + +} + +static int +prog_dmabuf_dac(struct au1550_state *s) +{ + stop_dac(s); + return prog_dmabuf(s, &s->dma_dac); +} + + +/* hold spinlock for the following */ +static void +dac_dma_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct au1550_state *s = (struct au1550_state *) dev_id; + struct dmabuf *db = &s->dma_dac; + u32 ac97c_stat; + + ac97c_stat = au_readl(PSC_AC97STAT); + if (ac97c_stat & (AC97C_XU | AC97C_XO | AC97C_TE)) + pr_debug("AC97C status = 0x%08x\n", ac97c_stat); + db->dma_qcount--; + + if (db->count >= db->fragsize) { + if (au1xxx_dbdma_put_source(db->dmanr, db->nextOut, + db->fragsize) == 0) { + err("qcount < 2 and no ring room!"); + } + db->nextOut += db->fragsize; + if (db->nextOut >= db->rawbuf + db->dmasize) + db->nextOut -= db->dmasize; + db->count -= db->fragsize; + db->total_bytes += db->dma_fragsize; + db->dma_qcount++; + } + + /* wake up anybody listening */ + if (waitqueue_active(&db->wait)) + wake_up(&db->wait); +} + + +static void +adc_dma_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct au1550_state *s = (struct au1550_state *)dev_id; + struct dmabuf *dp = &s->dma_adc; + u32 obytes; + char *obuf; + + /* Pull the buffer from the dma queue. + */ + au1xxx_dbdma_get_dest(dp->dmanr, (void *)(&obuf), &obytes); + + if ((dp->count + obytes) > dp->dmasize) { + /* Overrun. Stop ADC and log the error + */ + stop_adc(s); + dp->error++; + err("adc overrun"); + return; + } + + /* Put a new empty buffer on the destination DMA. + */ + au1xxx_dbdma_put_dest(dp->dmanr, dp->nextIn, dp->dma_fragsize); + + dp->nextIn += dp->dma_fragsize; + if (dp->nextIn >= dp->rawbuf + dp->dmasize) + dp->nextIn -= dp->dmasize; + + dp->count += obytes; + dp->total_bytes += obytes; + + /* wake up anybody listening + */ + if (waitqueue_active(&dp->wait)) + wake_up(&dp->wait); + +} + +static loff_t +au1550_llseek(struct file *file, loff_t offset, int origin) +{ + return -ESPIPE; +} + + +static int +au1550_open_mixdev(struct inode *inode, struct file *file) +{ + file->private_data = &au1550_state; + return 0; +} + +static int +au1550_release_mixdev(struct inode *inode, struct file *file) +{ + return 0; +} + +static int +mixdev_ioctl(struct ac97_codec *codec, unsigned int cmd, + unsigned long arg) +{ + return codec->mixer_ioctl(codec, cmd, arg); +} + +static int +au1550_ioctl_mixdev(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct au1550_state *s = (struct au1550_state *)file->private_data; + struct ac97_codec *codec = s->codec; + + return mixdev_ioctl(codec, cmd, arg); +} + +static /*const */ struct file_operations au1550_mixer_fops = { + owner:THIS_MODULE, + llseek:au1550_llseek, + ioctl:au1550_ioctl_mixdev, + open:au1550_open_mixdev, + release:au1550_release_mixdev, +}; + +static int +drain_dac(struct au1550_state *s, int nonblock) +{ + unsigned long flags; + int count, tmo; + + if (s->dma_dac.mapped || !s->dma_dac.ready || s->dma_dac.stopped) + return 0; + + for (;;) { + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= s->dma_dac.fragsize) + break; + if (signal_pending(current)) + break; + if (nonblock) + return -EBUSY; + tmo = 1000 * count / (s->no_vra ? + 48000 : s->dma_dac.sample_rate); + tmo /= s->dma_dac.dma_bytes_per_sample; + au1550_delay(tmo); + } + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +static inline u8 S16_TO_U8(s16 ch) +{ + return (u8) (ch >> 8) + 0x80; +} +static inline s16 U8_TO_S16(u8 ch) +{ + return (s16) (ch - 0x80) << 8; +} + +/* + * Translates user samples to dma buffer suitable for AC'97 DAC data: + * If mono, copy left channel to right channel in dma buffer. + * If 8 bit samples, cvt to 16-bit before writing to dma buffer. + * If interpolating (no VRA), duplicate every audio frame src_factor times. + */ +static int +translate_from_user(struct dmabuf *db, char* dmabuf, char* userbuf, + int dmacount) +{ + int sample, i; + int interp_bytes_per_sample; + int num_samples; + int mono = (db->num_channels == 1); + char usersample[12]; + s16 ch, dmasample[6]; + + if (db->sample_size == 16 && !mono && db->src_factor == 1) { + /* no translation necessary, just copy + */ + if (copy_from_user(dmabuf, userbuf, dmacount)) + return -EFAULT; + return dmacount; + } + + interp_bytes_per_sample = db->dma_bytes_per_sample * db->src_factor; + num_samples = dmacount / interp_bytes_per_sample; + + for (sample = 0; sample < num_samples; sample++) { + if (copy_from_user(usersample, userbuf, + db->user_bytes_per_sample)) { + return -EFAULT; + } + + for (i = 0; i < db->num_channels; i++) { + if (db->sample_size == 8) + ch = U8_TO_S16(usersample[i]); + else + ch = *((s16 *) (&usersample[i * 2])); + dmasample[i] = ch; + if (mono) + dmasample[i + 1] = ch; /* right channel */ + } + + /* duplicate every audio frame src_factor times + */ + for (i = 0; i < db->src_factor; i++) + memcpy(dmabuf, dmasample, db->dma_bytes_per_sample); + + userbuf += db->user_bytes_per_sample; + dmabuf += interp_bytes_per_sample; + } + + return num_samples * interp_bytes_per_sample; +} + +/* + * Translates AC'97 ADC samples to user buffer: + * If mono, send only left channel to user buffer. + * If 8 bit samples, cvt from 16 to 8 bit before writing to user buffer. + * If decimating (no VRA), skip over src_factor audio frames. + */ +static int +translate_to_user(struct dmabuf *db, char* userbuf, char* dmabuf, + int dmacount) +{ + int sample, i; + int interp_bytes_per_sample; + int num_samples; + int mono = (db->num_channels == 1); + char usersample[12]; + + if (db->sample_size == 16 && !mono && db->src_factor == 1) { + /* no translation necessary, just copy + */ + if (copy_to_user(userbuf, dmabuf, dmacount)) + return -EFAULT; + return dmacount; + } + + interp_bytes_per_sample = db->dma_bytes_per_sample * db->src_factor; + num_samples = dmacount / interp_bytes_per_sample; + + for (sample = 0; sample < num_samples; sample++) { + for (i = 0; i < db->num_channels; i++) { + if (db->sample_size == 8) + usersample[i] = + S16_TO_U8(*((s16 *) (&dmabuf[i * 2]))); + else + *((s16 *) (&usersample[i * 2])) = + *((s16 *) (&dmabuf[i * 2])); + } + + if (copy_to_user(userbuf, usersample, + db->user_bytes_per_sample)) { + return -EFAULT; + } + + userbuf += db->user_bytes_per_sample; + dmabuf += interp_bytes_per_sample; + } + + return num_samples * interp_bytes_per_sample; +} + +/* + * Copy audio data to/from user buffer from/to dma buffer, taking care + * that we wrap when reading/writing the dma buffer. Returns actual byte + * count written to or read from the dma buffer. + */ +static int +copy_dmabuf_user(struct dmabuf *db, char* userbuf, int count, int to_user) +{ + char *bufptr = to_user ? db->nextOut : db->nextIn; + char *bufend = db->rawbuf + db->dmasize; + int cnt, ret; + + if (bufptr + count > bufend) { + int partial = (int) (bufend - bufptr); + if (to_user) { + if ((cnt = translate_to_user(db, userbuf, + bufptr, partial)) < 0) + return cnt; + ret = cnt; + if ((cnt = translate_to_user(db, userbuf + partial, + db->rawbuf, + count - partial)) < 0) + return cnt; + ret += cnt; + } else { + if ((cnt = translate_from_user(db, bufptr, userbuf, + partial)) < 0) + return cnt; + ret = cnt; + if ((cnt = translate_from_user(db, db->rawbuf, + userbuf + partial, + count - partial)) < 0) + return cnt; + ret += cnt; + } + } else { + if (to_user) + ret = translate_to_user(db, userbuf, bufptr, count); + else + ret = translate_from_user(db, bufptr, userbuf, count); + } + + return ret; +} + + +static ssize_t +au1550_read(struct file *file, char *buffer, size_t count, loff_t *ppos) +{ + struct au1550_state *s = (struct au1550_state *)file->private_data; + struct dmabuf *db = &s->dma_adc; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + int cnt, usercnt, avail; + + if (db->mapped) + return -ENXIO; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; + + count *= db->cnt_factor; + + down(&s->sem); + add_wait_queue(&db->wait, &wait); + + while (count > 0) { + /* wait for samples in ADC dma buffer + */ + do { + if (db->stopped) + start_adc(s); + spin_lock_irqsave(&s->lock, flags); + avail = db->count; + if (avail <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (avail <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + up(&s->sem); + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out2; + } + down(&s->sem); + } + } while (avail <= 0); + + /* copy from nextOut to user + */ + if ((cnt = copy_dmabuf_user(db, buffer, + count > avail ? + avail : count, 1)) < 0) { + if (!ret) + ret = -EFAULT; + goto out; + } + + spin_lock_irqsave(&s->lock, flags); + db->count -= cnt; + db->nextOut += cnt; + if (db->nextOut >= db->rawbuf + db->dmasize) + db->nextOut -= db->dmasize; + spin_unlock_irqrestore(&s->lock, flags); + + count -= cnt; + usercnt = cnt / db->cnt_factor; + buffer += usercnt; + ret += usercnt; + } /* while (count > 0) */ + +out: + up(&s->sem); +out2: + remove_wait_queue(&db->wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +static ssize_t +au1550_write(struct file *file, const char *buffer, size_t count, loff_t * ppos) +{ + struct au1550_state *s = (struct au1550_state *)file->private_data; + struct dmabuf *db = &s->dma_dac; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret = 0; + unsigned long flags; + int cnt, usercnt, avail; + + pr_debug("write: count=%d\n", count); + + if (db->mapped) + return -ENXIO; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + + count *= db->cnt_factor; + + down(&s->sem); + add_wait_queue(&db->wait, &wait); + + while (count > 0) { + /* wait for space in playback buffer + */ + do { + spin_lock_irqsave(&s->lock, flags); + avail = (int) db->dmasize - db->count; + if (avail <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (avail <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + up(&s->sem); + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out2; + } + down(&s->sem); + } + } while (avail <= 0); + + /* copy from user to nextIn + */ + if ((cnt = copy_dmabuf_user(db, (char *) buffer, + count > avail ? + avail : count, 0)) < 0) { + if (!ret) + ret = -EFAULT; + goto out; + } + + spin_lock_irqsave(&s->lock, flags); + db->count += cnt; + db->nextIn += cnt; + if (db->nextIn >= db->rawbuf + db->dmasize) + db->nextIn -= db->dmasize; + + /* If the data is available, we want to keep two buffers + * on the dma queue. If the queue count reaches zero, + * we know the dma has stopped. + */ + while ((db->dma_qcount < 2) && (db->count >= db->fragsize)) { + if (au1xxx_dbdma_put_source(db->dmanr, db->nextOut, + db->fragsize) == 0) { + err("qcount < 2 and no ring room!"); + } + db->nextOut += db->fragsize; + if (db->nextOut >= db->rawbuf + db->dmasize) + db->nextOut -= db->dmasize; + db->total_bytes += db->dma_fragsize; + if (db->dma_qcount == 0) + start_dac(s); + db->dma_qcount++; + } + spin_unlock_irqrestore(&s->lock, flags); + + count -= cnt; + usercnt = cnt / db->cnt_factor; + buffer += usercnt; + ret += usercnt; + } /* while (count > 0) */ + +out: + up(&s->sem); +out2: + remove_wait_queue(&db->wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + + +/* No kernel lock - we have our own spinlock */ +static unsigned int +au1550_poll(struct file *file, struct poll_table_struct *wait) +{ + struct au1550_state *s = (struct au1550_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + if (file->f_mode & FMODE_WRITE) { + if (!s->dma_dac.ready) + return 0; + poll_wait(file, &s->dma_dac.wait, wait); + } + if (file->f_mode & FMODE_READ) { + if (!s->dma_adc.ready) + return 0; + poll_wait(file, &s->dma_adc.wait, wait); + } + + spin_lock_irqsave(&s->lock, flags); + + if (file->f_mode & FMODE_READ) { + if (s->dma_adc.count >= (signed)s->dma_adc.dma_fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->dma_dac.mapped) { + if (s->dma_dac.count >= + (signed)s->dma_dac.dma_fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed) s->dma_dac.dmasize >= + s->dma_dac.count + (signed)s->dma_dac.dma_fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int +au1550_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct au1550_state *s = (struct au1550_state *)file->private_data; + struct dmabuf *db; + unsigned long size; + int ret = 0; + + lock_kernel(); + down(&s->sem); + if (vma->vm_flags & VM_WRITE) + db = &s->dma_dac; + else if (vma->vm_flags & VM_READ) + db = &s->dma_adc; + else { + ret = -EINVAL; + goto out; + } + if (vma->vm_pgoff != 0) { + ret = -EINVAL; + goto out; + } + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << db->buforder)) { + ret = -EINVAL; + goto out; + } + if (remap_pfn_range(vma, vma->vm_start, page_to_pfn(virt_to_page(db->rawbuf)), + size, vma->vm_page_prot)) { + ret = -EAGAIN; + goto out; + } + vma->vm_flags &= ~VM_IO; + db->mapped = 1; +out: + up(&s->sem); + unlock_kernel(); + return ret; +} + +#ifdef DEBUG +static struct ioctl_str_t { + unsigned int cmd; + const char *str; +} ioctl_str[] = { + {SNDCTL_DSP_RESET, "SNDCTL_DSP_RESET"}, + {SNDCTL_DSP_SYNC, "SNDCTL_DSP_SYNC"}, + {SNDCTL_DSP_SPEED, "SNDCTL_DSP_SPEED"}, + {SNDCTL_DSP_STEREO, "SNDCTL_DSP_STEREO"}, + {SNDCTL_DSP_GETBLKSIZE, "SNDCTL_DSP_GETBLKSIZE"}, + {SNDCTL_DSP_SAMPLESIZE, "SNDCTL_DSP_SAMPLESIZE"}, + {SNDCTL_DSP_CHANNELS, "SNDCTL_DSP_CHANNELS"}, + {SOUND_PCM_WRITE_CHANNELS, "SOUND_PCM_WRITE_CHANNELS"}, + {SOUND_PCM_WRITE_FILTER, "SOUND_PCM_WRITE_FILTER"}, + {SNDCTL_DSP_POST, "SNDCTL_DSP_POST"}, + {SNDCTL_DSP_SUBDIVIDE, "SNDCTL_DSP_SUBDIVIDE"}, + {SNDCTL_DSP_SETFRAGMENT, "SNDCTL_DSP_SETFRAGMENT"}, + {SNDCTL_DSP_GETFMTS, "SNDCTL_DSP_GETFMTS"}, + {SNDCTL_DSP_SETFMT, "SNDCTL_DSP_SETFMT"}, + {SNDCTL_DSP_GETOSPACE, "SNDCTL_DSP_GETOSPACE"}, + {SNDCTL_DSP_GETISPACE, "SNDCTL_DSP_GETISPACE"}, + {SNDCTL_DSP_NONBLOCK, "SNDCTL_DSP_NONBLOCK"}, + {SNDCTL_DSP_GETCAPS, "SNDCTL_DSP_GETCAPS"}, + {SNDCTL_DSP_GETTRIGGER, "SNDCTL_DSP_GETTRIGGER"}, + {SNDCTL_DSP_SETTRIGGER, "SNDCTL_DSP_SETTRIGGER"}, + {SNDCTL_DSP_GETIPTR, "SNDCTL_DSP_GETIPTR"}, + {SNDCTL_DSP_GETOPTR, "SNDCTL_DSP_GETOPTR"}, + {SNDCTL_DSP_MAPINBUF, "SNDCTL_DSP_MAPINBUF"}, + {SNDCTL_DSP_MAPOUTBUF, "SNDCTL_DSP_MAPOUTBUF"}, + {SNDCTL_DSP_SETSYNCRO, "SNDCTL_DSP_SETSYNCRO"}, + {SNDCTL_DSP_SETDUPLEX, "SNDCTL_DSP_SETDUPLEX"}, + {SNDCTL_DSP_GETODELAY, "SNDCTL_DSP_GETODELAY"}, + {SNDCTL_DSP_GETCHANNELMASK, "SNDCTL_DSP_GETCHANNELMASK"}, + {SNDCTL_DSP_BIND_CHANNEL, "SNDCTL_DSP_BIND_CHANNEL"}, + {OSS_GETVERSION, "OSS_GETVERSION"}, + {SOUND_PCM_READ_RATE, "SOUND_PCM_READ_RATE"}, + {SOUND_PCM_READ_CHANNELS, "SOUND_PCM_READ_CHANNELS"}, + {SOUND_PCM_READ_BITS, "SOUND_PCM_READ_BITS"}, + {SOUND_PCM_READ_FILTER, "SOUND_PCM_READ_FILTER"} +}; +#endif + +static int +dma_count_done(struct dmabuf *db) +{ + if (db->stopped) + return 0; + + return db->dma_fragsize - au1xxx_get_dma_residue(db->dmanr); +} + + +static int +au1550_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct au1550_state *s = (struct au1550_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int count; + int val, mapped, ret, diff; + + mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac.mapped) || + ((file->f_mode & FMODE_READ) && s->dma_adc.mapped); + +#ifdef DEBUG + for (count=0; countf_mode & FMODE_WRITE) + return drain_dac(s, 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: + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + synchronize_irq(); + s->dma_dac.count = s->dma_dac.total_bytes = 0; + s->dma_dac.nextIn = s->dma_dac.nextOut = + s->dma_dac.rawbuf; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(); + s->dma_adc.count = s->dma_adc.total_bytes = 0; + s->dma_adc.nextIn = s->dma_adc.nextOut = + s->dma_adc.rawbuf; + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, (int *) arg)) + return -EFAULT; + if (val >= 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + set_adc_rate(s, val); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + set_dac_rate(s, val); + } + if (s->open_mode & FMODE_READ) + if ((ret = prog_dmabuf_adc(s))) + return ret; + if (s->open_mode & FMODE_WRITE) + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return put_user((file->f_mode & FMODE_READ) ? + s->dma_adc.sample_rate : + s->dma_dac.sample_rate, + (int *)arg); + + case SNDCTL_DSP_STEREO: + if (get_user(val, (int *) arg)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.num_channels = val ? 2 : 1; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.num_channels = val ? 2 : 1; + if (s->codec_ext_caps & AC97_EXT_DACS) { + /* disable surround and center/lfe in AC'97 + */ + u16 ext_stat = rdcodec(s->codec, + AC97_EXTENDED_STATUS); + wrcodec(s->codec, AC97_EXTENDED_STATUS, + ext_stat | (AC97_EXTSTAT_PRI | + AC97_EXTSTAT_PRJ | + AC97_EXTSTAT_PRK)); + } + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, (int *) arg)) + return -EFAULT; + if (val != 0) { + if (file->f_mode & FMODE_READ) { + if (val < 0 || val > 2) + return -EINVAL; + stop_adc(s); + s->dma_adc.num_channels = val; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + switch (val) { + case 1: + case 2: + break; + case 3: + case 5: + return -EINVAL; + case 4: + if (!(s->codec_ext_caps & + AC97_EXTID_SDAC)) + return -EINVAL; + break; + case 6: + if ((s->codec_ext_caps & + AC97_EXT_DACS) != AC97_EXT_DACS) + return -EINVAL; + break; + default: + return -EINVAL; + } + + stop_dac(s); + if (val <= 2 && + (s->codec_ext_caps & AC97_EXT_DACS)) { + /* disable surround and center/lfe + * channels in AC'97 + */ + u16 ext_stat = + rdcodec(s->codec, + AC97_EXTENDED_STATUS); + wrcodec(s->codec, + AC97_EXTENDED_STATUS, + ext_stat | (AC97_EXTSTAT_PRI | + AC97_EXTSTAT_PRJ | + AC97_EXTSTAT_PRK)); + } else if (val >= 4) { + /* enable surround, center/lfe + * channels in AC'97 + */ + u16 ext_stat = + rdcodec(s->codec, + AC97_EXTENDED_STATUS); + ext_stat &= ~AC97_EXTSTAT_PRJ; + if (val == 6) + ext_stat &= + ~(AC97_EXTSTAT_PRI | + AC97_EXTSTAT_PRK); + wrcodec(s->codec, + AC97_EXTENDED_STATUS, + ext_stat); + } + + s->dma_dac.num_channels = val; + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + } + return put_user(val, (int *) arg); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_LE | AFMT_U8, (int *) arg); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt */ + if (get_user(val, (int *) arg)) + return -EFAULT; + if (val != AFMT_QUERY) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + if (val == AFMT_S16_LE) + s->dma_adc.sample_size = 16; + else { + val = AFMT_U8; + s->dma_adc.sample_size = 8; + } + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + if (val == AFMT_S16_LE) + s->dma_dac.sample_size = 16; + else { + val = AFMT_U8; + s->dma_dac.sample_size = 8; + } + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + } else { + if (file->f_mode & FMODE_READ) + val = (s->dma_adc.sample_size == 16) ? + AFMT_S16_LE : AFMT_U8; + else + val = (s->dma_dac.sample_size == 16) ? + AFMT_S16_LE : AFMT_U8; + } + return put_user(val, (int *) arg); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + spin_lock_irqsave(&s->lock, flags); + if (file->f_mode & FMODE_READ && !s->dma_adc.stopped) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && !s->dma_dac.stopped) + val |= PCM_ENABLE_OUTPUT; + spin_unlock_irqrestore(&s->lock, flags); + 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) + start_adc(s); + else + stop_adc(s); + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) + start_dac(s); + else + stop_dac(s); + } + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + abinfo.fragsize = s->dma_dac.fragsize; + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + count -= dma_count_done(&s->dma_dac); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + abinfo.bytes = (s->dma_dac.dmasize - count) / + s->dma_dac.cnt_factor; + abinfo.fragstotal = s->dma_dac.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift; + pr_debug("ioctl SNDCTL_DSP_GETOSPACE: bytes=%d, fragments=%d\n", abinfo.bytes, abinfo.fragments); + return copy_to_user((void *) arg, &abinfo, + sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + abinfo.fragsize = s->dma_adc.fragsize; + spin_lock_irqsave(&s->lock, flags); + count = s->dma_adc.count; + count += dma_count_done(&s->dma_adc); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + abinfo.bytes = count / s->dma_adc.cnt_factor; + abinfo.fragstotal = s->dma_adc.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_adc.fragshift; + return copy_to_user((void *) arg, &abinfo, + sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + count -= dma_count_done(&s->dma_dac); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + count /= s->dma_dac.cnt_factor; + return put_user(count, (int *) arg); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cinfo.bytes = s->dma_adc.total_bytes; + count = s->dma_adc.count; + if (!s->dma_adc.stopped) { + diff = dma_count_done(&s->dma_adc); + count += diff; + cinfo.bytes += diff; + cinfo.ptr = virt_to_phys(s->dma_adc.nextIn) + diff - + virt_to_phys(s->dma_adc.rawbuf); + } else + cinfo.ptr = virt_to_phys(s->dma_adc.nextIn) - + virt_to_phys(s->dma_adc.rawbuf); + if (s->dma_adc.mapped) + s->dma_adc.count &= (s->dma_adc.dma_fragsize-1); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_adc.fragshift; + return copy_to_user((void *) arg, &cinfo, sizeof(cinfo)); + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cinfo.bytes = s->dma_dac.total_bytes; + count = s->dma_dac.count; + if (!s->dma_dac.stopped) { + diff = dma_count_done(&s->dma_dac); + count -= diff; + cinfo.bytes += diff; + cinfo.ptr = virt_to_phys(s->dma_dac.nextOut) + diff - + virt_to_phys(s->dma_dac.rawbuf); + } else + cinfo.ptr = virt_to_phys(s->dma_dac.nextOut) - + virt_to_phys(s->dma_dac.rawbuf); + if (s->dma_dac.mapped) + s->dma_dac.count &= (s->dma_dac.dma_fragsize-1); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_dac.fragshift; + return copy_to_user((void *) arg, &cinfo, sizeof(cinfo)); + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) + return put_user(s->dma_dac.fragsize, (int *) arg); + else + return put_user(s->dma_adc.fragsize, (int *) arg); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, (int *) arg)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ossfragshift = val & 0xffff; + s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_adc.ossfragshift < 4) + s->dma_adc.ossfragshift = 4; + if (s->dma_adc.ossfragshift > 15) + s->dma_adc.ossfragshift = 15; + if (s->dma_adc.ossmaxfrags < 4) + s->dma_adc.ossmaxfrags = 4; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ossfragshift = val & 0xffff; + s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac.ossfragshift < 4) + s->dma_dac.ossfragshift = 4; + if (s->dma_dac.ossfragshift > 15) + s->dma_dac.ossfragshift = 15; + if (s->dma_dac.ossmaxfrags < 4) + s->dma_dac.ossmaxfrags = 4; + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return 0; + + 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) { + stop_adc(s); + s->dma_adc.subdivision = val; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.subdivision = val; + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return 0; + + case SOUND_PCM_READ_RATE: + return put_user((file->f_mode & FMODE_READ) ? + s->dma_adc.sample_rate : + s->dma_dac.sample_rate, + (int *)arg); + + case SOUND_PCM_READ_CHANNELS: + if (file->f_mode & FMODE_READ) + return put_user(s->dma_adc.num_channels, (int *)arg); + else + return put_user(s->dma_dac.num_channels, (int *)arg); + + case SOUND_PCM_READ_BITS: + if (file->f_mode & FMODE_READ) + return put_user(s->dma_adc.sample_size, (int *)arg); + else + return put_user(s->dma_dac.sample_size, (int *)arg); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + } + + return mixdev_ioctl(s->codec, cmd, arg); +} + + +static int +au1550_open(struct inode *inode, struct file *file) +{ + int minor = MINOR(inode->i_rdev); + DECLARE_WAITQUEUE(wait, current); + struct au1550_state *s = &au1550_state; + int ret; + +#ifdef DEBUG + if (file->f_flags & O_NONBLOCK) + pr_debug("open: non-blocking\n"); + else + pr_debug("open: blocking\n"); +#endif + + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & file->f_mode) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + + stop_dac(s); + stop_adc(s); + + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = + s->dma_adc.subdivision = s->dma_adc.total_bytes = 0; + s->dma_adc.num_channels = 1; + s->dma_adc.sample_size = 8; + set_adc_rate(s, 8000); + if ((minor & 0xf) == SND_DEV_DSP16) + s->dma_adc.sample_size = 16; + } + + if (file->f_mode & FMODE_WRITE) { + s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = + s->dma_dac.subdivision = s->dma_dac.total_bytes = 0; + s->dma_dac.num_channels = 1; + s->dma_dac.sample_size = 8; + set_dac_rate(s, 8000); + if ((minor & 0xf) == SND_DEV_DSP16) + s->dma_dac.sample_size = 16; + } + + if (file->f_mode & FMODE_READ) { + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + + s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + up(&s->open_sem); + init_MUTEX(&s->sem); + return 0; +} + +static int +au1550_release(struct inode *inode, struct file *file) +{ + struct au1550_state *s = (struct au1550_state *)file->private_data; + + lock_kernel(); + + if (file->f_mode & FMODE_WRITE) { + unlock_kernel(); + drain_dac(s, file->f_flags & O_NONBLOCK); + lock_kernel(); + } + + down(&s->open_sem); + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + kfree(s->dma_dac.rawbuf); + s->dma_dac.rawbuf = NULL; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + kfree(s->dma_adc.rawbuf); + s->dma_adc.rawbuf = NULL; + } + s->open_mode &= ((~file->f_mode) & (FMODE_READ|FMODE_WRITE)); + up(&s->open_sem); + wake_up(&s->open_wait); + unlock_kernel(); + return 0; +} + +static /*const */ struct file_operations au1550_audio_fops = { + owner: THIS_MODULE, + llseek: au1550_llseek, + read: au1550_read, + write: au1550_write, + poll: au1550_poll, + ioctl: au1550_ioctl, + mmap: au1550_mmap, + open: au1550_open, + release: au1550_release, +}; + +MODULE_AUTHOR("Advanced Micro Devices (AMD), dan@embeddededge.com"); +MODULE_DESCRIPTION("Au1550 AC97 Audio Driver"); + +static int __devinit +au1550_probe(void) +{ + struct au1550_state *s = &au1550_state; + int val; + + memset(s, 0, sizeof(struct au1550_state)); + + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac.wait); + init_waitqueue_head(&s->open_wait); + init_MUTEX(&s->open_sem); + spin_lock_init(&s->lock); + + s->codec = ac97_alloc_codec(); + if(s->codec == NULL) { + err("Out of memory"); + return -1; + } + s->codec->private_data = s; + s->codec->id = 0; + s->codec->codec_read = rdcodec; + s->codec->codec_write = wrcodec; + s->codec->codec_wait = waitcodec; + + if (!request_mem_region(CPHYSADDR(AC97_PSC_SEL), + 0x30, "Au1550 AC97")) { + err("AC'97 ports in use"); + } + + /* Allocate the DMA Channels + */ + if ((s->dma_dac.dmanr = au1xxx_dbdma_chan_alloc(DBDMA_MEM_CHAN, + DBDMA_AC97_TX_CHAN, dac_dma_interrupt, (void *)s)) == 0) { + err("Can't get DAC DMA"); + goto err_dma1; + } + au1xxx_dbdma_set_devwidth(s->dma_dac.dmanr, 16); + if (au1xxx_dbdma_ring_alloc(s->dma_dac.dmanr, + NUM_DBDMA_DESCRIPTORS) == 0) { + err("Can't get DAC DMA descriptors"); + goto err_dma1; + } + + if ((s->dma_adc.dmanr = au1xxx_dbdma_chan_alloc(DBDMA_AC97_RX_CHAN, + DBDMA_MEM_CHAN, adc_dma_interrupt, (void *)s)) == 0) { + err("Can't get ADC DMA"); + goto err_dma2; + } + au1xxx_dbdma_set_devwidth(s->dma_adc.dmanr, 16); + if (au1xxx_dbdma_ring_alloc(s->dma_adc.dmanr, + NUM_DBDMA_DESCRIPTORS) == 0) { + err("Can't get ADC DMA descriptors"); + goto err_dma2; + } + + pr_info("DAC: DMA%d, ADC: DMA%d", DBDMA_AC97_TX_CHAN, DBDMA_AC97_RX_CHAN); + + /* register devices */ + + if ((s->dev_audio = register_sound_dsp(&au1550_audio_fops, -1)) < 0) + goto err_dev1; + if ((s->codec->dev_mixer = + register_sound_mixer(&au1550_mixer_fops, -1)) < 0) + goto err_dev2; + + /* The GPIO for the appropriate PSC was configured by the + * board specific start up. + * + * configure PSC for AC'97 + */ + au_writel(0, AC97_PSC_CTRL); /* Disable PSC */ + au_sync(); + au_writel((PSC_SEL_CLK_SERCLK | PSC_SEL_PS_AC97MODE), AC97_PSC_SEL); + au_sync(); + + /* cold reset the AC'97 + */ + au_writel(PSC_AC97RST_RST, PSC_AC97RST); + au_sync(); + au1550_delay(10); + au_writel(0, PSC_AC97RST); + au_sync(); + + /* need to delay around 500msec(bleech) to give + some CODECs enough time to wakeup */ + au1550_delay(500); + + /* warm reset the AC'97 to start the bitclk + */ + au_writel(PSC_AC97RST_SNC, PSC_AC97RST); + au_sync(); + udelay(100); + au_writel(0, PSC_AC97RST); + au_sync(); + + /* Enable PSC + */ + au_writel(PSC_CTRL_ENABLE, AC97_PSC_CTRL); + au_sync(); + + /* Wait for PSC ready. + */ + do { + val = readl((void *)PSC_AC97STAT); + au_sync(); + } while ((val & PSC_AC97STAT_SR) == 0); + + /* Configure AC97 controller. + * Deep FIFO, 16-bit sample, DMA, make sure DMA matches fifo size. + */ + val = PSC_AC97CFG_SET_LEN(16); + val |= PSC_AC97CFG_RT_FIFO8 | PSC_AC97CFG_TT_FIFO8; + + /* Enable device so we can at least + * talk over the AC-link. + */ + au_writel(val, PSC_AC97CFG); + au_writel(PSC_AC97MSK_ALLMASK, PSC_AC97MSK); + au_sync(); + val |= PSC_AC97CFG_DE_ENABLE; + au_writel(val, PSC_AC97CFG); + au_sync(); + + /* Wait for Device ready. + */ + do { + val = readl((void *)PSC_AC97STAT); + au_sync(); + } while ((val & PSC_AC97STAT_DR) == 0); + + /* codec init */ + if (!ac97_probe_codec(s->codec)) + goto err_dev3; + + s->codec_base_caps = rdcodec(s->codec, AC97_RESET); + s->codec_ext_caps = rdcodec(s->codec, AC97_EXTENDED_ID); + pr_info("AC'97 Base/Extended ID = %04x/%04x", + s->codec_base_caps, s->codec_ext_caps); + + if (!(s->codec_ext_caps & AC97_EXTID_VRA)) { + /* codec does not support VRA + */ + s->no_vra = 1; + } else if (!vra) { + /* Boot option says disable VRA + */ + u16 ac97_extstat = rdcodec(s->codec, AC97_EXTENDED_STATUS); + wrcodec(s->codec, AC97_EXTENDED_STATUS, + ac97_extstat & ~AC97_EXTSTAT_VRA); + s->no_vra = 1; + } + if (s->no_vra) + pr_info("no VRA, interpolating and decimating"); + + /* set mic to be the recording source */ + val = SOUND_MASK_MIC; + mixdev_ioctl(s->codec, SOUND_MIXER_WRITE_RECSRC, + (unsigned long) &val); + + return 0; + + err_dev3: + unregister_sound_mixer(s->codec->dev_mixer); + err_dev2: + unregister_sound_dsp(s->dev_audio); + err_dev1: + au1xxx_dbdma_chan_free(s->dma_adc.dmanr); + err_dma2: + au1xxx_dbdma_chan_free(s->dma_dac.dmanr); + err_dma1: + release_mem_region(CPHYSADDR(AC97_PSC_SEL), 0x30); + + ac97_release_codec(s->codec); + return -1; +} + +static void __devinit +au1550_remove(void) +{ + struct au1550_state *s = &au1550_state; + + if (!s) + return; + synchronize_irq(); + au1xxx_dbdma_chan_free(s->dma_adc.dmanr); + au1xxx_dbdma_chan_free(s->dma_dac.dmanr); + release_mem_region(CPHYSADDR(AC97_PSC_SEL), 0x30); + unregister_sound_dsp(s->dev_audio); + unregister_sound_mixer(s->codec->dev_mixer); + ac97_release_codec(s->codec); +} + +static int __init +init_au1550(void) +{ + return au1550_probe(); +} + +static void __exit +cleanup_au1550(void) +{ + au1550_remove(); +} + +module_init(init_au1550); +module_exit(cleanup_au1550); + +#ifndef MODULE + +static int __init +au1550_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ","))) { + if (!*this_opt) + continue; + if (!strncmp(this_opt, "vra", 3)) { + vra = 1; + } + } + + return 1; +} + +__setup("au1550_audio=", au1550_setup); + +#endif /* MODULE */ diff --git a/sound/oss/audio.c b/sound/oss/audio.c new file mode 100644 index 000000000000..22dd63c36816 --- /dev/null +++ b/sound/oss/audio.c @@ -0,0 +1,983 @@ +/* + * sound/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 + * Chris Rankin : Update the module-usage counter for the coprocessor, + * and decrement the counters again if we cannot open + * the audio device. + */ + +#include +#include +#include + +#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: + file->f_flags |= O_NONBLOCK; + 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; + + /* Substract 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; + /* Substract 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/audio_syms.c b/sound/oss/audio_syms.c new file mode 100644 index 000000000000..5da217fcbedd --- /dev/null +++ b/sound/oss/audio_syms.c @@ -0,0 +1,16 @@ +/* + * Exported symbols for audio driver. + */ + +#include + +char audio_syms_symbol; + +#include "sound_config.h" +#include "sound_calls.h" + +EXPORT_SYMBOL(DMAbuf_start_dma); +EXPORT_SYMBOL(DMAbuf_open_dma); +EXPORT_SYMBOL(DMAbuf_close_dma); +EXPORT_SYMBOL(DMAbuf_inputintr); +EXPORT_SYMBOL(DMAbuf_outputintr); diff --git a/sound/oss/awe_hw.h b/sound/oss/awe_hw.h new file mode 100644 index 000000000000..7e403ad68152 --- /dev/null +++ b/sound/oss/awe_hw.h @@ -0,0 +1,99 @@ +/* + * sound/awe_hw.h + * + * Access routines and definitions for the low level driver for the + * Creative AWE32/SB32/AWE64 wave table synth. + * version 0.4.4; Jan. 4, 2000 + * + * Copyright (C) 1996-2000 Takashi Iwai + * + * 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 AWE_HW_H_DEF +#define AWE_HW_H_DEF + +/* + * Emu-8000 control registers + * name(channel) reg, port + */ + +#define awe_cmd_idx(reg,ch) (((reg)<< 5) | (ch)) + +#define Data0 0 /* 0x620: doubleword r/w */ +#define Data1 1 /* 0xA20: doubleword r/w */ +#define Data2 2 /* 0xA22: word r/w */ +#define Data3 3 /* 0xE20: word r/w */ +#define Pointer 4 /* 0xE22 register pointer r/w */ + +#define AWE_CPF(ch) awe_cmd_idx(0,ch), Data0 /* DW: current pitch and fractional address */ +#define AWE_PTRX(ch) awe_cmd_idx(1,ch), Data0 /* DW: pitch target and reverb send */ +#define AWE_CVCF(ch) awe_cmd_idx(2,ch), Data0 /* DW: current volume and filter cutoff */ +#define AWE_VTFT(ch) awe_cmd_idx(3,ch), Data0 /* DW: volume and filter cutoff targets */ +#define AWE_0080(ch) awe_cmd_idx(4,ch), Data0 /* DW: ?? */ +#define AWE_00A0(ch) awe_cmd_idx(5,ch), Data0 /* DW: ?? */ +#define AWE_PSST(ch) awe_cmd_idx(6,ch), Data0 /* DW: pan send and loop start address */ +#define AWE_CSL(ch) awe_cmd_idx(7,ch), Data0 /* DW: chorus send and loop end address */ +#define AWE_CCCA(ch) awe_cmd_idx(0,ch), Data1 /* DW: Q, control bits, and current address */ +#define AWE_HWCF4 awe_cmd_idx(1,9), Data1 /* DW: config dw 4 */ +#define AWE_HWCF5 awe_cmd_idx(1,10), Data1 /* DW: config dw 5 */ +#define AWE_HWCF6 awe_cmd_idx(1,13), Data1 /* DW: config dw 6 */ +#define AWE_HWCF7 awe_cmd_idx(1,14), Data1 /* DW: config dw 7? (not documented) */ +#define AWE_SMALR awe_cmd_idx(1,20), Data1 /* DW: sound memory address for left read */ +#define AWE_SMARR awe_cmd_idx(1,21), Data1 /* DW: for right read */ +#define AWE_SMALW awe_cmd_idx(1,22), Data1 /* DW: sound memory address for left write */ +#define AWE_SMARW awe_cmd_idx(1,23), Data1 /* DW: for right write */ +#define AWE_SMLD awe_cmd_idx(1,26), Data1 /* W: sound memory left data */ +#define AWE_SMRD awe_cmd_idx(1,26), Data2 /* W: right data */ +#define AWE_WC awe_cmd_idx(1,27), Data2 /* W: sample counter */ +#define AWE_WC_Cmd awe_cmd_idx(1,27) +#define AWE_WC_Port Data2 +#define AWE_HWCF1 awe_cmd_idx(1,29), Data1 /* W: config w 1 */ +#define AWE_HWCF2 awe_cmd_idx(1,30), Data1 /* W: config w 2 */ +#define AWE_HWCF3 awe_cmd_idx(1,31), Data1 /* W: config w 3 */ +#define AWE_INIT1(ch) awe_cmd_idx(2,ch), Data1 /* W: init array 1 */ +#define AWE_INIT2(ch) awe_cmd_idx(2,ch), Data2 /* W: init array 2 */ +#define AWE_INIT3(ch) awe_cmd_idx(3,ch), Data1 /* W: init array 3 */ +#define AWE_INIT4(ch) awe_cmd_idx(3,ch), Data2 /* W: init array 4 */ +#define AWE_ENVVOL(ch) awe_cmd_idx(4,ch), Data1 /* W: volume envelope delay */ +#define AWE_DCYSUSV(ch) awe_cmd_idx(5,ch), Data1 /* W: volume envelope sustain and decay */ +#define AWE_ENVVAL(ch) awe_cmd_idx(6,ch), Data1 /* W: modulation envelope delay */ +#define AWE_DCYSUS(ch) awe_cmd_idx(7,ch), Data1 /* W: modulation envelope sustain and decay */ +#define AWE_ATKHLDV(ch) awe_cmd_idx(4,ch), Data2 /* W: volume envelope attack and hold */ +#define AWE_LFO1VAL(ch) awe_cmd_idx(5,ch), Data2 /* W: LFO#1 Delay */ +#define AWE_ATKHLD(ch) awe_cmd_idx(6,ch), Data2 /* W: modulation envelope attack and hold */ +#define AWE_LFO2VAL(ch) awe_cmd_idx(7,ch), Data2 /* W: LFO#2 Delay */ +#define AWE_IP(ch) awe_cmd_idx(0,ch), Data3 /* W: initial pitch */ +#define AWE_IFATN(ch) awe_cmd_idx(1,ch), Data3 /* W: initial filter cutoff and attenuation */ +#define AWE_PEFE(ch) awe_cmd_idx(2,ch), Data3 /* W: pitch and filter envelope heights */ +#define AWE_FMMOD(ch) awe_cmd_idx(3,ch), Data3 /* W: vibrato and filter modulation freq */ +#define AWE_TREMFRQ(ch) awe_cmd_idx(4,ch), Data3 /* W: LFO#1 tremolo amount and freq */ +#define AWE_FM2FRQ2(ch) awe_cmd_idx(5,ch), Data3 /* W: LFO#2 vibrato amount and freq */ + +/* used during detection (returns ROM version?; not documented in ADIP) */ +#define AWE_U1 0xE0, Data3 /* (R)(W) used in initialization */ +#define AWE_U2(ch) 0xC0+(ch), Data3 /* (W)(W) used in init envelope */ + + +#define AWE_MAX_VOICES 32 +#define AWE_NORMAL_VOICES 30 /*30&31 are reserved for DRAM refresh*/ + +#define AWE_MAX_CHANNELS 32 /* max midi channels (must >= voices) */ +#define AWE_MAX_LAYERS AWE_MAX_VOICES /* maximum number of multiple layers */ + +#define AWE_DRAM_OFFSET 0x200000 +#define AWE_MAX_DRAM_SIZE (28 * 1024) /* 28 MB is max onboard memory */ + +#endif diff --git a/sound/oss/awe_wave.c b/sound/oss/awe_wave.c new file mode 100644 index 000000000000..d2b9beda8ace --- /dev/null +++ b/sound/oss/awe_wave.c @@ -0,0 +1,6147 @@ +/* + * sound/awe_wave.c + * + * The low level driver for the AWE32/SB32/AWE64 wave table synth. + * version 0.4.4; Jan. 4, 2000 + * + * Copyright (C) 1996-2000 Takashi Iwai + * + * 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. + */ + +/* + * Changelog: + * Aug 18, 2003, Adam Belay + * - detection code rewrite + */ + +#include +#include +#include +#include +#include +#include + +#include "sound_config.h" + +#include "awe_wave.h" +#include "awe_hw.h" + +#ifdef AWE_HAS_GUS_COMPATIBILITY +#include "tuning.h" +#include +#endif + +/* + * debug message + */ + +#ifdef AWE_DEBUG_ON +#define DEBUG(LVL,XXX) {if (ctrls[AWE_MD_DEBUG_MODE] > LVL) { XXX; }} +#define ERRMSG(XXX) {if (ctrls[AWE_MD_DEBUG_MODE]) { XXX; }} +#define FATALERR(XXX) XXX +#else +#define DEBUG(LVL,XXX) /**/ +#define ERRMSG(XXX) XXX +#define FATALERR(XXX) XXX +#endif + +/* + * bank and voice record + */ + +typedef struct _sf_list sf_list; +typedef struct _awe_voice_list awe_voice_list; +typedef struct _awe_sample_list awe_sample_list; + +/* soundfont record */ +struct _sf_list { + unsigned short sf_id; /* id number */ + unsigned short type; /* lock & shared flags */ + int num_info; /* current info table index */ + int num_sample; /* current sample table index */ + int mem_ptr; /* current word byte pointer */ + awe_voice_list *infos, *last_infos; /* instruments */ + awe_sample_list *samples, *last_samples; /* samples */ +#ifdef AWE_ALLOW_SAMPLE_SHARING + sf_list *shared; /* shared list */ + unsigned char name[AWE_PATCH_NAME_LEN]; /* sharing id */ +#endif + sf_list *next, *prev; +}; + +/* instrument list */ +struct _awe_voice_list { + awe_voice_info v; /* instrument information */ + sf_list *holder; /* parent sf_list of this record */ + unsigned char bank, instr; /* preset number information */ + char type, disabled; /* type=normal/mapped, disabled=boolean */ + awe_voice_list *next; /* linked list with same sf_id */ + awe_voice_list *next_instr; /* instrument list */ + awe_voice_list *next_bank; /* hash table list */ +}; + +/* voice list type */ +#define V_ST_NORMAL 0 +#define V_ST_MAPPED 1 + +/* sample list */ +struct _awe_sample_list { + awe_sample_info v; /* sample information */ + sf_list *holder; /* parent sf_list of this record */ + awe_sample_list *next; /* linked list with same sf_id */ +}; + +/* sample and information table */ +static int current_sf_id; /* current number of fonts */ +static int locked_sf_id; /* locked position */ +static sf_list *sfhead, *sftail; /* linked-lists */ + +#define awe_free_mem_ptr() (sftail ? sftail->mem_ptr : 0) +#define awe_free_info() (sftail ? sftail->num_info : 0) +#define awe_free_sample() (sftail ? sftail->num_sample : 0) + +#define AWE_MAX_PRESETS 256 +#define AWE_DEFAULT_PRESET 0 +#define AWE_DEFAULT_BANK 0 +#define AWE_DEFAULT_DRUM 0 +#define AWE_DRUM_BANK 128 + +#define MAX_LAYERS AWE_MAX_VOICES + +/* preset table index */ +static awe_voice_list *preset_table[AWE_MAX_PRESETS]; + +/* + * voice table + */ + +/* effects table */ +typedef struct FX_Rec { /* channel effects */ + unsigned char flags[AWE_FX_END]; + short val[AWE_FX_END]; +} FX_Rec; + + +/* channel parameters */ +typedef struct _awe_chan_info { + int channel; /* channel number */ + int bank; /* current tone bank */ + int instr; /* current program */ + int bender; /* midi pitchbend (-8192 - 8192) */ + int bender_range; /* midi bender range (x100) */ + int panning; /* panning (0-127) */ + int main_vol; /* channel volume (0-127) */ + int expression_vol; /* midi expression (0-127) */ + int chan_press; /* channel pressure */ + int sustained; /* sustain status in MIDI */ + FX_Rec fx; /* effects */ + FX_Rec fx_layer[MAX_LAYERS]; /* layer effects */ +} awe_chan_info; + +/* voice parameters */ +typedef struct _voice_info { + int state; +#define AWE_ST_OFF (1<<0) /* no sound */ +#define AWE_ST_ON (1<<1) /* playing */ +#define AWE_ST_STANDBY (1<<2) /* stand by for playing */ +#define AWE_ST_SUSTAINED (1<<3) /* sustained */ +#define AWE_ST_MARK (1<<4) /* marked for allocation */ +#define AWE_ST_DRAM (1<<5) /* DRAM read/write */ +#define AWE_ST_FM (1<<6) /* reserved for FM */ +#define AWE_ST_RELEASED (1<<7) /* released */ + + int ch; /* midi channel */ + int key; /* internal key for search */ + int layer; /* layer number (for channel mode only) */ + int time; /* allocated time */ + awe_chan_info *cinfo; /* channel info */ + + int note; /* midi key (0-127) */ + int velocity; /* midi velocity (0-127) */ + int sostenuto; /* sostenuto on/off */ + awe_voice_info *sample; /* assigned voice */ + + /* EMU8000 parameters */ + int apitch; /* pitch parameter */ + int avol; /* volume parameter */ + int apan; /* panning parameter */ + int acutoff; /* cutoff parameter */ + short aaux; /* aux word */ +} voice_info; + +/* voice information */ +static voice_info voices[AWE_MAX_VOICES]; + +#define IS_NO_SOUND(v) (voices[v].state & (AWE_ST_OFF|AWE_ST_RELEASED|AWE_ST_STANDBY|AWE_ST_SUSTAINED)) +#define IS_NO_EFFECT(v) (voices[v].state != AWE_ST_ON) +#define IS_PLAYING(v) (voices[v].state & (AWE_ST_ON|AWE_ST_SUSTAINED|AWE_ST_RELEASED)) +#define IS_EMPTY(v) (voices[v].state & (AWE_ST_OFF|AWE_ST_MARK|AWE_ST_DRAM|AWE_ST_FM)) + + +/* MIDI channel effects information (for hw control) */ +static awe_chan_info channels[AWE_MAX_CHANNELS]; + + +/* + * global variables + */ + +#ifndef AWE_DEFAULT_BASE_ADDR +#define AWE_DEFAULT_BASE_ADDR 0 /* autodetect */ +#endif + +#ifndef AWE_DEFAULT_MEM_SIZE +#define AWE_DEFAULT_MEM_SIZE -1 /* autodetect */ +#endif + +static int io = AWE_DEFAULT_BASE_ADDR; /* Emu8000 base address */ +static int memsize = AWE_DEFAULT_MEM_SIZE; /* memory size in Kbytes */ +#ifdef CONFIG_PNP +static int isapnp = -1; +#else +static int isapnp; +#endif + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("SB AWE32/64 WaveTable driver"); +MODULE_LICENSE("GPL"); + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "base i/o port of Emu8000"); +module_param(memsize, int, 0); +MODULE_PARM_DESC(memsize, "onboard DRAM size in Kbytes"); +module_param(isapnp, bool, 0); +MODULE_PARM_DESC(isapnp, "use ISAPnP detection"); + +/* DRAM start offset */ +static int awe_mem_start = AWE_DRAM_OFFSET; + +/* maximum channels for playing */ +static int awe_max_voices = AWE_MAX_VOICES; + +static int patch_opened; /* sample already loaded? */ + +static char atten_relative = FALSE; +static short atten_offset; + +static int awe_present = FALSE; /* awe device present? */ +static int awe_busy = FALSE; /* awe device opened? */ + +static int my_dev = -1; + +#define DEFAULT_DRUM_FLAGS ((1 << 9) | (1 << 25)) +#define IS_DRUM_CHANNEL(c) (drum_flags & (1 << (c))) +#define DRUM_CHANNEL_ON(c) (drum_flags |= (1 << (c))) +#define DRUM_CHANNEL_OFF(c) (drum_flags &= ~(1 << (c))) +static unsigned int drum_flags = DEFAULT_DRUM_FLAGS; /* channel flags */ + +static int playing_mode = AWE_PLAY_INDIRECT; +#define SINGLE_LAYER_MODE() (playing_mode == AWE_PLAY_INDIRECT || playing_mode == AWE_PLAY_DIRECT) +#define MULTI_LAYER_MODE() (playing_mode == AWE_PLAY_MULTI || playing_mode == AWE_PLAY_MULTI2) + +static int current_alloc_time; /* voice allocation index for channel mode */ + +static struct synth_info awe_info = { + "AWE32 Synth", /* name */ + 0, /* device */ + SYNTH_TYPE_SAMPLE, /* synth_type */ + SAMPLE_TYPE_AWE32, /* synth_subtype */ + 0, /* perc_mode (obsolete) */ + AWE_MAX_VOICES, /* nr_voices */ + 0, /* nr_drums (obsolete) */ + 400 /* instr_bank_size */ +}; + + +static struct voice_alloc_info *voice_alloc; /* set at initialization */ + + +/* + * function prototypes + */ + +static int awe_request_region(void); +static void awe_release_region(void); + +static void awe_reset_samples(void); +/* emu8000 chip i/o access */ +static void setup_ports(int p1, int p2, int p3); +static void awe_poke(unsigned short cmd, unsigned short port, unsigned short data); +static void awe_poke_dw(unsigned short cmd, unsigned short port, unsigned int data); +static unsigned short awe_peek(unsigned short cmd, unsigned short port); +static unsigned int awe_peek_dw(unsigned short cmd, unsigned short port); +static void awe_wait(unsigned short delay); + +/* initialize emu8000 chip */ +static void awe_initialize(void); + +/* set voice parameters */ +static void awe_init_ctrl_parms(int init_all); +static void awe_init_voice_info(awe_voice_info *vp); +static void awe_init_voice_parm(awe_voice_parm *pp); +#ifdef AWE_HAS_GUS_COMPATIBILITY +static int freq_to_note(int freq); +static int calc_rate_offset(int Hz); +/*static int calc_parm_delay(int msec);*/ +static int calc_parm_hold(int msec); +static int calc_parm_attack(int msec); +static int calc_parm_decay(int msec); +static int calc_parm_search(int msec, short *table); +#endif /* gus compat */ + +/* turn on/off note */ +static void awe_note_on(int voice); +static void awe_note_off(int voice); +static void awe_terminate(int voice); +static void awe_exclusive_off(int voice); +static void awe_note_off_all(int do_sustain); + +/* calculate voice parameters */ +typedef void (*fx_affect_func)(int voice, int forced); +static void awe_set_pitch(int voice, int forced); +static void awe_set_voice_pitch(int voice, int forced); +static void awe_set_volume(int voice, int forced); +static void awe_set_voice_vol(int voice, int forced); +static void awe_set_pan(int voice, int forced); +static void awe_fx_fmmod(int voice, int forced); +static void awe_fx_tremfrq(int voice, int forced); +static void awe_fx_fm2frq2(int voice, int forced); +static void awe_fx_filterQ(int voice, int forced); +static void awe_calc_pitch(int voice); +#ifdef AWE_HAS_GUS_COMPATIBILITY +static void awe_calc_pitch_from_freq(int voice, int freq); +#endif +static void awe_calc_volume(int voice); +static void awe_update_volume(void); +static void awe_change_master_volume(short val); +static void awe_voice_init(int voice, int init_all); +static void awe_channel_init(int ch, int init_all); +static void awe_fx_init(int ch); +static void awe_send_effect(int voice, int layer, int type, int val); +static void awe_modwheel_change(int voice, int value); + +/* sequencer interface */ +static int awe_open(int dev, int mode); +static void awe_close(int dev); +static int awe_ioctl(int dev, unsigned int cmd, void __user * arg); +static int awe_kill_note(int dev, int voice, int note, int velocity); +static int awe_start_note(int dev, int v, int note_num, int volume); +static int awe_set_instr(int dev, int voice, int instr_no); +static int awe_set_instr_2(int dev, int voice, int instr_no); +static void awe_reset(int dev); +static void awe_hw_control(int dev, unsigned char *event); +static int awe_load_patch(int dev, int format, const char __user *addr, + int offs, int count, int pmgr_flag); +static void awe_aftertouch(int dev, int voice, int pressure); +static void awe_controller(int dev, int voice, int ctrl_num, int value); +static void awe_panning(int dev, int voice, int value); +static void awe_volume_method(int dev, int mode); +static void awe_bender(int dev, int voice, int value); +static int awe_alloc(int dev, int chn, int note, struct voice_alloc_info *alloc); +static void awe_setup_voice(int dev, int voice, int chn); + +#define awe_key_pressure(dev,voice,key,press) awe_start_note(dev,voice,(key)+128,press) + +/* hardware controls */ +#ifdef AWE_HAS_GUS_COMPATIBILITY +static void awe_hw_gus_control(int dev, int cmd, unsigned char *event); +#endif +static void awe_hw_awe_control(int dev, int cmd, unsigned char *event); +static void awe_voice_change(int voice, fx_affect_func func); +static void awe_sostenuto_on(int voice, int forced); +static void awe_sustain_off(int voice, int forced); +static void awe_terminate_and_init(int voice, int forced); + +/* voice search */ +static int awe_search_key(int bank, int preset, int note); +static awe_voice_list *awe_search_instr(int bank, int preset, int note); +static int awe_search_multi_voices(awe_voice_list *rec, int note, int velocity, awe_voice_info **vlist); +static void awe_alloc_multi_voices(int ch, int note, int velocity, int key); +static void awe_alloc_one_voice(int voice, int note, int velocity); +static int awe_clear_voice(void); + +/* load / remove patches */ +static int awe_open_patch(awe_patch_info *patch, const char __user *addr, int count); +static int awe_close_patch(awe_patch_info *patch, const char __user *addr, int count); +static int awe_unload_patch(awe_patch_info *patch, const char __user *addr, int count); +static int awe_load_info(awe_patch_info *patch, const char __user *addr, int count); +static int awe_remove_info(awe_patch_info *patch, const char __user *addr, int count); +static int awe_load_data(awe_patch_info *patch, const char __user *addr, int count); +static int awe_replace_data(awe_patch_info *patch, const char __user *addr, int count); +static int awe_load_map(awe_patch_info *patch, const char __user *addr, int count); +#ifdef AWE_HAS_GUS_COMPATIBILITY +static int awe_load_guspatch(const char __user *addr, int offs, int size, int pmgr_flag); +#endif +/*static int awe_probe_info(awe_patch_info *patch, const char __user *addr, int count);*/ +static int awe_probe_data(awe_patch_info *patch, const char __user *addr, int count); +static sf_list *check_patch_opened(int type, char *name); +static int awe_write_wave_data(const char __user *addr, int offset, awe_sample_list *sp, int channels); +static int awe_create_sf(int type, char *name); +static void awe_free_sf(sf_list *sf); +static void add_sf_info(sf_list *sf, awe_voice_list *rec); +static void add_sf_sample(sf_list *sf, awe_sample_list *smp); +static void purge_old_list(awe_voice_list *rec, awe_voice_list *next); +static void add_info_list(awe_voice_list *rec); +static void awe_remove_samples(int sf_id); +static void rebuild_preset_list(void); +static short awe_set_sample(awe_voice_list *rec); +static awe_sample_list *search_sample_index(sf_list *sf, int sample); + +static int is_identical_holder(sf_list *sf1, sf_list *sf2); +#ifdef AWE_ALLOW_SAMPLE_SHARING +static int is_identical_name(unsigned char *name, sf_list *p); +static int is_shared_sf(unsigned char *name); +static int info_duplicated(sf_list *sf, awe_voice_list *rec); +#endif /* allow sharing */ + +/* lowlevel functions */ +static void awe_init_audio(void); +static void awe_init_dma(void); +static void awe_init_array(void); +static void awe_send_array(unsigned short *data); +static void awe_tweak_voice(int voice); +static void awe_tweak(void); +static void awe_init_fm(void); +static int awe_open_dram_for_write(int offset, int channels); +static void awe_open_dram_for_check(void); +static void awe_close_dram(void); +/*static void awe_write_dram(unsigned short c);*/ +static int awe_detect_base(int addr); +static int awe_detect(void); +static void awe_check_dram(void); +static int awe_load_chorus_fx(awe_patch_info *patch, const char __user *addr, int count); +static void awe_set_chorus_mode(int mode); +static void awe_update_chorus_mode(void); +static int awe_load_reverb_fx(awe_patch_info *patch, const char __user *addr, int count); +static void awe_set_reverb_mode(int mode); +static void awe_update_reverb_mode(void); +static void awe_equalizer(int bass, int treble); +static void awe_update_equalizer(void); + +#ifdef CONFIG_AWE32_MIXER +static void attach_mixer(void); +static void unload_mixer(void); +#endif + +#ifdef CONFIG_AWE32_MIDIEMU +static void attach_midiemu(void); +static void unload_midiemu(void); +#endif + +#define limitvalue(x, a, b) if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b) + +/* + * control parameters + */ + + +#ifdef AWE_USE_NEW_VOLUME_CALC +#define DEF_VOLUME_CALC TRUE +#else +#define DEF_VOLUME_CALC FALSE +#endif /* new volume */ + +#define DEF_ZERO_ATTEN 32 /* 12dB below */ +#define DEF_MOD_SENSE 18 +#define DEF_CHORUS_MODE 2 +#define DEF_REVERB_MODE 4 +#define DEF_BASS_LEVEL 5 +#define DEF_TREBLE_LEVEL 9 + +static struct CtrlParmsDef { + int value; + int init_each_time; + void (*update)(void); +} ctrl_parms[AWE_MD_END] = { + {0,0, NULL}, {0,0, NULL}, /* <-- not used */ + {AWE_VERSION_NUMBER, FALSE, NULL}, + {TRUE, FALSE, NULL}, /* exclusive */ + {TRUE, FALSE, NULL}, /* realpan */ + {AWE_DEFAULT_BANK, FALSE, NULL}, /* gusbank */ + {FALSE, TRUE, NULL}, /* keep effect */ + {DEF_ZERO_ATTEN, FALSE, awe_update_volume}, /* zero_atten */ + {FALSE, FALSE, NULL}, /* chn_prior */ + {DEF_MOD_SENSE, FALSE, NULL}, /* modwheel sense */ + {AWE_DEFAULT_PRESET, FALSE, NULL}, /* def_preset */ + {AWE_DEFAULT_BANK, FALSE, NULL}, /* def_bank */ + {AWE_DEFAULT_DRUM, FALSE, NULL}, /* def_drum */ + {FALSE, FALSE, NULL}, /* toggle_drum_bank */ + {DEF_VOLUME_CALC, FALSE, awe_update_volume}, /* new_volume_calc */ + {DEF_CHORUS_MODE, FALSE, awe_update_chorus_mode}, /* chorus mode */ + {DEF_REVERB_MODE, FALSE, awe_update_reverb_mode}, /* reverb mode */ + {DEF_BASS_LEVEL, FALSE, awe_update_equalizer}, /* bass level */ + {DEF_TREBLE_LEVEL, FALSE, awe_update_equalizer}, /* treble level */ + {0, FALSE, NULL}, /* debug mode */ + {FALSE, FALSE, NULL}, /* pan exchange */ +}; + +static int ctrls[AWE_MD_END]; + + +/* + * synth operation table + */ + +static struct synth_operations awe_operations = +{ + .owner = THIS_MODULE, + .id = "EMU8K", + .info = &awe_info, + .midi_dev = 0, + .synth_type = SYNTH_TYPE_SAMPLE, + .synth_subtype = SAMPLE_TYPE_AWE32, + .open = awe_open, + .close = awe_close, + .ioctl = awe_ioctl, + .kill_note = awe_kill_note, + .start_note = awe_start_note, + .set_instr = awe_set_instr_2, + .reset = awe_reset, + .hw_control = awe_hw_control, + .load_patch = awe_load_patch, + .aftertouch = awe_aftertouch, + .controller = awe_controller, + .panning = awe_panning, + .volume_method = awe_volume_method, + .bender = awe_bender, + .alloc_voice = awe_alloc, + .setup_voice = awe_setup_voice +}; + +static void free_tables(void) +{ + if (sftail) { + sf_list *p, *prev; + for (p = sftail; p; p = prev) { + prev = p->prev; + awe_free_sf(p); + } + } + sfhead = sftail = NULL; +} + +/* + * clear sample tables + */ + +static void +awe_reset_samples(void) +{ + /* free all bank tables */ + memset(preset_table, 0, sizeof(preset_table)); + free_tables(); + + current_sf_id = 0; + locked_sf_id = 0; + patch_opened = 0; +} + + +/* + * EMU register access + */ + +/* select a given AWE32 pointer */ +static int awe_ports[5]; +static int port_setuped = FALSE; +static int awe_cur_cmd = -1; +#define awe_set_cmd(cmd) \ +if (awe_cur_cmd != cmd) { outw(cmd, awe_ports[Pointer]); awe_cur_cmd = cmd; } + +/* write 16bit data */ +static void +awe_poke(unsigned short cmd, unsigned short port, unsigned short data) +{ + awe_set_cmd(cmd); + outw(data, awe_ports[port]); +} + +/* write 32bit data */ +static void +awe_poke_dw(unsigned short cmd, unsigned short port, unsigned int data) +{ + unsigned short addr = awe_ports[port]; + awe_set_cmd(cmd); + outw(data, addr); /* write lower 16 bits */ + outw(data >> 16, addr + 2); /* write higher 16 bits */ +} + +/* read 16bit data */ +static unsigned short +awe_peek(unsigned short cmd, unsigned short port) +{ + unsigned short k; + awe_set_cmd(cmd); + k = inw(awe_ports[port]); + return k; +} + +/* read 32bit data */ +static unsigned int +awe_peek_dw(unsigned short cmd, unsigned short port) +{ + unsigned int k1, k2; + unsigned short addr = awe_ports[port]; + awe_set_cmd(cmd); + k1 = inw(addr); + k2 = inw(addr + 2); + k1 |= k2 << 16; + return k1; +} + +/* wait delay number of AWE32 44100Hz clocks */ +#ifdef WAIT_BY_LOOP /* wait by loop -- that's not good.. */ +static void +awe_wait(unsigned short delay) +{ + unsigned short clock, target; + unsigned short port = awe_ports[AWE_WC_Port]; + int counter; + + /* sample counter */ + awe_set_cmd(AWE_WC_Cmd); + clock = (unsigned short)inw(port); + target = clock + delay; + counter = 0; + if (target < clock) { + for (; (unsigned short)inw(port) > target; counter++) + if (counter > 65536) + break; + } + for (; (unsigned short)inw(port) < target; counter++) + if (counter > 65536) + break; +} +#else + +static void awe_wait(unsigned short delay) +{ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout((HZ*(unsigned long)delay + 44099)/44100); +} +/* +static void awe_wait(unsigned short delay) +{ + udelay(((unsigned long)delay * 1000000L + 44099) / 44100); +} +*/ +#endif /* wait by loop */ + +/* write a word data */ +#define awe_write_dram(c) awe_poke(AWE_SMLD, c) + +/* + * AWE32 voice parameters + */ + +/* initialize voice_info record */ +static void +awe_init_voice_info(awe_voice_info *vp) +{ + vp->sample = 0; + vp->rate_offset = 0; + + vp->start = 0; + vp->end = 0; + vp->loopstart = 0; + vp->loopend = 0; + vp->mode = 0; + vp->root = 60; + vp->tune = 0; + vp->low = 0; + vp->high = 127; + vp->vellow = 0; + vp->velhigh = 127; + + vp->fixkey = -1; + vp->fixvel = -1; + vp->fixpan = -1; + vp->pan = -1; + + vp->exclusiveClass = 0; + vp->amplitude = 127; + vp->attenuation = 0; + vp->scaleTuning = 100; + + awe_init_voice_parm(&vp->parm); +} + +/* initialize voice_parm record: + * Env1/2: delay=0, attack=0, hold=0, sustain=0, decay=0, release=0. + * Vibrato and Tremolo effects are zero. + * Cutoff is maximum. + * Chorus and Reverb effects are zero. + */ +static void +awe_init_voice_parm(awe_voice_parm *pp) +{ + pp->moddelay = 0x8000; + pp->modatkhld = 0x7f7f; + pp->moddcysus = 0x7f7f; + pp->modrelease = 0x807f; + pp->modkeyhold = 0; + pp->modkeydecay = 0; + + pp->voldelay = 0x8000; + pp->volatkhld = 0x7f7f; + pp->voldcysus = 0x7f7f; + pp->volrelease = 0x807f; + pp->volkeyhold = 0; + pp->volkeydecay = 0; + + pp->lfo1delay = 0x8000; + pp->lfo2delay = 0x8000; + pp->pefe = 0; + + pp->fmmod = 0; + pp->tremfrq = 0; + pp->fm2frq2 = 0; + + pp->cutoff = 0xff; + pp->filterQ = 0; + + pp->chorus = 0; + pp->reverb = 0; +} + + +#ifdef AWE_HAS_GUS_COMPATIBILITY + +/* convert frequency mHz to abstract cents (= midi key * 100) */ +static int +freq_to_note(int mHz) +{ + /* abscents = log(mHz/8176) / log(2) * 1200 */ + unsigned int max_val = (unsigned int)0xffffffff / 10000; + int i, times; + unsigned int base; + unsigned int freq; + int note, tune; + + if (mHz == 0) + return 0; + if (mHz < 0) + return 12799; /* maximum */ + + freq = mHz; + note = 0; + for (base = 8176 * 2; freq >= base; base *= 2) { + note += 12; + if (note >= 128) /* over maximum */ + return 12799; + } + base /= 2; + + /* to avoid overflow... */ + times = 10000; + while (freq > max_val) { + max_val *= 10; + times /= 10; + base /= 10; + } + + freq = freq * times / base; + for (i = 0; i < 12; i++) { + if (freq < semitone_tuning[i+1]) + break; + note++; + } + + tune = 0; + freq = freq * 10000 / semitone_tuning[i]; + for (i = 0; i < 100; i++) { + if (freq < cent_tuning[i+1]) + break; + tune++; + } + + return note * 100 + tune; +} + + +/* convert Hz to AWE32 rate offset: + * sample pitch offset for the specified sample rate + * rate=44100 is no offset, each 4096 is 1 octave (twice). + * eg, when rate is 22050, this offset becomes -4096. + */ +static int +calc_rate_offset(int Hz) +{ + /* offset = log(Hz / 44100) / log(2) * 4096 */ + int freq, base, i; + + /* maybe smaller than max (44100Hz) */ + if (Hz <= 0 || Hz >= 44100) return 0; + + base = 0; + for (freq = Hz * 2; freq < 44100; freq *= 2) + base++; + base *= 1200; + + freq = 44100 * 10000 / (freq/2); + for (i = 0; i < 12; i++) { + if (freq < semitone_tuning[i+1]) + break; + base += 100; + } + freq = freq * 10000 / semitone_tuning[i]; + for (i = 0; i < 100; i++) { + if (freq < cent_tuning[i+1]) + break; + base++; + } + return -base * 4096 / 1200; +} + + +/* + * convert envelope time parameter to AWE32 raw parameter + */ + +/* attack & decay/release time table (msec) */ +static short attack_time_tbl[128] = { +32767, 32767, 5989, 4235, 2994, 2518, 2117, 1780, 1497, 1373, 1259, 1154, 1058, 970, 890, 816, +707, 691, 662, 634, 607, 581, 557, 533, 510, 489, 468, 448, 429, 411, 393, 377, +361, 345, 331, 317, 303, 290, 278, 266, 255, 244, 234, 224, 214, 205, 196, 188, +180, 172, 165, 158, 151, 145, 139, 133, 127, 122, 117, 112, 107, 102, 98, 94, +90, 86, 82, 79, 75, 72, 69, 66, 63, 61, 58, 56, 53, 51, 49, 47, +45, 43, 41, 39, 37, 36, 34, 33, 31, 30, 29, 28, 26, 25, 24, 23, +22, 21, 20, 19, 19, 18, 17, 16, 16, 15, 15, 14, 13, 13, 12, 12, +11, 11, 10, 10, 10, 9, 9, 8, 8, 8, 8, 7, 7, 7, 6, 0, +}; + +static short decay_time_tbl[128] = { +32767, 32767, 22614, 15990, 11307, 9508, 7995, 6723, 5653, 5184, 4754, 4359, 3997, 3665, 3361, 3082, +2828, 2765, 2648, 2535, 2428, 2325, 2226, 2132, 2042, 1955, 1872, 1793, 1717, 1644, 1574, 1507, +1443, 1382, 1324, 1267, 1214, 1162, 1113, 1066, 978, 936, 897, 859, 822, 787, 754, 722, +691, 662, 634, 607, 581, 557, 533, 510, 489, 468, 448, 429, 411, 393, 377, 361, +345, 331, 317, 303, 290, 278, 266, 255, 244, 234, 224, 214, 205, 196, 188, 180, +172, 165, 158, 151, 145, 139, 133, 127, 122, 117, 112, 107, 102, 98, 94, 90, +86, 82, 79, 75, 72, 69, 66, 63, 61, 58, 56, 53, 51, 49, 47, 45, +43, 41, 39, 37, 36, 34, 33, 31, 30, 29, 28, 26, 25, 24, 23, 22, +}; + +#define calc_parm_delay(msec) (0x8000 - (msec) * 1000 / 725); + +/* delay time = 0x8000 - msec/92 */ +static int +calc_parm_hold(int msec) +{ + int val = (0x7f * 92 - msec) / 92; + if (val < 1) val = 1; + if (val > 127) val = 127; + return val; +} + +/* attack time: search from time table */ +static int +calc_parm_attack(int msec) +{ + return calc_parm_search(msec, attack_time_tbl); +} + +/* decay/release time: search from time table */ +static int +calc_parm_decay(int msec) +{ + return calc_parm_search(msec, decay_time_tbl); +} + +/* search an index for specified time from given time table */ +static int +calc_parm_search(int msec, short *table) +{ + int left = 1, right = 127, mid; + while (left < right) { + mid = (left + right) / 2; + if (msec < (int)table[mid]) + left = mid + 1; + else + right = mid; + } + return left; +} +#endif /* AWE_HAS_GUS_COMPATIBILITY */ + + +/* + * effects table + */ + +/* set an effect value */ +#define FX_FLAG_OFF 0 +#define FX_FLAG_SET 1 +#define FX_FLAG_ADD 2 + +#define FX_SET(rec,type,value) \ + ((rec)->flags[type] = FX_FLAG_SET, (rec)->val[type] = (value)) +#define FX_ADD(rec,type,value) \ + ((rec)->flags[type] = FX_FLAG_ADD, (rec)->val[type] = (value)) +#define FX_UNSET(rec,type) \ + ((rec)->flags[type] = FX_FLAG_OFF, (rec)->val[type] = 0) + +/* check the effect value is set */ +#define FX_ON(rec,type) ((rec)->flags[type]) + +#define PARM_BYTE 0 +#define PARM_WORD 1 +#define PARM_SIGN 2 + +static struct PARM_DEFS { + int type; /* byte or word */ + int low, high; /* value range */ + fx_affect_func realtime; /* realtime paramater change */ +} parm_defs[] = { + {PARM_WORD, 0, 0x8000, NULL}, /* env1 delay */ + {PARM_BYTE, 1, 0x7f, NULL}, /* env1 attack */ + {PARM_BYTE, 0, 0x7e, NULL}, /* env1 hold */ + {PARM_BYTE, 1, 0x7f, NULL}, /* env1 decay */ + {PARM_BYTE, 1, 0x7f, NULL}, /* env1 release */ + {PARM_BYTE, 0, 0x7f, NULL}, /* env1 sustain */ + {PARM_BYTE, 0, 0xff, NULL}, /* env1 pitch */ + {PARM_BYTE, 0, 0xff, NULL}, /* env1 cutoff */ + + {PARM_WORD, 0, 0x8000, NULL}, /* env2 delay */ + {PARM_BYTE, 1, 0x7f, NULL}, /* env2 attack */ + {PARM_BYTE, 0, 0x7e, NULL}, /* env2 hold */ + {PARM_BYTE, 1, 0x7f, NULL}, /* env2 decay */ + {PARM_BYTE, 1, 0x7f, NULL}, /* env2 release */ + {PARM_BYTE, 0, 0x7f, NULL}, /* env2 sustain */ + + {PARM_WORD, 0, 0x8000, NULL}, /* lfo1 delay */ + {PARM_BYTE, 0, 0xff, awe_fx_tremfrq}, /* lfo1 freq */ + {PARM_SIGN, -128, 127, awe_fx_tremfrq}, /* lfo1 volume */ + {PARM_SIGN, -128, 127, awe_fx_fmmod}, /* lfo1 pitch */ + {PARM_BYTE, 0, 0xff, awe_fx_fmmod}, /* lfo1 cutoff */ + + {PARM_WORD, 0, 0x8000, NULL}, /* lfo2 delay */ + {PARM_BYTE, 0, 0xff, awe_fx_fm2frq2}, /* lfo2 freq */ + {PARM_SIGN, -128, 127, awe_fx_fm2frq2}, /* lfo2 pitch */ + + {PARM_WORD, 0, 0xffff, awe_set_voice_pitch}, /* initial pitch */ + {PARM_BYTE, 0, 0xff, NULL}, /* chorus */ + {PARM_BYTE, 0, 0xff, NULL}, /* reverb */ + {PARM_BYTE, 0, 0xff, awe_set_volume}, /* initial cutoff */ + {PARM_BYTE, 0, 15, awe_fx_filterQ}, /* initial resonance */ + + {PARM_WORD, 0, 0xffff, NULL}, /* sample start */ + {PARM_WORD, 0, 0xffff, NULL}, /* loop start */ + {PARM_WORD, 0, 0xffff, NULL}, /* loop end */ + {PARM_WORD, 0, 0xffff, NULL}, /* coarse sample start */ + {PARM_WORD, 0, 0xffff, NULL}, /* coarse loop start */ + {PARM_WORD, 0, 0xffff, NULL}, /* coarse loop end */ + {PARM_BYTE, 0, 0xff, awe_set_volume}, /* initial attenuation */ +}; + + +static unsigned char +FX_BYTE(FX_Rec *rec, FX_Rec *lay, int type, unsigned char value) +{ + int effect = 0; + int on = 0; + if (lay && (on = FX_ON(lay, type)) != 0) + effect = lay->val[type]; + if (!on && (on = FX_ON(rec, type)) != 0) + effect = rec->val[type]; + if (on == FX_FLAG_ADD) { + if (parm_defs[type].type == PARM_SIGN) { + if (value > 0x7f) + effect += (int)value - 0x100; + else + effect += (int)value; + } else { + effect += (int)value; + } + } + if (on) { + if (effect < parm_defs[type].low) + effect = parm_defs[type].low; + else if (effect > parm_defs[type].high) + effect = parm_defs[type].high; + return (unsigned char)effect; + } + return value; +} + +/* get word effect value */ +static unsigned short +FX_WORD(FX_Rec *rec, FX_Rec *lay, int type, unsigned short value) +{ + int effect = 0; + int on = 0; + if (lay && (on = FX_ON(lay, type)) != 0) + effect = lay->val[type]; + if (!on && (on = FX_ON(rec, type)) != 0) + effect = rec->val[type]; + if (on == FX_FLAG_ADD) + effect += (int)value; + if (on) { + if (effect < parm_defs[type].low) + effect = parm_defs[type].low; + else if (effect > parm_defs[type].high) + effect = parm_defs[type].high; + return (unsigned short)effect; + } + return value; +} + +/* get word (upper=type1/lower=type2) effect value */ +static unsigned short +FX_COMB(FX_Rec *rec, FX_Rec *lay, int type1, int type2, unsigned short value) +{ + unsigned short tmp; + tmp = FX_BYTE(rec, lay, type1, (unsigned char)(value >> 8)); + tmp <<= 8; + tmp |= FX_BYTE(rec, lay, type2, (unsigned char)(value & 0xff)); + return tmp; +} + +/* address offset */ +static int +FX_OFFSET(FX_Rec *rec, FX_Rec *lay, int lo, int hi, int mode) +{ + int addr = 0; + if (lay && FX_ON(lay, hi)) + addr = (short)lay->val[hi]; + else if (FX_ON(rec, hi)) + addr = (short)rec->val[hi]; + addr = addr << 15; + if (lay && FX_ON(lay, lo)) + addr += (short)lay->val[lo]; + else if (FX_ON(rec, lo)) + addr += (short)rec->val[lo]; + if (!(mode & AWE_SAMPLE_8BITS)) + addr /= 2; + return addr; +} + + +/* + * turn on/off sample + */ + +/* table for volume target calculation */ +static unsigned short voltarget[16] = { + 0xEAC0, 0XE0C8, 0XD740, 0XCE20, 0XC560, 0XBD08, 0XB500, 0XAD58, + 0XA5F8, 0X9EF0, 0X9830, 0X91C0, 0X8B90, 0X85A8, 0X8000, 0X7A90 +}; + +static void +awe_note_on(int voice) +{ + unsigned int temp; + int addr; + int vtarget, ftarget, ptarget, pitch; + awe_voice_info *vp; + awe_voice_parm_block *parm; + FX_Rec *fx = &voices[voice].cinfo->fx; + FX_Rec *fx_lay = NULL; + if (voices[voice].layer < MAX_LAYERS) + fx_lay = &voices[voice].cinfo->fx_layer[voices[voice].layer]; + + /* A voice sample must assigned before calling */ + if ((vp = voices[voice].sample) == NULL || vp->index == 0) + return; + + parm = (awe_voice_parm_block*)&vp->parm; + + /* channel to be silent and idle */ + awe_poke(AWE_DCYSUSV(voice), 0x0080); + awe_poke(AWE_VTFT(voice), 0x0000FFFF); + awe_poke(AWE_CVCF(voice), 0x0000FFFF); + awe_poke(AWE_PTRX(voice), 0); + awe_poke(AWE_CPF(voice), 0); + + /* set pitch offset */ + awe_set_pitch(voice, TRUE); + + /* modulation & volume envelope */ + if (parm->modatk >= 0x80 && parm->moddelay >= 0x8000) { + awe_poke(AWE_ENVVAL(voice), 0xBFFF); + pitch = (parm->env1pit<<4) + voices[voice].apitch; + if (pitch > 0xffff) pitch = 0xffff; + /* calculate filter target */ + ftarget = parm->cutoff + parm->env1fc; + limitvalue(ftarget, 0, 255); + ftarget <<= 8; + } else { + awe_poke(AWE_ENVVAL(voice), + FX_WORD(fx, fx_lay, AWE_FX_ENV1_DELAY, parm->moddelay)); + ftarget = parm->cutoff; + ftarget <<= 8; + pitch = voices[voice].apitch; + } + + /* calcualte pitch target */ + if (pitch != 0xffff) { + ptarget = 1 << (pitch >> 12); + if (pitch & 0x800) ptarget += (ptarget*0x102e)/0x2710; + if (pitch & 0x400) ptarget += (ptarget*0x764)/0x2710; + if (pitch & 0x200) ptarget += (ptarget*0x389)/0x2710; + ptarget += (ptarget>>1); + if (ptarget > 0xffff) ptarget = 0xffff; + + } else ptarget = 0xffff; + if (parm->modatk >= 0x80) + awe_poke(AWE_ATKHLD(voice), + FX_BYTE(fx, fx_lay, AWE_FX_ENV1_HOLD, parm->modhld) << 8 | 0x7f); + else + awe_poke(AWE_ATKHLD(voice), + FX_COMB(fx, fx_lay, AWE_FX_ENV1_HOLD, AWE_FX_ENV1_ATTACK, + vp->parm.modatkhld)); + awe_poke(AWE_DCYSUS(voice), + FX_COMB(fx, fx_lay, AWE_FX_ENV1_SUSTAIN, AWE_FX_ENV1_DECAY, + vp->parm.moddcysus)); + + if (parm->volatk >= 0x80 && parm->voldelay >= 0x8000) { + awe_poke(AWE_ENVVOL(voice), 0xBFFF); + vtarget = voltarget[voices[voice].avol%0x10]>>(voices[voice].avol>>4); + } else { + awe_poke(AWE_ENVVOL(voice), + FX_WORD(fx, fx_lay, AWE_FX_ENV2_DELAY, vp->parm.voldelay)); + vtarget = 0; + } + if (parm->volatk >= 0x80) + awe_poke(AWE_ATKHLDV(voice), + FX_BYTE(fx, fx_lay, AWE_FX_ENV2_HOLD, parm->volhld) << 8 | 0x7f); + else + awe_poke(AWE_ATKHLDV(voice), + FX_COMB(fx, fx_lay, AWE_FX_ENV2_HOLD, AWE_FX_ENV2_ATTACK, + vp->parm.volatkhld)); + /* decay/sustain parameter for volume envelope must be set at last */ + + /* cutoff and volume */ + awe_set_volume(voice, TRUE); + + /* modulation envelope heights */ + awe_poke(AWE_PEFE(voice), + FX_COMB(fx, fx_lay, AWE_FX_ENV1_PITCH, AWE_FX_ENV1_CUTOFF, + vp->parm.pefe)); + + /* lfo1/2 delay */ + awe_poke(AWE_LFO1VAL(voice), + FX_WORD(fx, fx_lay, AWE_FX_LFO1_DELAY, vp->parm.lfo1delay)); + awe_poke(AWE_LFO2VAL(voice), + FX_WORD(fx, fx_lay, AWE_FX_LFO2_DELAY, vp->parm.lfo2delay)); + + /* lfo1 pitch & cutoff shift */ + awe_fx_fmmod(voice, TRUE); + /* lfo1 volume & freq */ + awe_fx_tremfrq(voice, TRUE); + /* lfo2 pitch & freq */ + awe_fx_fm2frq2(voice, TRUE); + /* pan & loop start */ + awe_set_pan(voice, TRUE); + + /* chorus & loop end (chorus 8bit, MSB) */ + addr = vp->loopend - 1; + addr += FX_OFFSET(fx, fx_lay, AWE_FX_LOOP_END, + AWE_FX_COARSE_LOOP_END, vp->mode); + temp = FX_BYTE(fx, fx_lay, AWE_FX_CHORUS, vp->parm.chorus); + temp = (temp <<24) | (unsigned int)addr; + awe_poke_dw(AWE_CSL(voice), temp); + DEBUG(4,printk("AWE32: [-- loopend=%x/%x]\n", vp->loopend, addr)); + + /* Q & current address (Q 4bit value, MSB) */ + addr = vp->start - 1; + addr += FX_OFFSET(fx, fx_lay, AWE_FX_SAMPLE_START, + AWE_FX_COARSE_SAMPLE_START, vp->mode); + temp = FX_BYTE(fx, fx_lay, AWE_FX_FILTERQ, vp->parm.filterQ); + temp = (temp<<28) | (unsigned int)addr; + awe_poke_dw(AWE_CCCA(voice), temp); + DEBUG(4,printk("AWE32: [-- startaddr=%x/%x]\n", vp->start, addr)); + + /* clear unknown registers */ + awe_poke_dw(AWE_00A0(voice), 0); + awe_poke_dw(AWE_0080(voice), 0); + + /* reset volume */ + awe_poke_dw(AWE_VTFT(voice), (vtarget<<16)|ftarget); + awe_poke_dw(AWE_CVCF(voice), (vtarget<<16)|ftarget); + + /* set reverb */ + temp = FX_BYTE(fx, fx_lay, AWE_FX_REVERB, vp->parm.reverb); + temp = (temp << 8) | (ptarget << 16) | voices[voice].aaux; + awe_poke_dw(AWE_PTRX(voice), temp); + awe_poke_dw(AWE_CPF(voice), ptarget << 16); + /* turn on envelope */ + awe_poke(AWE_DCYSUSV(voice), + FX_COMB(fx, fx_lay, AWE_FX_ENV2_SUSTAIN, AWE_FX_ENV2_DECAY, + vp->parm.voldcysus)); + + voices[voice].state = AWE_ST_ON; + + /* clear voice position for the next note on this channel */ + if (SINGLE_LAYER_MODE()) { + FX_UNSET(fx, AWE_FX_SAMPLE_START); + FX_UNSET(fx, AWE_FX_COARSE_SAMPLE_START); + } +} + + +/* turn off the voice */ +static void +awe_note_off(int voice) +{ + awe_voice_info *vp; + unsigned short tmp; + FX_Rec *fx = &voices[voice].cinfo->fx; + FX_Rec *fx_lay = NULL; + if (voices[voice].layer < MAX_LAYERS) + fx_lay = &voices[voice].cinfo->fx_layer[voices[voice].layer]; + + if ((vp = voices[voice].sample) == NULL) { + voices[voice].state = AWE_ST_OFF; + return; + } + + tmp = 0x8000 | FX_BYTE(fx, fx_lay, AWE_FX_ENV1_RELEASE, + (unsigned char)vp->parm.modrelease); + awe_poke(AWE_DCYSUS(voice), tmp); + tmp = 0x8000 | FX_BYTE(fx, fx_lay, AWE_FX_ENV2_RELEASE, + (unsigned char)vp->parm.volrelease); + awe_poke(AWE_DCYSUSV(voice), tmp); + voices[voice].state = AWE_ST_RELEASED; +} + +/* force to terminate the voice (no releasing echo) */ +static void +awe_terminate(int voice) +{ + awe_poke(AWE_DCYSUSV(voice), 0x807F); + awe_tweak_voice(voice); + voices[voice].state = AWE_ST_OFF; +} + +/* turn off other voices with the same exclusive class (for drums) */ +static void +awe_exclusive_off(int voice) +{ + int i, exclass; + + if (voices[voice].sample == NULL) + return; + if ((exclass = voices[voice].sample->exclusiveClass) == 0) + return; /* not exclusive */ + + /* turn off voices with the same class */ + for (i = 0; i < awe_max_voices; i++) { + if (i != voice && IS_PLAYING(i) && + voices[i].sample && voices[i].ch == voices[voice].ch && + voices[i].sample->exclusiveClass == exclass) { + DEBUG(4,printk("AWE32: [exoff(%d)]\n", i)); + awe_terminate(i); + awe_voice_init(i, TRUE); + } + } +} + + +/* + * change the parameters of an audible voice + */ + +/* change pitch */ +static void +awe_set_pitch(int voice, int forced) +{ + if (IS_NO_EFFECT(voice) && !forced) return; + awe_poke(AWE_IP(voice), voices[voice].apitch); + DEBUG(3,printk("AWE32: [-- pitch=%x]\n", voices[voice].apitch)); +} + +/* calculate & change pitch */ +static void +awe_set_voice_pitch(int voice, int forced) +{ + awe_calc_pitch(voice); + awe_set_pitch(voice, forced); +} + +/* change volume & cutoff */ +static void +awe_set_volume(int voice, int forced) +{ + awe_voice_info *vp; + unsigned short tmp2; + FX_Rec *fx = &voices[voice].cinfo->fx; + FX_Rec *fx_lay = NULL; + if (voices[voice].layer < MAX_LAYERS) + fx_lay = &voices[voice].cinfo->fx_layer[voices[voice].layer]; + + if (!IS_PLAYING(voice) && !forced) return; + if ((vp = voices[voice].sample) == NULL || vp->index == 0) + return; + + tmp2 = FX_BYTE(fx, fx_lay, AWE_FX_CUTOFF, + (unsigned char)voices[voice].acutoff); + tmp2 = (tmp2 << 8); + tmp2 |= FX_BYTE(fx, fx_lay, AWE_FX_ATTEN, + (unsigned char)voices[voice].avol); + awe_poke(AWE_IFATN(voice), tmp2); +} + +/* calculate & change volume */ +static void +awe_set_voice_vol(int voice, int forced) +{ + if (IS_EMPTY(voice)) + return; + awe_calc_volume(voice); + awe_set_volume(voice, forced); +} + + +/* change pan; this could make a click noise.. */ +static void +awe_set_pan(int voice, int forced) +{ + unsigned int temp; + int addr; + awe_voice_info *vp; + FX_Rec *fx = &voices[voice].cinfo->fx; + FX_Rec *fx_lay = NULL; + if (voices[voice].layer < MAX_LAYERS) + fx_lay = &voices[voice].cinfo->fx_layer[voices[voice].layer]; + + if (IS_NO_EFFECT(voice) && !forced) return; + if ((vp = voices[voice].sample) == NULL || vp->index == 0) + return; + + /* pan & loop start (pan 8bit, MSB, 0:right, 0xff:left) */ + if (vp->fixpan > 0) /* 0-127 */ + temp = 255 - (int)vp->fixpan * 2; + else { + int pos = 0; + if (vp->pan >= 0) /* 0-127 */ + pos = (int)vp->pan * 2 - 128; + pos += voices[voice].cinfo->panning; /* -128 - 127 */ + temp = 127 - pos; + } + limitvalue(temp, 0, 255); + if (ctrls[AWE_MD_PAN_EXCHANGE]) { + temp = 255 - temp; + } + if (forced || temp != voices[voice].apan) { + voices[voice].apan = temp; + if (temp == 0) + voices[voice].aaux = 0xff; + else + voices[voice].aaux = (-temp) & 0xff; + addr = vp->loopstart - 1; + addr += FX_OFFSET(fx, fx_lay, AWE_FX_LOOP_START, + AWE_FX_COARSE_LOOP_START, vp->mode); + temp = (temp<<24) | (unsigned int)addr; + awe_poke_dw(AWE_PSST(voice), temp); + DEBUG(4,printk("AWE32: [-- loopstart=%x/%x]\n", vp->loopstart, addr)); + } +} + +/* effects change during playing */ +static void +awe_fx_fmmod(int voice, int forced) +{ + awe_voice_info *vp; + FX_Rec *fx = &voices[voice].cinfo->fx; + FX_Rec *fx_lay = NULL; + if (voices[voice].layer < MAX_LAYERS) + fx_lay = &voices[voice].cinfo->fx_layer[voices[voice].layer]; + + if (IS_NO_EFFECT(voice) && !forced) return; + if ((vp = voices[voice].sample) == NULL || vp->index == 0) + return; + awe_poke(AWE_FMMOD(voice), + FX_COMB(fx, fx_lay, AWE_FX_LFO1_PITCH, AWE_FX_LFO1_CUTOFF, + vp->parm.fmmod)); +} + +/* set tremolo (lfo1) volume & frequency */ +static void +awe_fx_tremfrq(int voice, int forced) +{ + awe_voice_info *vp; + FX_Rec *fx = &voices[voice].cinfo->fx; + FX_Rec *fx_lay = NULL; + if (voices[voice].layer < MAX_LAYERS) + fx_lay = &voices[voice].cinfo->fx_layer[voices[voice].layer]; + + if (IS_NO_EFFECT(voice) && !forced) return; + if ((vp = voices[voice].sample) == NULL || vp->index == 0) + return; + awe_poke(AWE_TREMFRQ(voice), + FX_COMB(fx, fx_lay, AWE_FX_LFO1_VOLUME, AWE_FX_LFO1_FREQ, + vp->parm.tremfrq)); +} + +/* set lfo2 pitch & frequency */ +static void +awe_fx_fm2frq2(int voice, int forced) +{ + awe_voice_info *vp; + FX_Rec *fx = &voices[voice].cinfo->fx; + FX_Rec *fx_lay = NULL; + if (voices[voice].layer < MAX_LAYERS) + fx_lay = &voices[voice].cinfo->fx_layer[voices[voice].layer]; + + if (IS_NO_EFFECT(voice) && !forced) return; + if ((vp = voices[voice].sample) == NULL || vp->index == 0) + return; + awe_poke(AWE_FM2FRQ2(voice), + FX_COMB(fx, fx_lay, AWE_FX_LFO2_PITCH, AWE_FX_LFO2_FREQ, + vp->parm.fm2frq2)); +} + + +/* Q & current address (Q 4bit value, MSB) */ +static void +awe_fx_filterQ(int voice, int forced) +{ + unsigned int addr; + awe_voice_info *vp; + FX_Rec *fx = &voices[voice].cinfo->fx; + FX_Rec *fx_lay = NULL; + if (voices[voice].layer < MAX_LAYERS) + fx_lay = &voices[voice].cinfo->fx_layer[voices[voice].layer]; + + if (IS_NO_EFFECT(voice) && !forced) return; + if ((vp = voices[voice].sample) == NULL || vp->index == 0) + return; + + addr = awe_peek_dw(AWE_CCCA(voice)) & 0xffffff; + addr |= (FX_BYTE(fx, fx_lay, AWE_FX_FILTERQ, vp->parm.filterQ) << 28); + awe_poke_dw(AWE_CCCA(voice), addr); +} + +/* + * calculate pitch offset + * + * 0xE000 is no pitch offset at 44100Hz sample. + * Every 4096 is one octave. + */ + +static void +awe_calc_pitch(int voice) +{ + voice_info *vp = &voices[voice]; + awe_voice_info *ap; + awe_chan_info *cp = voices[voice].cinfo; + int offset; + + /* search voice information */ + if ((ap = vp->sample) == NULL) + return; + if (ap->index == 0) { + DEBUG(3,printk("AWE32: set sample (%d)\n", ap->sample)); + if (awe_set_sample((awe_voice_list*)ap) == 0) + return; + } + + /* calculate offset */ + if (ap->fixkey >= 0) { + DEBUG(3,printk("AWE32: p-> fixkey(%d) tune(%d)\n", ap->fixkey, ap->tune)); + offset = (ap->fixkey - ap->root) * 4096 / 12; + } else { + DEBUG(3,printk("AWE32: p(%d)-> root(%d) tune(%d)\n", vp->note, ap->root, ap->tune)); + offset = (vp->note - ap->root) * 4096 / 12; + DEBUG(4,printk("AWE32: p-> ofs=%d\n", offset)); + } + offset = (offset * ap->scaleTuning) / 100; + DEBUG(4,printk("AWE32: p-> scale* ofs=%d\n", offset)); + offset += ap->tune * 4096 / 1200; + DEBUG(4,printk("AWE32: p-> tune+ ofs=%d\n", offset)); + if (cp->bender != 0) { + DEBUG(3,printk("AWE32: p-> bend(%d) %d\n", voice, cp->bender)); + /* (819200: 1 semitone) ==> (4096: 12 semitones) */ + offset += cp->bender * cp->bender_range / 2400; + } + + /* add initial pitch correction */ + if (FX_ON(&cp->fx_layer[vp->layer], AWE_FX_INIT_PITCH)) + offset += cp->fx_layer[vp->layer].val[AWE_FX_INIT_PITCH]; + else if (FX_ON(&cp->fx, AWE_FX_INIT_PITCH)) + offset += cp->fx.val[AWE_FX_INIT_PITCH]; + + /* 0xe000: root pitch */ + vp->apitch = 0xe000 + ap->rate_offset + offset; + DEBUG(4,printk("AWE32: p-> sum aofs=%x, rate_ofs=%d\n", vp->apitch, ap->rate_offset)); + if (vp->apitch > 0xffff) + vp->apitch = 0xffff; + if (vp->apitch < 0) + vp->apitch = 0; +} + + +#ifdef AWE_HAS_GUS_COMPATIBILITY +/* calculate MIDI key and semitone from the specified frequency */ +static void +awe_calc_pitch_from_freq(int voice, int freq) +{ + voice_info *vp = &voices[voice]; + awe_voice_info *ap; + FX_Rec *fx = &voices[voice].cinfo->fx; + FX_Rec *fx_lay = NULL; + int offset; + int note; + + if (voices[voice].layer < MAX_LAYERS) + fx_lay = &voices[voice].cinfo->fx_layer[voices[voice].layer]; + + /* search voice information */ + if ((ap = vp->sample) == NULL) + return; + if (ap->index == 0) { + DEBUG(3,printk("AWE32: set sample (%d)\n", ap->sample)); + if (awe_set_sample((awe_voice_list*)ap) == 0) + return; + } + note = freq_to_note(freq); + offset = (note - ap->root * 100 + ap->tune) * 4096 / 1200; + offset = (offset * ap->scaleTuning) / 100; + if (fx_lay && FX_ON(fx_lay, AWE_FX_INIT_PITCH)) + offset += fx_lay->val[AWE_FX_INIT_PITCH]; + else if (FX_ON(fx, AWE_FX_INIT_PITCH)) + offset += fx->val[AWE_FX_INIT_PITCH]; + vp->apitch = 0xe000 + ap->rate_offset + offset; + if (vp->apitch > 0xffff) + vp->apitch = 0xffff; + if (vp->apitch < 0) + vp->apitch = 0; +} +#endif /* AWE_HAS_GUS_COMPATIBILITY */ + + +/* + * calculate volume attenuation + * + * Voice volume is controlled by volume attenuation parameter. + * So volume becomes maximum when avol is 0 (no attenuation), and + * minimum when 255 (-96dB or silence). + */ + +static int vol_table[128] = { + 255,111,95,86,79,74,70,66,63,61,58,56,54,52,50,49, + 47,46,45,43,42,41,40,39,38,37,36,35,34,34,33,32, + 31,31,30,29,29,28,27,27,26,26,25,24,24,23,23,22, + 22,21,21,21,20,20,19,19,18,18,18,17,17,16,16,16, + 15,15,15,14,14,14,13,13,13,12,12,12,11,11,11,10, + 10,10,10,9,9,9,8,8,8,8,7,7,7,7,6,6, + 6,6,5,5,5,5,5,4,4,4,4,3,3,3,3,3, + 2,2,2,2,2,1,1,1,1,1,0,0,0,0,0,0, +}; + +/* tables for volume->attenuation calculation */ +static unsigned char voltab1[128] = { + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x2b, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, + 0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a, + 0x19, 0x19, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14, + 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10, + 0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0e, 0x0d, + 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0a, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static unsigned char voltab2[128] = { + 0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x2a, + 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x24, 0x23, 0x22, 0x21, + 0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1c, 0x1b, 0x1a, + 0x1a, 0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15, 0x15, + 0x14, 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x10, + 0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, + 0x0d, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0b, 0x0a, 0x0a, + 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static unsigned char expressiontab[128] = { + 0x7f, 0x6c, 0x62, 0x5a, 0x54, 0x50, 0x4b, 0x48, 0x45, 0x42, + 0x40, 0x3d, 0x3b, 0x39, 0x38, 0x36, 0x34, 0x33, 0x31, 0x30, + 0x2f, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, + 0x24, 0x24, 0x23, 0x22, 0x21, 0x21, 0x20, 0x1f, 0x1e, 0x1e, + 0x1d, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a, 0x1a, 0x19, 0x18, 0x18, + 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x15, 0x14, 0x14, 0x13, + 0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10, 0x10, 0x0f, 0x0f, + 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, + 0x0b, 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06, + 0x06, 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, + 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static void +awe_calc_volume(int voice) +{ + voice_info *vp = &voices[voice]; + awe_voice_info *ap; + awe_chan_info *cp = voices[voice].cinfo; + int vol; + + /* search voice information */ + if ((ap = vp->sample) == NULL) + return; + + ap = vp->sample; + if (ap->index == 0) { + DEBUG(3,printk("AWE32: set sample (%d)\n", ap->sample)); + if (awe_set_sample((awe_voice_list*)ap) == 0) + return; + } + + if (ctrls[AWE_MD_NEW_VOLUME_CALC]) { + int main_vol = cp->main_vol * ap->amplitude / 127; + limitvalue(vp->velocity, 0, 127); + limitvalue(main_vol, 0, 127); + limitvalue(cp->expression_vol, 0, 127); + + vol = voltab1[main_vol] + voltab2[vp->velocity]; + vol = (vol * 8) / 3; + vol += ap->attenuation; + if (cp->expression_vol < 127) + vol += ((0x100 - vol) * expressiontab[cp->expression_vol])/128; + vol += atten_offset; + if (atten_relative) + vol += ctrls[AWE_MD_ZERO_ATTEN]; + limitvalue(vol, 0, 255); + vp->avol = vol; + + } else { + /* 0 - 127 */ + vol = (vp->velocity * cp->main_vol * cp->expression_vol) / (127*127); + vol = vol * ap->amplitude / 127; + + if (vol < 0) vol = 0; + if (vol > 127) vol = 127; + + /* calc to attenuation */ + vol = vol_table[vol]; + vol += (int)ap->attenuation; + vol += atten_offset; + if (atten_relative) + vol += ctrls[AWE_MD_ZERO_ATTEN]; + if (vol > 255) vol = 255; + + vp->avol = vol; + } + if (cp->bank != AWE_DRUM_BANK && ((awe_voice_parm_block*)(&ap->parm))->volatk < 0x7d) { + int atten; + if (vp->velocity < 70) atten = 70; + else atten = vp->velocity; + vp->acutoff = (atten * ap->parm.cutoff + 0xa0) >> 7; + } else { + vp->acutoff = ap->parm.cutoff; + } + DEBUG(3,printk("AWE32: [-- voice(%d) vol=%x]\n", voice, vol)); +} + +/* change master volume */ +static void +awe_change_master_volume(short val) +{ + limitvalue(val, 0, 127); + atten_offset = vol_table[val]; + atten_relative = TRUE; + awe_update_volume(); +} + +/* update volumes of all available channels */ +static void awe_update_volume(void) +{ + int i; + for (i = 0; i < awe_max_voices; i++) + awe_set_voice_vol(i, TRUE); +} + +/* set sostenuto on */ +static void awe_sostenuto_on(int voice, int forced) +{ + if (IS_NO_EFFECT(voice) && !forced) return; + voices[voice].sostenuto = 127; +} + + +/* drop sustain */ +static void awe_sustain_off(int voice, int forced) +{ + if (voices[voice].state == AWE_ST_SUSTAINED) { + awe_note_off(voice); + awe_fx_init(voices[voice].ch); + awe_voice_init(voice, FALSE); + } +} + + +/* terminate and initialize voice */ +static void awe_terminate_and_init(int voice, int forced) +{ + awe_terminate(voice); + awe_fx_init(voices[voice].ch); + awe_voice_init(voice, TRUE); +} + + +/* + * synth operation routines + */ + +#define AWE_VOICE_KEY(v) (0x8000 | (v)) +#define AWE_CHAN_KEY(c,n) (((c) << 8) | ((n) + 1)) +#define KEY_CHAN_MATCH(key,c) (((key) >> 8) == (c)) + +/* initialize the voice */ +static void +awe_voice_init(int voice, int init_all) +{ + voice_info *vp = &voices[voice]; + + /* reset voice search key */ + if (playing_mode == AWE_PLAY_DIRECT) + vp->key = AWE_VOICE_KEY(voice); + else + vp->key = 0; + + /* clear voice mapping */ + voice_alloc->map[voice] = 0; + + /* touch the timing flag */ + vp->time = current_alloc_time; + + /* initialize other parameters if necessary */ + if (init_all) { + vp->note = -1; + vp->velocity = 0; + vp->sostenuto = 0; + + vp->sample = NULL; + vp->cinfo = &channels[voice]; + vp->ch = voice; + vp->state = AWE_ST_OFF; + + /* emu8000 parameters */ + vp->apitch = 0; + vp->avol = 255; + vp->apan = -1; + } +} + +/* clear effects */ +static void awe_fx_init(int ch) +{ + if (SINGLE_LAYER_MODE() && !ctrls[AWE_MD_KEEP_EFFECT]) { + memset(&channels[ch].fx, 0, sizeof(channels[ch].fx)); + memset(&channels[ch].fx_layer, 0, sizeof(&channels[ch].fx_layer)); + } +} + +/* initialize channel info */ +static void awe_channel_init(int ch, int init_all) +{ + awe_chan_info *cp = &channels[ch]; + cp->channel = ch; + if (init_all) { + cp->panning = 0; /* zero center */ + cp->bender_range = 200; /* sense * 100 */ + cp->main_vol = 127; + if (MULTI_LAYER_MODE() && IS_DRUM_CHANNEL(ch)) { + cp->instr = ctrls[AWE_MD_DEF_DRUM]; + cp->bank = AWE_DRUM_BANK; + } else { + cp->instr = ctrls[AWE_MD_DEF_PRESET]; + cp->bank = ctrls[AWE_MD_DEF_BANK]; + } + } + + cp->bender = 0; /* zero tune skew */ + cp->expression_vol = 127; + cp->chan_press = 0; + cp->sustained = 0; + + if (! ctrls[AWE_MD_KEEP_EFFECT]) { + memset(&cp->fx, 0, sizeof(cp->fx)); + memset(&cp->fx_layer, 0, sizeof(cp->fx_layer)); + } +} + + +/* change the voice parameters; voice = channel */ +static void awe_voice_change(int voice, fx_affect_func func) +{ + int i; + switch (playing_mode) { + case AWE_PLAY_DIRECT: + func(voice, FALSE); + break; + case AWE_PLAY_INDIRECT: + for (i = 0; i < awe_max_voices; i++) + if (voices[i].key == AWE_VOICE_KEY(voice)) + func(i, FALSE); + break; + default: + for (i = 0; i < awe_max_voices; i++) + if (KEY_CHAN_MATCH(voices[i].key, voice)) + func(i, FALSE); + break; + } +} + + +/* + * device open / close + */ + +/* open device: + * reset status of all voices, and clear sample position flag + */ +static int +awe_open(int dev, int mode) +{ + if (awe_busy) + return -EBUSY; + + awe_busy = TRUE; + + /* set default mode */ + awe_init_ctrl_parms(FALSE); + atten_relative = TRUE; + atten_offset = 0; + drum_flags = DEFAULT_DRUM_FLAGS; + playing_mode = AWE_PLAY_INDIRECT; + + /* reset voices & channels */ + awe_reset(dev); + + patch_opened = 0; + + return 0; +} + + +/* close device: + * reset all voices again (terminate sounds) + */ +static void +awe_close(int dev) +{ + awe_reset(dev); + awe_busy = FALSE; +} + + +/* set miscellaneous mode parameters + */ +static void +awe_init_ctrl_parms(int init_all) +{ + int i; + for (i = 0; i < AWE_MD_END; i++) { + if (init_all || ctrl_parms[i].init_each_time) + ctrls[i] = ctrl_parms[i].value; + } +} + + +/* sequencer I/O control: + */ +static int +awe_ioctl(int dev, unsigned int cmd, void __user *arg) +{ + switch (cmd) { + case SNDCTL_SYNTH_INFO: + if (playing_mode == AWE_PLAY_DIRECT) + awe_info.nr_voices = awe_max_voices; + else + awe_info.nr_voices = AWE_MAX_CHANNELS; + if (copy_to_user(arg, &awe_info, sizeof(awe_info))) + return -EFAULT; + return 0; + break; + + case SNDCTL_SEQ_RESETSAMPLES: + awe_reset(dev); + awe_reset_samples(); + return 0; + break; + + case SNDCTL_SEQ_PERCMODE: + /* what's this? */ + return 0; + break; + + case SNDCTL_SYNTH_MEMAVL: + return memsize - awe_free_mem_ptr() * 2; + break; + + default: + printk(KERN_WARNING "AWE32: unsupported ioctl %d\n", cmd); + return -EINVAL; + break; + } +} + + +static int voice_in_range(int voice) +{ + if (playing_mode == AWE_PLAY_DIRECT) { + if (voice < 0 || voice >= awe_max_voices) + return FALSE; + } else { + if (voice < 0 || voice >= AWE_MAX_CHANNELS) + return FALSE; + } + return TRUE; +} + +static void release_voice(int voice, int do_sustain) +{ + if (IS_NO_SOUND(voice)) + return; + if (do_sustain && (voices[voice].cinfo->sustained == 127 || + voices[voice].sostenuto == 127)) + voices[voice].state = AWE_ST_SUSTAINED; + else { + awe_note_off(voice); + awe_fx_init(voices[voice].ch); + awe_voice_init(voice, FALSE); + } +} + +/* release all notes */ +static void awe_note_off_all(int do_sustain) +{ + int i; + for (i = 0; i < awe_max_voices; i++) + release_voice(i, do_sustain); +} + +/* kill a voice: + * not terminate, just release the voice. + */ +static int +awe_kill_note(int dev, int voice, int note, int velocity) +{ + int i, v2, key; + + DEBUG(2,printk("AWE32: [off(%d) nt=%d vl=%d]\n", voice, note, velocity)); + if (! voice_in_range(voice)) + return -EINVAL; + + switch (playing_mode) { + case AWE_PLAY_DIRECT: + case AWE_PLAY_INDIRECT: + key = AWE_VOICE_KEY(voice); + break; + + case AWE_PLAY_MULTI2: + v2 = voice_alloc->map[voice] >> 8; + voice_alloc->map[voice] = 0; + voice = v2; + if (voice < 0 || voice >= AWE_MAX_CHANNELS) + return -EINVAL; + /* continue to below */ + default: + key = AWE_CHAN_KEY(voice, note); + break; + } + + for (i = 0; i < awe_max_voices; i++) { + if (voices[i].key == key) + release_voice(i, TRUE); + } + return 0; +} + + +static void start_or_volume_change(int voice, int velocity) +{ + voices[voice].velocity = velocity; + awe_calc_volume(voice); + if (voices[voice].state == AWE_ST_STANDBY) + awe_note_on(voice); + else if (voices[voice].state == AWE_ST_ON) + awe_set_volume(voice, FALSE); +} + +static void set_and_start_voice(int voice, int state) +{ + /* calculate pitch & volume parameters */ + voices[voice].state = state; + awe_calc_pitch(voice); + awe_calc_volume(voice); + if (state == AWE_ST_ON) + awe_note_on(voice); +} + +/* start a voice: + * if note is 255, identical with aftertouch function. + * Otherwise, start a voice with specified not and volume. + */ +static int +awe_start_note(int dev, int voice, int note, int velocity) +{ + int i, key, state, volonly; + + DEBUG(2,printk("AWE32: [on(%d) nt=%d vl=%d]\n", voice, note, velocity)); + if (! voice_in_range(voice)) + return -EINVAL; + + if (velocity == 0) + state = AWE_ST_STANDBY; /* stand by for playing */ + else + state = AWE_ST_ON; /* really play */ + volonly = FALSE; + + switch (playing_mode) { + case AWE_PLAY_DIRECT: + case AWE_PLAY_INDIRECT: + key = AWE_VOICE_KEY(voice); + if (note == 255) + volonly = TRUE; + break; + + case AWE_PLAY_MULTI2: + voice = voice_alloc->map[voice] >> 8; + if (voice < 0 || voice >= AWE_MAX_CHANNELS) + return -EINVAL; + /* continue to below */ + default: + if (note >= 128) { /* key volume mode */ + note -= 128; + volonly = TRUE; + } + key = AWE_CHAN_KEY(voice, note); + break; + } + + /* dynamic volume change */ + if (volonly) { + for (i = 0; i < awe_max_voices; i++) { + if (voices[i].key == key) + start_or_volume_change(i, velocity); + } + return 0; + } + + /* if the same note still playing, stop it */ + if (playing_mode != AWE_PLAY_DIRECT || ctrls[AWE_MD_EXCLUSIVE_SOUND]) { + for (i = 0; i < awe_max_voices; i++) + if (voices[i].key == key) { + if (voices[i].state == AWE_ST_ON) { + awe_note_off(i); + awe_voice_init(i, FALSE); + } else if (voices[i].state == AWE_ST_STANDBY) + awe_voice_init(i, TRUE); + } + } + + /* allocate voices */ + if (playing_mode == AWE_PLAY_DIRECT) + awe_alloc_one_voice(voice, note, velocity); + else + awe_alloc_multi_voices(voice, note, velocity, key); + + /* turn off other voices exlusively (for drums) */ + for (i = 0; i < awe_max_voices; i++) + if (voices[i].key == key) + awe_exclusive_off(i); + + /* set up pitch and volume parameters */ + for (i = 0; i < awe_max_voices; i++) { + if (voices[i].key == key && voices[i].state == AWE_ST_OFF) + set_and_start_voice(i, state); + } + + return 0; +} + + +/* calculate hash key */ +static int +awe_search_key(int bank, int preset, int note) +{ + unsigned int key; + +#if 1 /* new hash table */ + if (bank == AWE_DRUM_BANK) + key = preset + note + 128; + else + key = bank + preset; +#else + key = preset; +#endif + key %= AWE_MAX_PRESETS; + + return (int)key; +} + + +/* search instrument from hash table */ +static awe_voice_list * +awe_search_instr(int bank, int preset, int note) +{ + awe_voice_list *p; + int key, key2; + + key = awe_search_key(bank, preset, note); + for (p = preset_table[key]; p; p = p->next_bank) { + if (p->instr == preset && p->bank == bank) + return p; + } + key2 = awe_search_key(bank, preset, 0); /* search default */ + if (key == key2) + return NULL; + for (p = preset_table[key2]; p; p = p->next_bank) { + if (p->instr == preset && p->bank == bank) + return p; + } + return NULL; +} + + +/* assign the instrument to a voice */ +static int +awe_set_instr_2(int dev, int voice, int instr_no) +{ + if (playing_mode == AWE_PLAY_MULTI2) { + voice = voice_alloc->map[voice] >> 8; + if (voice < 0 || voice >= AWE_MAX_CHANNELS) + return -EINVAL; + } + return awe_set_instr(dev, voice, instr_no); +} + +/* assign the instrument to a channel; voice is the channel number */ +static int +awe_set_instr(int dev, int voice, int instr_no) +{ + awe_chan_info *cinfo; + + if (! voice_in_range(voice)) + return -EINVAL; + + if (instr_no < 0 || instr_no >= AWE_MAX_PRESETS) + return -EINVAL; + + cinfo = &channels[voice]; + cinfo->instr = instr_no; + DEBUG(2,printk("AWE32: [program(%d) %d]\n", voice, instr_no)); + + return 0; +} + + +/* reset all voices; terminate sounds and initialize parameters */ +static void +awe_reset(int dev) +{ + int i; + current_alloc_time = 0; + /* don't turn off voice 31 and 32. they are used also for FM voices */ + for (i = 0; i < awe_max_voices; i++) { + awe_terminate(i); + awe_voice_init(i, TRUE); + } + for (i = 0; i < AWE_MAX_CHANNELS; i++) + awe_channel_init(i, TRUE); + for (i = 0; i < 16; i++) { + awe_operations.chn_info[i].controllers[CTL_MAIN_VOLUME] = 127; + awe_operations.chn_info[i].controllers[CTL_EXPRESSION] = 127; + } + awe_init_fm(); + awe_tweak(); +} + + +/* hardware specific control: + * GUS specific and AWE32 specific controls are available. + */ +static void +awe_hw_control(int dev, unsigned char *event) +{ + int cmd = event[2]; + if (cmd & _AWE_MODE_FLAG) + awe_hw_awe_control(dev, cmd & _AWE_MODE_VALUE_MASK, event); +#ifdef AWE_HAS_GUS_COMPATIBILITY + else + awe_hw_gus_control(dev, cmd & _AWE_MODE_VALUE_MASK, event); +#endif +} + + +#ifdef AWE_HAS_GUS_COMPATIBILITY + +/* GUS compatible controls */ +static void +awe_hw_gus_control(int dev, int cmd, unsigned char *event) +{ + int voice, i, key; + unsigned short p1; + short p2; + int plong; + + if (MULTI_LAYER_MODE()) + return; + if (cmd == _GUS_NUMVOICES) + return; + + voice = event[3]; + if (! voice_in_range(voice)) + return; + + p1 = *(unsigned short *) &event[4]; + p2 = *(short *) &event[6]; + plong = *(int*) &event[4]; + + switch (cmd) { + case _GUS_VOICESAMPLE: + awe_set_instr(dev, voice, p1); + return; + + case _GUS_VOICEBALA: + /* 0 to 15 --> -128 to 127 */ + awe_panning(dev, voice, ((int)p1 << 4) - 128); + return; + + case _GUS_VOICEVOL: + case _GUS_VOICEVOL2: + /* not supported yet */ + return; + + case _GUS_RAMPRANGE: + case _GUS_RAMPRATE: + case _GUS_RAMPMODE: + case _GUS_RAMPON: + case _GUS_RAMPOFF: + /* volume ramping not supported */ + return; + + case _GUS_VOLUME_SCALE: + return; + + case _GUS_VOICE_POS: + FX_SET(&channels[voice].fx, AWE_FX_SAMPLE_START, + (short)(plong & 0x7fff)); + FX_SET(&channels[voice].fx, AWE_FX_COARSE_SAMPLE_START, + (plong >> 15) & 0xffff); + return; + } + + key = AWE_VOICE_KEY(voice); + for (i = 0; i < awe_max_voices; i++) { + if (voices[i].key == key) { + switch (cmd) { + case _GUS_VOICEON: + awe_note_on(i); + break; + + case _GUS_VOICEOFF: + awe_terminate(i); + awe_fx_init(voices[i].ch); + awe_voice_init(i, TRUE); + break; + + case _GUS_VOICEFADE: + awe_note_off(i); + awe_fx_init(voices[i].ch); + awe_voice_init(i, FALSE); + break; + + case _GUS_VOICEFREQ: + awe_calc_pitch_from_freq(i, plong); + break; + } + } + } +} + +#endif /* gus_compat */ + + +/* AWE32 specific controls */ +static void +awe_hw_awe_control(int dev, int cmd, unsigned char *event) +{ + int voice; + unsigned short p1; + short p2; + int i; + + voice = event[3]; + if (! voice_in_range(voice)) + return; + + if (playing_mode == AWE_PLAY_MULTI2) { + voice = voice_alloc->map[voice] >> 8; + if (voice < 0 || voice >= AWE_MAX_CHANNELS) + return; + } + + p1 = *(unsigned short *) &event[4]; + p2 = *(short *) &event[6]; + + switch (cmd) { + case _AWE_DEBUG_MODE: + ctrls[AWE_MD_DEBUG_MODE] = p1; + printk(KERN_DEBUG "AWE32: debug mode = %d\n", ctrls[AWE_MD_DEBUG_MODE]); + break; + case _AWE_REVERB_MODE: + ctrls[AWE_MD_REVERB_MODE] = p1; + awe_update_reverb_mode(); + break; + + case _AWE_CHORUS_MODE: + ctrls[AWE_MD_CHORUS_MODE] = p1; + awe_update_chorus_mode(); + break; + + case _AWE_REMOVE_LAST_SAMPLES: + DEBUG(0,printk("AWE32: remove last samples\n")); + awe_reset(0); + if (locked_sf_id > 0) + awe_remove_samples(locked_sf_id); + break; + + case _AWE_INITIALIZE_CHIP: + awe_initialize(); + break; + + case _AWE_SEND_EFFECT: + i = -1; + if (p1 >= 0x100) { + i = (p1 >> 8); + if (i < 0 || i >= MAX_LAYERS) + break; + } + awe_send_effect(voice, i, p1, p2); + break; + + case _AWE_RESET_CHANNEL: + awe_channel_init(voice, !p1); + break; + + case _AWE_TERMINATE_ALL: + awe_reset(0); + break; + + case _AWE_TERMINATE_CHANNEL: + awe_voice_change(voice, awe_terminate_and_init); + break; + + case _AWE_RELEASE_ALL: + awe_note_off_all(FALSE); + break; + case _AWE_NOTEOFF_ALL: + awe_note_off_all(TRUE); + break; + + case _AWE_INITIAL_VOLUME: + DEBUG(0,printk("AWE32: init attenuation %d\n", p1)); + atten_relative = (char)p2; + atten_offset = (short)p1; + awe_update_volume(); + break; + + case _AWE_CHN_PRESSURE: + channels[voice].chan_press = p1; + awe_modwheel_change(voice, p1); + break; + + case _AWE_CHANNEL_MODE: + DEBUG(0,printk("AWE32: channel mode = %d\n", p1)); + playing_mode = p1; + awe_reset(0); + break; + + case _AWE_DRUM_CHANNELS: + DEBUG(0,printk("AWE32: drum flags = %x\n", p1)); + drum_flags = *(unsigned int*)&event[4]; + break; + + case _AWE_MISC_MODE: + DEBUG(0,printk("AWE32: ctrl parms = %d %d\n", p1, p2)); + if (p1 > AWE_MD_VERSION && p1 < AWE_MD_END) { + ctrls[p1] = p2; + if (ctrl_parms[p1].update) + ctrl_parms[p1].update(); + } + break; + + case _AWE_EQUALIZER: + ctrls[AWE_MD_BASS_LEVEL] = p1; + ctrls[AWE_MD_TREBLE_LEVEL] = p2; + awe_update_equalizer(); + break; + + default: + DEBUG(0,printk("AWE32: hw control cmd=%d voice=%d\n", cmd, voice)); + break; + } +} + + +/* change effects */ +static void +awe_send_effect(int voice, int layer, int type, int val) +{ + awe_chan_info *cinfo; + FX_Rec *fx; + int mode; + + cinfo = &channels[voice]; + if (layer >= 0 && layer < MAX_LAYERS) + fx = &cinfo->fx_layer[layer]; + else + fx = &cinfo->fx; + + if (type & 0x40) + mode = FX_FLAG_OFF; + else if (type & 0x80) + mode = FX_FLAG_ADD; + else + mode = FX_FLAG_SET; + type &= 0x3f; + + if (type >= 0 && type < AWE_FX_END) { + DEBUG(2,printk("AWE32: effects (%d) %d %d\n", voice, type, val)); + if (mode == FX_FLAG_SET) + FX_SET(fx, type, val); + else if (mode == FX_FLAG_ADD) + FX_ADD(fx, type, val); + else + FX_UNSET(fx, type); + if (mode != FX_FLAG_OFF && parm_defs[type].realtime) { + DEBUG(2,printk("AWE32: fx_realtime (%d)\n", voice)); + awe_voice_change(voice, parm_defs[type].realtime); + } + } +} + + +/* change modulation wheel; voice is already mapped on multi2 mode */ +static void +awe_modwheel_change(int voice, int value) +{ + int i; + awe_chan_info *cinfo; + + cinfo = &channels[voice]; + i = value * ctrls[AWE_MD_MOD_SENSE] / 1200; + FX_ADD(&cinfo->fx, AWE_FX_LFO1_PITCH, i); + awe_voice_change(voice, awe_fx_fmmod); + FX_ADD(&cinfo->fx, AWE_FX_LFO2_PITCH, i); + awe_voice_change(voice, awe_fx_fm2frq2); +} + + +/* voice pressure change */ +static void +awe_aftertouch(int dev, int voice, int pressure) +{ + int note; + + DEBUG(2,printk("AWE32: [after(%d) %d]\n", voice, pressure)); + if (! voice_in_range(voice)) + return; + + switch (playing_mode) { + case AWE_PLAY_DIRECT: + case AWE_PLAY_INDIRECT: + awe_start_note(dev, voice, 255, pressure); + break; + case AWE_PLAY_MULTI2: + note = (voice_alloc->map[voice] & 0xff) - 1; + awe_key_pressure(dev, voice, note + 0x80, pressure); + break; + } +} + + +/* voice control change */ +static void +awe_controller(int dev, int voice, int ctrl_num, int value) +{ + awe_chan_info *cinfo; + + if (! voice_in_range(voice)) + return; + + if (playing_mode == AWE_PLAY_MULTI2) { + voice = voice_alloc->map[voice] >> 8; + if (voice < 0 || voice >= AWE_MAX_CHANNELS) + return; + } + + cinfo = &channels[voice]; + + switch (ctrl_num) { + case CTL_BANK_SELECT: /* MIDI control #0 */ + DEBUG(2,printk("AWE32: [bank(%d) %d]\n", voice, value)); + if (MULTI_LAYER_MODE() && IS_DRUM_CHANNEL(voice) && + !ctrls[AWE_MD_TOGGLE_DRUM_BANK]) + break; + if (value < 0 || value > 255) + break; + cinfo->bank = value; + if (cinfo->bank == AWE_DRUM_BANK) + DRUM_CHANNEL_ON(cinfo->channel); + else + DRUM_CHANNEL_OFF(cinfo->channel); + awe_set_instr(dev, voice, cinfo->instr); + break; + + case CTL_MODWHEEL: /* MIDI control #1 */ + DEBUG(2,printk("AWE32: [modwheel(%d) %d]\n", voice, value)); + awe_modwheel_change(voice, value); + break; + + case CTRL_PITCH_BENDER: /* SEQ1 V2 contorl */ + DEBUG(2,printk("AWE32: [bend(%d) %d]\n", voice, value)); + /* zero centered */ + cinfo->bender = value; + awe_voice_change(voice, awe_set_voice_pitch); + break; + + case CTRL_PITCH_BENDER_RANGE: /* SEQ1 V2 control */ + DEBUG(2,printk("AWE32: [range(%d) %d]\n", voice, value)); + /* value = sense x 100 */ + cinfo->bender_range = value; + /* no audible pitch change yet.. */ + break; + + case CTL_EXPRESSION: /* MIDI control #11 */ + if (SINGLE_LAYER_MODE()) + value /= 128; + case CTRL_EXPRESSION: /* SEQ1 V2 control */ + DEBUG(2,printk("AWE32: [expr(%d) %d]\n", voice, value)); + /* 0 - 127 */ + cinfo->expression_vol = value; + awe_voice_change(voice, awe_set_voice_vol); + break; + + case CTL_PAN: /* MIDI control #10 */ + DEBUG(2,printk("AWE32: [pan(%d) %d]\n", voice, value)); + /* (0-127) -> signed 8bit */ + cinfo->panning = value * 2 - 128; + if (ctrls[AWE_MD_REALTIME_PAN]) + awe_voice_change(voice, awe_set_pan); + break; + + case CTL_MAIN_VOLUME: /* MIDI control #7 */ + if (SINGLE_LAYER_MODE()) + value = (value * 100) / 16383; + case CTRL_MAIN_VOLUME: /* SEQ1 V2 control */ + DEBUG(2,printk("AWE32: [mainvol(%d) %d]\n", voice, value)); + /* 0 - 127 */ + cinfo->main_vol = value; + awe_voice_change(voice, awe_set_voice_vol); + break; + + case CTL_EXT_EFF_DEPTH: /* reverb effects: 0-127 */ + DEBUG(2,printk("AWE32: [reverb(%d) %d]\n", voice, value)); + FX_SET(&cinfo->fx, AWE_FX_REVERB, value * 2); + break; + + case CTL_CHORUS_DEPTH: /* chorus effects: 0-127 */ + DEBUG(2,printk("AWE32: [chorus(%d) %d]\n", voice, value)); + FX_SET(&cinfo->fx, AWE_FX_CHORUS, value * 2); + break; + + case 120: /* all sounds off */ + awe_note_off_all(FALSE); + break; + case 123: /* all notes off */ + awe_note_off_all(TRUE); + break; + + case CTL_SUSTAIN: /* MIDI control #64 */ + cinfo->sustained = value; + if (value != 127) + awe_voice_change(voice, awe_sustain_off); + break; + + case CTL_SOSTENUTO: /* MIDI control #66 */ + if (value == 127) + awe_voice_change(voice, awe_sostenuto_on); + else + awe_voice_change(voice, awe_sustain_off); + break; + + default: + DEBUG(0,printk("AWE32: [control(%d) ctrl=%d val=%d]\n", + voice, ctrl_num, value)); + break; + } +} + + +/* voice pan change (value = -128 - 127) */ +static void +awe_panning(int dev, int voice, int value) +{ + awe_chan_info *cinfo; + + if (! voice_in_range(voice)) + return; + + if (playing_mode == AWE_PLAY_MULTI2) { + voice = voice_alloc->map[voice] >> 8; + if (voice < 0 || voice >= AWE_MAX_CHANNELS) + return; + } + + cinfo = &channels[voice]; + cinfo->panning = value; + DEBUG(2,printk("AWE32: [pan(%d) %d]\n", voice, cinfo->panning)); + if (ctrls[AWE_MD_REALTIME_PAN]) + awe_voice_change(voice, awe_set_pan); +} + + +/* volume mode change */ +static void +awe_volume_method(int dev, int mode) +{ + /* not impremented */ + DEBUG(0,printk("AWE32: [volmethod mode=%d]\n", mode)); +} + + +/* pitch wheel change: 0-16384 */ +static void +awe_bender(int dev, int voice, int value) +{ + awe_chan_info *cinfo; + + if (! voice_in_range(voice)) + return; + + if (playing_mode == AWE_PLAY_MULTI2) { + voice = voice_alloc->map[voice] >> 8; + if (voice < 0 || voice >= AWE_MAX_CHANNELS) + return; + } + + /* convert to zero centered value */ + cinfo = &channels[voice]; + cinfo->bender = value - 8192; + DEBUG(2,printk("AWE32: [bend(%d) %d]\n", voice, cinfo->bender)); + awe_voice_change(voice, awe_set_voice_pitch); +} + + +/* + * load a sound patch: + * three types of patches are accepted: AWE, GUS, and SYSEX. + */ + +static int +awe_load_patch(int dev, int format, const char __user *addr, + int offs, int count, int pmgr_flag) +{ + awe_patch_info patch; + int rc = 0; + +#ifdef AWE_HAS_GUS_COMPATIBILITY + if (format == GUS_PATCH) { + return awe_load_guspatch(addr, offs, count, pmgr_flag); + } else +#endif + if (format == SYSEX_PATCH) { + /* no system exclusive message supported yet */ + return 0; + } else if (format != AWE_PATCH) { + printk(KERN_WARNING "AWE32 Error: Invalid patch format (key) 0x%x\n", format); + return -EINVAL; + } + + if (count < AWE_PATCH_INFO_SIZE) { + printk(KERN_WARNING "AWE32 Error: Patch header too short\n"); + return -EINVAL; + } + if (copy_from_user(((char*)&patch) + offs, addr + offs, + AWE_PATCH_INFO_SIZE - offs)) + return -EFAULT; + + count -= AWE_PATCH_INFO_SIZE; + if (count < patch.len) { + printk(KERN_WARNING "AWE32: sample: Patch record too short (%d<%d)\n", + count, patch.len); + return -EINVAL; + } + + switch (patch.type) { + case AWE_LOAD_INFO: + rc = awe_load_info(&patch, addr, count); + break; + case AWE_LOAD_DATA: + rc = awe_load_data(&patch, addr, count); + break; + case AWE_OPEN_PATCH: + rc = awe_open_patch(&patch, addr, count); + break; + case AWE_CLOSE_PATCH: + rc = awe_close_patch(&patch, addr, count); + break; + case AWE_UNLOAD_PATCH: + rc = awe_unload_patch(&patch, addr, count); + break; + case AWE_REPLACE_DATA: + rc = awe_replace_data(&patch, addr, count); + break; + case AWE_MAP_PRESET: + rc = awe_load_map(&patch, addr, count); + break; + /* case AWE_PROBE_INFO: + rc = awe_probe_info(&patch, addr, count); + break;*/ + case AWE_PROBE_DATA: + rc = awe_probe_data(&patch, addr, count); + break; + case AWE_REMOVE_INFO: + rc = awe_remove_info(&patch, addr, count); + break; + case AWE_LOAD_CHORUS_FX: + rc = awe_load_chorus_fx(&patch, addr, count); + break; + case AWE_LOAD_REVERB_FX: + rc = awe_load_reverb_fx(&patch, addr, count); + break; + + default: + printk(KERN_WARNING "AWE32 Error: unknown patch format type %d\n", + patch.type); + rc = -EINVAL; + } + + return rc; +} + + +/* create an sf list record */ +static int +awe_create_sf(int type, char *name) +{ + sf_list *rec; + + /* terminate sounds */ + awe_reset(0); + rec = (sf_list *)kmalloc(sizeof(*rec), GFP_KERNEL); + if (rec == NULL) + return 1; /* no memory */ + rec->sf_id = current_sf_id + 1; + rec->type = type; + if (/*current_sf_id == 0 ||*/ (type & AWE_PAT_LOCKED) != 0) + locked_sf_id = current_sf_id + 1; + rec->num_info = awe_free_info(); + rec->num_sample = awe_free_sample(); + rec->mem_ptr = awe_free_mem_ptr(); + rec->infos = rec->last_infos = NULL; + rec->samples = rec->last_samples = NULL; + + /* add to linked-list */ + rec->next = NULL; + rec->prev = sftail; + if (sftail) + sftail->next = rec; + else + sfhead = rec; + sftail = rec; + current_sf_id++; + +#ifdef AWE_ALLOW_SAMPLE_SHARING + rec->shared = NULL; + if (name) + memcpy(rec->name, name, AWE_PATCH_NAME_LEN); + else + strcpy(rec->name, "*TEMPORARY*"); + if (current_sf_id > 1 && name && (type & AWE_PAT_SHARED) != 0) { + /* is the current font really a shared font? */ + if (is_shared_sf(rec->name)) { + /* check if the shared font is already installed */ + sf_list *p; + for (p = rec->prev; p; p = p->prev) { + if (is_identical_name(rec->name, p)) { + rec->shared = p; + break; + } + } + } + } +#endif /* allow sharing */ + + return 0; +} + + +#ifdef AWE_ALLOW_SAMPLE_SHARING + +/* check if the given name is a valid shared name */ +#define ASC_TO_KEY(c) ((c) - 'A' + 1) +static int is_shared_sf(unsigned char *name) +{ + static unsigned char id_head[4] = { + ASC_TO_KEY('A'), ASC_TO_KEY('W'), ASC_TO_KEY('E'), + AWE_MAJOR_VERSION, + }; + if (memcmp(name, id_head, 4) == 0) + return TRUE; + return FALSE; +} + +/* check if the given name matches to the existing list */ +static int is_identical_name(unsigned char *name, sf_list *p) +{ + char *id = p->name; + if (is_shared_sf(id) && memcmp(id, name, AWE_PATCH_NAME_LEN) == 0) + return TRUE; + return FALSE; +} + +/* check if the given voice info exists */ +static int info_duplicated(sf_list *sf, awe_voice_list *rec) +{ + /* search for all sharing lists */ + for (; sf; sf = sf->shared) { + awe_voice_list *p; + for (p = sf->infos; p; p = p->next) { + if (p->type == V_ST_NORMAL && + p->bank == rec->bank && + p->instr == rec->instr && + p->v.low == rec->v.low && + p->v.high == rec->v.high && + p->v.sample == rec->v.sample) + return TRUE; + } + } + return FALSE; +} + +#endif /* AWE_ALLOW_SAMPLE_SHARING */ + + +/* free sf_list record */ +/* linked-list in this function is not cared */ +static void +awe_free_sf(sf_list *sf) +{ + if (sf->infos) { + awe_voice_list *p, *next; + for (p = sf->infos; p; p = next) { + next = p->next; + kfree(p); + } + } + if (sf->samples) { + awe_sample_list *p, *next; + for (p = sf->samples; p; p = next) { + next = p->next; + kfree(p); + } + } + kfree(sf); +} + + +/* open patch; create sf list and set opened flag */ +static int +awe_open_patch(awe_patch_info *patch, const char __user *addr, int count) +{ + awe_open_parm parm; + int shared; + + if (copy_from_user(&parm, addr + AWE_PATCH_INFO_SIZE, sizeof(parm))) + return -EFAULT; + shared = FALSE; + +#ifdef AWE_ALLOW_SAMPLE_SHARING + if (sftail && (parm.type & AWE_PAT_SHARED) != 0) { + /* is the previous font the same font? */ + if (is_identical_name(parm.name, sftail)) { + /* then append to the previous */ + shared = TRUE; + awe_reset(0); + if (parm.type & AWE_PAT_LOCKED) + locked_sf_id = current_sf_id; + } + } +#endif /* allow sharing */ + if (! shared) { + if (awe_create_sf(parm.type, parm.name)) { + printk(KERN_ERR "AWE32: can't open: failed to alloc new list\n"); + return -ENOMEM; + } + } + patch_opened = TRUE; + return current_sf_id; +} + +/* check if the patch is already opened */ +static sf_list * +check_patch_opened(int type, char *name) +{ + if (! patch_opened) { + if (awe_create_sf(type, name)) { + printk(KERN_ERR "AWE32: failed to alloc new list\n"); + return NULL; + } + patch_opened = TRUE; + return sftail; + } + return sftail; +} + +/* close the patch; if no voice is loaded, remove the patch */ +static int +awe_close_patch(awe_patch_info *patch, const char __user *addr, int count) +{ + if (patch_opened && sftail) { + /* if no voice is loaded, release the current patch */ + if (sftail->infos == NULL) { + awe_reset(0); + awe_remove_samples(current_sf_id - 1); + } + } + patch_opened = 0; + return 0; +} + + +/* remove the latest patch */ +static int +awe_unload_patch(awe_patch_info *patch, const char __user *addr, int count) +{ + if (current_sf_id > 0 && current_sf_id > locked_sf_id) { + awe_reset(0); + awe_remove_samples(current_sf_id - 1); + } + return 0; +} + +/* allocate voice info list records */ +static awe_voice_list * +alloc_new_info(void) +{ + awe_voice_list *newlist; + + newlist = (awe_voice_list *)kmalloc(sizeof(*newlist), GFP_KERNEL); + if (newlist == NULL) { + printk(KERN_ERR "AWE32: can't alloc info table\n"); + return NULL; + } + return newlist; +} + +/* allocate sample info list records */ +static awe_sample_list * +alloc_new_sample(void) +{ + awe_sample_list *newlist; + + newlist = (awe_sample_list *)kmalloc(sizeof(*newlist), GFP_KERNEL); + if (newlist == NULL) { + printk(KERN_ERR "AWE32: can't alloc sample table\n"); + return NULL; + } + return newlist; +} + +/* load voice map */ +static int +awe_load_map(awe_patch_info *patch, const char __user *addr, int count) +{ + awe_voice_map map; + awe_voice_list *rec, *p; + sf_list *sf; + + /* get the link info */ + if (count < sizeof(map)) { + printk(KERN_WARNING "AWE32 Error: invalid patch info length\n"); + return -EINVAL; + } + if (copy_from_user(&map, addr + AWE_PATCH_INFO_SIZE, sizeof(map))) + return -EFAULT; + + /* check if the identical mapping already exists */ + p = awe_search_instr(map.map_bank, map.map_instr, map.map_key); + for (; p; p = p->next_instr) { + if (p->type == V_ST_MAPPED && + p->v.start == map.src_instr && + p->v.end == map.src_bank && + p->v.fixkey == map.src_key) + return 0; /* already present! */ + } + + if ((sf = check_patch_opened(AWE_PAT_TYPE_MAP, NULL)) == NULL) + return -ENOMEM; + + if ((rec = alloc_new_info()) == NULL) + return -ENOMEM; + + rec->bank = map.map_bank; + rec->instr = map.map_instr; + rec->type = V_ST_MAPPED; + rec->disabled = FALSE; + awe_init_voice_info(&rec->v); + if (map.map_key >= 0) { + rec->v.low = map.map_key; + rec->v.high = map.map_key; + } + rec->v.start = map.src_instr; + rec->v.end = map.src_bank; + rec->v.fixkey = map.src_key; + add_sf_info(sf, rec); + add_info_list(rec); + + return 0; +} + +#if 0 +/* probe preset in the current list -- nothing to be loaded */ +static int +awe_probe_info(awe_patch_info *patch, const char __user *addr, int count) +{ +#ifdef AWE_ALLOW_SAMPLE_SHARING + awe_voice_map map; + awe_voice_list *p; + + if (! patch_opened) + return -EINVAL; + + /* get the link info */ + if (count < sizeof(map)) { + printk(KERN_WARNING "AWE32 Error: invalid patch info length\n"); + return -EINVAL; + } + if (copy_from_user(&map, addr + AWE_PATCH_INFO_SIZE, sizeof(map))) + return -EFAULT; + + /* check if the identical mapping already exists */ + if (sftail == NULL) + return -EINVAL; + p = awe_search_instr(map.src_bank, map.src_instr, map.src_key); + for (; p; p = p->next_instr) { + if (p->type == V_ST_NORMAL && + is_identical_holder(p->holder, sftail) && + p->v.low <= map.src_key && + p->v.high >= map.src_key) + return 0; /* already present! */ + } +#endif /* allow sharing */ + return -EINVAL; +} +#endif + +/* probe sample in the current list -- nothing to be loaded */ +static int +awe_probe_data(awe_patch_info *patch, const char __user *addr, int count) +{ +#ifdef AWE_ALLOW_SAMPLE_SHARING + if (! patch_opened) + return -EINVAL; + + /* search the specified sample by optarg */ + if (search_sample_index(sftail, patch->optarg) != NULL) + return 0; +#endif /* allow sharing */ + return -EINVAL; +} + + +/* remove the present instrument layers */ +static int +remove_info(sf_list *sf, int bank, int instr) +{ + awe_voice_list *prev, *next, *p; + int removed = 0; + + prev = NULL; + for (p = sf->infos; p; p = next) { + next = p->next; + if (p->type == V_ST_NORMAL && + p->bank == bank && p->instr == instr) { + /* remove this layer */ + if (prev) + prev->next = next; + else + sf->infos = next; + if (p == sf->last_infos) + sf->last_infos = prev; + sf->num_info--; + removed++; + kfree(p); + } else + prev = p; + } + if (removed) + rebuild_preset_list(); + return removed; +} + +/* load voice information data */ +static int +awe_load_info(awe_patch_info *patch, const char __user *addr, int count) +{ + int offset; + awe_voice_rec_hdr hdr; + int i; + int total_size; + sf_list *sf; + awe_voice_list *rec; + + if (count < AWE_VOICE_REC_SIZE) { + printk(KERN_WARNING "AWE32 Error: invalid patch info length\n"); + return -EINVAL; + } + + offset = AWE_PATCH_INFO_SIZE; + if (copy_from_user((char*)&hdr, addr + offset, AWE_VOICE_REC_SIZE)) + return -EFAULT; + offset += AWE_VOICE_REC_SIZE; + + if (hdr.nvoices <= 0 || hdr.nvoices >= 100) { + printk(KERN_WARNING "AWE32 Error: Invalid voice number %d\n", hdr.nvoices); + return -EINVAL; + } + total_size = AWE_VOICE_REC_SIZE + AWE_VOICE_INFO_SIZE * hdr.nvoices; + if (count < total_size) { + printk(KERN_WARNING "AWE32 Error: patch length(%d) is smaller than nvoices(%d)\n", + count, hdr.nvoices); + return -EINVAL; + } + + if ((sf = check_patch_opened(AWE_PAT_TYPE_MISC, NULL)) == NULL) + return -ENOMEM; + + switch (hdr.write_mode) { + case AWE_WR_EXCLUSIVE: + /* exclusive mode - if the instrument already exists, + return error */ + for (rec = sf->infos; rec; rec = rec->next) { + if (rec->type == V_ST_NORMAL && + rec->bank == hdr.bank && + rec->instr == hdr.instr) + return -EINVAL; + } + break; + case AWE_WR_REPLACE: + /* replace mode - remove the instrument if it already exists */ + remove_info(sf, hdr.bank, hdr.instr); + break; + } + + /* append new layers */ + for (i = 0; i < hdr.nvoices; i++) { + rec = alloc_new_info(); + if (rec == NULL) + return -ENOMEM; + + rec->bank = hdr.bank; + rec->instr = hdr.instr; + rec->type = V_ST_NORMAL; + rec->disabled = FALSE; + + /* copy awe_voice_info parameters */ + if (copy_from_user(&rec->v, addr + offset, AWE_VOICE_INFO_SIZE)) { + kfree(rec); + return -EFAULT; + } + offset += AWE_VOICE_INFO_SIZE; +#ifdef AWE_ALLOW_SAMPLE_SHARING + if (sf && sf->shared) { + if (info_duplicated(sf, rec)) { + kfree(rec); + continue; + } + } +#endif /* allow sharing */ + if (rec->v.mode & AWE_MODE_INIT_PARM) + awe_init_voice_parm(&rec->v.parm); + add_sf_info(sf, rec); + awe_set_sample(rec); + add_info_list(rec); + } + + return 0; +} + + +/* remove instrument layers */ +static int +awe_remove_info(awe_patch_info *patch, const char __user *addr, int count) +{ + unsigned char bank, instr; + sf_list *sf; + + if (! patch_opened || (sf = sftail) == NULL) { + printk(KERN_WARNING "AWE32: remove_info: patch not opened\n"); + return -EINVAL; + } + + bank = ((unsigned short)patch->optarg >> 8) & 0xff; + instr = (unsigned short)patch->optarg & 0xff; + if (! remove_info(sf, bank, instr)) + return -EINVAL; + return 0; +} + + +/* load wave sample data */ +static int +awe_load_data(awe_patch_info *patch, const char __user *addr, int count) +{ + int offset, size; + int rc; + awe_sample_info tmprec; + awe_sample_list *rec; + sf_list *sf; + + if ((sf = check_patch_opened(AWE_PAT_TYPE_MISC, NULL)) == NULL) + return -ENOMEM; + + size = (count - AWE_SAMPLE_INFO_SIZE) / 2; + offset = AWE_PATCH_INFO_SIZE; + if (copy_from_user(&tmprec, addr + offset, AWE_SAMPLE_INFO_SIZE)) + return -EFAULT; + offset += AWE_SAMPLE_INFO_SIZE; + if (size != tmprec.size) { + printk(KERN_WARNING "AWE32: load: sample size differed (%d != %d)\n", + tmprec.size, size); + return -EINVAL; + } + + if (search_sample_index(sf, tmprec.sample) != NULL) { +#ifdef AWE_ALLOW_SAMPLE_SHARING + /* if shared sample, skip this data */ + if (sf->type & AWE_PAT_SHARED) + return 0; +#endif /* allow sharing */ + DEBUG(1,printk("AWE32: sample data %d already present\n", tmprec.sample)); + return -EINVAL; + } + + if ((rec = alloc_new_sample()) == NULL) + return -ENOMEM; + + memcpy(&rec->v, &tmprec, sizeof(tmprec)); + + if (rec->v.size > 0) { + if ((rc = awe_write_wave_data(addr, offset, rec, -1)) < 0) { + kfree(rec); + return rc; + } + sf->mem_ptr += rc; + } + + add_sf_sample(sf, rec); + return 0; +} + + +/* replace wave sample data */ +static int +awe_replace_data(awe_patch_info *patch, const char __user *addr, int count) +{ + int offset; + int size; + int rc; + int channels; + awe_sample_info cursmp; + int save_mem_ptr; + sf_list *sf; + awe_sample_list *rec; + + if (! patch_opened || (sf = sftail) == NULL) { + printk(KERN_WARNING "AWE32: replace: patch not opened\n"); + return -EINVAL; + } + + size = (count - AWE_SAMPLE_INFO_SIZE) / 2; + offset = AWE_PATCH_INFO_SIZE; + if (copy_from_user(&cursmp, addr + offset, AWE_SAMPLE_INFO_SIZE)) + return -EFAULT; + offset += AWE_SAMPLE_INFO_SIZE; + if (cursmp.size == 0 || size != cursmp.size) { + printk(KERN_WARNING "AWE32: replace: invalid sample size (%d!=%d)\n", + cursmp.size, size); + return -EINVAL; + } + channels = patch->optarg; + if (channels <= 0 || channels > AWE_NORMAL_VOICES) { + printk(KERN_WARNING "AWE32: replace: invalid channels %d\n", channels); + return -EINVAL; + } + + for (rec = sf->samples; rec; rec = rec->next) { + if (rec->v.sample == cursmp.sample) + break; + } + if (rec == NULL) { + printk(KERN_WARNING "AWE32: replace: cannot find existing sample data %d\n", + cursmp.sample); + return -EINVAL; + } + + if (rec->v.size != cursmp.size) { + printk(KERN_WARNING "AWE32: replace: exiting size differed (%d!=%d)\n", + rec->v.size, cursmp.size); + return -EINVAL; + } + + save_mem_ptr = awe_free_mem_ptr(); + sftail->mem_ptr = rec->v.start - awe_mem_start; + memcpy(&rec->v, &cursmp, sizeof(cursmp)); + rec->v.sf_id = current_sf_id; + if ((rc = awe_write_wave_data(addr, offset, rec, channels)) < 0) + return rc; + sftail->mem_ptr = save_mem_ptr; + + return 0; +} + + +/*----------------------------------------------------------------*/ + +static const char __user *readbuf_addr; +static int readbuf_offs; +static int readbuf_flags; + +/* initialize read buffer */ +static int +readbuf_init(const char __user *addr, int offset, awe_sample_info *sp) +{ + readbuf_addr = addr; + readbuf_offs = offset; + readbuf_flags = sp->mode_flags; + return 0; +} + +/* read directly from user buffer */ +static unsigned short +readbuf_word(int pos) +{ + unsigned short c; + /* read from user buffer */ + if (readbuf_flags & AWE_SAMPLE_8BITS) { + unsigned char cc; + get_user(cc, (unsigned char __user *)(readbuf_addr + readbuf_offs + pos)); + c = (unsigned short)cc << 8; /* convert 8bit -> 16bit */ + } else { + get_user(c, (unsigned short __user *)(readbuf_addr + readbuf_offs + pos * 2)); + } + if (readbuf_flags & AWE_SAMPLE_UNSIGNED) + c ^= 0x8000; /* unsigned -> signed */ + return c; +} + +#define readbuf_word_cache readbuf_word +#define readbuf_end() /**/ + +/*----------------------------------------------------------------*/ + +#define BLANK_LOOP_START 8 +#define BLANK_LOOP_END 40 +#define BLANK_LOOP_SIZE 48 + +/* loading onto memory - return the actual written size */ +static int +awe_write_wave_data(const char __user *addr, int offset, awe_sample_list *list, int channels) +{ + int i, truesize, dram_offset; + awe_sample_info *sp = &list->v; + int rc; + + /* be sure loop points start < end */ + if (sp->loopstart > sp->loopend) { + int tmp = sp->loopstart; + sp->loopstart = sp->loopend; + sp->loopend = tmp; + } + + /* compute true data size to be loaded */ + truesize = sp->size; + if (sp->mode_flags & (AWE_SAMPLE_BIDIR_LOOP|AWE_SAMPLE_REVERSE_LOOP)) + truesize += sp->loopend - sp->loopstart; + if (sp->mode_flags & AWE_SAMPLE_NO_BLANK) + truesize += BLANK_LOOP_SIZE; + if (awe_free_mem_ptr() + truesize >= memsize/2) { + DEBUG(-1,printk("AWE32 Error: Sample memory full\n")); + return -ENOSPC; + } + + /* recalculate address offset */ + sp->end -= sp->start; + sp->loopstart -= sp->start; + sp->loopend -= sp->start; + + dram_offset = awe_free_mem_ptr() + awe_mem_start; + sp->start = dram_offset; + sp->end += dram_offset; + sp->loopstart += dram_offset; + sp->loopend += dram_offset; + + /* set the total size (store onto obsolete checksum value) */ + if (sp->size == 0) + sp->checksum = 0; + else + sp->checksum = truesize; + + if ((rc = awe_open_dram_for_write(dram_offset, channels)) != 0) + return rc; + + if (readbuf_init(addr, offset, sp) < 0) + return -ENOSPC; + + for (i = 0; i < sp->size; i++) { + unsigned short c; + c = readbuf_word(i); + awe_write_dram(c); + if (i == sp->loopend && + (sp->mode_flags & (AWE_SAMPLE_BIDIR_LOOP|AWE_SAMPLE_REVERSE_LOOP))) { + int looplen = sp->loopend - sp->loopstart; + /* copy reverse loop */ + int k; + for (k = 1; k <= looplen; k++) { + c = readbuf_word_cache(i - k); + awe_write_dram(c); + } + if (sp->mode_flags & AWE_SAMPLE_BIDIR_LOOP) { + sp->end += looplen; + } else { + sp->start += looplen; + sp->end += looplen; + } + } + } + readbuf_end(); + + /* if no blank loop is attached in the sample, add it */ + if (sp->mode_flags & AWE_SAMPLE_NO_BLANK) { + for (i = 0; i < BLANK_LOOP_SIZE; i++) + awe_write_dram(0); + if (sp->mode_flags & AWE_SAMPLE_SINGLESHOT) { + sp->loopstart = sp->end + BLANK_LOOP_START; + sp->loopend = sp->end + BLANK_LOOP_END; + } + } + + awe_close_dram(); + + /* initialize FM */ + awe_init_fm(); + + return truesize; +} + + +/*----------------------------------------------------------------*/ + +#ifdef AWE_HAS_GUS_COMPATIBILITY + +/* calculate GUS envelope time: + * is this correct? i have no idea.. + */ +static int +calc_gus_envelope_time(int rate, int start, int end) +{ + int r, p, t; + r = (3 - ((rate >> 6) & 3)) * 3; + p = rate & 0x3f; + t = end - start; + if (t < 0) t = -t; + if (13 > r) + t = t << (13 - r); + else + t = t >> (r - 13); + return (t * 10) / (p * 441); +} + +#define calc_gus_sustain(val) (0x7f - vol_table[(val)/2]) +#define calc_gus_attenuation(val) vol_table[(val)/2] + +/* load GUS patch */ +static int +awe_load_guspatch(const char __user *addr, int offs, int size, int pmgr_flag) +{ + struct patch_info patch; + awe_voice_info *rec; + awe_sample_info *smp; + awe_voice_list *vrec; + awe_sample_list *smprec; + int sizeof_patch; + int note, rc; + sf_list *sf; + + sizeof_patch = (int)((long)&patch.data[0] - (long)&patch); /* header size */ + if (size < sizeof_patch) { + printk(KERN_WARNING "AWE32 Error: Patch header too short\n"); + return -EINVAL; + } + if (copy_from_user(((char*)&patch) + offs, addr + offs, sizeof_patch - offs)) + return -EFAULT; + size -= sizeof_patch; + if (size < patch.len) { + printk(KERN_WARNING "AWE32 Error: Patch record too short (%d<%d)\n", + size, patch.len); + return -EINVAL; + } + if ((sf = check_patch_opened(AWE_PAT_TYPE_GUS, NULL)) == NULL) + return -ENOMEM; + if ((smprec = alloc_new_sample()) == NULL) + return -ENOMEM; + if ((vrec = alloc_new_info()) == NULL) { + kfree(smprec); + return -ENOMEM; + } + + smp = &smprec->v; + smp->sample = sf->num_sample; + smp->start = 0; + smp->end = patch.len; + smp->loopstart = patch.loop_start; + smp->loopend = patch.loop_end; + smp->size = patch.len; + + /* set up mode flags */ + smp->mode_flags = 0; + if (!(patch.mode & WAVE_16_BITS)) + smp->mode_flags |= AWE_SAMPLE_8BITS; + if (patch.mode & WAVE_UNSIGNED) + smp->mode_flags |= AWE_SAMPLE_UNSIGNED; + smp->mode_flags |= AWE_SAMPLE_NO_BLANK; + if (!(patch.mode & (WAVE_LOOPING|WAVE_BIDIR_LOOP|WAVE_LOOP_BACK))) + smp->mode_flags |= AWE_SAMPLE_SINGLESHOT; + if (patch.mode & WAVE_BIDIR_LOOP) + smp->mode_flags |= AWE_SAMPLE_BIDIR_LOOP; + if (patch.mode & WAVE_LOOP_BACK) + smp->mode_flags |= AWE_SAMPLE_REVERSE_LOOP; + + DEBUG(0,printk("AWE32: [sample %d mode %x]\n", patch.instr_no, smp->mode_flags)); + if (patch.mode & WAVE_16_BITS) { + /* convert to word offsets */ + smp->size /= 2; + smp->end /= 2; + smp->loopstart /= 2; + smp->loopend /= 2; + } + smp->checksum_flag = 0; + smp->checksum = 0; + + if ((rc = awe_write_wave_data(addr, sizeof_patch, smprec, -1)) < 0) + return rc; + sf->mem_ptr += rc; + add_sf_sample(sf, smprec); + + /* set up voice info */ + rec = &vrec->v; + awe_init_voice_info(rec); + rec->sample = sf->num_info; /* the last sample */ + rec->rate_offset = calc_rate_offset(patch.base_freq); + note = freq_to_note(patch.base_note); + rec->root = note / 100; + rec->tune = -(note % 100); + rec->low = freq_to_note(patch.low_note) / 100; + rec->high = freq_to_note(patch.high_note) / 100; + DEBUG(1,printk("AWE32: [gus base offset=%d, note=%d, range=%d-%d(%d-%d)]\n", + rec->rate_offset, note, + rec->low, rec->high, + patch.low_note, patch.high_note)); + /* panning position; -128 - 127 => 0-127 */ + rec->pan = (patch.panning + 128) / 2; + + /* detuning is ignored */ + /* 6points volume envelope */ + if (patch.mode & WAVE_ENVELOPES) { + int attack, hold, decay, release; + attack = calc_gus_envelope_time + (patch.env_rate[0], 0, patch.env_offset[0]); + hold = calc_gus_envelope_time + (patch.env_rate[1], patch.env_offset[0], + patch.env_offset[1]); + decay = calc_gus_envelope_time + (patch.env_rate[2], patch.env_offset[1], + patch.env_offset[2]); + release = calc_gus_envelope_time + (patch.env_rate[3], patch.env_offset[1], + patch.env_offset[4]); + release += calc_gus_envelope_time + (patch.env_rate[4], patch.env_offset[3], + patch.env_offset[4]); + release += calc_gus_envelope_time + (patch.env_rate[5], patch.env_offset[4], + patch.env_offset[5]); + rec->parm.volatkhld = (calc_parm_hold(hold) << 8) | + calc_parm_attack(attack); + rec->parm.voldcysus = (calc_gus_sustain(patch.env_offset[2]) << 8) | + calc_parm_decay(decay); + rec->parm.volrelease = 0x8000 | calc_parm_decay(release); + DEBUG(2,printk("AWE32: [gusenv atk=%d, hld=%d, dcy=%d, rel=%d]\n", attack, hold, decay, release)); + rec->attenuation = calc_gus_attenuation(patch.env_offset[0]); + } + + /* tremolo effect */ + if (patch.mode & WAVE_TREMOLO) { + int rate = (patch.tremolo_rate * 1000 / 38) / 42; + rec->parm.tremfrq = ((patch.tremolo_depth / 2) << 8) | rate; + DEBUG(2,printk("AWE32: [gusenv tremolo rate=%d, dep=%d, tremfrq=%x]\n", + patch.tremolo_rate, patch.tremolo_depth, + rec->parm.tremfrq)); + } + /* vibrato effect */ + if (patch.mode & WAVE_VIBRATO) { + int rate = (patch.vibrato_rate * 1000 / 38) / 42; + rec->parm.fm2frq2 = ((patch.vibrato_depth / 6) << 8) | rate; + DEBUG(2,printk("AWE32: [gusenv vibrato rate=%d, dep=%d, tremfrq=%x]\n", + patch.tremolo_rate, patch.tremolo_depth, + rec->parm.tremfrq)); + } + + /* scale_freq, scale_factor, volume, and fractions not implemented */ + + /* append to the tail of the list */ + vrec->bank = ctrls[AWE_MD_GUS_BANK]; + vrec->instr = patch.instr_no; + vrec->disabled = FALSE; + vrec->type = V_ST_NORMAL; + + add_sf_info(sf, vrec); + add_info_list(vrec); + + /* set the voice index */ + awe_set_sample(vrec); + + return 0; +} + +#endif /* AWE_HAS_GUS_COMPATIBILITY */ + +/* + * sample and voice list handlers + */ + +/* append this to the current sf list */ +static void add_sf_info(sf_list *sf, awe_voice_list *rec) +{ + if (sf == NULL) + return; + rec->holder = sf; + rec->v.sf_id = sf->sf_id; + if (sf->last_infos) + sf->last_infos->next = rec; + else + sf->infos = rec; + sf->last_infos = rec; + rec->next = NULL; + sf->num_info++; +} + +/* prepend this sample to sf list */ +static void add_sf_sample(sf_list *sf, awe_sample_list *rec) +{ + if (sf == NULL) + return; + rec->holder = sf; + rec->v.sf_id = sf->sf_id; + if (sf->last_samples) + sf->last_samples->next = rec; + else + sf->samples = rec; + sf->last_samples = rec; + rec->next = NULL; + sf->num_sample++; +} + +/* purge the old records which don't belong with the same file id */ +static void purge_old_list(awe_voice_list *rec, awe_voice_list *next) +{ + rec->next_instr = next; + if (rec->bank == AWE_DRUM_BANK) { + /* remove samples with the same note range */ + awe_voice_list *cur, *prev = rec; + int low = rec->v.low; + int high = rec->v.high; + for (cur = next; cur; cur = cur->next_instr) { + if (cur->v.low == low && + cur->v.high == high && + ! is_identical_holder(cur->holder, rec->holder)) + prev->next_instr = cur->next_instr; + else + prev = cur; + } + } else { + if (! is_identical_holder(next->holder, rec->holder)) + /* remove all samples */ + rec->next_instr = NULL; + } +} + +/* prepend to top of the preset table */ +static void add_info_list(awe_voice_list *rec) +{ + awe_voice_list *prev, *cur; + int key; + + if (rec->disabled) + return; + + key = awe_search_key(rec->bank, rec->instr, rec->v.low); + prev = NULL; + for (cur = preset_table[key]; cur; cur = cur->next_bank) { + /* search the first record with the same bank number */ + if (cur->instr == rec->instr && cur->bank == rec->bank) { + /* replace the list with the new record */ + rec->next_bank = cur->next_bank; + if (prev) + prev->next_bank = rec; + else + preset_table[key] = rec; + purge_old_list(rec, cur); + return; + } + prev = cur; + } + + /* this is the first bank record.. just add this */ + rec->next_instr = NULL; + rec->next_bank = preset_table[key]; + preset_table[key] = rec; +} + +/* remove samples later than the specified sf_id */ +static void +awe_remove_samples(int sf_id) +{ + sf_list *p, *prev; + + if (sf_id <= 0) { + awe_reset_samples(); + return; + } + /* already removed? */ + if (current_sf_id <= sf_id) + return; + + for (p = sftail; p; p = prev) { + if (p->sf_id <= sf_id) + break; + prev = p->prev; + awe_free_sf(p); + } + sftail = p; + if (sftail) { + sf_id = sftail->sf_id; + sftail->next = NULL; + } else { + sf_id = 0; + sfhead = NULL; + } + current_sf_id = sf_id; + if (locked_sf_id > sf_id) + locked_sf_id = sf_id; + + rebuild_preset_list(); +} + +/* rebuild preset search list */ +static void rebuild_preset_list(void) +{ + sf_list *p; + awe_voice_list *rec; + + memset(preset_table, 0, sizeof(preset_table)); + + for (p = sfhead; p; p = p->next) { + for (rec = p->infos; rec; rec = rec->next) + add_info_list(rec); + } +} + +/* compare the given sf_id pair */ +static int is_identical_holder(sf_list *sf1, sf_list *sf2) +{ + if (sf1 == NULL || sf2 == NULL) + return FALSE; + if (sf1 == sf2) + return TRUE; +#ifdef AWE_ALLOW_SAMPLE_SHARING + { + /* compare with the sharing id */ + sf_list *p; + int counter = 0; + if (sf1->sf_id < sf2->sf_id) { /* make sure id1 > id2 */ + sf_list *tmp; tmp = sf1; sf1 = sf2; sf2 = tmp; + } + for (p = sf1->shared; p; p = p->shared) { + if (counter++ > current_sf_id) + break; /* strange sharing loop.. quit */ + if (p == sf2) + return TRUE; + } + } +#endif /* allow sharing */ + return FALSE; +} + +/* search the sample index matching with the given sample id */ +static awe_sample_list * +search_sample_index(sf_list *sf, int sample) +{ + awe_sample_list *p; +#ifdef AWE_ALLOW_SAMPLE_SHARING + int counter = 0; + while (sf) { + for (p = sf->samples; p; p = p->next) { + if (p->v.sample == sample) + return p; + } + sf = sf->shared; + if (counter++ > current_sf_id) + break; /* strange sharing loop.. quit */ + } +#else + if (sf) { + for (p = sf->samples; p; p = p->next) { + if (p->v.sample == sample) + return p; + } + } +#endif + return NULL; +} + +/* search the specified sample */ +/* non-zero = found */ +static short +awe_set_sample(awe_voice_list *rec) +{ + awe_sample_list *smp; + awe_voice_info *vp = &rec->v; + + vp->index = 0; + if ((smp = search_sample_index(rec->holder, vp->sample)) == NULL) + return 0; + + /* set the actual sample offsets */ + vp->start += smp->v.start; + vp->end += smp->v.end; + vp->loopstart += smp->v.loopstart; + vp->loopend += smp->v.loopend; + /* copy mode flags */ + vp->mode = smp->v.mode_flags; + /* set flag */ + vp->index = 1; + + return 1; +} + + +/* + * voice allocation + */ + +/* look for all voices associated with the specified note & velocity */ +static int +awe_search_multi_voices(awe_voice_list *rec, int note, int velocity, + awe_voice_info **vlist) +{ + int nvoices; + + nvoices = 0; + for (; rec; rec = rec->next_instr) { + if (note >= rec->v.low && + note <= rec->v.high && + velocity >= rec->v.vellow && + velocity <= rec->v.velhigh) { + if (rec->type == V_ST_MAPPED) { + /* mapper */ + vlist[0] = &rec->v; + return -1; + } + vlist[nvoices++] = &rec->v; + if (nvoices >= AWE_MAX_VOICES) + break; + } + } + return nvoices; +} + +/* store the voice list from the specified note and velocity. + if the preset is mapped, seek for the destination preset, and rewrite + the note number if necessary. + */ +static int +really_alloc_voices(int bank, int instr, int *note, int velocity, awe_voice_info **vlist) +{ + int nvoices; + awe_voice_list *vrec; + int level = 0; + + for (;;) { + vrec = awe_search_instr(bank, instr, *note); + nvoices = awe_search_multi_voices(vrec, *note, velocity, vlist); + if (nvoices == 0) { + if (bank == AWE_DRUM_BANK) + /* search default drumset */ + vrec = awe_search_instr(bank, ctrls[AWE_MD_DEF_DRUM], *note); + else + /* search default preset */ + vrec = awe_search_instr(ctrls[AWE_MD_DEF_BANK], instr, *note); + nvoices = awe_search_multi_voices(vrec, *note, velocity, vlist); + } + if (nvoices == 0) { + if (bank == AWE_DRUM_BANK && ctrls[AWE_MD_DEF_DRUM] != 0) + /* search default drumset */ + vrec = awe_search_instr(bank, 0, *note); + else if (bank != AWE_DRUM_BANK && ctrls[AWE_MD_DEF_BANK] != 0) + /* search default preset */ + vrec = awe_search_instr(0, instr, *note); + nvoices = awe_search_multi_voices(vrec, *note, velocity, vlist); + } + if (nvoices < 0) { /* mapping */ + int key = vlist[0]->fixkey; + instr = vlist[0]->start; + bank = vlist[0]->end; + if (level++ > 5) { + printk(KERN_ERR "AWE32: too deep mapping level\n"); + return 0; + } + if (key >= 0) + *note = key; + } else + break; + } + + return nvoices; +} + +/* allocate voices corresponding note and velocity; supports multiple insts. */ +static void +awe_alloc_multi_voices(int ch, int note, int velocity, int key) +{ + int i, v, nvoices, bank; + awe_voice_info *vlist[AWE_MAX_VOICES]; + + if (MULTI_LAYER_MODE() && IS_DRUM_CHANNEL(ch)) + bank = AWE_DRUM_BANK; /* always search drumset */ + else + bank = channels[ch].bank; + + /* check the possible voices; note may be changeable if mapped */ + nvoices = really_alloc_voices(bank, channels[ch].instr, + ¬e, velocity, vlist); + + /* set the voices */ + current_alloc_time++; + for (i = 0; i < nvoices; i++) { + v = awe_clear_voice(); + voices[v].key = key; + voices[v].ch = ch; + voices[v].note = note; + voices[v].velocity = velocity; + voices[v].time = current_alloc_time; + voices[v].cinfo = &channels[ch]; + voices[v].sample = vlist[i]; + voices[v].state = AWE_ST_MARK; + voices[v].layer = nvoices - i - 1; /* in reverse order */ + } + + /* clear the mark in allocated voices */ + for (i = 0; i < awe_max_voices; i++) { + if (voices[i].state == AWE_ST_MARK) + voices[i].state = AWE_ST_OFF; + + } +} + + +/* search an empty voice. + if no empty voice is found, at least terminate a voice + */ +static int +awe_clear_voice(void) +{ + enum { + OFF=0, RELEASED, SUSTAINED, PLAYING, END + }; + struct voice_candidate_t { + int best; + int time; + int vtarget; + } candidate[END]; + int i, type, vtarget; + + vtarget = 0xffff; + for (type = OFF; type < END; type++) { + candidate[type].best = -1; + candidate[type].time = current_alloc_time + 1; + candidate[type].vtarget = vtarget; + } + + for (i = 0; i < awe_max_voices; i++) { + if (voices[i].state & AWE_ST_OFF) + type = OFF; + else if (voices[i].state & AWE_ST_RELEASED) + type = RELEASED; + else if (voices[i].state & AWE_ST_SUSTAINED) + type = SUSTAINED; + else if (voices[i].state & ~AWE_ST_MARK) + type = PLAYING; + else + continue; +#ifdef AWE_CHECK_VTARGET + /* get current volume */ + vtarget = (awe_peek_dw(AWE_VTFT(i)) >> 16) & 0xffff; +#endif + if (candidate[type].best < 0 || + vtarget < candidate[type].vtarget || + (vtarget == candidate[type].vtarget && + voices[i].time < candidate[type].time)) { + candidate[type].best = i; + candidate[type].time = voices[i].time; + candidate[type].vtarget = vtarget; + } + } + + for (type = OFF; type < END; type++) { + if ((i = candidate[type].best) >= 0) { + if (voices[i].state != AWE_ST_OFF) + awe_terminate(i); + awe_voice_init(i, TRUE); + return i; + } + } + return 0; +} + + +/* search sample for the specified note & velocity and set it on the voice; + * note that voice is the voice index (not channel index) + */ +static void +awe_alloc_one_voice(int voice, int note, int velocity) +{ + int ch, nvoices, bank; + awe_voice_info *vlist[AWE_MAX_VOICES]; + + ch = voices[voice].ch; + if (MULTI_LAYER_MODE() && IS_DRUM_CHANNEL(voice)) + bank = AWE_DRUM_BANK; /* always search drumset */ + else + bank = voices[voice].cinfo->bank; + + nvoices = really_alloc_voices(bank, voices[voice].cinfo->instr, + ¬e, velocity, vlist); + if (nvoices > 0) { + voices[voice].time = ++current_alloc_time; + voices[voice].sample = vlist[0]; /* use the first one */ + voices[voice].layer = 0; + voices[voice].note = note; + voices[voice].velocity = velocity; + } +} + + +/* + * sequencer2 functions + */ + +/* search an empty voice; used by sequencer2 */ +static int +awe_alloc(int dev, int chn, int note, struct voice_alloc_info *alloc) +{ + playing_mode = AWE_PLAY_MULTI2; + awe_info.nr_voices = AWE_MAX_CHANNELS; + return awe_clear_voice(); +} + + +/* set up voice; used by sequencer2 */ +static void +awe_setup_voice(int dev, int voice, int chn) +{ + struct channel_info *info; + if (synth_devs[dev] == NULL || + (info = &synth_devs[dev]->chn_info[chn]) == NULL) + return; + + if (voice < 0 || voice >= awe_max_voices) + return; + + DEBUG(2,printk("AWE32: [setup(%d) ch=%d]\n", voice, chn)); + channels[chn].expression_vol = info->controllers[CTL_EXPRESSION]; + channels[chn].main_vol = info->controllers[CTL_MAIN_VOLUME]; + channels[chn].panning = + info->controllers[CTL_PAN] * 2 - 128; /* signed 8bit */ + channels[chn].bender = info->bender_value; /* zero center */ + channels[chn].bank = info->controllers[CTL_BANK_SELECT]; + channels[chn].sustained = info->controllers[CTL_SUSTAIN]; + if (info->controllers[CTL_EXT_EFF_DEPTH]) { + FX_SET(&channels[chn].fx, AWE_FX_REVERB, + info->controllers[CTL_EXT_EFF_DEPTH] * 2); + } + if (info->controllers[CTL_CHORUS_DEPTH]) { + FX_SET(&channels[chn].fx, AWE_FX_CHORUS, + info->controllers[CTL_CHORUS_DEPTH] * 2); + } + awe_set_instr(dev, chn, info->pgm_num); +} + + +#ifdef CONFIG_AWE32_MIXER +/* + * AWE32 mixer device control + */ + +static int awe_mixer_ioctl(int dev, unsigned int cmd, void __user *arg); + +static int my_mixerdev = -1; + +static struct mixer_operations awe_mixer_operations = { + .owner = THIS_MODULE, + .id = "AWE", + .name = "AWE32 Equalizer", + .ioctl = awe_mixer_ioctl, +}; + +static void __init attach_mixer(void) +{ + if ((my_mixerdev = sound_alloc_mixerdev()) >= 0) { + mixer_devs[my_mixerdev] = &awe_mixer_operations; + } +} + +static void unload_mixer(void) +{ + if (my_mixerdev >= 0) + sound_unload_mixerdev(my_mixerdev); +} + +static int +awe_mixer_ioctl(int dev, unsigned int cmd, void __user * arg) +{ + int i, level, value; + + if (((cmd >> 8) & 0xff) != 'M') + return -EINVAL; + + if (get_user(level, (int __user *)arg)) + return -EFAULT; + level = ((level & 0xff) + (level >> 8)) / 2; + DEBUG(0,printk("AWEMix: cmd=%x val=%d\n", cmd & 0xff, level)); + + if (_SIOC_DIR(cmd) & _SIOC_WRITE) { + switch (cmd & 0xff) { + case SOUND_MIXER_BASS: + value = level * 12 / 100; + if (value >= 12) + value = 11; + ctrls[AWE_MD_BASS_LEVEL] = value; + awe_update_equalizer(); + break; + case SOUND_MIXER_TREBLE: + value = level * 12 / 100; + if (value >= 12) + value = 11; + ctrls[AWE_MD_TREBLE_LEVEL] = value; + awe_update_equalizer(); + break; + case SOUND_MIXER_VOLUME: + level = level * 127 / 100; + if (level >= 128) level = 127; + atten_relative = FALSE; + atten_offset = vol_table[level]; + awe_update_volume(); + break; + } + } + switch (cmd & 0xff) { + case SOUND_MIXER_BASS: + level = ctrls[AWE_MD_BASS_LEVEL] * 100 / 24; + level = (level << 8) | level; + break; + case SOUND_MIXER_TREBLE: + level = ctrls[AWE_MD_TREBLE_LEVEL] * 100 / 24; + level = (level << 8) | level; + break; + case SOUND_MIXER_VOLUME: + value = atten_offset; + if (atten_relative) + value += ctrls[AWE_MD_ZERO_ATTEN]; + for (i = 127; i > 0; i--) { + if (value <= vol_table[i]) + break; + } + level = i * 100 / 127; + level = (level << 8) | level; + break; + case SOUND_MIXER_DEVMASK: + level = SOUND_MASK_BASS|SOUND_MASK_TREBLE|SOUND_MASK_VOLUME; + break; + default: + level = 0; + break; + } + if (put_user(level, (int __user *)arg)) + return -EFAULT; + return level; +} +#endif /* CONFIG_AWE32_MIXER */ + + +/* + * initialization of Emu8000 + */ + +/* intiailize audio channels */ +static void +awe_init_audio(void) +{ + int ch; + + /* turn off envelope engines */ + for (ch = 0; ch < AWE_MAX_VOICES; ch++) { + awe_poke(AWE_DCYSUSV(ch), 0x80); + } + + /* reset all other parameters to zero */ + for (ch = 0; ch < AWE_MAX_VOICES; ch++) { + awe_poke(AWE_ENVVOL(ch), 0); + awe_poke(AWE_ENVVAL(ch), 0); + awe_poke(AWE_DCYSUS(ch), 0); + awe_poke(AWE_ATKHLDV(ch), 0); + awe_poke(AWE_LFO1VAL(ch), 0); + awe_poke(AWE_ATKHLD(ch), 0); + awe_poke(AWE_LFO2VAL(ch), 0); + awe_poke(AWE_IP(ch), 0); + awe_poke(AWE_IFATN(ch), 0); + awe_poke(AWE_PEFE(ch), 0); + awe_poke(AWE_FMMOD(ch), 0); + awe_poke(AWE_TREMFRQ(ch), 0); + awe_poke(AWE_FM2FRQ2(ch), 0); + awe_poke_dw(AWE_PTRX(ch), 0); + awe_poke_dw(AWE_VTFT(ch), 0); + awe_poke_dw(AWE_PSST(ch), 0); + awe_poke_dw(AWE_CSL(ch), 0); + awe_poke_dw(AWE_CCCA(ch), 0); + } + + for (ch = 0; ch < AWE_MAX_VOICES; ch++) { + awe_poke_dw(AWE_CPF(ch), 0); + awe_poke_dw(AWE_CVCF(ch), 0); + } +} + + +/* initialize DMA address */ +static void +awe_init_dma(void) +{ + awe_poke_dw(AWE_SMALR, 0); + awe_poke_dw(AWE_SMARR, 0); + awe_poke_dw(AWE_SMALW, 0); + awe_poke_dw(AWE_SMARW, 0); +} + + +/* initialization arrays; from ADIP */ + +static unsigned short init1[128] = { + 0x03ff, 0x0030, 0x07ff, 0x0130, 0x0bff, 0x0230, 0x0fff, 0x0330, + 0x13ff, 0x0430, 0x17ff, 0x0530, 0x1bff, 0x0630, 0x1fff, 0x0730, + 0x23ff, 0x0830, 0x27ff, 0x0930, 0x2bff, 0x0a30, 0x2fff, 0x0b30, + 0x33ff, 0x0c30, 0x37ff, 0x0d30, 0x3bff, 0x0e30, 0x3fff, 0x0f30, + + 0x43ff, 0x0030, 0x47ff, 0x0130, 0x4bff, 0x0230, 0x4fff, 0x0330, + 0x53ff, 0x0430, 0x57ff, 0x0530, 0x5bff, 0x0630, 0x5fff, 0x0730, + 0x63ff, 0x0830, 0x67ff, 0x0930, 0x6bff, 0x0a30, 0x6fff, 0x0b30, + 0x73ff, 0x0c30, 0x77ff, 0x0d30, 0x7bff, 0x0e30, 0x7fff, 0x0f30, + + 0x83ff, 0x0030, 0x87ff, 0x0130, 0x8bff, 0x0230, 0x8fff, 0x0330, + 0x93ff, 0x0430, 0x97ff, 0x0530, 0x9bff, 0x0630, 0x9fff, 0x0730, + 0xa3ff, 0x0830, 0xa7ff, 0x0930, 0xabff, 0x0a30, 0xafff, 0x0b30, + 0xb3ff, 0x0c30, 0xb7ff, 0x0d30, 0xbbff, 0x0e30, 0xbfff, 0x0f30, + + 0xc3ff, 0x0030, 0xc7ff, 0x0130, 0xcbff, 0x0230, 0xcfff, 0x0330, + 0xd3ff, 0x0430, 0xd7ff, 0x0530, 0xdbff, 0x0630, 0xdfff, 0x0730, + 0xe3ff, 0x0830, 0xe7ff, 0x0930, 0xebff, 0x0a30, 0xefff, 0x0b30, + 0xf3ff, 0x0c30, 0xf7ff, 0x0d30, 0xfbff, 0x0e30, 0xffff, 0x0f30, +}; + +static unsigned short init2[128] = { + 0x03ff, 0x8030, 0x07ff, 0x8130, 0x0bff, 0x8230, 0x0fff, 0x8330, + 0x13ff, 0x8430, 0x17ff, 0x8530, 0x1bff, 0x8630, 0x1fff, 0x8730, + 0x23ff, 0x8830, 0x27ff, 0x8930, 0x2bff, 0x8a30, 0x2fff, 0x8b30, + 0x33ff, 0x8c30, 0x37ff, 0x8d30, 0x3bff, 0x8e30, 0x3fff, 0x8f30, + + 0x43ff, 0x8030, 0x47ff, 0x8130, 0x4bff, 0x8230, 0x4fff, 0x8330, + 0x53ff, 0x8430, 0x57ff, 0x8530, 0x5bff, 0x8630, 0x5fff, 0x8730, + 0x63ff, 0x8830, 0x67ff, 0x8930, 0x6bff, 0x8a30, 0x6fff, 0x8b30, + 0x73ff, 0x8c30, 0x77ff, 0x8d30, 0x7bff, 0x8e30, 0x7fff, 0x8f30, + + 0x83ff, 0x8030, 0x87ff, 0x8130, 0x8bff, 0x8230, 0x8fff, 0x8330, + 0x93ff, 0x8430, 0x97ff, 0x8530, 0x9bff, 0x8630, 0x9fff, 0x8730, + 0xa3ff, 0x8830, 0xa7ff, 0x8930, 0xabff, 0x8a30, 0xafff, 0x8b30, + 0xb3ff, 0x8c30, 0xb7ff, 0x8d30, 0xbbff, 0x8e30, 0xbfff, 0x8f30, + + 0xc3ff, 0x8030, 0xc7ff, 0x8130, 0xcbff, 0x8230, 0xcfff, 0x8330, + 0xd3ff, 0x8430, 0xd7ff, 0x8530, 0xdbff, 0x8630, 0xdfff, 0x8730, + 0xe3ff, 0x8830, 0xe7ff, 0x8930, 0xebff, 0x8a30, 0xefff, 0x8b30, + 0xf3ff, 0x8c30, 0xf7ff, 0x8d30, 0xfbff, 0x8e30, 0xffff, 0x8f30, +}; + +static unsigned short init3[128] = { + 0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5, + 0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x8F7C, 0x167E, 0xF254, + 0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x8BAA, 0x1B6D, 0xF234, + 0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x86E7, 0x229E, 0xF224, + + 0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x87F6, 0x2C28, 0xF254, + 0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x8F02, 0x1341, 0xF264, + 0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x8FA9, 0x3EB5, 0xF294, + 0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0xC4C3, 0x3EBB, 0xC5C3, + + 0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x8671, 0x14FD, 0x8287, + 0x3EBC, 0xE610, 0x3EC8, 0x8C7B, 0x031A, 0x87E6, 0x3EC8, 0x86F7, + 0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x821F, 0x3ECA, 0x8386, + 0x3EC1, 0x8C03, 0x3EC9, 0x831E, 0x3ECA, 0x8C4C, 0x3EBF, 0x8C55, + + 0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x8EAD, 0x3EC8, 0xD308, + 0x3EC2, 0x8F7E, 0x3ECB, 0x8219, 0x3ECB, 0xD26E, 0x3EC5, 0x831F, + 0x3EC6, 0xC308, 0x3EC3, 0xB2FF, 0x3EC9, 0x8265, 0x3EC9, 0x8319, + 0x1342, 0xD36E, 0x3EC7, 0xB3FF, 0x0000, 0x8365, 0x1420, 0x9570, +}; + +static unsigned short init4[128] = { + 0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5, + 0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x0F7C, 0x167E, 0x7254, + 0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x0BAA, 0x1B6D, 0x7234, + 0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x06E7, 0x229E, 0x7224, + + 0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x07F6, 0x2C28, 0x7254, + 0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x0F02, 0x1341, 0x7264, + 0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x0FA9, 0x3EB5, 0x7294, + 0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0x44C3, 0x3EBB, 0x45C3, + + 0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x0671, 0x14FD, 0x0287, + 0x3EBC, 0xE610, 0x3EC8, 0x0C7B, 0x031A, 0x07E6, 0x3EC8, 0x86F7, + 0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x021F, 0x3ECA, 0x0386, + 0x3EC1, 0x0C03, 0x3EC9, 0x031E, 0x3ECA, 0x8C4C, 0x3EBF, 0x0C55, + + 0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x0EAD, 0x3EC8, 0xD308, + 0x3EC2, 0x8F7E, 0x3ECB, 0x0219, 0x3ECB, 0xD26E, 0x3EC5, 0x031F, + 0x3EC6, 0xC308, 0x3EC3, 0x32FF, 0x3EC9, 0x0265, 0x3EC9, 0x8319, + 0x1342, 0xD36E, 0x3EC7, 0x33FF, 0x0000, 0x8365, 0x1420, 0x9570, +}; + + +/* send initialization arrays to start up */ +static void +awe_init_array(void) +{ + awe_send_array(init1); + awe_wait(1024); + awe_send_array(init2); + awe_send_array(init3); + awe_poke_dw(AWE_HWCF4, 0); + awe_poke_dw(AWE_HWCF5, 0x83); + awe_poke_dw(AWE_HWCF6, 0x8000); + awe_send_array(init4); +} + +/* send an initialization array */ +static void +awe_send_array(unsigned short *data) +{ + int i; + unsigned short *p; + + p = data; + for (i = 0; i < AWE_MAX_VOICES; i++, p++) + awe_poke(AWE_INIT1(i), *p); + for (i = 0; i < AWE_MAX_VOICES; i++, p++) + awe_poke(AWE_INIT2(i), *p); + for (i = 0; i < AWE_MAX_VOICES; i++, p++) + awe_poke(AWE_INIT3(i), *p); + for (i = 0; i < AWE_MAX_VOICES; i++, p++) + awe_poke(AWE_INIT4(i), *p); +} + + +/* + * set up awe32 channels to some known state. + */ + +/* set the envelope & LFO parameters to the default values; see ADIP */ +static void +awe_tweak_voice(int i) +{ + /* set all mod/vol envelope shape to minimum */ + awe_poke(AWE_ENVVOL(i), 0x8000); + awe_poke(AWE_ENVVAL(i), 0x8000); + awe_poke(AWE_DCYSUS(i), 0x7F7F); + awe_poke(AWE_ATKHLDV(i), 0x7F7F); + awe_poke(AWE_ATKHLD(i), 0x7F7F); + awe_poke(AWE_PEFE(i), 0); /* mod envelope height to zero */ + awe_poke(AWE_LFO1VAL(i), 0x8000); /* no delay for LFO1 */ + awe_poke(AWE_LFO2VAL(i), 0x8000); + awe_poke(AWE_IP(i), 0xE000); /* no pitch shift */ + awe_poke(AWE_IFATN(i), 0xFF00); /* volume to minimum */ + awe_poke(AWE_FMMOD(i), 0); + awe_poke(AWE_TREMFRQ(i), 0); + awe_poke(AWE_FM2FRQ2(i), 0); +} + +static void +awe_tweak(void) +{ + int i; + /* reset all channels */ + for (i = 0; i < awe_max_voices; i++) + awe_tweak_voice(i); +} + + +/* + * initializes the FM section of AWE32; + * see Vince Vu's unofficial AWE32 programming guide + */ + +static void +awe_init_fm(void) +{ +#ifndef AWE_ALWAYS_INIT_FM + /* if no extended memory is on board.. */ + if (memsize <= 0) + return; +#endif + DEBUG(3,printk("AWE32: initializing FM\n")); + + /* Initialize the last two channels for DRAM refresh and producing + the reverb and chorus effects for Yamaha OPL-3 synthesizer */ + + /* 31: FM left channel, 0xffffe0-0xffffe8 */ + awe_poke(AWE_DCYSUSV(30), 0x80); + awe_poke_dw(AWE_PSST(30), 0xFFFFFFE0); /* full left */ + awe_poke_dw(AWE_CSL(30), 0x00FFFFE8 | + (DEF_FM_CHORUS_DEPTH << 24)); + awe_poke_dw(AWE_PTRX(30), (DEF_FM_REVERB_DEPTH << 8)); + awe_poke_dw(AWE_CPF(30), 0); + awe_poke_dw(AWE_CCCA(30), 0x00FFFFE3); + + /* 32: FM right channel, 0xfffff0-0xfffff8 */ + awe_poke(AWE_DCYSUSV(31), 0x80); + awe_poke_dw(AWE_PSST(31), 0x00FFFFF0); /* full right */ + awe_poke_dw(AWE_CSL(31), 0x00FFFFF8 | + (DEF_FM_CHORUS_DEPTH << 24)); + awe_poke_dw(AWE_PTRX(31), (DEF_FM_REVERB_DEPTH << 8)); + awe_poke_dw(AWE_CPF(31), 0x8000); + awe_poke_dw(AWE_CCCA(31), 0x00FFFFF3); + + /* skew volume & cutoff */ + awe_poke_dw(AWE_VTFT(30), 0x8000FFFF); + awe_poke_dw(AWE_VTFT(31), 0x8000FFFF); + + voices[30].state = AWE_ST_FM; + voices[31].state = AWE_ST_FM; + + /* change maximum channels to 30 */ + awe_max_voices = AWE_NORMAL_VOICES; + if (playing_mode == AWE_PLAY_DIRECT) + awe_info.nr_voices = awe_max_voices; + else + awe_info.nr_voices = AWE_MAX_CHANNELS; + voice_alloc->max_voice = awe_max_voices; +} + +/* + * AWE32 DRAM access routines + */ + +/* open DRAM write accessing mode */ +static int +awe_open_dram_for_write(int offset, int channels) +{ + int vidx[AWE_NORMAL_VOICES]; + int i; + + if (channels < 0 || channels >= AWE_NORMAL_VOICES) { + channels = AWE_NORMAL_VOICES; + for (i = 0; i < AWE_NORMAL_VOICES; i++) + vidx[i] = i; + } else { + for (i = 0; i < channels; i++) { + vidx[i] = awe_clear_voice(); + voices[vidx[i]].state = AWE_ST_MARK; + } + } + + /* use all channels for DMA transfer */ + for (i = 0; i < channels; i++) { + if (vidx[i] < 0) continue; + awe_poke(AWE_DCYSUSV(vidx[i]), 0x80); + awe_poke_dw(AWE_VTFT(vidx[i]), 0); + awe_poke_dw(AWE_CVCF(vidx[i]), 0); + awe_poke_dw(AWE_PTRX(vidx[i]), 0x40000000); + awe_poke_dw(AWE_CPF(vidx[i]), 0x40000000); + awe_poke_dw(AWE_PSST(vidx[i]), 0); + awe_poke_dw(AWE_CSL(vidx[i]), 0); + awe_poke_dw(AWE_CCCA(vidx[i]), 0x06000000); + voices[vidx[i]].state = AWE_ST_DRAM; + } + /* point channels 31 & 32 to ROM samples for DRAM refresh */ + awe_poke_dw(AWE_VTFT(30), 0); + awe_poke_dw(AWE_PSST(30), 0x1d8); + awe_poke_dw(AWE_CSL(30), 0x1e0); + awe_poke_dw(AWE_CCCA(30), 0x1d8); + awe_poke_dw(AWE_VTFT(31), 0); + awe_poke_dw(AWE_PSST(31), 0x1d8); + awe_poke_dw(AWE_CSL(31), 0x1e0); + awe_poke_dw(AWE_CCCA(31), 0x1d8); + voices[30].state = AWE_ST_FM; + voices[31].state = AWE_ST_FM; + + /* if full bit is on, not ready to write on */ + if (awe_peek_dw(AWE_SMALW) & 0x80000000) { + for (i = 0; i < channels; i++) { + awe_poke_dw(AWE_CCCA(vidx[i]), 0); + voices[vidx[i]].state = AWE_ST_OFF; + } + printk("awe: not ready to write..\n"); + return -EPERM; + } + + /* set address to write */ + awe_poke_dw(AWE_SMALW, offset); + + return 0; +} + +/* open DRAM for RAM size detection */ +static void +awe_open_dram_for_check(void) +{ + int i; + for (i = 0; i < AWE_NORMAL_VOICES; i++) { + awe_poke(AWE_DCYSUSV(i), 0x80); + awe_poke_dw(AWE_VTFT(i), 0); + awe_poke_dw(AWE_CVCF(i), 0); + awe_poke_dw(AWE_PTRX(i), 0x40000000); + awe_poke_dw(AWE_CPF(i), 0x40000000); + awe_poke_dw(AWE_PSST(i), 0); + awe_poke_dw(AWE_CSL(i), 0); + if (i & 1) /* DMA write */ + awe_poke_dw(AWE_CCCA(i), 0x06000000); + else /* DMA read */ + awe_poke_dw(AWE_CCCA(i), 0x04000000); + voices[i].state = AWE_ST_DRAM; + } +} + + +/* close dram access */ +static void +awe_close_dram(void) +{ + int i; + /* wait until FULL bit in SMAxW register be false */ + for (i = 0; i < 10000; i++) { + if (!(awe_peek_dw(AWE_SMALW) & 0x80000000)) + break; + awe_wait(10); + } + + for (i = 0; i < AWE_NORMAL_VOICES; i++) { + if (voices[i].state == AWE_ST_DRAM) { + awe_poke_dw(AWE_CCCA(i), 0); + awe_poke(AWE_DCYSUSV(i), 0x807F); + voices[i].state = AWE_ST_OFF; + } + } +} + + +/* + * check dram size on AWE board + */ + +/* any three numbers you like */ +#define UNIQUE_ID1 0x1234 +#define UNIQUE_ID2 0x4321 +#define UNIQUE_ID3 0xABCD + +static void __init +awe_check_dram(void) +{ + if (awe_present) /* already initialized */ + return; + + if (memsize >= 0) { /* given by config file or module option */ + memsize *= 1024; /* convert to Kbytes */ + return; + } + + awe_open_dram_for_check(); + + memsize = 0; + + /* set up unique two id numbers */ + awe_poke_dw(AWE_SMALW, AWE_DRAM_OFFSET); + awe_poke(AWE_SMLD, UNIQUE_ID1); + awe_poke(AWE_SMLD, UNIQUE_ID2); + + while (memsize < AWE_MAX_DRAM_SIZE) { + awe_wait(5); + /* read a data on the DRAM start address */ + awe_poke_dw(AWE_SMALR, AWE_DRAM_OFFSET); + awe_peek(AWE_SMLD); /* discard stale data */ + if (awe_peek(AWE_SMLD) != UNIQUE_ID1) + break; + if (awe_peek(AWE_SMLD) != UNIQUE_ID2) + break; + memsize += 512; /* increment 512kbytes */ + /* Write a unique data on the test address; + * if the address is out of range, the data is written on + * 0x200000(=AWE_DRAM_OFFSET). Then the two id words are + * broken by this data. + */ + awe_poke_dw(AWE_SMALW, AWE_DRAM_OFFSET + memsize*512L); + awe_poke(AWE_SMLD, UNIQUE_ID3); + awe_wait(5); + /* read a data on the just written DRAM address */ + awe_poke_dw(AWE_SMALR, AWE_DRAM_OFFSET + memsize*512L); + awe_peek(AWE_SMLD); /* discard stale data */ + if (awe_peek(AWE_SMLD) != UNIQUE_ID3) + break; + } + awe_close_dram(); + + DEBUG(0,printk("AWE32: %d Kbytes memory detected\n", memsize)); + + /* convert to Kbytes */ + memsize *= 1024; +} + + +/*----------------------------------------------------------------*/ + +/* + * chorus and reverb controls; from VV's guide + */ + +/* 5 parameters for each chorus mode; 3 x 16bit, 2 x 32bit */ +static char chorus_defined[AWE_CHORUS_NUMBERS]; +static awe_chorus_fx_rec chorus_parm[AWE_CHORUS_NUMBERS] = { + {0xE600, 0x03F6, 0xBC2C ,0x00000000, 0x0000006D}, /* chorus 1 */ + {0xE608, 0x031A, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 2 */ + {0xE610, 0x031A, 0xBC84, 0x00000000, 0x00000083}, /* chorus 3 */ + {0xE620, 0x0269, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 4 */ + {0xE680, 0x04D3, 0xBCA6, 0x00000000, 0x0000005B}, /* feedback */ + {0xE6E0, 0x044E, 0xBC37, 0x00000000, 0x00000026}, /* flanger */ + {0xE600, 0x0B06, 0xBC00, 0x0000E000, 0x00000083}, /* short delay */ + {0xE6C0, 0x0B06, 0xBC00, 0x0000E000, 0x00000083}, /* short delay + feedback */ +}; + +static int +awe_load_chorus_fx(awe_patch_info *patch, const char __user *addr, int count) +{ + if (patch->optarg < AWE_CHORUS_PREDEFINED || patch->optarg >= AWE_CHORUS_NUMBERS) { + printk(KERN_WARNING "AWE32 Error: invalid chorus mode %d for uploading\n", patch->optarg); + return -EINVAL; + } + if (count < sizeof(awe_chorus_fx_rec)) { + printk(KERN_WARNING "AWE32 Error: too short chorus fx parameters\n"); + return -EINVAL; + } + if (copy_from_user(&chorus_parm[patch->optarg], addr + AWE_PATCH_INFO_SIZE, + sizeof(awe_chorus_fx_rec))) + return -EFAULT; + chorus_defined[patch->optarg] = TRUE; + return 0; +} + +static void +awe_set_chorus_mode(int effect) +{ + if (effect < 0 || effect >= AWE_CHORUS_NUMBERS || + (effect >= AWE_CHORUS_PREDEFINED && !chorus_defined[effect])) + return; + awe_poke(AWE_INIT3(9), chorus_parm[effect].feedback); + awe_poke(AWE_INIT3(12), chorus_parm[effect].delay_offset); + awe_poke(AWE_INIT4(3), chorus_parm[effect].lfo_depth); + awe_poke_dw(AWE_HWCF4, chorus_parm[effect].delay); + awe_poke_dw(AWE_HWCF5, chorus_parm[effect].lfo_freq); + awe_poke_dw(AWE_HWCF6, 0x8000); + awe_poke_dw(AWE_HWCF7, 0x0000); +} + +static void +awe_update_chorus_mode(void) +{ + awe_set_chorus_mode(ctrls[AWE_MD_CHORUS_MODE]); +} + +/*----------------------------------------------------------------*/ + +/* reverb mode settings; write the following 28 data of 16 bit length + * on the corresponding ports in the reverb_cmds array + */ +static char reverb_defined[AWE_CHORUS_NUMBERS]; +static awe_reverb_fx_rec reverb_parm[AWE_REVERB_NUMBERS] = { +{{ /* room 1 */ + 0xB488, 0xA450, 0x9550, 0x84B5, 0x383A, 0x3EB5, 0x72F4, + 0x72A4, 0x7254, 0x7204, 0x7204, 0x7204, 0x4416, 0x4516, + 0xA490, 0xA590, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* room 2 */ + 0xB488, 0xA458, 0x9558, 0x84B5, 0x383A, 0x3EB5, 0x7284, + 0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548, + 0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* room 3 */ + 0xB488, 0xA460, 0x9560, 0x84B5, 0x383A, 0x3EB5, 0x7284, + 0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4416, 0x4516, + 0xA490, 0xA590, 0x842C, 0x852C, 0x842C, 0x852C, 0x842B, + 0x852B, 0x842B, 0x852B, 0x842A, 0x852A, 0x842A, 0x852A, +}}, +{{ /* hall 1 */ + 0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7284, + 0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548, + 0xA440, 0xA540, 0x842B, 0x852B, 0x842B, 0x852B, 0x842A, + 0x852A, 0x842A, 0x852A, 0x8429, 0x8529, 0x8429, 0x8529, +}}, +{{ /* hall 2 */ + 0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7254, + 0x7234, 0x7224, 0x7254, 0x7264, 0x7294, 0x44C3, 0x45C3, + 0xA404, 0xA504, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* plate */ + 0xB4FF, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7234, + 0x7234, 0x7234, 0x7234, 0x7234, 0x7234, 0x4448, 0x4548, + 0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* delay */ + 0xB4FF, 0xA470, 0x9500, 0x84B5, 0x333A, 0x39B5, 0x7204, + 0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500, + 0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, + 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, +}}, +{{ /* panning delay */ + 0xB4FF, 0xA490, 0x9590, 0x8474, 0x333A, 0x39B5, 0x7204, + 0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500, + 0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, + 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, +}}, +}; + +static struct ReverbCmdPair { + unsigned short cmd, port; +} reverb_cmds[28] = { + {AWE_INIT1(0x03)}, {AWE_INIT1(0x05)}, {AWE_INIT4(0x1F)}, {AWE_INIT1(0x07)}, + {AWE_INIT2(0x14)}, {AWE_INIT2(0x16)}, {AWE_INIT1(0x0F)}, {AWE_INIT1(0x17)}, + {AWE_INIT1(0x1F)}, {AWE_INIT2(0x07)}, {AWE_INIT2(0x0F)}, {AWE_INIT2(0x17)}, + {AWE_INIT2(0x1D)}, {AWE_INIT2(0x1F)}, {AWE_INIT3(0x01)}, {AWE_INIT3(0x03)}, + {AWE_INIT1(0x09)}, {AWE_INIT1(0x0B)}, {AWE_INIT1(0x11)}, {AWE_INIT1(0x13)}, + {AWE_INIT1(0x19)}, {AWE_INIT1(0x1B)}, {AWE_INIT2(0x01)}, {AWE_INIT2(0x03)}, + {AWE_INIT2(0x09)}, {AWE_INIT2(0x0B)}, {AWE_INIT2(0x11)}, {AWE_INIT2(0x13)}, +}; + +static int +awe_load_reverb_fx(awe_patch_info *patch, const char __user *addr, int count) +{ + if (patch->optarg < AWE_REVERB_PREDEFINED || patch->optarg >= AWE_REVERB_NUMBERS) { + printk(KERN_WARNING "AWE32 Error: invalid reverb mode %d for uploading\n", patch->optarg); + return -EINVAL; + } + if (count < sizeof(awe_reverb_fx_rec)) { + printk(KERN_WARNING "AWE32 Error: too short reverb fx parameters\n"); + return -EINVAL; + } + if (copy_from_user(&reverb_parm[patch->optarg], addr + AWE_PATCH_INFO_SIZE, + sizeof(awe_reverb_fx_rec))) + return -EFAULT; + reverb_defined[patch->optarg] = TRUE; + return 0; +} + +static void +awe_set_reverb_mode(int effect) +{ + int i; + if (effect < 0 || effect >= AWE_REVERB_NUMBERS || + (effect >= AWE_REVERB_PREDEFINED && !reverb_defined[effect])) + return; + for (i = 0; i < 28; i++) + awe_poke(reverb_cmds[i].cmd, reverb_cmds[i].port, + reverb_parm[effect].parms[i]); +} + +static void +awe_update_reverb_mode(void) +{ + awe_set_reverb_mode(ctrls[AWE_MD_REVERB_MODE]); +} + +/* + * treble/bass equalizer control + */ + +static unsigned short bass_parm[12][3] = { + {0xD26A, 0xD36A, 0x0000}, /* -12 dB */ + {0xD25B, 0xD35B, 0x0000}, /* -8 */ + {0xD24C, 0xD34C, 0x0000}, /* -6 */ + {0xD23D, 0xD33D, 0x0000}, /* -4 */ + {0xD21F, 0xD31F, 0x0000}, /* -2 */ + {0xC208, 0xC308, 0x0001}, /* 0 (HW default) */ + {0xC219, 0xC319, 0x0001}, /* +2 */ + {0xC22A, 0xC32A, 0x0001}, /* +4 */ + {0xC24C, 0xC34C, 0x0001}, /* +6 */ + {0xC26E, 0xC36E, 0x0001}, /* +8 */ + {0xC248, 0xC348, 0x0002}, /* +10 */ + {0xC26A, 0xC36A, 0x0002}, /* +12 dB */ +}; + +static unsigned short treble_parm[12][9] = { + {0x821E, 0xC26A, 0x031E, 0xC36A, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, /* -12 dB */ + {0x821E, 0xC25B, 0x031E, 0xC35B, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xC24C, 0x031E, 0xC34C, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xC23D, 0x031E, 0xC33D, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xC21F, 0x031E, 0xC31F, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021E, 0xD208, 0x831E, 0xD308, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021D, 0xD219, 0x831D, 0xD319, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021C, 0xD22A, 0x831C, 0xD32A, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021A, 0xD24C, 0x831A, 0xD34C, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}, /* +8 (HW default) */ + {0x821D, 0xD219, 0x031D, 0xD319, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}, + {0x821C, 0xD22A, 0x031C, 0xD32A, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}, /* +12 dB */ +}; + + +/* + * set Emu8000 digital equalizer; from 0 to 11 [-12dB - 12dB] + */ +static void +awe_equalizer(int bass, int treble) +{ + unsigned short w; + + if (bass < 0 || bass > 11 || treble < 0 || treble > 11) + return; + awe_poke(AWE_INIT4(0x01), bass_parm[bass][0]); + awe_poke(AWE_INIT4(0x11), bass_parm[bass][1]); + awe_poke(AWE_INIT3(0x11), treble_parm[treble][0]); + awe_poke(AWE_INIT3(0x13), treble_parm[treble][1]); + awe_poke(AWE_INIT3(0x1B), treble_parm[treble][2]); + awe_poke(AWE_INIT4(0x07), treble_parm[treble][3]); + awe_poke(AWE_INIT4(0x0B), treble_parm[treble][4]); + awe_poke(AWE_INIT4(0x0D), treble_parm[treble][5]); + awe_poke(AWE_INIT4(0x17), treble_parm[treble][6]); + awe_poke(AWE_INIT4(0x19), treble_parm[treble][7]); + w = bass_parm[bass][2] + treble_parm[treble][8]; + awe_poke(AWE_INIT4(0x15), (unsigned short)(w + 0x0262)); + awe_poke(AWE_INIT4(0x1D), (unsigned short)(w + 0x8362)); +} + +static void awe_update_equalizer(void) +{ + awe_equalizer(ctrls[AWE_MD_BASS_LEVEL], ctrls[AWE_MD_TREBLE_LEVEL]); +} + + +/*----------------------------------------------------------------*/ + +#ifdef CONFIG_AWE32_MIDIEMU + +/* + * Emu8000 MIDI Emulation + */ + +/* + * midi queue record + */ + +/* queue type */ +enum { Q_NONE, Q_VARLEN, Q_READ, Q_SYSEX, }; + +#define MAX_MIDIBUF 64 + +/* midi status */ +typedef struct MidiStatus { + int queue; /* queue type */ + int qlen; /* queue length */ + int read; /* chars read */ + int status; /* current status */ + int chan; /* current channel */ + unsigned char buf[MAX_MIDIBUF]; +} MidiStatus; + +/* MIDI mode type */ +enum { MODE_GM, MODE_GS, MODE_XG, }; + +/* NRPN / CC -> Emu8000 parameter converter */ +typedef struct { + int control; + int awe_effect; + unsigned short (*convert)(int val); +} ConvTable; + + +/* + * prototypes + */ + +static int awe_midi_open(int dev, int mode, void (*input)(int,unsigned char), void (*output)(int)); +static void awe_midi_close(int dev); +static int awe_midi_ioctl(int dev, unsigned cmd, void __user * arg); +static int awe_midi_outputc(int dev, unsigned char midi_byte); + +static void init_midi_status(MidiStatus *st); +static void clear_rpn(void); +static void get_midi_char(MidiStatus *st, int c); +/*static void queue_varlen(MidiStatus *st, int c);*/ +static void special_event(MidiStatus *st, int c); +static void queue_read(MidiStatus *st, int c); +static void midi_note_on(MidiStatus *st); +static void midi_note_off(MidiStatus *st); +static void midi_key_pressure(MidiStatus *st); +static void midi_channel_pressure(MidiStatus *st); +static void midi_pitch_wheel(MidiStatus *st); +static void midi_program_change(MidiStatus *st); +static void midi_control_change(MidiStatus *st); +static void midi_select_bank(MidiStatus *st, int val); +static void midi_nrpn_event(MidiStatus *st); +static void midi_rpn_event(MidiStatus *st); +static void midi_detune(int chan, int coarse, int fine); +static void midi_system_exclusive(MidiStatus *st); +static int send_converted_effect(ConvTable *table, int num_tables, MidiStatus *st, int type, int val); +static int add_converted_effect(ConvTable *table, int num_tables, MidiStatus *st, int type, int val); +static int xg_control_change(MidiStatus *st, int cmd, int val); + +#define numberof(ary) (sizeof(ary)/sizeof(ary[0])) + + +/* + * OSS Midi device record + */ + +static struct midi_operations awe_midi_operations = +{ + .owner = THIS_MODULE, + .info = {"AWE Midi Emu", 0, 0, SNDCARD_SB}, + .in_info = {0}, + .open = awe_midi_open, /*open*/ + .close = awe_midi_close, /*close*/ + .ioctl = awe_midi_ioctl, /*ioctl*/ + .outputc = awe_midi_outputc, /*outputc*/ +}; + +static int my_mididev = -1; + +static void __init attach_midiemu(void) +{ + if ((my_mididev = sound_alloc_mididev()) < 0) + printk ("Sound: Too many midi devices detected\n"); + else + midi_devs[my_mididev] = &awe_midi_operations; +} + +static void unload_midiemu(void) +{ + if (my_mididev >= 0) + sound_unload_mididev(my_mididev); +} + + +/* + * open/close midi device + */ + +static int midi_opened = FALSE; + +static int midi_mode; +static int coarsetune, finetune; + +static int xg_mapping = TRUE; +static int xg_bankmode; + +/* effect sensitivity */ + +#define FX_CUTOFF 0 +#define FX_RESONANCE 1 +#define FX_ATTACK 2 +#define FX_RELEASE 3 +#define FX_VIBRATE 4 +#define FX_VIBDEPTH 5 +#define FX_VIBDELAY 6 +#define FX_NUMS 7 + +#define DEF_FX_CUTOFF 170 +#define DEF_FX_RESONANCE 6 +#define DEF_FX_ATTACK 50 +#define DEF_FX_RELEASE 50 +#define DEF_FX_VIBRATE 30 +#define DEF_FX_VIBDEPTH 4 +#define DEF_FX_VIBDELAY 1500 + +/* effect sense: */ +static int gs_sense[] = +{ + DEF_FX_CUTOFF, DEF_FX_RESONANCE, DEF_FX_ATTACK, DEF_FX_RELEASE, + DEF_FX_VIBRATE, DEF_FX_VIBDEPTH, DEF_FX_VIBDELAY +}; +static int xg_sense[] = +{ + DEF_FX_CUTOFF, DEF_FX_RESONANCE, DEF_FX_ATTACK, DEF_FX_RELEASE, + DEF_FX_VIBRATE, DEF_FX_VIBDEPTH, DEF_FX_VIBDELAY +}; + + +/* current status */ +static MidiStatus curst; + + +static int +awe_midi_open (int dev, int mode, + void (*input)(int,unsigned char), + void (*output)(int)) +{ + if (midi_opened) + return -EBUSY; + + midi_opened = TRUE; + + midi_mode = MODE_GM; + + curst.queue = Q_NONE; + curst.qlen = 0; + curst.read = 0; + curst.status = 0; + curst.chan = 0; + memset(curst.buf, 0, sizeof(curst.buf)); + + init_midi_status(&curst); + + return 0; +} + +static void +awe_midi_close (int dev) +{ + midi_opened = FALSE; +} + + +static int +awe_midi_ioctl (int dev, unsigned cmd, void __user *arg) +{ + return -EPERM; +} + +static int +awe_midi_outputc (int dev, unsigned char midi_byte) +{ + if (! midi_opened) + return 1; + + /* force to change playing mode */ + playing_mode = AWE_PLAY_MULTI; + + get_midi_char(&curst, midi_byte); + return 1; +} + + +/* + * initialize + */ + +static void init_midi_status(MidiStatus *st) +{ + clear_rpn(); + coarsetune = 0; + finetune = 0; +} + + +/* + * RPN & NRPN + */ + +#define MAX_MIDI_CHANNELS 16 + +/* RPN & NRPN */ +static unsigned char nrpn[MAX_MIDI_CHANNELS]; /* current event is NRPN? */ +static int msb_bit; /* current event is msb for RPN/NRPN */ +/* RPN & NRPN indeces */ +static unsigned char rpn_msb[MAX_MIDI_CHANNELS], rpn_lsb[MAX_MIDI_CHANNELS]; +/* RPN & NRPN values */ +static int rpn_val[MAX_MIDI_CHANNELS]; + +static void clear_rpn(void) +{ + int i; + for (i = 0; i < MAX_MIDI_CHANNELS; i++) { + nrpn[i] = 0; + rpn_msb[i] = 127; + rpn_lsb[i] = 127; + rpn_val[i] = 0; + } + msb_bit = 0; +} + + +/* + * process midi queue + */ + +/* status event types */ +typedef void (*StatusEvent)(MidiStatus *st); +static struct StatusEventList { + StatusEvent process; + int qlen; +} status_event[8] = { + {midi_note_off, 2}, + {midi_note_on, 2}, + {midi_key_pressure, 2}, + {midi_control_change, 2}, + {midi_program_change, 1}, + {midi_channel_pressure, 1}, + {midi_pitch_wheel, 2}, + {NULL, 0}, +}; + + +/* read a char from fifo and process it */ +static void get_midi_char(MidiStatus *st, int c) +{ + if (c == 0xfe) { + /* ignore active sense */ + st->queue = Q_NONE; + return; + } + + switch (st->queue) { + /* case Q_VARLEN: queue_varlen(st, c); break;*/ + case Q_READ: + case Q_SYSEX: + queue_read(st, c); + break; + case Q_NONE: + st->read = 0; + if ((c & 0xf0) == 0xf0) { + special_event(st, c); + } else if (c & 0x80) { /* status change */ + st->status = (c >> 4) & 0x07; + st->chan = c & 0x0f; + st->queue = Q_READ; + st->qlen = status_event[st->status].qlen; + if (st->qlen == 0) + st->queue = Q_NONE; + } + break; + } +} + +/* 0xfx events */ +static void special_event(MidiStatus *st, int c) +{ + switch (c) { + case 0xf0: /* system exclusive */ + st->queue = Q_SYSEX; + st->qlen = 0; + break; + case 0xf1: /* MTC quarter frame */ + case 0xf3: /* song select */ + st->queue = Q_READ; + st->qlen = 1; + break; + case 0xf2: /* song position */ + st->queue = Q_READ; + st->qlen = 2; + break; + } +} + +#if 0 +/* read variable length value */ +static void queue_varlen(MidiStatus *st, int c) +{ + st->qlen += (c & 0x7f); + if (c & 0x80) { + st->qlen <<= 7; + return; + } + if (st->qlen <= 0) { + st->qlen = 0; + st->queue = Q_NONE; + } + st->queue = Q_READ; + st->read = 0; +} +#endif + + +/* read a char */ +static void queue_read(MidiStatus *st, int c) +{ + if (st->read < MAX_MIDIBUF) { + if (st->queue != Q_SYSEX) + c &= 0x7f; + st->buf[st->read] = (unsigned char)c; + } + st->read++; + if (st->queue == Q_SYSEX && c == 0xf7) { + midi_system_exclusive(st); + st->queue = Q_NONE; + } else if (st->queue == Q_READ && st->read >= st->qlen) { + if (status_event[st->status].process) + status_event[st->status].process(st); + st->queue = Q_NONE; + } +} + + +/* + * status events + */ + +/* note on */ +static void midi_note_on(MidiStatus *st) +{ + DEBUG(2,printk("midi: note_on (%d) %d %d\n", st->chan, st->buf[0], st->buf[1])); + if (st->buf[1] == 0) + midi_note_off(st); + else + awe_start_note(0, st->chan, st->buf[0], st->buf[1]); +} + +/* note off */ +static void midi_note_off(MidiStatus *st) +{ + DEBUG(2,printk("midi: note_off (%d) %d %d\n", st->chan, st->buf[0], st->buf[1])); + awe_kill_note(0, st->chan, st->buf[0], st->buf[1]); +} + +/* key pressure change */ +static void midi_key_pressure(MidiStatus *st) +{ + awe_key_pressure(0, st->chan, st->buf[0], st->buf[1]); +} + +/* channel pressure change */ +static void midi_channel_pressure(MidiStatus *st) +{ + channels[st->chan].chan_press = st->buf[0]; + awe_modwheel_change(st->chan, st->buf[0]); +} + +/* pitch wheel change */ +static void midi_pitch_wheel(MidiStatus *st) +{ + int val = (int)st->buf[1] * 128 + st->buf[0]; + awe_bender(0, st->chan, val); +} + +/* program change */ +static void midi_program_change(MidiStatus *st) +{ + int preset; + preset = st->buf[0]; + if (midi_mode == MODE_GS && IS_DRUM_CHANNEL(st->chan) && preset == 127) + preset = 0; + else if (midi_mode == MODE_XG && xg_mapping && IS_DRUM_CHANNEL(st->chan)) + preset += 64; + + awe_set_instr(0, st->chan, preset); +} + +#define send_effect(chan,type,val) awe_send_effect(chan,-1,type,val) +#define add_effect(chan,type,val) awe_send_effect(chan,-1,(type)|0x80,val) +#define unset_effect(chan,type) awe_send_effect(chan,-1,(type)|0x40,0) + +/* midi control change */ +static void midi_control_change(MidiStatus *st) +{ + int cmd = st->buf[0]; + int val = st->buf[1]; + + DEBUG(2,printk("midi: control (%d) %d %d\n", st->chan, cmd, val)); + if (midi_mode == MODE_XG) { + if (xg_control_change(st, cmd, val)) + return; + } + + /* controls #31 - #64 are LSB of #0 - #31 */ + msb_bit = 1; + if (cmd >= 0x20 && cmd < 0x40) { + msb_bit = 0; + cmd -= 0x20; + } + + switch (cmd) { + case CTL_SOFT_PEDAL: + if (val == 127) + add_effect(st->chan, AWE_FX_CUTOFF, -160); + else + unset_effect(st->chan, AWE_FX_CUTOFF); + break; + + case CTL_BANK_SELECT: + midi_select_bank(st, val); + break; + + /* set RPN/NRPN parameter */ + case CTL_REGIST_PARM_NUM_MSB: + nrpn[st->chan]=0; rpn_msb[st->chan]=val; + break; + case CTL_REGIST_PARM_NUM_LSB: + nrpn[st->chan]=0; rpn_lsb[st->chan]=val; + break; + case CTL_NONREG_PARM_NUM_MSB: + nrpn[st->chan]=1; rpn_msb[st->chan]=val; + break; + case CTL_NONREG_PARM_NUM_LSB: + nrpn[st->chan]=1; rpn_lsb[st->chan]=val; + break; + + /* send RPN/NRPN entry */ + case CTL_DATA_ENTRY: + if (msb_bit) + rpn_val[st->chan] = val * 128; + else + rpn_val[st->chan] |= val; + if (nrpn[st->chan]) + midi_nrpn_event(st); + else + midi_rpn_event(st); + break; + + /* increase/decrease data entry */ + case CTL_DATA_INCREMENT: + rpn_val[st->chan]++; + midi_rpn_event(st); + break; + case CTL_DATA_DECREMENT: + rpn_val[st->chan]--; + midi_rpn_event(st); + break; + + /* default */ + default: + awe_controller(0, st->chan, cmd, val); + break; + } +} + +/* tone bank change */ +static void midi_select_bank(MidiStatus *st, int val) +{ + if (midi_mode == MODE_XG && msb_bit) { + xg_bankmode = val; + /* XG MSB value; not normal bank selection */ + switch (val) { + case 127: /* remap to drum channel */ + awe_controller(0, st->chan, CTL_BANK_SELECT, 128); + break; + default: /* remap to normal channel */ + awe_controller(0, st->chan, CTL_BANK_SELECT, val); + break; + } + return; + } else if (midi_mode == MODE_GS && !msb_bit) + /* ignore LSB bank in GS mode (used for mapping) */ + return; + + /* normal bank controls; accept both MSB and LSB */ + if (! IS_DRUM_CHANNEL(st->chan)) { + if (midi_mode == MODE_XG) { + if (xg_bankmode) return; + if (val == 64 || val == 126) + val = 0; + } else if (midi_mode == MODE_GS && val == 127) + val = 0; + awe_controller(0, st->chan, CTL_BANK_SELECT, val); + } +} + + +/* + * RPN events + */ + +static void midi_rpn_event(MidiStatus *st) +{ + int type; + type = (rpn_msb[st->chan]<<8) | rpn_lsb[st->chan]; + switch (type) { + case 0x0000: /* Pitch bend sensitivity */ + /* MSB only / 1 semitone per 128 */ + if (msb_bit) { + channels[st->chan].bender_range = + rpn_val[st->chan] * 100 / 128; + } + break; + + case 0x0001: /* fine tuning: */ + /* MSB/LSB, 8192=center, 100/8192 cent step */ + finetune = rpn_val[st->chan] - 8192; + midi_detune(st->chan, coarsetune, finetune); + break; + + case 0x0002: /* coarse tuning */ + /* MSB only / 8192=center, 1 semitone per 128 */ + if (msb_bit) { + coarsetune = rpn_val[st->chan] - 8192; + midi_detune(st->chan, coarsetune, finetune); + } + break; + + case 0x7F7F: /* "lock-in" RPN */ + break; + } +} + + +/* tuning: + * coarse = -8192 to 8192 (100 cent per 128) + * fine = -8192 to 8192 (max=100cent) + */ +static void midi_detune(int chan, int coarse, int fine) +{ + /* 4096 = 1200 cents in AWE parameter */ + int val; + val = coarse * 4096 / (12 * 128); + val += fine / 24; + if (val) + send_effect(chan, AWE_FX_INIT_PITCH, val); + else + unset_effect(chan, AWE_FX_INIT_PITCH); +} + + +/* + * system exclusive message + * GM/GS/XG macros are accepted + */ + +static void midi_system_exclusive(MidiStatus *st) +{ + /* GM on */ + static unsigned char gm_on_macro[] = { + 0x7e,0x7f,0x09,0x01, + }; + /* XG on */ + static unsigned char xg_on_macro[] = { + 0x43,0x10,0x4c,0x00,0x00,0x7e,0x00, + }; + /* GS prefix + * drum channel: XX=0x1?(channel), YY=0x15, ZZ=on/off + * reverb mode: XX=0x01, YY=0x30, ZZ=0-7 + * chorus mode: XX=0x01, YY=0x38, ZZ=0-7 + */ + static unsigned char gs_pfx_macro[] = { + 0x41,0x10,0x42,0x12,0x40,/*XX,YY,ZZ*/ + }; + +#if 0 + /* SC88 system mode set + * single module mode: XX=1 + * double module mode: XX=0 + */ + static unsigned char gs_mode_macro[] = { + 0x41,0x10,0x42,0x12,0x00,0x00,0x7F,/*ZZ*/ + }; + /* SC88 display macro: XX=01:bitmap, 00:text + */ + static unsigned char gs_disp_macro[] = { + 0x41,0x10,0x45,0x12,0x10,/*XX,00*/ + }; +#endif + + /* GM on */ + if (memcmp(st->buf, gm_on_macro, sizeof(gm_on_macro)) == 0) { + if (midi_mode != MODE_GS && midi_mode != MODE_XG) + midi_mode = MODE_GM; + init_midi_status(st); + } + + /* GS macros */ + else if (memcmp(st->buf, gs_pfx_macro, sizeof(gs_pfx_macro)) == 0) { + if (midi_mode != MODE_GS && midi_mode != MODE_XG) + midi_mode = MODE_GS; + + if (st->buf[5] == 0x00 && st->buf[6] == 0x7f && st->buf[7] == 0x00) { + /* GS reset */ + init_midi_status(st); + } + + else if ((st->buf[5] & 0xf0) == 0x10 && st->buf[6] == 0x15) { + /* drum pattern */ + int p = st->buf[5] & 0x0f; + if (p == 0) p = 9; + else if (p < 10) p--; + if (st->buf[7] == 0) + DRUM_CHANNEL_OFF(p); + else + DRUM_CHANNEL_ON(p); + + } else if ((st->buf[5] & 0xf0) == 0x10 && st->buf[6] == 0x21) { + /* program */ + int p = st->buf[5] & 0x0f; + if (p == 0) p = 9; + else if (p < 10) p--; + if (! IS_DRUM_CHANNEL(p)) + awe_set_instr(0, p, st->buf[7]); + + } else if (st->buf[5] == 0x01 && st->buf[6] == 0x30) { + /* reverb mode */ + awe_set_reverb_mode(st->buf[7]); + + } else if (st->buf[5] == 0x01 && st->buf[6] == 0x38) { + /* chorus mode */ + awe_set_chorus_mode(st->buf[7]); + + } else if (st->buf[5] == 0x00 && st->buf[6] == 0x04) { + /* master volume */ + awe_change_master_volume(st->buf[7]); + + } + } + + /* XG on */ + else if (memcmp(st->buf, xg_on_macro, sizeof(xg_on_macro)) == 0) { + midi_mode = MODE_XG; + xg_mapping = TRUE; + xg_bankmode = 0; + } +} + + +/*----------------------------------------------------------------*/ + +/* + * convert NRPN/control values + */ + +static int send_converted_effect(ConvTable *table, int num_tables, MidiStatus *st, int type, int val) +{ + int i, cval; + for (i = 0; i < num_tables; i++) { + if (table[i].control == type) { + cval = table[i].convert(val); + send_effect(st->chan, table[i].awe_effect, cval); + return TRUE; + } + } + return FALSE; +} + +static int add_converted_effect(ConvTable *table, int num_tables, MidiStatus *st, int type, int val) +{ + int i, cval; + for (i = 0; i < num_tables; i++) { + if (table[i].control == type) { + cval = table[i].convert(val); + add_effect(st->chan, table[i].awe_effect|0x80, cval); + return TRUE; + } + } + return FALSE; +} + + +/* + * AWE32 NRPN effects + */ + +static unsigned short fx_delay(int val); +static unsigned short fx_attack(int val); +static unsigned short fx_hold(int val); +static unsigned short fx_decay(int val); +static unsigned short fx_the_value(int val); +static unsigned short fx_twice_value(int val); +static unsigned short fx_conv_pitch(int val); +static unsigned short fx_conv_Q(int val); + +/* function for each NRPN */ /* [range] units */ +#define fx_env1_delay fx_delay /* [0,5900] 4msec */ +#define fx_env1_attack fx_attack /* [0,5940] 1msec */ +#define fx_env1_hold fx_hold /* [0,8191] 1msec */ +#define fx_env1_decay fx_decay /* [0,5940] 4msec */ +#define fx_env1_release fx_decay /* [0,5940] 4msec */ +#define fx_env1_sustain fx_the_value /* [0,127] 0.75dB */ +#define fx_env1_pitch fx_the_value /* [-127,127] 9.375cents */ +#define fx_env1_cutoff fx_the_value /* [-127,127] 56.25cents */ + +#define fx_env2_delay fx_delay /* [0,5900] 4msec */ +#define fx_env2_attack fx_attack /* [0,5940] 1msec */ +#define fx_env2_hold fx_hold /* [0,8191] 1msec */ +#define fx_env2_decay fx_decay /* [0,5940] 4msec */ +#define fx_env2_release fx_decay /* [0,5940] 4msec */ +#define fx_env2_sustain fx_the_value /* [0,127] 0.75dB */ + +#define fx_lfo1_delay fx_delay /* [0,5900] 4msec */ +#define fx_lfo1_freq fx_twice_value /* [0,127] 84mHz */ +#define fx_lfo1_volume fx_twice_value /* [0,127] 0.1875dB */ +#define fx_lfo1_pitch fx_the_value /* [-127,127] 9.375cents */ +#define fx_lfo1_cutoff fx_twice_value /* [-64,63] 56.25cents */ + +#define fx_lfo2_delay fx_delay /* [0,5900] 4msec */ +#define fx_lfo2_freq fx_twice_value /* [0,127] 84mHz */ +#define fx_lfo2_pitch fx_the_value /* [-127,127] 9.375cents */ + +#define fx_init_pitch fx_conv_pitch /* [-8192,8192] cents */ +#define fx_chorus fx_the_value /* [0,255] -- */ +#define fx_reverb fx_the_value /* [0,255] -- */ +#define fx_cutoff fx_twice_value /* [0,127] 62Hz */ +#define fx_filterQ fx_conv_Q /* [0,127] -- */ + +static unsigned short fx_delay(int val) +{ + return (unsigned short)calc_parm_delay(val); +} + +static unsigned short fx_attack(int val) +{ + return (unsigned short)calc_parm_attack(val); +} + +static unsigned short fx_hold(int val) +{ + return (unsigned short)calc_parm_hold(val); +} + +static unsigned short fx_decay(int val) +{ + return (unsigned short)calc_parm_decay(val); +} + +static unsigned short fx_the_value(int val) +{ + return (unsigned short)(val & 0xff); +} + +static unsigned short fx_twice_value(int val) +{ + return (unsigned short)((val * 2) & 0xff); +} + +static unsigned short fx_conv_pitch(int val) +{ + return (short)(val * 4096 / 1200); +} + +static unsigned short fx_conv_Q(int val) +{ + return (unsigned short)((val / 8) & 0xff); +} + + +static ConvTable awe_effects[] = +{ + { 0, AWE_FX_LFO1_DELAY, fx_lfo1_delay}, + { 1, AWE_FX_LFO1_FREQ, fx_lfo1_freq}, + { 2, AWE_FX_LFO2_DELAY, fx_lfo2_delay}, + { 3, AWE_FX_LFO2_FREQ, fx_lfo2_freq}, + + { 4, AWE_FX_ENV1_DELAY, fx_env1_delay}, + { 5, AWE_FX_ENV1_ATTACK,fx_env1_attack}, + { 6, AWE_FX_ENV1_HOLD, fx_env1_hold}, + { 7, AWE_FX_ENV1_DECAY, fx_env1_decay}, + { 8, AWE_FX_ENV1_SUSTAIN, fx_env1_sustain}, + { 9, AWE_FX_ENV1_RELEASE, fx_env1_release}, + + {10, AWE_FX_ENV2_DELAY, fx_env2_delay}, + {11, AWE_FX_ENV2_ATTACK, fx_env2_attack}, + {12, AWE_FX_ENV2_HOLD, fx_env2_hold}, + {13, AWE_FX_ENV2_DECAY, fx_env2_decay}, + {14, AWE_FX_ENV2_SUSTAIN, fx_env2_sustain}, + {15, AWE_FX_ENV2_RELEASE, fx_env2_release}, + + {16, AWE_FX_INIT_PITCH, fx_init_pitch}, + {17, AWE_FX_LFO1_PITCH, fx_lfo1_pitch}, + {18, AWE_FX_LFO2_PITCH, fx_lfo2_pitch}, + {19, AWE_FX_ENV1_PITCH, fx_env1_pitch}, + {20, AWE_FX_LFO1_VOLUME, fx_lfo1_volume}, + {21, AWE_FX_CUTOFF, fx_cutoff}, + {22, AWE_FX_FILTERQ, fx_filterQ}, + {23, AWE_FX_LFO1_CUTOFF, fx_lfo1_cutoff}, + {24, AWE_FX_ENV1_CUTOFF, fx_env1_cutoff}, + {25, AWE_FX_CHORUS, fx_chorus}, + {26, AWE_FX_REVERB, fx_reverb}, +}; + +static int num_awe_effects = numberof(awe_effects); + + +/* + * GS(SC88) NRPN effects; still experimental + */ + +/* cutoff: quarter semitone step, max=255 */ +static unsigned short gs_cutoff(int val) +{ + return (val - 64) * gs_sense[FX_CUTOFF] / 50; +} + +/* resonance: 0 to 15(max) */ +static unsigned short gs_filterQ(int val) +{ + return (val - 64) * gs_sense[FX_RESONANCE] / 50; +} + +/* attack: */ +static unsigned short gs_attack(int val) +{ + return -(val - 64) * gs_sense[FX_ATTACK] / 50; +} + +/* decay: */ +static unsigned short gs_decay(int val) +{ + return -(val - 64) * gs_sense[FX_RELEASE] / 50; +} + +/* release: */ +static unsigned short gs_release(int val) +{ + return -(val - 64) * gs_sense[FX_RELEASE] / 50; +} + +/* vibrato freq: 0.042Hz step, max=255 */ +static unsigned short gs_vib_rate(int val) +{ + return (val - 64) * gs_sense[FX_VIBRATE] / 50; +} + +/* vibrato depth: max=127, 1 octave */ +static unsigned short gs_vib_depth(int val) +{ + return (val - 64) * gs_sense[FX_VIBDEPTH] / 50; +} + +/* vibrato delay: -0.725msec step */ +static unsigned short gs_vib_delay(int val) +{ + return -(val - 64) * gs_sense[FX_VIBDELAY] / 50; +} + +static ConvTable gs_effects[] = +{ + {32, AWE_FX_CUTOFF, gs_cutoff}, + {33, AWE_FX_FILTERQ, gs_filterQ}, + {99, AWE_FX_ENV2_ATTACK, gs_attack}, + {100, AWE_FX_ENV2_DECAY, gs_decay}, + {102, AWE_FX_ENV2_RELEASE, gs_release}, + {8, AWE_FX_LFO1_FREQ, gs_vib_rate}, + {9, AWE_FX_LFO1_VOLUME, gs_vib_depth}, + {10, AWE_FX_LFO1_DELAY, gs_vib_delay}, +}; + +static int num_gs_effects = numberof(gs_effects); + + +/* + * NRPN events: accept as AWE32/SC88 specific controls + */ + +static void midi_nrpn_event(MidiStatus *st) +{ + if (rpn_msb[st->chan] == 127 && rpn_lsb[st->chan] <= 26) { + if (! msb_bit) /* both MSB/LSB necessary */ + send_converted_effect(awe_effects, num_awe_effects, + st, rpn_lsb[st->chan], + rpn_val[st->chan] - 8192); + } else if (rpn_msb[st->chan] == 1) { + if (msb_bit) /* only MSB is valid */ + add_converted_effect(gs_effects, num_gs_effects, + st, rpn_lsb[st->chan], + rpn_val[st->chan] / 128); + } +} + + +/* + * XG control effects; still experimental + */ + +/* cutoff: quarter semitone step, max=255 */ +static unsigned short xg_cutoff(int val) +{ + return (val - 64) * xg_sense[FX_CUTOFF] / 64; +} + +/* resonance: 0(open) to 15(most nasal) */ +static unsigned short xg_filterQ(int val) +{ + return (val - 64) * xg_sense[FX_RESONANCE] / 64; +} + +/* attack: */ +static unsigned short xg_attack(int val) +{ + return -(val - 64) * xg_sense[FX_ATTACK] / 64; +} + +/* release: */ +static unsigned short xg_release(int val) +{ + return -(val - 64) * xg_sense[FX_RELEASE] / 64; +} + +static ConvTable xg_effects[] = +{ + {71, AWE_FX_CUTOFF, xg_cutoff}, + {74, AWE_FX_FILTERQ, xg_filterQ}, + {72, AWE_FX_ENV2_RELEASE, xg_release}, + {73, AWE_FX_ENV2_ATTACK, xg_attack}, +}; + +static int num_xg_effects = numberof(xg_effects); + +static int xg_control_change(MidiStatus *st, int cmd, int val) +{ + return add_converted_effect(xg_effects, num_xg_effects, st, cmd, val); +} + +#endif /* CONFIG_AWE32_MIDIEMU */ + + +/*----------------------------------------------------------------*/ + + +/* + * initialization of AWE driver + */ + +static void +awe_initialize(void) +{ + DEBUG(0,printk("AWE32: initializing..\n")); + + /* initialize hardware configuration */ + awe_poke(AWE_HWCF1, 0x0059); + awe_poke(AWE_HWCF2, 0x0020); + + /* disable audio; this seems to reduce a clicking noise a bit.. */ + awe_poke(AWE_HWCF3, 0); + + /* initialize audio channels */ + awe_init_audio(); + + /* initialize DMA */ + awe_init_dma(); + + /* initialize init array */ + awe_init_array(); + + /* check DRAM memory size */ + awe_check_dram(); + + /* initialize the FM section of the AWE32 */ + awe_init_fm(); + + /* set up voice envelopes */ + awe_tweak(); + + /* enable audio */ + awe_poke(AWE_HWCF3, 0x0004); + + /* set default values */ + awe_init_ctrl_parms(TRUE); + + /* set equalizer */ + awe_update_equalizer(); + + /* set reverb & chorus modes */ + awe_update_reverb_mode(); + awe_update_chorus_mode(); +} + + +/* + * Core Device Management Functions + */ + +/* store values to i/o port array */ +static void setup_ports(int port1, int port2, int port3) +{ + awe_ports[0] = port1; + if (port2 == 0) + port2 = port1 + 0x400; + awe_ports[1] = port2; + awe_ports[2] = port2 + 2; + if (port3 == 0) + port3 = port1 + 0x800; + awe_ports[3] = port3; + awe_ports[4] = port3 + 2; + + port_setuped = TRUE; +} + +/* + * port request + * 0x620-623, 0xA20-A23, 0xE20-E23 + */ + +static int +awe_request_region(void) +{ + if (! port_setuped) + return 0; + if (! request_region(awe_ports[0], 4, "sound driver (AWE32)")) + return 0; + if (! request_region(awe_ports[1], 4, "sound driver (AWE32)")) + goto err_out; + if (! request_region(awe_ports[3], 4, "sound driver (AWE32)")) + goto err_out1; + return 1; +err_out1: + release_region(awe_ports[1], 4); +err_out: + release_region(awe_ports[0], 4); + return 0; +} + +static void +awe_release_region(void) +{ + if (! port_setuped) return; + release_region(awe_ports[0], 4); + release_region(awe_ports[1], 4); + release_region(awe_ports[3], 4); +} + +static int awe_attach_device(void) +{ + if (awe_present) return 0; /* for OSS38.. called twice? */ + + /* reserve I/O ports for awedrv */ + if (! awe_request_region()) { + printk(KERN_ERR "AWE32: I/O area already used.\n"); + return 0; + } + + /* set buffers to NULL */ + sfhead = sftail = NULL; + + my_dev = sound_alloc_synthdev(); + if (my_dev == -1) { + printk(KERN_ERR "AWE32 Error: too many synthesizers\n"); + awe_release_region(); + return 0; + } + + voice_alloc = &awe_operations.alloc; + voice_alloc->max_voice = awe_max_voices; + synth_devs[my_dev] = &awe_operations; + +#ifdef CONFIG_AWE32_MIXER + attach_mixer(); +#endif +#ifdef CONFIG_AWE32_MIDIEMU + attach_midiemu(); +#endif + + /* clear all samples */ + awe_reset_samples(); + + /* initialize AWE32 hardware */ + awe_initialize(); + + sprintf(awe_info.name, "AWE32-%s (RAM%dk)", + AWEDRV_VERSION, memsize/1024); + printk(KERN_INFO "\n", memsize/1024); + + awe_present = TRUE; + + return 1; +} + +static void awe_dettach_device(void) +{ + if (awe_present) { + awe_reset_samples(); + awe_release_region(); + free_tables(); +#ifdef CONFIG_AWE32_MIXER + unload_mixer(); +#endif +#ifdef CONFIG_AWE32_MIDIEMU + unload_midiemu(); +#endif + sound_unload_synthdev(my_dev); + awe_present = FALSE; + } +} + + +/* + * Legacy device Probing + */ + +/* detect emu8000 chip on the specified address; from VV's guide */ + +static int __init +awe_detect_base(int addr) +{ + setup_ports(addr, 0, 0); + if ((awe_peek(AWE_U1) & 0x000F) != 0x000C) + return 0; + if ((awe_peek(AWE_HWCF1) & 0x007E) != 0x0058) + return 0; + if ((awe_peek(AWE_HWCF2) & 0x0003) != 0x0003) + return 0; + DEBUG(0,printk("AWE32 found at %x\n", addr)); + return 1; +} + +static int __init awe_detect_legacy_devices(void) +{ + int base; + for (base = 0x620; base <= 0x680; base += 0x20) + if (awe_detect_base(base)) { + awe_attach_device(); + return 1; + } + DEBUG(0,printk("AWE32 Legacy detection failed\n")); + return 0; +} + + +/* + * PnP device Probing + */ + +static struct pnp_device_id awe_pnp_ids[] = { + {.id = "CTL0021", .driver_data = 0}, /* AWE32 WaveTable */ + {.id = "CTL0022", .driver_data = 0}, /* AWE64 WaveTable */ + {.id = "CTL0023", .driver_data = 0}, /* AWE64 Gold WaveTable */ + { } /* terminator */ +}; + +MODULE_DEVICE_TABLE(pnp, awe_pnp_ids); + +static int awe_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) +{ + int io1, io2, io3; + + if (awe_present) { + printk(KERN_ERR "AWE32: This driver only supports one AWE32 device, skipping.\n"); + } + + if (!pnp_port_valid(dev,0) || + !pnp_port_valid(dev,1) || + !pnp_port_valid(dev,2)) { + printk(KERN_ERR "AWE32: The PnP device does not have the required resources.\n"); + return -EINVAL; + } + io1 = pnp_port_start(dev,0); + io2 = pnp_port_start(dev,1); + io3 = pnp_port_start(dev,2); + printk(KERN_INFO "AWE32: A PnP Wave Table was detected at IO's %#x,%#x,%#x\n.", + io1, io2, io3); + setup_ports(io1, io2, io3); + + awe_attach_device(); + return 0; +} + +static void awe_pnp_remove(struct pnp_dev *dev) +{ + awe_dettach_device(); +} + +static struct pnp_driver awe_pnp_driver = { + .name = "AWE32", + .id_table = awe_pnp_ids, + .probe = awe_pnp_probe, + .remove = awe_pnp_remove, +}; + +static int __init awe_detect_pnp_devices(void) +{ + int ret; + + ret = pnp_register_driver(&awe_pnp_driver); + if (ret<0) + printk(KERN_ERR "AWE32: PnP support is unavailable.\n"); + return ret; +} + + +/* + * device / lowlevel (module) interface + */ + +static int __init +awe_detect(void) +{ + printk(KERN_INFO "AWE32: Probing for WaveTable...\n"); + if (isapnp) { + if (awe_detect_pnp_devices()>=0) + return 1; + } else + printk(KERN_INFO "AWE32: Skipping PnP detection.\n"); + + if (awe_detect_legacy_devices()) + return 1; + + return 0; +} + +static int __init attach_awe(void) +{ + return awe_detect() ? 0 : -ENODEV; +} + +static void __exit unload_awe(void) +{ + pnp_unregister_driver(&awe_pnp_driver); + awe_dettach_device(); +} + + +module_init(attach_awe); +module_exit(unload_awe); + +#ifndef MODULE +static int __init setup_awe(char *str) +{ + /* io, memsize, isapnp */ + int ints[4]; + + str = get_options(str, ARRAY_SIZE(ints), ints); + + io = ints[1]; + memsize = ints[2]; + isapnp = ints[3]; + + return 1; +} + +__setup("awe=", setup_awe); +#endif diff --git a/sound/oss/awe_wave.h b/sound/oss/awe_wave.h new file mode 100644 index 000000000000..a3aa018118bd --- /dev/null +++ b/sound/oss/awe_wave.h @@ -0,0 +1,77 @@ +/* + * sound/awe_config.h + * + * Configuration of AWE32/SB32/AWE64 wave table synth driver. + * version 0.4.4; Jan. 4, 2000 + * + * Copyright (C) 1996-1998 Takashi Iwai + * + * 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. + */ + +/* + * chorus & reverb effects send for FM chip: from 0 to 0xff + * larger numbers often cause weird sounds. + */ + +#define DEF_FM_CHORUS_DEPTH 0x10 +#define DEF_FM_REVERB_DEPTH 0x10 + + +/* + * other compile conditions + */ + +/* initialize FM passthrough even without extended RAM */ +#undef AWE_ALWAYS_INIT_FM + +/* debug on */ +#define AWE_DEBUG_ON + +/* GUS compatible mode */ +#define AWE_HAS_GUS_COMPATIBILITY + +/* add MIDI emulation by wavetable */ +#define CONFIG_AWE32_MIDIEMU + +/* add mixer control of emu8000 equalizer */ +#undef CONFIG_AWE32_MIXER + +/* use new volume calculation method as default */ +#define AWE_USE_NEW_VOLUME_CALC + +/* check current volume target for searching empty voices */ +#define AWE_CHECK_VTARGET + +/* allow sample sharing */ +#define AWE_ALLOW_SAMPLE_SHARING + +/* + * AWE32 card configuration: + * uncomment the following lines *ONLY* when auto detection doesn't + * work properly on your machine. + */ + +/*#define AWE_DEFAULT_BASE_ADDR 0x620*/ /* base port address */ +/*#define AWE_DEFAULT_MEM_SIZE 512*/ /* kbytes */ + +/* + * AWE driver version number + */ +#define AWE_MAJOR_VERSION 0 +#define AWE_MINOR_VERSION 4 +#define AWE_TINY_VERSION 4 +#define AWE_VERSION_NUMBER ((AWE_MAJOR_VERSION<<16)|(AWE_MINOR_VERSION<<8)|AWE_TINY_VERSION) +#define AWEDRV_VERSION "0.4.4" diff --git a/sound/oss/bin2hex.c b/sound/oss/bin2hex.c new file mode 100644 index 000000000000..b59109eb0db4 --- /dev/null +++ b/sound/oss/bin2hex.c @@ -0,0 +1,39 @@ +#include +#include +#include + +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/btaudio.c b/sound/oss/btaudio.c new file mode 100644 index 000000000000..a85093fec7be --- /dev/null +++ b/sound/oss/btaudio.c @@ -0,0 +1,1136 @@ +/* + btaudio - bt878 audio dma driver for linux 2.4.x + + (c) 2000-2002 Gerd Knorr + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* mmio access */ +#define btwrite(dat,adr) writel((dat), (bta->mmio+(adr))) +#define btread(adr) readl(bta->mmio+(adr)) + +#define btand(dat,adr) btwrite((dat) & btread(adr), adr) +#define btor(dat,adr) btwrite((dat) | btread(adr), adr) +#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr) + +/* registers (shifted because bta->mmio is long) */ +#define REG_INT_STAT (0x100 >> 2) +#define REG_INT_MASK (0x104 >> 2) +#define REG_GPIO_DMA_CTL (0x10c >> 2) +#define REG_PACKET_LEN (0x110 >> 2) +#define REG_RISC_STRT_ADD (0x114 >> 2) +#define REG_RISC_COUNT (0x120 >> 2) + +/* IRQ bits - REG_INT_(STAT|MASK) */ +#define IRQ_SCERR (1 << 19) +#define IRQ_OCERR (1 << 18) +#define IRQ_PABORT (1 << 17) +#define IRQ_RIPERR (1 << 16) +#define IRQ_PPERR (1 << 15) +#define IRQ_FDSR (1 << 14) +#define IRQ_FTRGT (1 << 13) +#define IRQ_FBUS (1 << 12) +#define IRQ_RISCI (1 << 11) +#define IRQ_OFLOW (1 << 3) + +#define IRQ_BTAUDIO (IRQ_SCERR | IRQ_OCERR | IRQ_PABORT | IRQ_RIPERR |\ + IRQ_PPERR | IRQ_FDSR | IRQ_FTRGT | IRQ_FBUS |\ + IRQ_RISCI) + +/* REG_GPIO_DMA_CTL bits */ +#define DMA_CTL_A_PWRDN (1 << 26) +#define DMA_CTL_DA_SBR (1 << 14) +#define DMA_CTL_DA_ES2 (1 << 13) +#define DMA_CTL_ACAP_EN (1 << 4) +#define DMA_CTL_RISC_EN (1 << 1) +#define DMA_CTL_FIFO_EN (1 << 0) + +/* RISC instructions */ +#define RISC_WRITE (0x01 << 28) +#define RISC_JUMP (0x07 << 28) +#define RISC_SYNC (0x08 << 28) + +/* RISC bits */ +#define RISC_WR_SOL (1 << 27) +#define RISC_WR_EOL (1 << 26) +#define RISC_IRQ (1 << 24) +#define RISC_SYNC_RESYNC (1 << 15) +#define RISC_SYNC_FM1 0x06 +#define RISC_SYNC_VRO 0x0c + +#define HWBASE_AD (448000) + +/* -------------------------------------------------------------- */ + +struct btaudio { + /* linked list */ + struct btaudio *next; + + /* device info */ + int dsp_digital; + int dsp_analog; + int mixer_dev; + struct pci_dev *pci; + unsigned int irq; + unsigned long mem; + unsigned long __iomem *mmio; + + /* locking */ + int users; + struct semaphore lock; + + /* risc instructions */ + unsigned int risc_size; + unsigned long *risc_cpu; + dma_addr_t risc_dma; + + /* audio data */ + unsigned int buf_size; + unsigned char *buf_cpu; + dma_addr_t buf_dma; + + /* buffer setup */ + int line_bytes; + int line_count; + int block_bytes; + int block_count; + + /* read fifo management */ + int recording; + int dma_block; + int read_offset; + int read_count; + wait_queue_head_t readq; + + /* settings */ + int gain[3]; + int source; + int bits; + int decimation; + int mixcount; + int sampleshift; + int channels; + int analog; + int rate; +}; + +struct cardinfo { + char *name; + int rate; +}; + +static struct btaudio *btaudios; +static unsigned int debug; +static unsigned int irq_debug; + +/* -------------------------------------------------------------- */ + +#define BUF_DEFAULT 128*1024 +#define BUF_MIN 8192 + +static int alloc_buffer(struct btaudio *bta) +{ + if (NULL == bta->buf_cpu) { + for (bta->buf_size = BUF_DEFAULT; bta->buf_size >= BUF_MIN; + bta->buf_size = bta->buf_size >> 1) { + bta->buf_cpu = pci_alloc_consistent + (bta->pci, bta->buf_size, &bta->buf_dma); + if (NULL != bta->buf_cpu) + break; + } + if (NULL == bta->buf_cpu) + return -ENOMEM; + memset(bta->buf_cpu,0,bta->buf_size); + } + if (NULL == bta->risc_cpu) { + bta->risc_size = PAGE_SIZE; + bta->risc_cpu = pci_alloc_consistent + (bta->pci, bta->risc_size, &bta->risc_dma); + if (NULL == bta->risc_cpu) { + pci_free_consistent(bta->pci, bta->buf_size, bta->buf_cpu, bta->buf_dma); + bta->buf_cpu = NULL; + return -ENOMEM; + } + } + return 0; +} + +static void free_buffer(struct btaudio *bta) +{ + if (NULL != bta->buf_cpu) { + pci_free_consistent(bta->pci, bta->buf_size, + bta->buf_cpu, bta->buf_dma); + bta->buf_cpu = NULL; + } + if (NULL != bta->risc_cpu) { + pci_free_consistent(bta->pci, bta->risc_size, + bta->risc_cpu, bta->risc_dma); + bta->risc_cpu = NULL; + } +} + +static int make_risc(struct btaudio *bta) +{ + int rp, bp, line, block; + unsigned long risc; + + bta->block_bytes = bta->buf_size >> 4; + bta->block_count = 1 << 4; + bta->line_bytes = bta->block_bytes; + bta->line_count = bta->block_count; + while (bta->line_bytes > 4095) { + bta->line_bytes >>= 1; + bta->line_count <<= 1; + } + if (bta->line_count > 255) + return -EINVAL; + if (debug) + printk(KERN_DEBUG + "btaudio: bufsize=%d - bs=%d bc=%d - ls=%d, lc=%d\n", + bta->buf_size,bta->block_bytes,bta->block_count, + bta->line_bytes,bta->line_count); + rp = 0; bp = 0; + block = 0; + bta->risc_cpu[rp++] = cpu_to_le32(RISC_SYNC|RISC_SYNC_FM1); + bta->risc_cpu[rp++] = cpu_to_le32(0); + for (line = 0; line < bta->line_count; line++) { + risc = RISC_WRITE | RISC_WR_SOL | RISC_WR_EOL; + risc |= bta->line_bytes; + if (0 == (bp & (bta->block_bytes-1))) { + risc |= RISC_IRQ; + risc |= (block & 0x0f) << 16; + risc |= (~block & 0x0f) << 20; + block++; + } + bta->risc_cpu[rp++] = cpu_to_le32(risc); + bta->risc_cpu[rp++] = cpu_to_le32(bta->buf_dma + bp); + bp += bta->line_bytes; + } + bta->risc_cpu[rp++] = cpu_to_le32(RISC_SYNC|RISC_SYNC_VRO); + bta->risc_cpu[rp++] = cpu_to_le32(0); + bta->risc_cpu[rp++] = cpu_to_le32(RISC_JUMP); + bta->risc_cpu[rp++] = cpu_to_le32(bta->risc_dma); + return 0; +} + +static int start_recording(struct btaudio *bta) +{ + int ret; + + if (0 != (ret = alloc_buffer(bta))) + return ret; + if (0 != (ret = make_risc(bta))) + return ret; + + btwrite(bta->risc_dma, REG_RISC_STRT_ADD); + btwrite((bta->line_count << 16) | bta->line_bytes, + REG_PACKET_LEN); + btwrite(IRQ_BTAUDIO, REG_INT_MASK); + if (bta->analog) { + btwrite(DMA_CTL_ACAP_EN | + DMA_CTL_RISC_EN | + DMA_CTL_FIFO_EN | + DMA_CTL_DA_ES2 | + ((bta->bits == 8) ? DMA_CTL_DA_SBR : 0) | + (bta->gain[bta->source] << 28) | + (bta->source << 24) | + (bta->decimation << 8), + REG_GPIO_DMA_CTL); + } else { + btwrite(DMA_CTL_ACAP_EN | + DMA_CTL_RISC_EN | + DMA_CTL_FIFO_EN | + DMA_CTL_DA_ES2 | + DMA_CTL_A_PWRDN | + (1 << 6) | + ((bta->bits == 8) ? DMA_CTL_DA_SBR : 0) | + (bta->gain[bta->source] << 28) | + (bta->source << 24) | + (bta->decimation << 8), + REG_GPIO_DMA_CTL); + } + bta->dma_block = 0; + bta->read_offset = 0; + bta->read_count = 0; + bta->recording = 1; + if (debug) + printk(KERN_DEBUG "btaudio: recording started\n"); + return 0; +} + +static void stop_recording(struct btaudio *bta) +{ + btand(~15, REG_GPIO_DMA_CTL); + bta->recording = 0; + if (debug) + printk(KERN_DEBUG "btaudio: recording stopped\n"); +} + + +/* -------------------------------------------------------------- */ + +static int btaudio_mixer_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct btaudio *bta; + + for (bta = btaudios; bta != NULL; bta = bta->next) + if (bta->mixer_dev == minor) + break; + if (NULL == bta) + return -ENODEV; + + if (debug) + printk("btaudio: open mixer [%d]\n",minor); + file->private_data = bta; + return 0; +} + +static int btaudio_mixer_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int btaudio_mixer_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct btaudio *bta = file->private_data; + int ret,val=0,i=0; + void __user *argp = (void __user *)arg; + + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + memset(&info,0,sizeof(info)); + strlcpy(info.id,"bt878",sizeof(info.id)); + strlcpy(info.name,"Brooktree Bt878 audio",sizeof(info.name)); + info.modify_counter = bta->mixcount; + if (copy_to_user(argp, &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,"bt878",sizeof(info.id)-1); + strlcpy(info.name,"Brooktree Bt878 audio",sizeof(info.name)); + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, (int __user *)argp); + + /* read */ + if (_SIOC_DIR(cmd) & _SIOC_WRITE) + if (get_user(val, (int __user *)argp)) + return -EFAULT; + + switch (cmd) { + case MIXER_READ(SOUND_MIXER_CAPS): + ret = SOUND_CAP_EXCL_INPUT; + break; + case MIXER_READ(SOUND_MIXER_STEREODEVS): + ret = 0; + break; + case MIXER_READ(SOUND_MIXER_RECMASK): + case MIXER_READ(SOUND_MIXER_DEVMASK): + ret = SOUND_MASK_LINE1|SOUND_MASK_LINE2|SOUND_MASK_LINE3; + break; + + case MIXER_WRITE(SOUND_MIXER_RECSRC): + if (val & SOUND_MASK_LINE1 && bta->source != 0) + bta->source = 0; + else if (val & SOUND_MASK_LINE2 && bta->source != 1) + bta->source = 1; + else if (val & SOUND_MASK_LINE3 && bta->source != 2) + bta->source = 2; + btaor((bta->gain[bta->source] << 28) | + (bta->source << 24), + 0x0cffffff, REG_GPIO_DMA_CTL); + case MIXER_READ(SOUND_MIXER_RECSRC): + switch (bta->source) { + case 0: ret = SOUND_MASK_LINE1; break; + case 1: ret = SOUND_MASK_LINE2; break; + case 2: ret = SOUND_MASK_LINE3; break; + default: ret = 0; + } + break; + + case MIXER_WRITE(SOUND_MIXER_LINE1): + case MIXER_WRITE(SOUND_MIXER_LINE2): + case MIXER_WRITE(SOUND_MIXER_LINE3): + if (MIXER_WRITE(SOUND_MIXER_LINE1) == cmd) + i = 0; + if (MIXER_WRITE(SOUND_MIXER_LINE2) == cmd) + i = 1; + if (MIXER_WRITE(SOUND_MIXER_LINE3) == cmd) + i = 2; + bta->gain[i] = (val & 0xff) * 15 / 100; + if (bta->gain[i] > 15) bta->gain[i] = 15; + if (bta->gain[i] < 0) bta->gain[i] = 0; + if (i == bta->source) + btaor((bta->gain[bta->source]<<28), + 0x0fffffff, REG_GPIO_DMA_CTL); + ret = bta->gain[i] * 100 / 15; + ret |= ret << 8; + break; + + case MIXER_READ(SOUND_MIXER_LINE1): + case MIXER_READ(SOUND_MIXER_LINE2): + case MIXER_READ(SOUND_MIXER_LINE3): + if (MIXER_READ(SOUND_MIXER_LINE1) == cmd) + i = 0; + if (MIXER_READ(SOUND_MIXER_LINE2) == cmd) + i = 1; + if (MIXER_READ(SOUND_MIXER_LINE3) == cmd) + i = 2; + ret = bta->gain[i] * 100 / 15; + ret |= ret << 8; + break; + + default: + return -EINVAL; + } + if (put_user(ret, (int __user *)argp)) + return -EFAULT; + return 0; +} + +static struct file_operations btaudio_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = btaudio_mixer_open, + .release = btaudio_mixer_release, + .ioctl = btaudio_mixer_ioctl, +}; + +/* -------------------------------------------------------------- */ + +static int btaudio_dsp_open(struct inode *inode, struct file *file, + struct btaudio *bta, int analog) +{ + down(&bta->lock); + if (bta->users) + goto busy; + bta->users++; + file->private_data = bta; + + bta->analog = analog; + bta->dma_block = 0; + bta->read_offset = 0; + bta->read_count = 0; + bta->sampleshift = 0; + + up(&bta->lock); + return 0; + + busy: + up(&bta->lock); + return -EBUSY; +} + +static int btaudio_dsp_open_digital(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct btaudio *bta; + + for (bta = btaudios; bta != NULL; bta = bta->next) + if (bta->dsp_digital == minor) + break; + if (NULL == bta) + return -ENODEV; + + if (debug) + printk("btaudio: open digital dsp [%d]\n",minor); + return btaudio_dsp_open(inode,file,bta,0); +} + +static int btaudio_dsp_open_analog(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct btaudio *bta; + + for (bta = btaudios; bta != NULL; bta = bta->next) + if (bta->dsp_analog == minor) + break; + if (NULL == bta) + return -ENODEV; + + if (debug) + printk("btaudio: open analog dsp [%d]\n",minor); + return btaudio_dsp_open(inode,file,bta,1); +} + +static int btaudio_dsp_release(struct inode *inode, struct file *file) +{ + struct btaudio *bta = file->private_data; + + down(&bta->lock); + if (bta->recording) + stop_recording(bta); + bta->users--; + up(&bta->lock); + return 0; +} + +static ssize_t btaudio_dsp_read(struct file *file, char __user *buffer, + size_t swcount, loff_t *ppos) +{ + struct btaudio *bta = file->private_data; + int hwcount = swcount << bta->sampleshift; + int nsrc, ndst, err, ret = 0; + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&bta->readq, &wait); + down(&bta->lock); + while (swcount > 0) { + if (0 == bta->read_count) { + if (!bta->recording) { + if (0 != (err = start_recording(bta))) { + if (0 == ret) + ret = err; + break; + } + } + if (file->f_flags & O_NONBLOCK) { + if (0 == ret) + ret = -EAGAIN; + break; + } + up(&bta->lock); + current->state = TASK_INTERRUPTIBLE; + schedule(); + down(&bta->lock); + if(signal_pending(current)) { + if (0 == ret) + ret = -EINTR; + break; + } + } + nsrc = (bta->read_count < hwcount) ? bta->read_count : hwcount; + if (nsrc > bta->buf_size - bta->read_offset) + nsrc = bta->buf_size - bta->read_offset; + ndst = nsrc >> bta->sampleshift; + + if ((bta->analog && 0 == bta->sampleshift) || + (!bta->analog && 2 == bta->channels)) { + /* just copy */ + if (copy_to_user(buffer + ret, bta->buf_cpu + bta->read_offset, nsrc)) { + if (0 == ret) + ret = -EFAULT; + break; + } + + } else if (!bta->analog) { + /* stereo => mono (digital audio) */ + __s16 *src = (__s16*)(bta->buf_cpu + bta->read_offset); + __s16 __user *dst = (__s16 __user *)(buffer + ret); + __s16 avg; + int n = ndst>>1; + if (!access_ok(VERIFY_WRITE, dst, ndst)) { + if (0 == ret) + ret = -EFAULT; + break; + } + for (; n; n--, dst++) { + avg = (__s16)le16_to_cpu(*src) / 2; src++; + avg += (__s16)le16_to_cpu(*src) / 2; src++; + __put_user(cpu_to_le16(avg),dst); + } + + } else if (8 == bta->bits) { + /* copy + byte downsampling (audio A/D) */ + __u8 *src = bta->buf_cpu + bta->read_offset; + __u8 __user *dst = buffer + ret; + int n = ndst; + if (!access_ok(VERIFY_WRITE, dst, ndst)) { + if (0 == ret) + ret = -EFAULT; + break; + } + for (; n; n--, src += (1 << bta->sampleshift), dst++) + __put_user(*src, dst); + + } else { + /* copy + word downsampling (audio A/D) */ + __u16 *src = (__u16*)(bta->buf_cpu + bta->read_offset); + __u16 __user *dst = (__u16 __user *)(buffer + ret); + int n = ndst>>1; + if (!access_ok(VERIFY_WRITE,dst,ndst)) { + if (0 == ret) + ret = -EFAULT; + break; + } + for (; n; n--, src += (1 << bta->sampleshift), dst++) + __put_user(*src, dst); + } + + ret += ndst; + swcount -= ndst; + hwcount -= nsrc; + bta->read_count -= nsrc; + bta->read_offset += nsrc; + if (bta->read_offset == bta->buf_size) + bta->read_offset = 0; + } + up(&bta->lock); + remove_wait_queue(&bta->readq, &wait); + current->state = TASK_RUNNING; + return ret; +} + +static ssize_t btaudio_dsp_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +static int btaudio_dsp_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct btaudio *bta = file->private_data; + int s, i, ret, val = 0; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, p); + case SNDCTL_DSP_GETCAPS: + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + if (bta->analog) { + for (s = 0; s < 16; s++) + if (val << s >= HWBASE_AD*4/15) + break; + for (i = 15; i >= 5; i--) + if (val << s <= HWBASE_AD*4/i) + break; + bta->sampleshift = s; + bta->decimation = i; + if (debug) + printk(KERN_DEBUG "btaudio: rate: req=%d " + "dec=%d shift=%d hwrate=%d swrate=%d\n", + val,i,s,(HWBASE_AD*4/i),(HWBASE_AD*4/i)>>s); + } else { + bta->sampleshift = (bta->channels == 2) ? 0 : 1; + bta->decimation = 0; + } + if (bta->recording) { + down(&bta->lock); + stop_recording(bta); + start_recording(bta); + up(&bta->lock); + } + /* fall through */ + case SOUND_PCM_READ_RATE: + if (bta->analog) { + return put_user(HWBASE_AD*4/bta->decimation>>bta->sampleshift, p); + } else { + return put_user(bta->rate, p); + } + + case SNDCTL_DSP_STEREO: + if (!bta->analog) { + if (get_user(val, p)) + return -EFAULT; + bta->channels = (val > 0) ? 2 : 1; + bta->sampleshift = (bta->channels == 2) ? 0 : 1; + if (debug) + printk(KERN_INFO + "btaudio: stereo=%d channels=%d\n", + val,bta->channels); + } else { + if (val == 1) + return -EFAULT; + else { + bta->channels = 1; + if (debug) + printk(KERN_INFO + "btaudio: stereo=0 channels=1\n"); + } + } + return put_user((bta->channels)-1, p); + + case SNDCTL_DSP_CHANNELS: + if (!bta->analog) { + if (get_user(val, p)) + return -EFAULT; + bta->channels = (val > 1) ? 2 : 1; + bta->sampleshift = (bta->channels == 2) ? 0 : 1; + if (debug) + printk(KERN_DEBUG + "btaudio: val=%d channels=%d\n", + val,bta->channels); + } + /* fall through */ + case SOUND_PCM_READ_CHANNELS: + return put_user(bta->channels, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + if (bta->analog) + return put_user(AFMT_S16_LE|AFMT_S8, p); + else + return put_user(AFMT_S16_LE, p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, p)) + return -EFAULT; + if (val != AFMT_QUERY) { + if (bta->analog) + bta->bits = (val == AFMT_S8) ? 8 : 16; + else + bta->bits = 16; + if (bta->recording) { + down(&bta->lock); + stop_recording(bta); + start_recording(bta); + up(&bta->lock); + } + } + if (debug) + printk(KERN_DEBUG "btaudio: fmt: bits=%d\n",bta->bits); + return put_user((bta->bits==16) ? AFMT_S16_LE : AFMT_S8, + p); + break; + case SOUND_PCM_READ_BITS: + return put_user(bta->bits, p); + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_RESET: + if (bta->recording) { + down(&bta->lock); + stop_recording(bta); + up(&bta->lock); + } + return 0; + case SNDCTL_DSP_GETBLKSIZE: + if (!bta->recording) { + if (0 != (ret = alloc_buffer(bta))) + return ret; + if (0 != (ret = make_risc(bta))) + return ret; + } + return put_user(bta->block_bytes>>bta->sampleshift,p); + + case SNDCTL_DSP_SYNC: + /* NOP */ + return 0; + case SNDCTL_DSP_GETISPACE: + { + audio_buf_info info; + if (!bta->recording) + return -EINVAL; + info.fragsize = bta->block_bytes>>bta->sampleshift; + info.fragstotal = bta->block_count; + info.bytes = bta->read_count; + info.fragments = info.bytes / info.fragsize; + if (debug) + printk(KERN_DEBUG "btaudio: SNDCTL_DSP_GETISPACE " + "returns %d/%d/%d/%d\n", + info.fragsize, info.fragstotal, + info.bytes, info.fragments); + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + return 0; + } +#if 0 /* TODO */ + case SNDCTL_DSP_GETTRIGGER: + case SNDCTL_DSP_SETTRIGGER: + case SNDCTL_DSP_SETFRAGMENT: +#endif + default: + return -EINVAL; + } +} + +static unsigned int btaudio_dsp_poll(struct file *file, struct poll_table_struct *wait) +{ + struct btaudio *bta = file->private_data; + unsigned int mask = 0; + + poll_wait(file, &bta->readq, wait); + + if (0 != bta->read_count) + mask |= (POLLIN | POLLRDNORM); + + return mask; +} + +static struct file_operations btaudio_digital_dsp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = btaudio_dsp_open_digital, + .release = btaudio_dsp_release, + .read = btaudio_dsp_read, + .write = btaudio_dsp_write, + .ioctl = btaudio_dsp_ioctl, + .poll = btaudio_dsp_poll, +}; + +static struct file_operations btaudio_analog_dsp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = btaudio_dsp_open_analog, + .release = btaudio_dsp_release, + .read = btaudio_dsp_read, + .write = btaudio_dsp_write, + .ioctl = btaudio_dsp_ioctl, + .poll = btaudio_dsp_poll, +}; + +/* -------------------------------------------------------------- */ + +static char *irq_name[] = { "", "", "", "OFLOW", "", "", "", "", "", "", "", + "RISCI", "FBUS", "FTRGT", "FDSR", "PPERR", + "RIPERR", "PABORT", "OCERR", "SCERR" }; + +static irqreturn_t btaudio_irq(int irq, void *dev_id, struct pt_regs * regs) +{ + int count = 0; + u32 stat,astat; + struct btaudio *bta = dev_id; + int handled = 0; + + for (;;) { + count++; + stat = btread(REG_INT_STAT); + astat = stat & btread(REG_INT_MASK); + if (!astat) + return IRQ_RETVAL(handled); + handled = 1; + btwrite(astat,REG_INT_STAT); + + if (irq_debug) { + int i; + printk(KERN_DEBUG "btaudio: irq loop=%d risc=%x, bits:", + count, stat>>28); + for (i = 0; i < (sizeof(irq_name)/sizeof(char*)); i++) { + if (stat & (1 << i)) + printk(" %s",irq_name[i]); + if (astat & (1 << i)) + printk("*"); + } + printk("\n"); + } + if (stat & IRQ_RISCI) { + int blocks; + blocks = (stat >> 28) - bta->dma_block; + if (blocks < 0) + blocks += bta->block_count; + bta->dma_block = stat >> 28; + if (bta->read_count + 2*bta->block_bytes > bta->buf_size) { + stop_recording(bta); + printk(KERN_INFO "btaudio: buffer overrun\n"); + } + if (blocks > 0) { + bta->read_count += blocks * bta->block_bytes; + wake_up_interruptible(&bta->readq); + } + } + if (count > 10) { + printk(KERN_WARNING + "btaudio: Oops - irq mask cleared\n"); + btwrite(0, REG_INT_MASK); + } + } + return IRQ_NONE; +} + +/* -------------------------------------------------------------- */ + +static unsigned int dsp1 = -1; +static unsigned int dsp2 = -1; +static unsigned int mixer = -1; +static int latency = -1; +static int digital = 1; +static int analog = 1; +static int rate; + +#define BTA_OSPREY200 1 + +static struct cardinfo cards[] = { + [0] = { + .name = "default", + .rate = 32000, + }, + [BTA_OSPREY200] = { + .name = "Osprey 200", + .rate = 44100, + }, +}; + +static int __devinit btaudio_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct btaudio *bta; + struct cardinfo *card = &cards[pci_id->driver_data]; + unsigned char revision,lat; + int rc = -EBUSY; + + if (pci_enable_device(pci_dev)) + return -EIO; + if (!request_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0), + "btaudio")) { + return -EBUSY; + } + + bta = kmalloc(sizeof(*bta),GFP_ATOMIC); + if (!bta) { + rc = -ENOMEM; + goto fail0; + } + memset(bta,0,sizeof(*bta)); + + bta->pci = pci_dev; + bta->irq = pci_dev->irq; + bta->mem = pci_resource_start(pci_dev,0); + bta->mmio = ioremap(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); + + bta->source = 1; + bta->bits = 8; + bta->channels = 1; + if (bta->analog) { + bta->decimation = 15; + } else { + bta->decimation = 0; + bta->sampleshift = 1; + } + + /* sample rate */ + bta->rate = card->rate; + if (rate) + bta->rate = rate; + + init_MUTEX(&bta->lock); + init_waitqueue_head(&bta->readq); + + if (-1 != latency) { + printk(KERN_INFO "btaudio: setting pci latency timer to %d\n", + latency); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency); + } + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &revision); + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &lat); + printk(KERN_INFO "btaudio: Bt%x (rev %d) at %02x:%02x.%x, ", + pci_dev->device,revision,pci_dev->bus->number, + PCI_SLOT(pci_dev->devfn),PCI_FUNC(pci_dev->devfn)); + printk("irq: %d, latency: %d, mmio: 0x%lx\n", + bta->irq, lat, bta->mem); + printk("btaudio: using card config \"%s\"\n", card->name); + + /* init hw */ + btwrite(0, REG_GPIO_DMA_CTL); + btwrite(0, REG_INT_MASK); + btwrite(~0U, REG_INT_STAT); + pci_set_master(pci_dev); + + if ((rc = request_irq(bta->irq, btaudio_irq, SA_SHIRQ|SA_INTERRUPT, + "btaudio",(void *)bta)) < 0) { + printk(KERN_WARNING + "btaudio: can't request irq (rc=%d)\n",rc); + goto fail1; + } + + /* register devices */ + if (digital) { + rc = bta->dsp_digital = + register_sound_dsp(&btaudio_digital_dsp_fops,dsp1); + if (rc < 0) { + printk(KERN_WARNING + "btaudio: can't register digital dsp (rc=%d)\n",rc); + goto fail2; + } + printk(KERN_INFO "btaudio: registered device dsp%d [digital]\n", + bta->dsp_digital >> 4); + } + if (analog) { + rc = bta->dsp_analog = + register_sound_dsp(&btaudio_analog_dsp_fops,dsp2); + if (rc < 0) { + printk(KERN_WARNING + "btaudio: can't register analog dsp (rc=%d)\n",rc); + goto fail3; + } + printk(KERN_INFO "btaudio: registered device dsp%d [analog]\n", + bta->dsp_analog >> 4); + rc = bta->mixer_dev = register_sound_mixer(&btaudio_mixer_fops,mixer); + if (rc < 0) { + printk(KERN_WARNING + "btaudio: can't register mixer (rc=%d)\n",rc); + goto fail4; + } + printk(KERN_INFO "btaudio: registered device mixer%d\n", + bta->mixer_dev >> 4); + } + + /* hook into linked list */ + bta->next = btaudios; + btaudios = bta; + + pci_set_drvdata(pci_dev,bta); + return 0; + + fail4: + unregister_sound_dsp(bta->dsp_analog); + fail3: + if (digital) + unregister_sound_dsp(bta->dsp_digital); + fail2: + free_irq(bta->irq,bta); + fail1: + kfree(bta); + fail0: + release_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); + return rc; +} + +static void __devexit btaudio_remove(struct pci_dev *pci_dev) +{ + struct btaudio *bta = pci_get_drvdata(pci_dev); + struct btaudio *walk; + + /* turn off all DMA / IRQs */ + btand(~15, REG_GPIO_DMA_CTL); + btwrite(0, REG_INT_MASK); + btwrite(~0U, REG_INT_STAT); + + /* unregister devices */ + if (digital) { + unregister_sound_dsp(bta->dsp_digital); + } + if (analog) { + unregister_sound_dsp(bta->dsp_analog); + unregister_sound_mixer(bta->mixer_dev); + } + + /* free resources */ + free_buffer(bta); + free_irq(bta->irq,bta); + release_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); + + /* remove from linked list */ + if (bta == btaudios) { + btaudios = NULL; + } else { + for (walk = btaudios; walk->next != bta; walk = walk->next) + ; /* if (NULL == walk->next) BUG(); */ + walk->next = bta->next; + } + + pci_set_drvdata(pci_dev, NULL); + kfree(bta); + return; +} + +/* -------------------------------------------------------------- */ + +static struct pci_device_id btaudio_pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_BROOKTREE, + .device = 0x0878, + .subvendor = 0x0070, + .subdevice = 0xff01, + .driver_data = BTA_OSPREY200, + },{ + .vendor = PCI_VENDOR_ID_BROOKTREE, + .device = 0x0878, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + },{ + .vendor = PCI_VENDOR_ID_BROOKTREE, + .device = 0x0878, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + },{ + /* --- end of list --- */ + } +}; + +static struct pci_driver btaudio_pci_driver = { + .name = "btaudio", + .id_table = btaudio_pci_tbl, + .probe = btaudio_probe, + .remove = __devexit_p(btaudio_remove), +}; + +static int btaudio_init_module(void) +{ + printk(KERN_INFO "btaudio: driver version 0.7 loaded [%s%s%s]\n", + digital ? "digital" : "", + analog && digital ? "+" : "", + analog ? "analog" : ""); + return pci_module_init(&btaudio_pci_driver); +} + +static void btaudio_cleanup_module(void) +{ + pci_unregister_driver(&btaudio_pci_driver); + return; +} + +module_init(btaudio_init_module); +module_exit(btaudio_cleanup_module); + +module_param(dsp1, int, S_IRUGO); +module_param(dsp2, int, S_IRUGO); +module_param(mixer, int, S_IRUGO); +module_param(debug, int, S_IRUGO | S_IWUSR); +module_param(irq_debug, int, S_IRUGO | S_IWUSR); +module_param(digital, int, S_IRUGO); +module_param(analog, int, S_IRUGO); +module_param(rate, int, S_IRUGO); +module_param(latency, int, S_IRUGO); +MODULE_PARM_DESC(latency,"pci latency timer"); + +MODULE_DEVICE_TABLE(pci, btaudio_pci_tbl); +MODULE_DESCRIPTION("bt878 audio dma driver"); +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("GPL"); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/sound/oss/cmpci.c b/sound/oss/cmpci.c new file mode 100644 index 000000000000..34720e66dae1 --- /dev/null +++ b/sound/oss/cmpci.c @@ -0,0 +1,3378 @@ +/* + * cmpci.c -- C-Media PCI audio driver. + * + * Copyright (C) 1999 C-media support (support@cmedia.com.tw) + * + * Based on the PCI drivers by Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * For update, visit: + * http://www.cmedia.com.tw + * + * 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. + * + * Special thanks to David C. Niemi, Jan Pfeifer + * + * + * Module command line parameters: + * none so far + * + * + * 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 + * + * The card has both an FM and a Wavetable synth, but I have to figure + * out first how to drive them... + * + * Revision history + * 06.05.98 0.1 Initial release + * 10.05.98 0.2 Fixed many bugs, esp. ADC rate calculation + * First stab at a simple midi interface (no bells&whistles) + * 13.05.98 0.3 Fix stupid cut&paste error: set_adc_rate was called instead of + * set_dac_rate in the FMODE_WRITE case in cm_open + * Fix hwptr out of bounds (now mpg123 works) + * 14.05.98 0.4 Don't allow excessive interrupt rates + * 08.06.98 0.5 First release using Alan Cox' soundcore instead of miscdevice + * 03.08.98 0.6 Do not include modversions.h + * Now mixer behaviour can basically be selected between + * "OSS documented" and "OSS actual" behaviour + * 31.08.98 0.7 Fix realplayer problems - dac.count issues + * 10.12.98 0.8 Fix drain_dac trying to wait on not yet initialized DMA + * 16.12.98 0.9 Fix a few f_file & FMODE_ bugs + * 06.01.99 0.10 remove the silly SA_INTERRUPT flag. + * hopefully killed the egcs section type conflict + * 12.03.99 0.11 cinfo.blocks should be reset after GETxPTR ioctl. + * reported by Johan Maes + * 22.03.99 0.12 return EAGAIN instead of EBUSY when O_NONBLOCK + * read/write cannot be executed + * 18.08.99 1.5 Only deallocate DMA buffer when unloading. + * 02.09.99 1.6 Enable SPDIF LOOP + * Change the mixer read back + * 21.09.99 2.33 Use RCS version as driver version. + * Add support for modem, S/PDIF loop and 4 channels. + * (8738 only) + * Fix bug cause x11amp cannot play. + * + * Fixes: + * Arnaldo Carvalho de Melo + * 18/05/2001 - .bss nitpicks, fix a bug in set_dac_channels where it + * was calling prog_dmabuf with s->lock held, call missing + * unlock_kernel in cm_midi_release + * 08/10/2001 - use set_current_state in some more places + * + * Carlos Eduardo Gorges + * Fri May 25 2001 + * - SMP support ( spin[un]lock* revision ) + * - speaker mixer support + * Mon Aug 13 2001 + * - optimizations and cleanups + * + * 03/01/2003 - open_mode fixes from Georg Acher + * Simon Braunschmidt + * Sat Jan 31 2004 + * - provide support for opl3 FM by releasing IO range after initialization + * + * ChenLi Tien + * Mar 9 2004 + * - Fix S/PDIF out if spdif_loop enabled + * - Load opl3 driver if enabled (fmio in proper range) + * - Load mpu401 if enabled (mpuio in proper range) + * Apr 5 2004 + * - Fix DUAL_DAC dma synchronization bug + * - Check exist FM/MPU401 I/O before activate. + * - Add AFTM_S16_BE format support, so MPlayer/Xine can play AC3/mutlichannel + * on Mac + * - Change to support kernel 2.6 so only small patch needed + * - All parameters default to 0 + * - Add spdif_out to send PCM through S/PDIF out jack + * - Add hw_copy to get 4-spaker output for general PCM/analog output + * + * Stefan Thater + * Apr 5 2004 + * - Fix mute single channel for CD/Line-in/AUX-in + */ +/*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef CONFIG_SOUND_CMPCI_MIDI +#include "sound_config.h" +#include "mpu401.h" +#endif +#ifdef CONFIG_SOUND_CMPCI_FM +#include "opl3.h" +#endif +#ifdef CONFIG_SOUND_CMPCI_JOYSTICK +#include +#endif + +/* --------------------------------------------------------------------- */ +#undef OSS_DOCUMENTED_MIXER_SEMANTICS +#undef DMABYTEIO +#define DBG(x) {} +/* --------------------------------------------------------------------- */ + +#define CM_MAGIC ((PCI_VENDOR_ID_CMEDIA<<16)|PCI_DEVICE_ID_CMEDIA_CM8338A) + +/* CM8338 registers definition ****************/ + +#define CODEC_CMI_FUNCTRL0 (0x00) +#define CODEC_CMI_FUNCTRL1 (0x04) +#define CODEC_CMI_CHFORMAT (0x08) +#define CODEC_CMI_INT_HLDCLR (0x0C) +#define CODEC_CMI_INT_STATUS (0x10) +#define CODEC_CMI_LEGACY_CTRL (0x14) +#define CODEC_CMI_MISC_CTRL (0x18) +#define CODEC_CMI_TDMA_POS (0x1C) +#define CODEC_CMI_MIXER (0x20) +#define CODEC_SB16_DATA (0x22) +#define CODEC_SB16_ADDR (0x23) +#define CODEC_CMI_MIXER1 (0x24) +#define CODEC_CMI_MIXER2 (0x25) +#define CODEC_CMI_AUX_VOL (0x26) +#define CODEC_CMI_MISC (0x27) +#define CODEC_CMI_AC97 (0x28) + +#define CODEC_CMI_CH0_FRAME1 (0x80) +#define CODEC_CMI_CH0_FRAME2 (0x84) +#define CODEC_CMI_CH1_FRAME1 (0x88) +#define CODEC_CMI_CH1_FRAME2 (0x8C) + +#define CODEC_CMI_SPDIF_CTRL (0x90) +#define CODEC_CMI_MISC_CTRL2 (0x92) + +#define CODEC_CMI_EXT_REG (0xF0) + +/* Mixer registers for SB16 ******************/ + +#define DSP_MIX_DATARESETIDX ((unsigned char)(0x00)) + +#define DSP_MIX_MASTERVOLIDX_L ((unsigned char)(0x30)) +#define DSP_MIX_MASTERVOLIDX_R ((unsigned char)(0x31)) +#define DSP_MIX_VOICEVOLIDX_L ((unsigned char)(0x32)) +#define DSP_MIX_VOICEVOLIDX_R ((unsigned char)(0x33)) +#define DSP_MIX_FMVOLIDX_L ((unsigned char)(0x34)) +#define DSP_MIX_FMVOLIDX_R ((unsigned char)(0x35)) +#define DSP_MIX_CDVOLIDX_L ((unsigned char)(0x36)) +#define DSP_MIX_CDVOLIDX_R ((unsigned char)(0x37)) +#define DSP_MIX_LINEVOLIDX_L ((unsigned char)(0x38)) +#define DSP_MIX_LINEVOLIDX_R ((unsigned char)(0x39)) + +#define DSP_MIX_MICVOLIDX ((unsigned char)(0x3A)) +#define DSP_MIX_SPKRVOLIDX ((unsigned char)(0x3B)) + +#define DSP_MIX_OUTMIXIDX ((unsigned char)(0x3C)) + +#define DSP_MIX_ADCMIXIDX_L ((unsigned char)(0x3D)) +#define DSP_MIX_ADCMIXIDX_R ((unsigned char)(0x3E)) + +#define DSP_MIX_INGAINIDX_L ((unsigned char)(0x3F)) +#define DSP_MIX_INGAINIDX_R ((unsigned char)(0x40)) +#define DSP_MIX_OUTGAINIDX_L ((unsigned char)(0x41)) +#define DSP_MIX_OUTGAINIDX_R ((unsigned char)(0x42)) + +#define DSP_MIX_AGCIDX ((unsigned char)(0x43)) + +#define DSP_MIX_TREBLEIDX_L ((unsigned char)(0x44)) +#define DSP_MIX_TREBLEIDX_R ((unsigned char)(0x45)) +#define DSP_MIX_BASSIDX_L ((unsigned char)(0x46)) +#define DSP_MIX_BASSIDX_R ((unsigned char)(0x47)) +#define DSP_MIX_EXTENSION ((unsigned char)(0xf0)) +// pseudo register for AUX +#define DSP_MIX_AUXVOL_L ((unsigned char)(0x50)) +#define DSP_MIX_AUXVOL_R ((unsigned char)(0x51)) + +// I/O length +#define CM_EXTENT_CODEC 0x100 +#define CM_EXTENT_MIDI 0x2 +#define CM_EXTENT_SYNTH 0x4 +#define CM_EXTENT_GAME 0x8 + +// Function Control Register 0 (00h) +#define CHADC0 0x01 +#define CHADC1 0x02 +#define PAUSE0 0x04 +#define PAUSE1 0x08 + +// Function Control Register 0+2 (02h) +#define CHEN0 0x01 +#define CHEN1 0x02 +#define RST_CH0 0x04 +#define RST_CH1 0x08 + +// Function Control Register 1 (04h) +#define JYSTK_EN 0x02 +#define UART_EN 0x04 +#define SPDO2DAC 0x40 +#define SPDFLOOP 0x80 + +// Function Control Register 1+1 (05h) +#define SPDF_0 0x01 +#define SPDF_1 0x02 +#define ASFC 0x1c +#define DSFC 0xe0 +#define SPDIF2DAC (SPDF_1 << 8 | SPDO2DAC) + +// Channel Format Register (08h) +#define CM_CFMT_STEREO 0x01 +#define CM_CFMT_16BIT 0x02 +#define CM_CFMT_MASK 0x03 +#define POLVALID 0x20 +#define INVSPDIFI 0x80 + +// Channel Format Register+2 (0ah) +#define SPD24SEL 0x20 + +// Channel Format Register+3 (0bh) +#define CHB3D 0x20 +#define CHB3D5C 0x80 + +// Interrupt Hold/Clear Register+2 (0eh) +#define CH0_INT_EN 0x01 +#define CH1_INT_EN 0x02 + +// Interrupt Register (10h) +#define CHINT0 0x01 +#define CHINT1 0x02 +#define CH0BUSY 0x04 +#define CH1BUSY 0x08 + +// Legacy Control/Status Register+1 (15h) +#define EXBASEN 0x10 +#define BASE2LIN 0x20 +#define CENTR2LIN 0x40 +#define CB2LIN (BASE2LIN | CENTR2LIN) +#define CHB3D6C 0x80 + +// Legacy Control/Status Register+2 (16h) +#define DAC2SPDO 0x20 +#define SPDCOPYRHT 0x40 +#define ENSPDOUT 0x80 + +// Legacy Control/Status Register+3 (17h) +#define FMSEL 0x03 +#define VSBSEL 0x0c +#define VMPU 0x60 +#define NXCHG 0x80 + +// Miscellaneous Control Register (18h) +#define REAR2LIN 0x20 +#define MUTECH1 0x40 +#define ENCENTER 0x80 + +// Miscellaneous Control Register+1 (19h) +#define SELSPDIFI2 0x01 +#define SPDF_AC97 0x80 + +// Miscellaneous Control Register+2 (1ah) +#define AC3_EN 0x04 +#define FM_EN 0x08 +#define SPD32SEL 0x20 +#define XCHGDAC 0x40 +#define ENDBDAC 0x80 + +// Miscellaneous Control Register+3 (1bh) +#define SPDIFI48K 0x01 +#define SPDO5V 0x02 +#define N4SPK3D 0x04 +#define RESET 0x40 +#define PWD 0x80 +#define SPDIF48K (SPDIFI48K << 24 | SPDF_AC97 << 8) + +// Mixer1 (24h) +#define CDPLAY 0x01 +#define X3DEN 0x02 +#define REAR2FRONT 0x10 +#define SPK4 0x20 +#define WSMUTE 0x40 +#define FMMUTE 0x80 + +// Miscellaneous Register (27h) +#define SPDVALID 0x02 +#define CENTR2MIC 0x04 + +// Miscellaneous Register2 (92h) +#define SPD32KFMT 0x10 + +#define CM_CFMT_DACSHIFT 2 +#define CM_CFMT_ADCSHIFT 0 +#define CM_FREQ_DACSHIFT 5 +#define CM_FREQ_ADCSHIFT 2 +#define RSTDAC RST_CH1 +#define RSTADC RST_CH0 +#define ENDAC CHEN1 +#define ENADC CHEN0 +#define PAUSEDAC PAUSE1 +#define PAUSEADC PAUSE0 +#define CODEC_CMI_ADC_FRAME1 CODEC_CMI_CH0_FRAME1 +#define CODEC_CMI_ADC_FRAME2 CODEC_CMI_CH0_FRAME2 +#define CODEC_CMI_DAC_FRAME1 CODEC_CMI_CH1_FRAME1 +#define CODEC_CMI_DAC_FRAME2 CODEC_CMI_CH1_FRAME2 +#define DACINT CHINT1 +#define ADCINT CHINT0 +#define DACBUSY CH1BUSY +#define ADCBUSY CH0BUSY +#define ENDACINT CH1_INT_EN +#define ENADCINT CH0_INT_EN + +static const unsigned sample_size[] = { 1, 2, 2, 4 }; +static const unsigned sample_shift[] = { 0, 1, 1, 2 }; + +#define SND_DEV_DSP16 5 + +#define NR_DEVICE 3 /* maximum number of devices */ + +#define set_dac1_rate set_adc_rate +#define set_dac1_rate_unlocked set_adc_rate_unlocked +#define stop_dac1 stop_adc +#define stop_dac1_unlocked stop_adc_unlocked +#define get_dmadac1 get_dmaadc + +static unsigned int devindex = 0; + +//*********************************************/ + +struct cm_state { + /* magic */ + unsigned int magic; + + /* list of cmedia devices */ + struct list_head devs; + + /* the corresponding pci_dev structure */ + struct pci_dev *dev; + + int dev_audio; /* soundcore stuff */ + int dev_mixer; + + unsigned int iosb, iobase, iosynth, + iomidi, iogame, irq; /* hardware resources */ + unsigned short deviceid; /* pci_id */ + + struct { /* mixer stuff */ + unsigned int modcnt; + unsigned short vol[13]; + } mix; + + unsigned int rateadc, ratedac; /* wave stuff */ + unsigned char fmt, enable; + + spinlock_t lock; + struct semaphore open_sem; + mode_t open_mode; + wait_queue_head_t open_wait; + + struct dmabuf { + void *rawbuf; + dma_addr_t dmaaddr; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + unsigned hwptr, swptr; + unsigned total_bytes; + int count; + unsigned error; /* over/underrun */ + wait_queue_head_t wait; + + unsigned fragsize; /* redundant, but makes calculations easier */ + unsigned dmasize; + unsigned fragsamples; + unsigned dmasamples; + + unsigned mapped:1; /* OSS stuff */ + unsigned ready:1; + unsigned endcleared:1; + unsigned enabled:1; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + } dma_dac, dma_adc; + +#ifdef CONFIG_SOUND_CMPCI_MIDI + int midi_devc; + struct address_info mpu_data; +#endif +#ifdef CONFIG_SOUND_CMPCI_JOYSTICK + struct gameport *gameport; +#endif + + int chip_version; + int max_channels; + int curr_channels; + int capability; /* HW capability, various for chip versions */ + + int status; /* HW or SW state */ + + int spdif_counter; /* spdif frame counter */ +}; + +/* flags used for capability */ +#define CAN_AC3_HW 0x00000001 /* 037 or later */ +#define CAN_AC3_SW 0x00000002 /* 033 or later */ +#define CAN_AC3 (CAN_AC3_HW | CAN_AC3_SW) +#define CAN_DUAL_DAC 0x00000004 /* 033 or later */ +#define CAN_MULTI_CH_HW 0x00000008 /* 039 or later */ +#define CAN_MULTI_CH (CAN_MULTI_CH_HW | CAN_DUAL_DAC) +#define CAN_LINE_AS_REAR 0x00000010 /* 033 or later */ +#define CAN_LINE_AS_BASS 0x00000020 /* 039 or later */ +#define CAN_MIC_AS_BASS 0x00000040 /* 039 or later */ + +/* flags used for status */ +#define DO_AC3_HW 0x00000001 +#define DO_AC3_SW 0x00000002 +#define DO_AC3 (DO_AC3_HW | DO_AC3_SW) +#define DO_DUAL_DAC 0x00000004 +#define DO_MULTI_CH_HW 0x00000008 +#define DO_MULTI_CH (DO_MULTI_CH_HW | DO_DUAL_DAC) +#define DO_LINE_AS_REAR 0x00000010 /* 033 or later */ +#define DO_LINE_AS_BASS 0x00000020 /* 039 or later */ +#define DO_MIC_AS_BASS 0x00000040 /* 039 or later */ +#define DO_SPDIF_OUT 0x00000100 +#define DO_SPDIF_IN 0x00000200 +#define DO_SPDIF_LOOP 0x00000400 +#define DO_BIGENDIAN_W 0x00001000 /* used in PowerPC */ +#define DO_BIGENDIAN_R 0x00002000 /* used in PowerPC */ + +static LIST_HEAD(devs); + +static int mpuio; +static int fmio; +static int joystick; +static int spdif_inverse; +static int spdif_loop; +static int spdif_out; +static int use_line_as_rear; +static int use_line_as_bass; +static int use_mic_as_bass; +static int mic_boost; +static int hw_copy; +module_param(mpuio, int, 0); +module_param(fmio, int, 0); +module_param(joystick, bool, 0); +module_param(spdif_inverse, bool, 0); +module_param(spdif_loop, bool, 0); +module_param(spdif_out, bool, 0); +module_param(use_line_as_rear, bool, 0); +module_param(use_line_as_bass, bool, 0); +module_param(use_mic_as_bass, bool, 0); +module_param(mic_boost, bool, 0); +module_param(hw_copy, bool, 0); +MODULE_PARM_DESC(mpuio, "(0x330, 0x320, 0x310, 0x300) Base of MPU-401, 0 to disable"); +MODULE_PARM_DESC(fmio, "(0x388, 0x3C8, 0x3E0) Base of OPL3, 0 to disable"); +MODULE_PARM_DESC(joystick, "(1/0) Enable joystick interface, still need joystick driver"); +MODULE_PARM_DESC(spdif_inverse, "(1/0) Invert S/PDIF-in signal"); +MODULE_PARM_DESC(spdif_loop, "(1/0) Route S/PDIF-in to S/PDIF-out directly"); +MODULE_PARM_DESC(spdif_out, "(1/0) Send PCM to S/PDIF-out (PCM volume will not function)"); +MODULE_PARM_DESC(use_line_as_rear, "(1/0) Use line-in jack as rear-out"); +MODULE_PARM_DESC(use_line_as_bass, "(1/0) Use line-in jack as bass/center"); +MODULE_PARM_DESC(use_mic_as_bass, "(1/0) Use mic-in jack as bass/center"); +MODULE_PARM_DESC(mic_boost, "(1/0) Enable microphone boost"); +MODULE_PARM_DESC(hw_copy, "Copy front channel to surround channel"); + +/* --------------------------------------------------------------------- */ + +static inline unsigned ld2(unsigned int x) +{ + unsigned exp=16,l=5,r=0; + static const unsigned num[]={0x2,0x4,0x10,0x100,0x10000}; + + /* num: 2, 4, 16, 256, 65536 */ + /* exp: 1, 2, 4, 8, 16 */ + + while(l--) { + if( x >= num[l] ) { + if(num[l]>2) x >>= exp; + r+=exp; + } + exp>>=1; + } + + return r; +} + +/* --------------------------------------------------------------------- */ + +static void maskb(unsigned int addr, unsigned int mask, unsigned int value) +{ + outb((inb(addr) & mask) | value, addr); +} + +static void maskw(unsigned int addr, unsigned int mask, unsigned int value) +{ + outw((inw(addr) & mask) | value, addr); +} + +static void maskl(unsigned int addr, unsigned int mask, unsigned int value) +{ + outl((inl(addr) & mask) | value, addr); +} + +static void set_dmadac1(struct cm_state *s, unsigned int addr, unsigned int count) +{ + if (addr) + outl(addr, s->iobase + CODEC_CMI_ADC_FRAME1); + outw(count - 1, s->iobase + CODEC_CMI_ADC_FRAME2); + maskb(s->iobase + CODEC_CMI_FUNCTRL0, ~CHADC0, 0); +} + +static void set_dmaadc(struct cm_state *s, unsigned int addr, unsigned int count) +{ + outl(addr, s->iobase + CODEC_CMI_ADC_FRAME1); + outw(count - 1, s->iobase + CODEC_CMI_ADC_FRAME2); + maskb(s->iobase + CODEC_CMI_FUNCTRL0, ~0, CHADC0); +} + +static void set_dmadac(struct cm_state *s, unsigned int addr, unsigned int count) +{ + outl(addr, s->iobase + CODEC_CMI_DAC_FRAME1); + outw(count - 1, s->iobase + CODEC_CMI_DAC_FRAME2); + maskb(s->iobase + CODEC_CMI_FUNCTRL0, ~CHADC1, 0); + if (s->status & DO_DUAL_DAC) + set_dmadac1(s, 0, count); +} + +static void set_countadc(struct cm_state *s, unsigned count) +{ + outw(count - 1, s->iobase + CODEC_CMI_ADC_FRAME2 + 2); +} + +static void set_countdac(struct cm_state *s, unsigned count) +{ + outw(count - 1, s->iobase + CODEC_CMI_DAC_FRAME2 + 2); + if (s->status & DO_DUAL_DAC) + set_countadc(s, count); +} + +static unsigned get_dmadac(struct cm_state *s) +{ + unsigned int curr_addr; + + curr_addr = inw(s->iobase + CODEC_CMI_DAC_FRAME2) + 1; + curr_addr <<= sample_shift[(s->fmt >> CM_CFMT_DACSHIFT) & CM_CFMT_MASK]; + curr_addr = s->dma_dac.dmasize - curr_addr; + + return curr_addr; +} + +static unsigned get_dmaadc(struct cm_state *s) +{ + unsigned int curr_addr; + + curr_addr = inw(s->iobase + CODEC_CMI_ADC_FRAME2) + 1; + curr_addr <<= sample_shift[(s->fmt >> CM_CFMT_ADCSHIFT) & CM_CFMT_MASK]; + curr_addr = s->dma_adc.dmasize - curr_addr; + + return curr_addr; +} + +static void wrmixer(struct cm_state *s, unsigned char idx, unsigned char data) +{ + unsigned char regval, pseudo; + + // pseudo register + if (idx == DSP_MIX_AUXVOL_L) { + data >>= 4; + data &= 0x0f; + regval = inb(s->iobase + CODEC_CMI_AUX_VOL) & ~0x0f; + outb(regval | data, s->iobase + CODEC_CMI_AUX_VOL); + return; + } + if (idx == DSP_MIX_AUXVOL_R) { + data &= 0xf0; + regval = inb(s->iobase + CODEC_CMI_AUX_VOL) & ~0xf0; + outb(regval | data, s->iobase + CODEC_CMI_AUX_VOL); + return; + } + outb(idx, s->iobase + CODEC_SB16_ADDR); + udelay(10); + // pseudo bits + if (idx == DSP_MIX_OUTMIXIDX) { + pseudo = data & ~0x1f; + pseudo >>= 1; + regval = inb(s->iobase + CODEC_CMI_MIXER2) & ~0x30; + outb(regval | pseudo, s->iobase + CODEC_CMI_MIXER2); + } + if (idx == DSP_MIX_ADCMIXIDX_L) { + pseudo = data & 0x80; + pseudo >>= 1; + regval = inb(s->iobase + CODEC_CMI_MIXER2) & ~0x40; + outb(regval | pseudo, s->iobase + CODEC_CMI_MIXER2); + } + if (idx == DSP_MIX_ADCMIXIDX_R) { + pseudo = data & 0x80; + regval = inb(s->iobase + CODEC_CMI_MIXER2) & ~0x80; + outb(regval | pseudo, s->iobase + CODEC_CMI_MIXER2); + } + outb(data, s->iobase + CODEC_SB16_DATA); + udelay(10); +} + +static unsigned char rdmixer(struct cm_state *s, unsigned char idx) +{ + unsigned char v, pseudo; + + // pseudo register + if (idx == DSP_MIX_AUXVOL_L) { + v = inb(s->iobase + CODEC_CMI_AUX_VOL) & 0x0f; + v <<= 4; + return v; + } + if (idx == DSP_MIX_AUXVOL_L) { + v = inb(s->iobase + CODEC_CMI_AUX_VOL) & 0xf0; + return v; + } + outb(idx, s->iobase + CODEC_SB16_ADDR); + udelay(10); + v = inb(s->iobase + CODEC_SB16_DATA); + udelay(10); + // pseudo bits + if (idx == DSP_MIX_OUTMIXIDX) { + pseudo = inb(s->iobase + CODEC_CMI_MIXER2) & 0x30; + pseudo <<= 1; + v |= pseudo; + } + if (idx == DSP_MIX_ADCMIXIDX_L) { + pseudo = inb(s->iobase + CODEC_CMI_MIXER2) & 0x40; + pseudo <<= 1; + v |= pseudo; + } + if (idx == DSP_MIX_ADCMIXIDX_R) { + pseudo = inb(s->iobase + CODEC_CMI_MIXER2) & 0x80; + v |= pseudo; + } + return v; +} + +static void set_fmt_unlocked(struct cm_state *s, unsigned char mask, unsigned char data) +{ + if (mask && s->chip_version > 0) { /* 8338 cannot keep this */ + s->fmt = inb(s->iobase + CODEC_CMI_CHFORMAT); + udelay(10); + } + s->fmt = (s->fmt & mask) | data; + outb(s->fmt, s->iobase + CODEC_CMI_CHFORMAT); + udelay(10); +} + +static void set_fmt(struct cm_state *s, unsigned char mask, unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + set_fmt_unlocked(s,mask,data); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void frobindir(struct cm_state *s, unsigned char idx, unsigned char mask, unsigned char data) +{ + outb(idx, s->iobase + CODEC_SB16_ADDR); + udelay(10); + outb((inb(s->iobase + CODEC_SB16_DATA) & mask) | data, s->iobase + CODEC_SB16_DATA); + udelay(10); +} + +static struct { + unsigned rate; + unsigned lower; + unsigned upper; + unsigned char freq; +} rate_lookup[] = +{ + { 5512, (0 + 5512) / 2, (5512 + 8000) / 2, 0 }, + { 8000, (5512 + 8000) / 2, (8000 + 11025) / 2, 4 }, + { 11025, (8000 + 11025) / 2, (11025 + 16000) / 2, 1 }, + { 16000, (11025 + 16000) / 2, (16000 + 22050) / 2, 5 }, + { 22050, (16000 + 22050) / 2, (22050 + 32000) / 2, 2 }, + { 32000, (22050 + 32000) / 2, (32000 + 44100) / 2, 6 }, + { 44100, (32000 + 44100) / 2, (44100 + 48000) / 2, 3 }, + { 48000, (44100 + 48000) / 2, 48000, 7 } +}; + +static void set_spdif_copyright(struct cm_state *s, int spdif_copyright) +{ + /* enable SPDIF-in Copyright */ + maskb(s->iobase + CODEC_CMI_LEGACY_CTRL + 2, ~SPDCOPYRHT, spdif_copyright ? SPDCOPYRHT : 0); +} + +static void set_spdif_loop(struct cm_state *s, int spdif_loop) +{ + /* enable SPDIF loop */ + if (spdif_loop) { + s->status |= DO_SPDIF_LOOP; + /* turn on spdif-in to spdif-out */ + maskb(s->iobase + CODEC_CMI_FUNCTRL1, ~0, SPDFLOOP); + } else { + s->status &= ~DO_SPDIF_LOOP; + /* turn off spdif-in to spdif-out */ + maskb(s->iobase + CODEC_CMI_FUNCTRL1, ~SPDFLOOP, 0); + } +} + +static void set_spdif_monitor(struct cm_state *s, int channel) +{ + // SPDO2DAC + maskw(s->iobase + CODEC_CMI_FUNCTRL1, ~SPDO2DAC, channel == 2 ? SPDO2DAC : 0); + // CDPLAY + if (s->chip_version >= 39) + maskb(s->iobase + CODEC_CMI_MIXER1, ~CDPLAY, channel ? CDPLAY : 0); +} + +static void set_spdifout_level(struct cm_state *s, int level5v) +{ + /* SPDO5V */ + if (s->chip_version > 0) + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 3, ~SPDO5V, level5v ? SPDO5V : 0); +} + +static void set_spdifin_inverse(struct cm_state *s, int spdif_inverse) +{ + if (s->chip_version == 0) /* 8338 has not this feature */ + return; + if (spdif_inverse) { + /* turn on spdif-in inverse */ + if (s->chip_version >= 39) + maskb(s->iobase + CODEC_CMI_CHFORMAT, ~0, INVSPDIFI); + else + maskb(s->iobase + CODEC_CMI_CHFORMAT + 2, ~0, 1); + } else { + /* turn off spdif-ininverse */ + if (s->chip_version >= 39) + maskb(s->iobase + CODEC_CMI_CHFORMAT, ~INVSPDIFI, 0); + else + maskb(s->iobase + CODEC_CMI_CHFORMAT + 2, ~1, 0); + } +} + +static void set_spdifin_channel2(struct cm_state *s, int channel2) +{ + /* SELSPDIFI2 */ + if (s->chip_version >= 39) + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 1, ~SELSPDIFI2, channel2 ? SELSPDIFI2 : 0); +} + +static void set_spdifin_valid(struct cm_state *s, int valid) +{ + /* SPDVALID */ + maskb(s->iobase + CODEC_CMI_MISC, ~SPDVALID, valid ? SPDVALID : 0); +} + +static void set_spdifout_unlocked(struct cm_state *s, unsigned rate) +{ + if (rate != 48000 && rate != 44100) + rate = 0; + if (rate == 48000 || rate == 44100) { + set_spdif_loop(s, 0); + // SPDF_1 + maskb(s->iobase + CODEC_CMI_FUNCTRL1 + 1, ~0, SPDF_1); + // SPDIFI48K SPDF_AC97 + maskl(s->iobase + CODEC_CMI_MISC_CTRL, ~SPDIF48K, rate == 48000 ? SPDIF48K : 0); + if (s->chip_version >= 55) + // SPD32KFMT + maskb(s->iobase + CODEC_CMI_MISC_CTRL2, ~SPD32KFMT, rate == 48000 ? SPD32KFMT : 0); + if (s->chip_version > 0) + // ENSPDOUT + maskb(s->iobase + CODEC_CMI_LEGACY_CTRL + 2, ~0, ENSPDOUT); + // monitor SPDIF out + set_spdif_monitor(s, 2); + s->status |= DO_SPDIF_OUT; + } else { + maskb(s->iobase + CODEC_CMI_FUNCTRL1 + 1, ~SPDF_1, 0); + maskb(s->iobase + CODEC_CMI_LEGACY_CTRL + 2, ~ENSPDOUT, 0); + // monitor none + set_spdif_monitor(s, 0); + s->status &= ~DO_SPDIF_OUT; + } +} + +static void set_spdifout(struct cm_state *s, unsigned rate) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + set_spdifout_unlocked(s,rate); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void set_spdifin_unlocked(struct cm_state *s, unsigned rate) +{ + if (rate == 48000 || rate == 44100) { + // SPDF_1 + maskb(s->iobase + CODEC_CMI_FUNCTRL1 + 1, ~0, SPDF_1); + // SPDIFI48K SPDF_AC97 + maskl(s->iobase + CODEC_CMI_MISC_CTRL, ~SPDIF48K, rate == 48000 ? SPDIF48K : 0); + s->status |= DO_SPDIF_IN; + } else { + maskb(s->iobase + CODEC_CMI_FUNCTRL1 + 1, ~SPDF_1, 0); + s->status &= ~DO_SPDIF_IN; + } +} + +static void set_spdifin(struct cm_state *s, unsigned rate) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + set_spdifin_unlocked(s,rate); + spin_unlock_irqrestore(&s->lock, flags); +} + +/* find parity for bit 4~30 */ +static unsigned parity(unsigned data) +{ + unsigned parity = 0; + int counter = 4; + + data >>= 4; // start from bit 4 + while (counter <= 30) { + if (data & 1) + parity++; + data >>= 1; + counter++; + } + return parity & 1; +} + +static void set_ac3_unlocked(struct cm_state *s, unsigned rate) +{ + if (!(s->capability & CAN_AC3)) + return; + /* enable AC3 */ + if (rate && rate != 44100) + rate = 48000; + if (rate == 48000 || rate == 44100) { + // mute DAC + maskb(s->iobase + CODEC_CMI_MIXER1, ~0, WSMUTE); + if (s->chip_version >= 39) + maskb(s->iobase + CODEC_CMI_MISC_CTRL, ~0, MUTECH1); + // AC3EN for 039, 0x04 + if (s->chip_version >= 39) { + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 2, ~0, AC3_EN); + if (s->chip_version == 55) + maskb(s->iobase + CODEC_CMI_SPDIF_CTRL, ~2, 0); + // AC3EN for 037, 0x10 + } else if (s->chip_version == 37) + maskb(s->iobase + CODEC_CMI_CHFORMAT + 2, ~0, 0x10); + if (s->capability & CAN_AC3_HW) { + // SPD24SEL for 039, 0x20, but cannot be set + if (s->chip_version == 39) + maskb(s->iobase + CODEC_CMI_CHFORMAT + 2, ~0, SPD24SEL); + // SPD24SEL for 037, 0x02 + else if (s->chip_version == 37) + maskb(s->iobase + CODEC_CMI_CHFORMAT + 2, ~0, 0x02); + if (s->chip_version >= 39) + maskb(s->iobase + CODEC_CMI_MIXER1, ~CDPLAY, 0); + + s->status |= DO_AC3_HW; + } else { + // SPD32SEL for 037 & 039 + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 2, ~0, SPD32SEL); + // set 176K sample rate to fix 033 HW bug + if (s->chip_version == 33) { + if (rate == 48000) + maskb(s->iobase + CODEC_CMI_CHFORMAT + 1, ~0, 0x08); + else + maskb(s->iobase + CODEC_CMI_CHFORMAT + 1, ~0x08, 0); + } + s->status |= DO_AC3_SW; + } + } else { + maskb(s->iobase + CODEC_CMI_MIXER1, ~WSMUTE, 0); + if (s->chip_version >= 39) + maskb(s->iobase + CODEC_CMI_MISC_CTRL, ~MUTECH1, 0); + maskb(s->iobase + CODEC_CMI_CHFORMAT + 2, ~(SPD24SEL|0x12), 0); + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 2, ~(SPD32SEL|AC3_EN), 0); + if (s->chip_version == 33) + maskb(s->iobase + CODEC_CMI_CHFORMAT + 1, ~0x08, 0); + if (s->chip_version >= 39) + maskb(s->iobase + CODEC_CMI_MIXER1, ~0, CDPLAY); + s->status &= ~DO_AC3; + } + s->spdif_counter = 0; +} + +static void set_line_as_rear(struct cm_state *s, int use_line_as_rear) +{ + if (!(s->capability & CAN_LINE_AS_REAR)) + return; + if (use_line_as_rear) { + maskb(s->iobase + CODEC_CMI_MIXER1, ~0, SPK4); + s->status |= DO_LINE_AS_REAR; + } else { + maskb(s->iobase + CODEC_CMI_MIXER1, ~SPK4, 0); + s->status &= ~DO_LINE_AS_REAR; + } +} + +static void set_line_as_bass(struct cm_state *s, int use_line_as_bass) +{ + if (!(s->capability & CAN_LINE_AS_BASS)) + return; + if (use_line_as_bass) { + maskb(s->iobase + CODEC_CMI_LEGACY_CTRL + 1, ~0, CB2LIN); + s->status |= DO_LINE_AS_BASS; + } else { + maskb(s->iobase + CODEC_CMI_LEGACY_CTRL + 1, ~CB2LIN, 0); + s->status &= ~DO_LINE_AS_BASS; + } +} + +static void set_mic_as_bass(struct cm_state *s, int use_mic_as_bass) +{ + if (!(s->capability & CAN_MIC_AS_BASS)) + return; + if (use_mic_as_bass) { + maskb(s->iobase + CODEC_CMI_MISC, ~0, 0x04); + s->status |= DO_MIC_AS_BASS; + } else { + maskb(s->iobase + CODEC_CMI_MISC, ~0x04, 0); + s->status &= ~DO_MIC_AS_BASS; + } +} + +static void set_hw_copy(struct cm_state *s, int hw_copy) +{ + if (s->max_channels > 2 && hw_copy) + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 3, ~0, N4SPK3D); + else + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 3, ~N4SPK3D, 0); +} + +static void set_ac3(struct cm_state *s, unsigned rate) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + set_spdifout_unlocked(s, rate); + set_ac3_unlocked(s, rate); + spin_unlock_irqrestore(&s->lock, flags); +} + +static int trans_ac3(struct cm_state *s, void *dest, const char __user *source, int size) +{ + int i = size / 2; + unsigned long data; + unsigned short data16; + unsigned long *dst = (unsigned long *) dest; + unsigned short __user *src = (unsigned short __user *)source; + int err; + + do { + if ((err = __get_user(data16, src++))) + return err; + data = (unsigned long)le16_to_cpu(data16); + data <<= 12; // ok for 16-bit data + if (s->spdif_counter == 2 || s->spdif_counter == 3) + data |= 0x40000000; // indicate AC-3 raw data + if (parity(data)) + data |= 0x80000000; // parity + if (s->spdif_counter == 0) + data |= 3; // preamble 'M' + else if (s->spdif_counter & 1) + data |= 5; // odd, 'W' + else + data |= 9; // even, 'M' + *dst++ = cpu_to_le32(data); + s->spdif_counter++; + if (s->spdif_counter == 384) + s->spdif_counter = 0; + } while (--i); + + return 0; +} + +static void set_adc_rate_unlocked(struct cm_state *s, unsigned rate) +{ + unsigned char freq = 4; + int i; + + if (rate > 48000) + rate = 48000; + if (rate < 8000) + rate = 8000; + for (i = 0; i < sizeof(rate_lookup) / sizeof(rate_lookup[0]); i++) { + if (rate > rate_lookup[i].lower && rate <= rate_lookup[i].upper) { + rate = rate_lookup[i].rate; + freq = rate_lookup[i].freq; + break; + } + } + s->rateadc = rate; + freq <<= CM_FREQ_ADCSHIFT; + + maskb(s->iobase + CODEC_CMI_FUNCTRL1 + 1, ~ASFC, freq); +} + +static void set_adc_rate(struct cm_state *s, unsigned rate) +{ + unsigned long flags; + unsigned char freq = 4; + int i; + + if (rate > 48000) + rate = 48000; + if (rate < 8000) + rate = 8000; + for (i = 0; i < sizeof(rate_lookup) / sizeof(rate_lookup[0]); i++) { + if (rate > rate_lookup[i].lower && rate <= rate_lookup[i].upper) { + rate = rate_lookup[i].rate; + freq = rate_lookup[i].freq; + break; + } + } + s->rateadc = rate; + freq <<= CM_FREQ_ADCSHIFT; + + spin_lock_irqsave(&s->lock, flags); + maskb(s->iobase + CODEC_CMI_FUNCTRL1 + 1, ~ASFC, freq); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void set_dac_rate(struct cm_state *s, unsigned rate) +{ + unsigned long flags; + unsigned char freq = 4; + int i; + + if (rate > 48000) + rate = 48000; + if (rate < 8000) + rate = 8000; + for (i = 0; i < sizeof(rate_lookup) / sizeof(rate_lookup[0]); i++) { + if (rate > rate_lookup[i].lower && rate <= rate_lookup[i].upper) { + rate = rate_lookup[i].rate; + freq = rate_lookup[i].freq; + break; + } + } + s->ratedac = rate; + freq <<= CM_FREQ_DACSHIFT; + + spin_lock_irqsave(&s->lock, flags); + maskb(s->iobase + CODEC_CMI_FUNCTRL1 + 1, ~DSFC, freq); + spin_unlock_irqrestore(&s->lock, flags); + + if (s->curr_channels <= 2 && spdif_out) + set_spdifout(s, rate); + if (s->status & DO_DUAL_DAC) + set_dac1_rate(s, rate); +} + +/* --------------------------------------------------------------------- */ +static inline void reset_adc(struct cm_state *s) +{ + /* reset bus master */ + outb(s->enable | RSTADC, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + udelay(10); + outb(s->enable & ~RSTADC, s->iobase + CODEC_CMI_FUNCTRL0 + 2); +} + +static inline void reset_dac(struct cm_state *s) +{ + /* reset bus master */ + outb(s->enable | RSTDAC, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + udelay(10); + outb(s->enable & ~RSTDAC, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + if (s->status & DO_DUAL_DAC) + reset_adc(s); +} + +static inline void pause_adc(struct cm_state *s) +{ + maskb(s->iobase + CODEC_CMI_FUNCTRL0, ~0, PAUSEADC); +} + +static inline void pause_dac(struct cm_state *s) +{ + maskb(s->iobase + CODEC_CMI_FUNCTRL0, ~0, PAUSEDAC); + if (s->status & DO_DUAL_DAC) + pause_adc(s); +} + +static inline void disable_adc(struct cm_state *s) +{ + /* disable channel */ + s->enable &= ~ENADC; + outb(s->enable, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + reset_adc(s); +} + +static inline void disable_dac(struct cm_state *s) +{ + /* disable channel */ + s->enable &= ~ENDAC; + outb(s->enable, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + reset_dac(s); + if (s->status & DO_DUAL_DAC) + disable_adc(s); +} + +static inline void enable_adc(struct cm_state *s) +{ + if (!(s->enable & ENADC)) { + /* enable channel */ + s->enable |= ENADC; + outb(s->enable, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + } + maskb(s->iobase + CODEC_CMI_FUNCTRL0, ~PAUSEADC, 0); +} + +static inline void enable_dac_unlocked(struct cm_state *s) +{ + if (!(s->enable & ENDAC)) { + /* enable channel */ + s->enable |= ENDAC; + outb(s->enable, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + } + maskb(s->iobase + CODEC_CMI_FUNCTRL0, ~PAUSEDAC, 0); + + if (s->status & DO_DUAL_DAC) + enable_adc(s); +} + +static inline void stop_adc_unlocked(struct cm_state *s) +{ + if (s->enable & ENADC) { + /* disable interrupt */ + maskb(s->iobase + CODEC_CMI_INT_HLDCLR + 2, ~ENADCINT, 0); + disable_adc(s); + } +} + +static inline void stop_adc(struct cm_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + stop_adc_unlocked(s); + spin_unlock_irqrestore(&s->lock, flags); + +} + +static inline void stop_dac_unlocked(struct cm_state *s) +{ + if (s->enable & ENDAC) { + /* disable interrupt */ + maskb(s->iobase + CODEC_CMI_INT_HLDCLR + 2, ~ENDACINT, 0); + disable_dac(s); + } + if (s->status & DO_DUAL_DAC) + stop_dac1_unlocked(s); +} + +static inline void stop_dac(struct cm_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + stop_dac_unlocked(s); + spin_unlock_irqrestore(&s->lock, flags); +} + +static inline void start_adc_unlocked(struct cm_state *s) +{ + if ((s->dma_adc.mapped || s->dma_adc.count < (signed)(s->dma_adc.dmasize - 2*s->dma_adc.fragsize)) + && s->dma_adc.ready) { + /* enable interrupt */ + maskb(s->iobase + CODEC_CMI_INT_HLDCLR + 2, ~0, ENADCINT); + enable_adc(s); + } +} + +static void start_adc(struct cm_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + start_adc_unlocked(s); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_dac1_unlocked(struct cm_state *s) +{ + if ((s->dma_adc.mapped || s->dma_adc.count > 0) && s->dma_adc.ready) { + /* enable interrupt */ + maskb(s->iobase + CODEC_CMI_INT_HLDCLR + 2, ~0, ENADCINT); + enable_dac_unlocked(s); + } +} + +static void start_dac_unlocked(struct cm_state *s) +{ + if ((s->dma_dac.mapped || s->dma_dac.count > 0) && s->dma_dac.ready) { + /* enable interrupt */ + maskb(s->iobase + CODEC_CMI_INT_HLDCLR + 2, ~0, ENDACINT); + enable_dac_unlocked(s); + } + if (s->status & DO_DUAL_DAC) + start_dac1_unlocked(s); +} + +static void start_dac(struct cm_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + start_dac_unlocked(s); + spin_unlock_irqrestore(&s->lock, flags); +} + +static int prog_dmabuf(struct cm_state *s, unsigned rec); + +static int set_dac_channels(struct cm_state *s, int channels) +{ + unsigned long flags; + static unsigned int fmmute = 0; + + spin_lock_irqsave(&s->lock, flags); + + if ((channels > 2) && (channels <= s->max_channels) + && (((s->fmt >> CM_CFMT_DACSHIFT) & CM_CFMT_MASK) == (CM_CFMT_STEREO | CM_CFMT_16BIT))) { + set_spdifout_unlocked(s, 0); + if (s->capability & CAN_MULTI_CH_HW) { + // NXCHG + maskb(s->iobase + CODEC_CMI_LEGACY_CTRL + 3, ~0, NXCHG); + // CHB3D or CHB3D5C + maskb(s->iobase + CODEC_CMI_CHFORMAT + 3, ~(CHB3D5C|CHB3D), channels > 4 ? CHB3D5C : CHB3D); + // CHB3D6C + maskb(s->iobase + CODEC_CMI_LEGACY_CTRL + 1, ~CHB3D6C, channels == 6 ? CHB3D6C : 0); + // ENCENTER + maskb(s->iobase + CODEC_CMI_MISC_CTRL, ~ENCENTER, channels == 6 ? ENCENTER : 0); + s->status |= DO_MULTI_CH_HW; + } else if (s->capability & CAN_DUAL_DAC) { + unsigned char fmtm = ~0, fmts = 0; + ssize_t ret; + + // ENDBDAC, turn on double DAC mode + // XCHGDAC, CH0 -> back, CH1->front + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 2, ~0, ENDBDAC|XCHGDAC); + // mute FM + fmmute = inb(s->iobase + CODEC_CMI_MIXER1) & FMMUTE; + maskb(s->iobase + CODEC_CMI_MIXER1, ~0, FMMUTE); + s->status |= DO_DUAL_DAC; + // prepare secondary buffer + spin_unlock_irqrestore(&s->lock, flags); + ret = prog_dmabuf(s, 1); + if (ret) return ret; + spin_lock_irqsave(&s->lock, flags); + + // copy the hw state + fmtm &= ~((CM_CFMT_STEREO | CM_CFMT_16BIT) << CM_CFMT_DACSHIFT); + fmtm &= ~((CM_CFMT_STEREO | CM_CFMT_16BIT) << CM_CFMT_ADCSHIFT); + // the HW only support 16-bit stereo + fmts |= CM_CFMT_16BIT << CM_CFMT_DACSHIFT; + fmts |= CM_CFMT_16BIT << CM_CFMT_ADCSHIFT; + fmts |= CM_CFMT_STEREO << CM_CFMT_DACSHIFT; + fmts |= CM_CFMT_STEREO << CM_CFMT_ADCSHIFT; + + set_fmt_unlocked(s, fmtm, fmts); + set_adc_rate_unlocked(s, s->ratedac); + } + // disable 4 speaker mode (analog duplicate) + set_hw_copy(s, 0); + s->curr_channels = channels; + + // enable jack redirect + set_line_as_rear(s, use_line_as_rear); + if (channels > 4) { + set_line_as_bass(s, use_line_as_bass); + set_mic_as_bass(s, use_mic_as_bass); + } + } else { + if (s->status & DO_MULTI_CH_HW) { + maskb(s->iobase + CODEC_CMI_LEGACY_CTRL + 3, ~NXCHG, 0); + maskb(s->iobase + CODEC_CMI_CHFORMAT + 3, ~(CHB3D5C|CHB3D), 0); + maskb(s->iobase + CODEC_CMI_LEGACY_CTRL + 1, ~CHB3D6C, 0); + } else if (s->status & DO_DUAL_DAC) { + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 2, ~ENDBDAC, 0); + maskb(s->iobase + CODEC_CMI_MIXER1, ~FMMUTE, fmmute); + } + // enable 4 speaker mode (analog duplicate) + set_hw_copy(s, hw_copy); + s->status &= ~DO_MULTI_CH; + s->curr_channels = s->fmt & (CM_CFMT_STEREO << CM_CFMT_DACSHIFT) ? 2 : 1; + // disable jack redirect + set_line_as_rear(s, hw_copy ? use_line_as_rear : 0); + set_line_as_bass(s, 0); + set_mic_as_bass(s, 0); + } + spin_unlock_irqrestore(&s->lock, flags); + return s->curr_channels; +} + +/* --------------------------------------------------------------------- */ + +#define DMABUF_DEFAULTORDER (16-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +static void dealloc_dmabuf(struct cm_state *s, struct dmabuf *db) +{ + struct page *pstart, *pend; + + if (db->rawbuf) { + /* undo marking the pages as reserved */ + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (pstart = virt_to_page(db->rawbuf); pstart <= pend; pstart++) + ClearPageReserved(pstart); + pci_free_consistent(s->dev, PAGE_SIZE << db->buforder, db->rawbuf, db->dmaaddr); + } + db->rawbuf = NULL; + db->mapped = db->ready = 0; +} + +/* Ch1 is used for playback, Ch0 is used for recording */ + +static int prog_dmabuf(struct cm_state *s, unsigned rec) +{ + struct dmabuf *db = rec ? &s->dma_adc : &s->dma_dac; + unsigned rate = rec ? s->rateadc : s->ratedac; + int order; + unsigned bytepersec; + unsigned bufs; + struct page *pstart, *pend; + unsigned char fmt; + unsigned long flags; + + fmt = s->fmt; + if (rec) { + stop_adc(s); + fmt >>= CM_CFMT_ADCSHIFT; + } else { + stop_dac(s); + fmt >>= CM_CFMT_DACSHIFT; + } + + fmt &= CM_CFMT_MASK; + db->hwptr = db->swptr = db->total_bytes = db->count = db->error = db->endcleared = 0; + if (!db->rawbuf) { + db->ready = db->mapped = 0; + for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--) + if ((db->rawbuf = pci_alloc_consistent(s->dev, PAGE_SIZE << order, &db->dmaaddr))) + break; + if (!db->rawbuf || !db->dmaaddr) + return -ENOMEM; + db->buforder = order; + /* now mark the pages as reserved; otherwise remap_pfn_range doesn't do what we want */ + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (pstart = virt_to_page(db->rawbuf); pstart <= pend; pstart++) + SetPageReserved(pstart); + } + bytepersec = rate << sample_shift[fmt]; + bufs = PAGE_SIZE << db->buforder; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < bytepersec) + db->fragshift = ld2(bytepersec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(bytepersec/100/(db->subdivision ? db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + db->numfrag = bufs >> db->fragshift; + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->numfrag = bufs >> db->fragshift; + } + db->fragsize = 1 << db->fragshift; + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; + /* to make fragsize >= 4096 */ + db->fragsamples = db->fragsize >> sample_shift[fmt]; + db->dmasize = db->numfrag << db->fragshift; + db->dmasamples = db->dmasize >> sample_shift[fmt]; + memset(db->rawbuf, (fmt & CM_CFMT_16BIT) ? 0 : 0x80, db->dmasize); + spin_lock_irqsave(&s->lock, flags); + if (rec) { + if (s->status & DO_DUAL_DAC) + set_dmadac1(s, db->dmaaddr, db->dmasize >> sample_shift[fmt]); + else + set_dmaadc(s, db->dmaaddr, db->dmasize >> sample_shift[fmt]); + /* program sample counts */ + set_countdac(s, db->fragsamples); + } else { + set_dmadac(s, db->dmaaddr, db->dmasize >> sample_shift[fmt]); + /* program sample counts */ + set_countdac(s, db->fragsamples); + } + spin_unlock_irqrestore(&s->lock, flags); + db->enabled = 1; + db->ready = 1; + return 0; +} + +static inline void clear_advance(struct cm_state *s) +{ + unsigned char c = (s->fmt & (CM_CFMT_16BIT << CM_CFMT_DACSHIFT)) ? 0 : 0x80; + unsigned char *buf = s->dma_dac.rawbuf; + unsigned char *buf1 = s->dma_adc.rawbuf; + unsigned bsize = s->dma_dac.dmasize; + unsigned bptr = s->dma_dac.swptr; + unsigned len = s->dma_dac.fragsize; + + if (bptr + len > bsize) { + unsigned x = bsize - bptr; + memset(buf + bptr, c, x); + if (s->status & DO_DUAL_DAC) + memset(buf1 + bptr, c, x); + bptr = 0; + len -= x; + } + memset(buf + bptr, c, len); + if (s->status & DO_DUAL_DAC) + memset(buf1 + bptr, c, len); +} + +/* call with spinlock held! */ +static void cm_update_ptr(struct cm_state *s) +{ + unsigned hwptr; + int diff; + + /* update ADC pointer */ + if (s->dma_adc.ready) { + if (s->status & DO_DUAL_DAC) { + /* the dac part will finish for this */ + } else { + hwptr = get_dmaadc(s) % s->dma_adc.dmasize; + diff = (s->dma_adc.dmasize + hwptr - s->dma_adc.hwptr) % s->dma_adc.dmasize; + s->dma_adc.hwptr = hwptr; + s->dma_adc.total_bytes += diff; + s->dma_adc.count += diff; + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + wake_up(&s->dma_adc.wait); + if (!s->dma_adc.mapped) { + if (s->dma_adc.count > (signed)(s->dma_adc.dmasize - ((3 * s->dma_adc.fragsize) >> 1))) { + pause_adc(s); + s->dma_adc.error++; + } + } + } + } + /* update DAC pointer */ + if (s->dma_dac.ready) { + hwptr = get_dmadac(s) % s->dma_dac.dmasize; + diff = (s->dma_dac.dmasize + hwptr - s->dma_dac.hwptr) % s->dma_dac.dmasize; + s->dma_dac.hwptr = hwptr; + s->dma_dac.total_bytes += diff; + if (s->status & DO_DUAL_DAC) { + s->dma_adc.hwptr = hwptr; + s->dma_adc.total_bytes += diff; + } + if (s->dma_dac.mapped) { + s->dma_dac.count += diff; + if (s->status & DO_DUAL_DAC) + s->dma_adc.count += diff; + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) + wake_up(&s->dma_dac.wait); + } else { + s->dma_dac.count -= diff; + if (s->status & DO_DUAL_DAC) + s->dma_adc.count -= diff; + if (s->dma_dac.count <= 0) { + pause_dac(s); + s->dma_dac.error++; + } else if (s->dma_dac.count <= (signed)s->dma_dac.fragsize && !s->dma_dac.endcleared) { + clear_advance(s); + s->dma_dac.endcleared = 1; + if (s->status & DO_DUAL_DAC) + s->dma_adc.endcleared = 1; + } + if (s->dma_dac.count + (signed)s->dma_dac.fragsize <= (signed)s->dma_dac.dmasize) + wake_up(&s->dma_dac.wait); + } + } +} + +static irqreturn_t cm_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct cm_state *s = (struct cm_state *)dev_id; + unsigned int intsrc, intstat; + unsigned char mask = 0; + + /* fastpath out, to ease interrupt sharing */ + intsrc = inl(s->iobase + CODEC_CMI_INT_STATUS); + if (!(intsrc & 0x80000000)) + return IRQ_NONE; + spin_lock(&s->lock); + intstat = inb(s->iobase + CODEC_CMI_INT_HLDCLR + 2); + /* acknowledge interrupt */ + if (intsrc & ADCINT) + mask |= ENADCINT; + if (intsrc & DACINT) + mask |= ENDACINT; + outb(intstat & ~mask, s->iobase + CODEC_CMI_INT_HLDCLR + 2); + outb(intstat | mask, s->iobase + CODEC_CMI_INT_HLDCLR + 2); + cm_update_ptr(s); + spin_unlock(&s->lock); +#ifdef CONFIG_SOUND_CMPCI_MIDI + if (intsrc & 0x00010000) { // UART interrupt + if (s->midi_devc && intchk_mpu401((void *)s->midi_devc)) + mpuintr(irq, (void *)s->midi_devc, regs); + else + inb(s->iomidi);// dummy read + } +#endif + return IRQ_HANDLED; +} + +/* --------------------------------------------------------------------- */ + +static const char invalid_magic[] = KERN_CRIT "cmpci: invalid magic value\n"; + +#define VALIDATE_STATE(s) \ +({ \ + if (!(s) || (s)->magic != CM_MAGIC) { \ + printk(invalid_magic); \ + return -ENXIO; \ + } \ +}) + +/* --------------------------------------------------------------------- */ + +#define MT_4 1 +#define MT_5MUTE 2 +#define MT_4MUTEMONO 3 +#define MT_6MUTE 4 +#define MT_5MUTEMONO 5 + +static const struct { + unsigned left; + unsigned right; + unsigned type; + unsigned rec; + unsigned play; +} mixtable[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_CD] = { DSP_MIX_CDVOLIDX_L, DSP_MIX_CDVOLIDX_R, MT_5MUTE, 0x04, 0x06 }, + [SOUND_MIXER_LINE] = { DSP_MIX_LINEVOLIDX_L, DSP_MIX_LINEVOLIDX_R, MT_5MUTE, 0x10, 0x18 }, + [SOUND_MIXER_MIC] = { DSP_MIX_MICVOLIDX, DSP_MIX_MICVOLIDX, MT_5MUTEMONO, 0x01, 0x01 }, + [SOUND_MIXER_SYNTH] = { DSP_MIX_FMVOLIDX_L, DSP_MIX_FMVOLIDX_R, MT_5MUTE, 0x40, 0x00 }, + [SOUND_MIXER_VOLUME] = { DSP_MIX_MASTERVOLIDX_L, DSP_MIX_MASTERVOLIDX_R, MT_5MUTE, 0x00, 0x00 }, + [SOUND_MIXER_PCM] = { DSP_MIX_VOICEVOLIDX_L, DSP_MIX_VOICEVOLIDX_R, MT_5MUTE, 0x00, 0x00 }, + [SOUND_MIXER_LINE1] = { DSP_MIX_AUXVOL_L, DSP_MIX_AUXVOL_R, MT_5MUTE, 0x80, 0x60 }, + [SOUND_MIXER_SPEAKER]= { DSP_MIX_SPKRVOLIDX, DSP_MIX_SPKRVOLIDX, MT_5MUTEMONO, 0x00, 0x01 } +}; + +static const unsigned char volidx[SOUND_MIXER_NRDEVICES] = +{ + [SOUND_MIXER_CD] = 1, + [SOUND_MIXER_LINE] = 2, + [SOUND_MIXER_MIC] = 3, + [SOUND_MIXER_SYNTH] = 4, + [SOUND_MIXER_VOLUME] = 5, + [SOUND_MIXER_PCM] = 6, + [SOUND_MIXER_LINE1] = 7, + [SOUND_MIXER_SPEAKER]= 8 +}; + +static unsigned mixer_outmask(struct cm_state *s) +{ + unsigned long flags; + int i, j, k; + + spin_lock_irqsave(&s->lock, flags); + j = rdmixer(s, DSP_MIX_OUTMIXIDX); + spin_unlock_irqrestore(&s->lock, flags); + for (k = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (j & mixtable[i].play) + k |= 1 << i; + return k; +} + +static unsigned mixer_recmask(struct cm_state *s) +{ + unsigned long flags; + int i, j, k; + + spin_lock_irqsave(&s->lock, flags); + j = rdmixer(s, DSP_MIX_ADCMIXIDX_L); + spin_unlock_irqrestore(&s->lock, flags); + for (k = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (j & mixtable[i].rec) + k |= 1 << i; + return k; +} + +static int mixer_ioctl(struct cm_state *s, unsigned int cmd, unsigned long arg) +{ + unsigned long flags; + int i, val, j; + unsigned char l, r, rl, rr; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + VALIDATE_STATE(s); + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + memset(&info, 0, sizeof(info)); + strlcpy(info.id, "cmpci", sizeof(info.id)); + strlcpy(info.name, "C-Media PCI", sizeof(info.name)); + info.modify_counter = s->mix.modcnt; + if (copy_to_user(argp, &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, "cmpci", sizeof(info.id)); + strlcpy(info.name, "C-Media cmpci", sizeof(info.name)); + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, p); + if (_IOC_TYPE(cmd) != 'M' || _SIOC_SIZE(cmd) != sizeof(int)) + return -EINVAL; + if (_SIOC_DIR(cmd) == _SIOC_READ) { + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + val = mixer_recmask(s); + return put_user(val, p); + + case SOUND_MIXER_OUTSRC: /* Arg contains a bit for each recording source */ + val = mixer_outmask(s); + return put_user(val, p); + + case SOUND_MIXER_DEVMASK: /* Arg contains a bit for each supported device */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].type) + val |= 1 << i; + return put_user(val, p); + + case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].rec) + val |= 1 << i; + return put_user(val, p); + + case SOUND_MIXER_OUTMASK: /* Arg contains a bit for each supported recording source */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].play) + val |= 1 << i; + return put_user(val, p); + + case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].type && mixtable[i].type != MT_4MUTEMONO) + val |= 1 << i; + return put_user(val, p); + + case SOUND_MIXER_CAPS: + return put_user(0, p); + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES || !mixtable[i].type) + return -EINVAL; + if (!volidx[i]) + return -EINVAL; + return put_user(s->mix.vol[volidx[i]-1], p); + } + } + if (_SIOC_DIR(cmd) != (_SIOC_READ|_SIOC_WRITE)) + return -EINVAL; + s->mix.modcnt++; + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + if (get_user(val, p)) + return -EFAULT; + i = generic_hweight32(val); + for (j = i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!(val & (1 << i))) + continue; + if (!mixtable[i].rec) { + val &= ~(1 << i); + continue; + } + j |= mixtable[i].rec; + } + spin_lock_irqsave(&s->lock, flags); + wrmixer(s, DSP_MIX_ADCMIXIDX_L, j); + wrmixer(s, DSP_MIX_ADCMIXIDX_R, (j & 1) | (j>>1) | (j & 0x80)); + spin_unlock_irqrestore(&s->lock, flags); + return 0; + + case SOUND_MIXER_OUTSRC: /* Arg contains a bit for each recording source */ + if (get_user(val, p)) + return -EFAULT; + for (j = i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!(val & (1 << i))) + continue; + if (!mixtable[i].play) { + val &= ~(1 << i); + continue; + } + j |= mixtable[i].play; + } + spin_lock_irqsave(&s->lock, flags); + wrmixer(s, DSP_MIX_OUTMIXIDX, j); + spin_unlock_irqrestore(&s->lock, flags); + return 0; + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES || !mixtable[i].type) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + l = val & 0xff; + r = (val >> 8) & 0xff; + if (l > 100) + l = 100; + if (r > 100) + r = 100; + spin_lock_irqsave(&s->lock, flags); + switch (mixtable[i].type) { + case MT_4: + if (l >= 10) + l -= 10; + if (r >= 10) + r -= 10; + frobindir(s, mixtable[i].left, 0xf0, l / 6); + frobindir(s, mixtable[i].right, 0xf0, l / 6); + break; + + case MT_4MUTEMONO: + rl = (l < 4 ? 0 : (l - 5) / 3) & 31; + rr = (rl >> 2) & 7; + wrmixer(s, mixtable[i].left, rl<<3); + if (i == SOUND_MIXER_MIC) + maskb(s->iobase + CODEC_CMI_MIXER2, ~0x0e, rr<<1); + break; + + case MT_5MUTEMONO: + rl = l < 4 ? 0 : (l - 5) / 3; + wrmixer(s, mixtable[i].left, rl<<3); + l = rdmixer(s, DSP_MIX_OUTMIXIDX) & ~mixtable[i].play; + r = rl ? mixtable[i].play : 0; + wrmixer(s, DSP_MIX_OUTMIXIDX, l | r); + /* for recording */ + if (i == SOUND_MIXER_MIC) { + if (s->chip_version >= 37) { + rr = rl >> 1; + maskb(s->iobase + CODEC_CMI_MIXER2, ~0x0e, (rr&0x07)<<1); + frobindir(s, DSP_MIX_EXTENSION, ~0x01, rr>>3); + } else { + rr = rl >> 2; + maskb(s->iobase + CODEC_CMI_MIXER2, ~0x0e, rr<<1); + } + } + break; + + case MT_5MUTE: + rl = l < 4 ? 0 : (l - 5) / 3; + rr = r < 4 ? 0 : (r - 5) / 3; + wrmixer(s, mixtable[i].left, rl<<3); + wrmixer(s, mixtable[i].right, rr<<3); + l = rdmixer(s, DSP_MIX_OUTMIXIDX); + l &= ~mixtable[i].play; + r = (rl|rr) ? mixtable[i].play : 0; + wrmixer(s, DSP_MIX_OUTMIXIDX, l | r); + break; + + case MT_6MUTE: + if (l < 6) + rl = 0x00; + else + rl = l * 2 / 3; + if (r < 6) + rr = 0x00; + else + rr = r * 2 / 3; + wrmixer(s, mixtable[i].left, rl); + wrmixer(s, mixtable[i].right, rr); + break; + } + spin_unlock_irqrestore(&s->lock, flags); + + if (!volidx[i]) + return -EINVAL; + s->mix.vol[volidx[i]-1] = val; + return put_user(s->mix.vol[volidx[i]-1], p); + } +} + +/* --------------------------------------------------------------------- */ + +static int cm_open_mixdev(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct list_head *list; + struct cm_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct cm_state, devs); + if (s->dev_mixer == minor) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + return nonseekable_open(inode, file); +} + +static int cm_release_mixdev(struct inode *inode, struct file *file) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + + VALIDATE_STATE(s); + return 0; +} + +static int cm_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + return mixer_ioctl((struct cm_state *)file->private_data, cmd, arg); +} + +static /*const*/ struct file_operations cm_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = cm_ioctl_mixdev, + .open = cm_open_mixdev, + .release = cm_release_mixdev, +}; + + +/* --------------------------------------------------------------------- */ + +static int drain_dac(struct cm_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int count, tmo; + + if (s->dma_dac.mapped || !s->dma_dac.ready) + return 0; + add_wait_queue(&s->dma_dac.wait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_dac.wait, &wait); + set_current_state(TASK_RUNNING); + return -EBUSY; + } + tmo = 3 * HZ * (count + s->dma_dac.fragsize) / 2 / s->ratedac; + tmo >>= sample_shift[(s->fmt >> CM_CFMT_DACSHIFT) & CM_CFMT_MASK]; + if (!schedule_timeout(tmo + 1)) + DBG(printk(KERN_DEBUG "cmpci: dma timed out??\n");) + } + remove_wait_queue(&s->dma_dac.wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static ssize_t cm_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_adc.mapped) + return -ENXIO; + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; + + add_wait_queue(&s->dma_adc.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + swptr = s->dma_adc.swptr; + cnt = s->dma_adc.dmasize-swptr; + if (s->dma_adc.count < cnt) + cnt = s->dma_adc.count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (s->dma_adc.enabled) + start_adc(s); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + if (!schedule_timeout(HZ)) { + printk(KERN_DEBUG "cmpci: read: chip lockup? dmasz %u fragsz %u count %i hwptr %u swptr %u\n", + s->dma_adc.dmasize, s->dma_adc.fragsize, s->dma_adc.count, + s->dma_adc.hwptr, s->dma_adc.swptr); + spin_lock_irqsave(&s->lock, flags); + stop_adc_unlocked(s); + set_dmaadc(s, s->dma_adc.dmaaddr, s->dma_adc.dmasamples); + /* program sample counts */ + set_countadc(s, s->dma_adc.fragsamples); + s->dma_adc.count = s->dma_adc.hwptr = s->dma_adc.swptr = 0; + spin_unlock_irqrestore(&s->lock, flags); + } + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out; + } + continue; + } + if (s->status & DO_BIGENDIAN_R) { + int i, err; + unsigned char *src; + char __user *dst = buffer; + unsigned char data[2]; + + src = (unsigned char *) (s->dma_adc.rawbuf + swptr); + // copy left/right sample at one time + for (i = 0; i < cnt / 2; i++) { + data[0] = src[1]; + data[1] = src[0]; + if ((err = __put_user(data[0], dst++))) { + ret = err; + goto out; + } + if ((err = __put_user(data[1], dst++))) { + ret = err; + goto out; + } + src += 2; + } + } else if (copy_to_user(buffer, s->dma_adc.rawbuf + swptr, cnt)) { + if (!ret) + ret = -EFAULT; + goto out; + } + swptr = (swptr + cnt) % s->dma_adc.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_adc.swptr = swptr; + s->dma_adc.count -= cnt; + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->dma_adc.enabled) + start_adc_unlocked(s); + spin_unlock_irqrestore(&s->lock, flags); + } +out: + remove_wait_queue(&s->dma_adc.wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +static ssize_t cm_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_dac.mapped) + return -ENXIO; + if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0))) + return ret; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + if (s->status & DO_DUAL_DAC) { + if (s->dma_adc.mapped) + return -ENXIO; + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + } + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; + + add_wait_queue(&s->dma_dac.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + if (s->dma_dac.count < 0) { + s->dma_dac.count = 0; + s->dma_dac.swptr = s->dma_dac.hwptr; + } + if (s->status & DO_DUAL_DAC) { + s->dma_adc.swptr = s->dma_dac.swptr; + s->dma_adc.count = s->dma_dac.count; + s->dma_adc.endcleared = s->dma_dac.endcleared; + } + swptr = s->dma_dac.swptr; + cnt = s->dma_dac.dmasize-swptr; + if (s->status & DO_AC3_SW) { + if (s->dma_dac.count + 2 * cnt > s->dma_dac.dmasize) + cnt = (s->dma_dac.dmasize - s->dma_dac.count) / 2; + } else { + if (s->dma_dac.count + cnt > s->dma_dac.dmasize) + cnt = s->dma_dac.dmasize - s->dma_dac.count; + } + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if ((s->status & DO_DUAL_DAC) && (cnt > count / 2)) + cnt = count / 2; + if (cnt <= 0) { + if (s->dma_dac.enabled) + start_dac(s); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + if (!schedule_timeout(HZ)) { + printk(KERN_DEBUG "cmpci: write: chip lockup? dmasz %u fragsz %u count %i hwptr %u swptr %u\n", + s->dma_dac.dmasize, s->dma_dac.fragsize, s->dma_dac.count, + s->dma_dac.hwptr, s->dma_dac.swptr); + spin_lock_irqsave(&s->lock, flags); + stop_dac_unlocked(s); + set_dmadac(s, s->dma_dac.dmaaddr, s->dma_dac.dmasamples); + /* program sample counts */ + set_countdac(s, s->dma_dac.fragsamples); + s->dma_dac.count = s->dma_dac.hwptr = s->dma_dac.swptr = 0; + if (s->status & DO_DUAL_DAC) { + set_dmadac1(s, s->dma_adc.dmaaddr, s->dma_adc.dmasamples); + s->dma_adc.count = s->dma_adc.hwptr = s->dma_adc.swptr = 0; + } + spin_unlock_irqrestore(&s->lock, flags); + } + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out; + } + continue; + } + if (s->status & DO_AC3_SW) { + int err; + + // clip exceeded data, caught by 033 and 037 + if (swptr + 2 * cnt > s->dma_dac.dmasize) + cnt = (s->dma_dac.dmasize - swptr) / 2; + if ((err = trans_ac3(s, s->dma_dac.rawbuf + swptr, buffer, cnt))) { + ret = err; + goto out; + } + swptr = (swptr + 2 * cnt) % s->dma_dac.dmasize; + } else if ((s->status & DO_DUAL_DAC) && (s->status & DO_BIGENDIAN_W)) { + int i, err; + const char __user *src = buffer; + unsigned char *dst0, *dst1; + unsigned char data[8]; + + dst0 = (unsigned char *) (s->dma_dac.rawbuf + swptr); + dst1 = (unsigned char *) (s->dma_adc.rawbuf + swptr); + // copy left/right sample at one time + for (i = 0; i < cnt / 4; i++) { + if ((err = __get_user(data[0], src++))) { + ret = err; + goto out; + } + if ((err = __get_user(data[1], src++))) { + ret = err; + goto out; + } + if ((err = __get_user(data[2], src++))) { + ret = err; + goto out; + } + if ((err = __get_user(data[3], src++))) { + ret = err; + goto out; + } + if ((err = __get_user(data[4], src++))) { + ret = err; + goto out; + } + if ((err = __get_user(data[5], src++))) { + ret = err; + goto out; + } + if ((err = __get_user(data[6], src++))) { + ret = err; + goto out; + } + if ((err = __get_user(data[7], src++))) { + ret = err; + goto out; + } + dst0[0] = data[1]; + dst0[1] = data[0]; + dst0[2] = data[3]; + dst0[3] = data[2]; + dst1[0] = data[5]; + dst1[1] = data[4]; + dst1[2] = data[7]; + dst1[3] = data[6]; + dst0 += 4; + dst1 += 4; + } + swptr = (swptr + cnt) % s->dma_dac.dmasize; + } else if (s->status & DO_DUAL_DAC) { + int i, err; + unsigned long __user *src = (unsigned long __user *) buffer; + unsigned long *dst0, *dst1; + + dst0 = (unsigned long *) (s->dma_dac.rawbuf + swptr); + dst1 = (unsigned long *) (s->dma_adc.rawbuf + swptr); + // copy left/right sample at one time + for (i = 0; i < cnt / 4; i++) { + if ((err = __get_user(*dst0++, src++))) { + ret = err; + goto out; + } + if ((err = __get_user(*dst1++, src++))) { + ret = err; + goto out; + } + } + swptr = (swptr + cnt) % s->dma_dac.dmasize; + } else if (s->status & DO_BIGENDIAN_W) { + int i, err; + const char __user *src = buffer; + unsigned char *dst; + unsigned char data[2]; + + dst = (unsigned char *) (s->dma_dac.rawbuf + swptr); + // swap hi/lo bytes for each sample + for (i = 0; i < cnt / 2; i++) { + if ((err = __get_user(data[0], src++))) { + ret = err; + goto out; + } + if ((err = __get_user(data[1], src++))) { + ret = err; + goto out; + } + dst[0] = data[1]; + dst[1] = data[0]; + dst += 2; + } + swptr = (swptr + cnt) % s->dma_dac.dmasize; + } else { + if (copy_from_user(s->dma_dac.rawbuf + swptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + goto out; + } + swptr = (swptr + cnt) % s->dma_dac.dmasize; + } + spin_lock_irqsave(&s->lock, flags); + s->dma_dac.swptr = swptr; + s->dma_dac.count += cnt; + if (s->status & DO_AC3_SW) + s->dma_dac.count += cnt; + s->dma_dac.endcleared = 0; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->status & DO_DUAL_DAC) { + count -= cnt; + buffer += cnt; + ret += cnt; + } + if (s->dma_dac.enabled) + start_dac(s); + } +out: + remove_wait_queue(&s->dma_dac.wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +static unsigned int cm_poll(struct file *file, struct poll_table_struct *wait) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) { + if (!s->dma_dac.ready && prog_dmabuf(s, 0)) + return 0; + poll_wait(file, &s->dma_dac.wait, wait); + } + if (file->f_mode & FMODE_READ) { + if (!s->dma_adc.ready && prog_dmabuf(s, 1)) + return 0; + poll_wait(file, &s->dma_adc.wait, wait); + } + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + if (file->f_mode & FMODE_READ) { + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->dma_dac.mapped) { + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)s->dma_dac.dmasize >= s->dma_dac.count + (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int cm_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + struct dmabuf *db; + int ret = -EINVAL; + unsigned long size; + + VALIDATE_STATE(s); + lock_kernel(); + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf(s, 0)) != 0) + goto out; + db = &s->dma_dac; + } else if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf(s, 1)) != 0) + goto out; + db = &s->dma_adc; + } else + goto out; + ret = -EINVAL; + if (vma->vm_pgoff != 0) + goto out; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << db->buforder)) + goto out; + ret = -EINVAL; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(db->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + goto out; + db->mapped = 1; + ret = 0; +out: + unlock_kernel(); + return ret; +} + +#define SNDCTL_SPDIF_COPYRIGHT _SIOW('S', 0, int) // set/reset S/PDIF copy protection +#define SNDCTL_SPDIF_LOOP _SIOW('S', 1, int) // set/reset S/PDIF loop +#define SNDCTL_SPDIF_MONITOR _SIOW('S', 2, int) // set S/PDIF monitor +#define SNDCTL_SPDIF_LEVEL _SIOW('S', 3, int) // set/reset S/PDIF out level +#define SNDCTL_SPDIF_INV _SIOW('S', 4, int) // set/reset S/PDIF in inverse +#define SNDCTL_SPDIF_SEL2 _SIOW('S', 5, int) // set S/PDIF in #2 +#define SNDCTL_SPDIF_VALID _SIOW('S', 6, int) // set S/PDIF valid +#define SNDCTL_SPDIFOUT _SIOW('S', 7, int) // set S/PDIF out +#define SNDCTL_SPDIFIN _SIOW('S', 8, int) // set S/PDIF out + +static int cm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int val, mapped, ret; + unsigned char fmtm, fmtd; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + 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: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_SYNC: + 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 | DSP_CAP_BIND, p); + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + synchronize_irq(s->irq); + s->dma_dac.swptr = s->dma_dac.hwptr = s->dma_dac.count = s->dma_dac.total_bytes = 0; + if (s->status & DO_DUAL_DAC) + s->dma_adc.swptr = s->dma_adc.hwptr = s->dma_adc.count = s->dma_adc.total_bytes = 0; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(s->irq); + s->dma_adc.swptr = s->dma_adc.hwptr = s->dma_adc.count = s->dma_adc.total_bytes = 0; + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { + if (file->f_mode & FMODE_READ) { + spin_lock_irqsave(&s->lock, flags); + stop_adc_unlocked(s); + s->dma_adc.ready = 0; + set_adc_rate_unlocked(s, val); + spin_unlock_irqrestore(&s->lock, flags); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (s->status & DO_DUAL_DAC) + s->dma_adc.ready = 0; + set_dac_rate(s, val); + } + } + return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, p); + + case SNDCTL_DSP_STEREO: + if (get_user(val, p)) + return -EFAULT; + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val) + fmtd |= CM_CFMT_STEREO << CM_CFMT_ADCSHIFT; + else + fmtm &= ~(CM_CFMT_STEREO << CM_CFMT_ADCSHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val) + fmtd |= CM_CFMT_STEREO << CM_CFMT_DACSHIFT; + else + fmtm &= ~(CM_CFMT_STEREO << CM_CFMT_DACSHIFT); + if (s->status & DO_DUAL_DAC) { + s->dma_adc.ready = 0; + if (val) + fmtd |= CM_CFMT_STEREO << CM_CFMT_ADCSHIFT; + else + fmtm &= ~(CM_CFMT_STEREO << CM_CFMT_ADCSHIFT); + } + } + set_fmt(s, fmtm, fmtd); + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + if (val != 0) { + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val >= 2) + fmtd |= CM_CFMT_STEREO << CM_CFMT_ADCSHIFT; + else + fmtm &= ~(CM_CFMT_STEREO << CM_CFMT_ADCSHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val >= 2) + fmtd |= CM_CFMT_STEREO << CM_CFMT_DACSHIFT; + else + fmtm &= ~(CM_CFMT_STEREO << CM_CFMT_DACSHIFT); + if (s->status & DO_DUAL_DAC) { + s->dma_adc.ready = 0; + if (val >= 2) + fmtd |= CM_CFMT_STEREO << CM_CFMT_ADCSHIFT; + else + fmtm &= ~(CM_CFMT_STEREO << CM_CFMT_ADCSHIFT); + } + } + set_fmt(s, fmtm, fmtd); + if ((s->capability & CAN_MULTI_CH) + && (file->f_mode & FMODE_WRITE)) { + val = set_dac_channels(s, val); + return put_user(val, p); + } + } + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (CM_CFMT_STEREO << CM_CFMT_ADCSHIFT) + : (CM_CFMT_STEREO << CM_CFMT_DACSHIFT))) ? 2 : 1, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_BE|AFMT_S16_LE|AFMT_U8| + ((s->capability & CAN_AC3) ? AFMT_AC3 : 0), p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, p)) + return -EFAULT; + if (val != AFMT_QUERY) { + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val == AFMT_S16_BE || val == AFMT_S16_LE) + fmtd |= CM_CFMT_16BIT << CM_CFMT_ADCSHIFT; + else + fmtm &= ~(CM_CFMT_16BIT << CM_CFMT_ADCSHIFT); + if (val == AFMT_S16_BE) + s->status |= DO_BIGENDIAN_R; + else + s->status &= ~DO_BIGENDIAN_R; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val == AFMT_S16_BE || val == AFMT_S16_LE || val == AFMT_AC3) + fmtd |= CM_CFMT_16BIT << CM_CFMT_DACSHIFT; + else + fmtm &= ~(CM_CFMT_16BIT << CM_CFMT_DACSHIFT); + if (val == AFMT_AC3) { + fmtd |= CM_CFMT_STEREO << CM_CFMT_DACSHIFT; + set_ac3(s, 48000); + } else + set_ac3(s, 0); + if (s->status & DO_DUAL_DAC) { + s->dma_adc.ready = 0; + if (val == AFMT_S16_BE || val == AFMT_S16_LE) + fmtd |= CM_CFMT_STEREO << CM_CFMT_ADCSHIFT; + else + fmtm &= ~(CM_CFMT_STEREO << CM_CFMT_ADCSHIFT); + } + if (val == AFMT_S16_BE) + s->status |= DO_BIGENDIAN_W; + else + s->status &= ~DO_BIGENDIAN_W; + } + set_fmt(s, fmtm, fmtd); + } + if (s->status & DO_AC3) return put_user(AFMT_AC3, p); + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (CM_CFMT_16BIT << CM_CFMT_ADCSHIFT) + : (CM_CFMT_16BIT << CM_CFMT_DACSHIFT))) ? val : AFMT_U8, p); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if (s->status & DO_DUAL_DAC) { + if (file->f_mode & FMODE_WRITE && + (s->enable & ENDAC) && + (s->enable & ENADC)) + val |= PCM_ENABLE_OUTPUT; + return put_user(val, p); + } + if (file->f_mode & FMODE_READ && s->enable & ENADC) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && s->enable & ENDAC) + val |= PCM_ENABLE_OUTPUT; + return put_user(val, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + if (val & PCM_ENABLE_INPUT) { + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + s->dma_adc.enabled = 1; + start_adc(s); + } else { + s->dma_adc.enabled = 0; + stop_adc(s); + } + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) { + if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0))) + return ret; + if (s->status & DO_DUAL_DAC) { + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + } + s->dma_dac.enabled = 1; + start_dac(s); + } else { + s->dma_dac.enabled = 0; + stop_dac(s); + } + } + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!(s->enable & ENDAC) && (val = prog_dmabuf(s, 0)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + abinfo.fragsize = s->dma_dac.fragsize; + abinfo.bytes = s->dma_dac.dmasize - s->dma_dac.count; + abinfo.fragstotal = s->dma_dac.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!(s->enable & ENADC) && (val = prog_dmabuf(s, 1)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + 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(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + val = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + return put_user(val, p); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + cinfo.bytes = s->dma_adc.total_bytes; + cinfo.blocks = s->dma_adc.count >> s->dma_adc.fragshift; + 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(argp, &cinfo, sizeof(cinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + cinfo.bytes = s->dma_dac.total_bytes; + 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; + if (s->status & DO_DUAL_DAC) { + if (s->dma_adc.mapped) + s->dma_adc.count &= s->dma_adc.fragsize-1; + } + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user(argp, &cinfo, sizeof(cinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + if ((val = prog_dmabuf(s, 0))) + return val; + if (s->status & DO_DUAL_DAC) { + if ((val = prog_dmabuf(s, 1))) + return val; + return put_user(2 * s->dma_dac.fragsize, p); + } + return put_user(s->dma_dac.fragsize, p); + } + if ((val = prog_dmabuf(s, 1))) + return val; + return put_user(s->dma_adc.fragsize, p); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = val & 0xffff; + s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_adc.ossfragshift < 4) + s->dma_adc.ossfragshift = 4; + if (s->dma_adc.ossfragshift > 15) + s->dma_adc.ossfragshift = 15; + if (s->dma_adc.ossmaxfrags < 4) + s->dma_adc.ossmaxfrags = 4; + } + if (file->f_mode & FMODE_WRITE) { + s->dma_dac.ossfragshift = val & 0xffff; + s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac.ossfragshift < 4) + s->dma_dac.ossfragshift = 4; + if (s->dma_dac.ossfragshift > 15) + s->dma_dac.ossfragshift = 15; + if (s->dma_dac.ossmaxfrags < 4) + s->dma_dac.ossmaxfrags = 4; + if (s->status & DO_DUAL_DAC) { + s->dma_adc.ossfragshift = s->dma_dac.ossfragshift; + s->dma_adc.ossmaxfrags = s->dma_dac.ossmaxfrags; + } + } + return 0; + + 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, p)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + if (file->f_mode & FMODE_READ) + s->dma_adc.subdivision = val; + if (file->f_mode & FMODE_WRITE) { + s->dma_dac.subdivision = val; + if (s->status & DO_DUAL_DAC) + s->dma_adc.subdivision = val; + } + return 0; + + case SOUND_PCM_READ_RATE: + return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, p); + + case SOUND_PCM_READ_CHANNELS: + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (CM_CFMT_STEREO << CM_CFMT_ADCSHIFT) : (CM_CFMT_STEREO << CM_CFMT_DACSHIFT))) ? 2 : 1, p); + + case SOUND_PCM_READ_BITS: + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (CM_CFMT_16BIT << CM_CFMT_ADCSHIFT) : (CM_CFMT_16BIT << CM_CFMT_DACSHIFT))) ? 16 : 8, p); + + case SOUND_PCM_READ_FILTER: + return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, p); + + case SNDCTL_DSP_GETCHANNELMASK: + return put_user(DSP_BIND_FRONT|DSP_BIND_SURR|DSP_BIND_CENTER_LFE|DSP_BIND_SPDIF, p); + + case SNDCTL_DSP_BIND_CHANNEL: + if (get_user(val, p)) + return -EFAULT; + if (val == DSP_BIND_QUERY) { + val = DSP_BIND_FRONT; + if (s->status & DO_SPDIF_OUT) + val |= DSP_BIND_SPDIF; + else { + if (s->curr_channels == 4) + val |= DSP_BIND_SURR; + if (s->curr_channels > 4) + val |= DSP_BIND_CENTER_LFE; + } + } else { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val & DSP_BIND_SPDIF) { + set_spdifin(s, s->rateadc); + if (!(s->status & DO_SPDIF_OUT)) + val &= ~DSP_BIND_SPDIF; + } + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val & DSP_BIND_SPDIF) { + set_spdifout(s, s->ratedac); + set_dac_channels(s, s->fmt & (CM_CFMT_STEREO << CM_CFMT_DACSHIFT) ? 2 : 1); + if (!(s->status & DO_SPDIF_OUT)) + val &= ~DSP_BIND_SPDIF; + } else { + int channels; + int mask; + + mask = val & (DSP_BIND_FRONT|DSP_BIND_SURR|DSP_BIND_CENTER_LFE); + switch (mask) { + case DSP_BIND_FRONT: + channels = 2; + break; + case DSP_BIND_FRONT|DSP_BIND_SURR: + channels = 4; + break; + case DSP_BIND_FRONT|DSP_BIND_SURR|DSP_BIND_CENTER_LFE: + channels = 6; + break; + default: + channels = s->fmt & (CM_CFMT_STEREO << CM_CFMT_DACSHIFT) ? 2 : 1; + break; + } + set_dac_channels(s, channels); + } + } + } + return put_user(val, p); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + case SNDCTL_DSP_SETSYNCRO: + return -EINVAL; + case SNDCTL_SPDIF_COPYRIGHT: + if (get_user(val, p)) + return -EFAULT; + set_spdif_copyright(s, val); + return 0; + case SNDCTL_SPDIF_LOOP: + if (get_user(val, p)) + return -EFAULT; + set_spdif_loop(s, val); + return 0; + case SNDCTL_SPDIF_MONITOR: + if (get_user(val, p)) + return -EFAULT; + set_spdif_monitor(s, val); + return 0; + case SNDCTL_SPDIF_LEVEL: + if (get_user(val, p)) + return -EFAULT; + set_spdifout_level(s, val); + return 0; + case SNDCTL_SPDIF_INV: + if (get_user(val, p)) + return -EFAULT; + set_spdifin_inverse(s, val); + return 0; + case SNDCTL_SPDIF_SEL2: + if (get_user(val, p)) + return -EFAULT; + set_spdifin_channel2(s, val); + return 0; + case SNDCTL_SPDIF_VALID: + if (get_user(val, p)) + return -EFAULT; + set_spdifin_valid(s, val); + return 0; + case SNDCTL_SPDIFOUT: + if (get_user(val, p)) + return -EFAULT; + set_spdifout(s, val ? s->ratedac : 0); + return 0; + case SNDCTL_SPDIFIN: + if (get_user(val, p)) + return -EFAULT; + set_spdifin(s, val ? s->rateadc : 0); + return 0; + } + return mixer_ioctl(s, cmd, arg); +} + +static int cm_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned char fmtm = ~0, fmts = 0; + struct list_head *list; + struct cm_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct cm_state, devs); + if (!((s->dev_audio ^ minor) & ~0xf)) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & file->f_mode) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + if (file->f_mode & FMODE_READ) { + s->status &= ~DO_BIGENDIAN_R; + fmtm &= ~((CM_CFMT_STEREO | CM_CFMT_16BIT) << CM_CFMT_ADCSHIFT); + if ((minor & 0xf) == SND_DEV_DSP16) + fmts |= CM_CFMT_16BIT << CM_CFMT_ADCSHIFT; + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = s->dma_adc.subdivision = 0; + s->dma_adc.enabled = 1; + set_adc_rate(s, 8000); + // spdif-in is turnned off by default + set_spdifin(s, 0); + } + if (file->f_mode & FMODE_WRITE) { + s->status &= ~DO_BIGENDIAN_W; + fmtm &= ~((CM_CFMT_STEREO | CM_CFMT_16BIT) << CM_CFMT_DACSHIFT); + if ((minor & 0xf) == SND_DEV_DSP16) + fmts |= CM_CFMT_16BIT << CM_CFMT_DACSHIFT; + s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = s->dma_dac.subdivision = 0; + s->dma_dac.enabled = 1; + set_dac_rate(s, 8000); + // clear previous multichannel, spdif, ac3 state + set_spdifout(s, 0); + set_ac3(s, 0); + set_dac_channels(s, 1); + } + set_fmt(s, fmtm, fmts); + s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int cm_release(struct inode *inode, struct file *file) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + + VALIDATE_STATE(s); + lock_kernel(); + if (file->f_mode & FMODE_WRITE) + drain_dac(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem); + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + + dealloc_dmabuf(s, &s->dma_dac); + if (s->status & DO_DUAL_DAC) + dealloc_dmabuf(s, &s->dma_adc); + + if (s->status & DO_MULTI_CH) + set_dac_channels(s, 1); + if (s->status & DO_AC3) + set_ac3(s, 0); + if (s->status & DO_SPDIF_OUT) + set_spdifout(s, 0); + /* enable SPDIF loop */ + set_spdif_loop(s, spdif_loop); + s->status &= ~DO_BIGENDIAN_W; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + dealloc_dmabuf(s, &s->dma_adc); + s->status &= ~DO_BIGENDIAN_R; + } + s->open_mode &= ~(file->f_mode & (FMODE_READ|FMODE_WRITE)); + up(&s->open_sem); + wake_up(&s->open_wait); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations cm_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = cm_read, + .write = cm_write, + .poll = cm_poll, + .ioctl = cm_ioctl, + .mmap = cm_mmap, + .open = cm_open, + .release = cm_release, +}; + +/* --------------------------------------------------------------------- */ + +static struct initvol { + int mixch; + int vol; +} initvol[] __devinitdata = { + { SOUND_MIXER_WRITE_CD, 0x4f4f }, + { SOUND_MIXER_WRITE_LINE, 0x4f4f }, + { SOUND_MIXER_WRITE_MIC, 0x4f4f }, + { SOUND_MIXER_WRITE_SYNTH, 0x4f4f }, + { SOUND_MIXER_WRITE_VOLUME, 0x4f4f }, + { SOUND_MIXER_WRITE_PCM, 0x4f4f } +}; + +/* check chip version and capability */ +static int query_chip(struct cm_state *s) +{ + int ChipVersion = -1; + unsigned char RegValue; + + // check reg 0Ch, bit 24-31 + RegValue = inb(s->iobase + CODEC_CMI_INT_HLDCLR + 3); + if (RegValue == 0) { + // check reg 08h, bit 24-28 + RegValue = inb(s->iobase + CODEC_CMI_CHFORMAT + 3); + RegValue &= 0x1f; + if (RegValue == 0) { + ChipVersion = 33; + s->max_channels = 4; + s->capability |= CAN_AC3_SW; + s->capability |= CAN_DUAL_DAC; + } else { + ChipVersion = 37; + s->max_channels = 4; + s->capability |= CAN_AC3_HW; + s->capability |= CAN_DUAL_DAC; + } + } else { + // check reg 0Ch, bit 26 + if (RegValue & (1 << (26-24))) { + ChipVersion = 39; + if (RegValue & (1 << (24-24))) + s->max_channels = 6; + else + s->max_channels = 4; + s->capability |= CAN_AC3_HW; + s->capability |= CAN_DUAL_DAC; + s->capability |= CAN_MULTI_CH_HW; + s->capability |= CAN_LINE_AS_BASS; + s->capability |= CAN_MIC_AS_BASS; + } else { + ChipVersion = 55; // 4 or 6 channels + s->max_channels = 6; + s->capability |= CAN_AC3_HW; + s->capability |= CAN_DUAL_DAC; + s->capability |= CAN_MULTI_CH_HW; + s->capability |= CAN_LINE_AS_BASS; + s->capability |= CAN_MIC_AS_BASS; + } + } + s->capability |= CAN_LINE_AS_REAR; + return ChipVersion; +} + +#ifdef CONFIG_SOUND_CMPCI_JOYSTICK +static int __devinit cm_create_gameport(struct cm_state *s, int io_port) +{ + struct gameport *gp; + + if (!request_region(io_port, CM_EXTENT_GAME, "cmpci GAME")) { + printk(KERN_ERR "cmpci: gameport io ports 0x%#x in use\n", io_port); + return -EBUSY; + } + + if (!(s->gameport = gp = gameport_allocate_port())) { + printk(KERN_ERR "cmpci: can not allocate memory for gameport\n"); + release_region(io_port, CM_EXTENT_GAME); + return -ENOMEM; + } + + gameport_set_name(gp, "C-Media GP"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(s->dev)); + gp->dev.parent = &s->dev->dev; + gp->io = io_port; + + /* enable joystick */ + maskb(s->iobase + CODEC_CMI_FUNCTRL1, ~0, 0x02); + + gameport_register_port(gp); + + return 0; +} + +static void __devexit cm_free_gameport(struct cm_state *s) +{ + if (s->gameport) { + int gpio = s->gameport->io; + + gameport_unregister_port(s->gameport); + s->gameport = NULL; + maskb(s->iobase + CODEC_CMI_FUNCTRL1, ~0x02, 0); + release_region(gpio, CM_EXTENT_GAME); + } +} +#else +static inline int cm_create_gameport(struct cm_state *s, int io_port) { return -ENOSYS; } +static inline void cm_free_gameport(struct cm_state *s) { } +#endif + +#define echo_option(x)\ +if (x) strcat(options, "" #x " ") + +static int __devinit cm_probe(struct pci_dev *pcidev, const struct pci_device_id *pciid) +{ + struct cm_state *s; + mm_segment_t fs; + int i, val, ret; + unsigned char reg_mask; + int timeout; + struct resource *ports; + struct { + unsigned short deviceid; + char *devicename; + } devicetable[] = { + { PCI_DEVICE_ID_CMEDIA_CM8338A, "CM8338A" }, + { PCI_DEVICE_ID_CMEDIA_CM8338B, "CM8338B" }, + { PCI_DEVICE_ID_CMEDIA_CM8738, "CM8738" }, + { PCI_DEVICE_ID_CMEDIA_CM8738B, "CM8738B" }, + }; + char *devicename = "unknown"; + char options[256]; + + if ((ret = pci_enable_device(pcidev))) + return ret; + if (!(pci_resource_flags(pcidev, 0) & IORESOURCE_IO)) + return -ENODEV; + if (pcidev->irq == 0) + return -ENODEV; + i = pci_set_dma_mask(pcidev, 0xffffffff); + if (i) { + printk(KERN_WARNING "cmpci: architecture does not support 32bit PCI busmaster DMA\n"); + return i; + } + s = kmalloc(sizeof(*s), GFP_KERNEL); + if (!s) { + printk(KERN_WARNING "cmpci: out of memory\n"); + return -ENOMEM; + } + /* search device name */ + for (i = 0; i < sizeof(devicetable) / sizeof(devicetable[0]); i++) { + if (devicetable[i].deviceid == pcidev->device) { + devicename = devicetable[i].devicename; + break; + } + } + memset(s, 0, sizeof(struct cm_state)); + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac.wait); + init_waitqueue_head(&s->open_wait); + init_MUTEX(&s->open_sem); + spin_lock_init(&s->lock); + s->magic = CM_MAGIC; + s->dev = pcidev; + s->iobase = pci_resource_start(pcidev, 0); + s->iosynth = fmio; + s->iomidi = mpuio; +#ifdef CONFIG_SOUND_CMPCI_MIDI + s->midi_devc = 0; +#endif + s->status = 0; + if (s->iobase == 0) + return -ENODEV; + s->irq = pcidev->irq; + + if (!request_region(s->iobase, CM_EXTENT_CODEC, "cmpci")) { + printk(KERN_ERR "cmpci: io ports %#x-%#x in use\n", s->iobase, s->iobase+CM_EXTENT_CODEC-1); + ret = -EBUSY; + goto err_region5; + } + /* dump parameters */ + strcpy(options, "cmpci: "); + echo_option(joystick); + echo_option(spdif_inverse); + echo_option(spdif_loop); + echo_option(spdif_out); + echo_option(use_line_as_rear); + echo_option(use_line_as_bass); + echo_option(use_mic_as_bass); + echo_option(mic_boost); + echo_option(hw_copy); + printk(KERN_INFO "%s\n", options); + + /* initialize codec registers */ + outb(0, s->iobase + CODEC_CMI_INT_HLDCLR + 2); /* disable ints */ + outb(0, s->iobase + CODEC_CMI_FUNCTRL0 + 2); /* disable channels */ + /* reset mixer */ + wrmixer(s, DSP_MIX_DATARESETIDX, 0); + + /* request irq */ + if ((ret = request_irq(s->irq, cm_interrupt, SA_SHIRQ, "cmpci", s))) { + printk(KERN_ERR "cmpci: irq %u in use\n", s->irq); + goto err_irq; + } + printk(KERN_INFO "cmpci: found %s adapter at io %#x irq %u\n", + devicename, s->iobase, s->irq); + /* register devices */ + if ((s->dev_audio = register_sound_dsp(&cm_audio_fops, -1)) < 0) { + ret = s->dev_audio; + goto err_dev1; + } + if ((s->dev_mixer = register_sound_mixer(&cm_mixer_fops, -1)) < 0) { + ret = s->dev_mixer; + goto err_dev2; + } + pci_set_master(pcidev); /* enable bus mastering */ + /* initialize the chips */ + fs = get_fs(); + set_fs(KERNEL_DS); + /* set mixer output */ + frobindir(s, DSP_MIX_OUTMIXIDX, 0x1f, 0x1f); + /* set mixer input */ + val = SOUND_MASK_LINE|SOUND_MASK_SYNTH|SOUND_MASK_CD|SOUND_MASK_MIC; + mixer_ioctl(s, SOUND_MIXER_WRITE_RECSRC, (unsigned long)&val); + for (i = 0; i < sizeof(initvol)/sizeof(initvol[0]); i++) { + val = initvol[i].vol; + mixer_ioctl(s, initvol[i].mixch, (unsigned long)&val); + } + set_fs(fs); + /* use channel 1 for playback, channel 0 for record */ + maskb(s->iobase + CODEC_CMI_FUNCTRL0, ~CHADC1, CHADC0); + /* turn off VMIC3 - mic boost */ + if (mic_boost) + maskb(s->iobase + CODEC_CMI_MIXER2, ~1, 0); + else + maskb(s->iobase + CODEC_CMI_MIXER2, ~0, 1); + s->deviceid = pcidev->device; + + if (pcidev->device == PCI_DEVICE_ID_CMEDIA_CM8738 + || pcidev->device == PCI_DEVICE_ID_CMEDIA_CM8738B) { + + /* chip version and hw capability check */ + s->chip_version = query_chip(s); + printk(KERN_INFO "cmpci: chip version = 0%d\n", s->chip_version); + + /* set SPDIF-in inverse before enable SPDIF loop */ + set_spdifin_inverse(s, spdif_inverse); + + /* use SPDIF in #1 */ + set_spdifin_channel2(s, 0); + } else { + s->chip_version = 0; + /* 8338 will fall here */ + s->max_channels = 4; + s->capability |= CAN_DUAL_DAC; + s->capability |= CAN_LINE_AS_REAR; + } + /* enable SPDIF loop */ + set_spdif_loop(s, spdif_loop); + + // enable 4 speaker mode (analog duplicate) + set_hw_copy(s, hw_copy); + + reg_mask = 0; +#ifdef CONFIG_SOUND_CMPCI_FM + /* disable FM */ + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 2, ~8, 0); + if (s->iosynth) { + /* don't enable OPL3 if there is one */ + if (opl3_detect(s->iosynth, NULL)) { + s->iosynth = 0; + } else { + /* set IO based at 0x388 */ + switch (s->iosynth) { + case 0x388: + reg_mask = 0; + break; + case 0x3C8: + reg_mask = 0x01; + break; + case 0x3E0: + reg_mask = 0x02; + break; + case 0x3E8: + reg_mask = 0x03; + break; + default: + s->iosynth = 0; + break; + } + maskb(s->iobase + CODEC_CMI_LEGACY_CTRL + 3, ~0x03, reg_mask); + /* enable FM */ + if (s->iosynth) { + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 2, ~0, 8); + if (opl3_detect(s->iosynth, NULL)) + ret = opl3_init(s->iosynth, NULL, THIS_MODULE); + else { + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 2, ~8, 0); + s->iosynth = 0; + } + } + } + } +#endif +#ifdef CONFIG_SOUND_CMPCI_MIDI + switch (s->iomidi) { + case 0x330: + reg_mask = 0; + break; + case 0x320: + reg_mask = 0x20; + break; + case 0x310: + reg_mask = 0x40; + break; + case 0x300: + reg_mask = 0x60; + break; + default: + s->iomidi = 0; + goto skip_mpu; + } + ports = request_region(s->iomidi, 2, "mpu401"); + if (!ports) + goto skip_mpu; + /* disable MPU-401 */ + maskb(s->iobase + CODEC_CMI_FUNCTRL1, ~0x04, 0); + s->mpu_data.name = "cmpci mpu"; + s->mpu_data.io_base = s->iomidi; + s->mpu_data.irq = -s->irq; // tell mpu401 to share irq + if (probe_mpu401(&s->mpu_data, ports)) { + release_region(s->iomidi, 2); + s->iomidi = 0; + goto skip_mpu; + } + maskb(s->iobase + CODEC_CMI_LEGACY_CTRL + 3, ~0x60, reg_mask); + /* enable MPU-401 */ + maskb(s->iobase + CODEC_CMI_FUNCTRL1, ~0, 0x04); + /* clear all previously received interrupt */ + for (timeout = 900000; timeout > 0; timeout--) { + if ((inb(s->iomidi + 1) && 0x80) == 0) + inb(s->iomidi); + else + break; + } + if (!probe_mpu401(&s->mpu_data, ports)) { + release_region(s->iomidi, 2); + s->iomidi = 0; + maskb(s->iobase + CODEC_CMI_FUNCTRL1, ~0, 0x04); + } else { + attach_mpu401(&s->mpu_data, THIS_MODULE); + s->midi_devc = s->mpu_data.slots[1]; + } +skip_mpu: +#endif + /* disable joystick port */ + maskb(s->iobase + CODEC_CMI_FUNCTRL1, ~0x02, 0); + if (joystick) + cm_create_gameport(s, 0x200); + + /* store it in the driver field */ + pci_set_drvdata(pcidev, s); + /* put it into driver list */ + list_add_tail(&s->devs, &devs); + /* increment devindex */ + if (devindex < NR_DEVICE-1) + devindex++; + return 0; + +err_dev2: + unregister_sound_dsp(s->dev_audio); +err_dev1: + printk(KERN_ERR "cmpci: cannot register misc device\n"); + free_irq(s->irq, s); +err_irq: + release_region(s->iobase, CM_EXTENT_CODEC); +err_region5: + kfree(s); + return ret; +} + +/* --------------------------------------------------------------------- */ + +MODULE_AUTHOR("ChenLi Tien, cltien@cmedia.com.tw"); +MODULE_DESCRIPTION("CM8x38 Audio Driver"); +MODULE_LICENSE("GPL"); + +static void __devexit cm_remove(struct pci_dev *dev) +{ + struct cm_state *s = pci_get_drvdata(dev); + + if (!s) + return; + + cm_free_gameport(s); + +#ifdef CONFIG_SOUND_CMPCI_FM + if (s->iosynth) { + /* disable FM */ + maskb(s->iobase + CODEC_CMI_MISC_CTRL + 2, ~8, 0); + } +#endif +#ifdef CONFIG_SOUND_CMPCI_MIDI + if (s->iomidi) { + unload_mpu401(&s->mpu_data); + /* disable MPU-401 */ + maskb(s->iobase + CODEC_CMI_FUNCTRL1, ~0x04, 0); + } +#endif + set_spdif_loop(s, 0); + list_del(&s->devs); + outb(0, s->iobase + CODEC_CMI_INT_HLDCLR + 2); /* disable ints */ + synchronize_irq(s->irq); + outb(0, s->iobase + CODEC_CMI_FUNCTRL0 + 2); /* disable channels */ + free_irq(s->irq, s); + + /* reset mixer */ + wrmixer(s, DSP_MIX_DATARESETIDX, 0); + + release_region(s->iobase, CM_EXTENT_CODEC); + unregister_sound_dsp(s->dev_audio); + unregister_sound_mixer(s->dev_mixer); + kfree(s); + pci_set_drvdata(dev, NULL); +} + +static struct pci_device_id id_table[] __devinitdata = { + { PCI_VENDOR_ID_CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8738B, PCI_ANY_ID, PCI_ANY_ID, 0, 0 }, + { PCI_VENDOR_ID_CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8738, PCI_ANY_ID, PCI_ANY_ID, 0, 0 }, + { PCI_VENDOR_ID_CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8338A, PCI_ANY_ID, PCI_ANY_ID, 0, 0 }, + { PCI_VENDOR_ID_CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8338B, PCI_ANY_ID, PCI_ANY_ID, 0, 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, id_table); + +static struct pci_driver cm_driver = { + .name = "cmpci", + .id_table = id_table, + .probe = cm_probe, + .remove = __devexit_p(cm_remove) +}; + +static int __init init_cmpci(void) +{ + printk(KERN_INFO "cmpci: version $Revision: 6.82 $ time " __TIME__ " " __DATE__ "\n"); + return pci_module_init(&cm_driver); +} + +static void __exit cleanup_cmpci(void) +{ + printk(KERN_INFO "cmpci: unloading\n"); + pci_unregister_driver(&cm_driver); +} + +module_init(init_cmpci); +module_exit(cleanup_cmpci); diff --git a/sound/oss/coproc.h b/sound/oss/coproc.h new file mode 100644 index 000000000000..7306346e9ac4 --- /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/cs4232.c b/sound/oss/cs4232.c new file mode 100644 index 000000000000..6ec308f5d935 --- /dev/null +++ b/sound/oss/cs4232.c @@ -0,0 +1,520 @@ +/* + * Copyright (C) by Hannu Savolainen 1993-1997 + * + * cs4232.c + * + * The low level driver for Crystal CS4232 based cards. The CS4232 is + * a PnP compatible chip which contains a CS4231A codec, SB emulation, + * a MPU401 compatible MIDI port, joystick and synthesizer and IDE CD-ROM + * interfaces. This is just a temporary driver until full PnP support + * gets implemented. Just the WSS codec, FM synth and the MIDI ports are + * supported. Other interfaces are left uninitialized. + * + * ifdef ...WAVEFRONT... + * + * Support is provided for initializing the WaveFront synth + * interface as well, which is logical device #4. Note that if + * you have a Tropez+ card, you probably don't need to setup + * the CS4232-supported MIDI interface, since it corresponds to + * the internal 26-pin header that's hard to access. Using this + * requires an additional IRQ, a resource none too plentiful in + * this environment. Just don't set module parameters mpuio and + * mpuirq, and the MIDI port will be left uninitialized. You can + * still use the ICS2115 hosted MIDI interface which corresponds + * to the 9-pin D connector on the back of the card. + * + * endif ...WAVEFRONT... + * + * Supported chips are: + * CS4232 + * CS4236 + * CS4236B + * + * Note: You will need a PnP config setup to initialise some CS4232 boards + * anyway. + * + * Changes + * John Rood Added Bose Sound System Support. + * Toshio Spoor + * Alan Cox Modularisation, Basic cleanups. + * Paul Barton-Davis Separated MPU configuration, added + * Tropez+ (WaveFront) support + * Christoph Hellwig Adapted to module_init/module_exit, + * simple cleanups + * Arnaldo C. de Melo got rid of attach_uart401 + * Bartlomiej Zolnierkiewicz + * Added some __init/__initdata/__exit + * Marcus Meissner Added ISA PnP support. + */ + +#include +#include +#include +#include + +#include "sound_config.h" + +#include "ad1848.h" +#include "mpu401.h" + +#define KEY_PORT 0x279 /* Same as LPT1 status port */ +#define CSN_NUM 0x99 /* Just a random number */ +#define INDEX_ADDRESS 0x00 /* (R0) Index Address Register */ +#define INDEX_DATA 0x01 /* (R1) Indexed Data Register */ +#define PIN_CONTROL 0x0a /* (I10) Pin Control */ +#define ENABLE_PINS 0xc0 /* XCTRL0/XCTRL1 enable */ + +static void CS_OUT(unsigned char a) +{ + outb(a, KEY_PORT); +} + +#define CS_OUT2(a, b) {CS_OUT(a);CS_OUT(b);} +#define CS_OUT3(a, b, c) {CS_OUT(a);CS_OUT(b);CS_OUT(c);} + +static int __initdata bss = 0; +static int mpu_base, mpu_irq; +static int synth_base, synth_irq; +static int mpu_detected; + +static int probe_cs4232_mpu(struct address_info *hw_config) +{ + /* + * Just write down the config values. + */ + + mpu_base = hw_config->io_base; + mpu_irq = hw_config->irq; + + return 1; +} + +static unsigned char crystal_key[] = /* A 32 byte magic key sequence */ +{ + 0x96, 0x35, 0x9a, 0xcd, 0xe6, 0xf3, 0x79, 0xbc, + 0x5e, 0xaf, 0x57, 0x2b, 0x15, 0x8a, 0xc5, 0xe2, + 0xf1, 0xf8, 0x7c, 0x3e, 0x9f, 0x4f, 0x27, 0x13, + 0x09, 0x84, 0x42, 0xa1, 0xd0, 0x68, 0x34, 0x1a +}; + +static void sleep(unsigned howlong) +{ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(howlong); +} + +static void enable_xctrl(int baseio) +{ + unsigned char regd; + + /* + * Some IBM Aptiva's have the Bose Sound System. By default + * the Bose Amplifier is disabled. The amplifier will be + * activated, by setting the XCTRL0 and XCTRL1 bits. + * Volume of the monitor bose speakers/woofer, can then + * be set by changing the PCM volume. + * + */ + + printk("cs4232: enabling Bose Sound System Amplifier.\n"); + + /* Switch to Pin Control Address */ + regd = inb(baseio + INDEX_ADDRESS) & 0xe0; + outb(((unsigned char) (PIN_CONTROL | regd)), baseio + INDEX_ADDRESS ); + + /* Activate the XCTRL0 and XCTRL1 Pins */ + regd = inb(baseio + INDEX_DATA); + outb(((unsigned char) (ENABLE_PINS | regd)), baseio + INDEX_DATA ); +} + +static int __init probe_cs4232(struct address_info *hw_config, int isapnp_configured) +{ + int i, n; + int base = hw_config->io_base, irq = hw_config->irq; + int dma1 = hw_config->dma, dma2 = hw_config->dma2; + struct resource *ports; + + if (base == -1 || irq == -1 || dma1 == -1) { + printk(KERN_ERR "cs4232: dma, irq and io must be set.\n"); + return 0; + } + + /* + * Verify that the I/O port range is free. + */ + + ports = request_region(base, 4, "ad1848"); + if (!ports) { + printk(KERN_ERR "cs4232.c: I/O port 0x%03x not free\n", base); + return 0; + } + if (ad1848_detect(ports, NULL, hw_config->osp)) { + goto got_it; /* The card is already active */ + } + if (isapnp_configured) { + printk(KERN_ERR "cs4232.c: ISA PnP configured, but not detected?\n"); + goto fail; + } + + /* + * This version of the driver doesn't use the PnP method when configuring + * the card but a simplified method defined by Crystal. This means that + * just one CS4232 compatible device can exist on the system. Also this + * method conflicts with possible PnP support in the OS. For this reason + * driver is just a temporary kludge. + * + * Also the Cirrus/Crystal method doesn't always work. Try ISA PnP first ;) + */ + + /* + * Repeat initialization few times since it doesn't always succeed in + * first time. + */ + + for (n = 0; n < 4; n++) + { + /* + * Wake up the card by sending a 32 byte Crystal key to the key port. + */ + + for (i = 0; i < 32; i++) + CS_OUT(crystal_key[i]); + + sleep(HZ / 10); + + /* + * Now set the CSN (Card Select Number). + */ + + CS_OUT2(0x06, CSN_NUM); + + /* + * Then set some config bytes. First logical device 0 + */ + + CS_OUT2(0x15, 0x00); /* Select logical device 0 (WSS/SB/FM) */ + CS_OUT3(0x47, (base >> 8) & 0xff, base & 0xff); /* WSS base */ + + if (check_region(0x388, 4)) /* Not free */ + CS_OUT3(0x48, 0x00, 0x00) /* FM base off */ + else + CS_OUT3(0x48, 0x03, 0x88); /* FM base 0x388 */ + + CS_OUT3(0x42, 0x00, 0x00); /* SB base off */ + CS_OUT2(0x22, irq); /* SB+WSS IRQ */ + CS_OUT2(0x2a, dma1); /* SB+WSS DMA */ + + if (dma2 != -1) + CS_OUT2(0x25, dma2) /* WSS DMA2 */ + else + CS_OUT2(0x25, 4); /* No WSS DMA2 */ + + CS_OUT2(0x33, 0x01); /* Activate logical dev 0 */ + + sleep(HZ / 10); + + /* + * Initialize logical device 3 (MPU) + */ + + if (mpu_base != 0 && mpu_irq != 0) + { + CS_OUT2(0x15, 0x03); /* Select logical device 3 (MPU) */ + CS_OUT3(0x47, (mpu_base >> 8) & 0xff, mpu_base & 0xff); /* MPU base */ + CS_OUT2(0x22, mpu_irq); /* MPU IRQ */ + CS_OUT2(0x33, 0x01); /* Activate logical dev 3 */ + } + + if(synth_base != 0) + { + CS_OUT2 (0x15, 0x04); /* logical device 4 (WaveFront) */ + CS_OUT3 (0x47, (synth_base >> 8) & 0xff, + synth_base & 0xff); /* base */ + CS_OUT2 (0x22, synth_irq); /* IRQ */ + CS_OUT2 (0x33, 0x01); /* Activate logical dev 4 */ + } + + /* + * Finally activate the chip + */ + + CS_OUT(0x79); + + sleep(HZ / 5); + + /* + * Then try to detect the codec part of the chip + */ + + if (ad1848_detect(ports, NULL, hw_config->osp)) + goto got_it; + + sleep(HZ); + } +fail: + release_region(base, 4); + return 0; + +got_it: + if (dma2 == -1) + dma2 = dma1; + + hw_config->slots[0] = ad1848_init("Crystal audio controller", ports, + irq, + dma1, /* Playback DMA */ + dma2, /* Capture DMA */ + 0, + hw_config->osp, + THIS_MODULE); + + if (hw_config->slots[0] != -1 && + audio_devs[hw_config->slots[0]]->mixer_dev!=-1) + { + /* Assume the mixer map is as suggested in the CS4232 databook */ + AD1848_REROUTE(SOUND_MIXER_LINE1, SOUND_MIXER_LINE); + AD1848_REROUTE(SOUND_MIXER_LINE2, SOUND_MIXER_CD); + AD1848_REROUTE(SOUND_MIXER_LINE3, SOUND_MIXER_SYNTH); /* FM synth */ + } + if (mpu_base != 0 && mpu_irq != 0) + { + static struct address_info hw_config2 = { + 0 + }; /* Ensure it's initialized */ + + hw_config2.io_base = mpu_base; + hw_config2.irq = mpu_irq; + hw_config2.dma = -1; + hw_config2.dma2 = -1; + hw_config2.always_detect = 0; + hw_config2.name = NULL; + hw_config2.driver_use_1 = 0; + hw_config2.driver_use_2 = 0; + hw_config2.card_subtype = 0; + + if (probe_uart401(&hw_config2, THIS_MODULE)) + { + mpu_detected = 1; + } + else + { + mpu_base = mpu_irq = 0; + } + hw_config->slots[1] = hw_config2.slots[1]; + } + + if (bss) + enable_xctrl(base); + + return 1; +} + +static void __devexit unload_cs4232(struct address_info *hw_config) +{ + int base = hw_config->io_base, irq = hw_config->irq; + int dma1 = hw_config->dma, dma2 = hw_config->dma2; + + if (dma2 == -1) + dma2 = dma1; + + ad1848_unload(base, + irq, + dma1, /* Playback DMA */ + dma2, /* Capture DMA */ + 0); + + sound_unload_audiodev(hw_config->slots[0]); + if (mpu_base != 0 && mpu_irq != 0 && mpu_detected) + { + static struct address_info hw_config2 = + { + 0 + }; /* Ensure it's initialized */ + + hw_config2.io_base = mpu_base; + hw_config2.irq = mpu_irq; + hw_config2.dma = -1; + hw_config2.dma2 = -1; + hw_config2.always_detect = 0; + hw_config2.name = NULL; + hw_config2.driver_use_1 = 0; + hw_config2.driver_use_2 = 0; + hw_config2.card_subtype = 0; + hw_config2.slots[1] = hw_config->slots[1]; + + unload_uart401(&hw_config2); + } +} + +static struct address_info cfg; +static struct address_info cfg_mpu; + +static int __initdata io = -1; +static int __initdata irq = -1; +static int __initdata dma = -1; +static int __initdata dma2 = -1; +static int __initdata mpuio = -1; +static int __initdata mpuirq = -1; +static int __initdata synthio = -1; +static int __initdata synthirq = -1; +static int __initdata isapnp = 1; + +MODULE_DESCRIPTION("CS4232 based soundcard driver"); +MODULE_AUTHOR("Hannu Savolainen, Paul Barton-Davis"); +MODULE_LICENSE("GPL"); + +module_param(io, int, 0); +MODULE_PARM_DESC(io,"base I/O port for AD1848"); +module_param(irq, int, 0); +MODULE_PARM_DESC(irq,"IRQ for AD1848 chip"); +module_param(dma, int, 0); +MODULE_PARM_DESC(dma,"8 bit DMA for AD1848 chip"); +module_param(dma2, int, 0); +MODULE_PARM_DESC(dma2,"16 bit DMA for AD1848 chip"); +module_param(mpuio, int, 0); +MODULE_PARM_DESC(mpuio,"MPU 401 base address"); +module_param(mpuirq, int, 0); +MODULE_PARM_DESC(mpuirq,"MPU 401 IRQ"); +module_param(synthio, int, 0); +MODULE_PARM_DESC(synthio,"Maui WaveTable base I/O port"); +module_param(synthirq, int, 0); +MODULE_PARM_DESC(synthirq,"Maui WaveTable IRQ"); +module_param(isapnp, bool, 0); +MODULE_PARM_DESC(isapnp,"Enable ISAPnP probing (default 1)"); +module_param(bss, bool, 0); +MODULE_PARM_DESC(bss,"Enable Bose Sound System Support (default 0)"); + +/* + * Install a CS4232 based card. Need to have ad1848 and mpu401 + * loaded ready. + */ + +/* All cs4232 based cards have the main ad1848 card either as CSC0000 or + * CSC0100. */ +static const struct pnp_device_id cs4232_pnp_table[] = { + { .id = "CSC0100", .driver_data = 0 }, + { .id = "CSC0000", .driver_data = 0 }, + /* Guillemot Turtlebeach something appears to be cs4232 compatible + * (untested) */ + { .id = "GIM0100", .driver_data = 0 }, + { .id = ""} +}; + +MODULE_DEVICE_TABLE(pnp, cs4232_pnp_table); + +static int cs4232_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) +{ + struct address_info *isapnpcfg; + + isapnpcfg=(struct address_info*)kmalloc(sizeof(*isapnpcfg),GFP_KERNEL); + if (!isapnpcfg) + return -ENOMEM; + + isapnpcfg->irq = pnp_irq(dev, 0); + isapnpcfg->dma = pnp_dma(dev, 0); + isapnpcfg->dma2 = pnp_dma(dev, 1); + isapnpcfg->io_base = pnp_port_start(dev, 0); + if (probe_cs4232(isapnpcfg,TRUE) == 0) { + printk(KERN_ERR "cs4232: ISA PnP card found, but not detected?\n"); + kfree(isapnpcfg); + return -ENODEV; + } + pnp_set_drvdata(dev,isapnpcfg); + return 0; +} + +static void __devexit cs4232_pnp_remove(struct pnp_dev *dev) +{ + struct address_info *cfg = pnp_get_drvdata(dev); + if (cfg) { + unload_cs4232(cfg); + kfree(cfg); + } +} + +static struct pnp_driver cs4232_driver = { + .name = "cs4232", + .id_table = cs4232_pnp_table, + .probe = cs4232_pnp_probe, + .remove = __devexit_p(cs4232_pnp_remove), +}; + +static int __init init_cs4232(void) +{ +#ifdef CONFIG_SOUND_WAVEFRONT_MODULE + if(synthio == -1) + printk(KERN_INFO "cs4232: set synthio and synthirq to use the wavefront facilities.\n"); + else { + synth_base = synthio; + synth_irq = synthirq; + } +#else + if(synthio != -1) + printk(KERN_WARNING "cs4232: wavefront support not enabled in this driver.\n"); +#endif + cfg.irq = -1; + + if (isapnp && + (pnp_register_driver(&cs4232_driver) > 0) + ) + return 0; + + if(io==-1||irq==-1||dma==-1) + { + printk(KERN_ERR "cs4232: Must set io, irq and dma.\n"); + return -ENODEV; + } + + cfg.io_base = io; + cfg.irq = irq; + cfg.dma = dma; + cfg.dma2 = dma2; + + cfg_mpu.io_base = -1; + cfg_mpu.irq = -1; + + if (mpuio != -1 && mpuirq != -1) { + cfg_mpu.io_base = mpuio; + cfg_mpu.irq = mpuirq; + probe_cs4232_mpu(&cfg_mpu); /* Bug always returns 0 not OK -- AC */ + } + + if (probe_cs4232(&cfg,FALSE) == 0) + return -ENODEV; + + return 0; +} + +static void __exit cleanup_cs4232(void) +{ + pnp_unregister_driver(&cs4232_driver); + if (cfg.irq != -1) + unload_cs4232(&cfg); /* Unloads global MPU as well, if needed */ +} + +module_init(init_cs4232); +module_exit(cleanup_cs4232); + +#ifndef MODULE +static int __init setup_cs4232(char *str) +{ + /* io, irq, dma, dma2 mpuio, mpuirq*/ + int ints[7]; + + /* If we have isapnp cards, no need for options */ + if (pnp_register_driver(&cs4232_driver) > 0) + return 1; + + str = get_options(str, ARRAY_SIZE(ints), ints); + + io = ints[1]; + irq = ints[2]; + dma = ints[3]; + dma2 = ints[4]; + mpuio = ints[5]; + mpuirq = ints[6]; + + return 1; +} + +__setup("cs4232=", setup_cs4232); +#endif diff --git a/sound/oss/cs4281/Makefile b/sound/oss/cs4281/Makefile new file mode 100644 index 000000000000..6d527e8530d6 --- /dev/null +++ b/sound/oss/cs4281/Makefile @@ -0,0 +1,6 @@ +# Makefile for Cirrus Logic-Crystal CS4281 +# + +obj-$(CONFIG_SOUND_CS4281) += cs4281.o + +cs4281-objs += cs4281m.o diff --git a/sound/oss/cs4281/cs4281_hwdefs.h b/sound/oss/cs4281/cs4281_hwdefs.h new file mode 100644 index 000000000000..701d595e33f5 --- /dev/null +++ b/sound/oss/cs4281/cs4281_hwdefs.h @@ -0,0 +1,1234 @@ +//**************************************************************************** +// +// HWDEFS.H - Definitions of the registers and data structures used by the +// CS4281 +// +// Copyright (c) 1999,2000,2001 Crystal Semiconductor Corp. +// +//**************************************************************************** + +#ifndef _H_HWDEFS +#define _H_HWDEFS + +//**************************************************************************** +// +// The following define the offsets of the registers located in the PCI +// configuration space of the CS4281 part. +// +//**************************************************************************** +#define PCICONFIG_DEVID_VENID 0x00000000L +#define PCICONFIG_STATUS_COMMAND 0x00000004L +#define PCICONFIG_CLASS_REVISION 0x00000008L +#define PCICONFIG_LATENCY_TIMER 0x0000000CL +#define PCICONFIG_BA0 0x00000010L +#define PCICONFIG_BA1 0x00000014L +#define PCICONFIG_SUBSYSID_SUBSYSVENID 0x0000002CL +#define PCICONFIG_INTERRUPT 0x0000003CL + +//**************************************************************************** +// +// The following define the offsets of the registers accessed via base address +// register zero on the CS4281 part. +// +//**************************************************************************** +#define BA0_HISR 0x00000000L +#define BA0_HICR 0x00000008L +#define BA0_HIMR 0x0000000CL +#define BA0_IIER 0x00000010L +#define BA0_HDSR0 0x000000F0L +#define BA0_HDSR1 0x000000F4L +#define BA0_HDSR2 0x000000F8L +#define BA0_HDSR3 0x000000FCL +#define BA0_DCA0 0x00000110L +#define BA0_DCC0 0x00000114L +#define BA0_DBA0 0x00000118L +#define BA0_DBC0 0x0000011CL +#define BA0_DCA1 0x00000120L +#define BA0_DCC1 0x00000124L +#define BA0_DBA1 0x00000128L +#define BA0_DBC1 0x0000012CL +#define BA0_DCA2 0x00000130L +#define BA0_DCC2 0x00000134L +#define BA0_DBA2 0x00000138L +#define BA0_DBC2 0x0000013CL +#define BA0_DCA3 0x00000140L +#define BA0_DCC3 0x00000144L +#define BA0_DBA3 0x00000148L +#define BA0_DBC3 0x0000014CL +#define BA0_DMR0 0x00000150L +#define BA0_DCR0 0x00000154L +#define BA0_DMR1 0x00000158L +#define BA0_DCR1 0x0000015CL +#define BA0_DMR2 0x00000160L +#define BA0_DCR2 0x00000164L +#define BA0_DMR3 0x00000168L +#define BA0_DCR3 0x0000016CL +#define BA0_DLMR 0x00000170L +#define BA0_DLSR 0x00000174L +#define BA0_FCR0 0x00000180L +#define BA0_FCR1 0x00000184L +#define BA0_FCR2 0x00000188L +#define BA0_FCR3 0x0000018CL +#define BA0_FPDR0 0x00000190L +#define BA0_FPDR1 0x00000194L +#define BA0_FPDR2 0x00000198L +#define BA0_FPDR3 0x0000019CL +#define BA0_FCHS 0x0000020CL +#define BA0_FSIC0 0x00000210L +#define BA0_FSIC1 0x00000214L +#define BA0_FSIC2 0x00000218L +#define BA0_FSIC3 0x0000021CL +#define BA0_PCICFG00 0x00000300L +#define BA0_PCICFG04 0x00000304L +#define BA0_PCICFG08 0x00000308L +#define BA0_PCICFG0C 0x0000030CL +#define BA0_PCICFG10 0x00000310L +#define BA0_PCICFG14 0x00000314L +#define BA0_PCICFG18 0x00000318L +#define BA0_PCICFG1C 0x0000031CL +#define BA0_PCICFG20 0x00000320L +#define BA0_PCICFG24 0x00000324L +#define BA0_PCICFG28 0x00000328L +#define BA0_PCICFG2C 0x0000032CL +#define BA0_PCICFG30 0x00000330L +#define BA0_PCICFG34 0x00000334L +#define BA0_PCICFG38 0x00000338L +#define BA0_PCICFG3C 0x0000033CL +#define BA0_PCICFG40 0x00000340L +#define BA0_PMCS 0x00000344L +#define BA0_CWPR 0x000003E0L +#define BA0_EPPMC 0x000003E4L +#define BA0_GPIOR 0x000003E8L +#define BA0_SPMC 0x000003ECL +#define BA0_CFLR 0x000003F0L +#define BA0_IISR 0x000003F4L +#define BA0_TMS 0x000003F8L +#define BA0_SSVID 0x000003FCL +#define BA0_CLKCR1 0x00000400L +#define BA0_FRR 0x00000410L +#define BA0_SLT12O 0x0000041CL +#define BA0_SERMC 0x00000420L +#define BA0_SERC1 0x00000428L +#define BA0_SERC2 0x0000042CL +#define BA0_SLT12M 0x0000045CL +#define BA0_ACCTL 0x00000460L +#define BA0_ACSTS 0x00000464L +#define BA0_ACOSV 0x00000468L +#define BA0_ACCAD 0x0000046CL +#define BA0_ACCDA 0x00000470L +#define BA0_ACISV 0x00000474L +#define BA0_ACSAD 0x00000478L +#define BA0_ACSDA 0x0000047CL +#define BA0_JSPT 0x00000480L +#define BA0_JSCTL 0x00000484L +#define BA0_MIDCR 0x00000490L +#define BA0_MIDCMD 0x00000494L +#define BA0_MIDSR 0x00000494L +#define BA0_MIDWP 0x00000498L +#define BA0_MIDRP 0x0000049CL +#define BA0_AODSD1 0x000004A8L +#define BA0_AODSD2 0x000004ACL +#define BA0_CFGI 0x000004B0L +#define BA0_SLT12M2 0x000004DCL +#define BA0_ACSTS2 0x000004E4L +#define BA0_ACISV2 0x000004F4L +#define BA0_ACSAD2 0x000004F8L +#define BA0_ACSDA2 0x000004FCL +#define BA0_IOTGP 0x00000500L +#define BA0_IOTSB 0x00000504L +#define BA0_IOTFM 0x00000508L +#define BA0_IOTDMA 0x0000050CL +#define BA0_IOTAC0 0x00000500L +#define BA0_IOTAC1 0x00000504L +#define BA0_IOTAC2 0x00000508L +#define BA0_IOTAC3 0x0000050CL +#define BA0_IOTPCP 0x0000052CL +#define BA0_IOTCC 0x00000530L +#define BA0_IOTCR 0x0000058CL +#define BA0_PCPRR 0x00000600L +#define BA0_PCPGR 0x00000604L +#define BA0_PCPCR 0x00000608L +#define BA0_PCPCIEN 0x00000608L +#define BA0_SBMAR 0x00000700L +#define BA0_SBMDR 0x00000704L +#define BA0_SBRR 0x00000708L +#define BA0_SBRDP 0x0000070CL +#define BA0_SBWDP 0x00000710L +#define BA0_SBWBS 0x00000710L +#define BA0_SBRBS 0x00000714L +#define BA0_FMSR 0x00000730L +#define BA0_B0AP 0x00000730L +#define BA0_FMDP 0x00000734L +#define BA0_B1AP 0x00000738L +#define BA0_B1DP 0x0000073CL +#define BA0_SSPM 0x00000740L +#define BA0_DACSR 0x00000744L +#define BA0_ADCSR 0x00000748L +#define BA0_SSCR 0x0000074CL +#define BA0_FMLVC 0x00000754L +#define BA0_FMRVC 0x00000758L +#define BA0_SRCSA 0x0000075CL +#define BA0_PPLVC 0x00000760L +#define BA0_PPRVC 0x00000764L +#define BA0_PASR 0x00000768L +#define BA0_CASR 0x0000076CL + +//**************************************************************************** +// +// The following define the offsets of the AC97 shadow registers, which appear +// as a virtual extension to the base address register zero memory range. +// +//**************************************************************************** +#define AC97_REG_OFFSET_MASK 0x0000007EL +#define AC97_CODEC_NUMBER_MASK 0x00003000L + +#define BA0_AC97_RESET 0x00001000L +#define BA0_AC97_MASTER_VOLUME 0x00001002L +#define BA0_AC97_HEADPHONE_VOLUME 0x00001004L +#define BA0_AC97_MASTER_VOLUME_MONO 0x00001006L +#define BA0_AC97_MASTER_TONE 0x00001008L +#define BA0_AC97_PC_BEEP_VOLUME 0x0000100AL +#define BA0_AC97_PHONE_VOLUME 0x0000100CL +#define BA0_AC97_MIC_VOLUME 0x0000100EL +#define BA0_AC97_LINE_IN_VOLUME 0x00001010L +#define BA0_AC97_CD_VOLUME 0x00001012L +#define BA0_AC97_VIDEO_VOLUME 0x00001014L +#define BA0_AC97_AUX_VOLUME 0x00001016L +#define BA0_AC97_PCM_OUT_VOLUME 0x00001018L +#define BA0_AC97_RECORD_SELECT 0x0000101AL +#define BA0_AC97_RECORD_GAIN 0x0000101CL +#define BA0_AC97_RECORD_GAIN_MIC 0x0000101EL +#define BA0_AC97_GENERAL_PURPOSE 0x00001020L +#define BA0_AC97_3D_CONTROL 0x00001022L +#define BA0_AC97_MODEM_RATE 0x00001024L +#define BA0_AC97_POWERDOWN 0x00001026L +#define BA0_AC97_EXT_AUDIO_ID 0x00001028L +#define BA0_AC97_EXT_AUDIO_POWER 0x0000102AL +#define BA0_AC97_PCM_FRONT_DAC_RATE 0x0000102CL +#define BA0_AC97_PCM_SURR_DAC_RATE 0x0000102EL +#define BA0_AC97_PCM_LFE_DAC_RATE 0x00001030L +#define BA0_AC97_PCM_LR_ADC_RATE 0x00001032L +#define BA0_AC97_MIC_ADC_RATE 0x00001034L +#define BA0_AC97_6CH_VOL_C_LFE 0x00001036L +#define BA0_AC97_6CH_VOL_SURROUND 0x00001038L +#define BA0_AC97_RESERVED_3A 0x0000103AL +#define BA0_AC97_EXT_MODEM_ID 0x0000103CL +#define BA0_AC97_EXT_MODEM_POWER 0x0000103EL +#define BA0_AC97_LINE1_CODEC_RATE 0x00001040L +#define BA0_AC97_LINE2_CODEC_RATE 0x00001042L +#define BA0_AC97_HANDSET_CODEC_RATE 0x00001044L +#define BA0_AC97_LINE1_CODEC_LEVEL 0x00001046L +#define BA0_AC97_LINE2_CODEC_LEVEL 0x00001048L +#define BA0_AC97_HANDSET_CODEC_LEVEL 0x0000104AL +#define BA0_AC97_GPIO_PIN_CONFIG 0x0000104CL +#define BA0_AC97_GPIO_PIN_TYPE 0x0000104EL +#define BA0_AC97_GPIO_PIN_STICKY 0x00001050L +#define BA0_AC97_GPIO_PIN_WAKEUP 0x00001052L +#define BA0_AC97_GPIO_PIN_STATUS 0x00001054L +#define BA0_AC97_MISC_MODEM_AFE_STAT 0x00001056L +#define BA0_AC97_RESERVED_58 0x00001058L +#define BA0_AC97_CRYSTAL_REV_N_FAB_ID 0x0000105AL +#define BA0_AC97_TEST_AND_MISC_CTRL 0x0000105CL +#define BA0_AC97_AC_MODE 0x0000105EL +#define BA0_AC97_MISC_CRYSTAL_CONTROL 0x00001060L +#define BA0_AC97_LINE1_HYPRID_CTRL 0x00001062L +#define BA0_AC97_VENDOR_RESERVED_64 0x00001064L +#define BA0_AC97_VENDOR_RESERVED_66 0x00001066L +#define BA0_AC97_SPDIF_CONTROL 0x00001068L +#define BA0_AC97_VENDOR_RESERVED_6A 0x0000106AL +#define BA0_AC97_VENDOR_RESERVED_6C 0x0000106CL +#define BA0_AC97_VENDOR_RESERVED_6E 0x0000106EL +#define BA0_AC97_VENDOR_RESERVED_70 0x00001070L +#define BA0_AC97_VENDOR_RESERVED_72 0x00001072L +#define BA0_AC97_VENDOR_RESERVED_74 0x00001074L +#define BA0_AC97_CAL_ADDRESS 0x00001076L +#define BA0_AC97_CAL_DATA 0x00001078L +#define BA0_AC97_VENDOR_RESERVED_7A 0x0000107AL +#define BA0_AC97_VENDOR_ID1 0x0000107CL +#define BA0_AC97_VENDOR_ID2 0x0000107EL + +//**************************************************************************** +// +// The following define the offsets of the registers and memories accessed via +// base address register one on the CS4281 part. +// +//**************************************************************************** + +//**************************************************************************** +// +// The following defines are for the flags in the PCI device ID/vendor ID +// register. +// +//**************************************************************************** +#define PDV_VENID_MASK 0x0000FFFFL +#define PDV_DEVID_MASK 0xFFFF0000L +#define PDV_VENID_SHIFT 0L +#define PDV_DEVID_SHIFT 16L +#define VENID_CIRRUS_LOGIC 0x1013L +#define DEVID_CS4281 0x6005L + +//**************************************************************************** +// +// The following defines are for the flags in the PCI status and command +// register. +// +//**************************************************************************** +#define PSC_IO_SPACE_ENABLE 0x00000001L +#define PSC_MEMORY_SPACE_ENABLE 0x00000002L +#define PSC_BUS_MASTER_ENABLE 0x00000004L +#define PSC_SPECIAL_CYCLES 0x00000008L +#define PSC_MWI_ENABLE 0x00000010L +#define PSC_VGA_PALETTE_SNOOP 0x00000020L +#define PSC_PARITY_RESPONSE 0x00000040L +#define PSC_WAIT_CONTROL 0x00000080L +#define PSC_SERR_ENABLE 0x00000100L +#define PSC_FAST_B2B_ENABLE 0x00000200L +#define PSC_UDF_MASK 0x007F0000L +#define PSC_FAST_B2B_CAPABLE 0x00800000L +#define PSC_PARITY_ERROR_DETECTED 0x01000000L +#define PSC_DEVSEL_TIMING_MASK 0x06000000L +#define PSC_TARGET_ABORT_SIGNALLED 0x08000000L +#define PSC_RECEIVED_TARGET_ABORT 0x10000000L +#define PSC_RECEIVED_MASTER_ABORT 0x20000000L +#define PSC_SIGNALLED_SERR 0x40000000L +#define PSC_DETECTED_PARITY_ERROR 0x80000000L +#define PSC_UDF_SHIFT 16L +#define PSC_DEVSEL_TIMING_SHIFT 25L + +//**************************************************************************** +// +// The following defines are for the flags in the PCI class/revision ID +// register. +// +//**************************************************************************** +#define PCR_REVID_MASK 0x000000FFL +#define PCR_INTERFACE_MASK 0x0000FF00L +#define PCR_SUBCLASS_MASK 0x00FF0000L +#define PCR_CLASS_MASK 0xFF000000L +#define PCR_REVID_SHIFT 0L +#define PCR_INTERFACE_SHIFT 8L +#define PCR_SUBCLASS_SHIFT 16L +#define PCR_CLASS_SHIFT 24L + +//**************************************************************************** +// +// The following defines are for the flags in the PCI latency timer register. +// +//**************************************************************************** +#define PLT_CACHE_LINE_SIZE_MASK 0x000000FFL +#define PLT_LATENCY_TIMER_MASK 0x0000FF00L +#define PLT_HEADER_TYPE_MASK 0x00FF0000L +#define PLT_BIST_MASK 0xFF000000L +#define PLT_CACHE_LINE_SIZE_SHIFT 0L +#define PLT_LATENCY_TIMER_SHIFT 8L +#define PLT_HEADER_TYPE_SHIFT 16L +#define PLT_BIST_SHIFT 24L + +//**************************************************************************** +// +// The following defines are for the flags in the PCI base address registers. +// +//**************************************************************************** +#define PBAR_MEMORY_SPACE_INDICATOR 0x00000001L +#define PBAR_LOCATION_TYPE_MASK 0x00000006L +#define PBAR_NOT_PREFETCHABLE 0x00000008L +#define PBAR_ADDRESS_MASK 0xFFFFFFF0L +#define PBAR_LOCATION_TYPE_SHIFT 1L + +//**************************************************************************** +// +// The following defines are for the flags in the PCI subsystem ID/subsystem +// vendor ID register. +// +//**************************************************************************** +#define PSS_SUBSYSTEM_VENDOR_ID_MASK 0x0000FFFFL +#define PSS_SUBSYSTEM_ID_MASK 0xFFFF0000L +#define PSS_SUBSYSTEM_VENDOR_ID_SHIFT 0L +#define PSS_SUBSYSTEM_ID_SHIFT 16L + +//**************************************************************************** +// +// The following defines are for the flags in the PCI interrupt register. +// +//**************************************************************************** +#define PI_LINE_MASK 0x000000FFL +#define PI_PIN_MASK 0x0000FF00L +#define PI_MIN_GRANT_MASK 0x00FF0000L +#define PI_MAX_LATENCY_MASK 0xFF000000L +#define PI_LINE_SHIFT 0L +#define PI_PIN_SHIFT 8L +#define PI_MIN_GRANT_SHIFT 16L +#define PI_MAX_LATENCY_SHIFT 24L + +//**************************************************************************** +// +// The following defines are for the flags in the host interrupt status +// register. +// +//**************************************************************************** +#define HISR_HVOLMASK 0x00000003L +#define HISR_VDNI 0x00000001L +#define HISR_VUPI 0x00000002L +#define HISR_GP1I 0x00000004L +#define HISR_GP3I 0x00000008L +#define HISR_GPSI 0x00000010L +#define HISR_GPPI 0x00000020L +#define HISR_DMAI 0x00040000L +#define HISR_FIFOI 0x00100000L +#define HISR_HVOL 0x00200000L +#define HISR_MIDI 0x00400000L +#define HISR_SBINT 0x00800000L +#define HISR_INTENA 0x80000000L +#define HISR_DMA_MASK 0x00000F00L +#define HISR_FIFO_MASK 0x0000F000L +#define HISR_DMA_SHIFT 8L +#define HISR_FIFO_SHIFT 12L +#define HISR_FIFO0 0x00001000L +#define HISR_FIFO1 0x00002000L +#define HISR_FIFO2 0x00004000L +#define HISR_FIFO3 0x00008000L +#define HISR_DMA0 0x00000100L +#define HISR_DMA1 0x00000200L +#define HISR_DMA2 0x00000400L +#define HISR_DMA3 0x00000800L +#define HISR_RESERVED 0x40000000L + +//**************************************************************************** +// +// The following defines are for the flags in the host interrupt control +// register. +// +//**************************************************************************** +#define HICR_IEV 0x00000001L +#define HICR_CHGM 0x00000002L + +//**************************************************************************** +// +// The following defines are for the flags in the DMA Mode Register n +// (DMRn) +// +//**************************************************************************** +#define DMRn_TR_MASK 0x0000000CL +#define DMRn_TR_SHIFT 2L +#define DMRn_AUTO 0x00000010L +#define DMRn_TR_READ 0x00000008L +#define DMRn_TR_WRITE 0x00000004L +#define DMRn_TYPE_MASK 0x000000C0L +#define DMRn_TYPE_SHIFT 6L +#define DMRn_SIZE8 0x00010000L +#define DMRn_MONO 0x00020000L +#define DMRn_BEND 0x00040000L +#define DMRn_USIGN 0x00080000L +#define DMRn_SIZE20 0x00100000L +#define DMRn_SWAPC 0x00400000L +#define DMRn_CBC 0x01000000L +#define DMRn_TBC 0x02000000L +#define DMRn_POLL 0x10000000L +#define DMRn_DMA 0x20000000L +#define DMRn_FSEL_MASK 0xC0000000L +#define DMRn_FSEL_SHIFT 30L +#define DMRn_FSEL0 0x00000000L +#define DMRn_FSEL1 0x40000000L +#define DMRn_FSEL2 0x80000000L +#define DMRn_FSEL3 0xC0000000L + +//**************************************************************************** +// +// The following defines are for the flags in the DMA Command Register n +// (DCRn) +// +//**************************************************************************** +#define DCRn_HTCIE 0x00020000L +#define DCRn_TCIE 0x00010000L +#define DCRn_MSK 0x00000001L + +//**************************************************************************** +// +// The following defines are for the flags in the FIFO Control +// register n.(FCRn) +// +//**************************************************************************** +#define FCRn_OF_MASK 0x0000007FL +#define FCRn_OF_SHIFT 0L +#define FCRn_SZ_MASK 0x00007F00L +#define FCRn_SZ_SHIFT 8L +#define FCRn_LS_MASK 0x001F0000L +#define FCRn_LS_SHIFT 16L +#define FCRn_RS_MASK 0x1F000000L +#define FCRn_RS_SHIFT 24L +#define FCRn_FEN 0x80000000L +#define FCRn_PSH 0x20000000L +#define FCRn_DACZ 0x40000000L + +//**************************************************************************** +// +// The following defines are for the flags in the serial port Power Management +// control register.(SPMC) +// +//**************************************************************************** +#define SPMC_RSTN 0x00000001L +#define SPMC_ASYN 0x00000002L +#define SPMC_WUP1 0x00000004L +#define SPMC_WUP2 0x00000008L +#define SPMC_ASDI2E 0x00000100L +#define SPMC_ESSPD 0x00000200L +#define SPMC_GISPEN 0x00004000L +#define SPMC_GIPPEN 0x00008000L + +//**************************************************************************** +// +// The following defines are for the flags in the Configuration Load register. +// (CFLR) +// +//**************************************************************************** +#define CFLR_CLOCK_SOURCE_MASK 0x00000003L +#define CFLR_CLOCK_SOURCE_AC97 0x00000001L + +#define CFLR_CB0_MASK 0x000000FFL +#define CFLR_CB1_MASK 0x0000FF00L +#define CFLR_CB2_MASK 0x00FF0000L +#define CFLR_CB3_MASK 0xFF000000L +#define CFLR_CB0_SHIFT 0L +#define CFLR_CB1_SHIFT 8L +#define CFLR_CB2_SHIFT 16L +#define CFLR_CB3_SHIFT 24L + +#define IOTCR_DMA0 0x00000000L +#define IOTCR_DMA1 0x00000400L +#define IOTCR_DMA2 0x00000800L +#define IOTCR_DMA3 0x00000C00L +#define IOTCR_CCLS 0x00000100L +#define IOTCR_PCPCI 0x00000200L +#define IOTCR_DDMA 0x00000300L + +#define SBWBS_WBB 0x00000080L + +//**************************************************************************** +// +// The following defines are for the flags in the SRC Slot Assignment Register +// (SRCSA) +// +//**************************************************************************** +#define SRCSA_PLSS_MASK 0x0000001FL +#define SRCSA_PLSS_SHIFT 0L +#define SRCSA_PRSS_MASK 0x00001F00L +#define SRCSA_PRSS_SHIFT 8L +#define SRCSA_CLSS_MASK 0x001F0000L +#define SRCSA_CLSS_SHIFT 16L +#define SRCSA_CRSS_MASK 0x1F000000L +#define SRCSA_CRSS_SHIFT 24L + +//**************************************************************************** +// +// The following defines are for the flags in the Sound System Power Management +// register.(SSPM) +// +//**************************************************************************** +#define SSPM_FPDN 0x00000080L +#define SSPM_MIXEN 0x00000040L +#define SSPM_CSRCEN 0x00000020L +#define SSPM_PSRCEN 0x00000010L +#define SSPM_JSEN 0x00000008L +#define SSPM_ACLEN 0x00000004L +#define SSPM_FMEN 0x00000002L + +//**************************************************************************** +// +// The following defines are for the flags in the Sound System Control +// Register. (SSCR) +// +//**************************************************************************** +#define SSCR_SB 0x00000004L +#define SSCR_HVC 0x00000008L +#define SSCR_LPFIFO 0x00000040L +#define SSCR_LPSRC 0x00000080L +#define SSCR_XLPSRC 0x00000100L +#define SSCR_MVMD 0x00010000L +#define SSCR_MVAD 0x00020000L +#define SSCR_MVLD 0x00040000L +#define SSCR_MVCS 0x00080000L + +//**************************************************************************** +// +// The following defines are for the flags in the Clock Control Register 1. +// (CLKCR1) +// +//**************************************************************************** +#define CLKCR1_DLLSS_MASK 0x0000000CL +#define CLKCR1_DLLSS_SHIFT 2L +#define CLKCR1_DLLP 0x00000010L +#define CLKCR1_SWCE 0x00000020L +#define CLKCR1_DLLOS 0x00000040L +#define CLKCR1_CKRA 0x00010000L +#define CLKCR1_CKRN 0x00020000L +#define CLKCR1_DLLRDY 0x01000000L +#define CLKCR1_CLKON 0x02000000L + +//**************************************************************************** +// +// The following defines are for the flags in the Sound Blaster Read Buffer +// Status.(SBRBS) +// +//**************************************************************************** +#define SBRBS_RD_MASK 0x0000007FL +#define SBRBS_RD_SHIFT 0L +#define SBRBS_RBF 0x00000080L + +//**************************************************************************** +// +// The following defines are for the flags in the serial port master control +// register.(SERMC) +// +//**************************************************************************** +#define SERMC_MSPE 0x00000001L +#define SERMC_PTC_MASK 0x0000000EL +#define SERMC_PTC_SHIFT 1L +#define SERMC_PTC_AC97 0x00000002L +#define SERMC_PLB 0x00000010L +#define SERMC_PXLB 0x00000020L +#define SERMC_LOFV 0x00080000L +#define SERMC_SLB 0x00100000L +#define SERMC_SXLB 0x00200000L +#define SERMC_ODSEN1 0x01000000L +#define SERMC_ODSEN2 0x02000000L + +//**************************************************************************** +// +// The following defines are for the flags in the General Purpose I/O Register. +// (GPIOR) +// +//**************************************************************************** +#define GPIOR_VDNS 0x00000001L +#define GPIOR_VUPS 0x00000002L +#define GPIOR_GP1S 0x00000004L +#define GPIOR_GP3S 0x00000008L +#define GPIOR_GPSS 0x00000010L +#define GPIOR_GPPS 0x00000020L +#define GPIOR_GP1D 0x00000400L +#define GPIOR_GP3D 0x00000800L +#define GPIOR_VDNLT 0x00010000L +#define GPIOR_VDNPO 0x00020000L +#define GPIOR_VDNST 0x00040000L +#define GPIOR_VDNW 0x00080000L +#define GPIOR_VUPLT 0x00100000L +#define GPIOR_VUPPO 0x00200000L +#define GPIOR_VUPST 0x00400000L +#define GPIOR_VUPW 0x00800000L +#define GPIOR_GP1OE 0x01000000L +#define GPIOR_GP1PT 0x02000000L +#define GPIOR_GP1ST 0x04000000L +#define GPIOR_GP1W 0x08000000L +#define GPIOR_GP3OE 0x10000000L +#define GPIOR_GP3PT 0x20000000L +#define GPIOR_GP3ST 0x40000000L +#define GPIOR_GP3W 0x80000000L + +//**************************************************************************** +// +// The following defines are for the flags in the clock control register 1. +// +//**************************************************************************** +#define CLKCR1_PLLSS_MASK 0x0000000CL +#define CLKCR1_PLLSS_SERIAL 0x00000000L +#define CLKCR1_PLLSS_CRYSTAL 0x00000004L +#define CLKCR1_PLLSS_PCI 0x00000008L +#define CLKCR1_PLLSS_RESERVED 0x0000000CL +#define CLKCR1_PLLP 0x00000010L +#define CLKCR1_SWCE 0x00000020L +#define CLKCR1_PLLOS 0x00000040L + +//**************************************************************************** +// +// The following defines are for the flags in the feature reporting register. +// +//**************************************************************************** +#define FRR_FAB_MASK 0x00000003L +#define FRR_MASK_MASK 0x0000001CL +#define FRR_ID_MASK 0x00003000L +#define FRR_FAB_SHIFT 0L +#define FRR_MASK_SHIFT 2L +#define FRR_ID_SHIFT 12L + +//**************************************************************************** +// +// The following defines are for the flags in the serial port 1 configuration +// register. +// +//**************************************************************************** +#define SERC1_VALUE 0x00000003L +#define SERC1_SO1EN 0x00000001L +#define SERC1_SO1F_MASK 0x0000000EL +#define SERC1_SO1F_CS423X 0x00000000L +#define SERC1_SO1F_AC97 0x00000002L +#define SERC1_SO1F_DAC 0x00000004L +#define SERC1_SO1F_SPDIF 0x00000006L + +//**************************************************************************** +// +// The following defines are for the flags in the serial port 2 configuration +// register. +// +//**************************************************************************** +#define SERC2_VALUE 0x00000003L +#define SERC2_SI1EN 0x00000001L +#define SERC2_SI1F_MASK 0x0000000EL +#define SERC2_SI1F_CS423X 0x00000000L +#define SERC2_SI1F_AC97 0x00000002L +#define SERC2_SI1F_ADC 0x00000004L +#define SERC2_SI1F_SPDIF 0x00000006L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 control register. +// +//**************************************************************************** +#define ACCTL_ESYN 0x00000002L +#define ACCTL_VFRM 0x00000004L +#define ACCTL_DCV 0x00000008L +#define ACCTL_CRW 0x00000010L +#define ACCTL_TC 0x00000040L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 status register. +// +//**************************************************************************** +#define ACSTS_CRDY 0x00000001L +#define ACSTS_VSTS 0x00000002L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 output slot valid +// register. +// +//**************************************************************************** +#define ACOSV_SLV3 0x00000001L +#define ACOSV_SLV4 0x00000002L +#define ACOSV_SLV5 0x00000004L +#define ACOSV_SLV6 0x00000008L +#define ACOSV_SLV7 0x00000010L +#define ACOSV_SLV8 0x00000020L +#define ACOSV_SLV9 0x00000040L +#define ACOSV_SLV10 0x00000080L +#define ACOSV_SLV11 0x00000100L +#define ACOSV_SLV12 0x00000200L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 command address +// register. +// +//**************************************************************************** +#define ACCAD_CI_MASK 0x0000007FL +#define ACCAD_CI_SHIFT 0L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 command data register. +// +//**************************************************************************** +#define ACCDA_CD_MASK 0x0000FFFFL +#define ACCDA_CD_SHIFT 0L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 input slot valid +// register. +// +//**************************************************************************** +#define ACISV_ISV3 0x00000001L +#define ACISV_ISV4 0x00000002L +#define ACISV_ISV5 0x00000004L +#define ACISV_ISV6 0x00000008L +#define ACISV_ISV7 0x00000010L +#define ACISV_ISV8 0x00000020L +#define ACISV_ISV9 0x00000040L +#define ACISV_ISV10 0x00000080L +#define ACISV_ISV11 0x00000100L +#define ACISV_ISV12 0x00000200L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 status address +// register. +// +//**************************************************************************** +#define ACSAD_SI_MASK 0x0000007FL +#define ACSAD_SI_SHIFT 0L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 status data register. +// +//**************************************************************************** +#define ACSDA_SD_MASK 0x0000FFFFL +#define ACSDA_SD_SHIFT 0L + +//**************************************************************************** +// +// The following defines are for the flags in the I/O trap address and control +// registers (all 12). +// +//**************************************************************************** +#define IOTAC_SA_MASK 0x0000FFFFL +#define IOTAC_MSK_MASK 0x000F0000L +#define IOTAC_IODC_MASK 0x06000000L +#define IOTAC_IODC_16_BIT 0x00000000L +#define IOTAC_IODC_10_BIT 0x02000000L +#define IOTAC_IODC_12_BIT 0x04000000L +#define IOTAC_WSPI 0x08000000L +#define IOTAC_RSPI 0x10000000L +#define IOTAC_WSE 0x20000000L +#define IOTAC_WE 0x40000000L +#define IOTAC_RE 0x80000000L +#define IOTAC_SA_SHIFT 0L +#define IOTAC_MSK_SHIFT 16L + +//**************************************************************************** +// +// The following defines are for the flags in the PC/PCI master enable +// register. +// +//**************************************************************************** +#define PCPCIEN_EN 0x00000001L + +//**************************************************************************** +// +// The following defines are for the flags in the joystick poll/trigger +// register. +// +//**************************************************************************** +#define JSPT_CAX 0x00000001L +#define JSPT_CAY 0x00000002L +#define JSPT_CBX 0x00000004L +#define JSPT_CBY 0x00000008L +#define JSPT_BA1 0x00000010L +#define JSPT_BA2 0x00000020L +#define JSPT_BB1 0x00000040L +#define JSPT_BB2 0x00000080L + +//**************************************************************************** +// +// The following defines are for the flags in the joystick control register. +// The TBF bit has been moved from MIDSR register to JSCTL register bit 8. +// +//**************************************************************************** +#define JSCTL_SP_MASK 0x00000003L +#define JSCTL_SP_SLOW 0x00000000L +#define JSCTL_SP_MEDIUM_SLOW 0x00000001L +#define JSCTL_SP_MEDIUM_FAST 0x00000002L +#define JSCTL_SP_FAST 0x00000003L +#define JSCTL_ARE 0x00000004L +#define JSCTL_TBF 0x00000100L + + +//**************************************************************************** +// +// The following defines are for the flags in the MIDI control register. +// +//**************************************************************************** +#define MIDCR_TXE 0x00000001L +#define MIDCR_RXE 0x00000002L +#define MIDCR_RIE 0x00000004L +#define MIDCR_TIE 0x00000008L +#define MIDCR_MLB 0x00000010L +#define MIDCR_MRST 0x00000020L + +//**************************************************************************** +// +// The following defines are for the flags in the MIDI status register. +// +//**************************************************************************** +#define MIDSR_RBE 0x00000080L +#define MIDSR_RDA 0x00008000L + +//**************************************************************************** +// +// The following defines are for the flags in the MIDI write port register. +// +//**************************************************************************** +#define MIDWP_MWD_MASK 0x000000FFL +#define MIDWP_MWD_SHIFT 0L + +//**************************************************************************** +// +// The following defines are for the flags in the MIDI read port register. +// +//**************************************************************************** +#define MIDRP_MRD_MASK 0x000000FFL +#define MIDRP_MRD_SHIFT 0L + +//**************************************************************************** +// +// The following defines are for the flags in the configuration interface +// register. +// +//**************************************************************************** +#define CFGI_CLK 0x00000001L +#define CFGI_DOUT 0x00000002L +#define CFGI_DIN_EEN 0x00000004L +#define CFGI_EELD 0x00000008L + +//**************************************************************************** +// +// The following defines are for the flags in the subsystem ID and vendor ID +// register. +// +//**************************************************************************** +#define SSVID_VID_MASK 0x0000FFFFL +#define SSVID_SID_MASK 0xFFFF0000L +#define SSVID_VID_SHIFT 0L +#define SSVID_SID_SHIFT 16L + +//**************************************************************************** +// +// The following defines are for the flags in the GPIO pin interface register. +// +//**************************************************************************** +#define GPIOR_VOLDN 0x00000001L +#define GPIOR_VOLUP 0x00000002L +#define GPIOR_SI2D 0x00000004L +#define GPIOR_SI2OE 0x00000008L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 status register 2. +// +//**************************************************************************** +#define ACSTS2_CRDY 0x00000001L +#define ACSTS2_VSTS 0x00000002L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 input slot valid +// register 2. +// +//**************************************************************************** +#define ACISV2_ISV3 0x00000001L +#define ACISV2_ISV4 0x00000002L +#define ACISV2_ISV5 0x00000004L +#define ACISV2_ISV6 0x00000008L +#define ACISV2_ISV7 0x00000010L +#define ACISV2_ISV8 0x00000020L +#define ACISV2_ISV9 0x00000040L +#define ACISV2_ISV10 0x00000080L +#define ACISV2_ISV11 0x00000100L +#define ACISV2_ISV12 0x00000200L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 status address +// register 2. +// +//**************************************************************************** +#define ACSAD2_SI_MASK 0x0000007FL +#define ACSAD2_SI_SHIFT 0L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 status data register 2. +// +//**************************************************************************** +#define ACSDA2_SD_MASK 0x0000FFFFL +#define ACSDA2_SD_SHIFT 0L + +//**************************************************************************** +// +// The following defines are for the flags in the I/O trap control register. +// +//**************************************************************************** +#define IOTCR_ITD 0x00000001L +#define IOTCR_HRV 0x00000002L +#define IOTCR_SRV 0x00000004L +#define IOTCR_DTI 0x00000008L +#define IOTCR_DFI 0x00000010L +#define IOTCR_DDP 0x00000020L +#define IOTCR_JTE 0x00000040L +#define IOTCR_PPE 0x00000080L + +//**************************************************************************** +// +// The following defines are for the flags in the I/O trap address and control +// registers for Hardware Master Volume. +// +//**************************************************************************** +#define IOTGP_SA_MASK 0x0000FFFFL +#define IOTGP_MSK_MASK 0x000F0000L +#define IOTGP_IODC_MASK 0x06000000L +#define IOTGP_IODC_16_BIT 0x00000000L +#define IOTGP_IODC_10_BIT 0x02000000L +#define IOTGP_IODC_12_BIT 0x04000000L +#define IOTGP_WSPI 0x08000000L +#define IOTGP_RSPI 0x10000000L +#define IOTGP_WSE 0x20000000L +#define IOTGP_WE 0x40000000L +#define IOTGP_RE 0x80000000L +#define IOTGP_SA_SHIFT 0L +#define IOTGP_MSK_SHIFT 16L + +//**************************************************************************** +// +// The following defines are for the flags in the I/O trap address and control +// registers for Sound Blaster +// +//**************************************************************************** +#define IOTSB_SA_MASK 0x0000FFFFL +#define IOTSB_MSK_MASK 0x000F0000L +#define IOTSB_IODC_MASK 0x06000000L +#define IOTSB_IODC_16_BIT 0x00000000L +#define IOTSB_IODC_10_BIT 0x02000000L +#define IOTSB_IODC_12_BIT 0x04000000L +#define IOTSB_WSPI 0x08000000L +#define IOTSB_RSPI 0x10000000L +#define IOTSB_WSE 0x20000000L +#define IOTSB_WE 0x40000000L +#define IOTSB_RE 0x80000000L +#define IOTSB_SA_SHIFT 0L +#define IOTSB_MSK_SHIFT 16L + +//**************************************************************************** +// +// The following defines are for the flags in the I/O trap address and control +// registers for FM. +// +//**************************************************************************** +#define IOTFM_SA_MASK 0x0000FFFFL +#define IOTFM_MSK_MASK 0x000F0000L +#define IOTFM_IODC_MASK 0x06000000L +#define IOTFM_IODC_16_BIT 0x00000000L +#define IOTFM_IODC_10_BIT 0x02000000L +#define IOTFM_IODC_12_BIT 0x04000000L +#define IOTFM_WSPI 0x08000000L +#define IOTFM_RSPI 0x10000000L +#define IOTFM_WSE 0x20000000L +#define IOTFM_WE 0x40000000L +#define IOTFM_RE 0x80000000L +#define IOTFM_SA_SHIFT 0L +#define IOTFM_MSK_SHIFT 16L + +//**************************************************************************** +// +// The following defines are for the flags in the PC/PCI request register. +// +//**************************************************************************** +#define PCPRR_RDC_MASK 0x00000007L +#define PCPRR_REQ 0x00008000L +#define PCPRR_RDC_SHIFT 0L + +//**************************************************************************** +// +// The following defines are for the flags in the PC/PCI grant register. +// +//**************************************************************************** +#define PCPGR_GDC_MASK 0x00000007L +#define PCPGR_VL 0x00008000L +#define PCPGR_GDC_SHIFT 0L + +//**************************************************************************** +// +// The following defines are for the flags in the PC/PCI Control Register. +// +//**************************************************************************** +#define PCPCR_EN 0x00000001L + +//**************************************************************************** +// +// The following defines are for the flags in the debug index register. +// +//**************************************************************************** +#define DREG_REGID_MASK 0x0000007FL +#define DREG_DEBUG 0x00000080L +#define DREG_RGBK_MASK 0x00000700L +#define DREG_TRAP 0x00000800L +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_TRAPX 0x00001000L +#endif +#endif +#define DREG_REGID_SHIFT 0L +#define DREG_RGBK_SHIFT 8L +#define DREG_RGBK_REGID_MASK 0x0000077FL +#define DREG_REGID_R0 0x00000010L +#define DREG_REGID_R1 0x00000011L +#define DREG_REGID_R2 0x00000012L +#define DREG_REGID_R3 0x00000013L +#define DREG_REGID_R4 0x00000014L +#define DREG_REGID_R5 0x00000015L +#define DREG_REGID_R6 0x00000016L +#define DREG_REGID_R7 0x00000017L +#define DREG_REGID_R8 0x00000018L +#define DREG_REGID_R9 0x00000019L +#define DREG_REGID_RA 0x0000001AL +#define DREG_REGID_RB 0x0000001BL +#define DREG_REGID_RC 0x0000001CL +#define DREG_REGID_RD 0x0000001DL +#define DREG_REGID_RE 0x0000001EL +#define DREG_REGID_RF 0x0000001FL +#define DREG_REGID_RA_BUS_LOW 0x00000020L +#define DREG_REGID_RA_BUS_HIGH 0x00000038L +#define DREG_REGID_YBUS_LOW 0x00000050L +#define DREG_REGID_YBUS_HIGH 0x00000058L +#define DREG_REGID_TRAP_0 0x00000100L +#define DREG_REGID_TRAP_1 0x00000101L +#define DREG_REGID_TRAP_2 0x00000102L +#define DREG_REGID_TRAP_3 0x00000103L +#define DREG_REGID_TRAP_4 0x00000104L +#define DREG_REGID_TRAP_5 0x00000105L +#define DREG_REGID_TRAP_6 0x00000106L +#define DREG_REGID_TRAP_7 0x00000107L +#define DREG_REGID_INDIRECT_ADDRESS 0x0000010EL +#define DREG_REGID_TOP_OF_STACK 0x0000010FL +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_REGID_TRAP_8 0x00000110L +#define DREG_REGID_TRAP_9 0x00000111L +#define DREG_REGID_TRAP_10 0x00000112L +#define DREG_REGID_TRAP_11 0x00000113L +#define DREG_REGID_TRAP_12 0x00000114L +#define DREG_REGID_TRAP_13 0x00000115L +#define DREG_REGID_TRAP_14 0x00000116L +#define DREG_REGID_TRAP_15 0x00000117L +#define DREG_REGID_TRAP_16 0x00000118L +#define DREG_REGID_TRAP_17 0x00000119L +#define DREG_REGID_TRAP_18 0x0000011AL +#define DREG_REGID_TRAP_19 0x0000011BL +#define DREG_REGID_TRAP_20 0x0000011CL +#define DREG_REGID_TRAP_21 0x0000011DL +#define DREG_REGID_TRAP_22 0x0000011EL +#define DREG_REGID_TRAP_23 0x0000011FL +#endif +#endif +#define DREG_REGID_RSA0_LOW 0x00000200L +#define DREG_REGID_RSA0_HIGH 0x00000201L +#define DREG_REGID_RSA1_LOW 0x00000202L +#define DREG_REGID_RSA1_HIGH 0x00000203L +#define DREG_REGID_RSA2 0x00000204L +#define DREG_REGID_RSA3 0x00000205L +#define DREG_REGID_RSI0_LOW 0x00000206L +#define DREG_REGID_RSI0_HIGH 0x00000207L +#define DREG_REGID_RSI1 0x00000208L +#define DREG_REGID_RSI2 0x00000209L +#define DREG_REGID_SAGUSTATUS 0x0000020AL +#define DREG_REGID_RSCONFIG01_LOW 0x0000020BL +#define DREG_REGID_RSCONFIG01_HIGH 0x0000020CL +#define DREG_REGID_RSCONFIG23_LOW 0x0000020DL +#define DREG_REGID_RSCONFIG23_HIGH 0x0000020EL +#define DREG_REGID_RSDMA01E 0x0000020FL +#define DREG_REGID_RSDMA23E 0x00000210L +#define DREG_REGID_RSD0_LOW 0x00000211L +#define DREG_REGID_RSD0_HIGH 0x00000212L +#define DREG_REGID_RSD1_LOW 0x00000213L +#define DREG_REGID_RSD1_HIGH 0x00000214L +#define DREG_REGID_RSD2_LOW 0x00000215L +#define DREG_REGID_RSD2_HIGH 0x00000216L +#define DREG_REGID_RSD3_LOW 0x00000217L +#define DREG_REGID_RSD3_HIGH 0x00000218L +#define DREG_REGID_SRAR_HIGH 0x0000021AL +#define DREG_REGID_SRAR_LOW 0x0000021BL +#define DREG_REGID_DMA_STATE 0x0000021CL +#define DREG_REGID_CURRENT_DMA_STREAM 0x0000021DL +#define DREG_REGID_NEXT_DMA_STREAM 0x0000021EL +#define DREG_REGID_CPU_STATUS 0x00000300L +#define DREG_REGID_MAC_MODE 0x00000301L +#define DREG_REGID_STACK_AND_REPEAT 0x00000302L +#define DREG_REGID_INDEX0 0x00000304L +#define DREG_REGID_INDEX1 0x00000305L +#define DREG_REGID_DMA_STATE_0_3 0x00000400L +#define DREG_REGID_DMA_STATE_4_7 0x00000404L +#define DREG_REGID_DMA_STATE_8_11 0x00000408L +#define DREG_REGID_DMA_STATE_12_15 0x0000040CL +#define DREG_REGID_DMA_STATE_16_19 0x00000410L +#define DREG_REGID_DMA_STATE_20_23 0x00000414L +#define DREG_REGID_DMA_STATE_24_27 0x00000418L +#define DREG_REGID_DMA_STATE_28_31 0x0000041CL +#define DREG_REGID_DMA_STATE_32_35 0x00000420L +#define DREG_REGID_DMA_STATE_36_39 0x00000424L +#define DREG_REGID_DMA_STATE_40_43 0x00000428L +#define DREG_REGID_DMA_STATE_44_47 0x0000042CL +#define DREG_REGID_DMA_STATE_48_51 0x00000430L +#define DREG_REGID_DMA_STATE_52_55 0x00000434L +#define DREG_REGID_DMA_STATE_56_59 0x00000438L +#define DREG_REGID_DMA_STATE_60_63 0x0000043CL +#define DREG_REGID_DMA_STATE_64_67 0x00000440L +#define DREG_REGID_DMA_STATE_68_71 0x00000444L +#define DREG_REGID_DMA_STATE_72_75 0x00000448L +#define DREG_REGID_DMA_STATE_76_79 0x0000044CL +#define DREG_REGID_DMA_STATE_80_83 0x00000450L +#define DREG_REGID_DMA_STATE_84_87 0x00000454L +#define DREG_REGID_DMA_STATE_88_91 0x00000458L +#define DREG_REGID_DMA_STATE_92_95 0x0000045CL +#define DREG_REGID_TRAP_SELECT 0x00000500L +#define DREG_REGID_TRAP_WRITE_0 0x00000500L +#define DREG_REGID_TRAP_WRITE_1 0x00000501L +#define DREG_REGID_TRAP_WRITE_2 0x00000502L +#define DREG_REGID_TRAP_WRITE_3 0x00000503L +#define DREG_REGID_TRAP_WRITE_4 0x00000504L +#define DREG_REGID_TRAP_WRITE_5 0x00000505L +#define DREG_REGID_TRAP_WRITE_6 0x00000506L +#define DREG_REGID_TRAP_WRITE_7 0x00000507L +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_REGID_TRAP_WRITE_8 0x00000510L +#define DREG_REGID_TRAP_WRITE_9 0x00000511L +#define DREG_REGID_TRAP_WRITE_10 0x00000512L +#define DREG_REGID_TRAP_WRITE_11 0x00000513L +#define DREG_REGID_TRAP_WRITE_12 0x00000514L +#define DREG_REGID_TRAP_WRITE_13 0x00000515L +#define DREG_REGID_TRAP_WRITE_14 0x00000516L +#define DREG_REGID_TRAP_WRITE_15 0x00000517L +#define DREG_REGID_TRAP_WRITE_16 0x00000518L +#define DREG_REGID_TRAP_WRITE_17 0x00000519L +#define DREG_REGID_TRAP_WRITE_18 0x0000051AL +#define DREG_REGID_TRAP_WRITE_19 0x0000051BL +#define DREG_REGID_TRAP_WRITE_20 0x0000051CL +#define DREG_REGID_TRAP_WRITE_21 0x0000051DL +#define DREG_REGID_TRAP_WRITE_22 0x0000051EL +#define DREG_REGID_TRAP_WRITE_23 0x0000051FL +#endif +#endif +#define DREG_REGID_MAC0_ACC0_LOW 0x00000600L +#define DREG_REGID_MAC0_ACC1_LOW 0x00000601L +#define DREG_REGID_MAC0_ACC2_LOW 0x00000602L +#define DREG_REGID_MAC0_ACC3_LOW 0x00000603L +#define DREG_REGID_MAC1_ACC0_LOW 0x00000604L +#define DREG_REGID_MAC1_ACC1_LOW 0x00000605L +#define DREG_REGID_MAC1_ACC2_LOW 0x00000606L +#define DREG_REGID_MAC1_ACC3_LOW 0x00000607L +#define DREG_REGID_MAC0_ACC0_MID 0x00000608L +#define DREG_REGID_MAC0_ACC1_MID 0x00000609L +#define DREG_REGID_MAC0_ACC2_MID 0x0000060AL +#define DREG_REGID_MAC0_ACC3_MID 0x0000060BL +#define DREG_REGID_MAC1_ACC0_MID 0x0000060CL +#define DREG_REGID_MAC1_ACC1_MID 0x0000060DL +#define DREG_REGID_MAC1_ACC2_MID 0x0000060EL +#define DREG_REGID_MAC1_ACC3_MID 0x0000060FL +#define DREG_REGID_MAC0_ACC0_HIGH 0x00000610L +#define DREG_REGID_MAC0_ACC1_HIGH 0x00000611L +#define DREG_REGID_MAC0_ACC2_HIGH 0x00000612L +#define DREG_REGID_MAC0_ACC3_HIGH 0x00000613L +#define DREG_REGID_MAC1_ACC0_HIGH 0x00000614L +#define DREG_REGID_MAC1_ACC1_HIGH 0x00000615L +#define DREG_REGID_MAC1_ACC2_HIGH 0x00000616L +#define DREG_REGID_MAC1_ACC3_HIGH 0x00000617L +#define DREG_REGID_RSHOUT_LOW 0x00000620L +#define DREG_REGID_RSHOUT_MID 0x00000628L +#define DREG_REGID_RSHOUT_HIGH 0x00000630L + +//**************************************************************************** +// +// The following defines are for the flags in the AC97 S/PDIF Control register. +// +//**************************************************************************** +#define SPDIF_CONTROL_SPDIF_EN 0x00008000L +#define SPDIF_CONTROL_VAL 0x00004000L +#define SPDIF_CONTROL_COPY 0x00000004L +#define SPDIF_CONTROL_CC0 0x00000010L +#define SPDIF_CONTROL_CC1 0x00000020L +#define SPDIF_CONTROL_CC2 0x00000040L +#define SPDIF_CONTROL_CC3 0x00000080L +#define SPDIF_CONTROL_CC4 0x00000100L +#define SPDIF_CONTROL_CC5 0x00000200L +#define SPDIF_CONTROL_CC6 0x00000400L +#define SPDIF_CONTROL_L 0x00000800L + +#endif // _H_HWDEFS diff --git a/sound/oss/cs4281/cs4281_wrapper-24.c b/sound/oss/cs4281/cs4281_wrapper-24.c new file mode 100644 index 000000000000..4559f02c9969 --- /dev/null +++ b/sound/oss/cs4281/cs4281_wrapper-24.c @@ -0,0 +1,41 @@ +/******************************************************************************* +* +* "cs4281_wrapper.c" -- Cirrus Logic-Crystal CS4281 linux audio driver. +* +* Copyright (C) 2000,2001 Cirrus Logic Corp. +* -- tom woller (twoller@crystal.cirrus.com) or +* (audio@crystal.cirrus.com). +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* 12/20/00 trw - new file. +* +*******************************************************************************/ + +#include + +static int cs4281_resume_null(struct pci_dev *pcidev) { return 0; } +static int cs4281_suspend_null(struct pci_dev *pcidev, pm_message_t state) { return 0; } + +#define free_dmabuf(state, dmabuf) \ + pci_free_consistent(state->pcidev, \ + PAGE_SIZE << (dmabuf)->buforder, \ + (dmabuf)->rawbuf, (dmabuf)->dmaaddr); +#define free_dmabuf2(state, dmabuf) \ + pci_free_consistent((state)->pcidev, \ + PAGE_SIZE << (state)->buforder_tmpbuff, \ + (state)->tmpbuff, (state)->dmaaddr_tmpbuff); +#define cs4x_pgoff(vma) ((vma)->vm_pgoff) + diff --git a/sound/oss/cs4281/cs4281m.c b/sound/oss/cs4281/cs4281m.c new file mode 100644 index 000000000000..d0d3963e1b83 --- /dev/null +++ b/sound/oss/cs4281/cs4281m.c @@ -0,0 +1,4505 @@ +/******************************************************************************* +* +* "cs4281.c" -- Cirrus Logic-Crystal CS4281 linux audio driver. +* +* 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). +* +* 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 cs4281_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. +* +*******************************************************************************/ + +/* uncomment the following line to disable building PM support into the driver */ +//#define NOT_CS4281_PM 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +//#include "cs_dm.h" +#include "cs4281_hwdefs.h" +#include "cs4281pm.h" + +struct cs4281_state; + +static void stop_dac(struct cs4281_state *s); +static void stop_adc(struct cs4281_state *s); +static void start_dac(struct cs4281_state *s); +static void start_adc(struct cs4281_state *s); +#undef OSS_DOCUMENTED_MIXER_SEMANTICS + +// --------------------------------------------------------------------- + +#ifndef PCI_VENDOR_ID_CIRRUS +#define PCI_VENDOR_ID_CIRRUS 0x1013 +#endif +#ifndef PCI_DEVICE_ID_CRYSTAL_CS4281 +#define PCI_DEVICE_ID_CRYSTAL_CS4281 0x6005 +#endif + +#define CS4281_MAGIC ((PCI_DEVICE_ID_CRYSTAL_CS4281<<16) | PCI_VENDOR_ID_CIRRUS) +#define CS4281_CFLR_DEFAULT 0x00000001 /* CFLR must be in AC97 link mode */ + +// 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). +static unsigned long defaultorder = 3; +module_param(defaultorder, ulong, 0); + +// +// Turn on/off debugging compilation by commenting out "#define CSDEBUG" +// +#define CSDEBUG 1 +#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_MIDI_WRITE 0x00000040 // write information for midi +#define CS_MIDI_READ 0x00000080 // read information for midi +#define CS_MPU401_WRITE 0x00000100 // write information for mpu401 +#define CS_MPU401_READ 0x00000200 // read information for mpu401 +#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_PM 0x00004000 // power management +#define CS_TMP 0x10000000 // tmp debug mask bit + +#define CS_IOCTL_CMD_SUSPEND 0x1 // suspend +#define CS_IOCTL_CMD_RESUME 0x2 // resume +// +// 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 = 1; // levels range from 1-9 +static unsigned long cs_debugmask = CS_INIT | CS_ERROR; // use CS_DBGOUT with various mask values +module_param(cs_debuglevel, ulong, 0); +module_param(cs_debugmask, ulong, 0); +#endif +#define CS_TRUE 1 +#define CS_FALSE 0 + +// MIDI buffer sizes +#define MIDIINBUF 500 +#define MIDIOUTBUF 500 + +#define FMODE_MIDI_SHIFT 3 +#define FMODE_MIDI_READ (FMODE_READ << FMODE_MIDI_SHIFT) +#define FMODE_MIDI_WRITE (FMODE_WRITE << FMODE_MIDI_SHIFT) + +#define CS4281_MAJOR_VERSION 1 +#define CS4281_MINOR_VERSION 13 +#ifdef __ia64__ +#define CS4281_ARCH 64 //architecture key +#else +#define CS4281_ARCH 32 //architecture key +#endif + +#define CS_TYPE_ADC 0 +#define CS_TYPE_DAC 1 + + +static const char invalid_magic[] = + KERN_CRIT "cs4281: invalid magic value\n"; + +#define VALIDATE_STATE(s) \ +({ \ + if (!(s) || (s)->magic != CS4281_MAGIC) { \ + printk(invalid_magic); \ + return -ENXIO; \ + } \ +}) + +//LIST_HEAD(cs4281_devs); +static struct list_head cs4281_devs = { &cs4281_devs, &cs4281_devs }; + +struct cs4281_state; + +#include "cs4281_wrapper-24.c" + +struct cs4281_state { + // magic + unsigned int magic; + + // we keep the cards in a linked list + struct cs4281_state *next; + + // pcidev is needed to turn off the DDMA controller at driver shutdown + struct pci_dev *pcidev; + struct list_head list; + + // soundcore stuff + int dev_audio; + int dev_mixer; + int dev_midi; + + // hardware resources + unsigned int pBA0phys, pBA1phys; + char __iomem *pBA0; + char __iomem *pBA1; + unsigned int irq; + + // 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; + unsigned char clkdiv; + } prop_dac, prop_adc; + unsigned conversion:1; // conversion from 16 to 8 bit in progress + void *tmpbuff; // tmp buffer for sample conversions + unsigned ena; + spinlock_t lock; + struct semaphore open_sem; + struct semaphore open_sem_adc; + struct semaphore open_sem_dac; + mode_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_tmpbuff; + unsigned buforder_tmpbuff; // Log base 2 of 'rawbuf' size in bytes.. + struct dmabuf { + void *rawbuf; // Physical address of + dma_addr_t dmaaddr; + unsigned buforder; // Log base 2 of 'rawbuf' 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; + // redundant, but makes calculations easier + unsigned fragsize; // 2**fragshift.. + unsigned dmasize; // 2**buforder. + unsigned fragsamples; + // OSS stuff + unsigned mapped:1; // Buffer mapped in cs4281_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; + } dma_dac, dma_adc; + + // midi stuff + struct { + unsigned ird, iwr, icnt; + unsigned ord, owr, ocnt; + wait_queue_head_t iwait; + wait_queue_head_t owait; + struct timer_list timer; + unsigned char ibuf[MIDIINBUF]; + unsigned char obuf[MIDIOUTBUF]; + } midi; + + struct cs4281_pm pm; + struct cs4281_pipeline pl[CS4281_NUMBER_OF_PIPELINES]; +}; + +#include "cs4281pm-24.c" + +#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) + +#define SOUND_MIXER_CS_APM _SIOWR('M',124, 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 prog_dmabuf_adc(struct cs4281_state *s); +static void prog_codec(struct cs4281_state *s, unsigned type); + +// --------------------------------------------------------------------- +// +// Hardware Interfaces For the CS4281 +// + + +//****************************************************************************** +// "delayus()-- Delay for the specified # of microseconds. +//****************************************************************************** +static void delayus(struct cs4281_state *s, u32 delay) +{ + u32 j; + if ((delay > 9999) && (s->pm.flags & CS4281_PM_IDLE)) { + j = (delay * HZ) / 1000000; /* calculate delay in jiffies */ + if (j < 1) + j = 1; /* minimum one jiffy. */ + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(j); + } else + udelay(delay); + return; +} + + +//****************************************************************************** +// "cs4281_read_ac97" -- Reads a word from the specified location in the +// CS4281's address space(based on the BA0 register). +// +// 1. Write ACCAD = Command Address Register = 46Ch for AC97 register address +// 2. Write ACCDA = Command Data Register = 470h for data to write to AC97 register, +// 0h for reads. +// 3. Write ACCTL = Control Register = 460h for initiating the write +// 4. Read ACCTL = 460h, DCV should be reset by now and 460h = 17h +// 5. if DCV not cleared, break and return error +// 6. Read ACSTS = Status Register = 464h, check VSTS bit +//**************************************************************************** +static int cs4281_read_ac97(struct cs4281_state *card, u32 offset, + u32 * value) +{ + u32 count, status; + + // Make sure that there is not data sitting + // around from a previous uncompleted access. + // ACSDA = Status Data Register = 47Ch + status = readl(card->pBA0 + BA0_ACSDA); + + // Setup the AC97 control registers on the CS4281 to send the + // appropriate command to the AC97 to perform the read. + // ACCAD = Command Address Register = 46Ch + // ACCDA = Command Data Register = 470h + // ACCTL = Control Register = 460h + // bit DCV - will clear when process completed + // bit CRW - Read command + // bit VFRM - valid frame enabled + // bit ESYN - ASYNC generation enabled + + // Get the actual AC97 register from the offset + writel(offset - BA0_AC97_RESET, card->pBA0 + BA0_ACCAD); + writel(0, card->pBA0 + BA0_ACCDA); + writel(ACCTL_DCV | ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN, + card->pBA0 + BA0_ACCTL); + + // Wait for the read to occur. + for (count = 0; count < 10; count++) { + // First, we want to wait for a short time. + udelay(25); + + // Now, check to see if the read has completed. + // ACCTL = 460h, DCV should be reset by now and 460h = 17h + if (!(readl(card->pBA0 + BA0_ACCTL) & ACCTL_DCV)) + break; + } + + // Make sure the read completed. + if (readl(card->pBA0 + BA0_ACCTL) & ACCTL_DCV) + return 1; + + // Wait for the valid status bit to go active. + for (count = 0; count < 10; count++) { + // Read the AC97 status register. + // ACSTS = Status Register = 464h + status = readl(card->pBA0 + BA0_ACSTS); + + // See if we have valid status. + // VSTS - Valid Status + if (status & ACSTS_VSTS) + break; + // Wait for a short while. + udelay(25); + } + + // Make sure we got valid status. + if (!(status & ACSTS_VSTS)) + return 1; + + // Read the data returned from the AC97 register. + // ACSDA = Status Data Register = 474h + *value = readl(card->pBA0 + BA0_ACSDA); + + // Success. + return (0); +} + + +//**************************************************************************** +// +// "cs4281_write_ac97()"-- writes a word to the specified location in the +// CS461x's address space (based on the part's base address zero register). +// +// 1. Write ACCAD = Command Address Register = 46Ch for AC97 register address +// 2. Write ACCDA = Command Data Register = 470h for data to write to AC97 reg. +// 3. Write ACCTL = Control Register = 460h for initiating the write +// 4. Read ACCTL = 460h, DCV should be reset by now and 460h = 07h +// 5. if DCV not cleared, break and return error +// +//**************************************************************************** +static int cs4281_write_ac97(struct cs4281_state *card, u32 offset, + u32 value) +{ + u32 count, status=0; + + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: cs_4281_write_ac97()+ \n")); + + // Setup the AC97 control registers on the CS4281 to send the + // appropriate command to the AC97 to perform the read. + // ACCAD = Command Address Register = 46Ch + // ACCDA = Command Data Register = 470h + // ACCTL = Control Register = 460h + // set DCV - will clear when process completed + // reset CRW - Write command + // set VFRM - valid frame enabled + // set ESYN - ASYNC generation enabled + // set RSTN - ARST# inactive, AC97 codec not reset + + // Get the actual AC97 register from the offset + + writel(offset - BA0_AC97_RESET, card->pBA0 + BA0_ACCAD); + writel(value, card->pBA0 + BA0_ACCDA); + writel(ACCTL_DCV | ACCTL_VFRM | ACCTL_ESYN, + card->pBA0 + BA0_ACCTL); + + // Wait for the write to occur. + for (count = 0; count < 100; count++) { + // First, we want to wait for a short time. + udelay(25); + // Now, check to see if the write has completed. + // ACCTL = 460h, DCV should be reset by now and 460h = 07h + status = readl(card->pBA0 + BA0_ACCTL); + if (!(status & ACCTL_DCV)) + break; + } + + // Make sure the write completed. + if (status & ACCTL_DCV) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_INFO + "cs4281: cs_4281_write_ac97()- unable to write. ACCTL_DCV active\n")); + return 1; + } + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: cs_4281_write_ac97()- 0\n")); + // Success. + return 0; +} + + +//****************************************************************************** +// "Init4281()" -- Bring up the part. +//****************************************************************************** +static __devinit int cs4281_hw_init(struct cs4281_state *card) +{ + u32 ac97_slotid; + u32 temp1, temp2; + + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: cs4281_hw_init()+ \n")); +#ifndef NOT_CS4281_PM + if(!card) + return 1; +#endif + temp2 = readl(card->pBA0 + BA0_CFLR); + CS_DBGOUT(CS_INIT | CS_ERROR | CS_PARMS, 4, printk(KERN_INFO + "cs4281: cs4281_hw_init() CFLR 0x%x\n", temp2)); + if(temp2 != CS4281_CFLR_DEFAULT) + { + CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_INFO + "cs4281: cs4281_hw_init() CFLR invalid - resetting from 0x%x to 0x%x\n", + temp2,CS4281_CFLR_DEFAULT)); + writel(CS4281_CFLR_DEFAULT, card->pBA0 + BA0_CFLR); + temp2 = readl(card->pBA0 + BA0_CFLR); + if(temp2 != CS4281_CFLR_DEFAULT) + { + CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_INFO + "cs4281: cs4281_hw_init() Invalid hardware - unable to configure CFLR\n")); + return 1; + } + } + + //***************************************7 + // Set up the Sound System Configuration + //*************************************** + + // Set the 'Configuration Write Protect' register + // to 4281h. Allows vendor-defined configuration + // space between 0e4h and 0ffh to be written. + + writel(0x4281, card->pBA0 + BA0_CWPR); // (3e0h) + + // (0), Blast the clock control register to zero so that the + // PLL starts out in a known state, and blast the master serial + // port control register to zero so that the serial ports also + // start out in a known state. + + writel(0, card->pBA0 + BA0_CLKCR1); // (400h) + writel(0, card->pBA0 + BA0_SERMC); // (420h) + + + // (1), Make ESYN go to zero to turn off + // the Sync pulse on the AC97 link. + + writel(0, card->pBA0 + BA0_ACCTL); + udelay(50); + + + // (2) Drive the ARST# pin low for a minimum of 1uS (as defined in + // the AC97 spec) and then drive it high. This is done for non + // AC97 modes since there might be logic external to the CS461x + // that uses the ARST# line for a reset. + + writel(0, card->pBA0 + BA0_SPMC); // (3ech) + udelay(100); + writel(SPMC_RSTN, card->pBA0 + BA0_SPMC); + delayus(card,50000); // Wait 50 ms for ABITCLK to become stable. + + // (3) Turn on the Sound System Clocks. + writel(CLKCR1_PLLP, card->pBA0 + BA0_CLKCR1); // (400h) + delayus(card,50000); // Wait for the PLL to stabilize. + // Turn on clocking of the core (CLKCR1(400h) = 0x00000030) + writel(CLKCR1_PLLP | CLKCR1_SWCE, card->pBA0 + BA0_CLKCR1); + + // (4) Power on everything for now.. + writel(0x7E, card->pBA0 + BA0_SSPM); // (740h) + + // (5) Wait for clock stabilization. + for (temp1 = 0; temp1 < 1000; temp1++) { + udelay(1000); + if (readl(card->pBA0 + BA0_CLKCR1) & CLKCR1_DLLRDY) + break; + } + if (!(readl(card->pBA0 + BA0_CLKCR1) & CLKCR1_DLLRDY)) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs4281: DLLRDY failed!\n")); + return -EIO; + } + // (6) Enable ASYNC generation. + writel(ACCTL_ESYN, card->pBA0 + BA0_ACCTL); // (460h) + + // Now wait 'for a short while' to allow the AC97 + // part to start generating bit clock. (so we don't + // Try to start the PLL without an input clock.) + delayus(card,50000); + + // Set the serial port timing configuration, so that the + // clock control circuit gets its clock from the right place. + writel(SERMC_PTC_AC97, card->pBA0 + BA0_SERMC); // (420h)=2. + + // (7) Wait for the codec ready signal from the AC97 codec. + + for (temp1 = 0; temp1 < 1000; temp1++) { + // Delay a mil to let things settle out and + // to prevent retrying the read too quickly. + udelay(1000); + if (readl(card->pBA0 + BA0_ACSTS) & ACSTS_CRDY) // If ready, (464h) + break; // exit the 'for' loop. + } + if (!(readl(card->pBA0 + BA0_ACSTS) & ACSTS_CRDY)) // If never came ready, + { + CS_DBGOUT(CS_FUNCTION, 2, printk(KERN_ERR + "cs4281: ACSTS never came ready!\n")); + return -EIO; // exit initialization. + } + // (8) Assert the 'valid frame' signal so we can + // begin sending commands to the AC97 codec. + writel(ACCTL_VFRM | ACCTL_ESYN, card->pBA0 + BA0_ACCTL); // (460h) + + // (9), Wait until CODEC calibration is finished. + // Print an error message if it doesn't. + for (temp1 = 0; temp1 < 1000; temp1++) { + delayus(card,10000); + // Read the AC97 Powerdown Control/Status Register. + cs4281_read_ac97(card, BA0_AC97_POWERDOWN, &temp2); + if ((temp2 & 0x0000000F) == 0x0000000F) + break; + } + if ((temp2 & 0x0000000F) != 0x0000000F) { + CS_DBGOUT(CS_FUNCTION, 2, printk(KERN_ERR + "cs4281: Codec failed to calibrate. Status = %.8x.\n", + temp2)); + return -EIO; + } + // (10), Set the serial port timing configuration, so that the + // clock control circuit gets its clock from the right place. + writel(SERMC_PTC_AC97, card->pBA0 + BA0_SERMC); // (420h)=2. + + + // (11) Wait until we've sampled input slots 3 & 4 as valid, meaning + // that the codec is pumping ADC data across the AC link. + for (temp1 = 0; temp1 < 1000; temp1++) { + // Delay a mil to let things settle out and + // to prevent retrying the read too quickly. + delayus(card,1000); //(test) + + // Read the input slot valid register; See + // if input slots 3 and 4 are valid yet. + if ( + (readl(card->pBA0 + BA0_ACISV) & + (ACISV_ISV3 | ACISV_ISV4)) == + (ACISV_ISV3 | ACISV_ISV4)) break; // Exit the 'for' if slots are valid. + } + // If we never got valid data, exit initialization. + if ((readl(card->pBA0 + BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) + != (ACISV_ISV3 | ACISV_ISV4)) { + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_ERR + "cs4281: Never got valid data!\n")); + return -EIO; // If no valid data, exit initialization. + } + // (12), Start digital data transfer of audio data to the codec. + writel(ACOSV_SLV3 | ACOSV_SLV4, card->pBA0 + BA0_ACOSV); // (468h) + + + //************************************** + // Unmute the Master and Alternate + // (headphone) volumes. Set to max. + //************************************** + cs4281_write_ac97(card, BA0_AC97_HEADPHONE_VOLUME, 0); + cs4281_write_ac97(card, BA0_AC97_MASTER_VOLUME, 0); + + //****************************************** + // Power on the DAC(AddDACUser()from main()) + //****************************************** + cs4281_read_ac97(card, BA0_AC97_POWERDOWN, &temp1); + cs4281_write_ac97(card, BA0_AC97_POWERDOWN, temp1 &= 0xfdff); + + // Wait until we sample a DAC ready state. + for (temp2 = 0; temp2 < 32; temp2++) { + // Let's wait a mil to let things settle. + delayus(card,1000); + // Read the current state of the power control reg. + cs4281_read_ac97(card, BA0_AC97_POWERDOWN, &temp1); + // If the DAC ready state bit is set, stop waiting. + if (temp1 & 0x2) + break; + } + + //****************************************** + // Power on the ADC(AddADCUser()from main()) + //****************************************** + cs4281_read_ac97(card, BA0_AC97_POWERDOWN, &temp1); + cs4281_write_ac97(card, BA0_AC97_POWERDOWN, temp1 &= 0xfeff); + + // Wait until we sample ADC ready state. + for (temp2 = 0; temp2 < 32; temp2++) { + // Let's wait a mil to let things settle. + delayus(card,1000); + // Read the current state of the power control reg. + cs4281_read_ac97(card, BA0_AC97_POWERDOWN, &temp1); + // If the ADC ready state bit is set, stop waiting. + if (temp1 & 0x1) + break; + } + // Set up 4281 Register contents that + // don't change for boot duration. + + // For playback, we map AC97 slot 3 and 4(Left + // & Right PCM playback) to DMA Channel 0. + // Set the fifo to be 15 bytes at offset zero. + + ac97_slotid = 0x01000f00; // FCR0.RS[4:0]=1(=>slot4, right PCM playback). + // FCR0.LS[4:0]=0(=>slot3, left PCM playback). + // FCR0.SZ[6-0]=15; FCR0.OF[6-0]=0. + writel(ac97_slotid, card->pBA0 + BA0_FCR0); // (180h) + writel(ac97_slotid | FCRn_FEN, card->pBA0 + BA0_FCR0); // Turn on FIFO Enable. + + // For capture, we map AC97 slot 10 and 11(Left + // and Right PCM Record) to DMA Channel 1. + // Set the fifo to be 15 bytes at offset sixteen. + ac97_slotid = 0x0B0A0f10; // FCR1.RS[4:0]=11(=>slot11, right PCM record). + // FCR1.LS[4:0]=10(=>slot10, left PCM record). + // FCR1.SZ[6-0]=15; FCR1.OF[6-0]=16. + writel(ac97_slotid | FCRn_PSH, card->pBA0 + BA0_FCR1); // (184h) + writel(ac97_slotid | FCRn_FEN, card->pBA0 + BA0_FCR1); // Turn on FIFO Enable. + + // Map the Playback SRC to the same AC97 slots(3 & 4-- + // --Playback left & right)as DMA channel 0. + // Map the record SRC to the same AC97 slots(10 & 11-- + // -- Record left & right) as DMA channel 1. + + ac97_slotid = 0x0b0a0100; // SCRSA.PRSS[4:0]=1(=>slot4, right PCM playback). + // SCRSA.PLSS[4:0]=0(=>slot3, left PCM playback). + // SCRSA.CRSS[4:0]=11(=>slot11, right PCM record) + // SCRSA.CLSS[4:0]=10(=>slot10, left PCM record). + writel(ac97_slotid, card->pBA0 + BA0_SRCSA); // (75ch) + + // Set 'Half Terminal Count Interrupt Enable' and 'Terminal + // Count Interrupt Enable' in DMA Control Registers 0 & 1. + // Set 'MSK' flag to 1 to keep the DMA engines paused. + temp1 = (DCRn_HTCIE | DCRn_TCIE | DCRn_MSK); // (00030001h) + writel(temp1, card->pBA0 + BA0_DCR0); // (154h + writel(temp1, card->pBA0 + BA0_DCR1); // (15ch) + + // Set 'Auto-Initialize Control' to 'enabled'; For playback, + // set 'Transfer Type Control'(TR[1:0]) to 'read transfer', + // for record, set Transfer Type Control to 'write transfer'. + // All other bits set to zero; Some will be changed @ transfer start. + temp1 = (DMRn_DMA | DMRn_AUTO | DMRn_TR_READ); // (20000018h) + writel(temp1, card->pBA0 + BA0_DMR0); // (150h) + temp1 = (DMRn_DMA | DMRn_AUTO | DMRn_TR_WRITE); // (20000014h) + writel(temp1, card->pBA0 + BA0_DMR1); // (158h) + + // Enable DMA interrupts generally, and + // DMA0 & DMA1 interrupts specifically. + temp1 = readl(card->pBA0 + BA0_HIMR) & 0xfffbfcff; + writel(temp1, card->pBA0 + BA0_HIMR); + + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: cs4281_hw_init()- 0\n")); + return 0; +} + +#ifndef NOT_CS4281_PM +static void printpm(struct cs4281_state *s) +{ + CS_DBGOUT(CS_PM, 9, printk("pm struct:\n")); + CS_DBGOUT(CS_PM, 9, printk("flags:0x%x u32CLKCR1_SAVE: 0%x u32SSPMValue: 0x%x\n", + (unsigned)s->pm.flags,s->pm.u32CLKCR1_SAVE,s->pm.u32SSPMValue)); + CS_DBGOUT(CS_PM, 9, printk("u32PPLVCvalue: 0x%x u32PPRVCvalue: 0x%x\n", + s->pm.u32PPLVCvalue,s->pm.u32PPRVCvalue)); + CS_DBGOUT(CS_PM, 9, printk("u32FMLVCvalue: 0x%x u32FMRVCvalue: 0x%x\n", + s->pm.u32FMLVCvalue,s->pm.u32FMRVCvalue)); + CS_DBGOUT(CS_PM, 9, printk("u32GPIORvalue: 0x%x u32JSCTLvalue: 0x%x\n", + s->pm.u32GPIORvalue,s->pm.u32JSCTLvalue)); + CS_DBGOUT(CS_PM, 9, printk("u32SSCR: 0x%x u32SRCSA: 0x%x\n", + s->pm.u32SSCR,s->pm.u32SRCSA)); + CS_DBGOUT(CS_PM, 9, printk("u32DacASR: 0x%x u32AdcASR: 0x%x\n", + s->pm.u32DacASR,s->pm.u32AdcASR)); + CS_DBGOUT(CS_PM, 9, printk("u32DacSR: 0x%x u32AdcSR: 0x%x\n", + s->pm.u32DacSR,s->pm.u32AdcSR)); + CS_DBGOUT(CS_PM, 9, printk("u32MIDCR_Save: 0x%x\n", + s->pm.u32MIDCR_Save)); + +} +static void printpipe(struct cs4281_pipeline *pl) +{ + + CS_DBGOUT(CS_PM, 9, printk("pm struct:\n")); + CS_DBGOUT(CS_PM, 9, printk("flags:0x%x number: 0%x\n", + (unsigned)pl->flags,pl->number)); + CS_DBGOUT(CS_PM, 9, printk("u32DBAnValue: 0%x u32DBCnValue: 0x%x\n", + pl->u32DBAnValue,pl->u32DBCnValue)); + CS_DBGOUT(CS_PM, 9, printk("u32DMRnValue: 0x%x u32DCRnValue: 0x%x\n", + pl->u32DMRnValue,pl->u32DCRnValue)); + CS_DBGOUT(CS_PM, 9, printk("u32DBAnAddress: 0x%x u32DBCnAddress: 0x%x\n", + pl->u32DBAnAddress,pl->u32DBCnAddress)); + CS_DBGOUT(CS_PM, 9, printk("u32DCAnAddress: 0x%x u32DCCnAddress: 0x%x\n", + pl->u32DCCnAddress,pl->u32DCCnAddress)); + CS_DBGOUT(CS_PM, 9, printk("u32DMRnAddress: 0x%x u32DCRnAddress: 0x%x\n", + pl->u32DMRnAddress,pl->u32DCRnAddress)); + CS_DBGOUT(CS_PM, 9, printk("u32HDSRnAddress: 0x%x u32DBAn_Save: 0x%x\n", + pl->u32HDSRnAddress,pl->u32DBAn_Save)); + CS_DBGOUT(CS_PM, 9, printk("u32DBCn_Save: 0x%x u32DMRn_Save: 0x%x\n", + pl->u32DBCn_Save,pl->u32DMRn_Save)); + CS_DBGOUT(CS_PM, 9, printk("u32DCRn_Save: 0x%x u32DCCn_Save: 0x%x\n", + pl->u32DCRn_Save,pl->u32DCCn_Save)); + CS_DBGOUT(CS_PM, 9, printk("u32DCAn_Save: 0x%x\n", + pl->u32DCAn_Save)); + CS_DBGOUT(CS_PM, 9, printk("u32FCRn_Save: 0x%x u32FSICn_Save: 0x%x\n", + pl->u32FCRn_Save,pl->u32FSICn_Save)); + CS_DBGOUT(CS_PM, 9, printk("u32FCRnValue: 0x%x u32FSICnValue: 0x%x\n", + pl->u32FCRnValue,pl->u32FSICnValue)); + CS_DBGOUT(CS_PM, 9, printk("u32FCRnAddress: 0x%x u32FSICnAddress: 0x%x\n", + pl->u32FCRnAddress,pl->u32FSICnAddress)); + CS_DBGOUT(CS_PM, 9, printk("u32FPDRnValue: 0x%x u32FPDRnAddress: 0x%x\n", + pl->u32FPDRnValue,pl->u32FPDRnAddress)); +} +static void printpipelines(struct cs4281_state *s) +{ + int i; + for(i=0;ipl[i].flags & CS4281_PIPELINE_VALID) + { + printpipe(&s->pl[i]); + } + } +} +/**************************************************************************** +* +* Suspend - save the ac97 regs, mute the outputs and power down the part. +* +****************************************************************************/ +static void cs4281_ac97_suspend(struct cs4281_state *s) +{ + int Count,i; + + CS_DBGOUT(CS_PM, 9, printk("cs4281: cs4281_ac97_suspend()+\n")); +/* +* change the state, save the current hwptr, then stop the dac/adc +*/ + s->pm.flags &= ~CS4281_PM_IDLE; + s->pm.flags |= CS4281_PM_SUSPENDING; + s->pm.u32hwptr_playback = readl(s->pBA0 + BA0_DCA0); + s->pm.u32hwptr_capture = readl(s->pBA0 + BA0_DCA1); + stop_dac(s); + stop_adc(s); + + for(Count = 0x2, i=0; (Count <= CS4281_AC97_HIGHESTREGTORESTORE) + && (i < CS4281_AC97_NUMBER_RESTORE_REGS); + Count += 2, i++) + { + cs4281_read_ac97(s, BA0_AC97_RESET + Count, &s->pm.ac97[i]); + } +/* +* Save the ac97 volume registers as well as the current powerdown state. +* Now, mute the all the outputs (master, headphone, and mono), as well +* as the PCM volume, in preparation for powering down the entire part. +*/ + cs4281_read_ac97(s, BA0_AC97_MASTER_VOLUME, &s->pm.u32AC97_master_volume); + cs4281_read_ac97(s, BA0_AC97_HEADPHONE_VOLUME, &s->pm.u32AC97_headphone_volume); + cs4281_read_ac97(s, BA0_AC97_MASTER_VOLUME_MONO, &s->pm.u32AC97_master_volume_mono); + cs4281_read_ac97(s, BA0_AC97_PCM_OUT_VOLUME, &s->pm.u32AC97_pcm_out_volume); + + cs4281_write_ac97(s, BA0_AC97_MASTER_VOLUME, 0x8000); + cs4281_write_ac97(s, BA0_AC97_HEADPHONE_VOLUME, 0x8000); + cs4281_write_ac97(s, BA0_AC97_MASTER_VOLUME_MONO, 0x8000); + cs4281_write_ac97(s, BA0_AC97_PCM_OUT_VOLUME, 0x8000); + + cs4281_read_ac97(s, BA0_AC97_POWERDOWN, &s->pm.u32AC97_powerdown); + cs4281_read_ac97(s, BA0_AC97_GENERAL_PURPOSE, &s->pm.u32AC97_general_purpose); + +/* +* And power down everything on the AC97 codec. +*/ + cs4281_write_ac97(s, BA0_AC97_POWERDOWN, 0xff00); + CS_DBGOUT(CS_PM, 9, printk("cs4281: cs4281_ac97_suspend()-\n")); +} + +/**************************************************************************** +* +* Resume - power up the part and restore its registers.. +* +****************************************************************************/ +static void cs4281_ac97_resume(struct cs4281_state *s) +{ + int Count,i; + + CS_DBGOUT(CS_PM, 9, printk("cs4281: cs4281_ac97_resume()+\n")); + +/* do not save the power state registers at this time + // + // If we saved away the power control registers, write them into the + // shadows so those saved values get restored instead of the current + // shadowed value. + // + if( bPowerStateSaved ) + { + PokeShadow( 0x26, ulSaveReg0x26 ); + bPowerStateSaved = FALSE; + } +*/ + +// +// First, we restore the state of the general purpose register. This +// contains the mic select (mic1 or mic2) and if we restore this after +// we restore the mic volume/boost state and mic2 was selected at +// suspend time, we will end up with a brief period of time where mic1 +// is selected with the volume/boost settings for mic2, causing +// acoustic feedback. So we restore the general purpose register +// first, thereby getting the correct mic selected before we restore +// the mic volume/boost. +// + cs4281_write_ac97(s, BA0_AC97_GENERAL_PURPOSE, s->pm.u32AC97_general_purpose); + +// +// Now, while the outputs are still muted, restore the state of power +// on the AC97 part. +// + cs4281_write_ac97(s, BA0_AC97_POWERDOWN, s->pm.u32AC97_powerdown); + +/* +* Restore just the first set of registers, from register number +* 0x02 to the register number that ulHighestRegToRestore specifies. +*/ + for( Count = 0x2, i=0; + (Count <= CS4281_AC97_HIGHESTREGTORESTORE) + && (i < CS4281_AC97_NUMBER_RESTORE_REGS); + Count += 2, i++) + { + cs4281_write_ac97(s, BA0_AC97_RESET + Count, s->pm.ac97[i]); + } + CS_DBGOUT(CS_PM, 9, printk("cs4281: cs4281_ac97_resume()-\n")); +} + +/* do not save the power state registers at this time +**************************************************************************** +* +* SavePowerState - Save the power registers away. +* +**************************************************************************** +void +HWAC97codec::SavePowerState(void) +{ + ENTRY(TM_OBJECTCALLS, "HWAC97codec::SavePowerState()\r\n"); + + ulSaveReg0x26 = PeekShadow(0x26); + + // + // Note that we have saved registers that need to be restored during a + // resume instead of ulAC97Regs[]. + // + bPowerStateSaved = TRUE; + +} // SavePowerState +*/ + +static void cs4281_SuspendFIFO(struct cs4281_state *s, struct cs4281_pipeline *pl) +{ + /* + * We need to save the contents of the BASIC FIFO Registers. + */ + pl->u32FCRn_Save = readl(s->pBA0 + pl->u32FCRnAddress); + pl->u32FSICn_Save = readl(s->pBA0 + pl->u32FSICnAddress); +} +static void cs4281_ResumeFIFO(struct cs4281_state *s, struct cs4281_pipeline *pl) +{ + /* + * We need to restore the contents of the BASIC FIFO Registers. + */ + writel(pl->u32FCRn_Save,s->pBA0 + pl->u32FCRnAddress); + writel(pl->u32FSICn_Save,s->pBA0 + pl->u32FSICnAddress); +} +static void cs4281_SuspendDMAengine(struct cs4281_state *s, struct cs4281_pipeline *pl) +{ + // + // We need to save the contents of the BASIC DMA Registers. + // + pl->u32DBAn_Save = readl(s->pBA0 + pl->u32DBAnAddress); + pl->u32DBCn_Save = readl(s->pBA0 + pl->u32DBCnAddress); + pl->u32DMRn_Save = readl(s->pBA0 + pl->u32DMRnAddress); + pl->u32DCRn_Save = readl(s->pBA0 + pl->u32DCRnAddress); + pl->u32DCCn_Save = readl(s->pBA0 + pl->u32DCCnAddress); + pl->u32DCAn_Save = readl(s->pBA0 + pl->u32DCAnAddress); +} +static void cs4281_ResumeDMAengine(struct cs4281_state *s, struct cs4281_pipeline *pl) +{ + // + // We need to save the contents of the BASIC DMA Registers. + // + writel( pl->u32DBAn_Save, s->pBA0 + pl->u32DBAnAddress); + writel( pl->u32DBCn_Save, s->pBA0 + pl->u32DBCnAddress); + writel( pl->u32DMRn_Save, s->pBA0 + pl->u32DMRnAddress); + writel( pl->u32DCRn_Save, s->pBA0 + pl->u32DCRnAddress); + writel( pl->u32DCCn_Save, s->pBA0 + pl->u32DCCnAddress); + writel( pl->u32DCAn_Save, s->pBA0 + pl->u32DCAnAddress); +} + +static int cs4281_suspend(struct cs4281_state *s) +{ + int i; + u32 u32CLKCR1; + struct cs4281_pm *pm = &s->pm; + CS_DBGOUT(CS_PM | CS_FUNCTION, 9, + printk("cs4281: cs4281_suspend()+ flags=%d\n", + (unsigned)s->pm.flags)); +/* +* check the current state, only suspend if IDLE +*/ + if(!(s->pm.flags & CS4281_PM_IDLE)) + { + CS_DBGOUT(CS_PM | CS_ERROR, 2, + printk("cs4281: cs4281_suspend() unable to suspend, not IDLE\n")); + return 1; + } + s->pm.flags &= ~CS4281_PM_IDLE; + s->pm.flags |= CS4281_PM_SUSPENDING; + +// +// Gershwin CLKRUN - Set CKRA +// + u32CLKCR1 = readl(s->pBA0 + BA0_CLKCR1); + + pm->u32CLKCR1_SAVE = u32CLKCR1; + if(!(u32CLKCR1 & 0x00010000 ) ) + writel(u32CLKCR1 | 0x00010000, s->pBA0 + BA0_CLKCR1); + +// +// First, turn on the clocks (yikes) to the devices, so that they will +// respond when we try to save their state. +// + if(!(u32CLKCR1 & CLKCR1_SWCE)) + { + writel(u32CLKCR1 | CLKCR1_SWCE , s->pBA0 + BA0_CLKCR1); + } + + // + // Save the power state + // + pm->u32SSPMValue = readl(s->pBA0 + BA0_SSPM); + + // + // Disable interrupts. + // + writel(HICR_CHGM, s->pBA0 + BA0_HICR); + + // + // Save the PCM Playback Left and Right Volume Control. + // + pm->u32PPLVCvalue = readl(s->pBA0 + BA0_PPLVC); + pm->u32PPRVCvalue = readl(s->pBA0 + BA0_PPRVC); + + // + // Save the FM Synthesis Left and Right Volume Control. + // + pm->u32FMLVCvalue = readl(s->pBA0 + BA0_FMLVC); + pm->u32FMRVCvalue = readl(s->pBA0 + BA0_FMRVC); + + // + // Save the GPIOR value. + // + pm->u32GPIORvalue = readl(s->pBA0 + BA0_GPIOR); + + // + // Save the JSCTL value. + // + pm->u32JSCTLvalue = readl(s->pBA0 + BA0_GPIOR); + + // + // Save Sound System Control Register + // + pm->u32SSCR = readl(s->pBA0 + BA0_SSCR); + + // + // Save SRC Slot Assinment register + // + pm->u32SRCSA = readl(s->pBA0 + BA0_SRCSA); + + // + // Save sample rate + // + pm->u32DacASR = readl(s->pBA0 + BA0_PASR); + pm->u32AdcASR = readl(s->pBA0 + BA0_CASR); + pm->u32DacSR = readl(s->pBA0 + BA0_DACSR); + pm->u32AdcSR = readl(s->pBA0 + BA0_ADCSR); + + // + // Loop through all of the PipeLines + // + for(i = 0; i < CS4281_NUMBER_OF_PIPELINES; i++) + { + if(s->pl[i].flags & CS4281_PIPELINE_VALID) + { + // + // Ask the DMAengines and FIFOs to Suspend. + // + cs4281_SuspendDMAengine(s,&s->pl[i]); + cs4281_SuspendFIFO(s,&s->pl[i]); + } + } + // + // We need to save the contents of the Midi Control Register. + // + pm->u32MIDCR_Save = readl(s->pBA0 + BA0_MIDCR); +/* +* save off the AC97 part information +*/ + cs4281_ac97_suspend(s); + + // + // Turn off the serial ports. + // + writel(0, s->pBA0 + BA0_SERMC); + + // + // Power off FM, Joystick, AC link, + // + writel(0, s->pBA0 + BA0_SSPM); + + // + // DLL off. + // + writel(0, s->pBA0 + BA0_CLKCR1); + + // + // AC link off. + // + writel(0, s->pBA0 + BA0_SPMC); + + // + // Put the chip into D3(hot) state. + // + // PokeBA0(BA0_PMCS, 0x00000003); + + // + // Gershwin CLKRUN - Clear CKRA + // + u32CLKCR1 = readl(s->pBA0 + BA0_CLKCR1); + writel(u32CLKCR1 & 0xFFFEFFFF, s->pBA0 + BA0_CLKCR1); + +#ifdef CSDEBUG + printpm(s); + printpipelines(s); +#endif + + s->pm.flags &= ~CS4281_PM_SUSPENDING; + s->pm.flags |= CS4281_PM_SUSPENDED; + + CS_DBGOUT(CS_PM | CS_FUNCTION, 9, + printk("cs4281: cs4281_suspend()- flags=%d\n", + (unsigned)s->pm.flags)); + return 0; +} + +static int cs4281_resume(struct cs4281_state *s) +{ + int i; + unsigned temp1; + u32 u32CLKCR1; + struct cs4281_pm *pm = &s->pm; + CS_DBGOUT(CS_PM | CS_FUNCTION, 4, + printk( "cs4281: cs4281_resume()+ flags=%d\n", + (unsigned)s->pm.flags)); + if(!(s->pm.flags & CS4281_PM_SUSPENDED)) + { + CS_DBGOUT(CS_PM | CS_ERROR, 2, + printk("cs4281: cs4281_resume() unable to resume, not SUSPENDED\n")); + return 1; + } + s->pm.flags &= ~CS4281_PM_SUSPENDED; + s->pm.flags |= CS4281_PM_RESUMING; + +// +// Gershwin CLKRUN - Set CKRA +// + u32CLKCR1 = readl(s->pBA0 + BA0_CLKCR1); + writel(u32CLKCR1 | 0x00010000, s->pBA0 + BA0_CLKCR1); + + // + // set the power state. + // + //old PokeBA0(BA0_PMCS, 0); + + // + // Program the clock circuit and serial ports. + // + temp1 = cs4281_hw_init(s); + if (temp1) { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, + printk(KERN_ERR + "cs4281: resume cs4281_hw_init() error.\n")); + return -1; + } + + // + // restore the Power state + // + writel(pm->u32SSPMValue, s->pBA0 + BA0_SSPM); + + // + // Set post SRC mix setting (FM or ALT48K) + // + writel(pm->u32SSPM_BITS, s->pBA0 + BA0_SSPM); + + // + // Loop through all of the PipeLines + // + for(i = 0; i < CS4281_NUMBER_OF_PIPELINES; i++) + { + if(s->pl[i].flags & CS4281_PIPELINE_VALID) + { + // + // Ask the DMAengines and FIFOs to Resume. + // + cs4281_ResumeDMAengine(s,&s->pl[i]); + cs4281_ResumeFIFO(s,&s->pl[i]); + } + } + // + // We need to restore the contents of the Midi Control Register. + // + writel(pm->u32MIDCR_Save, s->pBA0 + BA0_MIDCR); + + cs4281_ac97_resume(s); + // + // Restore the PCM Playback Left and Right Volume Control. + // + writel(pm->u32PPLVCvalue, s->pBA0 + BA0_PPLVC); + writel(pm->u32PPRVCvalue, s->pBA0 + BA0_PPRVC); + + // + // Restore the FM Synthesis Left and Right Volume Control. + // + writel(pm->u32FMLVCvalue, s->pBA0 + BA0_FMLVC); + writel(pm->u32FMRVCvalue, s->pBA0 + BA0_FMRVC); + + // + // Restore the JSCTL value. + // + writel(pm->u32JSCTLvalue, s->pBA0 + BA0_JSCTL); + + // + // Restore the GPIOR register value. + // + writel(pm->u32GPIORvalue, s->pBA0 + BA0_GPIOR); + + // + // Restore Sound System Control Register + // + writel(pm->u32SSCR, s->pBA0 + BA0_SSCR); + + // + // Restore SRC Slot Assignment register + // + writel(pm->u32SRCSA, s->pBA0 + BA0_SRCSA); + + // + // Restore sample rate + // + writel(pm->u32DacASR, s->pBA0 + BA0_PASR); + writel(pm->u32AdcASR, s->pBA0 + BA0_CASR); + writel(pm->u32DacSR, s->pBA0 + BA0_DACSR); + writel(pm->u32AdcSR, s->pBA0 + BA0_ADCSR); + + // + // Restore CFL1/2 registers we saved to compensate for OEM bugs. + // + // PokeBA0(BA0_CFLR, ulConfig); + + // + // Gershwin CLKRUN - Clear CKRA + // + writel(pm->u32CLKCR1_SAVE, s->pBA0 + BA0_CLKCR1); + + // + // Enable interrupts on the part. + // + writel(HICR_IEV | HICR_CHGM, s->pBA0 + BA0_HICR); + +#ifdef CSDEBUG + printpm(s); + printpipelines(s); +#endif +/* +* change the state, restore the current hwptrs, then stop the dac/adc +*/ + s->pm.flags |= CS4281_PM_IDLE; + s->pm.flags &= ~(CS4281_PM_SUSPENDING | CS4281_PM_SUSPENDED + | CS4281_PM_RESUMING | CS4281_PM_RESUMED); + + writel(s->pm.u32hwptr_playback, s->pBA0 + BA0_DCA0); + writel(s->pm.u32hwptr_capture, s->pBA0 + BA0_DCA1); + start_dac(s); + start_adc(s); + + CS_DBGOUT(CS_PM | CS_FUNCTION, 9, printk("cs4281: cs4281_resume()- flags=%d\n", + (unsigned)s->pm.flags)); + return 0; +} + +#endif + +//****************************************************************************** +// "cs4281_play_rate()" -- +//****************************************************************************** +static void cs4281_play_rate(struct cs4281_state *card, u32 playrate) +{ + u32 DACSRvalue = 1; + + // Based on the sample rate, program the DACSR register. + if (playrate == 8000) + DACSRvalue = 5; + if (playrate == 11025) + DACSRvalue = 4; + else if (playrate == 22050) + DACSRvalue = 2; + else if (playrate == 44100) + DACSRvalue = 1; + else if ((playrate <= 48000) && (playrate >= 6023)) + DACSRvalue = 24576000 / (playrate * 16); + else if (playrate < 6023) + // Not allowed by open. + return; + else if (playrate > 48000) + // Not allowed by open. + return; + CS_DBGOUT(CS_WAVE_WRITE | CS_PARMS, 2, printk(KERN_INFO + "cs4281: cs4281_play_rate(): DACSRvalue=0x%.8x playrate=%d\n", + DACSRvalue, playrate)); + // Write the 'sample rate select code' + // to the 'DAC Sample Rate' register. + writel(DACSRvalue, card->pBA0 + BA0_DACSR); // (744h) +} + +//****************************************************************************** +// "cs4281_record_rate()" -- Initialize the record sample rate converter. +//****************************************************************************** +static void cs4281_record_rate(struct cs4281_state *card, u32 outrate) +{ + u32 ADCSRvalue = 1; + + // + // Based on the sample rate, program the ADCSR register + // + if (outrate == 8000) + ADCSRvalue = 5; + if (outrate == 11025) + ADCSRvalue = 4; + else if (outrate == 22050) + ADCSRvalue = 2; + else if (outrate == 44100) + ADCSRvalue = 1; + else if ((outrate <= 48000) && (outrate >= 6023)) + ADCSRvalue = 24576000 / (outrate * 16); + else if (outrate < 6023) { + // Not allowed by open. + return; + } else if (outrate > 48000) { + // Not allowed by open. + return; + } + CS_DBGOUT(CS_WAVE_READ | CS_PARMS, 2, printk(KERN_INFO + "cs4281: cs4281_record_rate(): ADCSRvalue=0x%.8x outrate=%d\n", + ADCSRvalue, outrate)); + // Write the 'sample rate select code + // to the 'ADC Sample Rate' register. + writel(ADCSRvalue, card->pBA0 + BA0_ADCSR); // (748h) +} + + + +static void stop_dac(struct cs4281_state *s) +{ + unsigned long flags; + unsigned temp1; + + CS_DBGOUT(CS_WAVE_WRITE, 3, printk(KERN_INFO "cs4281: stop_dac():\n")); + spin_lock_irqsave(&s->lock, flags); + s->ena &= ~FMODE_WRITE; + temp1 = readl(s->pBA0 + BA0_DCR0) | DCRn_MSK; + writel(temp1, s->pBA0 + BA0_DCR0); + + spin_unlock_irqrestore(&s->lock, flags); +} + + +static void start_dac(struct cs4281_state *s) +{ + unsigned long flags; + unsigned temp1; + + CS_DBGOUT(CS_FUNCTION, 3, printk(KERN_INFO "cs4281: 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)) +#ifndef NOT_CS4281_PM + && (s->pm.flags & CS4281_PM_IDLE)) +#else +) +#endif + { + s->ena |= FMODE_WRITE; + temp1 = readl(s->pBA0 + BA0_DCR0) & ~DCRn_MSK; // Clear DMA0 channel mask. + writel(temp1, s->pBA0 + BA0_DCR0); // Start DMA'ing. + writel(HICR_IEV | HICR_CHGM, s->pBA0 + BA0_HICR); // Enable interrupts. + + writel(7, s->pBA0 + BA0_PPRVC); + writel(7, s->pBA0 + BA0_PPLVC); + CS_DBGOUT(CS_WAVE_WRITE | CS_PARMS, 8, printk(KERN_INFO + "cs4281: start_dac(): writel 0x%x start dma\n", temp1)); + + } + spin_unlock_irqrestore(&s->lock, flags); + CS_DBGOUT(CS_FUNCTION, 3, + printk(KERN_INFO "cs4281: start_dac()-\n")); +} + + +static void stop_adc(struct cs4281_state *s) +{ + unsigned long flags; + unsigned temp1; + + CS_DBGOUT(CS_FUNCTION, 3, + printk(KERN_INFO "cs4281: 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; + } + temp1 = readl(s->pBA0 + BA0_DCR1) | DCRn_MSK; + writel(temp1, s->pBA0 + BA0_DCR1); + spin_unlock_irqrestore(&s->lock, flags); + CS_DBGOUT(CS_FUNCTION, 3, + printk(KERN_INFO "cs4281: stop_adc()-\n")); +} + + +static void start_adc(struct cs4281_state *s) +{ + unsigned long flags; + unsigned temp1; + + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: start_adc()+\n")); + + if (!(s->ena & FMODE_READ) && + (s->dma_adc.mapped || s->dma_adc.count <= + (signed) (s->dma_adc.dmasize - 2 * s->dma_adc.fragsize)) + && s->dma_adc.ready +#ifndef NOT_CS4281_PM + && (s->pm.flags & CS4281_PM_IDLE)) +#else +) +#endif + { + 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, noticable 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); + + if (prog_dmabuf_adc(s) != 0) { + CS_DBGOUT(CS_ERROR, 2, printk(KERN_INFO + "cs4281: start_adc(): error in prog_dmabuf_adc\n")); + } + s->conversion = 1; + } + spin_lock_irqsave(&s->lock, flags); + s->ena |= FMODE_READ; + temp1 = readl(s->pBA0 + BA0_DCR1) & ~DCRn_MSK; // Clear DMA1 channel mask bit. + writel(temp1, s->pBA0 + BA0_DCR1); // Start recording + writel(HICR_IEV | HICR_CHGM, s->pBA0 + BA0_HICR); // Enable interrupts. + spin_unlock_irqrestore(&s->lock, flags); + + CS_DBGOUT(CS_PARMS, 6, printk(KERN_INFO + "cs4281: start_adc(): writel 0x%x \n", temp1)); + } + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: start_adc()-\n")); + +} + + +// --------------------------------------------------------------------- + +#define DMABUF_MINORDER 1 // ==> min buffer size = 8K. + + +static void dealloc_dmabuf(struct cs4281_state *s, struct dmabuf *db) +{ + struct page *map, *mapend; + + if (db->rawbuf) { + // Undo prog_dmabuf()'s marking the pages as reserved + mapend = + virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - + 1); + for (map = virt_to_page(db->rawbuf); map <= mapend; map++) + ClearPageReserved(map); + free_dmabuf(s, db); + } + if (s->tmpbuff && (db->type == CS_TYPE_ADC)) { + // Undo prog_dmabuf()'s marking the pages as reserved + mapend = + virt_to_page(s->tmpbuff + + (PAGE_SIZE << s->buforder_tmpbuff) - 1); + for (map = virt_to_page(s->tmpbuff); map <= mapend; map++) + ClearPageReserved(map); + free_dmabuf2(s, db); + } + s->tmpbuff = NULL; + db->rawbuf = NULL; + db->mapped = db->ready = 0; +} + +static int prog_dmabuf(struct cs4281_state *s, struct dmabuf *db) +{ + int order; + unsigned bytespersec, temp1; + unsigned bufs, sample_shift = 0; + struct page *map, *mapend; + unsigned long df; + + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: prog_dmabuf()+\n")); + db->hwptr = db->swptr = db->total_bytes = db->count = db->error = + db->endcleared = db->blocks = db->wakeup = db->underrun = 0; +/* +* check for order within limits, but do not overwrite value, check +* later for a fractional defaultorder (i.e. 100+). +*/ + if((defaultorder > 0) && (defaultorder < 12)) + df = defaultorder; + else + df = 1; + + if (!db->rawbuf) { + db->ready = db->mapped = 0; + for (order = df; order >= DMABUF_MINORDER; order--) + if ( (db->rawbuf = (void *) pci_alloc_consistent( + s->pcidev, PAGE_SIZE << order, &db-> dmaaddr))) + break; + if (!db->rawbuf) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs4281: prog_dmabuf(): unable to allocate rawbuf\n")); + return -ENOMEM; + } + db->buforder = order; + // Now mark the pages as reserved; otherwise the + // remap_pfn_range() in cs4281_mmap doesn't work. + // 1. get index to last page in mem_map array for rawbuf. + mapend = virt_to_page(db->rawbuf + + (PAGE_SIZE << db->buforder) - 1); + + // 2. mark each physical page in range as 'reserved'. + for (map = virt_to_page(db->rawbuf); map <= mapend; map++) + SetPageReserved(map); + } + if (!s->tmpbuff && (db->type == CS_TYPE_ADC)) { + for (order = df; order >= DMABUF_MINORDER; + order--) + if ( (s->tmpbuff = (void *) pci_alloc_consistent( + s->pcidev, PAGE_SIZE << order, + &s->dmaaddr_tmpbuff))) + break; + if (!s->tmpbuff) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs4281: prog_dmabuf(): unable to allocate tmpbuff\n")); + return -ENOMEM; + } + s->buforder_tmpbuff = order; + // Now mark the pages as reserved; otherwise the + // remap_pfn_range() in cs4281_mmap doesn't work. + // 1. get index to last page in mem_map array for rawbuf. + mapend = virt_to_page(s->tmpbuff + + (PAGE_SIZE << s->buforder_tmpbuff) - 1); + + // 2. mark each physical page in range as 'reserved'. + for (map = virt_to_page(s->tmpbuff); map <= mapend; map++) + SetPageReserved(map); + } + if (db->type == CS_TYPE_DAC) { + if (s->prop_dac.fmt & (AFMT_S16_LE | AFMT_U16_LE)) + sample_shift++; + if (s->prop_dac.channels > 1) + sample_shift++; + bytespersec = s->prop_dac.rate << sample_shift; + } else // CS_TYPE_ADC + { + if (s->prop_adc.fmt & (AFMT_S16_LE | AFMT_U16_LE)) + sample_shift++; + if (s->prop_adc.channels > 1) + sample_shift++; + bytespersec = s->prop_adc.rate << sample_shift; + } + bufs = PAGE_SIZE << db->buforder; + +/* +* 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. +*/ + if(defaultorder >= 100) + { + bufs = 1 << (defaultorder-100); + } + +#define INTERRUPT_RATE_MS 100 // Interrupt rate in milliseconds. + db->numfrag = 2; +/* +* Nominal frag size(bytes/interrupt) +*/ + temp1 = bytespersec / (1000 / INTERRUPT_RATE_MS); + db->fragshift = 8; // Min 256 bytes. + while (1 << db->fragshift < temp1) // Calc power of 2 frag size. + db->fragshift += 1; + db->fragsize = 1 << db->fragshift; + db->dmasize = db->fragsize * 2; + db->fragsamples = db->fragsize >> sample_shift; // # samples/fragment. + +// If the calculated size is larger than the allocated +// buffer, divide the allocated buffer into 2 fragments. + if (db->dmasize > bufs) { + + db->numfrag = 2; // Two fragments. + db->fragsize = bufs >> 1; // Each 1/2 the alloc'ed buffer. + db->fragsamples = db->fragsize >> sample_shift; // # samples/fragment. + db->dmasize = bufs; // Use all the alloc'ed buffer. + + db->fragshift = 0; // Calculate 'fragshift'. + temp1 = db->fragsize; // update_ptr() uses it + while ((temp1 >>= 1) > 1) // to calc 'total-bytes' + db->fragshift += 1; // returned in DSP_GETI/OPTR. + } + CS_DBGOUT(CS_PARMS, 3, printk(KERN_INFO + "cs4281: prog_dmabuf(): numfrag=%d fragsize=%d fragsamples=%d fragshift=%d bufs=%d fmt=0x%x ch=%d\n", + db->numfrag, db->fragsize, db->fragsamples, + db->fragshift, bufs, + (db->type == CS_TYPE_DAC) ? s->prop_dac.fmt : + s->prop_adc.fmt, + (db->type == CS_TYPE_DAC) ? s->prop_dac.channels : + s->prop_adc.channels)); + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: prog_dmabuf()-\n")); + return 0; +} + + +static int prog_dmabuf_adc(struct cs4281_state *s) +{ + unsigned long va; + unsigned count; + int c; + stop_adc(s); + s->dma_adc.type = CS_TYPE_ADC; + if ((c = prog_dmabuf(s, &s->dma_adc))) + return c; + + if (s->dma_adc.rawbuf) { + memset(s->dma_adc.rawbuf, + (s->prop_adc. + fmt & (AFMT_U8 | AFMT_U16_LE)) ? 0x80 : 0, + s->dma_adc.dmasize); + } + if (s->tmpbuff) { + memset(s->tmpbuff, + (s->prop_adc. + fmt & (AFMT_U8 | AFMT_U16_LE)) ? 0x80 : 0, + PAGE_SIZE << s->buforder_tmpbuff); + } + + va = virt_to_bus(s->dma_adc.rawbuf); + + count = s->dma_adc.dmasize; + + if (s->prop_adc. + fmt & (AFMT_S16_LE | AFMT_U16_LE | AFMT_S16_BE | AFMT_U16_BE)) + count /= 2; // 16-bit. + + if (s->prop_adc.channels > 1) + count /= 2; // Assume stereo. + + CS_DBGOUT(CS_WAVE_READ, 3, printk(KERN_INFO + "cs4281: prog_dmabuf_adc(): count=%d va=0x%.8x\n", + count, (unsigned) va)); + + writel(va, s->pBA0 + BA0_DBA1); // Set buffer start address. + writel(count - 1, s->pBA0 + BA0_DBC1); // Set count. + s->dma_adc.ready = 1; + return 0; +} + + +static int prog_dmabuf_dac(struct cs4281_state *s) +{ + unsigned long va; + unsigned count; + int c; + stop_dac(s); + s->dma_dac.type = CS_TYPE_DAC; + if ((c = prog_dmabuf(s, &s->dma_dac))) + return c; + memset(s->dma_dac.rawbuf, + (s->prop_dac.fmt & (AFMT_U8 | AFMT_U16_LE)) ? 0x80 : 0, + s->dma_dac.dmasize); + + va = virt_to_bus(s->dma_dac.rawbuf); + + count = s->dma_dac.dmasize; + if (s->prop_dac. + fmt & (AFMT_S16_LE | AFMT_U16_LE | AFMT_S16_BE | AFMT_U16_BE)) + count /= 2; // 16-bit. + + if (s->prop_dac.channels > 1) + count /= 2; // Assume stereo. + + writel(va, s->pBA0 + BA0_DBA0); // Set buffer start address. + writel(count - 1, s->pBA0 + BA0_DBC0); // Set count. + + CS_DBGOUT(CS_WAVE_WRITE, 3, printk(KERN_INFO + "cs4281: prog_dmabuf_dac(): count=%d va=0x%.8x\n", + count, (unsigned) va)); + + 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 + "cs4281: clear_advance(): memset %d at %p for %d size \n", + (unsigned)c, ((char *) buf) + bptr, len)); + memset(((char *) buf) + bptr, c, len); +} + + + +// call with spinlock held! +static void cs4281_update_ptr(struct cs4281_state *s, int intflag) +{ + int diff; + unsigned hwptr, va; + + // update ADC pointer + if (s->ena & FMODE_READ) { + hwptr = readl(s->pBA0 + BA0_DCA1); // Read capture DMA address. + va = virt_to_bus(s->dma_adc.rawbuf); + hwptr -= (unsigned) va; + diff = + (s->dma_adc.dmasize + hwptr - + s->dma_adc.hwptr) % s->dma_adc.dmasize; + s->dma_adc.hwptr = hwptr; + s->dma_adc.total_bytes += diff; + s->dma_adc.count += diff; + if (s->dma_adc.count > s->dma_adc.dmasize) + s->dma_adc.count = s->dma_adc.dmasize; + if (s->dma_adc.mapped) { + if (s->dma_adc.count >= + (signed) s->dma_adc.fragsize) wake_up(&s-> + dma_adc. + wait); + } else { + if (s->dma_adc.count > 0) + wake_up(&s->dma_adc.wait); + } + CS_DBGOUT(CS_PARMS, 8, printk(KERN_INFO + "cs4281: cs4281_update_ptr(): s=%p hwptr=%d total_bytes=%d count=%d \n", + s, s->dma_adc.hwptr, s->dma_adc.total_bytes, s->dma_adc.count)); + } + // 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) { + hwptr = readl(s->pBA0 + BA0_DCA0); // Read play DMA address. + va = virt_to_bus(s->dma_dac.rawbuf); + hwptr -= (unsigned) va; + diff = (s->dma_dac.dmasize + hwptr - + s->dma_dac.hwptr) % s->dma_dac.dmasize; + s->dma_dac.hwptr = hwptr; + s->dma_dac.total_bytes += diff; + if (s->dma_dac.mapped) { + s->dma_dac.count += diff; + if (s->dma_dac.count >= s->dma_dac.fragsize) { + s->dma_dac.wakeup = 1; + wake_up(&s->dma_dac.wait); + if (s->dma_dac.count > s->dma_dac.dmasize) + s->dma_dac.count &= + s->dma_dac.dmasize - 1; + } + } else { + s->dma_dac.count -= diff; + if (s->dma_dac.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 + "cs4281: cs4281_update_ptr(): memset %d at %p for %d size \n", + (unsigned)(s->prop_dac.fmt & + (AFMT_U8 | AFMT_U16_LE)) ? 0x80 : 0, + s->dma_dac.rawbuf, s->dma_dac.dmasize)); + memset(s->dma_dac.rawbuf, + (s->prop_dac. + fmt & (AFMT_U8 | AFMT_U16_LE)) ? + 0x80 : 0, s->dma_dac.dmasize); + if (s->dma_dac.count < 0) { + s->dma_dac.underrun = 1; + s->dma_dac.count = 0; + CS_DBGOUT(CS_ERROR, 9, printk(KERN_INFO + "cs4281: cs4281_update_ptr(): underrun\n")); + } + } else if (s->dma_dac.count <= + (signed) s->dma_dac.fragsize + && !s->dma_dac.endcleared) { + clear_advance(s->dma_dac.rawbuf, + s->dma_dac.dmasize, + s->dma_dac.swptr, + s->dma_dac.fragsize, + (s->prop_dac. + fmt & (AFMT_U8 | + AFMT_U16_LE)) ? 0x80 + : 0); + s->dma_dac.endcleared = 1; + } + if ( (s->dma_dac.count <= (signed) s->dma_dac.dmasize/2) || + intflag) + { + wake_up(&s->dma_dac.wait); + } + } + CS_DBGOUT(CS_PARMS, 8, printk(KERN_INFO + "cs4281: cs4281_update_ptr(): s=%p hwptr=%d total_bytes=%d count=%d \n", + s, s->dma_dac.hwptr, s->dma_dac.total_bytes, s->dma_dac.count)); + } +} + + +// --------------------------------------------------------------------- + +static void prog_codec(struct cs4281_state *s, unsigned type) +{ + unsigned long flags; + unsigned temp1, format; + + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: prog_codec()+ \n")); + + spin_lock_irqsave(&s->lock, flags); + if (type == CS_TYPE_ADC) { + temp1 = readl(s->pBA0 + BA0_DCR1); + writel(temp1 | DCRn_MSK, s->pBA0 + BA0_DCR1); // Stop capture DMA, if active. + + // program sampling rates + // Note, for CS4281, capture & play rates can be set independently. + cs4281_record_rate(s, s->prop_adc.rate); + + // program ADC parameters + format = DMRn_DMA | DMRn_AUTO | DMRn_TR_WRITE; + if (s->prop_adc. + fmt & (AFMT_S16_LE | AFMT_U16_LE | AFMT_S16_BE | AFMT_U16_BE)) { // 16-bit + if (s->prop_adc.fmt & (AFMT_S16_BE | AFMT_U16_BE)) // Big-endian? + format |= DMRn_BEND; + if (s->prop_adc.fmt & (AFMT_U16_LE | AFMT_U16_BE)) + format |= DMRn_USIGN; // Unsigned. + } else + format |= DMRn_SIZE8 | DMRn_USIGN; // 8-bit, unsigned + if (s->prop_adc.channels < 2) + format |= DMRn_MONO; + + writel(format, s->pBA0 + BA0_DMR1); + + CS_DBGOUT(CS_PARMS, 2, printk(KERN_INFO + "cs4281: prog_codec(): adc %s %s %s rate=%d DMR0 format=0x%.8x\n", + (format & DMRn_SIZE8) ? "8" : "16", + (format & DMRn_USIGN) ? "Unsigned" : "Signed", + (format & DMRn_MONO) ? "Mono" : "Stereo", + s->prop_adc.rate, format)); + + s->ena &= ~FMODE_READ; // not capturing data yet + } + + + if (type == CS_TYPE_DAC) { + temp1 = readl(s->pBA0 + BA0_DCR0); + writel(temp1 | DCRn_MSK, s->pBA0 + BA0_DCR0); // Stop play DMA, if active. + + // program sampling rates + // Note, for CS4281, capture & play rates can be set independently. + cs4281_play_rate(s, s->prop_dac.rate); + + // program DAC parameters + format = DMRn_DMA | DMRn_AUTO | DMRn_TR_READ; + if (s->prop_dac. + fmt & (AFMT_S16_LE | AFMT_U16_LE | AFMT_S16_BE | AFMT_U16_BE)) { // 16-bit + if (s->prop_dac.fmt & (AFMT_S16_BE | AFMT_U16_BE)) + format |= DMRn_BEND; // Big Endian. + if (s->prop_dac.fmt & (AFMT_U16_LE | AFMT_U16_BE)) + format |= DMRn_USIGN; // Unsigned. + } else + format |= DMRn_SIZE8 | DMRn_USIGN; // 8-bit, unsigned + + if (s->prop_dac.channels < 2) + format |= DMRn_MONO; + + writel(format, s->pBA0 + BA0_DMR0); + + + CS_DBGOUT(CS_PARMS, 2, printk(KERN_INFO + "cs4281: prog_codec(): dac %s %s %s rate=%d DMR0 format=0x%.8x\n", + (format & DMRn_SIZE8) ? "8" : "16", + (format & DMRn_USIGN) ? "Unsigned" : "Signed", + (format & DMRn_MONO) ? "Mono" : "Stereo", + s->prop_dac.rate, format)); + + s->ena &= ~FMODE_WRITE; // not capturing data yet + + } + spin_unlock_irqrestore(&s->lock, flags); + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: prog_codec()- \n")); +} + + +static int mixer_ioctl(struct cs4281_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 + }; + void __user *argp = (void __user *)arg; + + // 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[] = { + BA0_AC97_PCM_OUT_VOLUME, + BA0_AC97_AUX_VOLUME, + BA0_AC97_CD_VOLUME, + BA0_AC97_LINE_IN_VOLUME + }; + 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 + "cs4281: mixer_ioctl(): s=%p cmd=0x%.8x\n", 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) || + (cmd == SOUND_MIXER_CS_APM)) + { + switch (cmd) { + + case SOUND_MIXER_CS_GETDBGMASK: + return put_user(cs_debugmask, + (unsigned long __user *) argp); + + case SOUND_MIXER_CS_GETDBGLEVEL: + return put_user(cs_debuglevel, + (unsigned long __user *) argp); + + case SOUND_MIXER_CS_SETDBGMASK: + if (get_user(val, (unsigned long __user *) argp)) + return -EFAULT; + cs_debugmask = val; + return 0; + + case SOUND_MIXER_CS_SETDBGLEVEL: + if (get_user(val, (unsigned long __user *) argp)) + return -EFAULT; + cs_debuglevel = val; + return 0; +#ifndef NOT_CS4281_PM + case SOUND_MIXER_CS_APM: + if (get_user(val, (unsigned long __user *) argp)) + return -EFAULT; + if(val == CS_IOCTL_CMD_SUSPEND) + cs4281_suspend(s); + else if(val == CS_IOCTL_CMD_RESUME) + cs4281_resume(s); + else + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_INFO + "cs4281: mixer_ioctl(): invalid APM cmd (%d)\n", + val)); + } + return 0; +#endif + default: + CS_DBGOUT(CS_ERROR, 1, printk(KERN_INFO + "cs4281: mixer_ioctl(): ERROR unknown debug cmd\n")); + return 0; + } + } +#endif + + if (cmd == SOUND_MIXER_PRIVATE1) { + // enable/disable/query mixer preamp + if (get_user(val, (int __user *) argp)) + return -EFAULT; + if (val != -1) { + cs4281_read_ac97(s, BA0_AC97_MIC_VOLUME, &temp1); + temp1 = val ? (temp1 | 0x40) : (temp1 & 0xffbf); + cs4281_write_ac97(s, BA0_AC97_MIC_VOLUME, temp1); + } + cs4281_read_ac97(s, BA0_AC97_MIC_VOLUME, &temp1); + val = (temp1 & 0x40) ? 1 : 0; + return put_user(val, (int __user *) argp); + } + if (cmd == SOUND_MIXER_PRIVATE2) { + // enable/disable/query spatializer + if (get_user(val, (int __user *)argp)) + return -EFAULT; + if (val != -1) { + temp1 = (val & 0x3f) >> 2; + cs4281_write_ac97(s, BA0_AC97_3D_CONTROL, temp1); + cs4281_read_ac97(s, BA0_AC97_GENERAL_PURPOSE, + &temp1); + cs4281_write_ac97(s, BA0_AC97_GENERAL_PURPOSE, + temp1 | 0x2000); + } + cs4281_read_ac97(s, BA0_AC97_3D_CONTROL, &temp1); + return put_user((temp1 << 2) | 3, (int __user *)argp); + } + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + strlcpy(info.id, "CS4281", sizeof(info.id)); + strlcpy(info.name, "Crystal CS4281", sizeof(info.name)); + info.modify_counter = s->mix.modcnt; + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == SOUND_OLD_MIXER_INFO) { + _old_mixer_info info; + strlcpy(info.id, "CS4281", sizeof(info.id)); + strlcpy(info.name, "Crystal CS4281", sizeof(info.name)); + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, (int __user *) argp); + + 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 + cs4281_read_ac97(s, BA0_AC97_RECORD_SELECT, &temp1); + return put_user(mixer_src[temp1&7], (int __user *)argp); + + case SOUND_MIXER_DEVMASK: // Arg contains a bit for each supported device + return put_user(SOUND_MASK_PCM | SOUND_MASK_SYNTH | + SOUND_MASK_CD | SOUND_MASK_LINE | + SOUND_MASK_LINE1 | SOUND_MASK_MIC | + SOUND_MASK_VOLUME | + SOUND_MASK_RECLEV | + SOUND_MASK_SPEAKER, (int __user *)argp); + + case SOUND_MIXER_RECMASK: // Arg contains a bit for each supported recording source + return put_user(SOUND_MASK_LINE | SOUND_MASK_MIC | + SOUND_MASK_CD | SOUND_MASK_VOLUME | + SOUND_MASK_LINE1, (int __user *) argp); + + case SOUND_MIXER_STEREODEVS: // Mixer channels supporting stereo + return put_user(SOUND_MASK_PCM | SOUND_MASK_SYNTH | + SOUND_MASK_CD | SOUND_MASK_LINE | + SOUND_MASK_LINE1 | SOUND_MASK_MIC | + SOUND_MASK_VOLUME | + SOUND_MASK_RECLEV, (int __user *)argp); + + case SOUND_MIXER_CAPS: + return put_user(SOUND_CAP_EXCL_INPUT, (int __user *)argp); + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES + || !(vidx = mixtable1[i])) + return -EINVAL; + return put_user(s->mix.vol[vidx - 1], (int __user *)argp); + } + } + // 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 __user *)argp)) + 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; + cs4281_write_ac97(s, + BA0_AC97_RECORD_SELECT, + temp1); + return 0; + } + } + return 0; + + case SOUND_MIXER_VOLUME: + if (get_user(val, (int __user *)argp)) + 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; + + cs4281_write_ac97(s, BA0_AC97_MASTER_VOLUME, temp1); + cs4281_write_ac97(s, BA0_AC97_HEADPHONE_VOLUME, 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 __user *)argp); + + case SOUND_MIXER_SPEAKER: + if (get_user(val, (int __user *)argp)) + 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; + cs4281_write_ac97(s, BA0_AC97_PC_BEEP_VOLUME, 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 __user *)argp); + + case SOUND_MIXER_RECLEV: + if (get_user(val, (int __user *)argp)) + 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; + cs4281_write_ac97(s, BA0_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 __user *)argp); + + case SOUND_MIXER_MIC: + if (get_user(val, (int __user *)argp)) + 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; + } + cs4281_read_ac97(s, BA0_AC97_MIC_VOLUME, &temp1); + temp1 &= 0x40; // Isolate 20db gain bit. + if (rl < 3) { + temp1 |= 0x8000; + rl = 0; + } + rl = 31 - rl; // Convert volume to attenuation. + temp1 |= rl; + cs4281_write_ac97(s, BA0_AC97_MIC_VOLUME, 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 __user *)argp); + + + case SOUND_MIXER_SYNTH: + if (get_user(val, (int __user *)argp)) + return -EFAULT; + l = val & 0xff; + if (l > 100) + l = 100; + if (get_user(val, (int __user *)argp)) + 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 + BA0_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 + BA0_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 __user *)argp); + + + default: + CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO + "cs4281: mixer_ioctl(): default\n")); + + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES || !(vidx = mixtable1[i])) + return -EINVAL; + if (get_user(val, (int __user *)argp)) + 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; + cs4281_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 +#ifndef NOT_CS4281_PM + CS_DBGOUT(CS_PM, 9, printk(KERN_INFO + "write ac97 mixreg[%d]=0x%x mix.vol[]=0x%x\n", + vidx-1,temp1,s->mix.vol[vidx-1])); +#endif + return put_user(s->mix.vol[vidx - 1], (int __user *)argp); + } +} + + +// --------------------------------------------------------------------- + +static int cs4281_open_mixdev(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct cs4281_state *s=NULL; + struct list_head *entry; + + CS_DBGOUT(CS_FUNCTION | CS_OPEN, 4, + printk(KERN_INFO "cs4281: cs4281_open_mixdev()+\n")); + + list_for_each(entry, &cs4281_devs) + { + s = list_entry(entry, struct cs4281_state, list); + if(s->dev_mixer == minor) + break; + } + if (!s) + { + CS_DBGOUT(CS_FUNCTION | CS_OPEN | CS_ERROR, 2, + printk(KERN_INFO "cs4281: cs4281_open_mixdev()- -ENODEV\n")); + return -ENODEV; + } + VALIDATE_STATE(s); + file->private_data = s; + + CS_DBGOUT(CS_FUNCTION | CS_OPEN, 4, + printk(KERN_INFO "cs4281: cs4281_open_mixdev()- 0\n")); + + return nonseekable_open(inode, file); +} + + +static int cs4281_release_mixdev(struct inode *inode, struct file *file) +{ + struct cs4281_state *s = + (struct cs4281_state *) file->private_data; + + VALIDATE_STATE(s); + return 0; +} + + +static int cs4281_ioctl_mixdev(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return mixer_ioctl((struct cs4281_state *) file->private_data, cmd, + arg); +} + + +// ****************************************************************************************** +// Mixer file operations struct. +// ****************************************************************************************** +static /*const */ struct file_operations cs4281_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = cs4281_ioctl_mixdev, + .open = cs4281_open_mixdev, + .release = cs4281_release_mixdev, +}; + +// --------------------------------------------------------------------- + + +static int drain_adc(struct cs4281_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int count; + unsigned tmo; + + if (s->dma_adc.mapped) + return 0; + add_wait_queue(&s->dma_adc.wait, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->dma_adc.count; + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: drain_adc() %d\n", count)); + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) { + CS_DBGOUT(CS_FUNCTION, 2, printk(KERN_INFO + "cs4281: drain_adc() count<0\n")); + break; + } + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_adc.wait, &wait); + current->state = TASK_RUNNING; + return -EBUSY; + } + tmo = + 3 * HZ * (count + + s->dma_adc.fragsize) / 2 / s->prop_adc.rate; + if (s->prop_adc.fmt & (AFMT_S16_LE | AFMT_U16_LE)) + tmo >>= 1; + if (s->prop_adc.channels > 1) + tmo >>= 1; + if (!schedule_timeout(tmo + 1)) + printk(KERN_DEBUG "cs4281: dma timed out??\n"); + } + remove_wait_queue(&s->dma_adc.wait, &wait); + current->state = TASK_RUNNING; + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +static int drain_dac(struct cs4281_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int count; + unsigned tmo; + + if (s->dma_dac.mapped) + return 0; + add_wait_queue(&s->dma_dac.wait, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_dac.wait, &wait); + current->state = TASK_RUNNING; + return -EBUSY; + } + tmo = + 3 * HZ * (count + + s->dma_dac.fragsize) / 2 / s->prop_dac.rate; + if (s->prop_dac.fmt & (AFMT_S16_LE | AFMT_U16_LE)) + tmo >>= 1; + if (s->prop_dac.channels > 1) + tmo >>= 1; + if (!schedule_timeout(tmo + 1)) + printk(KERN_DEBUG "cs4281: dma timed out??\n"); + } + remove_wait_queue(&s->dma_dac.wait, &wait); + current->state = TASK_RUNNING; + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +//**************************************************************************** +// +// CopySamples copies 16-bit stereo samples from the source to the +// destination, possibly converting down to either 8-bit or mono or both. +// count specifies the number of output bytes to write. +// +// Arguments: +// +// dst - Pointer to a destination buffer. +// src - Pointer to a source buffer +// count - The number of bytes to copy into the destination buffer. +// iChannels - Stereo - 2 +// Mono - 1 +// fmt - AFMT_xxx (soundcard.h formats) +// +// NOTES: only call this routine for conversion to 8bit from 16bit +// +//**************************************************************************** +static void CopySamples(char *dst, char *src, int count, int iChannels, + unsigned fmt) +{ + + unsigned short *psSrc; + long lAudioSample; + + CS_DBGOUT(CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: CopySamples()+ ")); + CS_DBGOUT(CS_WAVE_READ, 8, printk(KERN_INFO + " dst=%p src=%p count=%d iChannels=%d fmt=0x%x\n", + dst, src, (unsigned) count, (unsigned) iChannels, (unsigned) fmt)); + + // Gershwin does format conversion in hardware so normally + // we don't do any host based coversion. The data formatter + // truncates 16 bit data to 8 bit and that causes some hiss. + // We have already forced the HW to do 16 bit sampling and + // 2 channel so that we can use software to round instead + // of truncate + + // + // See if the data should be output as 8-bit unsigned stereo. + // or if the data should be output at 8-bit unsigned mono. + // + if ( ((iChannels == 2) && (fmt & AFMT_U8)) || + ((iChannels == 1) && (fmt & AFMT_U8)) ) { + // + // Convert each 16-bit unsigned stereo sample to 8-bit unsigned + // stereo using rounding. + // + psSrc = (unsigned short *) src; + count = count / 2; + while (count--) { + lAudioSample = (long) psSrc[count] + (long) 0x80; + if (lAudioSample > 0xffff) { + lAudioSample = 0xffff; + } + dst[count] = (char) (lAudioSample >> 8); + } + } + // + // check for 8-bit signed stereo. + // + else if ((iChannels == 2) && (fmt & AFMT_S8)) { + // + // Convert each 16-bit stereo sample to 8-bit stereo using rounding. + // + psSrc = (short *) src; + while (count--) { + lAudioSample = + (((long) psSrc[0] + (long) psSrc[1]) / 2); + psSrc += 2; + *dst++ = (char) ((short) lAudioSample >> 8); + } + } + // + // Otherwise, the data should be output as 8-bit signed mono. + // + else if ((iChannels == 1) && (fmt & AFMT_S8)) { + // + // Convert each 16-bit signed mono sample to 8-bit signed mono + // using rounding. + // + psSrc = (short *) src; + count = count / 2; + while (count--) { + lAudioSample = + (((long) psSrc[0] + (long) psSrc[1]) / 2); + if (lAudioSample > 0x7fff) { + lAudioSample = 0x7fff; + } + psSrc += 2; + *dst++ = (char) ((short) lAudioSample >> 8); + } + } +} + +// +// cs_copy_to_user() +// replacement for the standard copy_to_user, to allow for a conversion from +// 16 bit to 8 bit if the record conversion is active. the cs4281 has some +// issues with 8 bit capture, so the driver always captures data in 16 bit +// and then if the user requested 8 bit, converts from 16 to 8 bit. +// +static unsigned cs_copy_to_user(struct cs4281_state *s, void __user *dest, + unsigned *hwsrc, unsigned cnt, + unsigned *copied) +{ + void *src = hwsrc; //default to the standard destination buffer addr + + CS_DBGOUT(CS_FUNCTION, 6, printk(KERN_INFO + "cs_copy_to_user()+ fmt=0x%x fmt_o=0x%x cnt=%d dest=%p\n", + s->prop_adc.fmt, s->prop_adc.fmt_original, + (unsigned) cnt, dest)); + + if (cnt > s->dma_adc.dmasize) { + cnt = s->dma_adc.dmasize; + } + if (!cnt) { + *copied = 0; + return 0; + } + if (s->conversion) { + if (!s->tmpbuff) { + *copied = cnt / 2; + return 0; + } + CopySamples(s->tmpbuff, (void *) hwsrc, cnt, + (unsigned) s->prop_adc.channels, + s->prop_adc.fmt_original); + src = s->tmpbuff; + cnt = cnt / 2; + } + + if (copy_to_user(dest, src, cnt)) { + *copied = 0; + return -EFAULT; + } + *copied = cnt; + CS_DBGOUT(CS_FUNCTION, 2, printk(KERN_INFO + "cs4281: cs_copy_to_user()- copied bytes is %d \n", cnt)); + return 0; +} + +// --------------------------------------------------------------------- + +static ssize_t cs4281_read(struct file *file, char __user *buffer, size_t count, + loff_t * ppos) +{ + struct cs4281_state *s = + (struct cs4281_state *) file->private_data; + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + unsigned copied = 0; + + CS_DBGOUT(CS_FUNCTION | CS_WAVE_READ, 2, + printk(KERN_INFO "cs4281: cs4281_read()+ %Zu \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=%Zu .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); + + // get the current copy point of the sw buffer + swptr = s->dma_adc.swptr; + + // cnt is the amount of unread bytes from the end of the + // hw buffer to the current sw pointer + cnt = s->dma_adc.dmasize - swptr; + + // 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 (s->dma_adc.count < cnt) + cnt = s->dma_adc.count; + 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 > (count * 2)) + cnt = (count * 2); + } else { + if (cnt > count) + cnt = 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 rawbuf+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=%Zu ", cnt, count)); + CS_DBGOUT(CS_WAVE_READ, 8, printk(KERN_INFO + " .dmasize=%d .count=%d buffer=%p ret=%Zd\n", + s->dma_adc.dmasize, s->dma_adc.count, buffer, ret)); + + if (cs_copy_to_user + (s, buffer, s->dma_adc.rawbuf + swptr, cnt, &copied)) + return ret ? ret : -EFAULT; + swptr = (swptr + cnt) % s->dma_adc.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_adc.swptr = swptr; + s->dma_adc.count -= cnt; + 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 "cs4281: cs4281_read()- %Zd\n", ret)); + return ret; +} + + +static ssize_t cs4281_write(struct file *file, const char __user *buffer, + size_t count, loff_t * ppos) +{ + struct cs4281_state *s = + (struct cs4281_state *) file->private_data; + ssize_t ret; + unsigned long flags; + unsigned swptr, hwptr, busaddr; + int cnt; + + CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE, 2, + printk(KERN_INFO "cs4281: cs4281_write()+ count=%Zu\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) { + spin_lock_irqsave(&s->lock, flags); + if (s->dma_dac.count < 0) { + s->dma_dac.count = 0; + s->dma_dac.swptr = s->dma_dac.hwptr; + } + if (s->dma_dac.underrun) { + s->dma_dac.underrun = 0; + hwptr = readl(s->pBA0 + BA0_DCA0); + busaddr = virt_to_bus(s->dma_dac.rawbuf); + hwptr -= (unsigned) busaddr; + s->dma_dac.swptr = s->dma_dac.hwptr = hwptr; + } + swptr = s->dma_dac.swptr; + cnt = s->dma_dac.dmasize - swptr; + if (s->dma_dac.count + cnt > s->dma_dac.dmasize) + cnt = s->dma_dac.dmasize - s->dma_dac.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(&s->dma_dac.wait); + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + continue; + } + if (copy_from_user(s->dma_dac.rawbuf + swptr, buffer, cnt)) + return ret ? ret : -EFAULT; + swptr = (swptr + cnt) % s->dma_dac.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_dac.swptr = swptr; + s->dma_dac.count += cnt; + s->dma_dac.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 "cs4281: cs4281_write()- %Zd\n", ret)); + return ret; +} + + +static unsigned int cs4281_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct cs4281_state *s = + (struct cs4281_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 "cs4281: cs4281_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 + "cs4281: cs4281_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 + "cs4281: cs4281_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); + cs4281_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.dmasize/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 "cs4281: cs4281_poll()- 0x%.8x\n", + mask)); + return mask; +} + + +static int cs4281_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct cs4281_state *s = + (struct cs4281_state *) file->private_data; + struct dmabuf *db; + int ret; + unsigned long size; + + CS_DBGOUT(CS_FUNCTION | CS_PARMS | CS_OPEN, 4, + printk(KERN_INFO "cs4281: cs4281_mmap()+\n")); + + VALIDATE_STATE(s); + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf_dac(s)) != 0) + return ret; + db = &s->dma_dac; + } else if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf_adc(s)) != 0) + return ret; + db = &s->dma_adc; + } else + return -EINVAL; +// +// only support PLAYBACK for now +// + db = &s->dma_dac; + + if (cs4x_pgoff(vma) != 0) + return -EINVAL; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << db->buforder)) + return -EINVAL; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(db->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + return -EAGAIN; + db->mapped = 1; + + CS_DBGOUT(CS_FUNCTION | CS_PARMS | CS_OPEN, 4, + printk(KERN_INFO "cs4281: cs4281_mmap()- 0 size=%d\n", + (unsigned) size)); + + return 0; +} + + +static int cs4281_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct cs4281_state *s = + (struct cs4281_state *) file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int val, mapped, ret; + int __user *p = (int __user *)arg; + + CS_DBGOUT(CS_FUNCTION, 4, printk(KERN_INFO + "cs4281: cs4281_ioctl(): file=%p cmd=0x%.8x\n", 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 + "cs4281: cs4281_ioctl(): SOUND_VERSION=0x%.8x\n", + SOUND_VERSION)); + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_SYNC: + CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO + "cs4281: cs4281_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, + p); + + case SNDCTL_DSP_RESET: + CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO + "cs4281: cs4281_ioctl(): DSP_RESET\n")); + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + synchronize_irq(s->irq); + s->dma_dac.swptr = s->dma_dac.hwptr = + s->dma_dac.count = s->dma_dac.total_bytes = + s->dma_dac.blocks = s->dma_dac.wakeup = 0; + prog_codec(s, CS_TYPE_DAC); + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(s->irq); + s->dma_adc.swptr = s->dma_adc.hwptr = + s->dma_adc.count = s->dma_adc.total_bytes = + s->dma_adc.blocks = s->dma_dac.wakeup = 0; + prog_codec(s, CS_TYPE_ADC); + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO + "cs4281: cs4281_ioctl(): DSP_SPEED val=%d\n", val)); + // + // support independent capture and playback channels + // assume that the file mode bit determines the + // direction of the data flow. + // + if (file->f_mode & FMODE_READ) { + if (val >= 0) { + stop_adc(s); + s->dma_adc.ready = 0; + // program sampling rates + if (val > 48000) + val = 48000; + if (val < 6300) + val = 6300; + s->prop_adc.rate = val; + prog_codec(s, CS_TYPE_ADC); + } + } + if (file->f_mode & FMODE_WRITE) { + if (val >= 0) { + stop_dac(s); + s->dma_dac.ready = 0; + // program sampling rates + if (val > 48000) + val = 48000; + if (val < 6300) + val = 6300; + s->prop_dac.rate = val; + prog_codec(s, CS_TYPE_DAC); + } + } + + if (file->f_mode & FMODE_WRITE) + val = s->prop_dac.rate; + else if (file->f_mode & FMODE_READ) + val = s->prop_adc.rate; + + return put_user(val, p); + + case SNDCTL_DSP_STEREO: + if (get_user(val, p)) + return -EFAULT; + CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO + "cs4281: cs4281_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; + prog_codec(s, CS_TYPE_ADC); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + s->prop_dac.channels = val ? 2 : 1; + prog_codec(s, CS_TYPE_DAC); + } + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO + "cs4281: cs4281_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; + prog_codec(s, CS_TYPE_ADC); + } + 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; + prog_codec(s, CS_TYPE_DAC); + } + } + + 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, p); + + case SNDCTL_DSP_GETFMTS: // Returns a mask + CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO + "cs4281: cs4281_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, p); + + case SNDCTL_DSP_SETFMT: + if (get_user(val, p)) + return -EFAULT; + CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO + "cs4281: cs4281_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; + prog_codec(s, CS_TYPE_ADC); + } + 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; + prog_codec(s, CS_TYPE_DAC); + } + } 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 + "cs4281: cs4281_ioctl(): DSP_SETFMT return val=0x%.8x\n", + val)); + return put_user(val, p); + + case SNDCTL_DSP_POST: + CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO + "cs4281: cs4281_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, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + 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); + cs4281_update_ptr(s,CS_FALSE); + abinfo.fragsize = s->dma_dac.fragsize; + if (s->dma_dac.mapped) + abinfo.bytes = s->dma_dac.dmasize; + else + abinfo.bytes = + s->dma_dac.dmasize - 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 + "cs4281: cs4281_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(p, &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); + cs4281_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(p, &abinfo, + sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + 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); + cs4281_update_ptr(s,CS_FALSE); + val = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + return put_user(val, p); + + 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); + cs4281_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); + if (copy_to_user(p, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 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); + cs4281_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); + if (copy_to_user(p, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 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, p); + } + if ((val = prog_dmabuf_adc(s))) + return val; + if (s->conversion) + return put_user(s->dma_adc.fragsize / 2, p); + else + return put_user(s->dma_adc.fragsize, p); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + 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, p)) + 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, p); + else if (file->f_mode & FMODE_WRITE) + return put_user(s->prop_dac.rate, p); + + case SOUND_PCM_READ_CHANNELS: + if (file->f_mode & FMODE_READ) + return put_user(s->prop_adc.channels, p); + else if (file->f_mode & FMODE_WRITE) + return put_user(s->prop_dac.channels, p); + + case SOUND_PCM_READ_BITS: + if (file->f_mode & FMODE_READ) + return + put_user( + (s->prop_adc. + fmt & (AFMT_S8 | AFMT_U8)) ? 8 : 16, + p); + else if (file->f_mode & FMODE_WRITE) + return + put_user( + (s->prop_dac. + fmt & (AFMT_S8 | AFMT_U8)) ? 8 : 16, + p); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + } + return mixer_ioctl(s, cmd, arg); +} + + +static int cs4281_release(struct inode *inode, struct file *file) +{ + struct cs4281_state *s = + (struct cs4281_state *) file->private_data; + + CS_DBGOUT(CS_FUNCTION | CS_RELEASE, 2, printk(KERN_INFO + "cs4281: cs4281_release(): inode=%p file=%p f_mode=%d\n", + inode, file, file->f_mode)); + + VALIDATE_STATE(s); + + if (file->f_mode & FMODE_WRITE) { + drain_dac(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem_dac); + stop_dac(s); + dealloc_dmabuf(s, &s->dma_dac); + s->open_mode &= ~FMODE_WRITE; + up(&s->open_sem_dac); + wake_up(&s->open_wait_dac); + } + if (file->f_mode & FMODE_READ) { + drain_adc(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem_adc); + stop_adc(s); + dealloc_dmabuf(s, &s->dma_adc); + s->open_mode &= ~FMODE_READ; + up(&s->open_sem_adc); + wake_up(&s->open_wait_adc); + } + return 0; +} + +static int cs4281_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct cs4281_state *s=NULL; + struct list_head *entry; + + CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, printk(KERN_INFO + "cs4281: cs4281_open(): inode=%p file=%p f_mode=0x%x\n", + inode, file, file->f_mode)); + + list_for_each(entry, &cs4281_devs) + { + s = list_entry(entry, struct cs4281_state, list); + + if (!((s->dev_audio ^ minor) & ~0xf)) + break; + } + if (entry == &cs4281_devs) + return -ENODEV; + if (!s) { + CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, printk(KERN_INFO + "cs4281: cs4281_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 + "cs4281: cs4281_open(): Error - must open READ and/or WRITE\n")); + return -ENODEV; + } + if (file->f_mode & FMODE_WRITE) { + down(&s->open_sem_dac); + while (s->open_mode & FMODE_WRITE) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem_dac); + return -EBUSY; + } + up(&s->open_sem_dac); + interruptible_sleep_on(&s->open_wait_dac); + + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem_dac); + } + } + if (file->f_mode & FMODE_READ) { + down(&s->open_sem_adc); + while (s->open_mode & FMODE_READ) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem_adc); + return -EBUSY; + } + up(&s->open_sem_adc); + interruptible_sleep_on(&s->open_wait_adc); + + if (signal_pending(current)) + return -ERESTARTSYS; + down(&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_U8; + s->prop_adc.fmt_original = s->prop_adc.fmt; + s->prop_adc.channels = 1; + s->prop_adc.rate = 8000; + s->prop_adc.clkdiv = 96 | 0x80; + s->conversion = 0; + s->ena &= ~FMODE_READ; + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = + s->dma_adc.subdivision = 0; + up(&s->open_sem_adc); + + if (prog_dmabuf_adc(s)) { + CS_DBGOUT(CS_OPEN | CS_ERROR, 2, printk(KERN_ERR + "cs4281: adc Program dmabufs failed.\n")); + cs4281_release(inode, file); + return -ENOMEM; + } + prog_codec(s, CS_TYPE_ADC); + } + if (file->f_mode & FMODE_WRITE) { + s->prop_dac.fmt = AFMT_U8; + s->prop_dac.fmt_original = s->prop_dac.fmt; + s->prop_dac.channels = 1; + s->prop_dac.rate = 8000; + s->prop_dac.clkdiv = 96 | 0x80; + s->conversion = 0; + s->ena &= ~FMODE_WRITE; + s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = + s->dma_dac.subdivision = 0; + up(&s->open_sem_dac); + + if (prog_dmabuf_dac(s)) { + CS_DBGOUT(CS_OPEN | CS_ERROR, 2, printk(KERN_ERR + "cs4281: dac Program dmabufs failed.\n")); + cs4281_release(inode, file); + return -ENOMEM; + } + prog_codec(s, CS_TYPE_DAC); + } + CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, + printk(KERN_INFO "cs4281: cs4281_open()- 0\n")); + return nonseekable_open(inode, file); +} + + +// ****************************************************************************************** +// Wave (audio) file operations struct. +// ****************************************************************************************** +static /*const */ struct file_operations cs4281_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = cs4281_read, + .write = cs4281_write, + .poll = cs4281_poll, + .ioctl = cs4281_ioctl, + .mmap = cs4281_mmap, + .open = cs4281_open, + .release = cs4281_release, +}; + +// --------------------------------------------------------------------- + +// hold spinlock for the following! +static void cs4281_handle_midi(struct cs4281_state *s) +{ + unsigned char ch; + int wake; + unsigned temp1; + + wake = 0; + while (!(readl(s->pBA0 + BA0_MIDSR) & 0x80)) { + ch = readl(s->pBA0 + BA0_MIDRP); + if (s->midi.icnt < MIDIINBUF) { + s->midi.ibuf[s->midi.iwr] = ch; + s->midi.iwr = (s->midi.iwr + 1) % MIDIINBUF; + s->midi.icnt++; + } + wake = 1; + } + if (wake) + wake_up(&s->midi.iwait); + wake = 0; + while (!(readl(s->pBA0 + BA0_MIDSR) & 0x40) && s->midi.ocnt > 0) { + temp1 = (s->midi.obuf[s->midi.ord]) & 0x000000ff; + writel(temp1, s->pBA0 + BA0_MIDWP); + s->midi.ord = (s->midi.ord + 1) % MIDIOUTBUF; + s->midi.ocnt--; + if (s->midi.ocnt < MIDIOUTBUF - 16) + wake = 1; + } + if (wake) + wake_up(&s->midi.owait); +} + + + +static irqreturn_t cs4281_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct cs4281_state *s = (struct cs4281_state *) dev_id; + unsigned int temp1; + + // fastpath out, to ease interrupt sharing + temp1 = readl(s->pBA0 + BA0_HISR); // Get Int Status reg. + + CS_DBGOUT(CS_INTERRUPT, 6, printk(KERN_INFO + "cs4281: cs4281_interrupt() BA0_HISR=0x%.8x\n", temp1)); +/* +* If not DMA or MIDI interrupt, then just return. +*/ + if (!(temp1 & (HISR_DMA0 | HISR_DMA1 | HISR_MIDI))) { + writel(HICR_IEV | HICR_CHGM, s->pBA0 + BA0_HICR); + CS_DBGOUT(CS_INTERRUPT, 9, printk(KERN_INFO + "cs4281: cs4281_interrupt(): returning not cs4281 interrupt.\n")); + return IRQ_NONE; + } + + if (temp1 & HISR_DMA0) // If play interrupt, + readl(s->pBA0 + BA0_HDSR0); // clear the source. + + if (temp1 & HISR_DMA1) // Same for play. + readl(s->pBA0 + BA0_HDSR1); + writel(HICR_IEV | HICR_CHGM, s->pBA0 + BA0_HICR); // Local EOI + + spin_lock(&s->lock); + cs4281_update_ptr(s,CS_TRUE); + cs4281_handle_midi(s); + spin_unlock(&s->lock); + return IRQ_HANDLED; +} + +// ************************************************************************** + +static void cs4281_midi_timer(unsigned long data) +{ + struct cs4281_state *s = (struct cs4281_state *) data; + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + cs4281_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + s->midi.timer.expires = jiffies + 1; + add_timer(&s->midi.timer); +} + + +// --------------------------------------------------------------------- + +static ssize_t cs4281_midi_read(struct file *file, char __user *buffer, + size_t count, loff_t * ppos) +{ + struct cs4281_state *s = + (struct cs4281_state *) file->private_data; + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.ird; + cnt = MIDIINBUF - ptr; + if (s->midi.icnt < cnt) + cnt = s->midi.icnt; + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) + return ret ? ret : -EAGAIN; + interruptible_sleep_on(&s->midi.iwait); + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + continue; + } + if (copy_to_user(buffer, s->midi.ibuf + ptr, cnt)) + return ret ? ret : -EFAULT; + ptr = (ptr + cnt) % MIDIINBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.ird = ptr; + s->midi.icnt -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + } + return ret; +} + + +static ssize_t cs4281_midi_write(struct file *file, const char __user *buffer, + size_t count, loff_t * ppos) +{ + struct cs4281_state *s = + (struct cs4281_state *) file->private_data; + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.owr; + cnt = MIDIOUTBUF - ptr; + if (s->midi.ocnt + cnt > MIDIOUTBUF) + cnt = MIDIOUTBUF - s->midi.ocnt; + if (cnt <= 0) + cs4281_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) + return ret ? ret : -EAGAIN; + interruptible_sleep_on(&s->midi.owait); + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + continue; + } + if (copy_from_user(s->midi.obuf + ptr, buffer, cnt)) + return ret ? ret : -EFAULT; + ptr = (ptr + cnt) % MIDIOUTBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.owr = ptr; + s->midi.ocnt += cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + spin_lock_irqsave(&s->lock, flags); + cs4281_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + } + return ret; +} + + +static unsigned int cs4281_midi_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct cs4281_state *s = + (struct cs4281_state *) file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_flags & FMODE_WRITE) + poll_wait(file, &s->midi.owait, wait); + if (file->f_flags & FMODE_READ) + poll_wait(file, &s->midi.iwait, wait); + spin_lock_irqsave(&s->lock, flags); + if (file->f_flags & FMODE_READ) { + if (s->midi.icnt > 0) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_flags & FMODE_WRITE) { + if (s->midi.ocnt < MIDIOUTBUF) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + + +static int cs4281_midi_open(struct inode *inode, struct file *file) +{ + unsigned long flags, temp1; + unsigned int minor = iminor(inode); + struct cs4281_state *s=NULL; + struct list_head *entry; + list_for_each(entry, &cs4281_devs) + { + s = list_entry(entry, struct cs4281_state, list); + + if (s->dev_midi == minor) + break; + } + + if (entry == &cs4281_devs) + return -ENODEV; + if (!s) + { + CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, printk(KERN_INFO + "cs4281: cs4281_open(): Error - unable to find audio state struct\n")); + return -ENODEV; + } + VALIDATE_STATE(s); + file->private_data = s; + // wait for device to become free + down(&s->open_sem); + while (s->open_mode & (file->f_mode << FMODE_MIDI_SHIFT)) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + up(&s->open_sem); + interruptible_sleep_on(&s->open_wait); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + writel(1, s->pBA0 + BA0_MIDCR); // Reset the interface. + writel(0, s->pBA0 + BA0_MIDCR); // Return to normal mode. + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + writel(0x0000000f, s->pBA0 + BA0_MIDCR); // Enable transmit, record, ints. + temp1 = readl(s->pBA0 + BA0_HIMR); + writel(temp1 & 0xffbfffff, s->pBA0 + BA0_HIMR); // Enable midi int. recognition. + writel(HICR_IEV | HICR_CHGM, s->pBA0 + BA0_HICR); // Enable interrupts + init_timer(&s->midi.timer); + s->midi.timer.expires = jiffies + 1; + s->midi.timer.data = (unsigned long) s; + s->midi.timer.function = cs4281_midi_timer; + add_timer(&s->midi.timer); + } + if (file->f_mode & FMODE_READ) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + } + if (file->f_mode & FMODE_WRITE) { + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + } + spin_unlock_irqrestore(&s->lock, flags); + s->open_mode |= + (file-> + f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ | + FMODE_MIDI_WRITE); + up(&s->open_sem); + return nonseekable_open(inode, file); +} + + +static int cs4281_midi_release(struct inode *inode, struct file *file) +{ + struct cs4281_state *s = + (struct cs4281_state *) file->private_data; + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + unsigned count, tmo; + + VALIDATE_STATE(s); + + if (file->f_mode & FMODE_WRITE) { + add_wait_queue(&s->midi.owait, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->midi.ocnt; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (file->f_flags & O_NONBLOCK) { + remove_wait_queue(&s->midi.owait, &wait); + current->state = TASK_RUNNING; + return -EBUSY; + } + tmo = (count * HZ) / 3100; + if (!schedule_timeout(tmo ? : 1) && tmo) + printk(KERN_DEBUG + "cs4281: midi timed out??\n"); + } + remove_wait_queue(&s->midi.owait, &wait); + current->state = TASK_RUNNING; + } + down(&s->open_sem); + s->open_mode &= + (~(file->f_mode << FMODE_MIDI_SHIFT)) & (FMODE_MIDI_READ | + FMODE_MIDI_WRITE); + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + writel(0, s->pBA0 + BA0_MIDCR); // Disable Midi interrupts. + del_timer(&s->midi.timer); + } + spin_unlock_irqrestore(&s->lock, flags); + up(&s->open_sem); + wake_up(&s->open_wait); + return 0; +} + +// ****************************************************************************************** +// Midi file operations struct. +// ****************************************************************************************** +static /*const */ struct file_operations cs4281_midi_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = cs4281_midi_read, + .write = cs4281_midi_write, + .poll = cs4281_midi_poll, + .open = cs4281_midi_open, + .release = cs4281_midi_release, +}; + + +// --------------------------------------------------------------------- + +// maximum number of devices +#define NR_DEVICE 8 // Only eight devices supported currently. + +// --------------------------------------------------------------------- + +static struct initvol { + int mixch; + int vol; +} initvol[] __devinitdata = { + + { + 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} +}; + + +#ifndef NOT_CS4281_PM +static void __devinit cs4281_BuildFIFO( + struct cs4281_pipeline *p, + struct cs4281_state *s) +{ + switch(p->number) + { + case 0: /* playback */ + { + p->u32FCRnAddress = BA0_FCR0; + p->u32FSICnAddress = BA0_FSIC0; + p->u32FPDRnAddress = BA0_FPDR0; + break; + } + case 1: /* capture */ + { + p->u32FCRnAddress = BA0_FCR1; + p->u32FSICnAddress = BA0_FSIC1; + p->u32FPDRnAddress = BA0_FPDR1; + break; + } + + case 2: + { + p->u32FCRnAddress = BA0_FCR2; + p->u32FSICnAddress = BA0_FSIC2; + p->u32FPDRnAddress = BA0_FPDR2; + break; + } + case 3: + { + p->u32FCRnAddress = BA0_FCR3; + p->u32FSICnAddress = BA0_FSIC3; + p->u32FPDRnAddress = BA0_FPDR3; + break; + } + default: + break; + } + // + // first read the hardware to initialize the member variables + // + p->u32FCRnValue = readl(s->pBA0 + p->u32FCRnAddress); + p->u32FSICnValue = readl(s->pBA0 + p->u32FSICnAddress); + p->u32FPDRnValue = readl(s->pBA0 + p->u32FPDRnAddress); + +} + +static void __devinit cs4281_BuildDMAengine( + struct cs4281_pipeline *p, + struct cs4281_state *s) +{ +/* +* initialize all the addresses of this pipeline dma info. +*/ + switch(p->number) + { + case 0: /* playback */ + { + p->u32DBAnAddress = BA0_DBA0; + p->u32DCAnAddress = BA0_DCA0; + p->u32DBCnAddress = BA0_DBC0; + p->u32DCCnAddress = BA0_DCC0; + p->u32DMRnAddress = BA0_DMR0; + p->u32DCRnAddress = BA0_DCR0; + p->u32HDSRnAddress = BA0_HDSR0; + break; + } + + case 1: /* capture */ + { + p->u32DBAnAddress = BA0_DBA1; + p->u32DCAnAddress = BA0_DCA1; + p->u32DBCnAddress = BA0_DBC1; + p->u32DCCnAddress = BA0_DCC1; + p->u32DMRnAddress = BA0_DMR1; + p->u32DCRnAddress = BA0_DCR1; + p->u32HDSRnAddress = BA0_HDSR1; + break; + } + + case 2: + { + p->u32DBAnAddress = BA0_DBA2; + p->u32DCAnAddress = BA0_DCA2; + p->u32DBCnAddress = BA0_DBC2; + p->u32DCCnAddress = BA0_DCC2; + p->u32DMRnAddress = BA0_DMR2; + p->u32DCRnAddress = BA0_DCR2; + p->u32HDSRnAddress = BA0_HDSR2; + break; + } + + case 3: + { + p->u32DBAnAddress = BA0_DBA3; + p->u32DCAnAddress = BA0_DCA3; + p->u32DBCnAddress = BA0_DBC3; + p->u32DCCnAddress = BA0_DCC3; + p->u32DMRnAddress = BA0_DMR3; + p->u32DCRnAddress = BA0_DCR3; + p->u32HDSRnAddress = BA0_HDSR3; + break; + } + default: + break; + } + +// +// Initialize the dma values for this pipeline +// + p->u32DBAnValue = readl(s->pBA0 + p->u32DBAnAddress); + p->u32DBCnValue = readl(s->pBA0 + p->u32DBCnAddress); + p->u32DMRnValue = readl(s->pBA0 + p->u32DMRnAddress); + p->u32DCRnValue = readl(s->pBA0 + p->u32DCRnAddress); + +} + +static void __devinit cs4281_InitPM(struct cs4281_state *s) +{ + int i; + struct cs4281_pipeline *p; + + for(i=0;ipl[i]; + p->number = i; + cs4281_BuildDMAengine(p,s); + cs4281_BuildFIFO(p,s); + /* + * currently only 2 pipelines are used + * so, only set the valid bit on the playback and capture. + */ + if( (i == CS4281_PLAYBACK_PIPELINE_NUMBER) || + (i == CS4281_CAPTURE_PIPELINE_NUMBER)) + p->flags |= CS4281_PIPELINE_VALID; + } + s->pm.u32SSPM_BITS = 0x7e; /* rev c, use 0x7c for rev a or b */ +} +#endif + +static int __devinit cs4281_probe(struct pci_dev *pcidev, + const struct pci_device_id *pciid) +{ +#ifndef NOT_CS4281_PM + struct pm_dev *pmdev; +#endif + struct cs4281_state *s; + dma_addr_t dma_mask; + mm_segment_t fs; + int i, val; + unsigned int temp1, temp2; + + CS_DBGOUT(CS_FUNCTION | CS_INIT, 2, + printk(KERN_INFO "cs4281: probe()+\n")); + + if (pci_enable_device(pcidev)) { + CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_ERR + "cs4281: pci_enable_device() failed\n")); + return -1; + } + if (!(pci_resource_flags(pcidev, 0) & IORESOURCE_MEM) || + !(pci_resource_flags(pcidev, 1) & IORESOURCE_MEM)) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs4281: probe()- Memory region not assigned\n")); + return -ENODEV; + } + if (pcidev->irq == 0) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs4281: probe() IRQ not assigned\n")); + return -ENODEV; + } + dma_mask = 0xffffffff; /* this enables playback and recording */ + i = pci_set_dma_mask(pcidev, dma_mask); + if (i) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs4281: probe() architecture does not support 32bit PCI busmaster DMA\n")); + return i; + } + if (!(s = kmalloc(sizeof(struct cs4281_state), GFP_KERNEL))) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs4281: probe() no memory for state struct.\n")); + return -1; + } + memset(s, 0, sizeof(struct cs4281_state)); + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac.wait); + init_waitqueue_head(&s->open_wait); + init_waitqueue_head(&s->open_wait_adc); + init_waitqueue_head(&s->open_wait_dac); + init_waitqueue_head(&s->midi.iwait); + init_waitqueue_head(&s->midi.owait); + init_MUTEX(&s->open_sem); + init_MUTEX(&s->open_sem_adc); + init_MUTEX(&s->open_sem_dac); + spin_lock_init(&s->lock); + s->pBA0phys = pci_resource_start(pcidev, 0); + s->pBA1phys = pci_resource_start(pcidev, 1); + + /* Convert phys to linear. */ + s->pBA0 = ioremap_nocache(s->pBA0phys, 4096); + if (!s->pBA0) { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_ERR + "cs4281: BA0 I/O mapping failed. Skipping part.\n")); + goto err_free; + } + s->pBA1 = ioremap_nocache(s->pBA1phys, 65536); + if (!s->pBA1) { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_ERR + "cs4281: BA1 I/O mapping failed. Skipping part.\n")); + goto err_unmap; + } + + temp1 = readl(s->pBA0 + BA0_PCICFG00); + temp2 = readl(s->pBA0 + BA0_PCICFG04); + + CS_DBGOUT(CS_INIT, 2, + printk(KERN_INFO + "cs4281: probe() BA0=0x%.8x BA1=0x%.8x pBA0=%p pBA1=%p \n", + (unsigned) temp1, (unsigned) temp2, s->pBA0, s->pBA1)); + CS_DBGOUT(CS_INIT, 2, + printk(KERN_INFO + "cs4281: probe() pBA0phys=0x%.8x pBA1phys=0x%.8x\n", + (unsigned) s->pBA0phys, (unsigned) s->pBA1phys)); + +#ifndef NOT_CS4281_PM + s->pm.flags = CS4281_PM_IDLE; +#endif + temp1 = cs4281_hw_init(s); + if (temp1) { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_ERR + "cs4281: cs4281_hw_init() failed. Skipping part.\n")); + goto err_irq; + } + s->magic = CS4281_MAGIC; + s->pcidev = pcidev; + s->irq = pcidev->irq; + if (request_irq + (s->irq, cs4281_interrupt, SA_SHIRQ, "Crystal CS4281", s)) { + CS_DBGOUT(CS_INIT | CS_ERROR, 1, + printk(KERN_ERR "cs4281: irq %u in use\n", s->irq)); + goto err_irq; + } + if ((s->dev_audio = register_sound_dsp(&cs4281_audio_fops, -1)) < + 0) { + CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_ERR + "cs4281: probe() register_sound_dsp() failed.\n")); + goto err_dev1; + } + if ((s->dev_mixer = register_sound_mixer(&cs4281_mixer_fops, -1)) < + 0) { + CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_ERR + "cs4281: probe() register_sound_mixer() failed.\n")); + goto err_dev2; + } + if ((s->dev_midi = register_sound_midi(&cs4281_midi_fops, -1)) < 0) { + CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_ERR + "cs4281: probe() register_sound_midi() failed.\n")); + goto err_dev3; + } +#ifndef NOT_CS4281_PM + cs4281_InitPM(s); + pmdev = cs_pm_register(PM_PCI_DEV, PM_PCI_ID(pcidev), cs4281_pm_callback); + if (pmdev) + { + CS_DBGOUT(CS_INIT | CS_PM, 4, printk(KERN_INFO + "cs4281: probe() pm_register() succeeded (%p).\n", pmdev)); + pmdev->data = s; + } + else + { + CS_DBGOUT(CS_INIT | CS_PM | CS_ERROR, 0, printk(KERN_INFO + "cs4281: probe() pm_register() failed (%p).\n", pmdev)); + s->pm.flags |= CS4281_PM_NOT_REGISTERED; + } +#endif + + pci_set_master(pcidev); // enable bus mastering + + fs = get_fs(); + set_fs(KERNEL_DS); + val = SOUND_MASK_LINE; + mixer_ioctl(s, SOUND_MIXER_WRITE_RECSRC, (unsigned long) &val); + for (i = 0; i < sizeof(initvol) / sizeof(initvol[0]); i++) { + val = initvol[i].vol; + mixer_ioctl(s, initvol[i].mixch, (unsigned long) &val); + } + val = 1; // enable mic preamp + mixer_ioctl(s, SOUND_MIXER_PRIVATE1, (unsigned long) &val); + set_fs(fs); + + pci_set_drvdata(pcidev, s); + list_add(&s->list, &cs4281_devs); + CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, printk(KERN_INFO + "cs4281: probe()- device allocated successfully\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: + iounmap(s->pBA1); + err_unmap: + iounmap(s->pBA0); + err_free: + kfree(s); + + CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_INFO + "cs4281: probe()- no device allocated\n")); + return -ENODEV; +} // probe_cs4281 + + +// --------------------------------------------------------------------- + +static void __devexit cs4281_remove(struct pci_dev *pci_dev) +{ + struct cs4281_state *s = pci_get_drvdata(pci_dev); + // stop DMA controller + synchronize_irq(s->irq); + free_irq(s->irq, s); + unregister_sound_dsp(s->dev_audio); + unregister_sound_mixer(s->dev_mixer); + unregister_sound_midi(s->dev_midi); + iounmap(s->pBA1); + iounmap(s->pBA0); + pci_set_drvdata(pci_dev,NULL); + list_del(&s->list); + kfree(s); + CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, printk(KERN_INFO + "cs4281: cs4281_remove()-: remove successful\n")); +} + +static struct pci_device_id cs4281_pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_CIRRUS, + .device = PCI_DEVICE_ID_CRYSTAL_CS4281, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, cs4281_pci_tbl); + +static struct pci_driver cs4281_pci_driver = { + .name = "cs4281", + .id_table = cs4281_pci_tbl, + .probe = cs4281_probe, + .remove = __devexit_p(cs4281_remove), + .suspend = CS4281_SUSPEND_TBL, + .resume = CS4281_RESUME_TBL, +}; + +static int __init cs4281_init_module(void) +{ + int rtn = 0; + CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, printk(KERN_INFO + "cs4281: cs4281_init_module()+ \n")); + printk(KERN_INFO "cs4281: version v%d.%02d.%d time " __TIME__ " " + __DATE__ "\n", CS4281_MAJOR_VERSION, CS4281_MINOR_VERSION, + CS4281_ARCH); + rtn = pci_module_init(&cs4281_pci_driver); + + CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: cs4281_init_module()- (%d)\n",rtn)); + return rtn; +} + +static void __exit cs4281_cleanup_module(void) +{ + pci_unregister_driver(&cs4281_pci_driver); +#ifndef NOT_CS4281_PM + cs_pm_unregister_all(cs4281_pm_callback); +#endif + CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, + printk(KERN_INFO "cs4281: cleanup_cs4281() finished\n")); +} +// --------------------------------------------------------------------- + +MODULE_AUTHOR("gw boynton, audio@crystal.cirrus.com"); +MODULE_DESCRIPTION("Cirrus Logic CS4281 Driver"); +MODULE_LICENSE("GPL"); + +// --------------------------------------------------------------------- + +module_init(cs4281_init_module); +module_exit(cs4281_cleanup_module); + diff --git a/sound/oss/cs4281/cs4281pm-24.c b/sound/oss/cs4281/cs4281pm-24.c new file mode 100644 index 000000000000..d2a453aff0aa --- /dev/null +++ b/sound/oss/cs4281/cs4281pm-24.c @@ -0,0 +1,84 @@ +/******************************************************************************* +* +* "cs4281pm.c" -- Cirrus Logic-Crystal CS4281 linux audio driver. +* +* Copyright (C) 2000,2001 Cirrus Logic Corp. +* -- tom woller (twoller@crystal.cirrus.com) or +* (audio@crystal.cirrus.com). +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* 12/22/00 trw - new file. +* +*******************************************************************************/ + +#ifndef NOT_CS4281_PM +#include + +#define cs_pm_register(a, b, c) pm_register((a), (b), (c)); +#define cs_pm_unregister_all(a) pm_unregister_all((a)); + +static int cs4281_suspend(struct cs4281_state *s); +static int cs4281_resume(struct cs4281_state *s); +/* +* for now (12/22/00) only enable the pm_register PM support. +* allow these table entries to be null. +#define CS4281_SUSPEND_TBL cs4281_suspend_tbl +#define CS4281_RESUME_TBL cs4281_resume_tbl +*/ +#define CS4281_SUSPEND_TBL cs4281_suspend_null +#define CS4281_RESUME_TBL cs4281_resume_null + +static int cs4281_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data) +{ + struct cs4281_state *state; + + CS_DBGOUT(CS_PM, 2, printk(KERN_INFO + "cs4281: cs4281_pm_callback dev=%p rqst=0x%x state=%p\n", + dev,(unsigned)rqst,data)); + state = (struct cs4281_state *) dev->data; + if (state) { + switch(rqst) { + case PM_SUSPEND: + CS_DBGOUT(CS_PM, 2, printk(KERN_INFO + "cs4281: PM suspend request\n")); + if(cs4281_suspend(state)) + { + CS_DBGOUT(CS_ERROR, 2, printk(KERN_INFO + "cs4281: PM suspend request refused\n")); + return 1; + } + break; + case PM_RESUME: + CS_DBGOUT(CS_PM, 2, printk(KERN_INFO + "cs4281: PM resume request\n")); + if(cs4281_resume(state)) + { + CS_DBGOUT(CS_ERROR, 2, printk(KERN_INFO + "cs4281: PM resume request refused\n")); + return 1; + } + break; + } + } + + return 0; +} + +#else /* CS4281_PM */ +#define CS4281_SUSPEND_TBL cs4281_suspend_null +#define CS4281_RESUME_TBL cs4281_resume_null +#endif /* CS4281_PM */ + diff --git a/sound/oss/cs4281/cs4281pm.h b/sound/oss/cs4281/cs4281pm.h new file mode 100644 index 000000000000..b44fdc9ce002 --- /dev/null +++ b/sound/oss/cs4281/cs4281pm.h @@ -0,0 +1,74 @@ +#ifndef NOT_CS4281_PM +/******************************************************************************* +* +* "cs4281pm.h" -- Cirrus Logic-Crystal CS4281 linux audio driver. +* +* Copyright (C) 2000,2001 Cirrus Logic Corp. +* -- tom woller (twoller@crystal.cirrus.com) or +* (audio@crystal.cirrus.com). +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* 12/22/00 trw - new file. +* +*******************************************************************************/ +/* general pm definitions */ +#define CS4281_AC97_HIGHESTREGTORESTORE 0x26 +#define CS4281_AC97_NUMBER_RESTORE_REGS (CS4281_AC97_HIGHESTREGTORESTORE/2-1) + +/* pipeline definitions */ +#define CS4281_NUMBER_OF_PIPELINES 4 +#define CS4281_PIPELINE_VALID 0x0001 +#define CS4281_PLAYBACK_PIPELINE_NUMBER 0x0000 +#define CS4281_CAPTURE_PIPELINE_NUMBER 0x0001 + +/* PM state defintions */ +#define CS4281_PM_NOT_REGISTERED 0x1000 +#define CS4281_PM_IDLE 0x0001 +#define CS4281_PM_SUSPENDING 0x0002 +#define CS4281_PM_SUSPENDED 0x0004 +#define CS4281_PM_RESUMING 0x0008 +#define CS4281_PM_RESUMED 0x0010 + +struct cs4281_pm { + unsigned long flags; + u32 u32CLKCR1_SAVE,u32SSPMValue,u32PPLVCvalue,u32PPRVCvalue; + u32 u32FMLVCvalue,u32FMRVCvalue,u32GPIORvalue,u32JSCTLvalue,u32SSCR; + u32 u32SRCSA,u32DacASR,u32AdcASR,u32DacSR,u32AdcSR,u32MIDCR_Save; + u32 u32SSPM_BITS; + u32 ac97[CS4281_AC97_NUMBER_RESTORE_REGS]; + u32 u32AC97_master_volume, u32AC97_headphone_volume, u32AC97_master_volume_mono; + u32 u32AC97_pcm_out_volume, u32AC97_powerdown, u32AC97_general_purpose; + u32 u32hwptr_playback,u32hwptr_capture; +}; + +struct cs4281_pipeline { + unsigned flags; + unsigned number; + u32 u32DBAnValue,u32DBCnValue,u32DMRnValue,u32DCRnValue; + u32 u32DBAnAddress,u32DCAnAddress,u32DBCnAddress,u32DCCnAddress; + u32 u32DMRnAddress,u32DCRnAddress,u32HDSRnAddress; + u32 u32DBAn_Save,u32DBCn_Save,u32DMRn_Save,u32DCRn_Save; + u32 u32DCCn_Save,u32DCAn_Save; +/* +* technically, these are fifo variables, but just map the +* first fifo with the first pipeline and then use the fifo +* variables inside of the pipeline struct. +*/ + u32 u32FCRn_Save,u32FSICn_Save; + u32 u32FCRnValue,u32FCRnAddress,u32FSICnValue,u32FSICnAddress; + u32 u32FPDRnValue,u32FPDRnAddress; +}; +#endif diff --git a/sound/oss/cs461x.h b/sound/oss/cs461x.h new file mode 100644 index 000000000000..0ce41338e6dd --- /dev/null +++ b/sound/oss/cs461x.h @@ -0,0 +1,1691 @@ +#ifndef __CS461X_H +#define __CS461X_H + +/* + * Copyright (c) by Cirrus Logic Corporation + * Copyright (c) by Jaroslav Kysela + * Definitions for Cirrus Logic CS461x chips + * + * + * 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 PCI_VENDOR_ID_CIRRUS +#define PCI_VENDOR_ID_CIRRUS 0x1013 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_4610 +#define PCI_DEVICE_ID_CIRRUS_4610 0x6001 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_4612 +#define PCI_DEVICE_ID_CIRRUS_4612 0x6003 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_4615 +#define PCI_DEVICE_ID_CIRRUS_4615 0x6004 +#endif + +/* + * Direct registers + */ + +/* + * The following define the offsets of the registers accessed via base address + * register zero on the CS461x part. + */ +#define BA0_HISR 0x00000000 +#define BA0_HSR0 0x00000004 +#define BA0_HICR 0x00000008 +#define BA0_DMSR 0x00000100 +#define BA0_HSAR 0x00000110 +#define BA0_HDAR 0x00000114 +#define BA0_HDMR 0x00000118 +#define BA0_HDCR 0x0000011C +#define BA0_PFMC 0x00000200 +#define BA0_PFCV1 0x00000204 +#define BA0_PFCV2 0x00000208 +#define BA0_PCICFG00 0x00000300 +#define BA0_PCICFG04 0x00000304 +#define BA0_PCICFG08 0x00000308 +#define BA0_PCICFG0C 0x0000030C +#define BA0_PCICFG10 0x00000310 +#define BA0_PCICFG14 0x00000314 +#define BA0_PCICFG18 0x00000318 +#define BA0_PCICFG1C 0x0000031C +#define BA0_PCICFG20 0x00000320 +#define BA0_PCICFG24 0x00000324 +#define BA0_PCICFG28 0x00000328 +#define BA0_PCICFG2C 0x0000032C +#define BA0_PCICFG30 0x00000330 +#define BA0_PCICFG34 0x00000334 +#define BA0_PCICFG38 0x00000338 +#define BA0_PCICFG3C 0x0000033C +#define BA0_CLKCR1 0x00000400 +#define BA0_CLKCR2 0x00000404 +#define BA0_PLLM 0x00000408 +#define BA0_PLLCC 0x0000040C +#define BA0_FRR 0x00000410 +#define BA0_CFL1 0x00000414 +#define BA0_CFL2 0x00000418 +#define BA0_SERMC1 0x00000420 +#define BA0_SERMC2 0x00000424 +#define BA0_SERC1 0x00000428 +#define BA0_SERC2 0x0000042C +#define BA0_SERC3 0x00000430 +#define BA0_SERC4 0x00000434 +#define BA0_SERC5 0x00000438 +#define BA0_SERBSP 0x0000043C +#define BA0_SERBST 0x00000440 +#define BA0_SERBCM 0x00000444 +#define BA0_SERBAD 0x00000448 +#define BA0_SERBCF 0x0000044C +#define BA0_SERBWP 0x00000450 +#define BA0_SERBRP 0x00000454 +#ifndef NO_CS4612 +#define BA0_ASER_FADDR 0x00000458 +#endif +#define BA0_ACCTL 0x00000460 +#define BA0_ACSTS 0x00000464 +#define BA0_ACOSV 0x00000468 +#define BA0_ACCAD 0x0000046C +#define BA0_ACCDA 0x00000470 +#define BA0_ACISV 0x00000474 +#define BA0_ACSAD 0x00000478 +#define BA0_ACSDA 0x0000047C +#define BA0_JSPT 0x00000480 +#define BA0_JSCTL 0x00000484 +#define BA0_JSC1 0x00000488 +#define BA0_JSC2 0x0000048C +#define BA0_MIDCR 0x00000490 +#define BA0_MIDSR 0x00000494 +#define BA0_MIDWP 0x00000498 +#define BA0_MIDRP 0x0000049C +#define BA0_JSIO 0x000004A0 +#ifndef NO_CS4612 +#define BA0_ASER_MASTER 0x000004A4 +#endif +#define BA0_CFGI 0x000004B0 +#define BA0_SSVID 0x000004B4 +#define BA0_GPIOR 0x000004B8 +#ifndef NO_CS4612 +#define BA0_EGPIODR 0x000004BC +#define BA0_EGPIOPTR 0x000004C0 +#define BA0_EGPIOTR 0x000004C4 +#define BA0_EGPIOWR 0x000004C8 +#define BA0_EGPIOSR 0x000004CC +#define BA0_SERC6 0x000004D0 +#define BA0_SERC7 0x000004D4 +#define BA0_SERACC 0x000004D8 +#define BA0_ACCTL2 0x000004E0 +#define BA0_ACSTS2 0x000004E4 +#define BA0_ACOSV2 0x000004E8 +#define BA0_ACCAD2 0x000004EC +#define BA0_ACCDA2 0x000004F0 +#define BA0_ACISV2 0x000004F4 +#define BA0_ACSAD2 0x000004F8 +#define BA0_ACSDA2 0x000004FC +#define BA0_IOTAC0 0x00000500 +#define BA0_IOTAC1 0x00000504 +#define BA0_IOTAC2 0x00000508 +#define BA0_IOTAC3 0x0000050C +#define BA0_IOTAC4 0x00000510 +#define BA0_IOTAC5 0x00000514 +#define BA0_IOTAC6 0x00000518 +#define BA0_IOTAC7 0x0000051C +#define BA0_IOTAC8 0x00000520 +#define BA0_IOTAC9 0x00000524 +#define BA0_IOTAC10 0x00000528 +#define BA0_IOTAC11 0x0000052C +#define BA0_IOTFR0 0x00000540 +#define BA0_IOTFR1 0x00000544 +#define BA0_IOTFR2 0x00000548 +#define BA0_IOTFR3 0x0000054C +#define BA0_IOTFR4 0x00000550 +#define BA0_IOTFR5 0x00000554 +#define BA0_IOTFR6 0x00000558 +#define BA0_IOTFR7 0x0000055C +#define BA0_IOTFIFO 0x00000580 +#define BA0_IOTRRD 0x00000584 +#define BA0_IOTFP 0x00000588 +#define BA0_IOTCR 0x0000058C +#define BA0_DPCID 0x00000590 +#define BA0_DPCIA 0x00000594 +#define BA0_DPCIC 0x00000598 +#define BA0_PCPCIR 0x00000600 +#define BA0_PCPCIG 0x00000604 +#define BA0_PCPCIEN 0x00000608 +#define BA0_EPCIPMC 0x00000610 +#endif + +/* + * The following define the offsets of the registers and memories accessed via + * base address register one on the CS461x part. + */ +#define BA1_SP_DMEM0 0x00000000 +#define BA1_SP_DMEM1 0x00010000 +#define BA1_SP_PMEM 0x00020000 +#define BA1_SP_REG 0x00030000 +#define BA1_SPCR 0x00030000 +#define BA1_DREG 0x00030004 +#define BA1_DSRWP 0x00030008 +#define BA1_TWPR 0x0003000C +#define BA1_SPWR 0x00030010 +#define BA1_SPIR 0x00030014 +#define BA1_FGR1 0x00030020 +#define BA1_SPCS 0x00030028 +#define BA1_SDSR 0x0003002C +#define BA1_FRMT 0x00030030 +#define BA1_FRCC 0x00030034 +#define BA1_FRSC 0x00030038 +#define BA1_OMNI_MEM 0x000E0000 + +/* + * The following defines are for the flags in the host interrupt status + * register. + */ +#define HISR_VC_MASK 0x0000FFFF +#define HISR_VC0 0x00000001 +#define HISR_VC1 0x00000002 +#define HISR_VC2 0x00000004 +#define HISR_VC3 0x00000008 +#define HISR_VC4 0x00000010 +#define HISR_VC5 0x00000020 +#define HISR_VC6 0x00000040 +#define HISR_VC7 0x00000080 +#define HISR_VC8 0x00000100 +#define HISR_VC9 0x00000200 +#define HISR_VC10 0x00000400 +#define HISR_VC11 0x00000800 +#define HISR_VC12 0x00001000 +#define HISR_VC13 0x00002000 +#define HISR_VC14 0x00004000 +#define HISR_VC15 0x00008000 +#define HISR_INT0 0x00010000 +#define HISR_INT1 0x00020000 +#define HISR_DMAI 0x00040000 +#define HISR_FROVR 0x00080000 +#define HISR_MIDI 0x00100000 +#ifdef NO_CS4612 +#define HISR_RESERVED 0x0FE00000 +#else +#define HISR_SBINT 0x00200000 +#define HISR_RESERVED 0x0FC00000 +#endif +#define HISR_H0P 0x40000000 +#define HISR_INTENA 0x80000000 + +/* + * The following defines are for the flags in the host signal register 0. + */ +#define HSR0_VC_MASK 0xFFFFFFFF +#define HSR0_VC16 0x00000001 +#define HSR0_VC17 0x00000002 +#define HSR0_VC18 0x00000004 +#define HSR0_VC19 0x00000008 +#define HSR0_VC20 0x00000010 +#define HSR0_VC21 0x00000020 +#define HSR0_VC22 0x00000040 +#define HSR0_VC23 0x00000080 +#define HSR0_VC24 0x00000100 +#define HSR0_VC25 0x00000200 +#define HSR0_VC26 0x00000400 +#define HSR0_VC27 0x00000800 +#define HSR0_VC28 0x00001000 +#define HSR0_VC29 0x00002000 +#define HSR0_VC30 0x00004000 +#define HSR0_VC31 0x00008000 +#define HSR0_VC32 0x00010000 +#define HSR0_VC33 0x00020000 +#define HSR0_VC34 0x00040000 +#define HSR0_VC35 0x00080000 +#define HSR0_VC36 0x00100000 +#define HSR0_VC37 0x00200000 +#define HSR0_VC38 0x00400000 +#define HSR0_VC39 0x00800000 +#define HSR0_VC40 0x01000000 +#define HSR0_VC41 0x02000000 +#define HSR0_VC42 0x04000000 +#define HSR0_VC43 0x08000000 +#define HSR0_VC44 0x10000000 +#define HSR0_VC45 0x20000000 +#define HSR0_VC46 0x40000000 +#define HSR0_VC47 0x80000000 + +/* + * The following defines are for the flags in the host interrupt control + * register. + */ +#define HICR_IEV 0x00000001 +#define HICR_CHGM 0x00000002 + +/* + * The following defines are for the flags in the DMA status register. + */ +#define DMSR_HP 0x00000001 +#define DMSR_HR 0x00000002 +#define DMSR_SP 0x00000004 +#define DMSR_SR 0x00000008 + +/* + * The following defines are for the flags in the host DMA source address + * register. + */ +#define HSAR_HOST_ADDR_MASK 0xFFFFFFFF +#define HSAR_DSP_ADDR_MASK 0x0000FFFF +#define HSAR_MEMID_MASK 0x000F0000 +#define HSAR_MEMID_SP_DMEM0 0x00000000 +#define HSAR_MEMID_SP_DMEM1 0x00010000 +#define HSAR_MEMID_SP_PMEM 0x00020000 +#define HSAR_MEMID_SP_DEBUG 0x00030000 +#define HSAR_MEMID_OMNI_MEM 0x000E0000 +#define HSAR_END 0x40000000 +#define HSAR_ERR 0x80000000 + +/* + * The following defines are for the flags in the host DMA destination address + * register. + */ +#define HDAR_HOST_ADDR_MASK 0xFFFFFFFF +#define HDAR_DSP_ADDR_MASK 0x0000FFFF +#define HDAR_MEMID_MASK 0x000F0000 +#define HDAR_MEMID_SP_DMEM0 0x00000000 +#define HDAR_MEMID_SP_DMEM1 0x00010000 +#define HDAR_MEMID_SP_PMEM 0x00020000 +#define HDAR_MEMID_SP_DEBUG 0x00030000 +#define HDAR_MEMID_OMNI_MEM 0x000E0000 +#define HDAR_END 0x40000000 +#define HDAR_ERR 0x80000000 + +/* + * The following defines are for the flags in the host DMA control register. + */ +#define HDMR_AC_MASK 0x0000F000 +#define HDMR_AC_8_16 0x00001000 +#define HDMR_AC_M_S 0x00002000 +#define HDMR_AC_B_L 0x00004000 +#define HDMR_AC_S_U 0x00008000 + +/* + * The following defines are for the flags in the host DMA control register. + */ +#define HDCR_COUNT_MASK 0x000003FF +#define HDCR_DONE 0x00004000 +#define HDCR_OPT 0x00008000 +#define HDCR_WBD 0x00400000 +#define HDCR_WBS 0x00800000 +#define HDCR_DMS_MASK 0x07000000 +#define HDCR_DMS_LINEAR 0x00000000 +#define HDCR_DMS_16_DWORDS 0x01000000 +#define HDCR_DMS_32_DWORDS 0x02000000 +#define HDCR_DMS_64_DWORDS 0x03000000 +#define HDCR_DMS_128_DWORDS 0x04000000 +#define HDCR_DMS_256_DWORDS 0x05000000 +#define HDCR_DMS_512_DWORDS 0x06000000 +#define HDCR_DMS_1024_DWORDS 0x07000000 +#define HDCR_DH 0x08000000 +#define HDCR_SMS_MASK 0x70000000 +#define HDCR_SMS_LINEAR 0x00000000 +#define HDCR_SMS_16_DWORDS 0x10000000 +#define HDCR_SMS_32_DWORDS 0x20000000 +#define HDCR_SMS_64_DWORDS 0x30000000 +#define HDCR_SMS_128_DWORDS 0x40000000 +#define HDCR_SMS_256_DWORDS 0x50000000 +#define HDCR_SMS_512_DWORDS 0x60000000 +#define HDCR_SMS_1024_DWORDS 0x70000000 +#define HDCR_SH 0x80000000 +#define HDCR_COUNT_SHIFT 0 + +/* + * The following defines are for the flags in the performance monitor control + * register. + */ +#define PFMC_C1SS_MASK 0x0000001F +#define PFMC_C1EV 0x00000020 +#define PFMC_C1RS 0x00008000 +#define PFMC_C2SS_MASK 0x001F0000 +#define PFMC_C2EV 0x00200000 +#define PFMC_C2RS 0x80000000 +#define PFMC_C1SS_SHIFT 0 +#define PFMC_C2SS_SHIFT 16 +#define PFMC_BUS_GRANT 0 +#define PFMC_GRANT_AFTER_REQ 1 +#define PFMC_TRANSACTION 2 +#define PFMC_DWORD_TRANSFER 3 +#define PFMC_SLAVE_READ 4 +#define PFMC_SLAVE_WRITE 5 +#define PFMC_PREEMPTION 6 +#define PFMC_DISCONNECT_RETRY 7 +#define PFMC_INTERRUPT 8 +#define PFMC_BUS_OWNERSHIP 9 +#define PFMC_TRANSACTION_LAG 10 +#define PFMC_PCI_CLOCK 11 +#define PFMC_SERIAL_CLOCK 12 +#define PFMC_SP_CLOCK 13 + +/* + * The following defines are for the flags in the performance counter value 1 + * register. + */ +#define PFCV1_PC1V_MASK 0xFFFFFFFF +#define PFCV1_PC1V_SHIFT 0 + +/* + * The following defines are for the flags in the performance counter value 2 + * register. + */ +#define PFCV2_PC2V_MASK 0xFFFFFFFF +#define PFCV2_PC2V_SHIFT 0 + +/* + * The following defines are for the flags in the clock control register 1. + */ +#define CLKCR1_OSCS 0x00000001 +#define CLKCR1_OSCP 0x00000002 +#define CLKCR1_PLLSS_MASK 0x0000000C +#define CLKCR1_PLLSS_SERIAL 0x00000000 +#define CLKCR1_PLLSS_CRYSTAL 0x00000004 +#define CLKCR1_PLLSS_PCI 0x00000008 +#define CLKCR1_PLLSS_RESERVED 0x0000000C +#define CLKCR1_PLLP 0x00000010 +#define CLKCR1_SWCE 0x00000020 +#define CLKCR1_PLLOS 0x00000040 + +/* + * The following defines are for the flags in the clock control register 2. + */ +#define CLKCR2_PDIVS_MASK 0x0000000F +#define CLKCR2_PDIVS_1 0x00000001 +#define CLKCR2_PDIVS_2 0x00000002 +#define CLKCR2_PDIVS_4 0x00000004 +#define CLKCR2_PDIVS_7 0x00000007 +#define CLKCR2_PDIVS_8 0x00000008 +#define CLKCR2_PDIVS_16 0x00000000 + +/* + * The following defines are for the flags in the PLL multiplier register. + */ +#define PLLM_MASK 0x000000FF +#define PLLM_SHIFT 0 + +/* + * The following defines are for the flags in the PLL capacitor coefficient + * register. + */ +#define PLLCC_CDR_MASK 0x00000007 +#ifndef NO_CS4610 +#define PLLCC_CDR_240_350_MHZ 0x00000000 +#define PLLCC_CDR_184_265_MHZ 0x00000001 +#define PLLCC_CDR_144_205_MHZ 0x00000002 +#define PLLCC_CDR_111_160_MHZ 0x00000003 +#define PLLCC_CDR_87_123_MHZ 0x00000004 +#define PLLCC_CDR_67_96_MHZ 0x00000005 +#define PLLCC_CDR_52_74_MHZ 0x00000006 +#define PLLCC_CDR_45_58_MHZ 0x00000007 +#endif +#ifndef NO_CS4612 +#define PLLCC_CDR_271_398_MHZ 0x00000000 +#define PLLCC_CDR_227_330_MHZ 0x00000001 +#define PLLCC_CDR_167_239_MHZ 0x00000002 +#define PLLCC_CDR_150_215_MHZ 0x00000003 +#define PLLCC_CDR_107_154_MHZ 0x00000004 +#define PLLCC_CDR_98_140_MHZ 0x00000005 +#define PLLCC_CDR_73_104_MHZ 0x00000006 +#define PLLCC_CDR_63_90_MHZ 0x00000007 +#endif +#define PLLCC_LPF_MASK 0x000000F8 +#ifndef NO_CS4610 +#define PLLCC_LPF_23850_60000_KHZ 0x00000000 +#define PLLCC_LPF_7960_26290_KHZ 0x00000008 +#define PLLCC_LPF_4160_10980_KHZ 0x00000018 +#define PLLCC_LPF_1740_4580_KHZ 0x00000038 +#define PLLCC_LPF_724_1910_KHZ 0x00000078 +#define PLLCC_LPF_317_798_KHZ 0x000000F8 +#endif +#ifndef NO_CS4612 +#define PLLCC_LPF_25580_64530_KHZ 0x00000000 +#define PLLCC_LPF_14360_37270_KHZ 0x00000008 +#define PLLCC_LPF_6100_16020_KHZ 0x00000018 +#define PLLCC_LPF_2540_6690_KHZ 0x00000038 +#define PLLCC_LPF_1050_2780_KHZ 0x00000078 +#define PLLCC_LPF_450_1160_KHZ 0x000000F8 +#endif + +/* + * The following defines are for the flags in the feature reporting register. + */ +#define FRR_FAB_MASK 0x00000003 +#define FRR_MASK_MASK 0x0000001C +#ifdef NO_CS4612 +#define FRR_CFOP_MASK 0x000000E0 +#else +#define FRR_CFOP_MASK 0x00000FE0 +#endif +#define FRR_CFOP_NOT_DVD 0x00000020 +#define FRR_CFOP_A3D 0x00000040 +#define FRR_CFOP_128_PIN 0x00000080 +#ifndef NO_CS4612 +#define FRR_CFOP_CS4280 0x00000800 +#endif +#define FRR_FAB_SHIFT 0 +#define FRR_MASK_SHIFT 2 +#define FRR_CFOP_SHIFT 5 + +/* + * The following defines are for the flags in the configuration load 1 + * register. + */ +#define CFL1_CLOCK_SOURCE_MASK 0x00000003 +#define CFL1_CLOCK_SOURCE_CS423X 0x00000000 +#define CFL1_CLOCK_SOURCE_AC97 0x00000001 +#define CFL1_CLOCK_SOURCE_CRYSTAL 0x00000002 +#define CFL1_CLOCK_SOURCE_DUAL_AC97 0x00000003 +#define CFL1_VALID_DATA_MASK 0x000000FF + +/* + * The following defines are for the flags in the configuration load 2 + * register. + */ +#define CFL2_VALID_DATA_MASK 0x000000FF + +/* + * The following defines are for the flags in the serial port master control + * register 1. + */ +#define SERMC1_MSPE 0x00000001 +#define SERMC1_PTC_MASK 0x0000000E +#define SERMC1_PTC_CS423X 0x00000000 +#define SERMC1_PTC_AC97 0x00000002 +#define SERMC1_PTC_DAC 0x00000004 +#define SERMC1_PLB 0x00000010 +#define SERMC1_XLB 0x00000020 + +/* + * The following defines are for the flags in the serial port master control + * register 2. + */ +#define SERMC2_LROE 0x00000001 +#define SERMC2_MCOE 0x00000002 +#define SERMC2_MCDIV 0x00000004 + +/* + * The following defines are for the flags in the serial port 1 configuration + * register. + */ +#define SERC1_SO1EN 0x00000001 +#define SERC1_SO1F_MASK 0x0000000E +#define SERC1_SO1F_CS423X 0x00000000 +#define SERC1_SO1F_AC97 0x00000002 +#define SERC1_SO1F_DAC 0x00000004 +#define SERC1_SO1F_SPDIF 0x00000006 + +/* + * The following defines are for the flags in the serial port 2 configuration + * register. + */ +#define SERC2_SI1EN 0x00000001 +#define SERC2_SI1F_MASK 0x0000000E +#define SERC2_SI1F_CS423X 0x00000000 +#define SERC2_SI1F_AC97 0x00000002 +#define SERC2_SI1F_ADC 0x00000004 +#define SERC2_SI1F_SPDIF 0x00000006 + +/* + * The following defines are for the flags in the serial port 3 configuration + * register. + */ +#define SERC3_SO2EN 0x00000001 +#define SERC3_SO2F_MASK 0x00000006 +#define SERC3_SO2F_DAC 0x00000000 +#define SERC3_SO2F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port 4 configuration + * register. + */ +#define SERC4_SO3EN 0x00000001 +#define SERC4_SO3F_MASK 0x00000006 +#define SERC4_SO3F_DAC 0x00000000 +#define SERC4_SO3F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port 5 configuration + * register. + */ +#define SERC5_SI2EN 0x00000001 +#define SERC5_SI2F_MASK 0x00000006 +#define SERC5_SI2F_ADC 0x00000000 +#define SERC5_SI2F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor sample + * pointer register. + */ +#define SERBSP_FSP_MASK 0x0000000F +#define SERBSP_FSP_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor status + * register. + */ +#define SERBST_RRDY 0x00000001 +#define SERBST_WBSY 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor command + * register. + */ +#define SERBCM_RDC 0x00000001 +#define SERBCM_WRC 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor address + * register. + */ +#ifdef NO_CS4612 +#define SERBAD_FAD_MASK 0x000000FF +#else +#define SERBAD_FAD_MASK 0x000001FF +#endif +#define SERBAD_FAD_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor + * configuration register. + */ +#define SERBCF_HBP 0x00000001 + +/* + * The following defines are for the flags in the serial port backdoor write + * port register. + */ +#define SERBWP_FWD_MASK 0x000FFFFF +#define SERBWP_FWD_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor read + * port register. + */ +#define SERBRP_FRD_MASK 0x000FFFFF +#define SERBRP_FRD_SHIFT 0 + +/* + * The following defines are for the flags in the async FIFO address register. + */ +#ifndef NO_CS4612 +#define ASER_FADDR_A1_MASK 0x000001FF +#define ASER_FADDR_EN1 0x00008000 +#define ASER_FADDR_A2_MASK 0x01FF0000 +#define ASER_FADDR_EN2 0x80000000 +#define ASER_FADDR_A1_SHIFT 0 +#define ASER_FADDR_A2_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the AC97 control register. + */ +#define ACCTL_RSTN 0x00000001 +#define ACCTL_ESYN 0x00000002 +#define ACCTL_VFRM 0x00000004 +#define ACCTL_DCV 0x00000008 +#define ACCTL_CRW 0x00000010 +#define ACCTL_ASYN 0x00000020 +#ifndef NO_CS4612 +#define ACCTL_TC 0x00000040 +#endif + +/* + * The following defines are for the flags in the AC97 status register. + */ +#define ACSTS_CRDY 0x00000001 +#define ACSTS_VSTS 0x00000002 +#ifndef NO_CS4612 +#define ACSTS_WKUP 0x00000004 +#endif + +/* + * The following defines are for the flags in the AC97 output slot valid + * register. + */ +#define ACOSV_SLV3 0x00000001 +#define ACOSV_SLV4 0x00000002 +#define ACOSV_SLV5 0x00000004 +#define ACOSV_SLV6 0x00000008 +#define ACOSV_SLV7 0x00000010 +#define ACOSV_SLV8 0x00000020 +#define ACOSV_SLV9 0x00000040 +#define ACOSV_SLV10 0x00000080 +#define ACOSV_SLV11 0x00000100 +#define ACOSV_SLV12 0x00000200 + +/* + * The following defines are for the flags in the AC97 command address + * register. + */ +#define ACCAD_CI_MASK 0x0000007F +#define ACCAD_CI_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 command data register. + */ +#define ACCDA_CD_MASK 0x0000FFFF +#define ACCDA_CD_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 input slot valid + * register. + */ +#define ACISV_ISV3 0x00000001 +#define ACISV_ISV4 0x00000002 +#define ACISV_ISV5 0x00000004 +#define ACISV_ISV6 0x00000008 +#define ACISV_ISV7 0x00000010 +#define ACISV_ISV8 0x00000020 +#define ACISV_ISV9 0x00000040 +#define ACISV_ISV10 0x00000080 +#define ACISV_ISV11 0x00000100 +#define ACISV_ISV12 0x00000200 + +/* + * The following defines are for the flags in the AC97 status address + * register. + */ +#define ACSAD_SI_MASK 0x0000007F +#define ACSAD_SI_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 status data register. + */ +#define ACSDA_SD_MASK 0x0000FFFF +#define ACSDA_SD_SHIFT 0 + +/* + * The following defines are for the flags in the joystick poll/trigger + * register. + */ +#define JSPT_CAX 0x00000001 +#define JSPT_CAY 0x00000002 +#define JSPT_CBX 0x00000004 +#define JSPT_CBY 0x00000008 +#define JSPT_BA1 0x00000010 +#define JSPT_BA2 0x00000020 +#define JSPT_BB1 0x00000040 +#define JSPT_BB2 0x00000080 + +/* + * The following defines are for the flags in the joystick control register. + */ +#define JSCTL_SP_MASK 0x00000003 +#define JSCTL_SP_SLOW 0x00000000 +#define JSCTL_SP_MEDIUM_SLOW 0x00000001 +#define JSCTL_SP_MEDIUM_FAST 0x00000002 +#define JSCTL_SP_FAST 0x00000003 +#define JSCTL_ARE 0x00000004 + +/* + * The following defines are for the flags in the joystick coordinate pair 1 + * readback register. + */ +#define JSC1_Y1V_MASK 0x0000FFFF +#define JSC1_X1V_MASK 0xFFFF0000 +#define JSC1_Y1V_SHIFT 0 +#define JSC1_X1V_SHIFT 16 + +/* + * The following defines are for the flags in the joystick coordinate pair 2 + * readback register. + */ +#define JSC2_Y2V_MASK 0x0000FFFF +#define JSC2_X2V_MASK 0xFFFF0000 +#define JSC2_Y2V_SHIFT 0 +#define JSC2_X2V_SHIFT 16 + +/* + * The following defines are for the flags in the MIDI control register. + */ +#define MIDCR_TXE 0x00000001 /* Enable transmitting. */ +#define MIDCR_RXE 0x00000002 /* Enable receiving. */ +#define MIDCR_RIE 0x00000004 /* Interrupt upon tx ready. */ +#define MIDCR_TIE 0x00000008 /* Interrupt upon rx ready. */ +#define MIDCR_MLB 0x00000010 /* Enable midi loopback. */ +#define MIDCR_MRST 0x00000020 /* Reset interface. */ + +/* + * The following defines are for the flags in the MIDI status register. + */ +#define MIDSR_TBF 0x00000001 /* Tx FIFO is full. */ +#define MIDSR_RBE 0x00000002 /* Rx FIFO is empty. */ + +/* + * The following defines are for the flags in the MIDI write port register. + */ +#define MIDWP_MWD_MASK 0x000000FF +#define MIDWP_MWD_SHIFT 0 + +/* + * The following defines are for the flags in the MIDI read port register. + */ +#define MIDRP_MRD_MASK 0x000000FF +#define MIDRP_MRD_SHIFT 0 + +/* + * The following defines are for the flags in the joystick GPIO register. + */ +#define JSIO_DAX 0x00000001 +#define JSIO_DAY 0x00000002 +#define JSIO_DBX 0x00000004 +#define JSIO_DBY 0x00000008 +#define JSIO_AXOE 0x00000010 +#define JSIO_AYOE 0x00000020 +#define JSIO_BXOE 0x00000040 +#define JSIO_BYOE 0x00000080 + +/* + * The following defines are for the flags in the master async/sync serial + * port enable register. + */ +#ifndef NO_CS4612 +#define ASER_MASTER_ME 0x00000001 +#endif + +/* + * The following defines are for the flags in the configuration interface + * register. + */ +#define CFGI_CLK 0x00000001 +#define CFGI_DOUT 0x00000002 +#define CFGI_DIN_EEN 0x00000004 +#define CFGI_EELD 0x00000008 + +/* + * The following defines are for the flags in the subsystem ID and vendor ID + * register. + */ +#define SSVID_VID_MASK 0x0000FFFF +#define SSVID_SID_MASK 0xFFFF0000 +#define SSVID_VID_SHIFT 0 +#define SSVID_SID_SHIFT 16 + +/* + * The following defines are for the flags in the GPIO pin interface register. + */ +#define GPIOR_VOLDN 0x00000001 +#define GPIOR_VOLUP 0x00000002 +#define GPIOR_SI2D 0x00000004 +#define GPIOR_SI2OE 0x00000008 + +/* + * The following defines are for the flags in the extended GPIO pin direction + * register. + */ +#ifndef NO_CS4612 +#define EGPIODR_GPOE0 0x00000001 +#define EGPIODR_GPOE1 0x00000002 +#define EGPIODR_GPOE2 0x00000004 +#define EGPIODR_GPOE3 0x00000008 +#define EGPIODR_GPOE4 0x00000010 +#define EGPIODR_GPOE5 0x00000020 +#define EGPIODR_GPOE6 0x00000040 +#define EGPIODR_GPOE7 0x00000080 +#define EGPIODR_GPOE8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin polarity/ + * type register. + */ +#ifndef NO_CS4612 +#define EGPIOPTR_GPPT0 0x00000001 +#define EGPIOPTR_GPPT1 0x00000002 +#define EGPIOPTR_GPPT2 0x00000004 +#define EGPIOPTR_GPPT3 0x00000008 +#define EGPIOPTR_GPPT4 0x00000010 +#define EGPIOPTR_GPPT5 0x00000020 +#define EGPIOPTR_GPPT6 0x00000040 +#define EGPIOPTR_GPPT7 0x00000080 +#define EGPIOPTR_GPPT8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin sticky + * register. + */ +#ifndef NO_CS4612 +#define EGPIOTR_GPS0 0x00000001 +#define EGPIOTR_GPS1 0x00000002 +#define EGPIOTR_GPS2 0x00000004 +#define EGPIOTR_GPS3 0x00000008 +#define EGPIOTR_GPS4 0x00000010 +#define EGPIOTR_GPS5 0x00000020 +#define EGPIOTR_GPS6 0x00000040 +#define EGPIOTR_GPS7 0x00000080 +#define EGPIOTR_GPS8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO ping wakeup + * register. + */ +#ifndef NO_CS4612 +#define EGPIOWR_GPW0 0x00000001 +#define EGPIOWR_GPW1 0x00000002 +#define EGPIOWR_GPW2 0x00000004 +#define EGPIOWR_GPW3 0x00000008 +#define EGPIOWR_GPW4 0x00000010 +#define EGPIOWR_GPW5 0x00000020 +#define EGPIOWR_GPW6 0x00000040 +#define EGPIOWR_GPW7 0x00000080 +#define EGPIOWR_GPW8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin status + * register. + */ +#ifndef NO_CS4612 +#define EGPIOSR_GPS0 0x00000001 +#define EGPIOSR_GPS1 0x00000002 +#define EGPIOSR_GPS2 0x00000004 +#define EGPIOSR_GPS3 0x00000008 +#define EGPIOSR_GPS4 0x00000010 +#define EGPIOSR_GPS5 0x00000020 +#define EGPIOSR_GPS6 0x00000040 +#define EGPIOSR_GPS7 0x00000080 +#define EGPIOSR_GPS8 0x00000100 +#endif + +/* + * The following defines are for the flags in the serial port 6 configuration + * register. + */ +#ifndef NO_CS4612 +#define SERC6_ASDO2EN 0x00000001 +#endif + +/* + * The following defines are for the flags in the serial port 7 configuration + * register. + */ +#ifndef NO_CS4612 +#define SERC7_ASDI2EN 0x00000001 +#define SERC7_POSILB 0x00000002 +#define SERC7_SIPOLB 0x00000004 +#define SERC7_SOSILB 0x00000008 +#define SERC7_SISOLB 0x00000010 +#endif + +/* + * The following defines are for the flags in the serial port AC link + * configuration register. + */ +#ifndef NO_CS4612 +#define SERACC_CODEC_TYPE_MASK 0x00000001 +#define SERACC_CODEC_TYPE_1_03 0x00000000 +#define SERACC_CODEC_TYPE_2_0 0x00000001 +#define SERACC_TWO_CODECS 0x00000002 +#define SERACC_MDM 0x00000004 +#define SERACC_HSP 0x00000008 +#endif + +/* + * The following defines are for the flags in the AC97 control register 2. + */ +#ifndef NO_CS4612 +#define ACCTL2_RSTN 0x00000001 +#define ACCTL2_ESYN 0x00000002 +#define ACCTL2_VFRM 0x00000004 +#define ACCTL2_DCV 0x00000008 +#define ACCTL2_CRW 0x00000010 +#define ACCTL2_ASYN 0x00000020 +#endif + +/* + * The following defines are for the flags in the AC97 status register 2. + */ +#ifndef NO_CS4612 +#define ACSTS2_CRDY 0x00000001 +#define ACSTS2_VSTS 0x00000002 +#endif + +/* + * The following defines are for the flags in the AC97 output slot valid + * register 2. + */ +#ifndef NO_CS4612 +#define ACOSV2_SLV3 0x00000001 +#define ACOSV2_SLV4 0x00000002 +#define ACOSV2_SLV5 0x00000004 +#define ACOSV2_SLV6 0x00000008 +#define ACOSV2_SLV7 0x00000010 +#define ACOSV2_SLV8 0x00000020 +#define ACOSV2_SLV9 0x00000040 +#define ACOSV2_SLV10 0x00000080 +#define ACOSV2_SLV11 0x00000100 +#define ACOSV2_SLV12 0x00000200 +#endif + +/* + * The following defines are for the flags in the AC97 command address + * register 2. + */ +#ifndef NO_CS4612 +#define ACCAD2_CI_MASK 0x0000007F +#define ACCAD2_CI_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 command data register + * 2. + */ +#ifndef NO_CS4612 +#define ACCDA2_CD_MASK 0x0000FFFF +#define ACCDA2_CD_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 input slot valid + * register 2. + */ +#ifndef NO_CS4612 +#define ACISV2_ISV3 0x00000001 +#define ACISV2_ISV4 0x00000002 +#define ACISV2_ISV5 0x00000004 +#define ACISV2_ISV6 0x00000008 +#define ACISV2_ISV7 0x00000010 +#define ACISV2_ISV8 0x00000020 +#define ACISV2_ISV9 0x00000040 +#define ACISV2_ISV10 0x00000080 +#define ACISV2_ISV11 0x00000100 +#define ACISV2_ISV12 0x00000200 +#endif + +/* + * The following defines are for the flags in the AC97 status address + * register 2. + */ +#ifndef NO_CS4612 +#define ACSAD2_SI_MASK 0x0000007F +#define ACSAD2_SI_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 status data register 2. + */ +#ifndef NO_CS4612 +#define ACSDA2_SD_MASK 0x0000FFFF +#define ACSDA2_SD_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the I/O trap address and control + * registers (all 12). + */ +#ifndef NO_CS4612 +#define IOTAC_SA_MASK 0x0000FFFF +#define IOTAC_MSK_MASK 0x000F0000 +#define IOTAC_IODC_MASK 0x06000000 +#define IOTAC_IODC_16_BIT 0x00000000 +#define IOTAC_IODC_10_BIT 0x02000000 +#define IOTAC_IODC_12_BIT 0x04000000 +#define IOTAC_WSPI 0x08000000 +#define IOTAC_RSPI 0x10000000 +#define IOTAC_WSE 0x20000000 +#define IOTAC_WE 0x40000000 +#define IOTAC_RE 0x80000000 +#define IOTAC_SA_SHIFT 0 +#define IOTAC_MSK_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap fast read registers + * (all 8). + */ +#ifndef NO_CS4612 +#define IOTFR_D_MASK 0x0000FFFF +#define IOTFR_A_MASK 0x000F0000 +#define IOTFR_R_MASK 0x0F000000 +#define IOTFR_ALL 0x40000000 +#define IOTFR_VL 0x80000000 +#define IOTFR_D_SHIFT 0 +#define IOTFR_A_SHIFT 16 +#define IOTFR_R_SHIFT 24 +#endif + +/* + * The following defines are for the flags in the I/O trap FIFO register. + */ +#ifndef NO_CS4612 +#define IOTFIFO_BA_MASK 0x00003FFF +#define IOTFIFO_S_MASK 0x00FF0000 +#define IOTFIFO_OF 0x40000000 +#define IOTFIFO_SPIOF 0x80000000 +#define IOTFIFO_BA_SHIFT 0 +#define IOTFIFO_S_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap retry read data + * register. + */ +#ifndef NO_CS4612 +#define IOTRRD_D_MASK 0x0000FFFF +#define IOTRRD_RDV 0x80000000 +#define IOTRRD_D_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the I/O trap FIFO pointer + * register. + */ +#ifndef NO_CS4612 +#define IOTFP_CA_MASK 0x00003FFF +#define IOTFP_PA_MASK 0x3FFF0000 +#define IOTFP_CA_SHIFT 0 +#define IOTFP_PA_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap control register. + */ +#ifndef NO_CS4612 +#define IOTCR_ITD 0x00000001 +#define IOTCR_HRV 0x00000002 +#define IOTCR_SRV 0x00000004 +#define IOTCR_DTI 0x00000008 +#define IOTCR_DFI 0x00000010 +#define IOTCR_DDP 0x00000020 +#define IOTCR_JTE 0x00000040 +#define IOTCR_PPE 0x00000080 +#endif + +/* + * The following defines are for the flags in the direct PCI data register. + */ +#ifndef NO_CS4612 +#define DPCID_D_MASK 0xFFFFFFFF +#define DPCID_D_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the direct PCI address register. + */ +#ifndef NO_CS4612 +#define DPCIA_A_MASK 0xFFFFFFFF +#define DPCIA_A_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the direct PCI command register. + */ +#ifndef NO_CS4612 +#define DPCIC_C_MASK 0x0000000F +#define DPCIC_C_IOREAD 0x00000002 +#define DPCIC_C_IOWRITE 0x00000003 +#define DPCIC_BE_MASK 0x000000F0 +#endif + +/* + * The following defines are for the flags in the PC/PCI request register. + */ +#ifndef NO_CS4612 +#define PCPCIR_RDC_MASK 0x00000007 +#define PCPCIR_C_MASK 0x00007000 +#define PCPCIR_REQ 0x00008000 +#define PCPCIR_RDC_SHIFT 0 +#define PCPCIR_C_SHIFT 12 +#endif + +/* + * The following defines are for the flags in the PC/PCI grant register. + */ +#ifndef NO_CS4612 +#define PCPCIG_GDC_MASK 0x00000007 +#define PCPCIG_VL 0x00008000 +#define PCPCIG_GDC_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the PC/PCI master enable + * register. + */ +#ifndef NO_CS4612 +#define PCPCIEN_EN 0x00000001 +#endif + +/* + * The following defines are for the flags in the extended PCI power + * management control register. + */ +#ifndef NO_CS4612 +#define EPCIPMC_GWU 0x00000001 +#define EPCIPMC_FSPC 0x00000002 +#endif + +/* + * The following defines are for the flags in the SP control register. + */ +#define SPCR_RUN 0x00000001 +#define SPCR_STPFR 0x00000002 +#define SPCR_RUNFR 0x00000004 +#define SPCR_TICK 0x00000008 +#define SPCR_DRQEN 0x00000020 +#define SPCR_RSTSP 0x00000040 +#define SPCR_OREN 0x00000080 +#ifndef NO_CS4612 +#define SPCR_PCIINT 0x00000100 +#define SPCR_OINTD 0x00000200 +#define SPCR_CRE 0x00008000 +#endif + +/* + * The following defines are for the flags in the debug index register. + */ +#define DREG_REGID_MASK 0x0000007F +#define DREG_DEBUG 0x00000080 +#define DREG_RGBK_MASK 0x00000700 +#define DREG_TRAP 0x00000800 +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_TRAPX 0x00001000 +#endif +#endif +#define DREG_REGID_SHIFT 0 +#define DREG_RGBK_SHIFT 8 +#define DREG_RGBK_REGID_MASK 0x0000077F +#define DREG_REGID_R0 0x00000010 +#define DREG_REGID_R1 0x00000011 +#define DREG_REGID_R2 0x00000012 +#define DREG_REGID_R3 0x00000013 +#define DREG_REGID_R4 0x00000014 +#define DREG_REGID_R5 0x00000015 +#define DREG_REGID_R6 0x00000016 +#define DREG_REGID_R7 0x00000017 +#define DREG_REGID_R8 0x00000018 +#define DREG_REGID_R9 0x00000019 +#define DREG_REGID_RA 0x0000001A +#define DREG_REGID_RB 0x0000001B +#define DREG_REGID_RC 0x0000001C +#define DREG_REGID_RD 0x0000001D +#define DREG_REGID_RE 0x0000001E +#define DREG_REGID_RF 0x0000001F +#define DREG_REGID_RA_BUS_LOW 0x00000020 +#define DREG_REGID_RA_BUS_HIGH 0x00000038 +#define DREG_REGID_YBUS_LOW 0x00000050 +#define DREG_REGID_YBUS_HIGH 0x00000058 +#define DREG_REGID_TRAP_0 0x00000100 +#define DREG_REGID_TRAP_1 0x00000101 +#define DREG_REGID_TRAP_2 0x00000102 +#define DREG_REGID_TRAP_3 0x00000103 +#define DREG_REGID_TRAP_4 0x00000104 +#define DREG_REGID_TRAP_5 0x00000105 +#define DREG_REGID_TRAP_6 0x00000106 +#define DREG_REGID_TRAP_7 0x00000107 +#define DREG_REGID_INDIRECT_ADDRESS 0x0000010E +#define DREG_REGID_TOP_OF_STACK 0x0000010F +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_REGID_TRAP_8 0x00000110 +#define DREG_REGID_TRAP_9 0x00000111 +#define DREG_REGID_TRAP_10 0x00000112 +#define DREG_REGID_TRAP_11 0x00000113 +#define DREG_REGID_TRAP_12 0x00000114 +#define DREG_REGID_TRAP_13 0x00000115 +#define DREG_REGID_TRAP_14 0x00000116 +#define DREG_REGID_TRAP_15 0x00000117 +#define DREG_REGID_TRAP_16 0x00000118 +#define DREG_REGID_TRAP_17 0x00000119 +#define DREG_REGID_TRAP_18 0x0000011A +#define DREG_REGID_TRAP_19 0x0000011B +#define DREG_REGID_TRAP_20 0x0000011C +#define DREG_REGID_TRAP_21 0x0000011D +#define DREG_REGID_TRAP_22 0x0000011E +#define DREG_REGID_TRAP_23 0x0000011F +#endif +#endif +#define DREG_REGID_RSA0_LOW 0x00000200 +#define DREG_REGID_RSA0_HIGH 0x00000201 +#define DREG_REGID_RSA1_LOW 0x00000202 +#define DREG_REGID_RSA1_HIGH 0x00000203 +#define DREG_REGID_RSA2 0x00000204 +#define DREG_REGID_RSA3 0x00000205 +#define DREG_REGID_RSI0_LOW 0x00000206 +#define DREG_REGID_RSI0_HIGH 0x00000207 +#define DREG_REGID_RSI1 0x00000208 +#define DREG_REGID_RSI2 0x00000209 +#define DREG_REGID_SAGUSTATUS 0x0000020A +#define DREG_REGID_RSCONFIG01_LOW 0x0000020B +#define DREG_REGID_RSCONFIG01_HIGH 0x0000020C +#define DREG_REGID_RSCONFIG23_LOW 0x0000020D +#define DREG_REGID_RSCONFIG23_HIGH 0x0000020E +#define DREG_REGID_RSDMA01E 0x0000020F +#define DREG_REGID_RSDMA23E 0x00000210 +#define DREG_REGID_RSD0_LOW 0x00000211 +#define DREG_REGID_RSD0_HIGH 0x00000212 +#define DREG_REGID_RSD1_LOW 0x00000213 +#define DREG_REGID_RSD1_HIGH 0x00000214 +#define DREG_REGID_RSD2_LOW 0x00000215 +#define DREG_REGID_RSD2_HIGH 0x00000216 +#define DREG_REGID_RSD3_LOW 0x00000217 +#define DREG_REGID_RSD3_HIGH 0x00000218 +#define DREG_REGID_SRAR_HIGH 0x0000021A +#define DREG_REGID_SRAR_LOW 0x0000021B +#define DREG_REGID_DMA_STATE 0x0000021C +#define DREG_REGID_CURRENT_DMA_STREAM 0x0000021D +#define DREG_REGID_NEXT_DMA_STREAM 0x0000021E +#define DREG_REGID_CPU_STATUS 0x00000300 +#define DREG_REGID_MAC_MODE 0x00000301 +#define DREG_REGID_STACK_AND_REPEAT 0x00000302 +#define DREG_REGID_INDEX0 0x00000304 +#define DREG_REGID_INDEX1 0x00000305 +#define DREG_REGID_DMA_STATE_0_3 0x00000400 +#define DREG_REGID_DMA_STATE_4_7 0x00000404 +#define DREG_REGID_DMA_STATE_8_11 0x00000408 +#define DREG_REGID_DMA_STATE_12_15 0x0000040C +#define DREG_REGID_DMA_STATE_16_19 0x00000410 +#define DREG_REGID_DMA_STATE_20_23 0x00000414 +#define DREG_REGID_DMA_STATE_24_27 0x00000418 +#define DREG_REGID_DMA_STATE_28_31 0x0000041C +#define DREG_REGID_DMA_STATE_32_35 0x00000420 +#define DREG_REGID_DMA_STATE_36_39 0x00000424 +#define DREG_REGID_DMA_STATE_40_43 0x00000428 +#define DREG_REGID_DMA_STATE_44_47 0x0000042C +#define DREG_REGID_DMA_STATE_48_51 0x00000430 +#define DREG_REGID_DMA_STATE_52_55 0x00000434 +#define DREG_REGID_DMA_STATE_56_59 0x00000438 +#define DREG_REGID_DMA_STATE_60_63 0x0000043C +#define DREG_REGID_DMA_STATE_64_67 0x00000440 +#define DREG_REGID_DMA_STATE_68_71 0x00000444 +#define DREG_REGID_DMA_STATE_72_75 0x00000448 +#define DREG_REGID_DMA_STATE_76_79 0x0000044C +#define DREG_REGID_DMA_STATE_80_83 0x00000450 +#define DREG_REGID_DMA_STATE_84_87 0x00000454 +#define DREG_REGID_DMA_STATE_88_91 0x00000458 +#define DREG_REGID_DMA_STATE_92_95 0x0000045C +#define DREG_REGID_TRAP_SELECT 0x00000500 +#define DREG_REGID_TRAP_WRITE_0 0x00000500 +#define DREG_REGID_TRAP_WRITE_1 0x00000501 +#define DREG_REGID_TRAP_WRITE_2 0x00000502 +#define DREG_REGID_TRAP_WRITE_3 0x00000503 +#define DREG_REGID_TRAP_WRITE_4 0x00000504 +#define DREG_REGID_TRAP_WRITE_5 0x00000505 +#define DREG_REGID_TRAP_WRITE_6 0x00000506 +#define DREG_REGID_TRAP_WRITE_7 0x00000507 +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_REGID_TRAP_WRITE_8 0x00000510 +#define DREG_REGID_TRAP_WRITE_9 0x00000511 +#define DREG_REGID_TRAP_WRITE_10 0x00000512 +#define DREG_REGID_TRAP_WRITE_11 0x00000513 +#define DREG_REGID_TRAP_WRITE_12 0x00000514 +#define DREG_REGID_TRAP_WRITE_13 0x00000515 +#define DREG_REGID_TRAP_WRITE_14 0x00000516 +#define DREG_REGID_TRAP_WRITE_15 0x00000517 +#define DREG_REGID_TRAP_WRITE_16 0x00000518 +#define DREG_REGID_TRAP_WRITE_17 0x00000519 +#define DREG_REGID_TRAP_WRITE_18 0x0000051A +#define DREG_REGID_TRAP_WRITE_19 0x0000051B +#define DREG_REGID_TRAP_WRITE_20 0x0000051C +#define DREG_REGID_TRAP_WRITE_21 0x0000051D +#define DREG_REGID_TRAP_WRITE_22 0x0000051E +#define DREG_REGID_TRAP_WRITE_23 0x0000051F +#endif +#endif +#define DREG_REGID_MAC0_ACC0_LOW 0x00000600 +#define DREG_REGID_MAC0_ACC1_LOW 0x00000601 +#define DREG_REGID_MAC0_ACC2_LOW 0x00000602 +#define DREG_REGID_MAC0_ACC3_LOW 0x00000603 +#define DREG_REGID_MAC1_ACC0_LOW 0x00000604 +#define DREG_REGID_MAC1_ACC1_LOW 0x00000605 +#define DREG_REGID_MAC1_ACC2_LOW 0x00000606 +#define DREG_REGID_MAC1_ACC3_LOW 0x00000607 +#define DREG_REGID_MAC0_ACC0_MID 0x00000608 +#define DREG_REGID_MAC0_ACC1_MID 0x00000609 +#define DREG_REGID_MAC0_ACC2_MID 0x0000060A +#define DREG_REGID_MAC0_ACC3_MID 0x0000060B +#define DREG_REGID_MAC1_ACC0_MID 0x0000060C +#define DREG_REGID_MAC1_ACC1_MID 0x0000060D +#define DREG_REGID_MAC1_ACC2_MID 0x0000060E +#define DREG_REGID_MAC1_ACC3_MID 0x0000060F +#define DREG_REGID_MAC0_ACC0_HIGH 0x00000610 +#define DREG_REGID_MAC0_ACC1_HIGH 0x00000611 +#define DREG_REGID_MAC0_ACC2_HIGH 0x00000612 +#define DREG_REGID_MAC0_ACC3_HIGH 0x00000613 +#define DREG_REGID_MAC1_ACC0_HIGH 0x00000614 +#define DREG_REGID_MAC1_ACC1_HIGH 0x00000615 +#define DREG_REGID_MAC1_ACC2_HIGH 0x00000616 +#define DREG_REGID_MAC1_ACC3_HIGH 0x00000617 +#define DREG_REGID_RSHOUT_LOW 0x00000620 +#define DREG_REGID_RSHOUT_MID 0x00000628 +#define DREG_REGID_RSHOUT_HIGH 0x00000630 + +/* + * The following defines are for the flags in the DMA stream requestor write + */ +#define DSRWP_DSR_MASK 0x0000000F +#define DSRWP_DSR_BG_RQ 0x00000001 +#define DSRWP_DSR_PRIORITY_MASK 0x00000006 +#define DSRWP_DSR_PRIORITY_0 0x00000000 +#define DSRWP_DSR_PRIORITY_1 0x00000002 +#define DSRWP_DSR_PRIORITY_2 0x00000004 +#define DSRWP_DSR_PRIORITY_3 0x00000006 +#define DSRWP_DSR_RQ_PENDING 0x00000008 + +/* + * The following defines are for the flags in the trap write port register. + */ +#define TWPR_TW_MASK 0x0000FFFF +#define TWPR_TW_SHIFT 0 + +/* + * The following defines are for the flags in the stack pointer write + * register. + */ +#define SPWR_STKP_MASK 0x0000000F +#define SPWR_STKP_SHIFT 0 + +/* + * The following defines are for the flags in the SP interrupt register. + */ +#define SPIR_FRI 0x00000001 +#define SPIR_DOI 0x00000002 +#define SPIR_GPI2 0x00000004 +#define SPIR_GPI3 0x00000008 +#define SPIR_IP0 0x00000010 +#define SPIR_IP1 0x00000020 +#define SPIR_IP2 0x00000040 +#define SPIR_IP3 0x00000080 + +/* + * The following defines are for the flags in the functional group 1 register. + */ +#define FGR1_F1S_MASK 0x0000FFFF +#define FGR1_F1S_SHIFT 0 + +/* + * The following defines are for the flags in the SP clock status register. + */ +#define SPCS_FRI 0x00000001 +#define SPCS_DOI 0x00000002 +#define SPCS_GPI2 0x00000004 +#define SPCS_GPI3 0x00000008 +#define SPCS_IP0 0x00000010 +#define SPCS_IP1 0x00000020 +#define SPCS_IP2 0x00000040 +#define SPCS_IP3 0x00000080 +#define SPCS_SPRUN 0x00000100 +#define SPCS_SLEEP 0x00000200 +#define SPCS_FG 0x00000400 +#define SPCS_ORUN 0x00000800 +#define SPCS_IRQ 0x00001000 +#define SPCS_FGN_MASK 0x0000E000 +#define SPCS_FGN_SHIFT 13 + +/* + * The following defines are for the flags in the SP DMA requestor status + * register. + */ +#define SDSR_DCS_MASK 0x000000FF +#define SDSR_DCS_SHIFT 0 +#define SDSR_DCS_NONE 0x00000007 + +/* + * The following defines are for the flags in the frame timer register. + */ +#define FRMT_FTV_MASK 0x0000FFFF +#define FRMT_FTV_SHIFT 0 + +/* + * The following defines are for the flags in the frame timer current count + * register. + */ +#define FRCC_FCC_MASK 0x0000FFFF +#define FRCC_FCC_SHIFT 0 + +/* + * The following defines are for the flags in the frame timer save count + * register. + */ +#define FRSC_FCS_MASK 0x0000FFFF +#define FRSC_FCS_SHIFT 0 + +/* + * The following define the various flags stored in the scatter/gather + * descriptors. + */ +#define DMA_SG_NEXT_ENTRY_MASK 0x00000FF8 +#define DMA_SG_SAMPLE_END_MASK 0x0FFF0000 +#define DMA_SG_SAMPLE_END_FLAG 0x10000000 +#define DMA_SG_LOOP_END_FLAG 0x20000000 +#define DMA_SG_SIGNAL_END_FLAG 0x40000000 +#define DMA_SG_SIGNAL_PAGE_FLAG 0x80000000 +#define DMA_SG_NEXT_ENTRY_SHIFT 3 +#define DMA_SG_SAMPLE_END_SHIFT 16 + +/* + * The following define the offsets of the fields within the on-chip generic + * DMA requestor. + */ +#define DMA_RQ_CONTROL1 0x00000000 +#define DMA_RQ_CONTROL2 0x00000004 +#define DMA_RQ_SOURCE_ADDR 0x00000008 +#define DMA_RQ_DESTINATION_ADDR 0x0000000C +#define DMA_RQ_NEXT_PAGE_ADDR 0x00000010 +#define DMA_RQ_NEXT_PAGE_SGDESC 0x00000014 +#define DMA_RQ_LOOP_START_ADDR 0x00000018 +#define DMA_RQ_POST_LOOP_ADDR 0x0000001C +#define DMA_RQ_PAGE_MAP_ADDR 0x00000020 + +/* + * The following defines are for the flags in the first control word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_C1_COUNT_MASK 0x000003FF +#define DMA_RQ_C1_DESTINATION_SCATTER 0x00001000 +#define DMA_RQ_C1_SOURCE_GATHER 0x00002000 +#define DMA_RQ_C1_DONE_FLAG 0x00004000 +#define DMA_RQ_C1_OPTIMIZE_STATE 0x00008000 +#define DMA_RQ_C1_SAMPLE_END_STATE_MASK 0x00030000 +#define DMA_RQ_C1_FULL_PAGE 0x00000000 +#define DMA_RQ_C1_BEFORE_SAMPLE_END 0x00010000 +#define DMA_RQ_C1_PAGE_MAP_ERROR 0x00020000 +#define DMA_RQ_C1_AT_SAMPLE_END 0x00030000 +#define DMA_RQ_C1_LOOP_END_STATE_MASK 0x000C0000 +#define DMA_RQ_C1_NOT_LOOP_END 0x00000000 +#define DMA_RQ_C1_BEFORE_LOOP_END 0x00040000 +#define DMA_RQ_C1_2PAGE_LOOP_BEGIN 0x00080000 +#define DMA_RQ_C1_LOOP_BEGIN 0x000C0000 +#define DMA_RQ_C1_PAGE_MAP_MASK 0x00300000 +#define DMA_RQ_C1_PM_NONE_PENDING 0x00000000 +#define DMA_RQ_C1_PM_NEXT_PENDING 0x00100000 +#define DMA_RQ_C1_PM_RESERVED 0x00200000 +#define DMA_RQ_C1_PM_LOOP_NEXT_PENDING 0x00300000 +#define DMA_RQ_C1_WRITEBACK_DEST_FLAG 0x00400000 +#define DMA_RQ_C1_WRITEBACK_SRC_FLAG 0x00800000 +#define DMA_RQ_C1_DEST_SIZE_MASK 0x07000000 +#define DMA_RQ_C1_DEST_LINEAR 0x00000000 +#define DMA_RQ_C1_DEST_MOD16 0x01000000 +#define DMA_RQ_C1_DEST_MOD32 0x02000000 +#define DMA_RQ_C1_DEST_MOD64 0x03000000 +#define DMA_RQ_C1_DEST_MOD128 0x04000000 +#define DMA_RQ_C1_DEST_MOD256 0x05000000 +#define DMA_RQ_C1_DEST_MOD512 0x06000000 +#define DMA_RQ_C1_DEST_MOD1024 0x07000000 +#define DMA_RQ_C1_DEST_ON_HOST 0x08000000 +#define DMA_RQ_C1_SOURCE_SIZE_MASK 0x70000000 +#define DMA_RQ_C1_SOURCE_LINEAR 0x00000000 +#define DMA_RQ_C1_SOURCE_MOD16 0x10000000 +#define DMA_RQ_C1_SOURCE_MOD32 0x20000000 +#define DMA_RQ_C1_SOURCE_MOD64 0x30000000 +#define DMA_RQ_C1_SOURCE_MOD128 0x40000000 +#define DMA_RQ_C1_SOURCE_MOD256 0x50000000 +#define DMA_RQ_C1_SOURCE_MOD512 0x60000000 +#define DMA_RQ_C1_SOURCE_MOD1024 0x70000000 +#define DMA_RQ_C1_SOURCE_ON_HOST 0x80000000 +#define DMA_RQ_C1_COUNT_SHIFT 0 + +/* + * The following defines are for the flags in the second control word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_C2_VIRTUAL_CHANNEL_MASK 0x0000003F +#define DMA_RQ_C2_VIRTUAL_SIGNAL_MASK 0x00000300 +#define DMA_RQ_C2_NO_VIRTUAL_SIGNAL 0x00000000 +#define DMA_RQ_C2_SIGNAL_EVERY_DMA 0x00000100 +#define DMA_RQ_C2_SIGNAL_SOURCE_PINGPONG 0x00000200 +#define DMA_RQ_C2_SIGNAL_DEST_PINGPONG 0x00000300 +#define DMA_RQ_C2_AUDIO_CONVERT_MASK 0x0000F000 +#define DMA_RQ_C2_AC_NONE 0x00000000 +#define DMA_RQ_C2_AC_8_TO_16_BIT 0x00001000 +#define DMA_RQ_C2_AC_MONO_TO_STEREO 0x00002000 +#define DMA_RQ_C2_AC_ENDIAN_CONVERT 0x00004000 +#define DMA_RQ_C2_AC_SIGNED_CONVERT 0x00008000 +#define DMA_RQ_C2_LOOP_END_MASK 0x0FFF0000 +#define DMA_RQ_C2_LOOP_MASK 0x30000000 +#define DMA_RQ_C2_NO_LOOP 0x00000000 +#define DMA_RQ_C2_ONE_PAGE_LOOP 0x10000000 +#define DMA_RQ_C2_TWO_PAGE_LOOP 0x20000000 +#define DMA_RQ_C2_MULTI_PAGE_LOOP 0x30000000 +#define DMA_RQ_C2_SIGNAL_LOOP_BACK 0x40000000 +#define DMA_RQ_C2_SIGNAL_POST_BEGIN_PAGE 0x80000000 +#define DMA_RQ_C2_VIRTUAL_CHANNEL_SHIFT 0 +#define DMA_RQ_C2_LOOP_END_SHIFT 16 + +/* + * The following defines are for the flags in the source and destination words + * of the on-chip generic DMA requestor. + */ +#define DMA_RQ_SD_ADDRESS_MASK 0x0000FFFF +#define DMA_RQ_SD_MEMORY_ID_MASK 0x000F0000 +#define DMA_RQ_SD_SP_PARAM_ADDR 0x00000000 +#define DMA_RQ_SD_SP_SAMPLE_ADDR 0x00010000 +#define DMA_RQ_SD_SP_PROGRAM_ADDR 0x00020000 +#define DMA_RQ_SD_SP_DEBUG_ADDR 0x00030000 +#define DMA_RQ_SD_OMNIMEM_ADDR 0x000E0000 +#define DMA_RQ_SD_END_FLAG 0x40000000 +#define DMA_RQ_SD_ERROR_FLAG 0x80000000 +#define DMA_RQ_SD_ADDRESS_SHIFT 0 + +/* + * The following defines are for the flags in the page map address word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_PMA_LOOP_THIRD_PAGE_ENTRY_MASK 0x00000FF8 +#define DMA_RQ_PMA_PAGE_TABLE_MASK 0xFFFFF000 +#define DMA_RQ_PMA_LOOP_THIRD_PAGE_ENTRY_SHIFT 3 +#define DMA_RQ_PMA_PAGE_TABLE_SHIFT 12 + +#define BA1_VARIDEC_BUF_1 0x000 + +#define BA1_PDTC 0x0c0 /* BA1_PLAY_DMA_TRANSACTION_COUNT_REG */ +#define BA1_PFIE 0x0c4 /* BA1_PLAY_FORMAT_&_INTERRUPT_ENABLE_REG */ +#define BA1_PBA 0x0c8 /* BA1_PLAY_BUFFER_ADDRESS */ +#define BA1_PVOL 0x0f8 /* BA1_PLAY_VOLUME_REG */ +#define BA1_PSRC 0x288 /* BA1_PLAY_SAMPLE_RATE_CORRECTION_REG */ +#define BA1_PCTL 0x2a4 /* BA1_PLAY_CONTROL_REG */ +#define BA1_PPI 0x2b4 /* BA1_PLAY_PHASE_INCREMENT_REG */ + +#define BA1_CCTL 0x064 /* BA1_CAPTURE_CONTROL_REG */ +#define BA1_CIE 0x104 /* BA1_CAPTURE_INTERRUPT_ENABLE_REG */ +#define BA1_CBA 0x10c /* BA1_CAPTURE_BUFFER_ADDRESS */ +#define BA1_CSRC 0x2c8 /* BA1_CAPTURE_SAMPLE_RATE_CORRECTION_REG */ +#define BA1_CCI 0x2d8 /* BA1_CAPTURE_COEFFICIENT_INCREMENT_REG */ +#define BA1_CD 0x2e0 /* BA1_CAPTURE_DELAY_REG */ +#define BA1_CPI 0x2f4 /* BA1_CAPTURE_PHASE_INCREMENT_REG */ +#define BA1_CVOL 0x2f8 /* BA1_CAPTURE_VOLUME_REG */ + +#define BA1_CFG1 0x134 /* BA1_CAPTURE_FRAME_GROUP_1_REG */ +#define BA1_CFG2 0x138 /* BA1_CAPTURE_FRAME_GROUP_2_REG */ +#define BA1_CCST 0x13c /* BA1_CAPTURE_CONSTANT_REG */ +#define BA1_CSPB 0x340 /* BA1_CAPTURE_SPB_ADDRESS */ + +/* + * + */ + +#define CS461X_MODE_OUTPUT (1<<0) /* MIDI UART - output */ +#define CS461X_MODE_INPUT (1<<1) /* MIDI UART - input */ + +//**************************************************************************** +// +// The following define the offsets of the AC97 shadow registers, which appear +// as a virtual extension to the base address register zero memory range. +// +//**************************************************************************** +#define AC97_REG_OFFSET_MASK 0x0000007EL +#define AC97_CODEC_NUMBER_MASK 0x00003000L + +#define BA0_AC97_RESET 0x00001000L +#define BA0_AC97_MASTER_VOLUME 0x00001002L +#define BA0_AC97_HEADPHONE_VOLUME 0x00001004L +#define BA0_AC97_MASTER_VOLUME_MONO 0x00001006L +#define BA0_AC97_MASTER_TONE 0x00001008L +#define BA0_AC97_PC_BEEP_VOLUME 0x0000100AL +#define BA0_AC97_PHONE_VOLUME 0x0000100CL +#define BA0_AC97_MIC_VOLUME 0x0000100EL +#define BA0_AC97_LINE_IN_VOLUME 0x00001010L +#define BA0_AC97_CD_VOLUME 0x00001012L +#define BA0_AC97_VIDEO_VOLUME 0x00001014L +#define BA0_AC97_AUX_VOLUME 0x00001016L +#define BA0_AC97_PCM_OUT_VOLUME 0x00001018L +#define BA0_AC97_RECORD_SELECT 0x0000101AL +#define BA0_AC97_RECORD_GAIN 0x0000101CL +#define BA0_AC97_RECORD_GAIN_MIC 0x0000101EL +#define BA0_AC97_GENERAL_PURPOSE 0x00001020L +#define BA0_AC97_3D_CONTROL 0x00001022L +#define BA0_AC97_MODEM_RATE 0x00001024L +#define BA0_AC97_POWERDOWN 0x00001026L +#define BA0_AC97_EXT_AUDIO_ID 0x00001028L +#define BA0_AC97_EXT_AUDIO_POWER 0x0000102AL +#define BA0_AC97_PCM_FRONT_DAC_RATE 0x0000102CL +#define BA0_AC97_PCM_SURR_DAC_RATE 0x0000102EL +#define BA0_AC97_PCM_LFE_DAC_RATE 0x00001030L +#define BA0_AC97_PCM_LR_ADC_RATE 0x00001032L +#define BA0_AC97_MIC_ADC_RATE 0x00001034L +#define BA0_AC97_6CH_VOL_C_LFE 0x00001036L +#define BA0_AC97_6CH_VOL_SURROUND 0x00001038L +#define BA0_AC97_RESERVED_3A 0x0000103AL +#define BA0_AC97_EXT_MODEM_ID 0x0000103CL +#define BA0_AC97_EXT_MODEM_POWER 0x0000103EL +#define BA0_AC97_LINE1_CODEC_RATE 0x00001040L +#define BA0_AC97_LINE2_CODEC_RATE 0x00001042L +#define BA0_AC97_HANDSET_CODEC_RATE 0x00001044L +#define BA0_AC97_LINE1_CODEC_LEVEL 0x00001046L +#define BA0_AC97_LINE2_CODEC_LEVEL 0x00001048L +#define BA0_AC97_HANDSET_CODEC_LEVEL 0x0000104AL +#define BA0_AC97_GPIO_PIN_CONFIG 0x0000104CL +#define BA0_AC97_GPIO_PIN_TYPE 0x0000104EL +#define BA0_AC97_GPIO_PIN_STICKY 0x00001050L +#define BA0_AC97_GPIO_PIN_WAKEUP 0x00001052L +#define BA0_AC97_GPIO_PIN_STATUS 0x00001054L +#define BA0_AC97_MISC_MODEM_AFE_STAT 0x00001056L +#define BA0_AC97_RESERVED_58 0x00001058L +#define BA0_AC97_CRYSTAL_REV_N_FAB_ID 0x0000105AL +#define BA0_AC97_TEST_AND_MISC_CTRL 0x0000105CL +#define BA0_AC97_AC_MODE 0x0000105EL +#define BA0_AC97_MISC_CRYSTAL_CONTROL 0x00001060L +#define BA0_AC97_LINE1_HYPRID_CTRL 0x00001062L +#define BA0_AC97_VENDOR_RESERVED_64 0x00001064L +#define BA0_AC97_VENDOR_RESERVED_66 0x00001066L +#define BA0_AC97_SPDIF_CONTROL 0x00001068L +#define BA0_AC97_VENDOR_RESERVED_6A 0x0000106AL +#define BA0_AC97_VENDOR_RESERVED_6C 0x0000106CL +#define BA0_AC97_VENDOR_RESERVED_6E 0x0000106EL +#define BA0_AC97_VENDOR_RESERVED_70 0x00001070L +#define BA0_AC97_VENDOR_RESERVED_72 0x00001072L +#define BA0_AC97_VENDOR_RESERVED_74 0x00001074L +#define BA0_AC97_CAL_ADDRESS 0x00001076L +#define BA0_AC97_CAL_DATA 0x00001078L +#define BA0_AC97_VENDOR_RESERVED_7A 0x0000107AL +#define BA0_AC97_VENDOR_ID1 0x0000107CL +#define BA0_AC97_VENDOR_ID2 0x0000107EL +#endif /* __CS461X_H */ diff --git a/sound/oss/cs461x_image.h b/sound/oss/cs461x_image.h new file mode 100644 index 000000000000..b5c5a46d3423 --- /dev/null +++ b/sound/oss/cs461x_image.h @@ -0,0 +1,322 @@ +/**************************************************************************** + * "CWCIMAGE.H"-- For CS46XX. Ver 1.04 + * Copyright 1998-2001 (c) Cirrus Logic Corp. + * Version 1.04 + **************************************************************************** + */ +#ifndef __CS_IMAGE_H +#define __CS_IMAGE_H + +#define CLEAR__COUNT 3 +#define FILL__COUNT 4 +#define BA1__DWORD_SIZE 13*1024+512 + +static struct +{ + unsigned BA1__DestByteOffset; + unsigned BA1__SourceSize; +} ClrStat[CLEAR__COUNT] ={ {0x00000000, 0x00003000 }, + {0x00010000, 0x00003800 }, + {0x00020000, 0x00007000 } }; + +static u32 FillArray1[]={ +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000163,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00200040,0x00008010,0x00000000, +0x00000000,0x80000001,0x00000001,0x00060000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00900080,0x00000173,0x00000000, +0x00000000,0x00000010,0x00800000,0x00900000, +0xf2c0000f,0x00000200,0x00000000,0x00010600, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000163,0x330300c2, +0x06000000,0x00000000,0x80008000,0x80008000, +0x3fc0000f,0x00000301,0x00010400,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00b00000,0x00d0806d,0x330480c3, +0x04800000,0x00000001,0x00800001,0x0000ffff, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x066a0600,0x06350070,0x0000929d,0x929d929d, +0x00000000,0x0000735a,0x00000600,0x00000000, +0x929d735a,0x00000000,0x00010000,0x735a735a, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x0000804f,0x000000c3, +0x05000000,0x00a00010,0x00000000,0x80008000, +0x00000000,0x00000000,0x00000700,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000080,0x00a00000,0x0000809a,0x000000c2, +0x07400000,0x00000000,0x80008000,0xffffffff, +0x00c80028,0x00005555,0x00000000,0x000107a0, +0x00c80028,0x000000c2,0x06800000,0x00000000, +0x06e00080,0x00300000,0x000080bb,0x000000c9, +0x07a00000,0x04000000,0x80008000,0xffffffff, +0x00c80028,0x00005555,0x00000000,0x00000780, +0x00c80028,0x000000c5,0xff800000,0x00000000, +0x00640080,0x00c00000,0x00008197,0x000000c9, +0x07800000,0x04000000,0x80008000,0xffffffff, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x0000805e,0x000000c1, +0x00000000,0x00800000,0x80008000,0x80008000, +0x00020000,0x0000ffff,0x00000000,0x00000000}; + +static u32 FillArray2[]={ +0x929d0600,0x929d929d,0x929d929d,0x929d0000, +0x929d929d,0x929d929d,0x929d929d,0x929d929d, +0x929d929d,0x00100635,0x060b013f,0x00000004, +0x00000001,0x007a0002,0x00000000,0x066e0610, +0x0105929d,0x929d929d,0x929d929d,0x929d929d, +0x929d929d,0xa431ac75,0x0001735a,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0x735a0051, +0x00000000,0x929d929d,0x929d929d,0x929d929d, +0x929d929d,0x929d929d,0x929d929d,0x929d929d, +0x929d929d,0x929d929d,0x00000000,0x06400136, +0x0000270f,0x00010000,0x007a0000,0x00000000, +0x068e0645,0x0105929d,0x929d929d,0x929d929d, +0x929d929d,0x929d929d,0xa431ac75,0x0001735a, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75, +0x735a0100,0x00000000,0x00000000,0x00000000}; + +static u32 FillArray3[]={ +0x00000000,0x00000000,0x00000000,0x00010004}; + +static u32 FillArray4[]={ +0x00040730,0x00001002,0x000f619e,0x00001003, +0x00001705,0x00001400,0x000a411e,0x00001003, +0x00040730,0x00001002,0x000f619e,0x00001003, +0x00009705,0x00001400,0x000a411e,0x00001003, +0x00040730,0x00001002,0x000f619e,0x00001003, +0x00011705,0x00001400,0x000a411e,0x00001003, +0x00040730,0x00001002,0x000f619e,0x00001003, +0x00019705,0x00001400,0x000a411e,0x00001003, +0x00040730,0x00001002,0x000f619e,0x00001003, +0x00021705,0x00001400,0x000a411e,0x00001003, +0x00040730,0x00001002,0x000f619e,0x00001003, +0x00029705,0x00001400,0x000a411e,0x00001003, +0x00040730,0x00001002,0x000f619e,0x00001003, +0x00031705,0x00001400,0x000a411e,0x00001003, +0x00040730,0x00001002,0x000f619e,0x00001003, +0x00039705,0x00001400,0x000a411e,0x00001003, +0x000fe19e,0x00001003,0x0009c730,0x00001003, +0x0008e19c,0x00001003,0x000083c1,0x00093040, +0x00098730,0x00001002,0x000ee19e,0x00001003, +0x00009705,0x00001400,0x000a211e,0x00001003, +0x00098730,0x00001002,0x000ee19e,0x00001003, +0x00011705,0x00001400,0x000a211e,0x00001003, +0x00098730,0x00001002,0x000ee19e,0x00001003, +0x00019705,0x00001400,0x000a211e,0x00001003, +0x00098730,0x00001002,0x000ee19e,0x00001003, +0x00021705,0x00001400,0x000a211e,0x00001003, +0x00098730,0x00001002,0x000ee19e,0x00001003, +0x00029705,0x00001400,0x000a211e,0x00001003, +0x00098730,0x00001002,0x000ee19e,0x00001003, +0x00031705,0x00001400,0x000a211e,0x00001003, +0x00098730,0x00001002,0x000ee19e,0x00001003, +0x00039705,0x00001400,0x000a211e,0x00001003, +0x0000a730,0x00001008,0x000e2730,0x00001002, +0x0000a731,0x00001002,0x0000a731,0x00001002, +0x0000a731,0x00001002,0x0000a731,0x00001002, +0x0000a731,0x00001002,0x0000a731,0x00001002, +0x00000000,0x00000000,0x000f619c,0x00001003, +0x0007f801,0x000c0000,0x00000037,0x00001000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x000c0000,0x00000000,0x00000000, +0x0000373c,0x00001000,0x00000000,0x00000000, +0x000ee19c,0x00001003,0x0007f801,0x000c0000, +0x00000037,0x00001000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x0000273c,0x00001000, +0x00000033,0x00001000,0x000e679e,0x00001003, +0x00007705,0x00001400,0x000ac71e,0x00001003, +0x00087fc1,0x000c3be0,0x0007f801,0x000c0000, +0x00000037,0x00001000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x0000a730,0x00001003, +0x00000033,0x00001000,0x0007f801,0x000c0000, +0x00000037,0x00001000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x000c0000, +0x00000032,0x00001000,0x0000273d,0x00001000, +0x0004a730,0x00001003,0x00000f41,0x00097140, +0x0000a841,0x0009b240,0x0000a0c1,0x0009f040, +0x0001c641,0x00093540,0x0001cec1,0x0009b5c0, +0x00000000,0x00000000,0x0001bf05,0x0003fc40, +0x00002725,0x000aa400,0x00013705,0x00093a00, +0x0000002e,0x0009d6c0,0x00038630,0x00001004, +0x0004ef0a,0x000eb785,0x0003fc8a,0x00000000, +0x00000000,0x000c70e0,0x0007d182,0x0002c640, +0x00000630,0x00001004,0x000799b8,0x0002c6c0, +0x00031705,0x00092240,0x00039f05,0x000932c0, +0x0003520a,0x00000000,0x00040731,0x0000100b, +0x00010705,0x000b20c0,0x00000000,0x000eba44, +0x00032108,0x000c60c4,0x00065208,0x000c2917, +0x000406b0,0x00001007,0x00012f05,0x00036880, +0x0002818e,0x000c0000,0x0004410a,0x00000000, +0x00040630,0x00001007,0x00029705,0x000c0000, +0x00000000,0x00000000,0x00003fc1,0x0003fc40, +0x000037c1,0x00091b40,0x00003fc1,0x000911c0, +0x000037c1,0x000957c0,0x00003fc1,0x000951c0, +0x000037c1,0x00000000,0x00003fc1,0x000991c0, +0x000037c1,0x00000000,0x00003fc1,0x0009d1c0, +0x000037c1,0x00000000,0x0001ccc1,0x000915c0, +0x0001c441,0x0009d800,0x0009cdc1,0x00091240, +0x0001c541,0x00091d00,0x0009cfc1,0x00095240, +0x0001c741,0x00095c80,0x000e8ca9,0x00099240, +0x000e85ad,0x00095640,0x00069ca9,0x00099d80, +0x000e952d,0x00099640,0x000eaca9,0x0009d6c0, +0x000ea5ad,0x00091a40,0x0006bca9,0x0009de80, +0x000eb52d,0x00095a40,0x000ecca9,0x00099ac0, +0x000ec5ad,0x0009da40,0x000edca9,0x0009d300, +0x000a6e0a,0x00001000,0x000ed52d,0x00091e40, +0x000eeca9,0x00095ec0,0x000ee5ad,0x00099e40, +0x0006fca9,0x00002500,0x000fb208,0x000c59a0, +0x000ef52d,0x0009de40,0x00068ca9,0x000912c1, +0x000683ad,0x00095241,0x00020f05,0x000991c1, +0x00000000,0x00000000,0x00086f88,0x00001000, +0x0009cf81,0x000b5340,0x0009c701,0x000b92c0, +0x0009de81,0x000bd300,0x0009d601,0x000b1700, +0x0001fd81,0x000b9d80,0x0009f501,0x000b57c0, +0x000a0f81,0x000bd740,0x00020701,0x000b5c80, +0x000a1681,0x000b97c0,0x00021601,0x00002500, +0x000a0701,0x000b9b40,0x000a0f81,0x000b1bc0, +0x00021681,0x00002d00,0x00020f81,0x000bd800, +0x000a0701,0x000b5bc0,0x00021601,0x00003500, +0x000a0f81,0x000b5f40,0x000a0701,0x000bdbc0, +0x00021681,0x00003d00,0x00020f81,0x000b1d00, +0x000a0701,0x000b1fc0,0x00021601,0x00020500, +0x00020f81,0x000b1341,0x000a0701,0x000b9fc0, +0x00021681,0x00020d00,0x00020f81,0x000bde80, +0x000a0701,0x000bdfc0,0x00021601,0x00021500, +0x00020f81,0x000b9341,0x00020701,0x000b53c1, +0x00021681,0x00021d00,0x000a0f81,0x000d0380, +0x0000b601,0x000b15c0,0x00007b01,0x00000000, +0x00007b81,0x000bd1c0,0x00007b01,0x00000000, +0x00007b81,0x000b91c0,0x00007b01,0x000b57c0, +0x00007b81,0x000b51c0,0x00007b01,0x000b1b40, +0x00007b81,0x000b11c0,0x00087b01,0x000c3dc0, +0x0007e488,0x000d7e45,0x00000000,0x000d7a44, +0x0007e48a,0x00000000,0x00011f05,0x00084080, +0x00000000,0x00000000,0x00001705,0x000b3540, +0x00008a01,0x000bf040,0x00007081,0x000bb5c0, +0x00055488,0x00000000,0x0000d482,0x0003fc40, +0x0003fc88,0x00000000,0x0001e401,0x000b3a00, +0x0001ec81,0x000bd6c0,0x0004ef08,0x000eb784, +0x000c86b0,0x00001007,0x00008281,0x000bb240, +0x0000b801,0x000b7140,0x00007888,0x00000000, +0x0000073c,0x00001000,0x0007f188,0x000c0000, +0x00000000,0x00000000,0x00055288,0x000c555c, +0x0005528a,0x000c0000,0x0009fa88,0x000c5d00, +0x0000fa88,0x00000000,0x00000032,0x00001000, +0x0000073d,0x00001000,0x0007f188,0x000c0000, +0x00000000,0x00000000,0x0008c01c,0x00001003, +0x00002705,0x00001008,0x0008b201,0x000c1392, +0x0000ba01,0x00000000,0x00008731,0x00001400, +0x0004c108,0x000fe0c4,0x00057488,0x00000000, +0x000a6388,0x00001001,0x0008b334,0x000bc141, +0x0003020e,0x00000000,0x000886b0,0x00001008, +0x00003625,0x000c5dfa,0x000a638a,0x00001001, +0x0008020e,0x00001002,0x0008a6b0,0x00001008, +0x0007f301,0x00000000,0x00000000,0x00000000, +0x00002725,0x000a8c40,0x000000ae,0x00000000, +0x000d8630,0x00001008,0x00000000,0x000c74e0, +0x0007d182,0x0002d640,0x000a8630,0x00001008, +0x000799b8,0x0002d6c0,0x0000748a,0x000c3ec5, +0x0007420a,0x000c0000,0x00062208,0x000c4117, +0x00070630,0x00001009,0x00000000,0x000c0000, +0x0001022e,0x00000000,0x0003a630,0x00001009, +0x00000000,0x000c0000,0x00000036,0x00001000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x0002a730,0x00001008,0x0007f801,0x000c0000, +0x00000037,0x00001000,0x00000000,0x00000000, +0x00000000,0x00000000,0x00000000,0x00000000, +0x00000000,0x00000000,0x0002a730,0x00001008, +0x00000033,0x00001000,0x0002a705,0x00001008, +0x00007a01,0x000c0000,0x000e6288,0x000d550a, +0x0006428a,0x00000000,0x00060730,0x0000100a, +0x00000000,0x000c0000,0x00000000,0x00000000, +0x0007aab0,0x00034880,0x00078fb0,0x0000100b, +0x00057488,0x00000000,0x00033b94,0x00081140, +0x000183ae,0x00000000,0x000786b0,0x0000100b, +0x00022f05,0x000c3545,0x0000eb8a,0x00000000, +0x00042731,0x00001003,0x0007aab0,0x00034880, +0x00048fb0,0x0000100a,0x00057488,0x00000000, +0x00033b94,0x00081140,0x000183ae,0x00000000, +0x000806b0,0x0000100b,0x00022f05,0x00000000, +0x00007401,0x00091140,0x00048f05,0x000951c0, +0x00042731,0x00001003,0x0000473d,0x00001000, +0x000f19b0,0x000bbc47,0x00080000,0x000bffc7, +0x000fe19e,0x00001003,0x00000000,0x00000000, +0x0008e19c,0x00001003,0x000083c1,0x00093040, +0x00000f41,0x00097140,0x0000a841,0x0009b240, +0x0000a0c1,0x0009f040,0x0001c641,0x00093540, +0x0001cec1,0x0009b5c0,0x00000000,0x000fdc44, +0x00055208,0x00000000,0x00010705,0x000a2880, +0x0000a23a,0x00093a00,0x0003fc8a,0x000df6c5, +0x0004ef0a,0x000c0000,0x00012f05,0x00036880, +0x00065308,0x000c2997,0x000d86b0,0x0000100a, +0x0004410a,0x000d40c7,0x00000000,0x00000000, +0x00080730,0x00001004,0x00056f0a,0x000ea105, +0x00000000,0x00000000,0x0000473d,0x00001000, +0x000f19b0,0x000bbc47,0x00080000,0x000bffc7, +0x0000273d,0x00001000,0x00000000,0x000eba44, +0x00048f05,0x0000f440,0x00007401,0x0000f7c0, +0x00000734,0x00001000,0x00010705,0x000a6880, +0x00006a88,0x000c75c4,0x00000000,0x000e5084, +0x00000000,0x000eba44,0x00087401,0x000e4782, +0x00000734,0x00001000,0x00010705,0x000a6880, +0x00006a88,0x000c75c4,0x0007c108,0x000c0000, +0x0007e721,0x000bed40,0x00005f25,0x000badc0, +0x0003ba97,0x000beb80,0x00065590,0x000b2e00, +0x00033217,0x00003ec0,0x00065590,0x000b8e40, +0x0003ed80,0x000491c0,0x00073fb0,0x00074c80, +0x000283a0,0x0000100c,0x000ee388,0x00042970, +0x00008301,0x00021ef2,0x000b8f14,0x0000000f, +0x000c4d8d,0x0000001b,0x000d6dc2,0x000e06c6, +0x000032ac,0x000c3916,0x0004edc2,0x00074c80, +0x00078898,0x00001000,0x00038894,0x00000032, +0x000c4d8d,0x00092e1b,0x000d6dc2,0x000e06c6, +0x0004edc2,0x000c1956,0x0000722c,0x00034a00, +0x00041705,0x0009ed40,0x00058730,0x00001400, +0x000d7488,0x000c3a00,0x00048f05,0x00000000}; + +static struct +{ u32 Offset; + u32 Size; + u32 *pFill; +} FillStat[FILL__COUNT] = { + {0x00000000, sizeof(FillArray1), FillArray1}, + {0x00001800, sizeof(FillArray2), FillArray2}, + {0x000137f0, sizeof(FillArray3), FillArray3}, + {0x00020000, sizeof(FillArray4), FillArray4} + }; + + +#endif diff --git a/sound/oss/cs46xx.c b/sound/oss/cs46xx.c new file mode 100644 index 000000000000..8ce6b48f1881 --- /dev/null +++ b/sound/oss/cs46xx.c @@ -0,0 +1,5794 @@ +/* + * Crystal SoundFusion CS46xx driver + * + * Copyright 1998-2001 Cirrus Logic Corporation + * + * Copyright 1999-2000 Jaroslav Kysela + * Copyright 2000 Alan Cox + * + * The core of this code is taken from the ALSA project driver by + * Jaroslav. Please send Jaroslav the credit for the driver and + * report bugs in this port to + * + * 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. + * Current maintainers: + * Cirrus Logic Corporation, Thomas Woller (tw) + * + * Nils Faerber (nf) + * + * Thanks to David Pollard for testing. + * + * Changes: + * 20000909-nf Changed cs_read, cs_write and drain_dac + * 20001025-tw Separate Playback/Capture structs and buffers. + * Added Scatter/Gather support for Playback. + * Added Capture. + * 20001027-nf Port to kernel 2.4.0-test9, some clean-ups + * Start of powermanagement support (CS46XX_PM). + * 20001128-tw Add module parm for default buffer order. + * added DMA_GFP flag to kmalloc dma buffer allocs. + * backfill silence to eliminate stuttering on + * underruns. + * 20001201-tw add resyncing of swptr on underruns. + * 20001205-tw-nf fixed GETOSPACE ioctl() after open() + * 20010113-tw patch from Hans Grobler general cleanup. + * 20010117-tw 2.4.0 pci cleanup, wrapper code for 2.2.16-2.4.0 + * 20010118-tw basic PM support for 2.2.16+ and 2.4.0/2.4.2. + * 20010228-dh patch from David Huggins - cs_update_ptr recursion. + * 20010409-tw add hercules game theatre XP amp code. + * 20010420-tw cleanup powerdown/up code. + * 20010521-tw eliminate pops, and fixes for powerdown. + * 20010525-tw added fixes for thinkpads with powerdown logic. + * 20010723-sh patch from Horms (Simon Horman) - + * SOUND_PCM_READ_BITS returns bits as set in driver + * rather than a logical or of the possible values. + * Various ioctls handle the case where the device + * is open for reading or writing but not both better. + * + * Status: + * Playback/Capture supported from 8k-48k. + * 16Bit Signed LE & 8Bit Unsigned, with Mono or Stereo supported. + * + * APM/PM - 2.2.x APM is enabled and functioning fine. APM can also + * be enabled for 2.4.x by modifying the CS46XX_ACPI_SUPPORT macro + * definition. + * + * Hercules Game Theatre XP - the EGPIO2 pin controls the external Amp, + * so, use the drain/polarity to enable. + * hercules_egpio_disable set to 1, will force a 0 to EGPIODR. + * + * VTB Santa Cruz - the GPIO7/GPIO8 on the Secondary Codec control + * the external amplifier for the "back" speakers, since we do not + * support the secondary codec then this external amp is also not + * turned on. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "cs46xxpm-24.h" +#include "cs46xx_wrapper-24.h" +#include "cs461x.h" + +/* MIDI buffer sizes */ +#define CS_MIDIINBUF 500 +#define CS_MIDIOUTBUF 500 + +#define ADC_RUNNING 1 +#define DAC_RUNNING 2 + +#define CS_FMT_16BIT 1 /* These are fixed in fact */ +#define CS_FMT_STEREO 2 +#define CS_FMT_MASK 3 + +#define CS_TYPE_ADC 1 +#define CS_TYPE_DAC 2 + +#define CS_TRUE 1 +#define CS_FALSE 0 + +#define CS_INC_USE_COUNT(m) (atomic_inc(m)) +#define CS_DEC_USE_COUNT(m) (atomic_dec(m)) +#define CS_DEC_AND_TEST(m) (atomic_dec_and_test(m)) +#define CS_IN_USE(m) (atomic_read(m) != 0) + +#define CS_DBGBREAKPOINT {__asm__("INT $3");} +/* + * CS461x definitions + */ + +#define CS461X_BA0_SIZE 0x2000 +#define CS461X_BA1_DATA0_SIZE 0x3000 +#define CS461X_BA1_DATA1_SIZE 0x3800 +#define CS461X_BA1_PRG_SIZE 0x7000 +#define CS461X_BA1_REG_SIZE 0x0100 + +#define GOF_PER_SEC 200 + +#define CSDEBUG_INTERFACE 1 +#define CSDEBUG 1 +/* + * Turn on/off debugging compilation by using 1/0 respectively for CSDEBUG + * + * + * 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 +/* + * 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_MIDI_WRITE 0x00000040 /* write information for midi */ +#define CS_MIDI_READ 0x00000080 /* read information for midi */ +#define CS_MPU401_WRITE 0x00000100 /* write information for mpu401 */ +#define CS_MPU401_READ 0x00000200 /* read information for mpu401 */ +#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_PM 0x00004000 /* PM */ +#define CS_TMP 0x10000000 /* tmp debug mask bit */ + +#define CS_IOCTL_CMD_SUSPEND 0x1 // suspend +#define CS_IOCTL_CMD_RESUME 0x2 // resume + +#if CSDEBUG +static unsigned long cs_debuglevel=1; /* levels range from 1-9 */ +module_param(cs_debuglevel, ulong, 0644); +static unsigned long cs_debugmask=CS_INIT | CS_ERROR; /* use CS_DBGOUT with various mask values */ +module_param(cs_debugmask, ulong, 0644); +#endif +static unsigned long hercules_egpio_disable; /* if non-zero set all EGPIO to 0 */ +module_param(hercules_egpio_disable, ulong, 0); +static unsigned long initdelay=700; /* PM delay in millisecs */ +module_param(initdelay, ulong, 0); +static unsigned long powerdown=-1; /* turn on/off powerdown processing in driver */ +module_param(powerdown, ulong, 0); +#define DMABUF_DEFAULTORDER 3 +static unsigned long defaultorder=DMABUF_DEFAULTORDER; +module_param(defaultorder, ulong, 0); + +static int external_amp; +module_param(external_amp, bool, 0); +static int thinkpad; +module_param(thinkpad, bool, 0); + +/* +* set the powerdown module parm to 0 to disable all +* powerdown. also set thinkpad to 1 to disable powerdown, +* but also to enable the clkrun functionality. +*/ +static unsigned cs_powerdown=1; +static unsigned cs_laptop_wait=1; + +/* An instance of the 4610 channel */ +struct cs_channel +{ + int used; + int num; + void *state; +}; + +#define CS46XX_MAJOR_VERSION "1" +#define CS46XX_MINOR_VERSION "28" + +#ifdef __ia64__ +#define CS46XX_ARCH "64" //architecture key +#else +#define CS46XX_ARCH "32" //architecture key +#endif + +static struct list_head cs46xx_devs = { &cs46xx_devs, &cs46xx_devs }; + +/* magic numbers to protect our data structures */ +#define CS_CARD_MAGIC 0x43525553 /* "CRUS" */ +#define CS_STATE_MAGIC 0x4c4f4749 /* "LOGI" */ +#define NR_HW_CH 3 + +/* maxinum number of AC97 codecs connected, AC97 2.0 defined 4 */ +#define NR_AC97 2 + +static const unsigned sample_size[] = { 1, 2, 2, 4 }; +static const unsigned sample_shift[] = { 0, 1, 1, 2 }; + +/* "software" or virtual channel, an instance of opened /dev/dsp */ +struct cs_state { + unsigned int magic; + struct cs_card *card; /* Card info */ + + /* single open lock mechanism, only used for recording */ + struct semaphore open_sem; + wait_queue_head_t open_wait; + + /* file mode */ + mode_t open_mode; + + /* virtual channel number */ + int virt; + + struct dmabuf { + /* wave sample stuff */ + unsigned int rate; + unsigned char fmt, enable; + + /* hardware channel */ + struct cs_channel *channel; + int pringbuf; /* Software ring slot */ + void *pbuf; /* 4K hardware DMA buffer */ + + /* OSS buffer management stuff */ + void *rawbuf; + dma_addr_t dma_handle; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + unsigned divisor; + unsigned type; + void *tmpbuff; /* tmp buffer for sample conversions */ + dma_addr_t dmaaddr; + dma_addr_t dmaaddr_tmpbuff; + unsigned buforder_tmpbuff; /* Log base 2 of size in bytes.. */ + + /* our buffer acts like a circular ring */ + unsigned hwptr; /* where dma last started, updated by update_ptr */ + unsigned swptr; /* where driver last clear/filled, updated by read/write */ + int count; /* bytes to be comsumed or been generated by dma machine */ + unsigned total_bytes; /* total bytes dmaed by hardware */ + unsigned blocks; /* total blocks */ + + unsigned error; /* number of over/underruns */ + unsigned underrun; /* underrun pending before next write has occurred */ + wait_queue_head_t wait; /* put process on wait queue when no more space in buffer */ + + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dmasize; + unsigned fragsamples; + + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned endcleared:1; + unsigned SGok:1; + unsigned update_flag; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + } dmabuf; + /* Guard against mmap/write/read races */ + struct semaphore sem; +}; + +struct cs_card { + struct cs_channel channel[2]; + unsigned int magic; + + /* We keep cs461x cards in a linked list */ + struct cs_card *next; + + /* The cs461x has a certain amount of cross channel interaction + so we use a single per card lock */ + spinlock_t lock; + + /* Keep AC97 sane */ + spinlock_t ac97_lock; + + /* mixer use count */ + atomic_t mixer_use_cnt; + + /* PCI device stuff */ + struct pci_dev * pci_dev; + struct list_head list; + + unsigned int pctl, cctl; /* Hardware DMA flag sets */ + + /* soundcore stuff */ + int dev_audio; + int dev_midi; + + /* structures for abstraction of hardware facilities, codecs, banks and channels*/ + struct ac97_codec *ac97_codec[NR_AC97]; + struct cs_state *states[2]; + + u16 ac97_features; + + int amplifier; /* Amplifier control */ + void (*amplifier_ctrl)(struct cs_card *, int); + void (*amp_init)(struct cs_card *); + + int active; /* Active clocking */ + void (*active_ctrl)(struct cs_card *, int); + + /* hardware resources */ + unsigned long ba0_addr; + unsigned long ba1_addr; + u32 irq; + + /* mappings */ + void __iomem *ba0; + union + { + struct + { + u8 __iomem *data0; + u8 __iomem *data1; + u8 __iomem *pmem; + u8 __iomem *reg; + } name; + u8 __iomem *idx[4]; + } ba1; + + /* Function support */ + struct cs_channel *(*alloc_pcm_channel)(struct cs_card *); + struct cs_channel *(*alloc_rec_pcm_channel)(struct cs_card *); + void (*free_pcm_channel)(struct cs_card *, int chan); + + /* /dev/midi stuff */ + struct { + unsigned ird, iwr, icnt; + unsigned ord, owr, ocnt; + wait_queue_head_t open_wait; + wait_queue_head_t iwait; + wait_queue_head_t owait; + spinlock_t lock; + unsigned char ibuf[CS_MIDIINBUF]; + unsigned char obuf[CS_MIDIOUTBUF]; + mode_t open_mode; + struct semaphore open_sem; + } midi; + struct cs46xx_pm pm; +}; + +static int cs_open_mixdev(struct inode *inode, struct file *file); +static int cs_release_mixdev(struct inode *inode, struct file *file); +static int cs_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg); +static int cs_hardware_init(struct cs_card *card); +static int cs46xx_powerup(struct cs_card *card, unsigned int type); +static int cs461x_powerdown(struct cs_card *card, unsigned int type, int suspendflag); +static void cs461x_clear_serial_FIFOs(struct cs_card *card, int type); +static int cs46xx_suspend_tbl(struct pci_dev *pcidev, pm_message_t state); +static int cs46xx_resume_tbl(struct pci_dev *pcidev); + +#ifndef CS46XX_ACPI_SUPPORT +static int cs46xx_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data); +#endif + +#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) +#define SOUND_MIXER_CS_APM _SIOWR('M',124, int) + +static void printioctl(unsigned int x) +{ + unsigned int i; + unsigned char vidx; + /* these values are incorrect for the ac97 driver, fix. + * 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: ") ); + break; + case SOUND_MIXER_CS_GETDBGLEVEL: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_CS_GETDBGLEVEL: ") ); + break; + case SOUND_MIXER_CS_SETDBGMASK: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_CS_SETDBGMASK: ") ); + break; + case SOUND_MIXER_CS_SETDBGLEVEL: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_CS_SETDBGLEVEL: ") ); + break; + case OSS_GETVERSION: + CS_DBGOUT(CS_IOCTL, 4, printk("OSS_GETVERSION: ") ); + break; + case SNDCTL_DSP_SYNC: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SYNC: ") ); + break; + case SNDCTL_DSP_SETDUPLEX: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETDUPLEX: ") ); + break; + case SNDCTL_DSP_GETCAPS: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETCAPS: ") ); + break; + case SNDCTL_DSP_RESET: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_RESET: ") ); + break; + case SNDCTL_DSP_SPEED: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SPEED: ") ); + break; + case SNDCTL_DSP_STEREO: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_STEREO: ") ); + break; + case SNDCTL_DSP_CHANNELS: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_CHANNELS: ") ); + break; + case SNDCTL_DSP_GETFMTS: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETFMTS: ") ); + break; + case SNDCTL_DSP_SETFMT: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETFMT: ") ); + break; + case SNDCTL_DSP_POST: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_POST: ") ); + break; + case SNDCTL_DSP_GETTRIGGER: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETTRIGGER: ") ); + break; + case SNDCTL_DSP_SETTRIGGER: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETTRIGGER: ") ); + break; + case SNDCTL_DSP_GETOSPACE: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETOSPACE: ") ); + break; + case SNDCTL_DSP_GETISPACE: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETISPACE: ") ); + break; + case SNDCTL_DSP_NONBLOCK: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_NONBLOCK: ") ); + break; + case SNDCTL_DSP_GETODELAY: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETODELAY: ") ); + break; + case SNDCTL_DSP_GETIPTR: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETIPTR: ") ); + break; + case SNDCTL_DSP_GETOPTR: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETOPTR: ") ); + break; + case SNDCTL_DSP_GETBLKSIZE: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETBLKSIZE: ") ); + break; + case SNDCTL_DSP_SETFRAGMENT: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETFRAGMENT: ") ); + break; + case SNDCTL_DSP_SUBDIVIDE: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SUBDIVIDE: ") ); + break; + case SOUND_PCM_READ_RATE: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_RATE: ") ); + break; + case SOUND_PCM_READ_CHANNELS: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_CHANNELS: ") ); + break; + case SOUND_PCM_READ_BITS: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_BITS: ") ); + break; + case SOUND_PCM_WRITE_FILTER: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_WRITE_FILTER: ") ); + break; + case SNDCTL_DSP_SETSYNCRO: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETSYNCRO: ") ); + break; + case SOUND_PCM_READ_FILTER: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_FILTER: ") ); + break; + + case SOUND_MIXER_PRIVATE1: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE1: ") ); + break; + case SOUND_MIXER_PRIVATE2: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE2: ") ); + break; + case SOUND_MIXER_PRIVATE3: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE3: ") ); + break; + case SOUND_MIXER_PRIVATE4: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE4: ") ); + break; + case SOUND_MIXER_PRIVATE5: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE5: ") ); + break; + case SOUND_MIXER_INFO: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_INFO: ") ); + break; + case SOUND_OLD_MIXER_INFO: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_OLD_MIXER_INFO: ") ); + break; + + default: + switch (_IOC_NR(x)) + { + case SOUND_MIXER_VOLUME: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_VOLUME: ") ); + break; + case SOUND_MIXER_SPEAKER: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_SPEAKER: ") ); + break; + case SOUND_MIXER_RECLEV: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_RECLEV: ") ); + break; + case SOUND_MIXER_MIC: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_MIC: ") ); + break; + case SOUND_MIXER_SYNTH: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_SYNTH: ") ); + break; + case SOUND_MIXER_RECSRC: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_RECSRC: ") ); + break; + case SOUND_MIXER_DEVMASK: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_DEVMASK: ") ); + break; + case SOUND_MIXER_RECMASK: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_RECMASK: ") ); + break; + case SOUND_MIXER_STEREODEVS: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_STEREODEVS: ") ); + break; + case SOUND_MIXER_CAPS: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_CAPS:") ); + 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 ",x,i) ); + } + else + { + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_IOCTL AC9x: 0x%.8x NR=%d ", + x,i) ); + } + break; + } + } + CS_DBGOUT(CS_IOCTL, 4, printk("command = 0x%x IOC_NR=%d\n",x, _IOC_NR(x)) ); +} +#endif + +/* + * common I/O routines + */ + +static void cs461x_poke(struct cs_card *codec, unsigned long reg, unsigned int val) +{ + writel(val, codec->ba1.idx[(reg >> 16) & 3]+(reg&0xffff)); +} + +static unsigned int cs461x_peek(struct cs_card *codec, unsigned long reg) +{ + return readl(codec->ba1.idx[(reg >> 16) & 3]+(reg&0xffff)); +} + +static void cs461x_pokeBA0(struct cs_card *codec, unsigned long reg, unsigned int val) +{ + writel(val, codec->ba0+reg); +} + +static unsigned int cs461x_peekBA0(struct cs_card *codec, unsigned long reg) +{ + return readl(codec->ba0+reg); +} + + +static u16 cs_ac97_get(struct ac97_codec *dev, u8 reg); +static void cs_ac97_set(struct ac97_codec *dev, u8 reg, u16 data); + +static struct cs_channel *cs_alloc_pcm_channel(struct cs_card *card) +{ + if(card->channel[1].used==1) + return NULL; + card->channel[1].used=1; + card->channel[1].num=1; + return &card->channel[1]; +} + +static struct cs_channel *cs_alloc_rec_pcm_channel(struct cs_card *card) +{ + if(card->channel[0].used==1) + return NULL; + card->channel[0].used=1; + card->channel[0].num=0; + return &card->channel[0]; +} + +static void cs_free_pcm_channel(struct cs_card *card, int channel) +{ + card->channel[channel].state = NULL; + card->channel[channel].used=0; +} + +/* + * setup a divisor value to help with conversion from + * 16bit Stereo, down to 8bit stereo/mono or 16bit mono. + * assign a divisor of 1 if using 16bit Stereo as that is + * the only format that the static image will capture. + */ +static void cs_set_divisor(struct dmabuf *dmabuf) +{ + if(dmabuf->type == CS_TYPE_DAC) + dmabuf->divisor = 1; + else if( !(dmabuf->fmt & CS_FMT_STEREO) && + (dmabuf->fmt & CS_FMT_16BIT)) + dmabuf->divisor = 2; + else if( (dmabuf->fmt & CS_FMT_STEREO) && + !(dmabuf->fmt & CS_FMT_16BIT)) + dmabuf->divisor = 2; + else if( !(dmabuf->fmt & CS_FMT_STEREO) && + !(dmabuf->fmt & CS_FMT_16BIT)) + dmabuf->divisor = 4; + else + dmabuf->divisor = 1; + + CS_DBGOUT(CS_PARMS | CS_FUNCTION, 8, printk( + "cs46xx: cs_set_divisor()- %s %d\n", + (dmabuf->type == CS_TYPE_ADC) ? "ADC" : "DAC", + dmabuf->divisor) ); +} + +/* +* mute some of the more prevalent registers to avoid popping. +*/ +static void cs_mute(struct cs_card *card, int state) +{ + struct ac97_codec *dev=card->ac97_codec[0]; + + CS_DBGOUT(CS_FUNCTION, 2, printk(KERN_INFO "cs46xx: cs_mute()+ %s\n", + (state == CS_TRUE) ? "Muting" : "UnMuting") ); + + if(state == CS_TRUE) + { + /* + * fix pops when powering up on thinkpads + */ + card->pm.u32AC97_master_volume = (u32)cs_ac97_get( dev, + (u8)BA0_AC97_MASTER_VOLUME); + card->pm.u32AC97_headphone_volume = (u32)cs_ac97_get(dev, + (u8)BA0_AC97_HEADPHONE_VOLUME); + card->pm.u32AC97_master_volume_mono = (u32)cs_ac97_get(dev, + (u8)BA0_AC97_MASTER_VOLUME_MONO); + card->pm.u32AC97_pcm_out_volume = (u32)cs_ac97_get(dev, + (u8)BA0_AC97_PCM_OUT_VOLUME); + + cs_ac97_set(dev, (u8)BA0_AC97_MASTER_VOLUME, 0x8000); + cs_ac97_set(dev, (u8)BA0_AC97_HEADPHONE_VOLUME, 0x8000); + cs_ac97_set(dev, (u8)BA0_AC97_MASTER_VOLUME_MONO, 0x8000); + cs_ac97_set(dev, (u8)BA0_AC97_PCM_OUT_VOLUME, 0x8000); + } + else + { + cs_ac97_set(dev, (u8)BA0_AC97_MASTER_VOLUME, card->pm.u32AC97_master_volume); + cs_ac97_set(dev, (u8)BA0_AC97_HEADPHONE_VOLUME, card->pm.u32AC97_headphone_volume); + cs_ac97_set(dev, (u8)BA0_AC97_MASTER_VOLUME_MONO, card->pm.u32AC97_master_volume_mono); + cs_ac97_set(dev, (u8)BA0_AC97_PCM_OUT_VOLUME, card->pm.u32AC97_pcm_out_volume); + } + CS_DBGOUT(CS_FUNCTION, 2, printk(KERN_INFO "cs46xx: cs_mute()-\n")); +} + +/* set playback sample rate */ +static unsigned int cs_set_dac_rate(struct cs_state * state, unsigned int rate) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned int tmp1, tmp2; + unsigned int phiIncr; + unsigned int correctionPerGOF, correctionPerSec; + unsigned long flags; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_set_dac_rate()+ %d\n",rate) ); + + /* + * Compute the values used to drive the actual sample rate conversion. + * The following formulas are being computed, using inline assembly + * since we need to use 64 bit arithmetic to compute the values: + * + * phiIncr = floor((Fs,in * 2^26) / Fs,out) + * correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) / + * GOF_PER_SEC) + * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr -M + * GOF_PER_SEC * correctionPerGOF + * + * i.e. + * + * phiIncr:other = dividend:remainder((Fs,in * 2^26) / Fs,out) + * correctionPerGOF:correctionPerSec = + * dividend:remainder(ulOther / GOF_PER_SEC) + */ + tmp1 = rate << 16; + phiIncr = tmp1 / 48000; + tmp1 -= phiIncr * 48000; + tmp1 <<= 10; + phiIncr <<= 10; + tmp2 = tmp1 / 48000; + phiIncr += tmp2; + tmp1 -= tmp2 * 48000; + correctionPerGOF = tmp1 / GOF_PER_SEC; + tmp1 -= correctionPerGOF * GOF_PER_SEC; + correctionPerSec = tmp1; + + /* + * Fill in the SampleRateConverter control block. + */ + + spin_lock_irqsave(&state->card->lock, flags); + cs461x_poke(state->card, BA1_PSRC, + ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF)); + cs461x_poke(state->card, BA1_PPI, phiIncr); + spin_unlock_irqrestore(&state->card->lock, flags); + dmabuf->rate = rate; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_set_dac_rate()- %d\n",rate) ); + return rate; +} + +/* set recording sample rate */ +static unsigned int cs_set_adc_rate(struct cs_state * state, unsigned int rate) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct cs_card *card = state->card; + unsigned int phiIncr, coeffIncr, tmp1, tmp2; + unsigned int correctionPerGOF, correctionPerSec, initialDelay; + unsigned int frameGroupLength, cnt; + unsigned long flags; + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_set_adc_rate()+ %d\n",rate) ); + + /* + * We can only decimate by up to a factor of 1/9th the hardware rate. + * Correct the value if an attempt is made to stray outside that limit. + */ + if ((rate * 9) < 48000) + rate = 48000 / 9; + + /* + * We can not capture at at rate greater than the Input Rate (48000). + * Return an error if an attempt is made to stray outside that limit. + */ + if (rate > 48000) + rate = 48000; + + /* + * Compute the values used to drive the actual sample rate conversion. + * The following formulas are being computed, using inline assembly + * since we need to use 64 bit arithmetic to compute the values: + * + * coeffIncr = -floor((Fs,out * 2^23) / Fs,in) + * phiIncr = floor((Fs,in * 2^26) / Fs,out) + * correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) / + * GOF_PER_SEC) + * correctionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - + * GOF_PER_SEC * correctionPerGOF + * initialDelay = ceil((24 * Fs,in) / Fs,out) + * + * i.e. + * + * coeffIncr = neg(dividend((Fs,out * 2^23) / Fs,in)) + * phiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) + * correctionPerGOF:correctionPerSec = + * dividend:remainder(ulOther / GOF_PER_SEC) + * initialDelay = dividend(((24 * Fs,in) + Fs,out - 1) / Fs,out) + */ + + tmp1 = rate << 16; + coeffIncr = tmp1 / 48000; + tmp1 -= coeffIncr * 48000; + tmp1 <<= 7; + coeffIncr <<= 7; + coeffIncr += tmp1 / 48000; + coeffIncr ^= 0xFFFFFFFF; + coeffIncr++; + tmp1 = 48000 << 16; + phiIncr = tmp1 / rate; + tmp1 -= phiIncr * rate; + tmp1 <<= 10; + phiIncr <<= 10; + tmp2 = tmp1 / rate; + phiIncr += tmp2; + tmp1 -= tmp2 * rate; + correctionPerGOF = tmp1 / GOF_PER_SEC; + tmp1 -= correctionPerGOF * GOF_PER_SEC; + correctionPerSec = tmp1; + initialDelay = ((48000 * 24) + rate - 1) / rate; + + /* + * Fill in the VariDecimate control block. + */ + spin_lock_irqsave(&card->lock, flags); + cs461x_poke(card, BA1_CSRC, + ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF)); + cs461x_poke(card, BA1_CCI, coeffIncr); + cs461x_poke(card, BA1_CD, + (((BA1_VARIDEC_BUF_1 + (initialDelay << 2)) << 16) & 0xFFFF0000) | 0x80); + cs461x_poke(card, BA1_CPI, phiIncr); + spin_unlock_irqrestore(&card->lock, flags); + + /* + * Figure out the frame group length for the write back task. Basically, + * this is just the factors of 24000 (2^6*3*5^3) that are not present in + * the output sample rate. + */ + frameGroupLength = 1; + for (cnt = 2; cnt <= 64; cnt *= 2) { + if (((rate / cnt) * cnt) != rate) + frameGroupLength *= 2; + } + if (((rate / 3) * 3) != rate) { + frameGroupLength *= 3; + } + for (cnt = 5; cnt <= 125; cnt *= 5) { + if (((rate / cnt) * cnt) != rate) + frameGroupLength *= 5; + } + + /* + * Fill in the WriteBack control block. + */ + spin_lock_irqsave(&card->lock, flags); + cs461x_poke(card, BA1_CFG1, frameGroupLength); + cs461x_poke(card, BA1_CFG2, (0x00800000 | frameGroupLength)); + cs461x_poke(card, BA1_CCST, 0x0000FFFF); + cs461x_poke(card, BA1_CSPB, ((65536 * rate) / 24000)); + cs461x_poke(card, (BA1_CSPB + 4), 0x0000FFFF); + spin_unlock_irqrestore(&card->lock, flags); + dmabuf->rate = rate; + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_set_adc_rate()- %d\n",rate) ); + return rate; +} + +/* prepare channel attributes for playback */ +static void cs_play_setup(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct cs_card *card = state->card; + unsigned int tmp, Count, playFormat; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_play_setup()+\n") ); + cs461x_poke(card, BA1_PVOL, 0x80008000); + if(!dmabuf->SGok) + cs461x_poke(card, BA1_PBA, virt_to_bus(dmabuf->pbuf)); + + Count = 4; + playFormat=cs461x_peek(card, BA1_PFIE); + if ((dmabuf->fmt & CS_FMT_STEREO)) { + playFormat &= ~DMA_RQ_C2_AC_MONO_TO_STEREO; + Count *= 2; + } + else + playFormat |= DMA_RQ_C2_AC_MONO_TO_STEREO; + + if ((dmabuf->fmt & CS_FMT_16BIT)) { + playFormat &= ~(DMA_RQ_C2_AC_8_TO_16_BIT + | DMA_RQ_C2_AC_SIGNED_CONVERT); + Count *= 2; + } + else + playFormat |= (DMA_RQ_C2_AC_8_TO_16_BIT + | DMA_RQ_C2_AC_SIGNED_CONVERT); + + cs461x_poke(card, BA1_PFIE, playFormat); + + tmp = cs461x_peek(card, BA1_PDTC); + tmp &= 0xfffffe00; + cs461x_poke(card, BA1_PDTC, tmp | --Count); + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_play_setup()-\n") ); + +} + +static struct InitStruct +{ + u32 off; + u32 val; +} InitArray[] = { {0x00000040, 0x3fc0000f}, + {0x0000004c, 0x04800000}, + + {0x000000b3, 0x00000780}, + {0x000000b7, 0x00000000}, + {0x000000bc, 0x07800000}, + + {0x000000cd, 0x00800000}, + }; + +/* + * "SetCaptureSPValues()" -- Initialize record task values before each + * capture startup. + */ +static void SetCaptureSPValues(struct cs_card *card) +{ + unsigned i, offset; + CS_DBGOUT(CS_FUNCTION, 8, printk("cs46xx: SetCaptureSPValues()+\n") ); + for(i=0; icard; + struct dmabuf *dmabuf = &state->dmabuf; + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_rec_setup()+\n") ); + + SetCaptureSPValues(card); + + /* + * set the attenuation to 0dB + */ + cs461x_poke(card, BA1_CVOL, 0x80008000); + + /* + * set the physical address of the capture buffer into the SP + */ + cs461x_poke(card, BA1_CBA, virt_to_bus(dmabuf->rawbuf)); + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_rec_setup()-\n") ); +} + + +/* get current playback/recording dma buffer pointer (byte offset from LBA), + called with spinlock held! */ + +static inline unsigned cs_get_dma_addr(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + u32 offset; + + if ( (!(dmabuf->enable & DAC_RUNNING)) && + (!(dmabuf->enable & ADC_RUNNING) ) ) + { + CS_DBGOUT(CS_ERROR, 2, printk( + "cs46xx: ERROR cs_get_dma_addr(): not enabled \n") ); + return 0; + } + + /* + * granularity is byte boundary, good part. + */ + if(dmabuf->enable & DAC_RUNNING) + { + offset = cs461x_peek(state->card, BA1_PBA); + } + else /* ADC_RUNNING must be set */ + { + offset = cs461x_peek(state->card, BA1_CBA); + } + CS_DBGOUT(CS_PARMS | CS_FUNCTION, 9, + printk("cs46xx: cs_get_dma_addr() %d\n",offset) ); + offset = (u32)bus_to_virt((unsigned long)offset) - (u32)dmabuf->rawbuf; + CS_DBGOUT(CS_PARMS | CS_FUNCTION, 8, + printk("cs46xx: cs_get_dma_addr()- %d\n",offset) ); + return offset; +} + +static void resync_dma_ptrs(struct cs_state *state) +{ + struct dmabuf *dmabuf; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: resync_dma_ptrs()+ \n") ); + if(state) + { + dmabuf = &state->dmabuf; + dmabuf->hwptr=dmabuf->swptr = 0; + dmabuf->pringbuf = 0; + } + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: resync_dma_ptrs()- \n") ); +} + +/* Stop recording (lock held) */ +static inline void __stop_adc(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct cs_card *card = state->card; + unsigned int tmp; + + dmabuf->enable &= ~ADC_RUNNING; + + tmp = cs461x_peek(card, BA1_CCTL); + tmp &= 0xFFFF0000; + cs461x_poke(card, BA1_CCTL, tmp ); +} + +static void stop_adc(struct cs_state *state) +{ + unsigned long flags; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: stop_adc()+ \n") ); + spin_lock_irqsave(&state->card->lock, flags); + __stop_adc(state); + spin_unlock_irqrestore(&state->card->lock, flags); + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: stop_adc()- \n") ); +} + +static void start_adc(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct cs_card *card = state->card; + unsigned long flags; + unsigned int tmp; + + spin_lock_irqsave(&card->lock, flags); + if (!(dmabuf->enable & ADC_RUNNING) && + ((dmabuf->mapped || dmabuf->count < (signed)dmabuf->dmasize) + && dmabuf->ready) && + ((card->pm.flags & CS46XX_PM_IDLE) || + (card->pm.flags & CS46XX_PM_RESUMED)) ) + { + dmabuf->enable |= ADC_RUNNING; + cs_set_divisor(dmabuf); + tmp = cs461x_peek(card, BA1_CCTL); + tmp &= 0xFFFF0000; + tmp |= card->cctl; + CS_DBGOUT(CS_FUNCTION, 2, printk( + "cs46xx: start_adc() poke 0x%x \n",tmp) ); + cs461x_poke(card, BA1_CCTL, tmp); + } + spin_unlock_irqrestore(&card->lock, flags); +} + +/* stop playback (lock held) */ +static inline void __stop_dac(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct cs_card *card = state->card; + unsigned int tmp; + + dmabuf->enable &= ~DAC_RUNNING; + + tmp=cs461x_peek(card, BA1_PCTL); + tmp&=0xFFFF; + cs461x_poke(card, BA1_PCTL, tmp); +} + +static void stop_dac(struct cs_state *state) +{ + unsigned long flags; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: stop_dac()+ \n") ); + spin_lock_irqsave(&state->card->lock, flags); + __stop_dac(state); + spin_unlock_irqrestore(&state->card->lock, flags); + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: stop_dac()- \n") ); +} + +static void start_dac(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct cs_card *card = state->card; + unsigned long flags; + int tmp; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: start_dac()+ \n") ); + spin_lock_irqsave(&card->lock, flags); + if (!(dmabuf->enable & DAC_RUNNING) && + ((dmabuf->mapped || dmabuf->count > 0) && dmabuf->ready) && + ((card->pm.flags & CS46XX_PM_IDLE) || + (card->pm.flags & CS46XX_PM_RESUMED)) ) + { + dmabuf->enable |= DAC_RUNNING; + tmp = cs461x_peek(card, BA1_PCTL); + tmp &= 0xFFFF; + tmp |= card->pctl; + CS_DBGOUT(CS_PARMS, 6, printk( + "cs46xx: start_dac() poke card=%p tmp=0x%.08x addr=%p \n", + card, (unsigned)tmp, + card->ba1.idx[(BA1_PCTL >> 16) & 3]+(BA1_PCTL&0xffff) ) ); + cs461x_poke(card, BA1_PCTL, tmp); + } + spin_unlock_irqrestore(&card->lock, flags); + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: start_dac()- \n") ); +} + +#define DMABUF_MINORDER 1 + +/* + * allocate DMA buffer, playback and recording buffers are separate. + */ +static int alloc_dmabuf(struct cs_state *state) +{ + + struct cs_card *card=state->card; + struct dmabuf *dmabuf = &state->dmabuf; + void *rawbuf = NULL; + void *tmpbuff = NULL; + int order; + struct page *map, *mapend; + unsigned long df; + + dmabuf->ready = dmabuf->mapped = 0; + dmabuf->SGok = 0; +/* +* check for order within limits, but do not overwrite value. +*/ + if((defaultorder > 1) && (defaultorder < 12)) + df = defaultorder; + else + df = 2; + + for (order = df; order >= DMABUF_MINORDER; order--) + if ( (rawbuf = (void *) pci_alloc_consistent( + card->pci_dev, PAGE_SIZE << order, &dmabuf->dmaaddr))) + break; + if (!rawbuf) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs46xx: alloc_dmabuf(): unable to allocate rawbuf\n")); + return -ENOMEM; + } + dmabuf->buforder = order; + dmabuf->rawbuf = rawbuf; + // Now mark the pages as reserved; otherwise the + // remap_pfn_range() in cs46xx_mmap doesn't work. + // 1. get index to last page in mem_map array for rawbuf. + mapend = virt_to_page(dmabuf->rawbuf + + (PAGE_SIZE << dmabuf->buforder) - 1); + + // 2. mark each physical page in range as 'reserved'. + for (map = virt_to_page(dmabuf->rawbuf); map <= mapend; map++) + cs4x_mem_map_reserve(map); + + CS_DBGOUT(CS_PARMS, 9, printk("cs46xx: alloc_dmabuf(): allocated %ld (order = %d) bytes at %p\n", + PAGE_SIZE << order, order, rawbuf) ); + +/* +* only allocate the conversion buffer for the ADC +*/ + if(dmabuf->type == CS_TYPE_DAC) + { + dmabuf->tmpbuff = NULL; + dmabuf->buforder_tmpbuff = 0; + return 0; + } +/* + * now the temp buffer for 16/8 conversions + */ + + tmpbuff = (void *) pci_alloc_consistent( + card->pci_dev, PAGE_SIZE << order, &dmabuf->dmaaddr_tmpbuff); + + if (!tmpbuff) + return -ENOMEM; + CS_DBGOUT(CS_PARMS, 9, printk("cs46xx: allocated %ld (order = %d) bytes at %p\n", + PAGE_SIZE << order, order, tmpbuff) ); + + dmabuf->tmpbuff = tmpbuff; + dmabuf->buforder_tmpbuff = order; + + // Now mark the pages as reserved; otherwise the + // remap_pfn_range() in cs46xx_mmap doesn't work. + // 1. get index to last page in mem_map array for rawbuf. + mapend = virt_to_page(dmabuf->tmpbuff + + (PAGE_SIZE << dmabuf->buforder_tmpbuff) - 1); + + // 2. mark each physical page in range as 'reserved'. + for (map = virt_to_page(dmabuf->tmpbuff); map <= mapend; map++) + cs4x_mem_map_reserve(map); + return 0; +} + +/* free DMA buffer */ +static void dealloc_dmabuf(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct page *map, *mapend; + + if (dmabuf->rawbuf) { + // Undo prog_dmabuf()'s marking the pages as reserved + mapend = virt_to_page(dmabuf->rawbuf + + (PAGE_SIZE << dmabuf->buforder) - 1); + for (map = virt_to_page(dmabuf->rawbuf); map <= mapend; map++) + cs4x_mem_map_unreserve(map); + free_dmabuf(state->card, dmabuf); + } + + if (dmabuf->tmpbuff) { + // Undo prog_dmabuf()'s marking the pages as reserved + mapend = virt_to_page(dmabuf->tmpbuff + + (PAGE_SIZE << dmabuf->buforder_tmpbuff) - 1); + for (map = virt_to_page(dmabuf->tmpbuff); map <= mapend; map++) + cs4x_mem_map_unreserve(map); + free_dmabuf2(state->card, dmabuf); + } + + dmabuf->rawbuf = NULL; + dmabuf->tmpbuff = NULL; + dmabuf->mapped = dmabuf->ready = 0; + dmabuf->SGok = 0; +} + +static int __prog_dmabuf(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + unsigned long allocated_pages, allocated_bytes; + unsigned long tmp1, tmp2, fmt=0; + unsigned long *ptmp = (unsigned long *) dmabuf->pbuf; + unsigned long SGarray[9], nSGpages=0; + int ret; + + CS_DBGOUT(CS_FUNCTION, 4, printk("cs46xx: prog_dmabuf()+ \n")); +/* + * check for CAPTURE and use only non-sg for initial release + */ + if(dmabuf->type == CS_TYPE_ADC) + { + CS_DBGOUT(CS_FUNCTION, 4, printk("cs46xx: prog_dmabuf() ADC\n")); + /* + * add in non-sg support for capture. + */ + spin_lock_irqsave(&state->card->lock, flags); + /* add code to reset the rawbuf memory. TRW */ + resync_dma_ptrs(state); + dmabuf->total_bytes = dmabuf->blocks = 0; + dmabuf->count = dmabuf->error = dmabuf->underrun = 0; + + dmabuf->SGok = 0; + + spin_unlock_irqrestore(&state->card->lock, flags); + + /* allocate DMA buffer if not allocated yet */ + if (!dmabuf->rawbuf || !dmabuf->tmpbuff) + if ((ret = alloc_dmabuf(state))) + return ret; + /* + * static image only supports 16Bit signed, stereo - hard code fmt + */ + fmt = CS_FMT_16BIT | CS_FMT_STEREO; + + dmabuf->numfrag = 2; + dmabuf->fragsize = 2048; + dmabuf->fragsamples = 2048 >> sample_shift[fmt]; + dmabuf->dmasize = 4096; + dmabuf->fragshift = 11; + + memset(dmabuf->rawbuf, (fmt & CS_FMT_16BIT) ? 0 : 0x80, + dmabuf->dmasize); + memset(dmabuf->tmpbuff, (fmt & CS_FMT_16BIT) ? 0 : 0x80, + PAGE_SIZE<buforder_tmpbuff); + + /* + * Now set up the ring + */ + + spin_lock_irqsave(&state->card->lock, flags); + cs_rec_setup(state); + spin_unlock_irqrestore(&state->card->lock, flags); + + /* set the ready flag for the dma buffer */ + dmabuf->ready = 1; + + CS_DBGOUT(CS_PARMS, 4, printk( + "cs46xx: prog_dmabuf(): CAPTURE rate=%d fmt=0x%x numfrag=%d " + "fragsize=%d dmasize=%d\n", + dmabuf->rate, dmabuf->fmt, dmabuf->numfrag, + dmabuf->fragsize, dmabuf->dmasize) ); + + CS_DBGOUT(CS_FUNCTION, 4, printk("cs46xx: prog_dmabuf()- 0 \n")); + return 0; + } + else if (dmabuf->type == CS_TYPE_DAC) + { + /* + * Must be DAC + */ + CS_DBGOUT(CS_FUNCTION, 4, printk("cs46xx: prog_dmabuf() DAC\n")); + spin_lock_irqsave(&state->card->lock, flags); + resync_dma_ptrs(state); + dmabuf->total_bytes = dmabuf->blocks = 0; + dmabuf->count = dmabuf->error = dmabuf->underrun = 0; + + dmabuf->SGok = 0; + + spin_unlock_irqrestore(&state->card->lock, flags); + + /* allocate DMA buffer if not allocated yet */ + if (!dmabuf->rawbuf) + if ((ret = alloc_dmabuf(state))) + return ret; + + allocated_pages = 1 << dmabuf->buforder; + allocated_bytes = allocated_pages*PAGE_SIZE; + + if(allocated_pages < 2) + { + CS_DBGOUT(CS_FUNCTION, 4, printk( + "cs46xx: prog_dmabuf() Error: allocated_pages too small (%d)\n", + (unsigned)allocated_pages)); + return -ENOMEM; + } + + /* Use all the pages allocated, fragsize 4k. */ + /* Use 'pbuf' for S/G page map table. */ + dmabuf->SGok = 1; /* Use S/G. */ + + nSGpages = allocated_bytes/4096; /* S/G pages always 4k. */ + + /* Set up S/G variables. */ + *ptmp = virt_to_bus(dmabuf->rawbuf); + *(ptmp+1) = 0x00000008; + for(tmp1= 1; tmp1 < nSGpages; tmp1++) { + *(ptmp+2*tmp1) = virt_to_bus( (dmabuf->rawbuf)+4096*tmp1); + if( tmp1 == nSGpages-1) + tmp2 = 0xbfff0000; + else + tmp2 = 0x80000000+8*(tmp1+1); + *(ptmp+2*tmp1+1) = tmp2; + } + SGarray[0] = 0x82c0200d; + SGarray[1] = 0xffff0000; + SGarray[2] = *ptmp; + SGarray[3] = 0x00010600; + SGarray[4] = *(ptmp+2); + SGarray[5] = 0x80000010; + SGarray[6] = *ptmp; + SGarray[7] = *(ptmp+2); + SGarray[8] = (virt_to_bus(dmabuf->pbuf) & 0xffff000) | 0x10; + + if (dmabuf->SGok) { + dmabuf->numfrag = nSGpages; + dmabuf->fragsize = 4096; + dmabuf->fragsamples = 4096 >> sample_shift[dmabuf->fmt]; + dmabuf->fragshift = 12; + dmabuf->dmasize = dmabuf->numfrag*4096; + } + else { + SGarray[0] = 0xf2c0000f; + SGarray[1] = 0x00000200; + SGarray[2] = 0; + SGarray[3] = 0x00010600; + SGarray[4]=SGarray[5]=SGarray[6]=SGarray[7]=SGarray[8] = 0; + dmabuf->numfrag = 2; + dmabuf->fragsize = 2048; + dmabuf->fragsamples = 2048 >> sample_shift[dmabuf->fmt]; + dmabuf->dmasize = 4096; + dmabuf->fragshift = 11; + } + for(tmp1 = 0; tmp1 < sizeof(SGarray)/4; tmp1++) + cs461x_poke( state->card, BA1_PDTC+tmp1*4, SGarray[tmp1]); + + memset(dmabuf->rawbuf, (dmabuf->fmt & CS_FMT_16BIT) ? 0 : 0x80, + dmabuf->dmasize); + + /* + * Now set up the ring + */ + + spin_lock_irqsave(&state->card->lock, flags); + cs_play_setup(state); + spin_unlock_irqrestore(&state->card->lock, flags); + + /* set the ready flag for the dma buffer */ + dmabuf->ready = 1; + + CS_DBGOUT(CS_PARMS, 4, printk( + "cs46xx: prog_dmabuf(): PLAYBACK rate=%d fmt=0x%x numfrag=%d " + "fragsize=%d dmasize=%d\n", + dmabuf->rate, dmabuf->fmt, dmabuf->numfrag, + dmabuf->fragsize, dmabuf->dmasize) ); + + CS_DBGOUT(CS_FUNCTION, 4, printk("cs46xx: prog_dmabuf()- \n")); + return 0; + } + else + { + CS_DBGOUT(CS_FUNCTION, 4, printk("cs46xx: prog_dmabuf()- Invalid Type %d\n", + dmabuf->type)); + } + return 1; +} + +static int prog_dmabuf(struct cs_state *state) +{ + int ret; + + down(&state->sem); + ret = __prog_dmabuf(state); + up(&state->sem); + + return ret; +} + +static void cs_clear_tail(struct cs_state *state) +{ +} + +static int drain_dac(struct cs_state *state, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + struct dmabuf *dmabuf = &state->dmabuf; + struct cs_card *card=state->card; + unsigned long flags; + unsigned long tmo; + int count; + + CS_DBGOUT(CS_FUNCTION, 4, printk("cs46xx: drain_dac()+ \n")); + if (dmabuf->mapped || !dmabuf->ready) + { + CS_DBGOUT(CS_FUNCTION, 4, printk("cs46xx: drain_dac()- 0, not ready\n")); + return 0; + } + + add_wait_queue(&dmabuf->wait, &wait); + for (;;) { + /* It seems that we have to set the current state to TASK_INTERRUPTIBLE + every time to make the process really go to sleep */ + current->state = TASK_INTERRUPTIBLE; + + spin_lock_irqsave(&state->card->lock, flags); + count = dmabuf->count; + spin_unlock_irqrestore(&state->card->lock, flags); + + if (count <= 0) + break; + + if (signal_pending(current)) + break; + + if (nonblock) { + remove_wait_queue(&dmabuf->wait, &wait); + current->state = TASK_RUNNING; + return -EBUSY; + } + + tmo = (dmabuf->dmasize * HZ) / dmabuf->rate; + tmo >>= sample_shift[dmabuf->fmt]; + tmo += (2048*HZ)/dmabuf->rate; + + if (!schedule_timeout(tmo ? tmo : 1) && tmo){ + printk(KERN_ERR "cs46xx: drain_dac, dma timeout? %d\n", count); + break; + } + } + remove_wait_queue(&dmabuf->wait, &wait); + current->state = TASK_RUNNING; + if (signal_pending(current)) + { + CS_DBGOUT(CS_FUNCTION, 4, printk("cs46xx: drain_dac()- -ERESTARTSYS\n")); + /* + * set to silence and let that clear the fifos. + */ + cs461x_clear_serial_FIFOs(card, CS_TYPE_DAC); + return -ERESTARTSYS; + } + + CS_DBGOUT(CS_FUNCTION, 4, printk("cs46xx: drain_dac()- 0\n")); + return 0; +} + + +/* update buffer manangement pointers, especially, dmabuf->count and dmabuf->hwptr */ +static void cs_update_ptr(struct cs_card *card, int wake) +{ + struct cs_state *state; + struct dmabuf *dmabuf; + unsigned hwptr; + int diff; + + /* error handling and process wake up for ADC */ + state = card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + if (dmabuf->enable & ADC_RUNNING) { + /* update hardware pointer */ + hwptr = cs_get_dma_addr(state); + + diff = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize; + CS_DBGOUT(CS_PARMS, 9, printk( + "cs46xx: cs_update_ptr()+ ADC hwptr=%d diff=%d\n", + hwptr,diff) ); + dmabuf->hwptr = hwptr; + dmabuf->total_bytes += diff; + dmabuf->count += diff; + if (dmabuf->count > dmabuf->dmasize) + dmabuf->count = dmabuf->dmasize; + + if(dmabuf->mapped) + { + if (wake && dmabuf->count >= (signed)dmabuf->fragsize) + wake_up(&dmabuf->wait); + } else + { + if (wake && dmabuf->count > 0) + wake_up(&dmabuf->wait); + } + } + } + +/* + * Now the DAC + */ + state = card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + /* error handling and process wake up for DAC */ + if (dmabuf->enable & DAC_RUNNING) { + /* update hardware pointer */ + hwptr = cs_get_dma_addr(state); + + diff = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize; + CS_DBGOUT(CS_PARMS, 9, printk( + "cs46xx: cs_update_ptr()+ DAC hwptr=%d diff=%d\n", + hwptr,diff) ); + dmabuf->hwptr = hwptr; + dmabuf->total_bytes += diff; + if (dmabuf->mapped) { + dmabuf->count += diff; + if (wake && dmabuf->count >= (signed)dmabuf->fragsize) + wake_up(&dmabuf->wait); + /* + * other drivers use fragsize, but don't see any sense + * in that, since dmasize is the buffer asked for + * via mmap. + */ + if( dmabuf->count > dmabuf->dmasize) + dmabuf->count &= dmabuf->dmasize-1; + } else { + dmabuf->count -= diff; + /* + * backfill with silence and clear out the last + * "diff" number of bytes. + */ + if(hwptr >= diff) + { + memset(dmabuf->rawbuf + hwptr - diff, + (dmabuf->fmt & CS_FMT_16BIT) ? 0 : 0x80, diff); + } + else + { + memset(dmabuf->rawbuf, + (dmabuf->fmt & CS_FMT_16BIT) ? 0 : 0x80, + (unsigned)hwptr); + memset((char *)dmabuf->rawbuf + + dmabuf->dmasize + hwptr - diff, + (dmabuf->fmt & CS_FMT_16BIT) ? 0 : 0x80, + diff - hwptr); + } + + if (dmabuf->count < 0 || dmabuf->count > dmabuf->dmasize) { + CS_DBGOUT(CS_ERROR, 2, printk(KERN_INFO + "cs46xx: ERROR DAC count<0 or count > dmasize (%d)\n", + dmabuf->count)); + /* + * buffer underrun or buffer overrun, reset the + * count of bytes written back to 0. + */ + if(dmabuf->count < 0) + dmabuf->underrun=1; + dmabuf->count = 0; + dmabuf->error++; + } + if (wake && dmabuf->count < (signed)dmabuf->dmasize/2) + wake_up(&dmabuf->wait); + } + } + } +} + + +/* hold spinlock for the following! */ +static void cs_handle_midi(struct cs_card *card) +{ + unsigned char ch; + int wake; + unsigned temp1; + + wake = 0; + while (!(cs461x_peekBA0(card, BA0_MIDSR) & MIDSR_RBE)) { + ch = cs461x_peekBA0(card, BA0_MIDRP); + if (card->midi.icnt < CS_MIDIINBUF) { + card->midi.ibuf[card->midi.iwr] = ch; + card->midi.iwr = (card->midi.iwr + 1) % CS_MIDIINBUF; + card->midi.icnt++; + } + wake = 1; + } + if (wake) + wake_up(&card->midi.iwait); + wake = 0; + while (!(cs461x_peekBA0(card, BA0_MIDSR) & MIDSR_TBF) && card->midi.ocnt > 0) { + temp1 = ( card->midi.obuf[card->midi.ord] ) & 0x000000ff; + cs461x_pokeBA0(card, BA0_MIDWP,temp1); + card->midi.ord = (card->midi.ord + 1) % CS_MIDIOUTBUF; + card->midi.ocnt--; + if (card->midi.ocnt < CS_MIDIOUTBUF-16) + wake = 1; + } + if (wake) + wake_up(&card->midi.owait); +} + +static irqreturn_t cs_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct cs_card *card = (struct cs_card *)dev_id; + /* Single channel card */ + struct cs_state *recstate = card->channel[0].state; + struct cs_state *playstate = card->channel[1].state; + u32 status; + + CS_DBGOUT(CS_INTERRUPT, 9, printk("cs46xx: cs_interrupt()+ \n")); + + spin_lock(&card->lock); + + status = cs461x_peekBA0(card, BA0_HISR); + + if ((status & 0x7fffffff) == 0) + { + cs461x_pokeBA0(card, BA0_HICR, HICR_CHGM|HICR_IEV); + spin_unlock(&card->lock); + return IRQ_HANDLED; /* Might be IRQ_NONE.. */ + } + + /* + * check for playback or capture interrupt only + */ + if( ((status & HISR_VC0) && playstate && playstate->dmabuf.ready) || + (((status & HISR_VC1) && recstate && recstate->dmabuf.ready)) ) + { + CS_DBGOUT(CS_INTERRUPT, 8, printk( + "cs46xx: cs_interrupt() interrupt bit(s) set (0x%x)\n",status)); + cs_update_ptr(card, CS_TRUE); + } + + if( status & HISR_MIDI ) + cs_handle_midi(card); + + /* clear 'em */ + cs461x_pokeBA0(card, BA0_HICR, HICR_CHGM|HICR_IEV); + spin_unlock(&card->lock); + CS_DBGOUT(CS_INTERRUPT, 9, printk("cs46xx: cs_interrupt()- \n")); + return IRQ_HANDLED; +} + + +/**********************************************************************/ + +static ssize_t cs_midi_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct cs_card *card = (struct cs_card *)file->private_data; + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; + while (count > 0) { + spin_lock_irqsave(&card->lock, flags); + ptr = card->midi.ird; + cnt = CS_MIDIINBUF - ptr; + if (card->midi.icnt < cnt) + cnt = card->midi.icnt; + spin_unlock_irqrestore(&card->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) + return ret ? ret : -EAGAIN; + interruptible_sleep_on(&card->midi.iwait); + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + continue; + } + if (copy_to_user(buffer, card->midi.ibuf + ptr, cnt)) + return ret ? ret : -EFAULT; + ptr = (ptr + cnt) % CS_MIDIINBUF; + spin_lock_irqsave(&card->lock, flags); + card->midi.ird = ptr; + card->midi.icnt -= cnt; + spin_unlock_irqrestore(&card->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + } + return ret; +} + + +static ssize_t cs_midi_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct cs_card *card = (struct cs_card *)file->private_data; + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; + while (count > 0) { + spin_lock_irqsave(&card->lock, flags); + ptr = card->midi.owr; + cnt = CS_MIDIOUTBUF - ptr; + if (card->midi.ocnt + cnt > CS_MIDIOUTBUF) + cnt = CS_MIDIOUTBUF - card->midi.ocnt; + if (cnt <= 0) + cs_handle_midi(card); + spin_unlock_irqrestore(&card->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) + return ret ? ret : -EAGAIN; + interruptible_sleep_on(&card->midi.owait); + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + continue; + } + if (copy_from_user(card->midi.obuf + ptr, buffer, cnt)) + return ret ? ret : -EFAULT; + ptr = (ptr + cnt) % CS_MIDIOUTBUF; + spin_lock_irqsave(&card->lock, flags); + card->midi.owr = ptr; + card->midi.ocnt += cnt; + spin_unlock_irqrestore(&card->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + spin_lock_irqsave(&card->lock, flags); + cs_handle_midi(card); + spin_unlock_irqrestore(&card->lock, flags); + } + return ret; +} + + +static unsigned int cs_midi_poll(struct file *file, struct poll_table_struct *wait) +{ + struct cs_card *card = (struct cs_card *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + if (file->f_flags & FMODE_WRITE) + poll_wait(file, &card->midi.owait, wait); + if (file->f_flags & FMODE_READ) + poll_wait(file, &card->midi.iwait, wait); + spin_lock_irqsave(&card->lock, flags); + if (file->f_flags & FMODE_READ) { + if (card->midi.icnt > 0) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_flags & FMODE_WRITE) { + if (card->midi.ocnt < CS_MIDIOUTBUF) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&card->lock, flags); + return mask; +} + + +static int cs_midi_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct cs_card *card=NULL; + unsigned long flags; + struct list_head *entry; + + list_for_each(entry, &cs46xx_devs) + { + card = list_entry(entry, struct cs_card, list); + if (card->dev_midi == minor) + break; + } + + if (entry == &cs46xx_devs) + return -ENODEV; + if (!card) + { + CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, printk(KERN_INFO + "cs46xx: cs46xx_midi_open(): Error - unable to find card struct\n")); + return -ENODEV; + } + + file->private_data = card; + /* wait for device to become free */ + down(&card->midi.open_sem); + while (card->midi.open_mode & file->f_mode) { + if (file->f_flags & O_NONBLOCK) { + up(&card->midi.open_sem); + return -EBUSY; + } + up(&card->midi.open_sem); + interruptible_sleep_on(&card->midi.open_wait); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&card->midi.open_sem); + } + spin_lock_irqsave(&card->midi.lock, flags); + if (!(card->midi.open_mode & (FMODE_READ | FMODE_WRITE))) { + card->midi.ird = card->midi.iwr = card->midi.icnt = 0; + card->midi.ord = card->midi.owr = card->midi.ocnt = 0; + card->midi.ird = card->midi.iwr = card->midi.icnt = 0; + cs461x_pokeBA0(card, BA0_MIDCR, 0x0000000f); /* Enable xmit, rcv. */ + cs461x_pokeBA0(card, BA0_HICR, HICR_IEV | HICR_CHGM); /* Enable interrupts */ + } + if (file->f_mode & FMODE_READ) { + card->midi.ird = card->midi.iwr = card->midi.icnt = 0; + } + if (file->f_mode & FMODE_WRITE) { + card->midi.ord = card->midi.owr = card->midi.ocnt = 0; + } + spin_unlock_irqrestore(&card->midi.lock, flags); + card->midi.open_mode |= (file->f_mode & (FMODE_READ | FMODE_WRITE)); + up(&card->midi.open_sem); + return 0; +} + + +static int cs_midi_release(struct inode *inode, struct file *file) +{ + struct cs_card *card = (struct cs_card *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + unsigned count, tmo; + + if (file->f_mode & FMODE_WRITE) { + current->state = TASK_INTERRUPTIBLE; + add_wait_queue(&card->midi.owait, &wait); + for (;;) { + spin_lock_irqsave(&card->midi.lock, flags); + count = card->midi.ocnt; + spin_unlock_irqrestore(&card->midi.lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (file->f_flags & O_NONBLOCK) + break; + tmo = (count * HZ) / 3100; + if (!schedule_timeout(tmo ? : 1) && tmo) + printk(KERN_DEBUG "cs46xx: midi timed out??\n"); + } + remove_wait_queue(&card->midi.owait, &wait); + current->state = TASK_RUNNING; + } + down(&card->midi.open_sem); + card->midi.open_mode &= (~(file->f_mode & (FMODE_READ | FMODE_WRITE))); + up(&card->midi.open_sem); + wake_up(&card->midi.open_wait); + return 0; +} + +/* + * Midi file operations struct. + */ +static /*const*/ struct file_operations cs_midi_fops = { + CS_OWNER CS_THIS_MODULE + .llseek = no_llseek, + .read = cs_midi_read, + .write = cs_midi_write, + .poll = cs_midi_poll, + .open = cs_midi_open, + .release = cs_midi_release, +}; + +/* + * + * CopySamples copies 16-bit stereo signed samples from the source to the + * destination, possibly converting down to unsigned 8-bit and/or mono. + * count specifies the number of output bytes to write. + * + * Arguments: + * + * dst - Pointer to a destination buffer. + * src - Pointer to a source buffer + * count - The number of bytes to copy into the destination buffer. + * fmt - CS_FMT_16BIT and/or CS_FMT_STEREO bits + * dmabuf - pointer to the dma buffer structure + * + * NOTES: only call this routine if the output desired is not 16 Signed Stereo + * + * + */ +static void CopySamples(char *dst, char *src, int count, unsigned fmt, + struct dmabuf *dmabuf) +{ + + s32 s32AudioSample; + s16 *psSrc=(s16 *)src; + s16 *psDst=(s16 *)dst; + u8 *pucDst=(u8 *)dst; + + CS_DBGOUT(CS_FUNCTION, 2, printk(KERN_INFO "cs46xx: CopySamples()+ ") ); + CS_DBGOUT(CS_WAVE_READ, 8, printk(KERN_INFO + " dst=%p src=%p count=%d fmt=0x%x\n", + dst,src,count,fmt) ); + + /* + * See if the data should be output as 8-bit unsigned stereo. + */ + if((fmt & CS_FMT_STEREO) && !(fmt & CS_FMT_16BIT)) + { + /* + * Convert each 16-bit signed stereo sample to 8-bit unsigned + * stereo using rounding. + */ + psSrc = (s16 *)src; + count = count/2; + while(count--) + { + *(pucDst++) = (u8)(((s16)(*psSrc++) + (s16)0x8000) >> 8); + } + } + /* + * See if the data should be output at 8-bit unsigned mono. + */ + else if(!(fmt & CS_FMT_STEREO) && !(fmt & CS_FMT_16BIT)) + { + /* + * Convert each 16-bit signed stereo sample to 8-bit unsigned + * mono using averaging and rounding. + */ + psSrc = (s16 *)src; + count = count/2; + while(count--) + { + s32AudioSample = ((*psSrc)+(*(psSrc + 1)))/2 + (s32)0x80; + if(s32AudioSample > 0x7fff) + s32AudioSample = 0x7fff; + *(pucDst++) = (u8)(((s16)s32AudioSample + (s16)0x8000) >> 8); + psSrc += 2; + } + } + /* + * See if the data should be output at 16-bit signed mono. + */ + else if(!(fmt & CS_FMT_STEREO) && (fmt & CS_FMT_16BIT)) + { + /* + * Convert each 16-bit signed stereo sample to 16-bit signed + * mono using averaging. + */ + psSrc = (s16 *)src; + count = count/2; + while(count--) + { + *(psDst++) = (s16)((*psSrc)+(*(psSrc + 1)))/2; + psSrc += 2; + } + } +} + +/* + * cs_copy_to_user() + * replacement for the standard copy_to_user, to allow for a conversion from + * 16 bit to 8 bit and from stereo to mono, if the record conversion is active. + * The current CS46xx/CS4280 static image only records in 16bit unsigned Stereo, + * so we convert from any of the other format combinations. + */ +static unsigned cs_copy_to_user( + struct cs_state *s, + void __user *dest, + void *hwsrc, + unsigned cnt, + unsigned *copied) +{ + struct dmabuf *dmabuf = &s->dmabuf; + void *src = hwsrc; /* default to the standard destination buffer addr */ + + CS_DBGOUT(CS_FUNCTION, 6, printk(KERN_INFO + "cs_copy_to_user()+ fmt=0x%x cnt=%d dest=%p\n", + dmabuf->fmt,(unsigned)cnt,dest) ); + + if(cnt > dmabuf->dmasize) + { + cnt = dmabuf->dmasize; + } + if(!cnt) + { + *copied = 0; + return 0; + } + if(dmabuf->divisor != 1) + { + if(!dmabuf->tmpbuff) + { + *copied = cnt/dmabuf->divisor; + return 0; + } + + CopySamples((char *)dmabuf->tmpbuff, (char *)hwsrc, cnt, + dmabuf->fmt, dmabuf); + src = dmabuf->tmpbuff; + cnt = cnt/dmabuf->divisor; + } + if (copy_to_user(dest, src, cnt)) + { + CS_DBGOUT(CS_FUNCTION, 2, printk(KERN_ERR + "cs46xx: cs_copy_to_user()- fault dest=%p src=%p cnt=%d\n", + dest,src,cnt) ); + *copied = 0; + return -EFAULT; + } + *copied = cnt; + CS_DBGOUT(CS_FUNCTION, 2, printk(KERN_INFO + "cs46xx: cs_copy_to_user()- copied bytes is %d \n",cnt) ); + return 0; +} + +/* in this loop, dmabuf.count signifies the amount of data that is waiting to be copied to + the user's buffer. it is filled by the dma machine and drained by this loop. */ +static ssize_t cs_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct cs_card *card = (struct cs_card *) file->private_data; + struct cs_state *state; + DECLARE_WAITQUEUE(wait, current); + struct dmabuf *dmabuf; + ssize_t ret = 0; + unsigned long flags; + unsigned swptr; + int cnt; + unsigned copied=0; + + CS_DBGOUT(CS_WAVE_READ | CS_FUNCTION, 4, + printk("cs46xx: cs_read()+ %zd\n",count) ); + state = (struct cs_state *)card->states[0]; + if(!state) + return -ENODEV; + dmabuf = &state->dmabuf; + + if (dmabuf->mapped) + return -ENXIO; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + + down(&state->sem); + if (!dmabuf->ready && (ret = __prog_dmabuf(state))) + goto out2; + + add_wait_queue(&state->dmabuf.wait, &wait); + while (count > 0) { + while(!(card->pm.flags & CS46XX_PM_IDLE)) + { + schedule(); + if (signal_pending(current)) { + if(!ret) ret = -ERESTARTSYS; + goto out; + } + } + spin_lock_irqsave(&state->card->lock, flags); + swptr = dmabuf->swptr; + cnt = dmabuf->dmasize - swptr; + if (dmabuf->count < cnt) + cnt = dmabuf->count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&state->card->lock, flags); + + if (cnt > (count * dmabuf->divisor)) + cnt = count * dmabuf->divisor; + if (cnt <= 0) { + /* buffer is empty, start the dma machine and wait for data to be + recorded */ + start_adc(state); + if (file->f_flags & O_NONBLOCK) { + if (!ret) ret = -EAGAIN; + goto out; + } + up(&state->sem); + schedule(); + if (signal_pending(current)) { + if(!ret) ret = -ERESTARTSYS; + goto out; + } + down(&state->sem); + if (dmabuf->mapped) + { + if(!ret) + ret = -ENXIO; + goto out; + } + continue; + } + + CS_DBGOUT(CS_WAVE_READ, 2, printk(KERN_INFO + "_read() copy_to cnt=%d count=%zd ", cnt,count) ); + CS_DBGOUT(CS_WAVE_READ, 8, printk(KERN_INFO + " .dmasize=%d .count=%d buffer=%p ret=%zd\n", + dmabuf->dmasize,dmabuf->count,buffer,ret) ); + + if (cs_copy_to_user(state, buffer, + (char *)dmabuf->rawbuf + swptr, cnt, &copied)) + { + if (!ret) ret = -EFAULT; + goto out; + } + swptr = (swptr + cnt) % dmabuf->dmasize; + spin_lock_irqsave(&card->lock, flags); + dmabuf->swptr = swptr; + dmabuf->count -= cnt; + spin_unlock_irqrestore(&card->lock, flags); + count -= copied; + buffer += copied; + ret += copied; + start_adc(state); + } +out: + remove_wait_queue(&state->dmabuf.wait, &wait); +out2: + up(&state->sem); + set_current_state(TASK_RUNNING); + CS_DBGOUT(CS_WAVE_READ | CS_FUNCTION, 4, + printk("cs46xx: cs_read()- %zd\n",ret) ); + return ret; +} + +/* in this loop, dmabuf.count signifies the amount of data that is waiting to be dma to + the soundcard. it is drained by the dma machine and filled by this loop. */ +static ssize_t cs_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct cs_card *card = (struct cs_card *) file->private_data; + struct cs_state *state; + DECLARE_WAITQUEUE(wait, current); + struct dmabuf *dmabuf; + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + CS_DBGOUT(CS_WAVE_WRITE | CS_FUNCTION, 4, + printk("cs46xx: cs_write called, count = %zd\n", count) ); + state = (struct cs_state *)card->states[1]; + if(!state) + return -ENODEV; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + dmabuf = &state->dmabuf; + + down(&state->sem); + if (dmabuf->mapped) + { + ret = -ENXIO; + goto out; + } + + if (!dmabuf->ready && (ret = __prog_dmabuf(state))) + goto out; + add_wait_queue(&state->dmabuf.wait, &wait); + ret = 0; +/* +* Start the loop to read from the user's buffer and write to the dma buffer. +* check for PM events and underrun/overrun in the loop. +*/ + while (count > 0) { + while(!(card->pm.flags & CS46XX_PM_IDLE)) + { + schedule(); + if (signal_pending(current)) { + if(!ret) ret = -ERESTARTSYS; + goto out; + } + } + spin_lock_irqsave(&state->card->lock, flags); + if (dmabuf->count < 0) { + /* buffer underrun, we are recovering from sleep_on_timeout, + resync hwptr and swptr */ + dmabuf->count = 0; + dmabuf->swptr = dmabuf->hwptr; + } + if (dmabuf->underrun) + { + dmabuf->underrun = 0; + dmabuf->hwptr = cs_get_dma_addr(state); + dmabuf->swptr = dmabuf->hwptr; + } + + swptr = dmabuf->swptr; + cnt = dmabuf->dmasize - swptr; + if (dmabuf->count + cnt > dmabuf->dmasize) + cnt = dmabuf->dmasize - dmabuf->count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&state->card->lock, flags); + + if (cnt > count) + cnt = count; + if (cnt <= 0) { + /* buffer is full, start the dma machine and wait for data to be + played */ + start_dac(state); + if (file->f_flags & O_NONBLOCK) { + if (!ret) ret = -EAGAIN; + goto out; + } + up(&state->sem); + schedule(); + if (signal_pending(current)) { + if(!ret) ret = -ERESTARTSYS; + goto out; + } + down(&state->sem); + if (dmabuf->mapped) + { + if(!ret) + ret = -ENXIO; + goto out; + } + continue; + } + if (copy_from_user(dmabuf->rawbuf + swptr, buffer, cnt)) { + if (!ret) ret = -EFAULT; + goto out; + } + spin_lock_irqsave(&state->card->lock, flags); + swptr = (swptr + cnt) % dmabuf->dmasize; + dmabuf->swptr = swptr; + dmabuf->count += cnt; + if(dmabuf->count > dmabuf->dmasize) + { + CS_DBGOUT(CS_WAVE_WRITE | CS_ERROR, 2, printk( + "cs46xx: cs_write() d->count > dmasize - resetting\n")); + dmabuf->count = dmabuf->dmasize; + } + dmabuf->endcleared = 0; + spin_unlock_irqrestore(&state->card->lock, flags); + + count -= cnt; + buffer += cnt; + ret += cnt; + start_dac(state); + } +out: + up(&state->sem); + remove_wait_queue(&state->dmabuf.wait, &wait); + set_current_state(TASK_RUNNING); + + CS_DBGOUT(CS_WAVE_WRITE | CS_FUNCTION, 2, + printk("cs46xx: cs_write()- ret=%zd\n", ret) ); + return ret; +} + +static unsigned int cs_poll(struct file *file, struct poll_table_struct *wait) +{ + struct cs_card *card = (struct cs_card *)file->private_data; + struct dmabuf *dmabuf; + struct cs_state *state; + + unsigned long flags; + unsigned int mask = 0; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_poll()+ \n")); + if (!(file->f_mode & (FMODE_WRITE | FMODE_READ))) + { + return -EINVAL; + } + if (file->f_mode & FMODE_WRITE) + { + state = card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + poll_wait(file, &dmabuf->wait, wait); + } + } + if (file->f_mode & FMODE_READ) + { + state = card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + poll_wait(file, &dmabuf->wait, wait); + } + } + + spin_lock_irqsave(&card->lock, flags); + cs_update_ptr(card, CS_FALSE); + if (file->f_mode & FMODE_READ) { + state = card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + if (dmabuf->count >= (signed)dmabuf->fragsize) + mask |= POLLIN | POLLRDNORM; + } + } + if (file->f_mode & FMODE_WRITE) { + state = card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + if (dmabuf->mapped) { + if (dmabuf->count >= (signed)dmabuf->fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)dmabuf->dmasize >= dmabuf->count + + (signed)dmabuf->fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + } + spin_unlock_irqrestore(&card->lock, flags); + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_poll()- (0x%x) \n", + mask)); + return mask; +} + +/* + * We let users mmap the ring buffer. Its not the real DMA buffer but + * that side of the code is hidden in the IRQ handling. We do a software + * emulation of DMA from a 64K or so buffer into a 2K FIFO. + * (the hardware probably deserves a moan here but Crystal send me nice + * toys ;)). + */ + +static int cs_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct cs_card *card = (struct cs_card *)file->private_data; + struct cs_state *state; + struct dmabuf *dmabuf; + int ret = 0; + unsigned long size; + + CS_DBGOUT(CS_FUNCTION | CS_PARMS, 2, printk("cs46xx: cs_mmap()+ file=%p %s %s\n", + file, vma->vm_flags & VM_WRITE ? "VM_WRITE" : "", + vma->vm_flags & VM_READ ? "VM_READ" : "") ); + + if (vma->vm_flags & VM_WRITE) { + state = card->states[1]; + if(state) + { + CS_DBGOUT(CS_OPEN, 2, printk( + "cs46xx: cs_mmap() VM_WRITE - state TRUE prog_dmabuf DAC\n") ); + if ((ret = prog_dmabuf(state)) != 0) + return ret; + } + } else if (vma->vm_flags & VM_READ) { + state = card->states[0]; + if(state) + { + CS_DBGOUT(CS_OPEN, 2, printk( + "cs46xx: cs_mmap() VM_READ - state TRUE prog_dmabuf ADC\n") ); + if ((ret = prog_dmabuf(state)) != 0) + return ret; + } + } else { + CS_DBGOUT(CS_ERROR, 2, printk( + "cs46xx: cs_mmap() return -EINVAL\n") ); + return -EINVAL; + } + +/* + * For now ONLY support playback, but seems like the only way to use + * mmap() is to open an FD with RDWR, just read or just write access + * does not function, get an error back from the kernel. + * Also, QuakeIII opens with RDWR! So, there must be something + * to needing read/write access mapping. So, allow read/write but + * use the DAC only. + */ + state = card->states[1]; + if (!state) { + ret = -EINVAL; + goto out; + } + + down(&state->sem); + dmabuf = &state->dmabuf; + if (cs4x_pgoff(vma) != 0) + { + ret = -EINVAL; + goto out; + } + size = vma->vm_end - vma->vm_start; + + CS_DBGOUT(CS_PARMS, 2, printk("cs46xx: cs_mmap(): size=%d\n",(unsigned)size) ); + + if (size > (PAGE_SIZE << dmabuf->buforder)) + { + ret = -EINVAL; + goto out; + } + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(dmabuf->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + { + ret = -EAGAIN; + goto out; + } + dmabuf->mapped = 1; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_mmap()-\n") ); +out: + up(&state->sem); + return ret; +} + +static int cs_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct cs_card *card = (struct cs_card *)file->private_data; + struct cs_state *state; + struct dmabuf *dmabuf=NULL; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int val, valsave, mapped, ret; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + mapped = (file->f_mode & FMODE_READ) && dmabuf->mapped; + } + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + mapped |= (file->f_mode & FMODE_WRITE) && dmabuf->mapped; + } + +#if CSDEBUG + printioctl(cmd); +#endif + + switch (cmd) + { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_RESET: + /* FIXME: spin_lock ? */ + if (file->f_mode & FMODE_WRITE) { + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + stop_dac(state); + synchronize_irq(card->irq); + dmabuf->ready = 0; + resync_dma_ptrs(state); + dmabuf->swptr = dmabuf->hwptr = 0; + dmabuf->count = dmabuf->total_bytes = 0; + dmabuf->blocks = 0; + dmabuf->SGok = 0; + } + } + if (file->f_mode & FMODE_READ) { + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + stop_adc(state); + synchronize_irq(card->irq); + resync_dma_ptrs(state); + dmabuf->ready = 0; + dmabuf->swptr = dmabuf->hwptr = 0; + dmabuf->count = dmabuf->total_bytes = 0; + dmabuf->blocks = 0; + dmabuf->SGok = 0; + } + } + CS_DBGOUT(CS_IOCTL, 2, printk("cs46xx: DSP_RESET()-\n") ); + return 0; + + case SNDCTL_DSP_SYNC: + if (file->f_mode & FMODE_WRITE) + return drain_dac(state, file->f_flags & O_NONBLOCK); + return 0; + + case SNDCTL_DSP_SPEED: /* set sample rate */ + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { + if (file->f_mode & FMODE_READ) { + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + stop_adc(state); + dmabuf->ready = 0; + dmabuf->SGok = 0; + cs_set_adc_rate(state, val); + cs_set_divisor(dmabuf); + } + } + if (file->f_mode & FMODE_WRITE) { + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + stop_dac(state); + dmabuf->ready = 0; + dmabuf->SGok = 0; + cs_set_dac_rate(state, val); + cs_set_divisor(dmabuf); + } + } + CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk( + "cs46xx: cs_ioctl() DSP_SPEED %s %s %d\n", + file->f_mode & FMODE_WRITE ? "DAC" : "", + file->f_mode & FMODE_READ ? "ADC" : "", + dmabuf->rate ) ); + return put_user(dmabuf->rate, p); + } + return put_user(0, p); + + case SNDCTL_DSP_STEREO: /* set stereo or mono channel */ + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_WRITE) { + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + stop_dac(state); + dmabuf->ready = 0; + dmabuf->SGok = 0; + if(val) + dmabuf->fmt |= CS_FMT_STEREO; + else + dmabuf->fmt &= ~CS_FMT_STEREO; + cs_set_divisor(dmabuf); + CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk( + "cs46xx: DSP_STEREO() DAC %s\n", + (dmabuf->fmt & CS_FMT_STEREO) ? + "STEREO":"MONO") ); + } + } + if (file->f_mode & FMODE_READ) { + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + stop_adc(state); + dmabuf->ready = 0; + dmabuf->SGok = 0; + if(val) + dmabuf->fmt |= CS_FMT_STEREO; + else + dmabuf->fmt &= ~CS_FMT_STEREO; + cs_set_divisor(dmabuf); + CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk( + "cs46xx: DSP_STEREO() ADC %s\n", + (dmabuf->fmt & CS_FMT_STEREO) ? + "STEREO":"MONO") ); + } + } + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + if ((val = prog_dmabuf(state))) + return val; + return put_user(dmabuf->fragsize, p); + } + } + if (file->f_mode & FMODE_READ) { + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + if ((val = prog_dmabuf(state))) + return val; + return put_user(dmabuf->fragsize/dmabuf->divisor, + p); + } + } + return put_user(0, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask of supported sample format*/ + return put_user(AFMT_S16_LE | AFMT_U8, p); + + case SNDCTL_DSP_SETFMT: /* Select sample format */ + if (get_user(val, p)) + return -EFAULT; + CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk( + "cs46xx: cs_ioctl() DSP_SETFMT %s %s %s %s\n", + file->f_mode & FMODE_WRITE ? "DAC" : "", + file->f_mode & FMODE_READ ? "ADC" : "", + val == AFMT_S16_LE ? "16Bit Signed" : "", + val == AFMT_U8 ? "8Bit Unsigned" : "") ); + valsave = val; + if (val != AFMT_QUERY) { + if(val==AFMT_S16_LE || val==AFMT_U8) + { + if (file->f_mode & FMODE_WRITE) { + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + stop_dac(state); + dmabuf->ready = 0; + dmabuf->SGok = 0; + if(val==AFMT_S16_LE) + dmabuf->fmt |= CS_FMT_16BIT; + else + dmabuf->fmt &= ~CS_FMT_16BIT; + cs_set_divisor(dmabuf); + if((ret = prog_dmabuf(state))) + return ret; + } + } + if (file->f_mode & FMODE_READ) { + val = valsave; + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + stop_adc(state); + dmabuf->ready = 0; + dmabuf->SGok = 0; + if(val==AFMT_S16_LE) + dmabuf->fmt |= CS_FMT_16BIT; + else + dmabuf->fmt &= ~CS_FMT_16BIT; + cs_set_divisor(dmabuf); + if((ret = prog_dmabuf(state))) + return ret; + } + } + } + else + { + CS_DBGOUT(CS_IOCTL | CS_ERROR, 2, printk( + "cs46xx: DSP_SETFMT() Unsupported format (0x%x)\n", + valsave) ); + } + } + else + { + if(file->f_mode & FMODE_WRITE) + { + state = (struct cs_state *)card->states[1]; + if(state) + dmabuf = &state->dmabuf; + } + else if(file->f_mode & FMODE_READ) + { + state = (struct cs_state *)card->states[0]; + if(state) + dmabuf = &state->dmabuf; + } + } + if(dmabuf) + { + if(dmabuf->fmt & CS_FMT_16BIT) + return put_user(AFMT_S16_LE, p); + else + return put_user(AFMT_U8, p); + } + return put_user(0, p); + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + if (val != 0) { + if (file->f_mode & FMODE_WRITE) { + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + stop_dac(state); + dmabuf->ready = 0; + dmabuf->SGok = 0; + if(val>1) + dmabuf->fmt |= CS_FMT_STEREO; + else + dmabuf->fmt &= ~CS_FMT_STEREO; + cs_set_divisor(dmabuf); + if (prog_dmabuf(state)) + return 0; + } + } + if (file->f_mode & FMODE_READ) { + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + stop_adc(state); + dmabuf->ready = 0; + dmabuf->SGok = 0; + if(val>1) + dmabuf->fmt |= CS_FMT_STEREO; + else + dmabuf->fmt &= ~CS_FMT_STEREO; + cs_set_divisor(dmabuf); + if (prog_dmabuf(state)) + return 0; + } + } + } + return put_user((dmabuf->fmt & CS_FMT_STEREO) ? 2 : 1, + p); + + case SNDCTL_DSP_POST: + /* + * There will be a longer than normal pause in the data. + * so... do nothing, because there is nothing that we can do. + */ + return 0; + + case SNDCTL_DSP_SUBDIVIDE: + if (file->f_mode & FMODE_WRITE) { + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + if (dmabuf->subdivision) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + if (val != 1 && val != 2) + return -EINVAL; + dmabuf->subdivision = val; + } + } + if (file->f_mode & FMODE_READ) { + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + if (dmabuf->subdivision) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + if (val != 1 && val != 2) + return -EINVAL; + dmabuf->subdivision = val; + } + } + return 0; + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + + if (file->f_mode & FMODE_WRITE) { + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + dmabuf->ossfragshift = val & 0xffff; + dmabuf->ossmaxfrags = (val >> 16) & 0xffff; + } + } + if (file->f_mode & FMODE_READ) { + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + dmabuf->ossfragshift = val & 0xffff; + dmabuf->ossmaxfrags = (val >> 16) & 0xffff; + } + } + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + spin_lock_irqsave(&state->card->lock, flags); + cs_update_ptr(card, CS_TRUE); + abinfo.fragsize = dmabuf->fragsize; + abinfo.fragstotal = dmabuf->numfrag; + /* + * for mmap we always have total space available + */ + if (dmabuf->mapped) + abinfo.bytes = dmabuf->dmasize; + else + abinfo.bytes = dmabuf->dmasize - dmabuf->count; + + abinfo.fragments = abinfo.bytes >> dmabuf->fragshift; + spin_unlock_irqrestore(&state->card->lock, flags); + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + } + return -ENODEV; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + spin_lock_irqsave(&state->card->lock, flags); + cs_update_ptr(card, CS_TRUE); + abinfo.fragsize = dmabuf->fragsize/dmabuf->divisor; + abinfo.bytes = dmabuf->count/dmabuf->divisor; + abinfo.fragstotal = dmabuf->numfrag; + abinfo.fragments = abinfo.bytes >> dmabuf->fragshift; + spin_unlock_irqrestore(&state->card->lock, flags); + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + } + return -ENODEV; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETCAPS: + return put_user(DSP_CAP_REALTIME|DSP_CAP_TRIGGER|DSP_CAP_MMAP, + p); + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + CS_DBGOUT(CS_IOCTL, 2, printk("cs46xx: DSP_GETTRIGGER()+\n") ); + if (file->f_mode & FMODE_WRITE) + { + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + if(dmabuf->enable & DAC_RUNNING) + val |= PCM_ENABLE_INPUT; + } + } + if (file->f_mode & FMODE_READ) + { + if(state) + { + state = (struct cs_state *)card->states[0]; + dmabuf = &state->dmabuf; + if(dmabuf->enable & ADC_RUNNING) + val |= PCM_ENABLE_OUTPUT; + } + } + CS_DBGOUT(CS_IOCTL, 2, printk("cs46xx: DSP_GETTRIGGER()- val=0x%x\n",val) ); + return put_user(val, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + if (val & PCM_ENABLE_INPUT) { + if (!dmabuf->ready && (ret = prog_dmabuf(state))) + return ret; + start_adc(state); + } else + stop_adc(state); + } + } + if (file->f_mode & FMODE_WRITE) { + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + if (val & PCM_ENABLE_OUTPUT) { + if (!dmabuf->ready && (ret = prog_dmabuf(state))) + return ret; + start_dac(state); + } else + stop_dac(state); + } + } + return 0; + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + state = (struct cs_state *)card->states[0]; + if(state) + { + dmabuf = &state->dmabuf; + spin_lock_irqsave(&state->card->lock, flags); + cs_update_ptr(card, CS_TRUE); + cinfo.bytes = dmabuf->total_bytes/dmabuf->divisor; + cinfo.blocks = dmabuf->count/dmabuf->divisor >> dmabuf->fragshift; + cinfo.ptr = dmabuf->hwptr/dmabuf->divisor; + spin_unlock_irqrestore(&state->card->lock, flags); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + } + return -ENODEV; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + spin_lock_irqsave(&state->card->lock, flags); + cs_update_ptr(card, CS_TRUE); + cinfo.bytes = dmabuf->total_bytes; + if (dmabuf->mapped) + { + cinfo.blocks = (cinfo.bytes >> dmabuf->fragshift) + - dmabuf->blocks; + CS_DBGOUT(CS_PARMS, 8, + printk("total_bytes=%d blocks=%d dmabuf->blocks=%d\n", + cinfo.bytes,cinfo.blocks,dmabuf->blocks) ); + dmabuf->blocks = cinfo.bytes >> dmabuf->fragshift; + } + else + { + cinfo.blocks = dmabuf->count >> dmabuf->fragshift; + } + cinfo.ptr = dmabuf->hwptr; + + CS_DBGOUT(CS_PARMS, 4, printk( + "cs46xx: GETOPTR bytes=%d blocks=%d ptr=%d\n", + cinfo.bytes,cinfo.blocks,cinfo.ptr) ); + spin_unlock_irqrestore(&state->card->lock, flags); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + } + return -ENODEV; + + case SNDCTL_DSP_SETDUPLEX: + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + spin_lock_irqsave(&state->card->lock, flags); + cs_update_ptr(card, CS_TRUE); + val = dmabuf->count; + spin_unlock_irqrestore(&state->card->lock, flags); + } + else + val = 0; + return put_user(val, p); + + case SOUND_PCM_READ_RATE: + if(file->f_mode & FMODE_READ) + state = (struct cs_state *)card->states[0]; + else + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + return put_user(dmabuf->rate, p); + } + return put_user(0, p); + + + case SOUND_PCM_READ_CHANNELS: + if(file->f_mode & FMODE_READ) + state = (struct cs_state *)card->states[0]; + else + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + return put_user((dmabuf->fmt & CS_FMT_STEREO) ? 2 : 1, + p); + } + return put_user(0, p); + + case SOUND_PCM_READ_BITS: + if(file->f_mode & FMODE_READ) + state = (struct cs_state *)card->states[0]; + else + state = (struct cs_state *)card->states[1]; + if(state) + { + dmabuf = &state->dmabuf; + return put_user((dmabuf->fmt & CS_FMT_16BIT) ? + AFMT_S16_LE : AFMT_U8, p); + + } + return put_user(0, p); + + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + } + return -EINVAL; +} + + +/* + * AMP control - null AMP + */ + +static void amp_none(struct cs_card *card, int change) +{ +} + +/* + * Crystal EAPD mode + */ + +static void amp_voyetra(struct cs_card *card, int change) +{ + /* Manage the EAPD bit on the Crystal 4297 + and the Analog AD1885 */ + + int old=card->amplifier; + + card->amplifier+=change; + if(card->amplifier && !old) + { + /* Turn the EAPD amp on */ + cs_ac97_set(card->ac97_codec[0], AC97_POWER_CONTROL, + cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) | + 0x8000); + } + else if(old && !card->amplifier) + { + /* Turn the EAPD amp off */ + cs_ac97_set(card->ac97_codec[0], AC97_POWER_CONTROL, + cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + ~0x8000); + } +} + + +/* + * Game Theatre XP card - EGPIO[2] is used to enable the external amp. + */ + +static void amp_hercules(struct cs_card *card, int change) +{ + int old=card->amplifier; + if(!card) + { + CS_DBGOUT(CS_ERROR, 2, printk(KERN_INFO + "cs46xx: amp_hercules() called before initialized.\n")); + return; + } + card->amplifier+=change; + if( (card->amplifier && !old) && !(hercules_egpio_disable)) + { + CS_DBGOUT(CS_PARMS, 4, printk(KERN_INFO + "cs46xx: amp_hercules() external amp enabled\n")); + cs461x_pokeBA0(card, BA0_EGPIODR, + EGPIODR_GPOE2); /* enable EGPIO2 output */ + cs461x_pokeBA0(card, BA0_EGPIOPTR, + EGPIOPTR_GPPT2); /* open-drain on output */ + } + else if(old && !card->amplifier) + { + CS_DBGOUT(CS_PARMS, 4, printk(KERN_INFO + "cs46xx: amp_hercules() external amp disabled\n")); + cs461x_pokeBA0(card, BA0_EGPIODR, 0); /* disable */ + cs461x_pokeBA0(card, BA0_EGPIOPTR, 0); /* disable */ + } +} + +/* + * Handle the CLKRUN on a thinkpad. We must disable CLKRUN support + * whenever we need to beat on the chip. + * + * The original idea and code for this hack comes from David Kaiser at + * Linuxcare. Perhaps one day Crystal will document their chips well + * enough to make them useful. + */ + +static void clkrun_hack(struct cs_card *card, int change) +{ + struct pci_dev *acpi_dev; + u16 control; + u8 pp; + unsigned long port; + int old=card->active; + + card->active+=change; + + acpi_dev = pci_find_device(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3, NULL); + if(acpi_dev == NULL) + return; /* Not a thinkpad thats for sure */ + + /* Find the control port */ + pci_read_config_byte(acpi_dev, 0x41, &pp); + port=pp<<8; + + /* Read ACPI port */ + control=inw(port+0x10); + + /* Flip CLKRUN off while running */ + if(!card->active && old) + { + CS_DBGOUT(CS_PARMS , 9, printk( KERN_INFO + "cs46xx: clkrun() enable clkrun - change=%d active=%d\n", + change,card->active)); + outw(control|0x2000, port+0x10); + } + else + { + /* + * sometimes on a resume the bit is set, so always reset the bit. + */ + CS_DBGOUT(CS_PARMS , 9, printk( KERN_INFO + "cs46xx: clkrun() disable clkrun - change=%d active=%d\n", + change,card->active)); + outw(control&~0x2000, port+0x10); + } +} + + +static int cs_open(struct inode *inode, struct file *file) +{ + struct cs_card *card = (struct cs_card *)file->private_data; + struct cs_state *state = NULL; + struct dmabuf *dmabuf = NULL; + struct list_head *entry; + unsigned int minor = iminor(inode); + int ret=0; + unsigned int tmp; + + CS_DBGOUT(CS_OPEN | CS_FUNCTION, 2, printk("cs46xx: cs_open()+ file=%p %s %s\n", + file, file->f_mode & FMODE_WRITE ? "FMODE_WRITE" : "", + file->f_mode & FMODE_READ ? "FMODE_READ" : "") ); + + list_for_each(entry, &cs46xx_devs) + { + card = list_entry(entry, struct cs_card, list); + + if (!((card->dev_audio ^ minor) & ~0xf)) + break; + } + if (entry == &cs46xx_devs) + return -ENODEV; + if (!card) { + CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, printk(KERN_INFO + "cs46xx: cs_open(): Error - unable to find audio card struct\n")); + return -ENODEV; + } + + /* + * hardcode state[0] for capture, [1] for playback + */ + if(file->f_mode & FMODE_READ) + { + CS_DBGOUT(CS_WAVE_READ, 2, printk("cs46xx: cs_open() FMODE_READ\n") ); + if (card->states[0] == NULL) { + state = card->states[0] = (struct cs_state *) + kmalloc(sizeof(struct cs_state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + memset(state, 0, sizeof(struct cs_state)); + init_MUTEX(&state->sem); + dmabuf = &state->dmabuf; + dmabuf->pbuf = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + if(dmabuf->pbuf==NULL) + { + kfree(state); + card->states[0]=NULL; + return -ENOMEM; + } + } + else + { + state = card->states[0]; + if(state->open_mode & FMODE_READ) + return -EBUSY; + } + dmabuf->channel = card->alloc_rec_pcm_channel(card); + + if (dmabuf->channel == NULL) { + kfree (card->states[0]); + card->states[0] = NULL; + return -ENODEV; + } + + /* Now turn on external AMP if needed */ + state->card = card; + state->card->active_ctrl(state->card,1); + state->card->amplifier_ctrl(state->card,1); + + if( (tmp = cs46xx_powerup(card, CS_POWER_ADC)) ) + { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_INFO + "cs46xx: cs46xx_powerup of ADC failed (0x%x)\n",tmp) ); + return -EIO; + } + + dmabuf->channel->state = state; + /* initialize the virtual channel */ + state->virt = 0; + state->magic = CS_STATE_MAGIC; + init_waitqueue_head(&dmabuf->wait); + init_MUTEX(&state->open_sem); + file->private_data = card; + + down(&state->open_sem); + + /* set default sample format. According to OSS Programmer's Guide /dev/dsp + should be default to unsigned 8-bits, mono, with sample rate 8kHz and + /dev/dspW will accept 16-bits sample */ + + /* Default input is 8bit mono */ + dmabuf->fmt &= ~CS_FMT_MASK; + dmabuf->type = CS_TYPE_ADC; + dmabuf->ossfragshift = 0; + dmabuf->ossmaxfrags = 0; + dmabuf->subdivision = 0; + cs_set_adc_rate(state, 8000); + cs_set_divisor(dmabuf); + + state->open_mode |= FMODE_READ; + up(&state->open_sem); + } + if(file->f_mode & FMODE_WRITE) + { + CS_DBGOUT(CS_OPEN, 2, printk("cs46xx: cs_open() FMODE_WRITE\n") ); + if (card->states[1] == NULL) { + state = card->states[1] = (struct cs_state *) + kmalloc(sizeof(struct cs_state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + memset(state, 0, sizeof(struct cs_state)); + init_MUTEX(&state->sem); + dmabuf = &state->dmabuf; + dmabuf->pbuf = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + if(dmabuf->pbuf==NULL) + { + kfree(state); + card->states[1]=NULL; + return -ENOMEM; + } + } + else + { + state = card->states[1]; + if(state->open_mode & FMODE_WRITE) + return -EBUSY; + } + dmabuf->channel = card->alloc_pcm_channel(card); + + if (dmabuf->channel == NULL) { + kfree (card->states[1]); + card->states[1] = NULL; + return -ENODEV; + } + + /* Now turn on external AMP if needed */ + state->card = card; + state->card->active_ctrl(state->card,1); + state->card->amplifier_ctrl(state->card,1); + + if( (tmp = cs46xx_powerup(card, CS_POWER_DAC)) ) + { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_INFO + "cs46xx: cs46xx_powerup of DAC failed (0x%x)\n",tmp) ); + return -EIO; + } + + dmabuf->channel->state = state; + /* initialize the virtual channel */ + state->virt = 1; + state->magic = CS_STATE_MAGIC; + init_waitqueue_head(&dmabuf->wait); + init_MUTEX(&state->open_sem); + file->private_data = card; + + down(&state->open_sem); + + /* set default sample format. According to OSS Programmer's Guide /dev/dsp + should be default to unsigned 8-bits, mono, with sample rate 8kHz and + /dev/dspW will accept 16-bits sample */ + + /* Default output is 8bit mono. */ + dmabuf->fmt &= ~CS_FMT_MASK; + dmabuf->type = CS_TYPE_DAC; + dmabuf->ossfragshift = 0; + dmabuf->ossmaxfrags = 0; + dmabuf->subdivision = 0; + cs_set_dac_rate(state, 8000); + cs_set_divisor(dmabuf); + + state->open_mode |= FMODE_WRITE; + up(&state->open_sem); + if((ret = prog_dmabuf(state))) + return ret; + } + CS_DBGOUT(CS_OPEN | CS_FUNCTION, 2, printk("cs46xx: cs_open()- 0\n") ); + return nonseekable_open(inode, file); +} + +static int cs_release(struct inode *inode, struct file *file) +{ + struct cs_card *card = (struct cs_card *)file->private_data; + struct dmabuf *dmabuf; + struct cs_state *state; + unsigned int tmp; + CS_DBGOUT(CS_RELEASE | CS_FUNCTION, 2, printk("cs46xx: cs_release()+ file=%p %s %s\n", + file, file->f_mode & FMODE_WRITE ? "FMODE_WRITE" : "", + file->f_mode & FMODE_READ ? "FMODE_READ" : "") ); + + if (!(file->f_mode & (FMODE_WRITE | FMODE_READ))) + { + return -EINVAL; + } + state = card->states[1]; + if(state) + { + if ( (state->open_mode & FMODE_WRITE) & (file->f_mode & FMODE_WRITE) ) + { + CS_DBGOUT(CS_RELEASE, 2, printk("cs46xx: cs_release() FMODE_WRITE\n") ); + dmabuf = &state->dmabuf; + cs_clear_tail(state); + drain_dac(state, file->f_flags & O_NONBLOCK); + /* stop DMA state machine and free DMA buffers/channels */ + down(&state->open_sem); + stop_dac(state); + dealloc_dmabuf(state); + state->card->free_pcm_channel(state->card, dmabuf->channel->num); + free_page((unsigned long)state->dmabuf.pbuf); + + /* we're covered by the open_sem */ + up(&state->open_sem); + state->card->states[state->virt] = NULL; + state->open_mode &= (~file->f_mode) & (FMODE_READ|FMODE_WRITE); + + if( (tmp = cs461x_powerdown(card, CS_POWER_DAC, CS_FALSE )) ) + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_INFO + "cs46xx: cs_release_mixdev() powerdown DAC failure (0x%x)\n",tmp) ); + } + + /* Now turn off external AMP if needed */ + state->card->amplifier_ctrl(state->card, -1); + state->card->active_ctrl(state->card, -1); + + kfree(state); + } + } + + state = card->states[0]; + if(state) + { + if ( (state->open_mode & FMODE_READ) & (file->f_mode & FMODE_READ) ) + { + CS_DBGOUT(CS_RELEASE, 2, printk("cs46xx: cs_release() FMODE_READ\n") ); + dmabuf = &state->dmabuf; + down(&state->open_sem); + stop_adc(state); + dealloc_dmabuf(state); + state->card->free_pcm_channel(state->card, dmabuf->channel->num); + free_page((unsigned long)state->dmabuf.pbuf); + + /* we're covered by the open_sem */ + up(&state->open_sem); + state->card->states[state->virt] = NULL; + state->open_mode &= (~file->f_mode) & (FMODE_READ|FMODE_WRITE); + + if( (tmp = cs461x_powerdown(card, CS_POWER_ADC, CS_FALSE )) ) + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_INFO + "cs46xx: cs_release_mixdev() powerdown ADC failure (0x%x)\n",tmp) ); + } + + /* Now turn off external AMP if needed */ + state->card->amplifier_ctrl(state->card, -1); + state->card->active_ctrl(state->card, -1); + + kfree(state); + } + } + + CS_DBGOUT(CS_FUNCTION | CS_RELEASE, 2, printk("cs46xx: cs_release()- 0\n") ); + return 0; +} + +static void printpm(struct cs_card *s) +{ + CS_DBGOUT(CS_PM, 9, printk("pm struct:\n")); + CS_DBGOUT(CS_PM, 9, printk("flags:0x%x u32CLKCR1_SAVE: 0%x u32SSPMValue: 0x%x\n", + (unsigned)s->pm.flags,s->pm.u32CLKCR1_SAVE,s->pm.u32SSPMValue)); + CS_DBGOUT(CS_PM, 9, printk("u32PPLVCvalue: 0x%x u32PPRVCvalue: 0x%x\n", + s->pm.u32PPLVCvalue,s->pm.u32PPRVCvalue)); + CS_DBGOUT(CS_PM, 9, printk("u32FMLVCvalue: 0x%x u32FMRVCvalue: 0x%x\n", + s->pm.u32FMLVCvalue,s->pm.u32FMRVCvalue)); + CS_DBGOUT(CS_PM, 9, printk("u32GPIORvalue: 0x%x u32JSCTLvalue: 0x%x\n", + s->pm.u32GPIORvalue,s->pm.u32JSCTLvalue)); + CS_DBGOUT(CS_PM, 9, printk("u32SSCR: 0x%x u32SRCSA: 0x%x\n", + s->pm.u32SSCR,s->pm.u32SRCSA)); + CS_DBGOUT(CS_PM, 9, printk("u32DacASR: 0x%x u32AdcASR: 0x%x\n", + s->pm.u32DacASR,s->pm.u32AdcASR)); + CS_DBGOUT(CS_PM, 9, printk("u32DacSR: 0x%x u32AdcSR: 0x%x\n", + s->pm.u32DacSR,s->pm.u32AdcSR)); + CS_DBGOUT(CS_PM, 9, printk("u32MIDCR_Save: 0x%x\n", + s->pm.u32MIDCR_Save)); + CS_DBGOUT(CS_PM, 9, printk("u32AC97_powerdown: 0x%x _general_purpose 0x%x\n", + s->pm.u32AC97_powerdown,s->pm.u32AC97_general_purpose)); + CS_DBGOUT(CS_PM, 9, printk("u32AC97_master_volume: 0x%x\n", + s->pm.u32AC97_master_volume)); + CS_DBGOUT(CS_PM, 9, printk("u32AC97_headphone_volume: 0x%x\n", + s->pm.u32AC97_headphone_volume)); + CS_DBGOUT(CS_PM, 9, printk("u32AC97_master_volume_mono: 0x%x\n", + s->pm.u32AC97_master_volume_mono)); + CS_DBGOUT(CS_PM, 9, printk("u32AC97_pcm_out_volume: 0x%x\n", + s->pm.u32AC97_pcm_out_volume)); + CS_DBGOUT(CS_PM, 9, printk("dmabuf_swptr_play: 0x%x dmabuf_count_play: %d\n", + s->pm.dmabuf_swptr_play,s->pm.dmabuf_count_play)); + CS_DBGOUT(CS_PM, 9, printk("dmabuf_swptr_capture: 0x%x dmabuf_count_capture: %d\n", + s->pm.dmabuf_swptr_capture,s->pm.dmabuf_count_capture)); + +} + +/**************************************************************************** +* +* Suspend - save the ac97 regs, mute the outputs and power down the part. +* +****************************************************************************/ +static void cs46xx_ac97_suspend(struct cs_card *card) +{ + int Count,i; + struct ac97_codec *dev=card->ac97_codec[0]; + unsigned int tmp; + + CS_DBGOUT(CS_PM, 9, printk("cs46xx: cs46xx_ac97_suspend()+\n")); + + if(card->states[1]) + { + stop_dac(card->states[1]); + resync_dma_ptrs(card->states[1]); + } + if(card->states[0]) + { + stop_adc(card->states[0]); + resync_dma_ptrs(card->states[0]); + } + + for(Count = 0x2, i=0; (Count <= CS46XX_AC97_HIGHESTREGTORESTORE) + && (i < CS46XX_AC97_NUMBER_RESTORE_REGS); + Count += 2, i++) + { + card->pm.ac97[i] = cs_ac97_get(dev, BA0_AC97_RESET + Count); + } +/* +* Save the ac97 volume registers as well as the current powerdown state. +* Now, mute the all the outputs (master, headphone, and mono), as well +* as the PCM volume, in preparation for powering down the entire part. + card->pm.u32AC97_master_volume = (u32)cs_ac97_get( dev, + (u8)BA0_AC97_MASTER_VOLUME); + card->pm.u32AC97_headphone_volume = (u32)cs_ac97_get(dev, + (u8)BA0_AC97_HEADPHONE_VOLUME); + card->pm.u32AC97_master_volume_mono = (u32)cs_ac97_get(dev, + (u8)BA0_AC97_MASTER_VOLUME_MONO); + card->pm.u32AC97_pcm_out_volume = (u32)cs_ac97_get(dev, + (u8)BA0_AC97_PCM_OUT_VOLUME); +*/ +/* +* mute the outputs +*/ + cs_ac97_set(dev, (u8)BA0_AC97_MASTER_VOLUME, 0x8000); + cs_ac97_set(dev, (u8)BA0_AC97_HEADPHONE_VOLUME, 0x8000); + cs_ac97_set(dev, (u8)BA0_AC97_MASTER_VOLUME_MONO, 0x8000); + cs_ac97_set(dev, (u8)BA0_AC97_PCM_OUT_VOLUME, 0x8000); + +/* +* save the registers that cause pops +*/ + card->pm.u32AC97_powerdown = (u32)cs_ac97_get(dev, (u8)AC97_POWER_CONTROL); + card->pm.u32AC97_general_purpose = (u32)cs_ac97_get(dev, (u8)BA0_AC97_GENERAL_PURPOSE); +/* +* And power down everything on the AC97 codec. +* well, for now, only power down the DAC/ADC and MIXER VREFON components. +* trouble with removing VREF. +*/ + if( (tmp = cs461x_powerdown(card, CS_POWER_DAC | CS_POWER_ADC | + CS_POWER_MIXVON, CS_TRUE )) ) + { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_INFO + "cs46xx: cs46xx_ac97_suspend() failure (0x%x)\n",tmp) ); + } + + CS_DBGOUT(CS_PM, 9, printk("cs46xx: cs46xx_ac97_suspend()-\n")); +} + +/**************************************************************************** +* +* Resume - power up the part and restore its registers.. +* +****************************************************************************/ +static void cs46xx_ac97_resume(struct cs_card *card) +{ + int Count,i; + struct ac97_codec *dev=card->ac97_codec[0]; + + CS_DBGOUT(CS_PM, 9, printk("cs46xx: cs46xx_ac97_resume()+\n")); + +/* +* First, we restore the state of the general purpose register. This +* contains the mic select (mic1 or mic2) and if we restore this after +* we restore the mic volume/boost state and mic2 was selected at +* suspend time, we will end up with a brief period of time where mic1 +* is selected with the volume/boost settings for mic2, causing +* acoustic feedback. So we restore the general purpose register +* first, thereby getting the correct mic selected before we restore +* the mic volume/boost. +*/ + cs_ac97_set(dev, (u8)BA0_AC97_GENERAL_PURPOSE, + (u16)card->pm.u32AC97_general_purpose); +/* +* Now, while the outputs are still muted, restore the state of power +* on the AC97 part. +*/ + cs_ac97_set(dev, (u8)BA0_AC97_POWERDOWN, (u16)card->pm.u32AC97_powerdown); + mdelay(5 * cs_laptop_wait); +/* +* Restore just the first set of registers, from register number +* 0x02 to the register number that ulHighestRegToRestore specifies. +*/ + for( Count = 0x2, i=0; + (Count <= CS46XX_AC97_HIGHESTREGTORESTORE) + && (i < CS46XX_AC97_NUMBER_RESTORE_REGS); + Count += 2, i++) + { + cs_ac97_set(dev, (u8)(BA0_AC97_RESET + Count), (u16)card->pm.ac97[i]); + } + + /* Check if we have to init the amplifier */ + if(card->amp_init) + card->amp_init(card); + + CS_DBGOUT(CS_PM, 9, printk("cs46xx: cs46xx_ac97_resume()-\n")); +} + + +static int cs46xx_restart_part(struct cs_card *card) +{ + struct dmabuf *dmabuf; + CS_DBGOUT(CS_PM | CS_FUNCTION, 4, + printk( "cs46xx: cs46xx_restart_part()+\n")); + if(card->states[1]) + { + dmabuf = &card->states[1]->dmabuf; + dmabuf->ready = 0; + resync_dma_ptrs(card->states[1]); + cs_set_divisor(dmabuf); + if(__prog_dmabuf(card->states[1])) + { + CS_DBGOUT(CS_PM | CS_ERROR, 1, + printk("cs46xx: cs46xx_restart_part()- (-1) prog_dmabuf() dac error\n")); + return -1; + } + cs_set_dac_rate(card->states[1], dmabuf->rate); + } + if(card->states[0]) + { + dmabuf = &card->states[0]->dmabuf; + dmabuf->ready = 0; + resync_dma_ptrs(card->states[0]); + cs_set_divisor(dmabuf); + if(__prog_dmabuf(card->states[0])) + { + CS_DBGOUT(CS_PM | CS_ERROR, 1, + printk("cs46xx: cs46xx_restart_part()- (-1) prog_dmabuf() adc error\n")); + return -1; + } + cs_set_adc_rate(card->states[0], dmabuf->rate); + } + card->pm.flags |= CS46XX_PM_RESUMED; + if(card->states[0]) + start_adc(card->states[0]); + if(card->states[1]) + start_dac(card->states[1]); + + card->pm.flags |= CS46XX_PM_IDLE; + card->pm.flags &= ~(CS46XX_PM_SUSPENDING | CS46XX_PM_SUSPENDED + | CS46XX_PM_RESUMING | CS46XX_PM_RESUMED); + if(card->states[0]) + wake_up(&card->states[0]->dmabuf.wait); + if(card->states[1]) + wake_up(&card->states[1]->dmabuf.wait); + + CS_DBGOUT(CS_PM | CS_FUNCTION, 4, + printk( "cs46xx: cs46xx_restart_part()-\n")); + return 0; +} + + +static void cs461x_reset(struct cs_card *card); +static void cs461x_proc_stop(struct cs_card *card); +static int cs46xx_suspend(struct cs_card *card, u32 state) +{ + unsigned int tmp; + CS_DBGOUT(CS_PM | CS_FUNCTION, 4, + printk("cs46xx: cs46xx_suspend()+ flags=0x%x s=%p\n", + (unsigned)card->pm.flags,card)); +/* +* check the current state, only suspend if IDLE +*/ + if(!(card->pm.flags & CS46XX_PM_IDLE)) + { + CS_DBGOUT(CS_PM | CS_ERROR, 2, + printk("cs46xx: cs46xx_suspend() unable to suspend, not IDLE\n")); + return 1; + } + card->pm.flags &= ~CS46XX_PM_IDLE; + card->pm.flags |= CS46XX_PM_SUSPENDING; + + card->active_ctrl(card,1); + + tmp = cs461x_peek(card, BA1_PFIE); + tmp &= ~0x0000f03f; + tmp |= 0x00000010; + cs461x_poke(card, BA1_PFIE, tmp); /* playback interrupt disable */ + + tmp = cs461x_peek(card, BA1_CIE); + tmp &= ~0x0000003f; + tmp |= 0x00000011; + cs461x_poke(card, BA1_CIE, tmp); /* capture interrupt disable */ + + /* + * Stop playback DMA. + */ + tmp = cs461x_peek(card, BA1_PCTL); + cs461x_poke(card, BA1_PCTL, tmp & 0x0000ffff); + + /* + * Stop capture DMA. + */ + tmp = cs461x_peek(card, BA1_CCTL); + cs461x_poke(card, BA1_CCTL, tmp & 0xffff0000); + + if(card->states[1]) + { + card->pm.dmabuf_swptr_play = card->states[1]->dmabuf.swptr; + card->pm.dmabuf_count_play = card->states[1]->dmabuf.count; + } + if(card->states[0]) + { + card->pm.dmabuf_swptr_capture = card->states[0]->dmabuf.swptr; + card->pm.dmabuf_count_capture = card->states[0]->dmabuf.count; + } + + cs46xx_ac97_suspend(card); + + /* + * Reset the processor. + */ + cs461x_reset(card); + + cs461x_proc_stop(card); + + /* + * Power down the DAC and ADC. For now leave the other areas on. + */ + cs_ac97_set(card->ac97_codec[0], AC97_POWER_CONTROL, 0x0300); + + /* + * Power down the PLL. + */ + cs461x_pokeBA0(card, BA0_CLKCR1, 0); + + /* + * Turn off the Processor by turning off the software clock enable flag in + * the clock control register. + */ + tmp = cs461x_peekBA0(card, BA0_CLKCR1) & ~CLKCR1_SWCE; + cs461x_pokeBA0(card, BA0_CLKCR1, tmp); + + card->active_ctrl(card,-1); + + card->pm.flags &= ~CS46XX_PM_SUSPENDING; + card->pm.flags |= CS46XX_PM_SUSPENDED; + + printpm(card); + + CS_DBGOUT(CS_PM | CS_FUNCTION, 4, + printk("cs46xx: cs46xx_suspend()- flags=0x%x\n", + (unsigned)card->pm.flags)); + return 0; +} + +static int cs46xx_resume(struct cs_card *card) +{ + int i; + + CS_DBGOUT(CS_PM | CS_FUNCTION, 4, + printk( "cs46xx: cs46xx_resume()+ flags=0x%x\n", + (unsigned)card->pm.flags)); + if(!(card->pm.flags & CS46XX_PM_SUSPENDED)) + { + CS_DBGOUT(CS_PM | CS_ERROR, 2, + printk("cs46xx: cs46xx_resume() unable to resume, not SUSPENDED\n")); + return 1; + } + card->pm.flags |= CS46XX_PM_RESUMING; + card->pm.flags &= ~CS46XX_PM_SUSPENDED; + printpm(card); + card->active_ctrl(card, 1); + + for(i=0;i<5;i++) + { + if (cs_hardware_init(card) != 0) + { + CS_DBGOUT(CS_PM | CS_ERROR, 4, printk( + "cs46xx: cs46xx_resume()- ERROR in cs_hardware_init()\n")); + mdelay(10 * cs_laptop_wait); + cs461x_reset(card); + continue; + } + break; + } + if(i>=4) + { + CS_DBGOUT(CS_PM | CS_ERROR, 1, printk( + "cs46xx: cs46xx_resume()- cs_hardware_init() failed, retried %d times.\n",i)); + return 0; + } + + if(cs46xx_restart_part(card)) + { + CS_DBGOUT(CS_PM | CS_ERROR, 4, printk( + "cs46xx: cs46xx_resume(): cs46xx_restart_part() returned error\n")); + } + + card->active_ctrl(card, -1); + + CS_DBGOUT(CS_PM | CS_FUNCTION, 4, printk("cs46xx: cs46xx_resume()- flags=0x%x\n", + (unsigned)card->pm.flags)); + return 0; +} + +static /*const*/ struct file_operations cs461x_fops = { + CS_OWNER CS_THIS_MODULE + .llseek = no_llseek, + .read = cs_read, + .write = cs_write, + .poll = cs_poll, + .ioctl = cs_ioctl, + .mmap = cs_mmap, + .open = cs_open, + .release = cs_release, +}; + +/* Write AC97 codec registers */ + + +static u16 _cs_ac97_get(struct ac97_codec *dev, u8 reg) +{ + struct cs_card *card = dev->private_data; + int count,loopcnt; + unsigned int tmp; + u16 ret; + + /* + * 1. Write ACCAD = Command Address Register = 46Ch for AC97 register address + * 2. Write ACCDA = Command Data Register = 470h for data to write to AC97 + * 3. Write ACCTL = Control Register = 460h for initiating the write + * 4. Read ACCTL = 460h, DCV should be reset by now and 460h = 17h + * 5. if DCV not cleared, break and return error + * 6. Read ACSTS = Status Register = 464h, check VSTS bit + */ + + cs461x_peekBA0(card, BA0_ACSDA); + + /* + * Setup the AC97 control registers on the CS461x to send the + * appropriate command to the AC97 to perform the read. + * ACCAD = Command Address Register = 46Ch + * ACCDA = Command Data Register = 470h + * ACCTL = Control Register = 460h + * set DCV - will clear when process completed + * set CRW - Read command + * set VFRM - valid frame enabled + * set ESYN - ASYNC generation enabled + * set RSTN - ARST# inactive, AC97 codec not reset + */ + + cs461x_pokeBA0(card, BA0_ACCAD, reg); + cs461x_pokeBA0(card, BA0_ACCDA, 0); + cs461x_pokeBA0(card, BA0_ACCTL, ACCTL_DCV | ACCTL_CRW | + ACCTL_VFRM | ACCTL_ESYN | + ACCTL_RSTN); + + + /* + * Wait for the read to occur. + */ + if(!(card->pm.flags & CS46XX_PM_IDLE)) + loopcnt = 2000; + else + loopcnt = 500 * cs_laptop_wait; + loopcnt *= cs_laptop_wait; + for (count = 0; count < loopcnt; count++) { + /* + * First, we want to wait for a short time. + */ + udelay(10 * cs_laptop_wait); + /* + * Now, check to see if the read has completed. + * ACCTL = 460h, DCV should be reset by now and 460h = 17h + */ + if (!(cs461x_peekBA0(card, BA0_ACCTL) & ACCTL_DCV)) + break; + } + + /* + * Make sure the read completed. + */ + if (cs461x_peekBA0(card, BA0_ACCTL) & ACCTL_DCV) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_WARNING + "cs46xx: AC'97 read problem (ACCTL_DCV), reg = 0x%x returning 0xffff\n", reg)); + return 0xffff; + } + + /* + * Wait for the valid status bit to go active. + */ + + if(!(card->pm.flags & CS46XX_PM_IDLE)) + loopcnt = 2000; + else + loopcnt = 1000; + loopcnt *= cs_laptop_wait; + for (count = 0; count < loopcnt; count++) { + /* + * Read the AC97 status register. + * ACSTS = Status Register = 464h + * VSTS - Valid Status + */ + if (cs461x_peekBA0(card, BA0_ACSTS) & ACSTS_VSTS) + break; + udelay(10 * cs_laptop_wait); + } + + /* + * Make sure we got valid status. + */ + if (!( (tmp=cs461x_peekBA0(card, BA0_ACSTS)) & ACSTS_VSTS)) { + CS_DBGOUT(CS_ERROR, 2, printk(KERN_WARNING + "cs46xx: AC'97 read problem (ACSTS_VSTS), reg = 0x%x val=0x%x 0xffff \n", + reg, tmp)); + return 0xffff; + } + + /* + * Read the data returned from the AC97 register. + * ACSDA = Status Data Register = 474h + */ + CS_DBGOUT(CS_FUNCTION, 9, printk(KERN_INFO + "cs46xx: cs_ac97_get() reg = 0x%x, val = 0x%x, BA0_ACCAD = 0x%x\n", + reg, cs461x_peekBA0(card, BA0_ACSDA), + cs461x_peekBA0(card, BA0_ACCAD))); + ret = cs461x_peekBA0(card, BA0_ACSDA); + return ret; +} + +static u16 cs_ac97_get(struct ac97_codec *dev, u8 reg) +{ + u16 ret; + struct cs_card *card = dev->private_data; + + spin_lock(&card->ac97_lock); + ret = _cs_ac97_get(dev, reg); + spin_unlock(&card->ac97_lock); + return ret; +} + +static void cs_ac97_set(struct ac97_codec *dev, u8 reg, u16 val) +{ + struct cs_card *card = dev->private_data; + int count; + int val2 = 0; + + spin_lock(&card->ac97_lock); + + if(reg == AC97_CD_VOL) + { + val2 = _cs_ac97_get(dev, AC97_CD_VOL); + } + + + /* + * 1. Write ACCAD = Command Address Register = 46Ch for AC97 register address + * 2. Write ACCDA = Command Data Register = 470h for data to write to AC97 + * 3. Write ACCTL = Control Register = 460h for initiating the write + * 4. Read ACCTL = 460h, DCV should be reset by now and 460h = 07h + * 5. if DCV not cleared, break and return error + */ + + /* + * Setup the AC97 control registers on the CS461x to send the + * appropriate command to the AC97 to perform the read. + * ACCAD = Command Address Register = 46Ch + * ACCDA = Command Data Register = 470h + * ACCTL = Control Register = 460h + * set DCV - will clear when process completed + * reset CRW - Write command + * set VFRM - valid frame enabled + * set ESYN - ASYNC generation enabled + * set RSTN - ARST# inactive, AC97 codec not reset + */ + cs461x_pokeBA0(card, BA0_ACCAD, reg); + cs461x_pokeBA0(card, BA0_ACCDA, val); + cs461x_peekBA0(card, BA0_ACCTL); + cs461x_pokeBA0(card, BA0_ACCTL, 0 | ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); + cs461x_pokeBA0(card, BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM | + ACCTL_ESYN | ACCTL_RSTN); + for (count = 0; count < 1000; count++) { + /* + * First, we want to wait for a short time. + */ + udelay(10 * cs_laptop_wait); + /* + * Now, check to see if the write has completed. + * ACCTL = 460h, DCV should be reset by now and 460h = 07h + */ + if (!(cs461x_peekBA0(card, BA0_ACCTL) & ACCTL_DCV)) + break; + } + /* + * Make sure the write completed. + */ + if (cs461x_peekBA0(card, BA0_ACCTL) & ACCTL_DCV) + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_WARNING + "cs46xx: AC'97 write problem, reg = 0x%x, val = 0x%x\n", reg, val)); + } + + spin_unlock(&card->ac97_lock); + + /* + * Adjust power if the mixer is selected/deselected according + * to the CD. + * + * IF the CD is a valid input source (mixer or direct) AND + * the CD is not muted THEN power is needed + * + * We do two things. When record select changes the input to + * add/remove the CD we adjust the power count if the CD is + * unmuted. + * + * When the CD mute changes we adjust the power level if the + * CD was a valid input. + * + * We also check for CD volume != 0, as the CD mute isn't + * normally tweaked from userspace. + */ + + /* CD mute change ? */ + + if(reg==AC97_CD_VOL) + { + /* Mute bit change ? */ + if((val2^val)&0x8000 || ((val2 == 0x1f1f || val == 0x1f1f) && val2 != val)) + { + /* This is a hack but its cleaner than the alternatives. + Right now card->ac97_codec[0] might be NULL as we are + still doing codec setup. This does an early assignment + to avoid the problem if it occurs */ + + if(card->ac97_codec[0]==NULL) + card->ac97_codec[0]=dev; + + /* Mute on */ + if(val&0x8000 || val == 0x1f1f) + card->amplifier_ctrl(card, -1); + else /* Mute off power on */ + { + if(card->amp_init) + card->amp_init(card); + card->amplifier_ctrl(card, 1); + } + } + } +} + + +/* OSS /dev/mixer file operation methods */ + +static int cs_open_mixdev(struct inode *inode, struct file *file) +{ + int i=0; + unsigned int minor = iminor(inode); + struct cs_card *card=NULL; + struct list_head *entry; + unsigned int tmp; + + CS_DBGOUT(CS_FUNCTION | CS_OPEN, 4, + printk(KERN_INFO "cs46xx: cs_open_mixdev()+\n")); + + list_for_each(entry, &cs46xx_devs) + { + card = list_entry(entry, struct cs_card, list); + for (i = 0; i < NR_AC97; i++) + if (card->ac97_codec[i] != NULL && + card->ac97_codec[i]->dev_mixer == minor) + goto match; + } + if (!card) + { + CS_DBGOUT(CS_FUNCTION | CS_OPEN | CS_ERROR, 2, + printk(KERN_INFO "cs46xx: cs46xx_open_mixdev()- -ENODEV\n")); + return -ENODEV; + } + match: + if(!card->ac97_codec[i]) + return -ENODEV; + file->private_data = card->ac97_codec[i]; + + card->active_ctrl(card,1); + if(!CS_IN_USE(&card->mixer_use_cnt)) + { + if( (tmp = cs46xx_powerup(card, CS_POWER_MIXVON )) ) + { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_INFO + "cs46xx: cs_open_mixdev() powerup failure (0x%x)\n",tmp) ); + return -EIO; + } + } + card->amplifier_ctrl(card, 1); + CS_INC_USE_COUNT(&card->mixer_use_cnt); + CS_DBGOUT(CS_FUNCTION | CS_OPEN, 4, + printk(KERN_INFO "cs46xx: cs_open_mixdev()- 0\n")); + return nonseekable_open(inode, file); +} + +static int cs_release_mixdev(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct cs_card *card=NULL; + struct list_head *entry; + int i; + unsigned int tmp; + + CS_DBGOUT(CS_FUNCTION | CS_RELEASE, 4, + printk(KERN_INFO "cs46xx: cs_release_mixdev()+\n")); + list_for_each(entry, &cs46xx_devs) + { + card = list_entry(entry, struct cs_card, list); + for (i = 0; i < NR_AC97; i++) + if (card->ac97_codec[i] != NULL && + card->ac97_codec[i]->dev_mixer == minor) + goto match; + } + if (!card) + { + CS_DBGOUT(CS_FUNCTION | CS_OPEN | CS_ERROR, 2, + printk(KERN_INFO "cs46xx: cs46xx_open_mixdev()- -ENODEV\n")); + return -ENODEV; + } +match: + if(!CS_DEC_AND_TEST(&card->mixer_use_cnt)) + { + CS_DBGOUT(CS_FUNCTION | CS_RELEASE, 4, + printk(KERN_INFO "cs46xx: cs_release_mixdev()- no powerdown, usecnt>0\n")); + card->active_ctrl(card, -1); + card->amplifier_ctrl(card, -1); + return 0; + } +/* +* ok, no outstanding mixer opens, so powerdown. +*/ + if( (tmp = cs461x_powerdown(card, CS_POWER_MIXVON, CS_FALSE )) ) + { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_INFO + "cs46xx: cs_release_mixdev() powerdown MIXVON failure (0x%x)\n",tmp) ); + card->active_ctrl(card, -1); + card->amplifier_ctrl(card, -1); + return -EIO; + } + card->active_ctrl(card, -1); + card->amplifier_ctrl(card, -1); + CS_DBGOUT(CS_FUNCTION | CS_RELEASE, 4, + printk(KERN_INFO "cs46xx: cs_release_mixdev()- 0\n")); + return 0; +} + +static int cs_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct ac97_codec *codec = (struct ac97_codec *)file->private_data; + struct cs_card *card=NULL; + struct list_head *entry; + unsigned long __user *p = (long __user *)arg; + +#if CSDEBUG_INTERFACE + int val; + + if( (cmd == SOUND_MIXER_CS_GETDBGMASK) || + (cmd == SOUND_MIXER_CS_SETDBGMASK) || + (cmd == SOUND_MIXER_CS_GETDBGLEVEL) || + (cmd == SOUND_MIXER_CS_SETDBGLEVEL) || + (cmd == SOUND_MIXER_CS_APM)) + { + switch(cmd) + { + + case SOUND_MIXER_CS_GETDBGMASK: + return put_user(cs_debugmask, p); + + case SOUND_MIXER_CS_GETDBGLEVEL: + return put_user(cs_debuglevel, p); + + case SOUND_MIXER_CS_SETDBGMASK: + if (get_user(val, p)) + return -EFAULT; + cs_debugmask = val; + return 0; + + case SOUND_MIXER_CS_SETDBGLEVEL: + if (get_user(val, p)) + return -EFAULT; + cs_debuglevel = val; + return 0; + + case SOUND_MIXER_CS_APM: + if (get_user(val, p)) + return -EFAULT; + if(val == CS_IOCTL_CMD_SUSPEND) + { + list_for_each(entry, &cs46xx_devs) + { + card = list_entry(entry, struct cs_card, list); + cs46xx_suspend(card, 0); + } + + } + else if(val == CS_IOCTL_CMD_RESUME) + { + list_for_each(entry, &cs46xx_devs) + { + card = list_entry(entry, struct cs_card, list); + cs46xx_resume(card); + } + } + else + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_INFO + "cs46xx: mixer_ioctl(): invalid APM cmd (%d)\n", + val)); + } + return 0; + + default: + CS_DBGOUT(CS_ERROR, 1, printk(KERN_INFO + "cs46xx: mixer_ioctl(): ERROR unknown debug cmd\n") ); + return 0; + } + } +#endif + return codec->mixer_ioctl(codec, cmd, arg); +} + +static /*const*/ struct file_operations cs_mixer_fops = { + CS_OWNER CS_THIS_MODULE + .llseek = no_llseek, + .ioctl = cs_ioctl_mixdev, + .open = cs_open_mixdev, + .release = cs_release_mixdev, +}; + +/* AC97 codec initialisation. */ +static int __init cs_ac97_init(struct cs_card *card) +{ + int num_ac97 = 0; + int ready_2nd = 0; + struct ac97_codec *codec; + u16 eid; + + CS_DBGOUT(CS_FUNCTION | CS_INIT, 2, printk(KERN_INFO + "cs46xx: cs_ac97_init()+\n") ); + + for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) { + if ((codec = ac97_alloc_codec()) == NULL) + return -ENOMEM; + + /* initialize some basic codec information, other fields will be filled + in ac97_probe_codec */ + codec->private_data = card; + codec->id = num_ac97; + + codec->codec_read = cs_ac97_get; + codec->codec_write = cs_ac97_set; + + if (ac97_probe_codec(codec) == 0) + { + CS_DBGOUT(CS_FUNCTION | CS_INIT, 2, printk(KERN_INFO + "cs46xx: cs_ac97_init()- codec number %d not found\n", + num_ac97) ); + card->ac97_codec[num_ac97] = NULL; + break; + } + CS_DBGOUT(CS_FUNCTION | CS_INIT, 2, printk(KERN_INFO + "cs46xx: cs_ac97_init() found codec %d\n",num_ac97) ); + + eid = cs_ac97_get(codec, AC97_EXTENDED_ID); + + if(eid==0xFFFF) + { + printk(KERN_WARNING "cs46xx: codec %d not present\n",num_ac97); + ac97_release_codec(codec); + break; + } + + card->ac97_features = eid; + + if ((codec->dev_mixer = register_sound_mixer(&cs_mixer_fops, -1)) < 0) { + printk(KERN_ERR "cs46xx: couldn't register mixer!\n"); + ac97_release_codec(codec); + break; + } + card->ac97_codec[num_ac97] = codec; + + CS_DBGOUT(CS_FUNCTION | CS_INIT, 2, printk(KERN_INFO + "cs46xx: cs_ac97_init() ac97_codec[%d] set to %p\n", + (unsigned int)num_ac97, + codec)); + /* if there is no secondary codec at all, don't probe any more */ + if (!ready_2nd) + { + num_ac97 += 1; + break; + } + } + CS_DBGOUT(CS_FUNCTION | CS_INIT, 2, printk(KERN_INFO + "cs46xx: cs_ac97_init()- %d\n", (unsigned int)num_ac97)); + return num_ac97; +} + +/* + * load the static image into the DSP + */ +#include "cs461x_image.h" +static void cs461x_download_image(struct cs_card *card) +{ + unsigned i, j, temp1, temp2, offset, count; + unsigned char __iomem *pBA1 = ioremap(card->ba1_addr, 0x40000); + for( i=0; i < CLEAR__COUNT; i++) + { + offset = ClrStat[i].BA1__DestByteOffset; + count = ClrStat[i].BA1__SourceSize; + for( temp1 = offset; temp1<(offset+count); temp1+=4 ) + writel(0, pBA1+temp1); + } + + for(i=0; iac97_codec[0], AC97_POWER_CONTROL); + CS_DBGOUT(CS_FUNCTION, 8, printk(KERN_INFO + "cs46xx: cs461x_powerdown() powerdown reg=0x%x\n",tmp)); +/* +* if powering down only the VREF, and not powering down the DAC/ADC, +* then do not power down the VREF, UNLESS both the DAC and ADC are not +* currently powered down. If powering down DAC and ADC, then +* it is possible to power down the VREF (ON). +*/ + if ( ((type & CS_POWER_MIXVON) && + (!(type & CS_POWER_ADC) || (!(type & CS_POWER_DAC))) ) + && + ((tmp & CS_AC97_POWER_CONTROL_ADC_ON) || + (tmp & CS_AC97_POWER_CONTROL_DAC_ON) ) ) + { + CS_DBGOUT(CS_FUNCTION, 8, printk(KERN_INFO + "cs46xx: cs461x_powerdown()- 0 unable to powerdown. tmp=0x%x\n",tmp)); + return 0; + } +/* +* for now, always keep power to the mixer block. +* not sure why it's a problem but it seems to be if we power off. +*/ + type &= ~CS_POWER_MIXVON; + type &= ~CS_POWER_MIXVOFF; + + /* + * Power down indicated areas. + */ + if(type & CS_POWER_MIXVOFF) + { + + CS_DBGOUT(CS_FUNCTION, 4, + printk(KERN_INFO "cs46xx: cs461x_powerdown()+ MIXVOFF\n")); + /* + * Power down the MIXER (VREF ON) on the AC97 card. + */ + tmp = cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL); + if (tmp & CS_AC97_POWER_CONTROL_MIXVOFF_ON) + { + if(!muted) + { + cs_mute(card, CS_TRUE); + muted=1; + } + tmp |= CS_AC97_POWER_CONTROL_MIXVOFF; + cs_ac97_set(card->ac97_codec[0], AC97_POWER_CONTROL, tmp ); + /* + * Now, we wait until we sample a ready state. + */ + for (count = 0; count < 32; count++) { + /* + * First, lets wait a short while to let things settle out a + * bit, and to prevent retrying the read too quickly. + */ + udelay(500); + + /* + * Read the current state of the power control register. + */ + if (!(cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_MIXVOFF_ON)) + break; + } + + /* + * Check the status.. + */ + if (cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_MIXVOFF_ON) + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_WARNING + "cs46xx: powerdown MIXVOFF failed\n")); + return 1; + } + } + } + if(type & CS_POWER_MIXVON) + { + + CS_DBGOUT(CS_FUNCTION, 4, + printk(KERN_INFO "cs46xx: cs461x_powerdown()+ MIXVON\n")); + /* + * Power down the MIXER (VREF ON) on the AC97 card. + */ + tmp = cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL); + if (tmp & CS_AC97_POWER_CONTROL_MIXVON_ON) + { + if(!muted) + { + cs_mute(card, CS_TRUE); + muted=1; + } + tmp |= CS_AC97_POWER_CONTROL_MIXVON; + cs_ac97_set(card->ac97_codec[0], AC97_POWER_CONTROL, tmp ); + /* + * Now, we wait until we sample a ready state. + */ + for (count = 0; count < 32; count++) { + /* + * First, lets wait a short while to let things settle out a + * bit, and to prevent retrying the read too quickly. + */ + udelay(500); + + /* + * Read the current state of the power control register. + */ + if (!(cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_MIXVON_ON)) + break; + } + + /* + * Check the status.. + */ + if (cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_MIXVON_ON) + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_WARNING + "cs46xx: powerdown MIXVON failed\n")); + return 1; + } + } + } + if(type & CS_POWER_ADC) + { + /* + * Power down the ADC on the AC97 card. + */ + CS_DBGOUT(CS_FUNCTION, 4, printk(KERN_INFO "cs46xx: cs461x_powerdown()+ ADC\n")); + tmp = cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL); + if (tmp & CS_AC97_POWER_CONTROL_ADC_ON) + { + if(!muted) + { + cs_mute(card, CS_TRUE); + muted=1; + } + tmp |= CS_AC97_POWER_CONTROL_ADC; + cs_ac97_set(card->ac97_codec[0], AC97_POWER_CONTROL, tmp ); + + /* + * Now, we wait until we sample a ready state. + */ + for (count = 0; count < 32; count++) { + /* + * First, lets wait a short while to let things settle out a + * bit, and to prevent retrying the read too quickly. + */ + udelay(500); + + /* + * Read the current state of the power control register. + */ + if (!(cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_ADC_ON)) + break; + } + + /* + * Check the status.. + */ + if (cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_ADC_ON) + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_WARNING + "cs46xx: powerdown ADC failed\n")); + return 1; + } + } + } + if(type & CS_POWER_DAC) + { + /* + * Power down the DAC on the AC97 card. + */ + + CS_DBGOUT(CS_FUNCTION, 4, + printk(KERN_INFO "cs46xx: cs461x_powerdown()+ DAC\n")); + tmp = cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL); + if (tmp & CS_AC97_POWER_CONTROL_DAC_ON) + { + if(!muted) + { + cs_mute(card, CS_TRUE); + muted=1; + } + tmp |= CS_AC97_POWER_CONTROL_DAC; + cs_ac97_set(card->ac97_codec[0], AC97_POWER_CONTROL, tmp ); + /* + * Now, we wait until we sample a ready state. + */ + for (count = 0; count < 32; count++) { + /* + * First, lets wait a short while to let things settle out a + * bit, and to prevent retrying the read too quickly. + */ + udelay(500); + + /* + * Read the current state of the power control register. + */ + if (!(cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_DAC_ON)) + break; + } + + /* + * Check the status.. + */ + if (cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_DAC_ON) + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_WARNING + "cs46xx: powerdown DAC failed\n")); + return 1; + } + } + } + tmp = cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL); + if(muted) + cs_mute(card, CS_FALSE); + CS_DBGOUT(CS_FUNCTION, 4, printk(KERN_INFO + "cs46xx: cs461x_powerdown()- 0 tmp=0x%x\n",tmp)); + return 0; +} + +static int cs46xx_powerup(struct cs_card *card, unsigned int type) +{ + int count; + unsigned int tmp=0,muted=0; + + CS_DBGOUT(CS_FUNCTION, 8, printk(KERN_INFO + "cs46xx: cs46xx_powerup()+ type=0x%x\n",type)); + /* + * check for VREF and powerup if need to. + */ + if(type & CS_POWER_MIXVON) + type |= CS_POWER_MIXVOFF; + if(type & (CS_POWER_DAC | CS_POWER_ADC)) + type |= CS_POWER_MIXVON | CS_POWER_MIXVOFF; + + /* + * Power up indicated areas. + */ + if(type & CS_POWER_MIXVOFF) + { + + CS_DBGOUT(CS_FUNCTION, 4, + printk(KERN_INFO "cs46xx: cs46xx_powerup()+ MIXVOFF\n")); + /* + * Power up the MIXER (VREF ON) on the AC97 card. + */ + tmp = cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL); + if (!(tmp & CS_AC97_POWER_CONTROL_MIXVOFF_ON)) + { + if(!muted) + { + cs_mute(card, CS_TRUE); + muted=1; + } + tmp &= ~CS_AC97_POWER_CONTROL_MIXVOFF; + cs_ac97_set(card->ac97_codec[0], AC97_POWER_CONTROL, tmp ); + /* + * Now, we wait until we sample a ready state. + */ + for (count = 0; count < 32; count++) { + /* + * First, lets wait a short while to let things settle out a + * bit, and to prevent retrying the read too quickly. + */ + udelay(500); + + /* + * Read the current state of the power control register. + */ + if (cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_MIXVOFF_ON) + break; + } + + /* + * Check the status.. + */ + if (!(cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_MIXVOFF_ON)) + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_WARNING + "cs46xx: powerup MIXVOFF failed\n")); + return 1; + } + } + } + if(type & CS_POWER_MIXVON) + { + + CS_DBGOUT(CS_FUNCTION, 4, + printk(KERN_INFO "cs46xx: cs46xx_powerup()+ MIXVON\n")); + /* + * Power up the MIXER (VREF ON) on the AC97 card. + */ + tmp = cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL); + if (!(tmp & CS_AC97_POWER_CONTROL_MIXVON_ON)) + { + if(!muted) + { + cs_mute(card, CS_TRUE); + muted=1; + } + tmp &= ~CS_AC97_POWER_CONTROL_MIXVON; + cs_ac97_set(card->ac97_codec[0], AC97_POWER_CONTROL, tmp ); + /* + * Now, we wait until we sample a ready state. + */ + for (count = 0; count < 32; count++) { + /* + * First, lets wait a short while to let things settle out a + * bit, and to prevent retrying the read too quickly. + */ + udelay(500); + + /* + * Read the current state of the power control register. + */ + if (cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_MIXVON_ON) + break; + } + + /* + * Check the status.. + */ + if (!(cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_MIXVON_ON)) + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_WARNING + "cs46xx: powerup MIXVON failed\n")); + return 1; + } + } + } + if(type & CS_POWER_ADC) + { + /* + * Power up the ADC on the AC97 card. + */ + CS_DBGOUT(CS_FUNCTION, 4, printk(KERN_INFO "cs46xx: cs46xx_powerup()+ ADC\n")); + tmp = cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL); + if (!(tmp & CS_AC97_POWER_CONTROL_ADC_ON)) + { + if(!muted) + { + cs_mute(card, CS_TRUE); + muted=1; + } + tmp &= ~CS_AC97_POWER_CONTROL_ADC; + cs_ac97_set(card->ac97_codec[0], AC97_POWER_CONTROL, tmp ); + + /* + * Now, we wait until we sample a ready state. + */ + for (count = 0; count < 32; count++) { + /* + * First, lets wait a short while to let things settle out a + * bit, and to prevent retrying the read too quickly. + */ + udelay(500); + + /* + * Read the current state of the power control register. + */ + if (cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_ADC_ON) + break; + } + + /* + * Check the status.. + */ + if (!(cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_ADC_ON)) + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_WARNING + "cs46xx: powerup ADC failed\n")); + return 1; + } + } + } + if(type & CS_POWER_DAC) + { + /* + * Power up the DAC on the AC97 card. + */ + + CS_DBGOUT(CS_FUNCTION, 4, + printk(KERN_INFO "cs46xx: cs46xx_powerup()+ DAC\n")); + tmp = cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL); + if (!(tmp & CS_AC97_POWER_CONTROL_DAC_ON)) + { + if(!muted) + { + cs_mute(card, CS_TRUE); + muted=1; + } + tmp &= ~CS_AC97_POWER_CONTROL_DAC; + cs_ac97_set(card->ac97_codec[0], AC97_POWER_CONTROL, tmp ); + /* + * Now, we wait until we sample a ready state. + */ + for (count = 0; count < 32; count++) { + /* + * First, lets wait a short while to let things settle out a + * bit, and to prevent retrying the read too quickly. + */ + udelay(500); + + /* + * Read the current state of the power control register. + */ + if (cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_DAC_ON) + break; + } + + /* + * Check the status.. + */ + if (!(cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL) & + CS_AC97_POWER_CONTROL_DAC_ON)) + { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_WARNING + "cs46xx: powerup DAC failed\n")); + return 1; + } + } + } + tmp = cs_ac97_get(card->ac97_codec[0], AC97_POWER_CONTROL); + if(muted) + cs_mute(card, CS_FALSE); + CS_DBGOUT(CS_FUNCTION, 4, printk(KERN_INFO + "cs46xx: cs46xx_powerup()- 0 tmp=0x%x\n",tmp)); + return 0; +} + + +static void cs461x_proc_start(struct cs_card *card) +{ + int cnt; + + /* + * Set the frame timer to reflect the number of cycles per frame. + */ + cs461x_poke(card, BA1_FRMT, 0xadf); + /* + * Turn on the run, run at frame, and DMA enable bits in the local copy of + * the SP control register. + */ + cs461x_poke(card, BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN); + /* + * Wait until the run at frame bit resets itself in the SP control + * register. + */ + for (cnt = 0; cnt < 25; cnt++) { + udelay(50); + if (!(cs461x_peek(card, BA1_SPCR) & SPCR_RUNFR)) + break; + } + + if (cs461x_peek(card, BA1_SPCR) & SPCR_RUNFR) + printk(KERN_WARNING "cs46xx: SPCR_RUNFR never reset\n"); +} + +static void cs461x_proc_stop(struct cs_card *card) +{ + /* + * Turn off the run, run at frame, and DMA enable bits in the local copy of + * the SP control register. + */ + cs461x_poke(card, BA1_SPCR, 0); +} + +static int cs_hardware_init(struct cs_card *card) +{ + unsigned long end_time; + unsigned int tmp,count; + + CS_DBGOUT(CS_FUNCTION | CS_INIT, 2, printk(KERN_INFO + "cs46xx: cs_hardware_init()+\n") ); + /* + * First, blast the clock control register to zero so that the PLL starts + * out in a known state, and blast the master serial port control register + * to zero so that the serial ports also start out in a known state. + */ + cs461x_pokeBA0(card, BA0_CLKCR1, 0); + cs461x_pokeBA0(card, BA0_SERMC1, 0); + + /* + * If we are in AC97 mode, then we must set the part to a host controlled + * AC-link. Otherwise, we won't be able to bring up the link. + */ + cs461x_pokeBA0(card, BA0_SERACC, SERACC_HSP | SERACC_CODEC_TYPE_1_03); /* 1.03 card */ + /* cs461x_pokeBA0(card, BA0_SERACC, SERACC_HSP | SERACC_CODEC_TYPE_2_0); */ /* 2.00 card */ + + /* + * Drive the ARST# pin low for a minimum of 1uS (as defined in the AC97 + * spec) and then drive it high. This is done for non AC97 modes since + * there might be logic external to the CS461x that uses the ARST# line + * for a reset. + */ + cs461x_pokeBA0(card, BA0_ACCTL, 1); + udelay(50); + cs461x_pokeBA0(card, BA0_ACCTL, 0); + udelay(50); + cs461x_pokeBA0(card, BA0_ACCTL, ACCTL_RSTN); + + /* + * The first thing we do here is to enable sync generation. As soon + * as we start receiving bit clock, we'll start producing the SYNC + * signal. + */ + cs461x_pokeBA0(card, BA0_ACCTL, ACCTL_ESYN | ACCTL_RSTN); + + /* + * Now wait for a short while to allow the AC97 part to start + * generating bit clock (so we don't try to start the PLL without an + * input clock). + */ + mdelay(5 * cs_laptop_wait); /* 1 should be enough ?? (and pigs might fly) */ + + /* + * Set the serial port timing configuration, so that + * the clock control circuit gets its clock from the correct place. + */ + cs461x_pokeBA0(card, BA0_SERMC1, SERMC1_PTC_AC97); + + /* + * The part seems to not be ready for a while after a resume. + * so, if we are resuming, then wait for 700 mils. Note that 600 mils + * is not enough for some platforms! tested on an IBM Thinkpads and + * reference cards. + */ + if(!(card->pm.flags & CS46XX_PM_IDLE)) + mdelay(initdelay); + /* + * Write the selected clock control setup to the hardware. Do not turn on + * SWCE yet (if requested), so that the devices clocked by the output of + * PLL are not clocked until the PLL is stable. + */ + cs461x_pokeBA0(card, BA0_PLLCC, PLLCC_LPF_1050_2780_KHZ | PLLCC_CDR_73_104_MHZ); + cs461x_pokeBA0(card, BA0_PLLM, 0x3a); + cs461x_pokeBA0(card, BA0_CLKCR2, CLKCR2_PDIVS_8); + + /* + * Power up the PLL. + */ + cs461x_pokeBA0(card, BA0_CLKCR1, CLKCR1_PLLP); + + /* + * Wait until the PLL has stabilized. + */ + mdelay(5 * cs_laptop_wait); /* Again 1 should be enough ?? */ + + /* + * Turn on clocking of the core so that we can setup the serial ports. + */ + tmp = cs461x_peekBA0(card, BA0_CLKCR1) | CLKCR1_SWCE; + cs461x_pokeBA0(card, BA0_CLKCR1, tmp); + + /* + * Fill the serial port FIFOs with silence. + */ + cs461x_clear_serial_FIFOs(card,CS_TYPE_DAC | CS_TYPE_ADC); + + /* + * Set the serial port FIFO pointer to the first sample in the FIFO. + */ + /* cs461x_pokeBA0(card, BA0_SERBSP, 0); */ + + /* + * Write the serial port configuration to the part. The master + * enable bit is not set until all other values have been written. + */ + cs461x_pokeBA0(card, BA0_SERC1, SERC1_SO1F_AC97 | SERC1_SO1EN); + cs461x_pokeBA0(card, BA0_SERC2, SERC2_SI1F_AC97 | SERC1_SO1EN); + cs461x_pokeBA0(card, BA0_SERMC1, SERMC1_PTC_AC97 | SERMC1_MSPE); + + + mdelay(5 * cs_laptop_wait); /* Shouldnt be needed ?? */ + +/* +* If we are resuming under 2.2.x then we can not schedule a timeout. +* so, just spin the CPU. +*/ + if(card->pm.flags & CS46XX_PM_IDLE) + { + /* + * Wait for the card ready signal from the AC97 card. + */ + end_time = jiffies + 3 * (HZ >> 2); + do { + /* + * Read the AC97 status register to see if we've seen a CODEC READY + * signal from the AC97 card. + */ + if (cs461x_peekBA0(card, BA0_ACSTS) & ACSTS_CRDY) + break; + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(1); + } while (time_before(jiffies, end_time)); + } + else + { + for (count = 0; count < 100; count++) { + // First, we want to wait for a short time. + udelay(25 * cs_laptop_wait); + + if (cs461x_peekBA0(card, BA0_ACSTS) & ACSTS_CRDY) + break; + } + } + + /* + * Make sure CODEC is READY. + */ + if (!(cs461x_peekBA0(card, BA0_ACSTS) & ACSTS_CRDY)) { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_WARNING + "cs46xx: create - never read card ready from AC'97\n")); + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_WARNING + "cs46xx: probably not a bug, try using the CS4232 driver,\n")); + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_WARNING + "cs46xx: or turn off any automatic Power Management support in the BIOS.\n")); + return -EIO; + } + + /* + * Assert the vaid frame signal so that we can start sending commands + * to the AC97 card. + */ + cs461x_pokeBA0(card, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); + + if(card->pm.flags & CS46XX_PM_IDLE) + { + /* + * Wait until we've sampled input slots 3 and 4 as valid, meaning that + * the card is pumping ADC data across the AC-link. + */ + end_time = jiffies + 3 * (HZ >> 2); + do { + /* + * Read the input slot valid register and see if input slots 3 and + * 4 are valid yet. + */ + if ((cs461x_peekBA0(card, BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) == (ACISV_ISV3 | ACISV_ISV4)) + break; + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(1); + } while (time_before(jiffies, end_time)); + } + else + { + for (count = 0; count < 100; count++) { + // First, we want to wait for a short time. + udelay(25 * cs_laptop_wait); + + if ((cs461x_peekBA0(card, BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) == (ACISV_ISV3 | ACISV_ISV4)) + break; + } + } + /* + * Make sure input slots 3 and 4 are valid. If not, then return + * an error. + */ + if ((cs461x_peekBA0(card, BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) != (ACISV_ISV3 | ACISV_ISV4)) { + printk(KERN_WARNING "cs46xx: create - never read ISV3 & ISV4 from AC'97\n"); + return -EIO; + } + + /* + * Now, assert valid frame and the slot 3 and 4 valid bits. This will + * commense the transfer of digital audio data to the AC97 card. + */ + cs461x_pokeBA0(card, BA0_ACOSV, ACOSV_SLV3 | ACOSV_SLV4); + + /* + * Turn off the Processor by turning off the software clock enable flag in + * the clock control register. + */ + /* tmp = cs461x_peekBA0(card, BA0_CLKCR1) & ~CLKCR1_SWCE; */ + /* cs461x_pokeBA0(card, BA0_CLKCR1, tmp); */ + + /* + * Reset the processor. + */ + cs461x_reset(card); + + /* + * Download the image to the processor. + */ + + cs461x_download_image(card); + + /* + * Stop playback DMA. + */ + tmp = cs461x_peek(card, BA1_PCTL); + card->pctl = tmp & 0xffff0000; + cs461x_poke(card, BA1_PCTL, tmp & 0x0000ffff); + + /* + * Stop capture DMA. + */ + tmp = cs461x_peek(card, BA1_CCTL); + card->cctl = tmp & 0x0000ffff; + cs461x_poke(card, BA1_CCTL, tmp & 0xffff0000); + + /* initialize AC97 codec and register /dev/mixer */ + if(card->pm.flags & CS46XX_PM_IDLE) + { + if (cs_ac97_init(card) <= 0) + { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_INFO + "cs46xx: cs_ac97_init() failure\n") ); + return -EIO; + } + } + else + { + cs46xx_ac97_resume(card); + } + + cs461x_proc_start(card); + + /* + * Enable interrupts on the part. + */ + cs461x_pokeBA0(card, BA0_HICR, HICR_IEV | HICR_CHGM); + + tmp = cs461x_peek(card, BA1_PFIE); + tmp &= ~0x0000f03f; + cs461x_poke(card, BA1_PFIE, tmp); /* playback interrupt enable */ + + tmp = cs461x_peek(card, BA1_CIE); + tmp &= ~0x0000003f; + tmp |= 0x00000001; + cs461x_poke(card, BA1_CIE, tmp); /* capture interrupt enable */ + + /* + * If IDLE then Power down the part. We will power components up + * when we need them. + */ + if(card->pm.flags & CS46XX_PM_IDLE) + { + if(!cs_powerdown) + { + if( (tmp = cs46xx_powerup(card, CS_POWER_DAC | CS_POWER_ADC | + CS_POWER_MIXVON )) ) + { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_INFO + "cs46xx: cs461x_powerup() failure (0x%x)\n",tmp) ); + return -EIO; + } + } + else + { + if( (tmp = cs461x_powerdown(card, CS_POWER_DAC | CS_POWER_ADC | + CS_POWER_MIXVON, CS_FALSE )) ) + { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_INFO + "cs46xx: cs461x_powerdown() failure (0x%x)\n",tmp) ); + return -EIO; + } + } + } + CS_DBGOUT(CS_FUNCTION | CS_INIT, 2, printk(KERN_INFO + "cs46xx: cs_hardware_init()- 0\n")); + return 0; +} + +/* install the driver, we do not allocate hardware channel nor DMA buffer now, they are defered + until "ACCESS" time (in prog_dmabuf called by open/read/write/ioctl/mmap) */ + +/* + * Card subid table + */ + +struct cs_card_type +{ + u16 vendor; + u16 id; + char *name; + void (*amp)(struct cs_card *, int); + void (*amp_init)(struct cs_card *); + void (*active)(struct cs_card *, int); +}; + +static struct cs_card_type cards[] = { + { + .vendor = 0x1489, + .id = 0x7001, + .name = "Genius Soundmaker 128 value", + .amp = amp_none, + }, + { + .vendor = 0x5053, + .id = 0x3357, + .name = "Voyetra", + .amp = amp_voyetra, + }, + { + .vendor = 0x1071, + .id = 0x6003, + .name = "Mitac MI6020/21", + .amp = amp_voyetra, + }, + { + .vendor = 0x14AF, + .id = 0x0050, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + }, + { + .vendor = 0x1681, + .id = 0x0050, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + }, + { + .vendor = 0x1681, + .id = 0x0051, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + }, + { + .vendor = 0x1681, + .id = 0x0052, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + }, + { + .vendor = 0x1681, + .id = 0x0053, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + }, + { + .vendor = 0x1681, + .id = 0x0054, + .name = "Hercules Game Theatre XP", + .amp = amp_hercules, + }, + { + .vendor = 0x1681, + .id = 0xa010, + .name = "Hercules Fortissimo II", + .amp = amp_none, + }, + /* Not sure if the 570 needs the clkrun hack */ + { + .vendor = PCI_VENDOR_ID_IBM, + .id = 0x0132, + .name = "Thinkpad 570", + .amp = amp_none, + .active = clkrun_hack, + }, + { + .vendor = PCI_VENDOR_ID_IBM, + .id = 0x0153, + .name = "Thinkpad 600X/A20/T20", + .amp = amp_none, + .active = clkrun_hack, + }, + { + .vendor = PCI_VENDOR_ID_IBM, + .id = 0x1010, + .name = "Thinkpad 600E (unsupported)", + }, + { + .name = "Card without SSID set", + }, + { 0, }, +}; + +MODULE_AUTHOR("Alan Cox , Jaroslav Kysela, "); +MODULE_DESCRIPTION("Crystal SoundFusion Audio Support"); +MODULE_LICENSE("GPL"); + + +static const char cs46xx_banner[] = KERN_INFO "Crystal 4280/46xx + AC97 Audio, version " CS46XX_MAJOR_VERSION "." CS46XX_MINOR_VERSION "." CS46XX_ARCH ", " __TIME__ " " __DATE__ "\n"; +static const char fndmsg[] = KERN_INFO "cs46xx: Found %d audio device(s).\n"; + +static int __devinit cs46xx_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pciid) +{ + struct pm_dev *pmdev; + int i,j; + u16 ss_card, ss_vendor; + struct cs_card *card; + dma_addr_t dma_mask; + struct cs_card_type *cp = &cards[0]; + + CS_DBGOUT(CS_FUNCTION | CS_INIT, 2, + printk(KERN_INFO "cs46xx: probe()+\n")); + + dma_mask = 0xffffffff; /* this enables playback and recording */ + if (pci_enable_device(pci_dev)) { + CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_ERR + "cs46xx: pci_enable_device() failed\n")); + return -1; + } + if (!RSRCISMEMORYREGION(pci_dev, 0) || + !RSRCISMEMORYREGION(pci_dev, 1)) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs46xx: probe()- Memory region not assigned\n")); + return -1; + } + if (pci_dev->irq == 0) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs46xx: probe() IRQ not assigned\n")); + return -1; + } + if (!pci_dma_supported(pci_dev, 0xffffffff)) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs46xx: probe() architecture does not support 32bit PCI busmaster DMA\n")); + return -1; + } + pci_read_config_word(pci_dev, PCI_SUBSYSTEM_VENDOR_ID, &ss_vendor); + pci_read_config_word(pci_dev, PCI_SUBSYSTEM_ID, &ss_card); + + if ((card = kmalloc(sizeof(struct cs_card), GFP_KERNEL)) == NULL) { + printk(KERN_ERR "cs46xx: out of memory\n"); + return -ENOMEM; + } + memset(card, 0, sizeof(*card)); + card->ba0_addr = RSRCADDRESS(pci_dev, 0); + card->ba1_addr = RSRCADDRESS(pci_dev, 1); + card->pci_dev = pci_dev; + card->irq = pci_dev->irq; + card->magic = CS_CARD_MAGIC; + spin_lock_init(&card->lock); + spin_lock_init(&card->ac97_lock); + + pci_set_master(pci_dev); + + printk(cs46xx_banner); + printk(KERN_INFO "cs46xx: Card found at 0x%08lx and 0x%08lx, IRQ %d\n", + card->ba0_addr, card->ba1_addr, card->irq); + + card->alloc_pcm_channel = cs_alloc_pcm_channel; + card->alloc_rec_pcm_channel = cs_alloc_rec_pcm_channel; + card->free_pcm_channel = cs_free_pcm_channel; + card->amplifier_ctrl = amp_none; + card->active_ctrl = amp_none; + + while (cp->name) + { + if(cp->vendor == ss_vendor && cp->id == ss_card) + { + card->amplifier_ctrl = cp->amp; + if(cp->active) + card->active_ctrl = cp->active; + if(cp->amp_init) + card->amp_init = cp->amp_init; + break; + } + cp++; + } + if (cp->name==NULL) + { + printk(KERN_INFO "cs46xx: Unknown card (%04X:%04X) at 0x%08lx/0x%08lx, IRQ %d\n", + ss_vendor, ss_card, card->ba0_addr, card->ba1_addr, card->irq); + } + else + { + printk(KERN_INFO "cs46xx: %s (%04X:%04X) at 0x%08lx/0x%08lx, IRQ %d\n", + cp->name, ss_vendor, ss_card, card->ba0_addr, card->ba1_addr, card->irq); + } + + if (card->amplifier_ctrl==NULL) + { + card->amplifier_ctrl = amp_none; + card->active_ctrl = clkrun_hack; + } + + if (external_amp == 1) + { + printk(KERN_INFO "cs46xx: Crystal EAPD support forced on.\n"); + card->amplifier_ctrl = amp_voyetra; + } + + if (thinkpad == 1) + { + printk(KERN_INFO "cs46xx: Activating CLKRUN hack for Thinkpad.\n"); + card->active_ctrl = clkrun_hack; + } +/* +* The thinkpads don't work well without runtime updating on their kernel +* delay values (or any laptop with variable CPU speeds really). +* so, just to be safe set the init delay to 2100. Eliminates +* failures on T21 Thinkpads. remove this code when the udelay +* and mdelay kernel code is replaced by a pm timer, or the delays +* work well for battery and/or AC power both. +*/ + if(card->active_ctrl == clkrun_hack) + { + initdelay = 2100; + cs_laptop_wait = 5; + } + if((card->active_ctrl == clkrun_hack) && !(powerdown == 1)) + { +/* +* for some currently unknown reason, powering down the DAC and ADC component +* blocks on thinkpads causes some funky behavior... distoorrrtion and ac97 +* codec access problems. probably the serial clock becomes unsynced. +* added code to sync the chips back up, but only helped about 70% the time. +*/ + cs_powerdown = 0; + } + if(powerdown == 0) + cs_powerdown = 0; + card->active_ctrl(card, 1); + + /* claim our iospace and irq */ + + card->ba0 = ioremap_nocache(card->ba0_addr, CS461X_BA0_SIZE); + card->ba1.name.data0 = ioremap_nocache(card->ba1_addr + BA1_SP_DMEM0, CS461X_BA1_DATA0_SIZE); + card->ba1.name.data1 = ioremap_nocache(card->ba1_addr + BA1_SP_DMEM1, CS461X_BA1_DATA1_SIZE); + card->ba1.name.pmem = ioremap_nocache(card->ba1_addr + BA1_SP_PMEM, CS461X_BA1_PRG_SIZE); + card->ba1.name.reg = ioremap_nocache(card->ba1_addr + BA1_SP_REG, CS461X_BA1_REG_SIZE); + + CS_DBGOUT(CS_INIT, 4, printk(KERN_INFO + "cs46xx: card=%p card->ba0=%p\n",card,card->ba0) ); + CS_DBGOUT(CS_INIT, 4, printk(KERN_INFO + "cs46xx: card->ba1=%p %p %p %p\n", + card->ba1.name.data0, + card->ba1.name.data1, + card->ba1.name.pmem, + card->ba1.name.reg) ); + + if(card->ba0 == 0 || card->ba1.name.data0 == 0 || + card->ba1.name.data1 == 0 || card->ba1.name.pmem == 0 || + card->ba1.name.reg == 0) + goto fail2; + + if (request_irq(card->irq, &cs_interrupt, SA_SHIRQ, "cs46xx", card)) { + printk(KERN_ERR "cs46xx: unable to allocate irq %d\n", card->irq); + goto fail2; + } + /* register /dev/dsp */ + if ((card->dev_audio = register_sound_dsp(&cs461x_fops, -1)) < 0) { + printk(KERN_ERR "cs46xx: unable to register dsp\n"); + goto fail; + } + + /* register /dev/midi */ + if((card->dev_midi = register_sound_midi(&cs_midi_fops, -1)) < 0) + printk(KERN_ERR "cs46xx: unable to register midi\n"); + + card->pm.flags |= CS46XX_PM_IDLE; + for(i=0;i<5;i++) + { + if (cs_hardware_init(card) != 0) + { + CS_DBGOUT(CS_ERROR, 4, printk( + "cs46xx: ERROR in cs_hardware_init()... retrying\n")); + for (j = 0; j < NR_AC97; j++) + if (card->ac97_codec[j] != NULL) { + unregister_sound_mixer(card->ac97_codec[j]->dev_mixer); + ac97_release_codec(card->ac97_codec[j]); + } + mdelay(10 * cs_laptop_wait); + continue; + } + break; + } + if(i>=4) + { + CS_DBGOUT(CS_PM | CS_ERROR, 1, printk( + "cs46xx: cs46xx_probe()- cs_hardware_init() failed, retried %d times.\n",i)); + unregister_sound_dsp(card->dev_audio); + if(card->dev_midi) + unregister_sound_midi(card->dev_midi); + goto fail; + } + + init_waitqueue_head(&card->midi.open_wait); + init_MUTEX(&card->midi.open_sem); + init_waitqueue_head(&card->midi.iwait); + init_waitqueue_head(&card->midi.owait); + cs461x_pokeBA0(card, BA0_MIDCR, MIDCR_MRST); + cs461x_pokeBA0(card, BA0_MIDCR, 0); + + /* + * Check if we have to init the amplifier, but probably already done + * since the CD logic in the ac97 init code will turn on the ext amp. + */ + if(cp->amp_init) + cp->amp_init(card); + card->active_ctrl(card, -1); + + PCI_SET_DRIVER_DATA(pci_dev, card); + PCI_SET_DMA_MASK(pci_dev, dma_mask); + list_add(&card->list, &cs46xx_devs); + + pmdev = cs_pm_register(PM_PCI_DEV, PM_PCI_ID(pci_dev), cs46xx_pm_callback); + if (pmdev) + { + CS_DBGOUT(CS_INIT | CS_PM, 4, printk(KERN_INFO + "cs46xx: probe() pm_register() succeeded (%p).\n", + pmdev)); + pmdev->data = card; + } + else + { + CS_DBGOUT(CS_INIT | CS_PM | CS_ERROR, 2, printk(KERN_INFO + "cs46xx: probe() pm_register() failed (%p).\n", + pmdev)); + card->pm.flags |= CS46XX_PM_NOT_REGISTERED; + } + + CS_DBGOUT(CS_PM, 9, printk(KERN_INFO "cs46xx: pm.flags=0x%x card=%p\n", + (unsigned)card->pm.flags,card)); + + CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, printk(KERN_INFO + "cs46xx: probe()- device allocated successfully\n")); + return 0; + +fail: + free_irq(card->irq, card); +fail2: + if(card->ba0) + iounmap(card->ba0); + if(card->ba1.name.data0) + iounmap(card->ba1.name.data0); + if(card->ba1.name.data1) + iounmap(card->ba1.name.data1); + if(card->ba1.name.pmem) + iounmap(card->ba1.name.pmem); + if(card->ba1.name.reg) + iounmap(card->ba1.name.reg); + kfree(card); + CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_INFO + "cs46xx: probe()- no device allocated\n")); + return -ENODEV; +} // probe_cs46xx + +// --------------------------------------------------------------------- + +static void __devexit cs46xx_remove(struct pci_dev *pci_dev) +{ + struct cs_card *card = PCI_GET_DRIVER_DATA(pci_dev); + int i; + unsigned int tmp; + + CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, printk(KERN_INFO + "cs46xx: cs46xx_remove()+\n")); + + card->active_ctrl(card,1); + + tmp = cs461x_peek(card, BA1_PFIE); + tmp &= ~0x0000f03f; + tmp |= 0x00000010; + cs461x_poke(card, BA1_PFIE, tmp); /* playback interrupt disable */ + + tmp = cs461x_peek(card, BA1_CIE); + tmp &= ~0x0000003f; + tmp |= 0x00000011; + cs461x_poke(card, BA1_CIE, tmp); /* capture interrupt disable */ + + /* + * Stop playback DMA. + */ + tmp = cs461x_peek(card, BA1_PCTL); + cs461x_poke(card, BA1_PCTL, tmp & 0x0000ffff); + + /* + * Stop capture DMA. + */ + tmp = cs461x_peek(card, BA1_CCTL); + cs461x_poke(card, BA1_CCTL, tmp & 0xffff0000); + + /* + * Reset the processor. + */ + cs461x_reset(card); + + cs461x_proc_stop(card); + + /* + * Power down the DAC and ADC. We will power them up (if) when we need + * them. + */ + if( (tmp = cs461x_powerdown(card, CS_POWER_DAC | CS_POWER_ADC | + CS_POWER_MIXVON, CS_TRUE )) ) + { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk(KERN_INFO + "cs46xx: cs461x_powerdown() failure (0x%x)\n",tmp) ); + } + + /* + * Power down the PLL. + */ + cs461x_pokeBA0(card, BA0_CLKCR1, 0); + + /* + * Turn off the Processor by turning off the software clock enable flag in + * the clock control register. + */ + tmp = cs461x_peekBA0(card, BA0_CLKCR1) & ~CLKCR1_SWCE; + cs461x_pokeBA0(card, BA0_CLKCR1, tmp); + + card->active_ctrl(card,-1); + + /* free hardware resources */ + free_irq(card->irq, card); + iounmap(card->ba0); + iounmap(card->ba1.name.data0); + iounmap(card->ba1.name.data1); + iounmap(card->ba1.name.pmem); + iounmap(card->ba1.name.reg); + + /* unregister audio devices */ + for (i = 0; i < NR_AC97; i++) + if (card->ac97_codec[i] != NULL) { + unregister_sound_mixer(card->ac97_codec[i]->dev_mixer); + ac97_release_codec(card->ac97_codec[i]); + } + unregister_sound_dsp(card->dev_audio); + if(card->dev_midi) + unregister_sound_midi(card->dev_midi); + list_del(&card->list); + kfree(card); + PCI_SET_DRIVER_DATA(pci_dev,NULL); + + CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, printk(KERN_INFO + "cs46xx: cs46xx_remove()-: remove successful\n")); +} + +enum { + CS46XX_4610 = 0, + CS46XX_4612, /* same as 4630 */ + CS46XX_4615, /* same as 4624 */ +}; + +static struct pci_device_id cs46xx_pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_CIRRUS, + .device = PCI_DEVICE_ID_CIRRUS_4610, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = CS46XX_4610, + }, + { + .vendor = PCI_VENDOR_ID_CIRRUS, + .device = PCI_DEVICE_ID_CIRRUS_4612, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = CS46XX_4612, + }, + { + .vendor = PCI_VENDOR_ID_CIRRUS, + .device = PCI_DEVICE_ID_CIRRUS_4615, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = CS46XX_4615, + }, + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, cs46xx_pci_tbl); + +static struct pci_driver cs46xx_pci_driver = { + .name = "cs46xx", + .id_table = cs46xx_pci_tbl, + .probe = cs46xx_probe, + .remove = __devexit_p(cs46xx_remove), + .suspend = CS46XX_SUSPEND_TBL, + .resume = CS46XX_RESUME_TBL, +}; + +static int __init cs46xx_init_module(void) +{ + int rtn = 0; + CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, printk(KERN_INFO + "cs46xx: cs46xx_init_module()+ \n")); + rtn = pci_module_init(&cs46xx_pci_driver); + + if(rtn == -ENODEV) + { + CS_DBGOUT(CS_ERROR | CS_INIT, 1, printk( + "cs46xx: Unable to detect valid cs46xx device\n")); + } + + CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, + printk(KERN_INFO "cs46xx: cs46xx_init_module()- (%d)\n",rtn)); + return rtn; +} + +static void __exit cs46xx_cleanup_module(void) +{ + pci_unregister_driver(&cs46xx_pci_driver); + cs_pm_unregister_all(cs46xx_pm_callback); + CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, + printk(KERN_INFO "cs46xx: cleanup_cs46xx() finished\n")); +} + +module_init(cs46xx_init_module); +module_exit(cs46xx_cleanup_module); + +#ifndef CS46XX_ACPI_SUPPORT +static int cs46xx_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data) +{ + struct cs_card *card; + + CS_DBGOUT(CS_PM, 2, printk(KERN_INFO + "cs46xx: cs46xx_pm_callback dev=%p rqst=0x%x card=%p\n", + dev,(unsigned)rqst,data)); + card = (struct cs_card *) dev->data; + if (card) { + switch(rqst) { + case PM_SUSPEND: + CS_DBGOUT(CS_PM, 2, printk(KERN_INFO + "cs46xx: PM suspend request\n")); + if(cs46xx_suspend(card, 0)) + { + CS_DBGOUT(CS_ERROR, 2, printk(KERN_INFO + "cs46xx: PM suspend request refused\n")); + return 1; + } + break; + case PM_RESUME: + CS_DBGOUT(CS_PM, 2, printk(KERN_INFO + "cs46xx: PM resume request\n")); + if(cs46xx_resume(card)) + { + CS_DBGOUT(CS_ERROR, 2, printk(KERN_INFO + "cs46xx: PM resume request refused\n")); + return 1; + } + break; + } + } + + return 0; +} +#endif + +#if CS46XX_ACPI_SUPPORT +static int cs46xx_suspend_tbl(struct pci_dev *pcidev, pm_message_t state) +{ + struct cs_card *s = PCI_GET_DRIVER_DATA(pcidev); + CS_DBGOUT(CS_PM | CS_FUNCTION, 2, + printk(KERN_INFO "cs46xx: cs46xx_suspend_tbl request\n")); + cs46xx_suspend(s, 0); + return 0; +} + +static int cs46xx_resume_tbl(struct pci_dev *pcidev) +{ + struct cs_card *s = PCI_GET_DRIVER_DATA(pcidev); + CS_DBGOUT(CS_PM | CS_FUNCTION, 2, + printk(KERN_INFO "cs46xx: cs46xx_resume_tbl request\n")); + cs46xx_resume(s); + return 0; +} +#endif diff --git a/sound/oss/cs46xx_wrapper-24.h b/sound/oss/cs46xx_wrapper-24.h new file mode 100644 index 000000000000..f68e01181a7c --- /dev/null +++ b/sound/oss/cs46xx_wrapper-24.h @@ -0,0 +1,56 @@ +/******************************************************************************* +* +* "cs46xx_wrapper.c" -- Cirrus Logic-Crystal CS46XX linux audio driver. +* +* Copyright (C) 2000,2001 Cirrus Logic Corp. +* -- tom woller (twoller@crystal.cirrus.com) or +* (pcaudio@crystal.cirrus.com). +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* 01/11/2001 trw - new file from cs4281 wrapper code. +* +*******************************************************************************/ +#ifndef __CS46XX_WRAPPER24_H +#define __CS46XX_WRAPPER24_H + +#include + +#define CS_OWNER .owner = +#define CS_THIS_MODULE THIS_MODULE, +static inline void cs46xx_null(struct pci_dev *pcidev) { return; } +#define cs4x_mem_map_reserve(page) SetPageReserved(page) +#define cs4x_mem_map_unreserve(page) ClearPageReserved(page) + +#define free_dmabuf(card, dmabuf) \ + pci_free_consistent((card)->pci_dev, \ + PAGE_SIZE << (dmabuf)->buforder, \ + (dmabuf)->rawbuf, (dmabuf)->dmaaddr); +#define free_dmabuf2(card, dmabuf) \ + pci_free_consistent((card)->pci_dev, \ + PAGE_SIZE << (dmabuf)->buforder_tmpbuff, \ + (dmabuf)->tmpbuff, (dmabuf)->dmaaddr_tmpbuff); +#define cs4x_pgoff(vma) ((vma)->vm_pgoff) + +#define RSRCISIOREGION(dev,num) ((dev)->resource[(num)].start != 0 && \ + ((dev)->resource[(num)].flags & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) +#define RSRCISMEMORYREGION(dev,num) ((dev)->resource[(num)].start != 0 && \ + ((dev)->resource[(num)].flags & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_MEMORY) +#define RSRCADDRESS(dev,num) ((dev)->resource[(num)].start) +#define PCI_GET_DRIVER_DATA pci_get_drvdata +#define PCI_SET_DRIVER_DATA pci_set_drvdata +#define PCI_SET_DMA_MASK(pcidev,mask) pcidev->dma_mask = mask + +#endif diff --git a/sound/oss/cs46xxpm-24.h b/sound/oss/cs46xxpm-24.h new file mode 100644 index 000000000000..e220bd7240f1 --- /dev/null +++ b/sound/oss/cs46xxpm-24.h @@ -0,0 +1,52 @@ +/******************************************************************************* +* +* "cs46xxpm-24.h" -- Cirrus Logic-Crystal CS46XX linux audio driver. +* +* Copyright (C) 2000,2001 Cirrus Logic Corp. +* -- tom woller (twoller@crystal.cirrus.com) or +* (pcaudio@crystal.cirrus.com). +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* 12/22/00 trw - new file. +* +*******************************************************************************/ +#ifndef __CS46XXPM24_H +#define __CS46XXPM24_H + +#include +#include "cs46xxpm.h" + + +#define CS46XX_ACPI_SUPPORT 1 +#ifdef CS46XX_ACPI_SUPPORT +/* +* for now (12/22/00) only enable the pm_register PM support. +* allow these table entries to be null. +*/ +static int cs46xx_suspend_tbl(struct pci_dev *pcidev, pm_message_t state); +static int cs46xx_resume_tbl(struct pci_dev *pcidev); +#define cs_pm_register(a, b, c) NULL +#define cs_pm_unregister_all(a) +#define CS46XX_SUSPEND_TBL cs46xx_suspend_tbl +#define CS46XX_RESUME_TBL cs46xx_resume_tbl +#else +#define cs_pm_register(a, b, c) pm_register((a), (b), (c)); +#define cs_pm_unregister_all(a) pm_unregister_all((a)); +#define CS46XX_SUSPEND_TBL cs46xx_null +#define CS46XX_RESUME_TBL cs46xx_null +#endif + +#endif diff --git a/sound/oss/cs46xxpm.h b/sound/oss/cs46xxpm.h new file mode 100644 index 000000000000..2932b6e0e0bb --- /dev/null +++ b/sound/oss/cs46xxpm.h @@ -0,0 +1,70 @@ +/******************************************************************************* +* +* "cs46xxpm.h" -- Cirrus Logic-Crystal CS46XX linux audio driver. +* +* Copyright (C) 2000,2001 Cirrus Logic Corp. +* -- tom woller (twoller@crystal.cirrus.com) or +* (pcaudio@crystal.cirrus.com). +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* 12/22/00 trw - new file. +* +*******************************************************************************/ +#ifndef __CS46XXPM_H +#define __CS46XXPM_H + +#define CS46XX_AC97_HIGHESTREGTORESTORE 0x26 +#define CS46XX_AC97_NUMBER_RESTORE_REGS (CS46XX_AC97_HIGHESTREGTORESTORE/2-1) + +/* PM state defintions */ +#define CS46XX_PM_NOT_REGISTERED 0x1000 +#define CS46XX_PM_IDLE 0x0001 +#define CS46XX_PM_SUSPENDING 0x0002 +#define CS46XX_PM_SUSPENDED 0x0004 +#define CS46XX_PM_RESUMING 0x0008 +#define CS46XX_PM_RESUMED 0x0010 + +#define CS_POWER_DAC 0x0001 +#define CS_POWER_ADC 0x0002 +#define CS_POWER_MIXVON 0x0004 +#define CS_POWER_MIXVOFF 0x0008 +#define CS_AC97_POWER_CONTROL_ON 0xf000 /* always on bits (inverted) */ +#define CS_AC97_POWER_CONTROL_ADC 0x0100 +#define CS_AC97_POWER_CONTROL_DAC 0x0200 +#define CS_AC97_POWER_CONTROL_MIXVON 0x0400 +#define CS_AC97_POWER_CONTROL_MIXVOFF 0x0800 +#define CS_AC97_POWER_CONTROL_ADC_ON 0x0001 +#define CS_AC97_POWER_CONTROL_DAC_ON 0x0002 +#define CS_AC97_POWER_CONTROL_MIXVON_ON 0x0004 +#define CS_AC97_POWER_CONTROL_MIXVOFF_ON 0x0008 + +struct cs46xx_pm { + unsigned long flags; + u32 u32CLKCR1_SAVE,u32SSPMValue,u32PPLVCvalue,u32PPRVCvalue; + u32 u32FMLVCvalue,u32FMRVCvalue,u32GPIORvalue,u32JSCTLvalue,u32SSCR; + u32 u32SRCSA,u32DacASR,u32AdcASR,u32DacSR,u32AdcSR,u32MIDCR_Save; + u32 u32SSPM_BITS; + u32 ac97[CS46XX_AC97_NUMBER_RESTORE_REGS]; + u32 u32AC97_master_volume, u32AC97_headphone_volume, u32AC97_master_volume_mono; + u32 u32AC97_pcm_out_volume, u32AC97_powerdown, u32AC97_general_purpose; + u32 u32hwptr_playback,u32hwptr_capture; + unsigned dmabuf_swptr_play; + int dmabuf_count_play; + unsigned dmabuf_swptr_capture; + int dmabuf_count_capture; +}; + +#endif diff --git a/sound/oss/dev_table.c b/sound/oss/dev_table.c new file mode 100644 index 000000000000..f65a90469d8a --- /dev/null +++ b/sound/oss/dev_table.c @@ -0,0 +1,214 @@ +/* + * sound/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 + +#define _DEV_TABLE_C_ +#include "sound_config.h" + +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))); + + if (sound_nblocks < 1024) + sound_nblocks++; + + op = (struct audio_operations *) (sound_mem_blocks[sound_nblocks] = vmalloc(sizeof(struct audio_operations))); + + if (sound_nblocks < 1024) + sound_nblocks++; + if (d == NULL || op == NULL) { + printk(KERN_ERR "Sound: Can't allocate driver for (%s)\n", name); + sound_unload_audiodev(num); + return -(ENOMEM); + } + memset((char *) op, 0, sizeof(struct audio_operations)); + 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; +} + +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] = vmalloc(sizeof(struct mixer_operations))); + + if (sound_nblocks < 1024) + sound_nblocks++; + if (op == NULL) { + printk(KERN_ERR "Sound: Can't allocate mixer driver for (%s)\n", name); + return -ENOMEM; + } + memset((char *) op, 0, sizeof(struct mixer_operations)); + memcpy((char *) op, (char *) driver, driver_size); + + strlcpy(op->name, name, sizeof(op->name)); + op->devc = devc; + + mixer_devs[n] = op; + return n; +} + +void sound_unload_audiodev(int dev) +{ + if (dev != -1) { + DMAbuf_deinit(dev); + audio_devs[dev] = NULL; + unregister_sound_dsp((dev<<4)+3); + } +} + +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; +} + +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; +} + +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; +} + +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; +} + +void sound_unload_mixerdev(int dev) +{ + if (dev != -1) { + mixer_devs[dev] = NULL; + unregister_sound_mixer(dev<<4); + num_mixers--; + } +} + +void sound_unload_mididev(int dev) +{ + if (dev != -1) { + midi_devs[dev] = NULL; + unregister_sound_midi((dev<<4)+2); + } +} + +void sound_unload_synthdev(int dev) +{ + if (dev != -1) + synth_devs[dev] = NULL; +} + +void sound_unload_timerdev(int dev) +{ + if (dev != -1) + sound_timer_devs[dev] = NULL; +} diff --git a/sound/oss/dev_table.h b/sound/oss/dev_table.h new file mode 100644 index 000000000000..adf1d625b576 --- /dev/null +++ b/sound/oss/dev_table.h @@ -0,0 +1,405 @@ +/* + * 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 +/* + * 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 offs, 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); +}; + +#ifdef _DEV_TABLE_C_ +struct audio_operations *audio_devs[MAX_AUDIO_DEV]; +int num_audiodevs; +struct mixer_operations *mixer_devs[MAX_MIXER_DEV]; +int num_mixers; +struct synth_operations *synth_devs[MAX_SYNTH_DEV+MAX_MIDI_DEV]; +int num_synths; +struct midi_operations *midi_devs[MAX_MIDI_DEV]; +int num_midis; + +extern struct sound_timer_operations default_sound_timer; +struct sound_timer_operations *sound_timer_devs[MAX_TIMER_DEV] = { + &default_sound_timer, NULL +}; +int num_sound_timers = 1; +#else +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; +#endif /* _DEV_TABLE_C_ */ + +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/dm.h b/sound/oss/dm.h new file mode 100644 index 000000000000..14a90593c44f --- /dev/null +++ b/sound/oss/dm.h @@ -0,0 +1,79 @@ +#ifndef _DRIVERS_SOUND_DM_H +#define _DRIVERS_SOUND_DM_H + +/* + * Definitions of the 'direct midi sound' interface used + * by the newer commercial OSS package. We should export + * this to userland somewhere in glibc later. + */ + +/* + * Data structure composing an FM "note" or sound event. + */ + +struct dm_fm_voice +{ + u8 op; + u8 voice; + u8 am; + u8 vibrato; + u8 do_sustain; + u8 kbd_scale; + u8 harmonic; + u8 scale_level; + u8 volume; + u8 attack; + u8 decay; + u8 sustain; + u8 release; + u8 feedback; + u8 connection; + u8 left; + u8 right; + u8 waveform; +}; + +/* + * This describes an FM note by its voice, octave, frequency number (10bit) + * and key on/off. + */ + +struct dm_fm_note +{ + u8 voice; + u8 octave; + u32 fnum; + u8 key_on; +}; + +/* + * FM parameters that apply globally to all voices, and thus are not "notes" + */ + +struct dm_fm_params +{ + u8 am_depth; + u8 vib_depth; + u8 kbd_split; + u8 rhythm; + + /* This block is the percussion instrument data */ + u8 bass; + u8 snare; + u8 tomtom; + u8 cymbal; + u8 hihat; +}; + +/* + * FM mode ioctl settings + */ + +#define FM_IOCTL_RESET 0x20 +#define FM_IOCTL_PLAY_NOTE 0x21 +#define FM_IOCTL_SET_VOICE 0x22 +#define FM_IOCTL_SET_PARAMS 0x23 +#define FM_IOCTL_SET_MODE 0x24 +#define FM_IOCTL_SET_OPL 0x25 + +#endif diff --git a/sound/oss/dmabuf.c b/sound/oss/dmabuf.c new file mode 100644 index 000000000000..baf4244a54f2 --- /dev/null +++ b/sound/oss/dmabuf.c @@ -0,0 +1,1298 @@ +/* + * sound/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 "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; +} +/* aquires 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 = DMAbuf_get_buffer_pointer(dev, dmap, DMODE_OUTPUT); + /* Check for pointer wrapping situation */ + if (active_offs < 0 || 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); +} +/* aquires 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 aquire 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; +} + +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); +} +/* 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); +} + +int DMAbuf_open_dma(int dev) +{ + /* + * NOTE! This routine opens only the primary DMA channel (output). + */ + struct audio_operations *adev = audio_devs[dev]; + int err; + + if ((err = open_dmap(adev, OPEN_READWRITE, adev->dmap_out)) < 0) + return -EBUSY; + dma_init_buffers(adev->dmap_out); + adev->dmap_out->flags |= DMA_ALLOC_DONE; + adev->dmap_out->fragment_size = adev->dmap_out->buffsize; + + if (adev->dmap_out->dma >= 0) { + unsigned long flags; + + flags=claim_dma_lock(); + clear_dma_ff(adev->dmap_out->dma); + disable_dma(adev->dmap_out->dma); + release_dma_lock(flags); + } + return 0; +} + +void DMAbuf_close_dma(int dev) +{ + close_dmap(audio_devs[dev], audio_devs[dev]->dmap_out); +} + +void DMAbuf_init(int dev, int dma1, int dma2) +{ + struct audio_operations *adev = audio_devs[dev]; + /* + * NOTE! This routine could be called several times. + */ + + /* drag in audio_syms.o */ + { + extern char audio_syms_symbol; + audio_syms_symbol = 0; + } + + 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 000000000000..cb845580fe03 --- /dev/null +++ b/sound/oss/dmasound/Kconfig @@ -0,0 +1,58 @@ +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 + . + +config DMASOUND_PMAC + tristate "PowerMac DMA sound support" + depends on PPC32 && PPC_PMAC && SOUND && I2C + select DMASOUND + help + If you want to use the internal audio of your PowerMac 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 + . + +config DMASOUND_PAULA + tristate "Amiga DMA sound support" + depends on (AMIGA || APUS) && 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 + . + +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 + . + +config DMASOUND + tristate diff --git a/sound/oss/dmasound/Makefile b/sound/oss/dmasound/Makefile new file mode 100644 index 000000000000..4611636b1a81 --- /dev/null +++ b/sound/oss/dmasound/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for the DMA sound driver +# + +dmasound_pmac-y += dmasound_awacs.o \ + trans_16.o dac3550a.o tas_common.o \ + tas3001c.o tas3001c_tables.o \ + tas3004.o tas3004_tables.o + +obj-$(CONFIG_DMASOUND_ATARI) += dmasound_core.o dmasound_atari.o +obj-$(CONFIG_DMASOUND_PMAC) += dmasound_core.o dmasound_pmac.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/awacs_defs.h b/sound/oss/dmasound/awacs_defs.h new file mode 100644 index 000000000000..2194f46b046c --- /dev/null +++ b/sound/oss/dmasound/awacs_defs.h @@ -0,0 +1,251 @@ +/*********************************************************/ +/* This file was written by someone, somewhere, sometime */ +/* And is released into the Public Domain */ +/*********************************************************/ + +#ifndef _AWACS_DEFS_H_ +#define _AWACS_DEFS_H_ + +/*******************************/ +/* AWACs Audio Register Layout */ +/*******************************/ + +struct awacs_regs { + unsigned control; /* Audio control register */ + unsigned pad0[3]; + unsigned codec_ctrl; /* Codec control register */ + unsigned pad1[3]; + unsigned codec_stat; /* Codec status register */ + unsigned pad2[3]; + unsigned clip_count; /* Clipping count register */ + unsigned pad3[3]; + unsigned byteswap; /* Data is little-endian if 1 */ +}; + +/*******************/ +/* Audio Bit Masks */ +/*******************/ + +/* Audio Control Reg Bit Masks */ +/* ----- ------- --- --- ----- */ +#define MASK_ISFSEL (0xf) /* Input SubFrame Select */ +#define MASK_OSFSEL (0xf << 4) /* Output SubFrame Select */ +#define MASK_RATE (0x7 << 8) /* Sound Rate */ +#define MASK_CNTLERR (0x1 << 11) /* Error */ +#define MASK_PORTCHG (0x1 << 12) /* Port Change */ +#define MASK_IEE (0x1 << 13) /* Enable Interrupt on Error */ +#define MASK_IEPC (0x1 << 14) /* Enable Interrupt on Port Change */ +#define MASK_SSFSEL (0x3 << 15) /* Status SubFrame Select */ + +/* Audio Codec Control Reg Bit Masks */ +/* ----- ----- ------- --- --- ----- */ +#define MASK_NEWECMD (0x1 << 24) /* Lock: don't write to reg when 1 */ +#define MASK_EMODESEL (0x3 << 22) /* Send info out on which frame? */ +#define MASK_EXMODEADDR (0x3ff << 12) /* Extended Mode Address -- 10 bits */ +#define MASK_EXMODEDATA (0xfff) /* Extended Mode Data -- 12 bits */ + +/* Audio Codec Control Address Values / Masks */ +/* ----- ----- ------- ------- ------ - ----- */ +#define MASK_ADDR0 (0x0 << 12) /* Expanded Data Mode Address 0 */ +#define MASK_ADDR_MUX MASK_ADDR0 /* Mux Control */ +#define MASK_ADDR_GAIN MASK_ADDR0 + +#define MASK_ADDR1 (0x1 << 12) /* Expanded Data Mode Address 1 */ +#define MASK_ADDR_MUTE MASK_ADDR1 +#define MASK_ADDR_RATE MASK_ADDR1 + +#define MASK_ADDR2 (0x2 << 12) /* Expanded Data Mode Address 2 */ +#define MASK_ADDR_VOLA MASK_ADDR2 /* Volume Control A -- Headphones */ +#define MASK_ADDR_VOLHD MASK_ADDR2 + +#define MASK_ADDR4 (0x4 << 12) /* Expanded Data Mode Address 4 */ +#define MASK_ADDR_VOLC MASK_ADDR4 /* Volume Control C -- Speaker */ +#define MASK_ADDR_VOLSPK MASK_ADDR4 + +/* additional registers of screamer */ +#define MASK_ADDR5 (0x5 << 12) /* Expanded Data Mode Address 5 */ +#define MASK_ADDR6 (0x6 << 12) /* Expanded Data Mode Address 6 */ +#define MASK_ADDR7 (0x7 << 12) /* Expanded Data Mode Address 7 */ + +/* Address 0 Bit Masks & Macros */ +/* ------- - --- ----- - ------ */ +#define MASK_GAINRIGHT (0xf) /* Gain Right Mask */ +#define MASK_GAINLEFT (0xf << 4) /* Gain Left Mask */ +#define MASK_GAINLINE (0x1 << 8) /* Disable Mic preamp */ +#define MASK_GAINMIC (0x0 << 8) /* Enable Mic preamp */ + +#define MASK_MUX_CD (0x1 << 9) /* Select CD in MUX */ +#define MASK_MUX_MIC (0x1 << 10) /* Select Mic in MUX */ +#define MASK_MUX_AUDIN (0x1 << 11) /* Select Audio In in MUX */ +#define MASK_MUX_LINE MASK_MUX_AUDIN + +#define GAINRIGHT(x) ((x) & MASK_GAINRIGHT) +#define GAINLEFT(x) (((x) << 4) & MASK_GAINLEFT) + +#define DEF_CD_GAIN 0x00bb +#define DEF_MIC_GAIN 0x00cc + +/* Address 1 Bit Masks */ +/* ------- - --- ----- */ +#define MASK_ADDR1RES1 (0x3) /* Reserved */ +#define MASK_RECALIBRATE (0x1 << 2) /* Recalibrate */ +#define MASK_SAMPLERATE (0x7 << 3) /* Sample Rate: */ +#define MASK_LOOPTHRU (0x1 << 6) /* Loopthrough Enable */ +#define MASK_CMUTE (0x1 << 7) /* Output C (Speaker) Mute when 1 */ +#define MASK_SPKMUTE MASK_CMUTE +#define MASK_ADDR1RES2 (0x1 << 8) /* Reserved */ +#define MASK_AMUTE (0x1 << 9) /* Output A (Headphone) Mute when 1 */ +#define MASK_HDMUTE MASK_AMUTE +#define MASK_PAROUT0 (0x1 << 10) /* Parallel Output 0 */ +#define MASK_PAROUT1 (0x2 << 10) /* Parallel Output 1 */ + +#define MASK_MIC_BOOST (0x4) /* screamer mic boost */ + +#define SAMPLERATE_48000 (0x0 << 3) /* 48 or 44.1 kHz */ +#define SAMPLERATE_32000 (0x1 << 3) /* 32 or 29.4 kHz */ +#define SAMPLERATE_24000 (0x2 << 3) /* 24 or 22.05 kHz */ +#define SAMPLERATE_19200 (0x3 << 3) /* 19.2 or 17.64 kHz */ +#define SAMPLERATE_16000 (0x4 << 3) /* 16 or 14.7 kHz */ +#define SAMPLERATE_12000 (0x5 << 3) /* 12 or 11.025 kHz */ +#define SAMPLERATE_9600 (0x6 << 3) /* 9.6 or 8.82 kHz */ +#define SAMPLERATE_8000 (0x7 << 3) /* 8 or 7.35 kHz */ + +/* Address 2 & 4 Bit Masks & Macros */ +/* ------- - - - --- ----- - ------ */ +#define MASK_OUTVOLRIGHT (0xf) /* Output Right Volume */ +#define MASK_ADDR2RES1 (0x2 << 4) /* Reserved */ +#define MASK_ADDR4RES1 MASK_ADDR2RES1 +#define MASK_OUTVOLLEFT (0xf << 6) /* Output Left Volume */ +#define MASK_ADDR2RES2 (0x2 << 10) /* Reserved */ +#define MASK_ADDR4RES2 MASK_ADDR2RES2 + +#define VOLRIGHT(x) (((~(x)) & MASK_OUTVOLRIGHT)) +#define VOLLEFT(x) (((~(x)) << 6) & MASK_OUTVOLLEFT) + +/* Audio Codec Status Reg Bit Masks */ +/* ----- ----- ------ --- --- ----- */ +#define MASK_EXTEND (0x1 << 23) /* Extend */ +#define MASK_VALID (0x1 << 22) /* Valid Data? */ +#define MASK_OFLEFT (0x1 << 21) /* Overflow Left */ +#define MASK_OFRIGHT (0x1 << 20) /* Overflow Right */ +#define MASK_ERRCODE (0xf << 16) /* Error Code */ +#define MASK_REVISION (0xf << 12) /* Revision Number */ +#define MASK_MFGID (0xf << 8) /* Mfg. ID */ +#define MASK_CODSTATRES (0xf << 4) /* bits 4 - 7 reserved */ +#define MASK_INPPORT (0xf) /* Input Port */ +#define MASK_HDPCONN 8 /* headphone plugged in */ + +/* Clipping Count Reg Bit Masks */ +/* -------- ----- --- --- ----- */ +#define MASK_CLIPLEFT (0xff << 7) /* Clipping Count, Left Channel */ +#define MASK_CLIPRIGHT (0xff) /* Clipping Count, Right Channel */ + +/* DBDMA ChannelStatus Bit Masks */ +/* ----- ------------- --- ----- */ +#define MASK_CSERR (0x1 << 7) /* Error */ +#define MASK_EOI (0x1 << 6) /* End of Input -- only for Input Channel */ +#define MASK_CSUNUSED (0x1f << 1) /* bits 1-5 not used */ +#define MASK_WAIT (0x1) /* Wait */ + +/* Various Rates */ +/* ------- ----- */ +#define RATE_48000 (0x0 << 8) /* 48 kHz */ +#define RATE_44100 (0x0 << 8) /* 44.1 kHz */ +#define RATE_32000 (0x1 << 8) /* 32 kHz */ +#define RATE_29400 (0x1 << 8) /* 29.4 kHz */ +#define RATE_24000 (0x2 << 8) /* 24 kHz */ +#define RATE_22050 (0x2 << 8) /* 22.05 kHz */ +#define RATE_19200 (0x3 << 8) /* 19.2 kHz */ +#define RATE_17640 (0x3 << 8) /* 17.64 kHz */ +#define RATE_16000 (0x4 << 8) /* 16 kHz */ +#define RATE_14700 (0x4 << 8) /* 14.7 kHz */ +#define RATE_12000 (0x5 << 8) /* 12 kHz */ +#define RATE_11025 (0x5 << 8) /* 11.025 kHz */ +#define RATE_9600 (0x6 << 8) /* 9.6 kHz */ +#define RATE_8820 (0x6 << 8) /* 8.82 kHz */ +#define RATE_8000 (0x7 << 8) /* 8 kHz */ +#define RATE_7350 (0x7 << 8) /* 7.35 kHz */ + +#define RATE_LOW 1 /* HIGH = 48kHz, etc; LOW = 44.1kHz, etc. */ + +/*******************/ +/* Burgundy values */ +/*******************/ + +#define MASK_ADDR_BURGUNDY_INPSEL21 (0x11 << 12) +#define MASK_ADDR_BURGUNDY_INPSEL3 (0x12 << 12) + +#define MASK_ADDR_BURGUNDY_GAINCH1 (0x13 << 12) +#define MASK_ADDR_BURGUNDY_GAINCH2 (0x14 << 12) +#define MASK_ADDR_BURGUNDY_GAINCH3 (0x15 << 12) +#define MASK_ADDR_BURGUNDY_GAINCH4 (0x16 << 12) + +#define MASK_ADDR_BURGUNDY_VOLCH1 (0x20 << 12) +#define MASK_ADDR_BURGUNDY_VOLCH2 (0x21 << 12) +#define MASK_ADDR_BURGUNDY_VOLCH3 (0x22 << 12) +#define MASK_ADDR_BURGUNDY_VOLCH4 (0x23 << 12) + +#define MASK_ADDR_BURGUNDY_OUTPUTSELECTS (0x2B << 12) +#define MASK_ADDR_BURGUNDY_OUTPUTENABLES (0x2F << 12) + +#define MASK_ADDR_BURGUNDY_MASTER_VOLUME (0x30 << 12) + +#define MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES (0x60 << 12) + +#define MASK_ADDR_BURGUNDY_ATTENSPEAKER (0x62 << 12) +#define MASK_ADDR_BURGUNDY_ATTENLINEOUT (0x63 << 12) +#define MASK_ADDR_BURGUNDY_ATTENHP (0x64 << 12) + +#define MASK_ADDR_BURGUNDY_VOLCD (MASK_ADDR_BURGUNDY_VOLCH1) +#define MASK_ADDR_BURGUNDY_VOLLINE (MASK_ADDR_BURGUNDY_VOLCH2) +#define MASK_ADDR_BURGUNDY_VOLMIC (MASK_ADDR_BURGUNDY_VOLCH3) +#define MASK_ADDR_BURGUNDY_VOLMODEM (MASK_ADDR_BURGUNDY_VOLCH4) + +#define MASK_ADDR_BURGUNDY_GAINCD (MASK_ADDR_BURGUNDY_GAINCH1) +#define MASK_ADDR_BURGUNDY_GAINLINE (MASK_ADDR_BURGUNDY_GAINCH2) +#define MASK_ADDR_BURGUNDY_GAINMIC (MASK_ADDR_BURGUNDY_GAINCH3) +#define MASK_ADDR_BURGUNDY_GAINMODEM (MASK_ADDR_BURGUNDY_VOLCH4) + + +/* These are all default values for the burgundy */ +#define DEF_BURGUNDY_INPSEL21 (0xAA) +#define DEF_BURGUNDY_INPSEL3 (0x0A) + +#define DEF_BURGUNDY_GAINCD (0x33) +#define DEF_BURGUNDY_GAINLINE (0x44) +#define DEF_BURGUNDY_GAINMIC (0x44) +#define DEF_BURGUNDY_GAINMODEM (0x06) + +/* Remember: lowest volume here is 0x9b */ +#define DEF_BURGUNDY_VOLCD (0xCCCCCCCC) +#define DEF_BURGUNDY_VOLLINE (0x00000000) +#define DEF_BURGUNDY_VOLMIC (0x00000000) +#define DEF_BURGUNDY_VOLMODEM (0xCCCCCCCC) + +#define DEF_BURGUNDY_OUTPUTSELECTS (0x010f010f) +#define DEF_BURGUNDY_OUTPUTENABLES (0x0A) + +#define DEF_BURGUNDY_MASTER_VOLUME (0xFFFFFFFF) + +#define DEF_BURGUNDY_MORE_OUTPUTENABLES (0x7E) + +#define DEF_BURGUNDY_ATTENSPEAKER (0x44) +#define DEF_BURGUNDY_ATTENLINEOUT (0xCC) +#define DEF_BURGUNDY_ATTENHP (0xCC) + +/*********************/ +/* i2s layout values */ +/*********************/ + +#define I2S_REG_INT_CTL 0x00 +#define I2S_REG_SERIAL_FORMAT 0x10 +#define I2S_REG_CODEC_MSG_OUT 0x20 +#define I2S_REG_CODEC_MSG_IN 0x30 +#define I2S_REG_FRAME_COUNT 0x40 +#define I2S_REG_FRAME_MATCH 0x50 +#define I2S_REG_DATAWORD_SIZES 0x60 +#define I2S_REG_PEAKLEVEL_SEL 0x70 +#define I2S_REG_PEAKLEVEL_IN0 0x80 +#define I2S_REG_PEAKLEVEL_IN1 0x90 + +#endif /* _AWACS_DEFS_H_ */ diff --git a/sound/oss/dmasound/dac3550a.c b/sound/oss/dmasound/dac3550a.c new file mode 100644 index 000000000000..533895eba0eb --- /dev/null +++ b/sound/oss/dmasound/dac3550a.c @@ -0,0 +1,210 @@ +/* + * Driver for the i2c/i2s based DAC3550a sound chip used + * on some Apple iBooks. Also known as "DACA". + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dmasound.h" + +/* FYI: This code was derived from the tas3001c.c Texas/Tumbler mixer + * control code, as well as info derived from the AppleDACAAudio driver + * from Darwin CVS (main thing I derived being register numbers and + * values, as well as when to make the calls). */ + +#define I2C_DRIVERID_DACA (0xFDCB) + +#define DACA_VERSION "0.1" +#define DACA_DATE "20010930" + +static int cur_left_vol; +static int cur_right_vol; +static struct i2c_client *daca_client; + +static int daca_attach_adapter(struct i2c_adapter *adapter); +static int daca_detect_client(struct i2c_adapter *adapter, int address); +static int daca_detach_client(struct i2c_client *client); + +struct i2c_driver daca_driver = { + .owner = THIS_MODULE, + .name = "DAC3550A driver V " DACA_VERSION, + .id = I2C_DRIVERID_DACA, + .flags = I2C_DF_NOTIFY, + .attach_adapter = daca_attach_adapter, + .detach_client = daca_detach_client, +}; + +#define VOL_MAX ((1<<20) - 1) + +void daca_get_volume(uint * left_vol, uint *right_vol) +{ + *left_vol = cur_left_vol >> 5; + *right_vol = cur_right_vol >> 5; +} + +int daca_set_volume(uint left_vol, uint right_vol) +{ + unsigned short voldata; + + if (!daca_client) + return -1; + + /* Derived from experience, not from any specific values */ + left_vol <<= 5; + right_vol <<= 5; + + if (left_vol > VOL_MAX) + left_vol = VOL_MAX; + if (right_vol > VOL_MAX) + right_vol = VOL_MAX; + + voldata = ((left_vol >> 14) & 0x3f) << 8; + voldata |= (right_vol >> 14) & 0x3f; + + if (i2c_smbus_write_word_data(daca_client, 2, voldata) < 0) { + printk("daca: failed to set volume \n"); + return -1; + } + + cur_left_vol = left_vol; + cur_right_vol = right_vol; + + return 0; +} + +int daca_leave_sleep(void) +{ + if (!daca_client) + return -1; + + /* Do a short sleep, just to make sure I2C bus is awake and paying + * attention to us + */ + msleep(20); + /* Write the sample rate reg the value it needs */ + i2c_smbus_write_byte_data(daca_client, 1, 8); + daca_set_volume(cur_left_vol >> 5, cur_right_vol >> 5); + /* Another short delay, just to make sure the other I2C bus writes + * have taken... + */ + msleep(20); + /* Write the global config reg - invert right power amp, + * DAC on, use 5-volt mode */ + i2c_smbus_write_byte_data(daca_client, 3, 0x45); + + return 0; +} + +int daca_enter_sleep(void) +{ + if (!daca_client) + return -1; + + i2c_smbus_write_byte_data(daca_client, 1, 8); + daca_set_volume(cur_left_vol >> 5, cur_right_vol >> 5); + + /* Write the global config reg - invert right power amp, + * DAC on, enter low-power mode, use 5-volt mode + */ + i2c_smbus_write_byte_data(daca_client, 3, 0x65); + + return 0; +} + +static int daca_attach_adapter(struct i2c_adapter *adapter) +{ + if (!strncmp(adapter->name, "mac-io", 6)) + daca_detect_client(adapter, 0x4d); + return 0; +} + +static int daca_init_client(struct i2c_client * new_client) +{ + /* + * Probe is not working with the current i2c-keywest + * driver. We try to use addr 0x4d on each adapters + * instead, by setting the format register. + * + * FIXME: I'm sure that can be obtained from the + * device-tree. --BenH. + */ + + /* Write the global config reg - invert right power amp, + * DAC on, use 5-volt mode + */ + if (i2c_smbus_write_byte_data(new_client, 3, 0x45)) + return -1; + + i2c_smbus_write_byte_data(new_client, 1, 8); + daca_client = new_client; + daca_set_volume(15000, 15000); + + return 0; +} + +static int daca_detect_client(struct i2c_adapter *adapter, int address) +{ + const char *client_name = "DAC 3550A Digital Equalizer"; + struct i2c_client *new_client; + int rc = -ENODEV; + + new_client = kmalloc(sizeof(*new_client), GFP_KERNEL); + if (!new_client) + return -ENOMEM; + memset(new_client, 0, sizeof(*new_client)); + + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &daca_driver; + new_client->flags = 0; + strcpy(new_client->name, client_name); + + if (daca_init_client(new_client)) + goto bail; + + /* Tell the i2c layer a new client has arrived */ + if (i2c_attach_client(new_client)) + goto bail; + + return 0; + bail: + kfree(new_client); + return rc; +} + + +static int daca_detach_client(struct i2c_client *client) +{ + if (client == daca_client) + daca_client = NULL; + + i2c_detach_client(client); + kfree(client); + return 0; +} + +void daca_cleanup(void) +{ + i2c_del_driver(&daca_driver); +} + +int daca_init(void) +{ + printk("dac3550a driver version %s (%s)\n",DACA_VERSION,DACA_DATE); + return i2c_add_driver(&daca_driver); +} diff --git a/sound/oss/dmasound/dmasound.h b/sound/oss/dmasound/dmasound.h new file mode 100644 index 000000000000..9a2f50f0b184 --- /dev/null +++ b/sound/oss/dmasound/dmasound.h @@ -0,0 +1,277 @@ +#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 +#include + +#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 +#undef HAS_RECORD + +#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) + +#if defined(CONFIG_DMASOUND_PMAC) || defined(CONFIG_DMASOUND_PMAC_MODULE) +#define HAS_RECORD +#endif + + /* + * 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, int); + 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)(mode_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 */ +#ifdef HAS_RECORD + TRANS *trans_read; /* supported translations */ +#endif + 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 open_mode; + 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 + +#ifdef HAS_RECORD +extern struct sound_queue dmasound_read_sq; +#define read_sq dmasound_read_sq +#endif + +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_bal; /* Balance factor for expanding (not volume!) */ +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 000000000000..8daaf87664ba --- /dev/null +++ b/sound/oss/dmasound/dmasound_atari.c @@ -0,0 +1,1600 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_s8(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_u8(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_s16be(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_u16be(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_s16le(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_u16le(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_law(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_s8(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_u8(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_s16be(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_u16be(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_s16le(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_u16le(const u_char *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); + + +/*** Low level stuff *********************************************************/ + + +static void *AtaAlloc(unsigned int size, int 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, struct pt_regs *fp); + +/*** 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(mode_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 *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 *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 *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 *)userPtr)++)) + return -EFAULT; + *p++ = data ^ 0x8080; + count--; + } + } + *frameUsed += used; + return used; +} + + +static ssize_t ata_ct_s16be(const u_char *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 *)userPtr)++)) + return -EFAULT; + *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 *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 *)userPtr)++)) + return -EFAULT; + 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_long data; + if (get_user(data, ((u_int *)userPtr)++)) + return -EFAULT; + *p++ = data ^ 0x80008000; + count--; + } + *frameUsed += used; + } + return used; +} + + +static ssize_t ata_ct_s16le(const u_char *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 *)userPtr)++)) + return -EFAULT; + 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 *)userPtr)++)) + return -EFAULT; + data = le2be16dbl(data); + *p++ = data; + count--; + } + *frameUsed += used; + } + return used; +} + + +static ssize_t ata_ct_u16le(const u_char *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 *)userPtr)++)) + return -EFAULT; + 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 *)userPtr)++)) + return -EFAULT; + data = le2be16dbl(data) ^ 0x80008000; + *p++ = data; + count--; + } + *frameUsed += used; + } + return used; +} + + +static ssize_t ata_ctx_law(const u_char *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 *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 *)userPtr)++)) + return -EFAULT; + 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 *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 *)userPtr)++)) + return -EFAULT; + 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 *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 *)userPtr)++)) + return -EFAULT; + 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 *)userPtr)++)) + return -EFAULT; + 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 *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 *)userPtr)++)) + return -EFAULT; + 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 *)userPtr)++)) + return -EFAULT; + 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 *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 *)userPtr)++)) + return -EFAULT; + 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 *)userPtr)++)) + return -EFAULT; + 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 *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 *)userPtr)++)) + return -EFAULT; + 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 *)userPtr)++)) + return -EFAULT; + 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, int 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. */ + mfp.tim_ct_a = 0; /* ++roman: Stop timer before programming! */ + mfp.tim_dt_a = 1; /* Cause interrupt after first event. */ + mfp.tim_ct_a = 8; /* Turn on event counting. */ + /* Register interrupt handler. */ + request_irq(IRQ_MFP_TIMA, AtaInterrupt, IRQ_TYPE_SLOW, "DMA sound", + AtaInterrupt); + mfp.int_en_a |= 0x20; /* Turn interrupt on. */ + mfp.int_mk_a |= 0x20; + return 1; +} + +#ifdef MODULE +static void AtaIrqCleanUp(void) +{ + mfp.tim_ct_a = 0; /* stop timer */ + 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, struct pt_regs *fp) +{ +#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; + return IRQ_HANDLED; + } + + 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; + } + + /* 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. */ + 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(mode_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 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 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 ((mfp.int_en_a & 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_awacs.c b/sound/oss/dmasound/dmasound_awacs.c new file mode 100644 index 000000000000..5281b88987f3 --- /dev/null +++ b/sound/oss/dmasound/dmasound_awacs.c @@ -0,0 +1,3176 @@ +/* + * linux/sound/oss/dmasound/dmasound_awacs.c + * + * PowerMac `AWACS' and `Burgundy' DMA Sound Driver + * with some limited support for DACA & Tumbler + * + * See linux/sound/oss/dmasound/dmasound_core.c for copyright and + * history prior to 2001/01/26. + * + * 26/01/2001 ed 0.1 Iain Sandoe + * - added version info. + * - moved dbdma command buffer allocation to PMacXXXSqSetup() + * - fixed up beep dbdma cmd buffers + * + * 08/02/2001 [0.2] + * - make SNDCTL_DSP_GETFMTS return the correct info for the h/w + * - move soft format translations to a separate file + * - [0.3] make SNDCTL_DSP_GETCAPS return correct info. + * - [0.4] more informative machine name strings. + * - [0.5] + * - record changes. + * - made the default_hard/soft entries. + * 04/04/2001 [0.6] + * - minor correction to bit assignments in awacs_defs.h + * - incorporate mixer changes from 2.2.x back-port. + * - take out passthru as a rec input (it isn't). + * - make Input Gain slider work the 'right way up'. + * - try to make the mixer sliders more logical - so now the + * input selectors are just two-state (>50% == ON) and the + * Input Gain slider handles the rest of the gain issues. + * - try to pick slider representations that most closely match + * the actual use - e.g. IGain for input gain... + * - first stab at over/under-run detection. + * - minor cosmetic changes to IRQ identification. + * - fix bug where rates > max would be reported as supported. + * - first stab at over/under-run detection. + * - make use of i2c for mixer settings conditional on perch + * rather than cuda (some machines without perch have cuda). + * - fix bug where TX stops when dbdma status comes up "DEAD" + * so far only reported on PowerComputing clones ... but. + * - put in AWACS/Screamer register write timeouts. + * - part way to partitioning the init() stuff + * - first pass at 'tumbler' stuff (not support - just an attempt + * to allow the driver to load on new G4s). + * 01/02/2002 [0.7] - BenH + * - all sort of minor bits went in since the latest update, I + * bumped the version number for that reason + * + * 07/26/2002 [0.8] - BenH + * - More minor bits since last changelog (I should be more careful + * with those) + * - Support for snapper & better tumbler integration by Toby Sargeant + * - Headphone detect for scremer by Julien Blache + * - More tumbler fixed by Andreas Schwab + * 11/29/2003 [0.8.1] - Renzo Davoli (King Enzo) + * - Support for Snapper line in + * - snapper input resampling (for rates < 44100) + * - software line gain control + */ + +/* GENERAL FIXME/TODO: check that the assumptions about what is written to + mac-io is valid for DACA & Tumbler. + + This driver is in bad need of a rewrite. The dbdma code has to be split, + some proper device-tree parsing code has to be written, etc... +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_ADB_CUDA +#include +#endif +#ifdef CONFIG_ADB_PMU +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "awacs_defs.h" +#include "dmasound.h" +#include "tas3001c.h" +#include "tas3004.h" +#include "tas_common.h" + +#define DMASOUND_AWACS_REVISION 0 +#define DMASOUND_AWACS_EDITION 7 + +#define AWACS_SNAPPER 110 /* fake revision # for snapper */ +#define AWACS_BURGUNDY 100 /* fake revision # for burgundy */ +#define AWACS_TUMBLER 90 /* fake revision # for tumbler */ +#define AWACS_DACA 80 /* fake revision # for daca (ibook) */ +#define AWACS_AWACS 2 /* holding revision for AWACS */ +#define AWACS_SCREAMER 3 /* holding revision for Screamer */ +/* + * Interrupt numbers and addresses, & info obtained from the device tree. + */ +static int awacs_irq, awacs_tx_irq, awacs_rx_irq; +static volatile struct awacs_regs __iomem *awacs; +static volatile u32 __iomem *i2s; +static volatile struct dbdma_regs __iomem *awacs_txdma, *awacs_rxdma; +static int awacs_rate_index; +static int awacs_subframe; +static struct device_node* awacs_node; +static struct device_node* i2s_node; + +static char awacs_name[64]; +static int awacs_revision; +static int awacs_sleeping; +static DECLARE_MUTEX(dmasound_sem); + +static int sound_device_id; /* exists after iMac revA */ +static int hw_can_byteswap = 1 ; /* most pmac sound h/w can */ + +/* model info */ +/* To be replaced with better interaction with pmac_feature.c */ +static int is_pbook_3X00; +static int is_pbook_g3; + +/* expansion info */ +static int has_perch; +static int has_ziva; + +/* for earlier powerbooks which need fiddling with mac-io to enable + * cd etc. +*/ +static unsigned char __iomem *latch_base; +static unsigned char __iomem *macio_base; + +/* + * Space for the DBDMA command blocks. + */ +static void *awacs_tx_cmd_space; +static volatile struct dbdma_cmd *awacs_tx_cmds; +static int number_of_tx_cmd_buffers; + +static void *awacs_rx_cmd_space; +static volatile struct dbdma_cmd *awacs_rx_cmds; +static int number_of_rx_cmd_buffers; + +/* + * Cached values of AWACS registers (we can't read them). + * Except on the burgundy (and screamer). XXX + */ + +int awacs_reg[8]; +int awacs_reg1_save; + +/* tracking values for the mixer contents +*/ + +static int spk_vol; +static int line_vol; +static int passthru_vol; + +static int ip_gain; /* mic preamp settings */ +static int rec_lev = 0x4545 ; /* default CD gain 69 % */ +static int mic_lev; +static int cd_lev = 0x6363 ; /* 99 % */ +static int line_lev; + +static int hdp_connected; + +/* + * Stuff for outputting a beep. The values range from -327 to +327 + * so we can multiply by an amplitude in the range 0..100 to get a + * signed short value to put in the output buffer. + */ +static short beep_wform[256] = { + 0, 40, 79, 117, 153, 187, 218, 245, + 269, 288, 304, 316, 323, 327, 327, 324, + 318, 310, 299, 288, 275, 262, 249, 236, + 224, 213, 204, 196, 190, 186, 183, 182, + 182, 183, 186, 189, 192, 196, 200, 203, + 206, 208, 209, 209, 209, 207, 204, 201, + 197, 193, 188, 183, 179, 174, 170, 166, + 163, 161, 160, 159, 159, 160, 161, 162, + 164, 166, 168, 169, 171, 171, 171, 170, + 169, 167, 163, 159, 155, 150, 144, 139, + 133, 128, 122, 117, 113, 110, 107, 105, + 103, 103, 103, 103, 104, 104, 105, 105, + 105, 103, 101, 97, 92, 86, 78, 68, + 58, 45, 32, 18, 3, -11, -26, -41, + -55, -68, -79, -88, -95, -100, -102, -102, + -99, -93, -85, -75, -62, -48, -33, -16, + 0, 16, 33, 48, 62, 75, 85, 93, + 99, 102, 102, 100, 95, 88, 79, 68, + 55, 41, 26, 11, -3, -18, -32, -45, + -58, -68, -78, -86, -92, -97, -101, -103, + -105, -105, -105, -104, -104, -103, -103, -103, + -103, -105, -107, -110, -113, -117, -122, -128, + -133, -139, -144, -150, -155, -159, -163, -167, + -169, -170, -171, -171, -171, -169, -168, -166, + -164, -162, -161, -160, -159, -159, -160, -161, + -163, -166, -170, -174, -179, -183, -188, -193, + -197, -201, -204, -207, -209, -209, -209, -208, + -206, -203, -200, -196, -192, -189, -186, -183, + -182, -182, -183, -186, -190, -196, -204, -213, + -224, -236, -249, -262, -275, -288, -299, -310, + -318, -324, -327, -327, -323, -316, -304, -288, + -269, -245, -218, -187, -153, -117, -79, -40, +}; + +/* beep support */ +#define BEEP_SRATE 22050 /* 22050 Hz sample rate */ +#define BEEP_BUFLEN 512 +#define BEEP_VOLUME 15 /* 0 - 100 */ + +static int beep_vol = BEEP_VOLUME; +static int beep_playing; +static int awacs_beep_state; +static short *beep_buf; +static void *beep_dbdma_cmd_space; +static volatile struct dbdma_cmd *beep_dbdma_cmd; + +/* Burgundy functions */ +static void awacs_burgundy_wcw(unsigned addr,unsigned newval); +static unsigned awacs_burgundy_rcw(unsigned addr); +static void awacs_burgundy_write_volume(unsigned address, int volume); +static int awacs_burgundy_read_volume(unsigned address); +static void awacs_burgundy_write_mvolume(unsigned address, int volume); +static int awacs_burgundy_read_mvolume(unsigned address); + +/* we will allocate a single 'emergency' dbdma cmd block to use if the + tx status comes up "DEAD". This happens on some PowerComputing Pmac + clones, either owing to a bug in dbdma or some interaction between + IDE and sound. However, this measure would deal with DEAD status if + if appeared elsewhere. + + for the sake of memory efficiency we'll allocate this cmd as part of + the beep cmd stuff. +*/ + +static volatile struct dbdma_cmd *emergency_dbdma_cmd; + +#ifdef CONFIG_PMAC_PBOOK +/* + * Stuff for restoring after a sleep. + */ +static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when); +struct pmu_sleep_notifier awacs_sleep_notifier = { + awacs_sleep_notify, SLEEP_LEVEL_SOUND, +}; +#endif /* CONFIG_PMAC_PBOOK */ + +/* for (soft) sample rate translations */ +int expand_bal; /* Balance factor for expanding (not volume!) */ +int expand_read_bal; /* Balance factor for expanding reads (not volume!) */ + +/*** Low level stuff *********************************************************/ + +static void *PMacAlloc(unsigned int size, int flags); +static void PMacFree(void *ptr, unsigned int size); +static int PMacIrqInit(void); +#ifdef MODULE +static void PMacIrqCleanup(void); +#endif +static void PMacSilence(void); +static void PMacInit(void); +static int PMacSetFormat(int format); +static int PMacSetVolume(int volume); +static void PMacPlay(void); +static void PMacRecord(void); +static irqreturn_t pmac_awacs_tx_intr(int irq, void *devid, struct pt_regs *regs); +static irqreturn_t pmac_awacs_rx_intr(int irq, void *devid, struct pt_regs *regs); +static irqreturn_t pmac_awacs_intr(int irq, void *devid, struct pt_regs *regs); +static void awacs_write(int val); +static int awacs_get_volume(int reg, int lshift); +static int awacs_volume_setter(int volume, int n, int mute, int lshift); + + +/*** Mid level stuff **********************************************************/ + +static int PMacMixerIoctl(u_int cmd, u_long arg); +static int PMacWriteSqSetup(void); +static int PMacReadSqSetup(void); +static void PMacAbortRead(void); + +extern TRANS transAwacsNormal ; +extern TRANS transAwacsExpand ; +extern TRANS transAwacsNormalRead ; +extern TRANS transAwacsExpandRead ; + +extern int daca_init(void); +extern void daca_cleanup(void); +extern int daca_set_volume(uint left_vol, uint right_vol); +extern void daca_get_volume(uint * left_vol, uint *right_vol); +extern int daca_enter_sleep(void); +extern int daca_leave_sleep(void); + +#define TRY_LOCK() \ + if ((rc = down_interruptible(&dmasound_sem)) != 0) \ + return rc; +#define LOCK() down(&dmasound_sem); + +#define UNLOCK() up(&dmasound_sem); + +/* We use different versions that the ones provided in dmasound.h + * + * FIXME: Use different names ;) + */ +#undef IOCTL_IN +#undef IOCTL_OUT + +#define IOCTL_IN(arg, ret) \ + rc = get_user(ret, (int __user *)(arg)); \ + if (rc) break; +#define IOCTL_OUT(arg, ret) \ + ioctl_return2((int __user *)(arg), ret) + +static inline int ioctl_return2(int __user *addr, int value) +{ + return value < 0 ? value : put_user(value, addr); +} + + +/*** AE - TUMBLER / SNAPPER START ************************************************/ + + +int gpio_audio_reset, gpio_audio_reset_pol; +int gpio_amp_mute, gpio_amp_mute_pol; +int gpio_headphone_mute, gpio_headphone_mute_pol; +int gpio_headphone_detect, gpio_headphone_detect_pol; +int gpio_headphone_irq; + +int +setup_audio_gpio(const char *name, const char* compatible, int *gpio_addr, int* gpio_pol) +{ + struct device_node *np; + u32* pp; + + np = find_devices("gpio"); + if (!np) + return -ENODEV; + + np = np->child; + while(np != 0) { + if (name) { + char *property = get_property(np,"audio-gpio",NULL); + if (property != 0 && strcmp(property,name) == 0) + break; + } else if (compatible && device_is_compatible(np, compatible)) + break; + np = np->sibling; + } + if (!np) + return -ENODEV; + pp = (u32 *)get_property(np, "AAPL,address", NULL); + if (!pp) + return -ENODEV; + *gpio_addr = (*pp) & 0x0000ffff; + pp = (u32 *)get_property(np, "audio-gpio-active-state", NULL); + if (pp) + *gpio_pol = *pp; + else + *gpio_pol = 1; + if (np->n_intrs > 0) + return np->intrs[0].line; + + return 0; +} + +static inline void +write_audio_gpio(int gpio_addr, int data) +{ + if (!gpio_addr) + return; + pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio_addr, data ? 0x05 : 0x04); +} + +static inline int +read_audio_gpio(int gpio_addr) +{ + if (!gpio_addr) + return 0; + return ((pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio_addr, 0) & 0x02) !=0); +} + +/* + * Headphone interrupt via GPIO (Tumbler, Snapper, DACA) + */ +static irqreturn_t +headphone_intr(int irq, void *devid, struct pt_regs *regs) +{ + unsigned long flags; + + spin_lock_irqsave(&dmasound.lock, flags); + if (read_audio_gpio(gpio_headphone_detect) == gpio_headphone_detect_pol) { + printk(KERN_INFO "Audio jack plugged, muting speakers.\n"); + write_audio_gpio(gpio_headphone_mute, !gpio_headphone_mute_pol); + write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol); + tas_output_device_change(sound_device_id,TAS_OUTPUT_HEADPHONES,0); + } else { + printk(KERN_INFO "Audio jack unplugged, enabling speakers.\n"); + write_audio_gpio(gpio_amp_mute, !gpio_amp_mute_pol); + write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol); + tas_output_device_change(sound_device_id,TAS_OUTPUT_INTERNAL_SPKR,0); + } + spin_unlock_irqrestore(&dmasound.lock, flags); + return IRQ_HANDLED; +} + + +/* Initialize tumbler */ + +static int +tas_dmasound_init(void) +{ + setup_audio_gpio( + "audio-hw-reset", + NULL, + &gpio_audio_reset, + &gpio_audio_reset_pol); + setup_audio_gpio( + "amp-mute", + NULL, + &gpio_amp_mute, + &gpio_amp_mute_pol); + setup_audio_gpio("headphone-mute", + NULL, + &gpio_headphone_mute, + &gpio_headphone_mute_pol); + gpio_headphone_irq = setup_audio_gpio( + "headphone-detect", + NULL, + &gpio_headphone_detect, + &gpio_headphone_detect_pol); + /* Fix some broken OF entries in desktop machines */ + if (!gpio_headphone_irq) + gpio_headphone_irq = setup_audio_gpio( + NULL, + "keywest-gpio15", + &gpio_headphone_detect, + &gpio_headphone_detect_pol); + + write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol); + msleep(100); + write_audio_gpio(gpio_audio_reset, !gpio_audio_reset_pol); + msleep(100); + if (gpio_headphone_irq) { + if (request_irq(gpio_headphone_irq,headphone_intr,0,"Headphone detect",NULL) < 0) { + printk(KERN_ERR "tumbler: Can't request headphone interrupt\n"); + gpio_headphone_irq = 0; + } else { + u8 val; + /* Activate headphone status interrupts */ + val = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio_headphone_detect, 0); + pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio_headphone_detect, val | 0x80); + /* Trigger it */ + headphone_intr(0,NULL,NULL); + } + } + if (!gpio_headphone_irq) { + /* Some machine enter this case ? */ + printk(KERN_WARNING "tumbler: Headphone detect IRQ not found, enabling all outputs !\n"); + write_audio_gpio(gpio_amp_mute, !gpio_amp_mute_pol); + write_audio_gpio(gpio_headphone_mute, !gpio_headphone_mute_pol); + } + return 0; +} + + +static int +tas_dmasound_cleanup(void) +{ + if (gpio_headphone_irq) + free_irq(gpio_headphone_irq, NULL); + return 0; +} + +/* We don't support 48k yet */ +static int tas_freqs[1] = { 44100 } ; +static int tas_freqs_ok[1] = { 1 } ; + +/* don't know what to do really - just have to leave it where + * OF left things +*/ + +static int +tas_set_frame_rate(void) +{ + if (i2s) { + out_le32(i2s + (I2S_REG_SERIAL_FORMAT >> 2), 0x41190000); + out_le32(i2s + (I2S_REG_DATAWORD_SIZES >> 2), 0x02000200); + } + dmasound.hard.speed = 44100 ; + awacs_rate_index = 0 ; + return 44100 ; +} + +static int +tas_mixer_ioctl(u_int cmd, u_long arg) +{ + int __user *argp = (int __user *)arg; + int data; + int rc; + + rc=tas_device_ioctl(cmd, arg); + if (rc != -EINVAL) { + return rc; + } + + if ((cmd & ~0xff) == MIXER_WRITE(0) && + tas_supported_mixers() & (1<<(cmd & 0xff))) { + rc = get_user(data, argp); + if (rc<0) return rc; + tas_set_mixer_level(cmd & 0xff, data); + tas_get_mixer_level(cmd & 0xff, &data); + return ioctl_return2(argp, data); + } + if ((cmd & ~0xff) == MIXER_READ(0) && + tas_supported_mixers() & (1<<(cmd & 0xff))) { + tas_get_mixer_level(cmd & 0xff, &data); + return ioctl_return2(argp, data); + } + + switch(cmd) { + case SOUND_MIXER_READ_DEVMASK: + data = tas_supported_mixers() | SOUND_MASK_SPEAKER; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_STEREODEVS: + data = tas_stereo_mixers(); + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_CAPS: + rc = IOCTL_OUT(arg, 0); + break; + case SOUND_MIXER_READ_RECMASK: + // XXX FIXME: find a way to check what is really available */ + data = SOUND_MASK_LINE | SOUND_MASK_MIC; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_RECSRC: + if (awacs_reg[0] & MASK_MUX_AUDIN) + data |= SOUND_MASK_LINE; + if (awacs_reg[0] & MASK_MUX_MIC) + data |= SOUND_MASK_MIC; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_WRITE_RECSRC: + IOCTL_IN(arg, data); + data =0; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_WRITE_SPEAKER: /* really bell volume */ + IOCTL_IN(arg, data); + beep_vol = data & 0xff; + /* fall through */ + case SOUND_MIXER_READ_SPEAKER: + rc = IOCTL_OUT(arg, (beep_vol<<8) | beep_vol); + break; + case SOUND_MIXER_OUTMASK: + case SOUND_MIXER_OUTSRC: + default: + rc = -EINVAL; + } + + return rc; +} + +static void __init +tas_init_frame_rates(unsigned int *prop, unsigned int l) +{ + int i ; + if (prop) { + for (i=0; i<1; i++) + tas_freqs_ok[i] = 0; + for (l /= sizeof(int); l > 0; --l) { + unsigned int r = *prop++; + /* Apple 'Fixed' format */ + if (r >= 0x10000) + r >>= 16; + for (i = 0; i < 1; ++i) { + if (r == tas_freqs[i]) { + tas_freqs_ok[i] = 1; + break; + } + } + } + } + /* else we assume that all the rates are available */ +} + + +/*** AE - TUMBLER / SNAPPER END ************************************************/ + + + +/*** Low level stuff *********************************************************/ + +/* + * PCI PowerMac, with AWACS, Screamer, Burgundy, DACA or Tumbler and DBDMA. + */ +static void *PMacAlloc(unsigned int size, int flags) +{ + return kmalloc(size, flags); +} + +static void PMacFree(void *ptr, unsigned int size) +{ + kfree(ptr); +} + +static int __init PMacIrqInit(void) +{ + if (awacs) + if (request_irq(awacs_irq, pmac_awacs_intr, 0, "Built-in Sound misc", NULL)) + return 0; + if (request_irq(awacs_tx_irq, pmac_awacs_tx_intr, 0, "Built-in Sound out", NULL) + || request_irq(awacs_rx_irq, pmac_awacs_rx_intr, 0, "Built-in Sound in", NULL)) + return 0; + return 1; +} + +#ifdef MODULE +static void PMacIrqCleanup(void) +{ + /* turn off input & output dma */ + DBDMA_DO_STOP(awacs_txdma); + DBDMA_DO_STOP(awacs_rxdma); + + if (awacs) + /* disable interrupts from awacs interface */ + out_le32(&awacs->control, in_le32(&awacs->control) & 0xfff); + + /* Switch off the sound clock */ + pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 0); + /* Make sure proper bits are set on pismo & tipb */ + if ((machine_is_compatible("PowerBook3,1") || + machine_is_compatible("PowerBook3,2")) && awacs) { + awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1; + awacs_write(MASK_ADDR1 | awacs_reg[1]); + msleep(200); + } + if (awacs) + free_irq(awacs_irq, NULL); + free_irq(awacs_tx_irq, NULL); + free_irq(awacs_rx_irq, NULL); + + if (awacs) + iounmap(awacs); + if (i2s) + iounmap(i2s); + iounmap(awacs_txdma); + iounmap(awacs_rxdma); + + release_OF_resource(awacs_node, 0); + release_OF_resource(awacs_node, 1); + release_OF_resource(awacs_node, 2); + + if (awacs_tx_cmd_space) + kfree(awacs_tx_cmd_space); + if (awacs_rx_cmd_space) + kfree(awacs_rx_cmd_space); + if (beep_dbdma_cmd_space) + kfree(beep_dbdma_cmd_space); + if (beep_buf) + kfree(beep_buf); +#ifdef CONFIG_PMAC_PBOOK + pmu_unregister_sleep_notifier(&awacs_sleep_notifier); +#endif +} +#endif /* MODULE */ + +static void PMacSilence(void) +{ + /* turn off output dma */ + DBDMA_DO_STOP(awacs_txdma); +} + +/* don't know what to do really - just have to leave it where + * OF left things +*/ + +static int daca_set_frame_rate(void) +{ + if (i2s) { + out_le32(i2s + (I2S_REG_SERIAL_FORMAT >> 2), 0x41190000); + out_le32(i2s + (I2S_REG_DATAWORD_SIZES >> 2), 0x02000200); + } + dmasound.hard.speed = 44100 ; + awacs_rate_index = 0 ; + return 44100 ; +} + +static int awacs_freqs[8] = { + 44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350 +}; +static int awacs_freqs_ok[8] = { 1, 1, 1, 1, 1, 1, 1, 1 }; + +static int +awacs_set_frame_rate(int desired, int catch_r) +{ + int tolerance, i = 8 ; + /* + * If we have a sample rate which is within catchRadius percent + * of the requested value, we don't have to expand the samples. + * Otherwise choose the next higher rate. + * N.B.: burgundy awacs only works at 44100 Hz. + */ + do { + tolerance = catch_r * awacs_freqs[--i] / 100; + if (awacs_freqs_ok[i] + && dmasound.soft.speed <= awacs_freqs[i] + tolerance) + break; + } while (i > 0); + dmasound.hard.speed = awacs_freqs[i]; + awacs_rate_index = i; + + out_le32(&awacs->control, MASK_IEPC | (i << 8) | 0x11 ); + awacs_reg[1] = (awacs_reg[1] & ~MASK_SAMPLERATE) | (i << 3); + awacs_write(awacs_reg[1] | MASK_ADDR1); + return dmasound.hard.speed; +} + +static int +burgundy_set_frame_rate(void) +{ + awacs_rate_index = 0 ; + awacs_reg[1] = (awacs_reg[1] & ~MASK_SAMPLERATE) ; + /* XXX disable error interrupt on burgundy for now */ + out_le32(&awacs->control, MASK_IEPC | 0 | 0x11 | MASK_IEE); + return 44100 ; +} + +static int +set_frame_rate(int desired, int catch_r) +{ + switch (awacs_revision) { + case AWACS_BURGUNDY: + dmasound.hard.speed = burgundy_set_frame_rate(); + break ; + case AWACS_TUMBLER: + case AWACS_SNAPPER: + dmasound.hard.speed = tas_set_frame_rate(); + break ; + case AWACS_DACA: + dmasound.hard.speed = + daca_set_frame_rate(); + break ; + default: + dmasound.hard.speed = awacs_set_frame_rate(desired, + catch_r); + break ; + } + return dmasound.hard.speed ; +} + +static void +awacs_recalibrate(void) +{ + /* Sorry for the horrible delays... I hope to get that improved + * by making the whole PM process asynchronous in a future version + */ + msleep(750); + awacs_reg[1] |= MASK_CMUTE | MASK_AMUTE; + awacs_write(awacs_reg[1] | MASK_RECALIBRATE | MASK_ADDR1); + msleep(1000); + awacs_write(awacs_reg[1] | MASK_ADDR1); +} + +static void PMacInit(void) +{ + int tolerance; + + switch (dmasound.soft.format) { + case AFMT_S16_LE: + case AFMT_U16_LE: + if (hw_can_byteswap) + dmasound.hard.format = AFMT_S16_LE; + else + dmasound.hard.format = AFMT_S16_BE; + break; + default: + dmasound.hard.format = AFMT_S16_BE; + break; + } + dmasound.hard.stereo = 1; + dmasound.hard.size = 16; + + /* set dmasound.hard.speed - on the basis of what we want (soft) + * and the tolerance we'll allow. + */ + set_frame_rate(dmasound.soft.speed, catchRadius) ; + + tolerance = (catchRadius * dmasound.hard.speed) / 100; + if (dmasound.soft.speed >= dmasound.hard.speed - tolerance) { + dmasound.trans_write = &transAwacsNormal; + dmasound.trans_read = &transAwacsNormalRead; + } else { + dmasound.trans_write = &transAwacsExpand; + dmasound.trans_read = &transAwacsExpandRead; + } + + if (awacs) { + if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE)) + out_le32(&awacs->byteswap, BS_VAL); + else + out_le32(&awacs->byteswap, 0); + } + + expand_bal = -dmasound.soft.speed; + expand_read_bal = -dmasound.soft.speed; +} + +static int PMacSetFormat(int format) +{ + int size; + int req_format = format; + + 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_LE: + if(!hw_can_byteswap) + format = AFMT_S16_BE; + case AFMT_S16_BE: + size = 16; + break; + case AFMT_U16_LE: + if(!hw_can_byteswap) + format = AFMT_U16_BE; + case AFMT_U16_BE: + size = 16; + break; + default: /* :-) */ + printk(KERN_ERR "dmasound: unknown format 0x%x, using AFMT_U8\n", + format); + size = 8; + format = AFMT_U8; + } + + if (req_format == format) { + dmasound.soft.format = format; + dmasound.soft.size = size; + if (dmasound.minDev == SND_DEV_DSP) { + dmasound.dsp.format = format; + dmasound.dsp.size = size; + } + } + + return format; +} + +#define AWACS_VOLUME_TO_MASK(x) (15 - ((((x) - 1) * 15) / 99)) +#define AWACS_MASK_TO_VOLUME(y) (100 - ((y) * 99 / 15)) + +static int awacs_get_volume(int reg, int lshift) +{ + int volume; + + volume = AWACS_MASK_TO_VOLUME((reg >> lshift) & 0xf); + volume |= AWACS_MASK_TO_VOLUME(reg & 0xf) << 8; + return volume; +} + +static int awacs_volume_setter(int volume, int n, int mute, int lshift) +{ + int r1, rn; + + if (mute && volume == 0) { + r1 = awacs_reg[1] | mute; + } else { + r1 = awacs_reg[1] & ~mute; + rn = awacs_reg[n] & ~(0xf | (0xf << lshift)); + rn |= ((AWACS_VOLUME_TO_MASK(volume & 0xff) & 0xf) << lshift); + rn |= AWACS_VOLUME_TO_MASK((volume >> 8) & 0xff) & 0xf; + awacs_reg[n] = rn; + awacs_write((n << 12) | rn); + volume = awacs_get_volume(rn, lshift); + } + if (r1 != awacs_reg[1]) { + awacs_reg[1] = r1; + awacs_write(r1 | MASK_ADDR1); + } + return volume; +} + +static int PMacSetVolume(int volume) +{ + printk(KERN_WARNING "Bogus call to PMacSetVolume !\n"); + return 0; +} + +static void awacs_setup_for_beep(int speed) +{ + out_le32(&awacs->control, + (in_le32(&awacs->control) & ~0x1f00) + | ((speed > 0 ? speed : awacs_rate_index) << 8)); + + if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE) && speed == -1) + out_le32(&awacs->byteswap, BS_VAL); + else + out_le32(&awacs->byteswap, 0); +} + +/* CHECK: how much of this *really* needs IRQs masked? */ +static void __PMacPlay(void) +{ + volatile struct dbdma_cmd *cp; + int next_frg, count; + + count = 300 ; /* > two cycles at the lowest sample rate */ + + /* what we want to send next */ + next_frg = (write_sq.front + write_sq.active) % write_sq.max_count; + + if (awacs_beep_state) { + /* sound takes precedence over beeps */ + /* stop the dma channel */ + out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); + while ( (in_le32(&awacs_txdma->status) & RUN) && count--) + udelay(1); + if (awacs) + awacs_setup_for_beep(-1); + out_le32(&awacs_txdma->cmdptr, + virt_to_bus(&(awacs_tx_cmds[next_frg]))); + + beep_playing = 0; + awacs_beep_state = 0; + } + /* this won't allow more than two frags to be in the output queue at + once. (or one, if the max frags is 2 - because count can't exceed + 2 in that case) + */ + while (write_sq.active < 2 && write_sq.active < write_sq.count) { + count = (write_sq.count == write_sq.active + 1) ? + write_sq.rear_size:write_sq.block_size ; + if (count < write_sq.block_size) { + if (!write_sq.syncing) /* last block not yet filled,*/ + break; /* and we're not syncing or POST-ed */ + else { + /* pretend the block is full to force a new + block to be started on the next write */ + write_sq.rear_size = write_sq.block_size ; + write_sq.syncing &= ~2 ; /* clear POST */ + } + } + cp = &awacs_tx_cmds[next_frg]; + st_le16(&cp->req_count, count); + st_le16(&cp->xfer_status, 0); + st_le16(&cp->command, OUTPUT_MORE + INTR_ALWAYS); + /* put a STOP at the end of the queue - but only if we have + space for it. This means that, if we under-run and we only + have two fragments, we might re-play sound from an existing + queued frag. I guess the solution to that is not to set two + frags if you are likely to under-run... + */ + if (write_sq.count < write_sq.max_count) { + if (++next_frg >= write_sq.max_count) + next_frg = 0 ; /* wrap */ + /* if we get here then we've underrun so we will stop*/ + st_le16(&awacs_tx_cmds[next_frg].command, DBDMA_STOP); + } + /* set the dbdma controller going, if it is not already */ + if (write_sq.active == 0) + out_le32(&awacs_txdma->cmdptr, virt_to_bus(cp)); + (void)in_le32(&awacs_txdma->status); + out_le32(&awacs_txdma->control, ((RUN|WAKE) << 16) + (RUN|WAKE)); + ++write_sq.active; + } +} + +static void PMacPlay(void) +{ + LOCK(); + if (!awacs_sleeping) { + unsigned long flags; + + spin_lock_irqsave(&dmasound.lock, flags); + __PMacPlay(); + spin_unlock_irqrestore(&dmasound.lock, flags); + } + UNLOCK(); +} + +static void PMacRecord(void) +{ + unsigned long flags; + + if (read_sq.active) + return; + + spin_lock_irqsave(&dmasound.lock, flags); + + /* This is all we have to do......Just start it up. + */ + out_le32(&awacs_rxdma->control, ((RUN|WAKE) << 16) + (RUN|WAKE)); + read_sq.active = 1; + + spin_unlock_irqrestore(&dmasound.lock, flags); +} + +/* if the TX status comes up "DEAD" - reported on some Power Computing machines + we need to re-start the dbdma - but from a different physical start address + and with a different transfer length. It would get very messy to do this + with the normal dbdma_cmd blocks - we would have to re-write the buffer start + addresses each time. So, we will keep a single dbdma_cmd block which can be + fiddled with. + When DEAD status is first reported the content of the faulted dbdma block is + copied into the emergency buffer and we note that the buffer is in use. + we then bump the start physical address by the amount that was successfully + output before it died. + On any subsequent DEAD result we just do the bump-ups (we know that we are + already using the emergency dbdma_cmd). + CHECK: this just tries to "do it". It is possible that we should abandon + xfers when the number of residual bytes gets below a certain value - I can + see that this might cause a loop-forever if too small a transfer causes + DEAD status. However this is a TODO for now - we'll see what gets reported. + When we get a successful transfer result with the emergency buffer we just + pretend that it completed using the original dmdma_cmd and carry on. The + 'next_cmd' field will already point back to the original loop of blocks. +*/ + +static irqreturn_t +pmac_awacs_tx_intr(int irq, void *devid, struct pt_regs *regs) +{ + int i = write_sq.front; + int stat; + int i_nowrap = write_sq.front; + volatile struct dbdma_cmd *cp; + /* != 0 when we are dealing with a DEAD xfer */ + static int emergency_in_use; + + spin_lock(&dmasound.lock); + while (write_sq.active > 0) { /* we expect to have done something*/ + if (emergency_in_use) /* we are dealing with DEAD xfer */ + cp = emergency_dbdma_cmd ; + else + cp = &awacs_tx_cmds[i]; + stat = ld_le16(&cp->xfer_status); + if (stat & DEAD) { + unsigned short req, res ; + unsigned int phy ; +#ifdef DEBUG_DMASOUND +printk("dmasound_pmac: tx-irq: xfer died - patching it up...\n") ; +#endif + /* to clear DEAD status we must first clear RUN + set it to quiescent to be on the safe side */ + (void)in_le32(&awacs_txdma->status); + out_le32(&awacs_txdma->control, + (RUN|PAUSE|FLUSH|WAKE) << 16); + write_sq.died++ ; + if (!emergency_in_use) { /* new problem */ + memcpy((void *)emergency_dbdma_cmd, (void *)cp, + sizeof(struct dbdma_cmd)); + emergency_in_use = 1; + cp = emergency_dbdma_cmd; + } + /* now bump the values to reflect the amount + we haven't yet shifted */ + req = ld_le16(&cp->req_count); + res = ld_le16(&cp->res_count); + phy = ld_le32(&cp->phy_addr); + phy += (req - res); + st_le16(&cp->req_count, res); + st_le16(&cp->res_count, 0); + st_le16(&cp->xfer_status, 0); + st_le32(&cp->phy_addr, phy); + st_le32(&cp->cmd_dep, virt_to_bus(&awacs_tx_cmds[(i+1)%write_sq.max_count])); + st_le16(&cp->command, OUTPUT_MORE | BR_ALWAYS | INTR_ALWAYS); + + /* point at our patched up command block */ + out_le32(&awacs_txdma->cmdptr, virt_to_bus(cp)); + /* we must re-start the controller */ + (void)in_le32(&awacs_txdma->status); + /* should complete clearing the DEAD status */ + out_le32(&awacs_txdma->control, + ((RUN|WAKE) << 16) + (RUN|WAKE)); + break; /* this block is still going */ + } + if ((stat & ACTIVE) == 0) + break; /* this frame is still going */ + if (emergency_in_use) + emergency_in_use = 0 ; /* done that */ + --write_sq.count; + --write_sq.active; + i_nowrap++; + if (++i >= write_sq.max_count) + i = 0; + } + + /* if we stopped and we were not sync-ing - then we under-ran */ + if( write_sq.syncing == 0 ){ + stat = in_le32(&awacs_txdma->status) ; + /* we hit the dbdma_stop */ + if( (stat & ACTIVE) == 0 ) write_sq.xruns++ ; + } + + /* if we used some data up then wake the writer to supply some more*/ + if (i_nowrap != write_sq.front) + WAKE_UP(write_sq.action_queue); + write_sq.front = i; + + /* but make sure we funnel what we've already got */\ + if (!awacs_sleeping) + __PMacPlay(); + + /* make the wake-on-empty conditional on syncing */ + if (!write_sq.active && (write_sq.syncing & 1)) + WAKE_UP(write_sq.sync_queue); /* any time we're empty */ + spin_unlock(&dmasound.lock); + return IRQ_HANDLED; +} + + +static irqreturn_t +pmac_awacs_rx_intr(int irq, void *devid, struct pt_regs *regs) +{ + int stat ; + /* For some reason on my PowerBook G3, I get one interrupt + * when the interrupt vector is installed (like something is + * pending). This happens before the dbdma is initialized by + * us, so I just check the command pointer and if it is zero, + * just blow it off. + */ + if (in_le32(&awacs_rxdma->cmdptr) == 0) + return IRQ_HANDLED; + + /* We also want to blow 'em off when shutting down. + */ + if (read_sq.active == 0) + return IRQ_HANDLED; + + spin_lock(&dmasound.lock); + /* Check multiple buffers in case we were held off from + * interrupt processing for a long time. Geeze, I really hope + * this doesn't happen. + */ + while ((stat=awacs_rx_cmds[read_sq.rear].xfer_status)) { + + /* if we got a "DEAD" status then just log it for now. + and try to restart dma. + TODO: figure out how best to fix it up + */ + if (stat & DEAD){ +#ifdef DEBUG_DMASOUND +printk("dmasound_pmac: rx-irq: DIED - attempting resurection\n"); +#endif + /* to clear DEAD status we must first clear RUN + set it to quiescent to be on the safe side */ + (void)in_le32(&awacs_txdma->status); + out_le32(&awacs_txdma->control, + (RUN|PAUSE|FLUSH|WAKE) << 16); + awacs_rx_cmds[read_sq.rear].xfer_status = 0; + awacs_rx_cmds[read_sq.rear].res_count = 0; + read_sq.died++ ; + (void)in_le32(&awacs_txdma->status); + /* re-start the same block */ + out_le32(&awacs_rxdma->cmdptr, + virt_to_bus(&awacs_rx_cmds[read_sq.rear])); + /* we must re-start the controller */ + (void)in_le32(&awacs_rxdma->status); + /* should complete clearing the DEAD status */ + out_le32(&awacs_rxdma->control, + ((RUN|WAKE) << 16) + (RUN|WAKE)); + spin_unlock(&dmasound.lock); + return IRQ_HANDLED; /* try this block again */ + } + /* Clear status and move on to next buffer. + */ + awacs_rx_cmds[read_sq.rear].xfer_status = 0; + read_sq.rear++; + + /* Wrap the buffer ring. + */ + if (read_sq.rear >= read_sq.max_active) + read_sq.rear = 0; + + /* If we have caught up to the front buffer, bump it. + * This will cause weird (but not fatal) results if the + * read loop is currently using this buffer. The user is + * behind in this case anyway, so weird things are going + * to happen. + */ + if (read_sq.rear == read_sq.front) { + read_sq.front++; + read_sq.xruns++ ; /* we overan */ + if (read_sq.front >= read_sq.max_active) + read_sq.front = 0; + } + } + + WAKE_UP(read_sq.action_queue); + spin_unlock(&dmasound.lock); + return IRQ_HANDLED; +} + + +static irqreturn_t +pmac_awacs_intr(int irq, void *devid, struct pt_regs *regs) +{ + int ctrl; + int status; + int r1; + + spin_lock(&dmasound.lock); + ctrl = in_le32(&awacs->control); + status = in_le32(&awacs->codec_stat); + + if (ctrl & MASK_PORTCHG) { + /* tested on Screamer, should work on others too */ + if (awacs_revision == AWACS_SCREAMER) { + if (((status & MASK_HDPCONN) >> 3) && (hdp_connected == 0)) { + hdp_connected = 1; + + r1 = awacs_reg[1] | MASK_SPKMUTE; + awacs_reg[1] = r1; + awacs_write(r1 | MASK_ADDR_MUTE); + } else if (((status & MASK_HDPCONN) >> 3 == 0) && (hdp_connected == 1)) { + hdp_connected = 0; + + r1 = awacs_reg[1] & ~MASK_SPKMUTE; + awacs_reg[1] = r1; + awacs_write(r1 | MASK_ADDR_MUTE); + } + } + } + if (ctrl & MASK_CNTLERR) { + int err = (in_le32(&awacs->codec_stat) & MASK_ERRCODE) >> 16; + /* CHECK: we just swallow burgundy errors at the moment..*/ + if (err != 0 && awacs_revision != AWACS_BURGUNDY) + printk(KERN_ERR "dmasound_pmac: error %x\n", err); + } + /* Writing 1s to the CNTLERR and PORTCHG bits clears them... */ + out_le32(&awacs->control, ctrl); + spin_unlock(&dmasound.lock); + return IRQ_HANDLED; +} + +static void +awacs_write(int val) +{ + int count = 300 ; + if (awacs_revision >= AWACS_DACA || !awacs) + return ; + + while ((in_le32(&awacs->codec_ctrl) & MASK_NEWECMD) && count--) + udelay(1) ; /* timeout is > 2 samples at lowest rate */ + out_le32(&awacs->codec_ctrl, val | (awacs_subframe << 22)); + (void)in_le32(&awacs->byteswap); +} + +/* this is called when the beep timer expires... it will be called even + if the beep has been overidden by other sound output. +*/ +static void awacs_nosound(unsigned long xx) +{ + unsigned long flags; + int count = 600 ; /* > four samples at lowest rate */ + + spin_lock_irqsave(&dmasound.lock, flags); + if (beep_playing) { + st_le16(&beep_dbdma_cmd->command, DBDMA_STOP); + out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); + while ((in_le32(&awacs_txdma->status) & RUN) && count--) + udelay(1); + if (awacs) + awacs_setup_for_beep(-1); + beep_playing = 0; + } + spin_unlock_irqrestore(&dmasound.lock, flags); +} + +/* + * We generate the beep with a single dbdma command that loops a buffer + * forever - without generating interrupts. + * + * So, to stop it you have to stop dma output as per awacs_nosound. + */ +static int awacs_beep_event(struct input_dev *dev, unsigned int type, + unsigned int code, int hz) +{ + unsigned long flags; + int beep_speed = 0; + int srate; + int period, ncycles, nsamples; + int i, j, f; + short *p; + static int beep_hz_cache; + static int beep_nsamples_cache; + static int beep_volume_cache; + + if (type != EV_SND) + return -1; + switch (code) { + case SND_BELL: + if (hz) + hz = 1000; + break; + case SND_TONE: + break; + default: + return -1; + } + + if (beep_buf == NULL) + return -1; + + /* quick-hack fix for DACA, Burgundy & Tumbler */ + + if (awacs_revision >= AWACS_DACA){ + srate = 44100 ; + } else { + for (i = 0; i < 8 && awacs_freqs[i] >= BEEP_SRATE; ++i) + if (awacs_freqs_ok[i]) + beep_speed = i; + srate = awacs_freqs[beep_speed]; + } + + if (hz <= srate / BEEP_BUFLEN || hz > srate / 2) { + /* cancel beep currently playing */ + awacs_nosound(0); + return 0; + } + + spin_lock_irqsave(&dmasound.lock, flags); + if (beep_playing || write_sq.active || beep_buf == NULL) { + spin_unlock_irqrestore(&dmasound.lock, flags); + return -1; /* too hard, sorry :-( */ + } + beep_playing = 1; + st_le16(&beep_dbdma_cmd->command, OUTPUT_MORE + BR_ALWAYS); + spin_unlock_irqrestore(&dmasound.lock, flags); + + if (hz == beep_hz_cache && beep_vol == beep_volume_cache) { + nsamples = beep_nsamples_cache; + } else { + period = srate * 256 / hz; /* fixed point */ + ncycles = BEEP_BUFLEN * 256 / period; + nsamples = (period * ncycles) >> 8; + f = ncycles * 65536 / nsamples; + j = 0; + p = beep_buf; + for (i = 0; i < nsamples; ++i, p += 2) { + p[0] = p[1] = beep_wform[j >> 8] * beep_vol; + j = (j + f) & 0xffff; + } + beep_hz_cache = hz; + beep_volume_cache = beep_vol; + beep_nsamples_cache = nsamples; + } + + st_le16(&beep_dbdma_cmd->req_count, nsamples*4); + st_le16(&beep_dbdma_cmd->xfer_status, 0); + st_le32(&beep_dbdma_cmd->cmd_dep, virt_to_bus(beep_dbdma_cmd)); + st_le32(&beep_dbdma_cmd->phy_addr, virt_to_bus(beep_buf)); + awacs_beep_state = 1; + + spin_lock_irqsave(&dmasound.lock, flags); + if (beep_playing) { /* i.e. haven't been terminated already */ + int count = 300 ; + out_le32(&awacs_txdma->control, (RUN|WAKE|FLUSH|PAUSE) << 16); + while ((in_le32(&awacs_txdma->status) & RUN) && count--) + udelay(1); /* timeout > 2 samples at lowest rate*/ + if (awacs) + awacs_setup_for_beep(beep_speed); + out_le32(&awacs_txdma->cmdptr, virt_to_bus(beep_dbdma_cmd)); + (void)in_le32(&awacs_txdma->status); + out_le32(&awacs_txdma->control, RUN | (RUN << 16)); + } + spin_unlock_irqrestore(&dmasound.lock, flags); + + return 0; +} + +/* used in init and for wake-up */ + +static void +load_awacs(void) +{ + awacs_write(awacs_reg[0] + MASK_ADDR0); + awacs_write(awacs_reg[1] + MASK_ADDR1); + awacs_write(awacs_reg[2] + MASK_ADDR2); + awacs_write(awacs_reg[4] + MASK_ADDR4); + + if (awacs_revision == AWACS_SCREAMER) { + awacs_write(awacs_reg[5] + MASK_ADDR5); + msleep(100); + awacs_write(awacs_reg[6] + MASK_ADDR6); + msleep(2); + awacs_write(awacs_reg[1] + MASK_ADDR1); + awacs_write(awacs_reg[7] + MASK_ADDR7); + } + if (awacs) { + if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE)) + out_le32(&awacs->byteswap, BS_VAL); + else + out_le32(&awacs->byteswap, 0); + } +} + +#ifdef CONFIG_PMAC_PBOOK +/* + * Save state when going to sleep, restore it afterwards. + */ +/* FIXME: sort out disabling/re-enabling of read stuff as well */ +static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when) +{ + unsigned long flags; + + switch (when) { + case PBOOK_SLEEP_NOW: + LOCK(); + awacs_sleeping = 1; + /* Tell the rest of the driver we are now going to sleep */ + mb(); + if (awacs_revision == AWACS_SCREAMER || + awacs_revision == AWACS_AWACS) { + awacs_reg1_save = awacs_reg[1]; + awacs_reg[1] |= MASK_AMUTE | MASK_CMUTE; + awacs_write(MASK_ADDR1 | awacs_reg[1]); + } + + PMacSilence(); + /* stop rx - if going - a bit of a daft user... but */ + out_le32(&awacs_rxdma->control, (RUN|WAKE|FLUSH << 16)); + /* deny interrupts */ + if (awacs) + disable_irq(awacs_irq); + disable_irq(awacs_tx_irq); + disable_irq(awacs_rx_irq); + /* Chip specific sleep code */ + switch (awacs_revision) { + case AWACS_TUMBLER: + case AWACS_SNAPPER: + write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol); + write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol); + tas_enter_sleep(); + write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol); + break ; + case AWACS_DACA: + daca_enter_sleep(); + break ; + case AWACS_BURGUNDY: + break ; + case AWACS_SCREAMER: + case AWACS_AWACS: + default: + out_le32(&awacs->control, 0x11) ; + break ; + } + /* Disable sound clock */ + pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 0); + /* According to Darwin, we do that after turning off the sound + * chip clock. All this will have to be cleaned up once we properly + * parse the OF sound-objects + */ + if ((machine_is_compatible("PowerBook3,1") || + machine_is_compatible("PowerBook3,2")) && awacs) { + awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1; + awacs_write(MASK_ADDR1 | awacs_reg[1]); + msleep(200); + } + break; + case PBOOK_WAKE: + /* Enable sound clock */ + pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 1); + if ((machine_is_compatible("PowerBook3,1") || + machine_is_compatible("PowerBook3,2")) && awacs) { + msleep(100); + awacs_reg[1] &= ~(MASK_PAROUT0 | MASK_PAROUT1); + awacs_write(MASK_ADDR1 | awacs_reg[1]); + msleep(300); + } else + msleep(1000); + /* restore settings */ + switch (awacs_revision) { + case AWACS_TUMBLER: + case AWACS_SNAPPER: + write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol); + write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol); + write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol); + msleep(100); + write_audio_gpio(gpio_audio_reset, !gpio_audio_reset_pol); + msleep(150); + tas_leave_sleep(); /* Stub for now */ + headphone_intr(0,NULL,NULL); + break; + case AWACS_DACA: + msleep(10); /* Check this !!! */ + daca_leave_sleep(); + break ; /* dont know how yet */ + case AWACS_BURGUNDY: + break ; + case AWACS_SCREAMER: + case AWACS_AWACS: + default: + load_awacs() ; + break ; + } + /* Recalibrate chip */ + if (awacs_revision == AWACS_SCREAMER && awacs) + awacs_recalibrate(); + /* Make sure dma is stopped */ + PMacSilence(); + if (awacs) + enable_irq(awacs_irq); + enable_irq(awacs_tx_irq); + enable_irq(awacs_rx_irq); + if (awacs) { + /* OK, allow ints back again */ + out_le32(&awacs->control, MASK_IEPC + | (awacs_rate_index << 8) | 0x11 + | (awacs_revision < AWACS_DACA ? MASK_IEE: 0)); + } + if (macio_base && is_pbook_g3) { + /* FIXME: should restore the setup we had...*/ + out_8(macio_base + 0x37, 3); + } else if (is_pbook_3X00) { + in_8(latch_base + 0x190); + } + /* Remove mute */ + if (awacs_revision == AWACS_SCREAMER || + awacs_revision == AWACS_AWACS) { + awacs_reg[1] = awacs_reg1_save; + awacs_write(MASK_ADDR1 | awacs_reg[1]); + } + awacs_sleeping = 0; + /* Resume pending sounds. */ + /* we don't try to restart input... */ + spin_lock_irqsave(&dmasound.lock, flags); + __PMacPlay(); + spin_unlock_irqrestore(&dmasound.lock, flags); + UNLOCK(); + } + return PBOOK_SLEEP_OK; +} +#endif /* CONFIG_PMAC_PBOOK */ + + +/* All the burgundy functions: */ + +/* Waits for busy flag to clear */ +inline static void +awacs_burgundy_busy_wait(void) +{ + int count = 50; /* > 2 samples at 44k1 */ + while ((in_le32(&awacs->codec_ctrl) & MASK_NEWECMD) && count--) + udelay(1) ; +} + +inline static void +awacs_burgundy_extend_wait(void) +{ + int count = 50 ; /* > 2 samples at 44k1 */ + while ((!(in_le32(&awacs->codec_stat) & MASK_EXTEND)) && count--) + udelay(1) ; + count = 50; + while ((in_le32(&awacs->codec_stat) & MASK_EXTEND) && count--) + udelay(1); +} + +static void +awacs_burgundy_wcw(unsigned addr, unsigned val) +{ + out_le32(&awacs->codec_ctrl, addr + 0x200c00 + (val & 0xff)); + awacs_burgundy_busy_wait(); + out_le32(&awacs->codec_ctrl, addr + 0x200d00 +((val>>8) & 0xff)); + awacs_burgundy_busy_wait(); + out_le32(&awacs->codec_ctrl, addr + 0x200e00 +((val>>16) & 0xff)); + awacs_burgundy_busy_wait(); + out_le32(&awacs->codec_ctrl, addr + 0x200f00 +((val>>24) & 0xff)); + awacs_burgundy_busy_wait(); +} + +static unsigned +awacs_burgundy_rcw(unsigned addr) +{ + unsigned val = 0; + unsigned long flags; + + /* should have timeouts here */ + spin_lock_irqsave(&dmasound.lock, flags); + + out_le32(&awacs->codec_ctrl, addr + 0x100000); + awacs_burgundy_busy_wait(); + awacs_burgundy_extend_wait(); + val += (in_le32(&awacs->codec_stat) >> 4) & 0xff; + + out_le32(&awacs->codec_ctrl, addr + 0x100100); + awacs_burgundy_busy_wait(); + awacs_burgundy_extend_wait(); + val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<8; + + out_le32(&awacs->codec_ctrl, addr + 0x100200); + awacs_burgundy_busy_wait(); + awacs_burgundy_extend_wait(); + val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<16; + + out_le32(&awacs->codec_ctrl, addr + 0x100300); + awacs_burgundy_busy_wait(); + awacs_burgundy_extend_wait(); + val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<24; + + spin_unlock_irqrestore(&dmasound.lock, flags); + + return val; +} + + +static void +awacs_burgundy_wcb(unsigned addr, unsigned val) +{ + out_le32(&awacs->codec_ctrl, addr + 0x300000 + (val & 0xff)); + awacs_burgundy_busy_wait(); +} + +static unsigned +awacs_burgundy_rcb(unsigned addr) +{ + unsigned val = 0; + unsigned long flags; + + /* should have timeouts here */ + spin_lock_irqsave(&dmasound.lock, flags); + + out_le32(&awacs->codec_ctrl, addr + 0x100000); + awacs_burgundy_busy_wait(); + awacs_burgundy_extend_wait(); + val += (in_le32(&awacs->codec_stat) >> 4) & 0xff; + + spin_unlock_irqrestore(&dmasound.lock, flags); + + return val; +} + +static int +awacs_burgundy_check(void) +{ + /* Checks to see the chip is alive and kicking */ + int error = in_le32(&awacs->codec_ctrl) & MASK_ERRCODE; + + return error == 0xf0000; +} + +static int +awacs_burgundy_init(void) +{ + if (awacs_burgundy_check()) { + printk(KERN_WARNING "dmasound_pmac: burgundy not working :-(\n"); + return 1; + } + + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_OUTPUTENABLES, + DEF_BURGUNDY_OUTPUTENABLES); + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + DEF_BURGUNDY_MORE_OUTPUTENABLES); + awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_OUTPUTSELECTS, + DEF_BURGUNDY_OUTPUTSELECTS); + + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_INPSEL21, + DEF_BURGUNDY_INPSEL21); + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_INPSEL3, + DEF_BURGUNDY_INPSEL3); + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINCD, + DEF_BURGUNDY_GAINCD); + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINLINE, + DEF_BURGUNDY_GAINLINE); + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINMIC, + DEF_BURGUNDY_GAINMIC); + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINMODEM, + DEF_BURGUNDY_GAINMODEM); + + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER, + DEF_BURGUNDY_ATTENSPEAKER); + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENLINEOUT, + DEF_BURGUNDY_ATTENLINEOUT); + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENHP, + DEF_BURGUNDY_ATTENHP); + + awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_MASTER_VOLUME, + DEF_BURGUNDY_MASTER_VOLUME); + awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLCD, + DEF_BURGUNDY_VOLCD); + awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLLINE, + DEF_BURGUNDY_VOLLINE); + awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLMIC, + DEF_BURGUNDY_VOLMIC); + return 0; +} + +static void +awacs_burgundy_write_volume(unsigned address, int volume) +{ + int hardvolume,lvolume,rvolume; + + lvolume = (volume & 0xff) ? (volume & 0xff) + 155 : 0; + rvolume = ((volume >>8)&0xff) ? ((volume >> 8)&0xff ) + 155 : 0; + + hardvolume = lvolume + (rvolume << 16); + + awacs_burgundy_wcw(address, hardvolume); +} + +static int +awacs_burgundy_read_volume(unsigned address) +{ + int softvolume,wvolume; + + wvolume = awacs_burgundy_rcw(address); + + softvolume = (wvolume & 0xff) - 155; + softvolume += (((wvolume >> 16) & 0xff) - 155)<<8; + + return softvolume > 0 ? softvolume : 0; +} + +static int +awacs_burgundy_read_mvolume(unsigned address) +{ + int lvolume,rvolume,wvolume; + + wvolume = awacs_burgundy_rcw(address); + + wvolume &= 0xffff; + + rvolume = (wvolume & 0xff) - 155; + lvolume = ((wvolume & 0xff00)>>8) - 155; + + return lvolume + (rvolume << 8); +} + +static void +awacs_burgundy_write_mvolume(unsigned address, int volume) +{ + int lvolume,rvolume,hardvolume; + + lvolume = (volume &0xff) ? (volume & 0xff) + 155 :0; + rvolume = ((volume >>8) & 0xff) ? (volume >> 8) + 155 :0; + + hardvolume = lvolume + (rvolume << 8); + hardvolume += (hardvolume << 16); + + awacs_burgundy_wcw(address, hardvolume); +} + +/* End burgundy functions */ + +/* Set up output volumes on machines with the 'perch/whisper' extension card. + * this has an SGS i2c chip (7433) which is accessed using the cuda. + * + * TODO: split this out and make use of the other parts of the SGS chip to + * do Bass, Treble etc. + */ + +static void +awacs_enable_amp(int spkr_vol) +{ +#ifdef CONFIG_ADB_CUDA + struct adb_request req; + + if (sys_ctrler != SYS_CTRLER_CUDA) + return; + + /* turn on headphones */ + cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, + 0x8a, 4, 0); + while (!req.complete) cuda_poll(); + cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, + 0x8a, 6, 0); + while (!req.complete) cuda_poll(); + + /* turn on speaker */ + cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, + 0x8a, 3, (100 - (spkr_vol & 0xff)) * 32 / 100); + while (!req.complete) cuda_poll(); + cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, + 0x8a, 5, (100 - ((spkr_vol >> 8) & 0xff)) * 32 / 100); + while (!req.complete) cuda_poll(); + + cuda_request(&req, NULL, 5, CUDA_PACKET, + CUDA_GET_SET_IIC, 0x8a, 1, 0x29); + while (!req.complete) cuda_poll(); +#endif /* CONFIG_ADB_CUDA */ +} + + +/*** Mid level stuff *********************************************************/ + + +/* + * /dev/mixer abstraction + */ + +static void do_line_lev(int data) +{ + line_lev = data ; + awacs_reg[0] &= ~MASK_MUX_AUDIN; + if ((data & 0xff) >= 50) + awacs_reg[0] |= MASK_MUX_AUDIN; + awacs_write(MASK_ADDR0 | awacs_reg[0]); +} + +static void do_ip_gain(int data) +{ + ip_gain = data ; + data &= 0xff; + awacs_reg[0] &= ~MASK_GAINLINE; + if (awacs_revision == AWACS_SCREAMER) { + awacs_reg[6] &= ~MASK_MIC_BOOST ; + if (data >= 33) { + awacs_reg[0] |= MASK_GAINLINE; + if( data >= 66) + awacs_reg[6] |= MASK_MIC_BOOST ; + } + awacs_write(MASK_ADDR6 | awacs_reg[6]) ; + } else { + if (data >= 50) + awacs_reg[0] |= MASK_GAINLINE; + } + awacs_write(MASK_ADDR0 | awacs_reg[0]); +} + +static void do_mic_lev(int data) +{ + mic_lev = data ; + data &= 0xff; + awacs_reg[0] &= ~MASK_MUX_MIC; + if (data >= 50) + awacs_reg[0] |= MASK_MUX_MIC; + awacs_write(MASK_ADDR0 | awacs_reg[0]); +} + +static void do_cd_lev(int data) +{ + cd_lev = data ; + awacs_reg[0] &= ~MASK_MUX_CD; + if ((data & 0xff) >= 50) + awacs_reg[0] |= MASK_MUX_CD; + awacs_write(MASK_ADDR0 | awacs_reg[0]); +} + +static void do_rec_lev(int data) +{ + int left, right ; + rec_lev = data ; + /* need to fudge this to use the volume setter routine */ + left = 100 - (data & 0xff) ; if( left < 0 ) left = 0 ; + right = 100 - ((data >> 8) & 0xff) ; if( right < 0 ) right = 0 ; + left |= (right << 8 ); + left = awacs_volume_setter(left, 0, 0, 4); +} + +static void do_passthru_vol(int data) +{ + passthru_vol = data ; + awacs_reg[1] &= ~MASK_LOOPTHRU; + if (awacs_revision == AWACS_SCREAMER) { + if( data ) { /* switch it on for non-zero */ + awacs_reg[1] |= MASK_LOOPTHRU; + awacs_write(MASK_ADDR1 | awacs_reg[1]); + } + data = awacs_volume_setter(data, 5, 0, 6) ; + } else { + if ((data & 0xff) >= 50) + awacs_reg[1] |= MASK_LOOPTHRU; + awacs_write(MASK_ADDR1 | awacs_reg[1]); + data = (awacs_reg[1] & MASK_LOOPTHRU)? 100: 0; + } +} + +static int awacs_mixer_ioctl(u_int cmd, u_long arg) +{ + int data; + int rc; + + switch (cmd) { + case SOUND_MIXER_READ_CAPS: + /* say we will allow multiple inputs? prob. wrong + so I'm switching it to single */ + return IOCTL_OUT(arg, 1); + case SOUND_MIXER_READ_DEVMASK: + data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER + | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD + | SOUND_MASK_IGAIN | SOUND_MASK_RECLEV + | SOUND_MASK_ALTPCM + | SOUND_MASK_MONITOR; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_RECMASK: + data = SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_RECSRC: + data = 0; + if (awacs_reg[0] & MASK_MUX_AUDIN) + data |= SOUND_MASK_LINE; + if (awacs_reg[0] & MASK_MUX_MIC) + data |= SOUND_MASK_MIC; + if (awacs_reg[0] & MASK_MUX_CD) + data |= SOUND_MASK_CD; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_WRITE_RECSRC: + IOCTL_IN(arg, data); + data &= (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD); + awacs_reg[0] &= ~(MASK_MUX_CD | MASK_MUX_MIC + | MASK_MUX_AUDIN); + if (data & SOUND_MASK_LINE) + awacs_reg[0] |= MASK_MUX_AUDIN; + if (data & SOUND_MASK_MIC) + awacs_reg[0] |= MASK_MUX_MIC; + if (data & SOUND_MASK_CD) + awacs_reg[0] |= MASK_MUX_CD; + awacs_write(awacs_reg[0] | MASK_ADDR0); + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_STEREODEVS: + data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER| SOUND_MASK_RECLEV ; + if (awacs_revision == AWACS_SCREAMER) + data |= SOUND_MASK_MONITOR ; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_WRITE_VOLUME: + IOCTL_IN(arg, data); + line_vol = data ; + awacs_volume_setter(data, 2, 0, 6); + /* fall through */ + case SOUND_MIXER_READ_VOLUME: + rc = IOCTL_OUT(arg, line_vol); + break; + case SOUND_MIXER_WRITE_SPEAKER: + IOCTL_IN(arg, data); + spk_vol = data ; + if (has_perch) + awacs_enable_amp(data); + else + (void)awacs_volume_setter(data, 4, MASK_CMUTE, 6); + /* fall though */ + case SOUND_MIXER_READ_SPEAKER: + rc = IOCTL_OUT(arg, spk_vol); + break; + case SOUND_MIXER_WRITE_ALTPCM: /* really bell volume */ + IOCTL_IN(arg, data); + beep_vol = data & 0xff; + /* fall through */ + case SOUND_MIXER_READ_ALTPCM: + rc = IOCTL_OUT(arg, beep_vol); + break; + case SOUND_MIXER_WRITE_LINE: + IOCTL_IN(arg, data); + do_line_lev(data) ; + /* fall through */ + case SOUND_MIXER_READ_LINE: + rc = IOCTL_OUT(arg, line_lev); + break; + case SOUND_MIXER_WRITE_IGAIN: + IOCTL_IN(arg, data); + do_ip_gain(data) ; + /* fall through */ + case SOUND_MIXER_READ_IGAIN: + rc = IOCTL_OUT(arg, ip_gain); + break; + case SOUND_MIXER_WRITE_MIC: + IOCTL_IN(arg, data); + do_mic_lev(data); + /* fall through */ + case SOUND_MIXER_READ_MIC: + rc = IOCTL_OUT(arg, mic_lev); + break; + case SOUND_MIXER_WRITE_CD: + IOCTL_IN(arg, data); + do_cd_lev(data); + /* fall through */ + case SOUND_MIXER_READ_CD: + rc = IOCTL_OUT(arg, cd_lev); + break; + case SOUND_MIXER_WRITE_RECLEV: + IOCTL_IN(arg, data); + do_rec_lev(data) ; + /* fall through */ + case SOUND_MIXER_READ_RECLEV: + rc = IOCTL_OUT(arg, rec_lev); + break; + case MIXER_WRITE(SOUND_MIXER_MONITOR): + IOCTL_IN(arg, data); + do_passthru_vol(data) ; + /* fall through */ + case MIXER_READ(SOUND_MIXER_MONITOR): + rc = IOCTL_OUT(arg, passthru_vol); + break; + default: + rc = -EINVAL; + } + + return rc; +} + +static void awacs_mixer_init(void) +{ + awacs_volume_setter(line_vol, 2, 0, 6); + if (has_perch) + awacs_enable_amp(spk_vol); + else + (void)awacs_volume_setter(spk_vol, 4, MASK_CMUTE, 6); + do_line_lev(line_lev) ; + do_ip_gain(ip_gain) ; + do_mic_lev(mic_lev) ; + do_cd_lev(cd_lev) ; + do_rec_lev(rec_lev) ; + do_passthru_vol(passthru_vol) ; +} + +static int burgundy_mixer_ioctl(u_int cmd, u_long arg) +{ + int data; + int rc; + + /* We are, we are, we are... Burgundy or better */ + switch(cmd) { + case SOUND_MIXER_READ_DEVMASK: + data = SOUND_MASK_VOLUME | SOUND_MASK_CD | + SOUND_MASK_LINE | SOUND_MASK_MIC | + SOUND_MASK_SPEAKER | SOUND_MASK_ALTPCM; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_RECMASK: + data = SOUND_MASK_LINE | SOUND_MASK_MIC + | SOUND_MASK_CD; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_RECSRC: + data = 0; + if (awacs_reg[0] & MASK_MUX_AUDIN) + data |= SOUND_MASK_LINE; + if (awacs_reg[0] & MASK_MUX_MIC) + data |= SOUND_MASK_MIC; + if (awacs_reg[0] & MASK_MUX_CD) + data |= SOUND_MASK_CD; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_WRITE_RECSRC: + IOCTL_IN(arg, data); + data &= (SOUND_MASK_LINE + | SOUND_MASK_MIC | SOUND_MASK_CD); + awacs_reg[0] &= ~(MASK_MUX_CD | MASK_MUX_MIC + | MASK_MUX_AUDIN); + if (data & SOUND_MASK_LINE) + awacs_reg[0] |= MASK_MUX_AUDIN; + if (data & SOUND_MASK_MIC) + awacs_reg[0] |= MASK_MUX_MIC; + if (data & SOUND_MASK_CD) + awacs_reg[0] |= MASK_MUX_CD; + awacs_write(awacs_reg[0] | MASK_ADDR0); + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_STEREODEVS: + data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER + | SOUND_MASK_RECLEV | SOUND_MASK_CD + | SOUND_MASK_LINE; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_CAPS: + rc = IOCTL_OUT(arg, 0); + break; + case SOUND_MIXER_WRITE_VOLUME: + IOCTL_IN(arg, data); + awacs_burgundy_write_mvolume(MASK_ADDR_BURGUNDY_MASTER_VOLUME, data); + /* Fall through */ + case SOUND_MIXER_READ_VOLUME: + rc = IOCTL_OUT(arg, awacs_burgundy_read_mvolume(MASK_ADDR_BURGUNDY_MASTER_VOLUME)); + break; + case SOUND_MIXER_WRITE_SPEAKER: + IOCTL_IN(arg, data); + if (!(data & 0xff)) { + /* Mute the left speaker */ + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) & ~0x2); + } else { + /* Unmute the left speaker */ + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) | 0x2); + } + if (!(data & 0xff00)) { + /* Mute the right speaker */ + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) & ~0x4); + } else { + /* Unmute the right speaker */ + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) | 0x4); + } + + data = (((data&0xff)*16)/100 > 0xf ? 0xf : + (((data&0xff)*16)/100)) + + ((((data>>8)*16)/100 > 0xf ? 0xf : + ((((data>>8)*16)/100)))<<4); + + awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER, ~data); + /* Fall through */ + case SOUND_MIXER_READ_SPEAKER: + data = awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER); + data = (((data & 0xf)*100)/16) + ((((data>>4)*100)/16)<<8); + rc = IOCTL_OUT(arg, (~data) & 0x0000ffff); + break; + case SOUND_MIXER_WRITE_ALTPCM: /* really bell volume */ + IOCTL_IN(arg, data); + beep_vol = data & 0xff; + /* fall through */ + case SOUND_MIXER_READ_ALTPCM: + rc = IOCTL_OUT(arg, beep_vol); + break; + case SOUND_MIXER_WRITE_LINE: + IOCTL_IN(arg, data); + awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLLINE, data); + + /* fall through */ + case SOUND_MIXER_READ_LINE: + data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLLINE); + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_WRITE_MIC: + IOCTL_IN(arg, data); + /* Mic is mono device */ + data = (data << 8) + (data << 24); + awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLMIC, data); + /* fall through */ + case SOUND_MIXER_READ_MIC: + data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLMIC); + data <<= 24; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_WRITE_CD: + IOCTL_IN(arg, data); + awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLCD, data); + /* fall through */ + case SOUND_MIXER_READ_CD: + data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLCD); + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_WRITE_RECLEV: + IOCTL_IN(arg, data); + data = awacs_volume_setter(data, 0, 0, 4); + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_RECLEV: + data = awacs_get_volume(awacs_reg[0], 4); + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_OUTMASK: + case SOUND_MIXER_OUTSRC: + default: + rc = -EINVAL; + } + + return rc; +} + +static int daca_mixer_ioctl(u_int cmd, u_long arg) +{ + int data; + int rc; + + /* And the DACA's no genius either! */ + + switch(cmd) { + case SOUND_MIXER_READ_DEVMASK: + data = SOUND_MASK_VOLUME; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_RECMASK: + data = 0; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_RECSRC: + data = 0; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_WRITE_RECSRC: + IOCTL_IN(arg, data); + data =0; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_STEREODEVS: + data = SOUND_MASK_VOLUME; + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_READ_CAPS: + rc = IOCTL_OUT(arg, 0); + break; + case SOUND_MIXER_WRITE_VOLUME: + IOCTL_IN(arg, data); + daca_set_volume(data, data); + /* Fall through */ + case SOUND_MIXER_READ_VOLUME: + daca_get_volume(& data, &data); + rc = IOCTL_OUT(arg, data); + break; + case SOUND_MIXER_OUTMASK: + case SOUND_MIXER_OUTSRC: + default: + rc = -EINVAL; + } + return rc; +} + +static int PMacMixerIoctl(u_int cmd, u_long arg) +{ + int rc; + + /* Different IOCTLS for burgundy and, eventually, DACA & Tumbler */ + + TRY_LOCK(); + + switch (awacs_revision){ + case AWACS_BURGUNDY: + rc = burgundy_mixer_ioctl(cmd, arg); + break ; + case AWACS_DACA: + rc = daca_mixer_ioctl(cmd, arg); + break; + case AWACS_TUMBLER: + case AWACS_SNAPPER: + rc = tas_mixer_ioctl(cmd, arg); + break ; + default: /* ;-)) */ + rc = awacs_mixer_ioctl(cmd, arg); + } + + UNLOCK(); + + return rc; +} + +static void PMacMixerInit(void) +{ + switch (awacs_revision) { + case AWACS_TUMBLER: + printk("AE-Init tumbler mixer\n"); + break ; + case AWACS_SNAPPER: + printk("AE-Init snapper mixer\n"); + break ; + case AWACS_DACA: + case AWACS_BURGUNDY: + break ; /* don't know yet */ + case AWACS_AWACS: + case AWACS_SCREAMER: + default: + awacs_mixer_init() ; + break ; + } +} + +/* Write/Read sq setup functions: + Check to see if we have enough (or any) dbdma cmd buffers for the + user's fragment settings. If not, allocate some. If this fails we will + point at the beep buffer - as an emergency provision - to stop dma tromping + on some random bit of memory (if someone lets it go anyway). + The command buffers are then set up to point to the fragment buffers + (allocated elsewhere). We need n+1 commands the last of which holds + a NOP + loop to start. +*/ + +static int PMacWriteSqSetup(void) +{ + int i, count = 600 ; + volatile struct dbdma_cmd *cp; + + LOCK(); + + /* stop the controller from doing any output - if it isn't already. + it _should_ be before this is called anyway */ + + out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); + while ((in_le32(&awacs_txdma->status) & RUN) && count--) + udelay(1); +#ifdef DEBUG_DMASOUND +if (count <= 0) + printk("dmasound_pmac: write sq setup: timeout waiting for dma to stop\n"); +#endif + + if ((write_sq.max_count + 1) > number_of_tx_cmd_buffers) { + if (awacs_tx_cmd_space) + kfree(awacs_tx_cmd_space); + number_of_tx_cmd_buffers = 0; + + /* we need nbufs + 1 (for the loop) and we should request + 1 + again because the DBDMA_ALIGN might pull the start up by up + to sizeof(struct dbdma_cmd) - 4. + */ + + awacs_tx_cmd_space = kmalloc + ((write_sq.max_count + 1 + 1) * sizeof(struct dbdma_cmd), + GFP_KERNEL); + if (awacs_tx_cmd_space == NULL) { + /* don't leave it dangling - nasty but better than a + random address */ + out_le32(&awacs_txdma->cmdptr, virt_to_bus(beep_dbdma_cmd)); + printk(KERN_ERR + "dmasound_pmac: can't allocate dbdma cmd buffers" + ", driver disabled\n"); + UNLOCK(); + return -ENOMEM; + } + awacs_tx_cmds = (volatile struct dbdma_cmd *) + DBDMA_ALIGN(awacs_tx_cmd_space); + number_of_tx_cmd_buffers = write_sq.max_count + 1; + } + + cp = awacs_tx_cmds; + memset((void *)cp, 0, (write_sq.max_count+1) * sizeof(struct dbdma_cmd)); + for (i = 0; i < write_sq.max_count; ++i, ++cp) { + st_le32(&cp->phy_addr, virt_to_bus(write_sq.buffers[i])); + } + st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS); + st_le32(&cp->cmd_dep, virt_to_bus(awacs_tx_cmds)); + /* point the controller at the command stack - ready to go */ + out_le32(&awacs_txdma->cmdptr, virt_to_bus(awacs_tx_cmds)); + UNLOCK(); + return 0; +} + +static int PMacReadSqSetup(void) +{ + int i, count = 600; + volatile struct dbdma_cmd *cp; + + LOCK(); + + /* stop the controller from doing any input - if it isn't already. + it _should_ be before this is called anyway */ + + out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); + while ((in_le32(&awacs_rxdma->status) & RUN) && count--) + udelay(1); +#ifdef DEBUG_DMASOUND +if (count <= 0) + printk("dmasound_pmac: read sq setup: timeout waiting for dma to stop\n"); +#endif + + if ((read_sq.max_count+1) > number_of_rx_cmd_buffers ) { + if (awacs_rx_cmd_space) + kfree(awacs_rx_cmd_space); + number_of_rx_cmd_buffers = 0; + + /* we need nbufs + 1 (for the loop) and we should request + 1 again + because the DBDMA_ALIGN might pull the start up by up to + sizeof(struct dbdma_cmd) - 4 (assuming kmalloc aligns 32 bits). + */ + + awacs_rx_cmd_space = kmalloc + ((read_sq.max_count + 1 + 1) * sizeof(struct dbdma_cmd), + GFP_KERNEL); + if (awacs_rx_cmd_space == NULL) { + /* don't leave it dangling - nasty but better than a + random address */ + out_le32(&awacs_rxdma->cmdptr, virt_to_bus(beep_dbdma_cmd)); + printk(KERN_ERR + "dmasound_pmac: can't allocate dbdma cmd buffers" + ", driver disabled\n"); + UNLOCK(); + return -ENOMEM; + } + awacs_rx_cmds = (volatile struct dbdma_cmd *) + DBDMA_ALIGN(awacs_rx_cmd_space); + number_of_rx_cmd_buffers = read_sq.max_count + 1 ; + } + cp = awacs_rx_cmds; + memset((void *)cp, 0, (read_sq.max_count+1) * sizeof(struct dbdma_cmd)); + + /* Set dma buffers up in a loop */ + for (i = 0; i < read_sq.max_count; i++,cp++) { + st_le32(&cp->phy_addr, virt_to_bus(read_sq.buffers[i])); + st_le16(&cp->command, INPUT_MORE + INTR_ALWAYS); + st_le16(&cp->req_count, read_sq.block_size); + st_le16(&cp->xfer_status, 0); + } + + /* The next two lines make the thing loop around. + */ + st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS); + st_le32(&cp->cmd_dep, virt_to_bus(awacs_rx_cmds)); + /* point the controller at the command stack - ready to go */ + out_le32(&awacs_rxdma->cmdptr, virt_to_bus(awacs_rx_cmds)); + + UNLOCK(); + return 0; +} + +/* TODO: this needs work to guarantee that when it returns DMA has stopped + but in a more elegant way than is done here.... +*/ + +static void PMacAbortRead(void) +{ + int i; + volatile struct dbdma_cmd *cp; + + LOCK(); + /* give it a chance to update the output and provide the IRQ + that is expected. + */ + + out_le32(&awacs_rxdma->control, ((FLUSH) << 16) + FLUSH ); + + cp = awacs_rx_cmds; + for (i = 0; i < read_sq.max_count; i++,cp++) + st_le16(&cp->command, DBDMA_STOP); + /* + * We should probably wait for the thing to stop before we + * release the memory. + */ + + msleep(100) ; /* give it a (small) chance to act */ + + /* apply the sledgehammer approach - just stop it now */ + + out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); + UNLOCK(); +} + +extern char *get_afmt_string(int); +static int PMacStateInfo(char *b, size_t sp) +{ + int i, len = 0; + len = sprintf(b,"HW rates: "); + switch (awacs_revision){ + case AWACS_DACA: + case AWACS_BURGUNDY: + len += sprintf(b,"44100 ") ; + break ; + case AWACS_TUMBLER: + case AWACS_SNAPPER: + for (i=0; i<1; i++){ + if (tas_freqs_ok[i]) + len += sprintf(b+len,"%d ", tas_freqs[i]) ; + } + break ; + case AWACS_AWACS: + case AWACS_SCREAMER: + default: + for (i=0; i<8; i++){ + if (awacs_freqs_ok[i]) + len += sprintf(b+len,"%d ", awacs_freqs[i]) ; + } + break ; + } + len += sprintf(b+len,"s/sec\n") ; + if (len < sp) { + len += sprintf(b+len,"HW AFMTS: "); + i = AFMT_U16_BE ; + while (i) { + if (i & dmasound.mach.hardware_afmts) + len += sprintf(b+len,"%s ", + get_afmt_string(i & dmasound.mach.hardware_afmts)); + i >>= 1 ; + } + len += sprintf(b+len,"\n") ; + } + return len ; +} + +/*** Machine definitions *****************************************************/ + +static SETTINGS def_hard = { + .format = AFMT_S16_BE, + .stereo = 1, + .size = 16, + .speed = 44100 +} ; + +static SETTINGS def_soft = { + .format = AFMT_S16_BE, + .stereo = 1, + .size = 16, + .speed = 44100 +} ; + +static MACHINE machPMac = { + .name = awacs_name, + .name2 = "PowerMac Built-in Sound", + .owner = THIS_MODULE, + .dma_alloc = PMacAlloc, + .dma_free = PMacFree, + .irqinit = PMacIrqInit, +#ifdef MODULE + .irqcleanup = PMacIrqCleanup, +#endif /* MODULE */ + .init = PMacInit, + .silence = PMacSilence, + .setFormat = PMacSetFormat, + .setVolume = PMacSetVolume, + .play = PMacPlay, + .record = NULL, /* default to no record */ + .mixer_init = PMacMixerInit, + .mixer_ioctl = PMacMixerIoctl, + .write_sq_setup = PMacWriteSqSetup, + .read_sq_setup = PMacReadSqSetup, + .state_info = PMacStateInfo, + .abort_read = PMacAbortRead, + .min_dsp_speed = 7350, + .max_dsp_speed = 44100, + .version = ((DMASOUND_AWACS_REVISION<<8) + DMASOUND_AWACS_EDITION) +}; + + +/*** Config & Setup **********************************************************/ + +/* Check for pmac models that we care about in terms of special actions. +*/ + +void __init +set_model(void) +{ + /* portables/lap-tops */ + + if (machine_is_compatible("AAPL,3400/2400") || + machine_is_compatible("AAPL,3500")) { + is_pbook_3X00 = 1 ; + } + if (machine_is_compatible("PowerBook1,1") || /* lombard */ + machine_is_compatible("AAPL,PowerBook1998")){ /* wallstreet */ + is_pbook_g3 = 1 ; + return ; + } +} + +/* Get the OF node that tells us about the registers, interrupts etc. to use + for sound IO. + + On most machines the sound IO OF node is the 'davbus' node. On newer pmacs + with DACA (& Tumbler) the node to use is i2s-a. On much older machines i.e. + before 9500 there is no davbus node and we have to use the 'awacs' property. + + In the latter case we signal this by setting the codec value - so that the + code that looks for chip properties knows how to go about it. +*/ + +static struct device_node* __init +get_snd_io_node(void) +{ + struct device_node *np = NULL; + + /* set up awacs_node for early OF which doesn't have a full set of + * properties on davbus + */ + + awacs_node = find_devices("awacs"); + if (awacs_node) + awacs_revision = AWACS_AWACS; + + /* powermac models after 9500 (other than those which use DACA or + * Tumbler) have a node called "davbus". + */ + np = find_devices("davbus"); + /* + * if we didn't find a davbus device, try 'i2s-a' since + * this seems to be what iBooks (& Tumbler) have. + */ + if (np == NULL) + np = i2s_node = find_devices("i2s-a"); + + /* if we didn't find this - perhaps we are on an early model + * which _only_ has an 'awacs' node + */ + if (np == NULL && awacs_node) + np = awacs_node ; + + /* if we failed all these return null - this will cause the + * driver to give up... + */ + return np ; +} + +/* Get the OF node that contains the info about the sound chip, inputs s-rates + etc. + This node does not exist (or contains much reduced info) on earlier machines + we have to deduce the info other ways for these. +*/ + +static struct device_node* __init +get_snd_info_node(struct device_node *io) +{ + struct device_node *info; + + info = find_devices("sound"); + while (info && info->parent != io) + info = info->next; + return info; +} + +/* Find out what type of codec we have. +*/ + +static int __init +get_codec_type(struct device_node *info) +{ + /* already set if pre-davbus model and info will be NULL */ + int codec = awacs_revision ; + + if (info) { + /* must do awacs first to allow screamer to overide it */ + if (device_is_compatible(info, "awacs")) + codec = AWACS_AWACS ; + if (device_is_compatible(info, "screamer")) + codec = AWACS_SCREAMER; + if (device_is_compatible(info, "burgundy")) + codec = AWACS_BURGUNDY ; + if (device_is_compatible(info, "daca")) + codec = AWACS_DACA; + if (device_is_compatible(info, "tumbler")) + codec = AWACS_TUMBLER; + if (device_is_compatible(info, "snapper")) + codec = AWACS_SNAPPER; + } + return codec ; +} + +/* find out what type, if any, of expansion card we have +*/ +static void __init +get_expansion_type(void) +{ + if (find_devices("perch") != NULL) + has_perch = 1; + + if (find_devices("pb-ziva-pc") != NULL) + has_ziva = 1; + /* need to work out how we deal with iMac SRS module */ +} + +/* set up frame rates. + * I suspect that these routines don't quite go about it the right way: + * - where there is more than one rate - I think that the first property + * value is the number of rates. + * TODO: check some more device trees and modify accordingly + * Set dmasound.mach.max_dsp_rate on the basis of these routines. +*/ + +static void __init +awacs_init_frame_rates(unsigned int *prop, unsigned int l) +{ + int i ; + if (prop) { + for (i=0; i<8; i++) + awacs_freqs_ok[i] = 0 ; + for (l /= sizeof(int); l > 0; --l) { + unsigned int r = *prop++; + /* Apple 'Fixed' format */ + if (r >= 0x10000) + r >>= 16; + for (i = 0; i < 8; ++i) { + if (r == awacs_freqs[i]) { + awacs_freqs_ok[i] = 1; + break; + } + } + } + } + /* else we assume that all the rates are available */ +} + +static void __init +burgundy_init_frame_rates(unsigned int *prop, unsigned int l) +{ + int temp[9] ; + int i = 0 ; + if (prop) { + for (l /= sizeof(int); l > 0; --l) { + unsigned int r = *prop++; + /* Apple 'Fixed' format */ + if (r >= 0x10000) + r >>= 16; + temp[i] = r ; + i++ ; if(i>=9) i=8; + } + } +#ifdef DEBUG_DMASOUND +if (i > 1){ + int j; + printk("dmasound_pmac: burgundy with multiple frame rates\n"); + for(j=0; j 0; --l) { + unsigned int r = *prop++; + /* Apple 'Fixed' format */ + if (r >= 0x10000) + r >>= 16; + temp[i] = r ; + i++ ; if(i>=9) i=8; + + } + } +#ifdef DEBUG_DMASOUND +if (i > 1){ + int j; + printk("dmasound_pmac: DACA with multiple frame rates\n"); + for(j=0; jparent; mio ; mio = mio->parent) { + if (strcmp(mio->name, "mac-io") == 0) { + if (device_is_compatible(mio, "Keylargo")) + kl = 1; + break; + } + } + hw_can_byteswap = !kl; +} + +/* Allocate the resources necessary for beep generation. This cannot be (quite) + done statically (yet) because we cannot do virt_to_bus() on static vars when + the code is loaded as a module. + + for the sake of saving the possibility that two allocations will incur the + overhead of two pull-ups in DBDMA_ALIGN() we allocate the 'emergency' dmdma + command here as well... even tho' it is not part of the beep process. +*/ + +int32_t +__init setup_beep(void) +{ + /* Initialize beep stuff */ + /* want one cmd buffer for beeps, and a second one for emergencies + - i.e. dbdma error conditions. + ask for three to allow for pull up in DBDMA_ALIGN(). + */ + beep_dbdma_cmd_space = + kmalloc((2 + 1) * sizeof(struct dbdma_cmd), GFP_KERNEL); + if(beep_dbdma_cmd_space == NULL) { + printk(KERN_ERR "dmasound_pmac: no beep dbdma cmd space\n") ; + return -ENOMEM ; + } + beep_dbdma_cmd = (volatile struct dbdma_cmd *) + DBDMA_ALIGN(beep_dbdma_cmd_space); + /* set up emergency dbdma cmd */ + emergency_dbdma_cmd = beep_dbdma_cmd+1 ; + beep_buf = (short *) kmalloc(BEEP_BUFLEN * 4, GFP_KERNEL); + if (beep_buf == NULL) { + printk(KERN_ERR "dmasound_pmac: no memory for beep buffer\n"); + if( beep_dbdma_cmd_space ) kfree(beep_dbdma_cmd_space) ; + return -ENOMEM ; + } + return 0 ; +} + +static struct input_dev awacs_beep_dev = { + .evbit = { BIT(EV_SND) }, + .sndbit = { BIT(SND_BELL) | BIT(SND_TONE) }, + .event = awacs_beep_event, + .name = "dmasound beeper", + .phys = "macio/input0", /* what the heck is this?? */ + .id = { + .bustype = BUS_HOST, + }, +}; + +int __init dmasound_awacs_init(void) +{ + struct device_node *io = NULL, *info = NULL; + int vol, res; + + if (_machine != _MACH_Pmac) + return -ENODEV; + + awacs_subframe = 0; + awacs_revision = 0; + hw_can_byteswap = 1 ; /* most can */ + + /* look for models we need to handle specially */ + set_model() ; + + /* find the OF node that tells us about the dbdma stuff + */ + io = get_snd_io_node(); + if (io == NULL) { +#ifdef DEBUG_DMASOUND +printk("dmasound_pmac: couldn't find sound io OF node\n"); +#endif + return -ENODEV ; + } + + /* find the OF node that tells us about the sound sub-system + * this doesn't exist on pre-davbus machines (earlier than 9500) + */ + if (awacs_revision != AWACS_AWACS) { /* set for pre-davbus */ + info = get_snd_info_node(io) ; + if (info == NULL){ +#ifdef DEBUG_DMASOUND +printk("dmasound_pmac: couldn't find 'sound' OF node\n"); +#endif + return -ENODEV ; + } + } + + awacs_revision = get_codec_type(info) ; + if (awacs_revision == 0) { +#ifdef DEBUG_DMASOUND +printk("dmasound_pmac: couldn't find a Codec we can handle\n"); +#endif + return -ENODEV ; /* we don't know this type of h/w */ + } + + /* set up perch, ziva, SRS or whatever else we have as sound + * expansion. + */ + get_expansion_type(); + + /* we've now got enough information to make up the audio topology. + * we will map the sound part of mac-io now so that we can probe for + * other info if necessary (early AWACS we want to read chip ids) + */ + + if (io->n_addrs < 3 || io->n_intrs < 3) { + /* OK - maybe we need to use the 'awacs' node (on earlier + * machines). + */ + if (awacs_node) { + io = awacs_node ; + if (io->n_addrs < 3 || io->n_intrs < 3) { + printk("dmasound_pmac: can't use %s" + " (%d addrs, %d intrs)\n", + io->full_name, io->n_addrs, io->n_intrs); + return -ENODEV; + } + } else { + printk("dmasound_pmac: can't use %s (%d addrs, %d intrs)\n", + io->full_name, io->n_addrs, io->n_intrs); + } + } + + if (!request_OF_resource(io, 0, NULL)) { + printk(KERN_ERR "dmasound: can't request IO resource !\n"); + return -ENODEV; + } + if (!request_OF_resource(io, 1, " (tx dma)")) { + release_OF_resource(io, 0); + printk(KERN_ERR "dmasound: can't request TX DMA resource !\n"); + return -ENODEV; + } + + if (!request_OF_resource(io, 2, " (rx dma)")) { + release_OF_resource(io, 0); + release_OF_resource(io, 1); + printk(KERN_ERR "dmasound: can't request RX DMA resource !\n"); + return -ENODEV; + } + + /* all OF versions I've seen use this value */ + if (i2s_node) + i2s = ioremap(io->addrs[0].address, 0x1000); + else + awacs = ioremap(io->addrs[0].address, 0x1000); + awacs_txdma = ioremap(io->addrs[1].address, 0x100); + awacs_rxdma = ioremap(io->addrs[2].address, 0x100); + + /* first of all make sure that the chip is powered up....*/ + pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, io, 0, 1); + if (awacs_revision == AWACS_SCREAMER && awacs) + awacs_recalibrate(); + + awacs_irq = io->intrs[0].line; + awacs_tx_irq = io->intrs[1].line; + awacs_rx_irq = io->intrs[2].line; + + /* Hack for legacy crap that will be killed someday */ + awacs_node = io; + + /* if we have an awacs or screamer - probe the chip to make + * sure we have the right revision. + */ + + if (awacs_revision <= AWACS_SCREAMER){ + uint32_t temp, rev, mfg ; + /* find out the awacs revision from the chip */ + temp = in_le32(&awacs->codec_stat); + rev = (temp >> 12) & 0xf; + mfg = (temp >> 8) & 0xf; +#ifdef DEBUG_DMASOUND +printk("dmasound_pmac: Awacs/Screamer Codec Mfct: %d Rev %d\n", mfg, rev); +#endif + if (rev >= AWACS_SCREAMER) + awacs_revision = AWACS_SCREAMER ; + else + awacs_revision = rev ; + } + + dmasound.mach = machPMac; + + /* find out other bits & pieces from OF, these may be present + only on some models ... so be careful. + */ + + /* in the absence of a frame rates property we will use the defaults + */ + + if (info) { + unsigned int *prop, l; + + sound_device_id = 0; + /* device ID appears post g3 b&w */ + prop = (unsigned int *)get_property(info, "device-id", NULL); + if (prop != 0) + sound_device_id = *prop; + + /* look for a property saying what sample rates + are available */ + + prop = (unsigned int *)get_property(info, "sample-rates", &l); + if (prop == 0) + prop = (unsigned int *) get_property + (info, "output-frame-rates", &l); + + /* if it's there use it to set up frame rates */ + init_frame_rates(prop, l) ; + } + + if (awacs) + out_le32(&awacs->control, 0x11); /* set everything quiesent */ + + set_hw_byteswap(io) ; /* figure out if the h/w can do it */ + +#ifdef CONFIG_NVRAM + /* get default volume from nvram */ + vol = ((pmac_xpram_read( 8 ) & 7 ) << 1 ); +#else + vol = 0; +#endif + + /* set up tracking values */ + spk_vol = vol * 100 ; + spk_vol /= 7 ; /* get set value to a percentage */ + spk_vol |= (spk_vol << 8) ; /* equal left & right */ + line_vol = passthru_vol = spk_vol ; + + /* fill regs that are shared between AWACS & Burgundy */ + + awacs_reg[2] = vol + (vol << 6); + awacs_reg[4] = vol + (vol << 6); + awacs_reg[5] = vol + (vol << 6); /* screamer has loopthru vol control */ + awacs_reg[6] = 0; /* maybe should be vol << 3 for PCMCIA speaker */ + awacs_reg[7] = 0; + + awacs_reg[0] = MASK_MUX_CD; + awacs_reg[1] = MASK_LOOPTHRU; + + /* FIXME: Only machines with external SRS module need MASK_PAROUT */ + if (has_perch || sound_device_id == 0x5 + || /*sound_device_id == 0x8 ||*/ sound_device_id == 0xb) + awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1; + + switch (awacs_revision) { + case AWACS_TUMBLER: + tas_register_driver(&tas3001c_hooks); + tas_init(I2C_DRIVERID_TAS3001C, I2C_DRIVERNAME_TAS3001C); + tas_dmasound_init(); + tas_post_init(); + break ; + case AWACS_SNAPPER: + tas_register_driver(&tas3004_hooks); + tas_init(I2C_DRIVERID_TAS3004,I2C_DRIVERNAME_TAS3004); + tas_dmasound_init(); + tas_post_init(); + break; + case AWACS_DACA: + daca_init(); + break; + case AWACS_BURGUNDY: + awacs_burgundy_init(); + break ; + case AWACS_SCREAMER: + case AWACS_AWACS: + default: + load_awacs(); + break ; + } + + /* enable/set-up external modules - when we know how */ + + if (has_perch) + awacs_enable_amp(100 * 0x101); + + /* Reset dbdma channels */ + out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16); + while (in_le32(&awacs_txdma->status) & RUN) + udelay(1); + out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16); + while (in_le32(&awacs_rxdma->status) & RUN) + udelay(1); + + /* Initialize beep stuff */ + if ((res=setup_beep())) + return res ; + +#ifdef CONFIG_PMAC_PBOOK + pmu_register_sleep_notifier(&awacs_sleep_notifier); +#endif /* CONFIG_PMAC_PBOOK */ + + /* Powerbooks have odd ways of enabling inputs such as + an expansion-bay CD or sound from an internal modem + or a PC-card modem. */ + if (is_pbook_3X00) { + /* + * Enable CD and PC-card sound inputs. + * This is done by reading from address + * f301a000, + 0x10 to enable the expansion-bay + * CD sound input, + 0x80 to enable the PC-card + * sound input. The 0x100 enables the SCSI bus + * terminator power. + */ + latch_base = ioremap (0xf301a000, 0x1000); + in_8(latch_base + 0x190); + + } else if (is_pbook_g3) { + struct device_node* mio; + macio_base = NULL; + for (mio = io->parent; mio; mio = mio->parent) { + if (strcmp(mio->name, "mac-io") == 0 + && mio->n_addrs > 0) { + macio_base = ioremap(mio->addrs[0].address, 0x40); + break; + } + } + /* + * Enable CD sound input. + * The relevant bits for writing to this byte are 0x8f. + * I haven't found out what the 0x80 bit does. + * For the 0xf bits, writing 3 or 7 enables the CD + * input, any other value disables it. Values + * 1, 3, 5, 7 enable the microphone. Values 0, 2, + * 4, 6, 8 - f enable the input from the modem. + * -- paulus. + */ + if (macio_base) + out_8(macio_base + 0x37, 3); + } + + if (hw_can_byteswap) + dmasound.mach.hardware_afmts = (AFMT_S16_BE | AFMT_S16_LE) ; + else + dmasound.mach.hardware_afmts = AFMT_S16_BE ; + + /* shut out chips that do output only. + * may need to extend this to machines which have no inputs - even tho' + * they use screamer - IIRC one of the powerbooks is like this. + */ + + if (awacs_revision != AWACS_DACA) { + dmasound.mach.capabilities = DSP_CAP_DUPLEX ; + dmasound.mach.record = PMacRecord ; + } + + dmasound.mach.default_hard = def_hard ; + dmasound.mach.default_soft = def_soft ; + + switch (awacs_revision) { + case AWACS_BURGUNDY: + sprintf(awacs_name, "PowerMac Burgundy ") ; + break ; + case AWACS_DACA: + sprintf(awacs_name, "PowerMac DACA ") ; + break ; + case AWACS_TUMBLER: + sprintf(awacs_name, "PowerMac Tumbler ") ; + break ; + case AWACS_SNAPPER: + sprintf(awacs_name, "PowerMac Snapper ") ; + break ; + case AWACS_SCREAMER: + sprintf(awacs_name, "PowerMac Screamer ") ; + break ; + case AWACS_AWACS: + default: + sprintf(awacs_name, "PowerMac AWACS rev %d ", awacs_revision) ; + break ; + } + + /* + * XXX: we should handle errors here, but that would mean + * rewriting the whole init code. later.. + */ + input_register_device(&awacs_beep_dev); + + return dmasound_init(); +} + +static void __exit dmasound_awacs_cleanup(void) +{ + input_unregister_device(&awacs_beep_dev); + + switch (awacs_revision) { + case AWACS_TUMBLER: + case AWACS_SNAPPER: + tas_dmasound_cleanup(); + tas_cleanup(); + break ; + case AWACS_DACA: + daca_cleanup(); + break; + } + dmasound_deinit(); + +} + +MODULE_DESCRIPTION("PowerMac built-in audio driver."); +MODULE_LICENSE("GPL"); + +module_init(dmasound_awacs_init); +module_exit(dmasound_awacs_cleanup); diff --git a/sound/oss/dmasound/dmasound_core.c b/sound/oss/dmasound/dmasound_core.c new file mode 100644 index 000000000000..c9302a1e515b --- /dev/null +++ b/sound/oss/dmasound/dmasound_core.c @@ -0,0 +1,1829 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include + +#include "dmasound.h" + +#define DMASOUND_CORE_REVISION 1 +#define DMASOUND_CORE_EDITION 6 + + /* + * Declarations + */ + +int dmasound_catchRadius = 0; +MODULE_PARM(dmasound_catchRadius, "i"); + +static unsigned int numWriteBufs = DEFAULT_N_BUFFERS; +MODULE_PARM(numWriteBufs, "i"); +static unsigned int writeBufSize = DEFAULT_BUFF_SIZE ; /* in bytes */ +MODULE_PARM(writeBufSize, "i"); + +#ifdef HAS_RECORD +static unsigned int numReadBufs = DEFAULT_N_BUFFERS; +MODULE_PARM(numReadBufs, "i"); +static unsigned int readBufSize = DEFAULT_BUFF_SIZE; /* in bytes */ +MODULE_PARM(readBufSize, "i"); +#endif + +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 */ + +/* software implemented recording volume! */ +uint software_input_volume = SW_INPUT_VOLUME_SCALE * SW_INPUT_VOLUME_DEFAULT; +EXPORT_SYMBOL(software_input_volume); + +/* control over who can modify resources shared between play/record */ +static mode_t shared_resource_owner; +static int shared_resources_initialised; + + /* + * Mid level stuff + */ + +struct sound_settings dmasound = { .lock = SPIN_LOCK_UNLOCKED }; + +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) +{ + if (!try_module_get(dmasound.mach.owner)) + return -ENODEV; + mixer.busy = 1; + return 0; +} + +static int mixer_release(struct inode *inode, struct file *file) +{ + lock_kernel(); + mixer.busy = 0; + module_put(dmasound.mach.owner); + unlock_kernel(); + return 0; +} +static int mixer_ioctl(struct inode *inode, 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 struct file_operations mixer_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = mixer_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) ; +#ifdef HAS_RECORD +struct sound_queue dmasound_read_sq; +static void sq_reset_input(void) ; +#endif + +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; + } +#ifdef HAS_RECORD + else { + sq->rear = 0; + setup_func = dmasound.mach.read_sq_setup; + } +#endif + 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.open_mode & O_NONBLOCK) + 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); +#ifdef HAS_RECORD + if (file->f_mode & FMODE_READ) + poll_wait(file, &read_sq.action_queue, wait); + if (file->f_mode & FMODE_READ) + if (read_sq.block_size - read_sq.rear_size > 0) + mask |= POLLIN | POLLRDNORM; +#endif + 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; + +} + +#ifdef HAS_RECORD + /* + * Here is how the values are used for reading. + * The value 'active' simply indicates the DMA is running. This is done + * so the driver semantics are DMA starts when the first read is posted. + * The value 'front' indicates the buffer we should next send to the user. + * The value 'rear' indicates the buffer the DMA is currently filling. + * When 'front' == 'rear' the buffer "ring" is empty (we always have an + * empty available). The 'rear_size' is used to track partial offsets + * into the buffer we are currently returning to the user. + + * This level (> [1.5]) doesn't care what strategy the LL driver uses with + * DMA on over-run. It can leave it running (and keep active == 1) or it + * can kill it and set active == 0 in which case this routine will spot + * it and restart the DMA. + */ + +static ssize_t sq_read(struct file *file, char __user *dst, size_t uLeft, + loff_t *ppos) +{ + + ssize_t uRead, bLeft, bUsed, uUsed; + + if (uLeft == 0) + return 0; + + /* cater for the compatibility mode - record compiled in but no LL */ + if (dmasound.mach.record == NULL) + return -EINVAL ; + + /* see comment in sq_write() + */ + + if( shared_resources_initialised == 0) { + dmasound.mach.init() ; + shared_resources_initialised = 1 ; + } + + /* set up the sq if it is not already done. see comments in sq_write(). + */ + + if (read_sq.locked == 0) { + if ((uRead = sq_setup(&read_sq)) < 0) + return uRead ; + } + + uRead = 0; + + /* Move what the user requests, depending upon other options. + */ + while (uLeft > 0) { + + /* we happened to get behind and the LL driver killed DMA + then we should set it going again. This also sets it + going the first time through. + */ + if ( !read_sq.active ) + dmasound.mach.record(); + + /* When front == rear, the DMA is not done yet. + */ + while (read_sq.front == read_sq.rear) { + if (read_sq.open_mode & O_NONBLOCK) { + return uRead > 0 ? uRead : -EAGAIN; + } + SLEEP(read_sq.action_queue); + if (signal_pending(current)) + return uRead > 0 ? uRead : -EINTR; + } + + /* The amount we move is either what is left in the + * current buffer or what the user wants. + */ + bLeft = read_sq.block_size - read_sq.rear_size; + bUsed = read_sq.rear_size; + uUsed = sound_copy_translate(dmasound.trans_read, dst, uLeft, + read_sq.buffers[read_sq.front], + &bUsed, bLeft); + if (uUsed <= 0) + return uUsed; + dst += uUsed; + uRead += uUsed; + uLeft -= uUsed; + read_sq.rear_size += bUsed; + if (read_sq.rear_size >= read_sq.block_size) { + read_sq.rear_size = 0; + read_sq.front++; + if (read_sq.front >= read_sq.max_active) + read_sq.front = 0; + } + } + return uRead; +} +#endif /* HAS_RECORD */ + +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, + mode_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, mode_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->open_mode = file->f_mode; + } + 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 ) + +#ifdef HAS_RECORD +#define read_sq_init_waitqueue() sq_init_waitqueue(&read_sq) +#if 0 /* blocking open() */ +#define read_sq_wake_up(file) sq_wake_up(&read_sq, file, FMODE_READ) +#endif +#define read_sq_release_buffers() sq_release_buffers(&read_sq) +#define read_sq_open(file) \ + sq_open2(&read_sq, file, FMODE_READ, numReadBufs, readBufSize ) +#else +#define read_sq_init_waitqueue() do {} while (0) +#if 0 /* blocking open() */ +#define read_sq_wake_up(file) do {} while (0) +#endif +#define read_sq_release_buffers() do {} while (0) +#define sq_reset_input() do {} while (0) +#endif + +static int sq_open(struct inode *inode, struct file *file) +{ + int rc; + + if (!try_module_get(dmasound.mach.owner)) + return -ENODEV; + + rc = write_sq_open(file); /* checks the f_mode */ + if (rc) + goto out; +#ifdef HAS_RECORD + if (dmasound.mach.record) { + rc = read_sq_open(file); /* checks the f_mode */ + if (rc) + goto out; + } else { /* no record function installed; in compat mode */ + if (file->f_mode & FMODE_READ) { + /* TODO: if O_RDWR, release any resources grabbed by write part */ + rc = -ENXIO; + goto out; + } + } +#else /* !HAS_RECORD */ + 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; + } +#endif /* HAS_RECORD */ + + 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 + + return 0; + out: + module_put(dmasound.mach.owner); + 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 ; +} + +#ifdef HAS_RECORD + +static void sq_reset_input(void) +{ + if (dmasound.mach.record && read_sq.active) { + if (dmasound.mach.abort_read) { /* this routine must really be present */ + read_sq.syncing = 1 ; + /* this can use the read_sq.sync_queue to sleep if + necessary - it should not return until DMA + is really stopped - because we might deallocate + the buffers as the next action... + */ + dmasound.mach.abort_read() ; + } else { + printk(KERN_ERR + "dmasound_core: %s has no abort_read()!! all bets are off\n", + dmasound.mach.name) ; + } + } + read_sq.syncing = + read_sq.active = + read_sq.front = + read_sq.count = + read_sq.rear = 0 ; + + /* OK - we can unlock the parameters and fragment settings */ + read_sq.locked = 0 ; + read_sq.user_frags = 0 ; + read_sq.user_frag_size = 0 ; +} + +#endif + +static void sq_reset(void) +{ + sq_reset_output() ; + sq_reset_input() ; + /* 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; + + lock_kernel(); + +#ifdef HAS_RECORD + /* probably best to do the read side first - so that time taken to do it + overlaps with playing any remaining output samples. + */ + if (file->f_mode & FMODE_READ) { + sq_reset_input() ; /* make sure dma is stopped and all is quiet */ + read_sq_release_buffers(); + read_sq.busy = 0; + } +#endif + + if (file->f_mode & FMODE_WRITE) { + if (write_sq.busy) + rc = sq_fsync(file, file->f_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() */ + + unlock_kernel(); + + 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(mode_t md) +{ + if (shared_resource_owner) + return (shared_resource_owner & md ) ; + 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) +{ +#ifdef HAS_RECORD + if (dmasound.mach.record) + if (read_sq.locked) + return 0 ; +#endif + 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< 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 inode *inode, 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 ; +#ifdef HAS_RECORD + if (dmasound.mach.record && (file->f_mode & FMODE_READ)) { + if ( !read_sq.locked ) + sq_setup(&read_sq) ; /* set params */ + size = read_sq.user_frag_size ; + } +#endif + 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 imediately. + */ + result = 0 ; + if ((file->f_mode & FMODE_READ) && dmasound.mach.record) + sq_reset_input() ; + if (file->f_mode & FMODE_WRITE) { + result = sq_fsync(file, file->f_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; +#ifdef HAS_RECORD + if ((file->f_mode & FMODE_READ) && dmasound.mach.record) { + result = set_queue_frags(&read_sq, nbufs, size) ; + if (result) + return result ; + } +#endif + 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(inode, file, cmd, arg); + } + return -EINVAL; +} + +static struct file_operations sq_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sq_write, + .poll = sq_poll, + .ioctl = sq_ioctl, + .open = sq_open, + .release = sq_release, +#ifdef HAS_RECORD + .read = NULL /* default to no read for compat mode */ +#endif +}; + +static int sq_init(void) +{ +#ifndef MODULE + int sq_unit; +#endif + +#ifdef HAS_RECORD + if (dmasound.mach.record) + sq_fops.read = sq_read ; +#endif + sq_unit = register_sound_dsp(&sq_fops, -1); + if (sq_unit < 0) { + printk(KERN_ERR "dmasound_core: couldn't register fops\n") ; + return sq_unit ; + } + + write_sq_init_waitqueue(); + read_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. +*/ + +#ifdef HAS_RECORD +#define STAT_BUFF_LEN 1024 +#else +#define STAT_BUFF_LEN 768 +#endif + +/* this is how much space we will allow the low-level driver to use + in the stat buffer. Currently, 2 * (80 character line + ). + 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 */ + +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; + + if (state.busy) + return -EBUSY; + + if (!try_module_get(dmasound.mach.owner)) + return -ENODEV; + 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) ; +#ifdef HAS_RECORD + if (dmasound.mach.record) + len += sprintf(buffer+len,"%9s:%8d%6d\n", + "read", read_sq.numBufs, read_sq.bufSize) ; +#endif + 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 HAS_RECORD + if (dmasound.mach.record) + len += sprintf(buffer+len,"%9s:%7d%8d%7d%5d%5d%4d%7d%2d%2d%2d%2d%7d\n", + "read", read_sq.max_count, read_sq.block_size, + read_sq.max_active, read_sq.front, read_sq.rear, + read_sq.count, read_sq.rear_size, read_sq.active, + read_sq.busy, read_sq.syncing, read_sq.locked, read_sq.xruns) ; +#endif +#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; + return 0; +} + +static int state_release(struct inode *inode, struct file *file) +{ + lock_kernel(); + state.busy = 0; + module_put(dmasound.mach.owner); + unlock_kernel(); + 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 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) ; +#ifdef HAS_RECORD + if (dmasound.mach.record) + printk(KERN_INFO + "Read will use %4d fragments of %7d bytes as default\n", + numReadBufs, readBufSize) ; +#endif + + return 0; +} + +#ifdef MODULE + +void dmasound_deinit(void) +{ + if (irq_installed) { + sound_silence(); + dmasound.mach.irqcleanup(); + irq_installed = 0; + } + + write_sq_release_buffers(); + read_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]) { +#ifdef HAS_RECORD + case 5: + if ((ints[5] < 0) || (ints[5] > MAX_CATCH_RADIUS)) + printk("dmasound_setup: invalid catch radius, using default = %d\n", catchRadius); + else + catchRadius = ints[5]; + /* fall through */ + case 4: + if (ints[4] < MIN_BUFFERS) + printk("dmasound_setup: invalid number of read buffers, using default = %d\n", + numReadBufs); + else + numReadBufs = ints[4]; + /* fall through */ + case 3: + if ((size = ints[3]) < 256) /* check for small buffer specs */ + size <<= 10 ; + if (size < MIN_BUFSIZE || size > MAX_BUFSIZE) + printk("dmasound_setup: invalid read buffer size, using default = %d\n", readBufSize); + else + readBufSize = size; + /* fall through */ +#else + 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 */ +#endif + 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); +#ifdef HAS_RECORD +EXPORT_SYMBOL(dmasound_read_sq); +#endif +EXPORT_SYMBOL(dmasound_catchRadius); +#ifdef HAS_8BIT_TABLES +EXPORT_SYMBOL(dmasound_ulaw2dma8); +EXPORT_SYMBOL(dmasound_alaw2dma8); +#endif +EXPORT_SYMBOL(get_afmt_string) ; diff --git a/sound/oss/dmasound/dmasound_paula.c b/sound/oss/dmasound/dmasound_paula.c new file mode 100644 index 000000000000..558db5311e06 --- /dev/null +++ b/sound/oss/dmasound/dmasound_paula.c @@ -0,0 +1,743 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dmasound.h" + +#define DMASOUND_PAULA_REVISION 0 +#define DMASOUND_PAULA_EDITION 4 + + /* + * 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, int 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, struct pt_regs *fp); + +#ifdef CONFIG_HEARTBEAT + + /* + * Heartbeat interferes with sound since the 7 kHz low-pass filter and the + * power LED are controlled by the same line. + */ + +#ifdef CONFIG_APUS +#define mach_heartbeat ppc_md.heartbeat +#endif + +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 *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 *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 *userPtr, size_t userCount, \ + u_char frame[], ssize_t *frameUsed, \ + ssize_t frameLeft) \ +{ \ + 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, ((u_short *)userPtr)++)) \ + 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, ((u_short *)userPtr)++)) \ + return -EFAULT; \ + data = convsample(data); \ + *lefth++ = data>>8; \ + *leftl++ = (data>>2) & 0x3f; \ + if (get_user(data, ((u_short *)userPtr)++)) \ + 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, int 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, struct pt_regs *fp) +{ + 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: overlowed 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 **********************************************************/ + + +int __init dmasound_paula_init(void) +{ + int err; + + if (MACH_IS_AMIGA && AMIGAHW_PRESENT(AMI_AUDIO)) { + if (!request_mem_region(CUSTOM_PHYSADDR+0xa0, 0x40, + "dmasound [Paula]")) + return -EBUSY; + dmasound.mach = machAmiga; + dmasound.mach.default_hard = def_hard ; + dmasound.mach.default_soft = def_soft ; + err = dmasound_init(); + if (err) + release_mem_region(CUSTOM_PHYSADDR+0xa0, 0x40); + return err; + } else + return -ENODEV; +} + +static void __exit dmasound_paula_cleanup(void) +{ + dmasound_deinit(); + release_mem_region(CUSTOM_PHYSADDR+0xa0, 0x40); +} + +module_init(dmasound_paula_init); +module_exit(dmasound_paula_cleanup); +MODULE_LICENSE("GPL"); diff --git a/sound/oss/dmasound/dmasound_q40.c b/sound/oss/dmasound/dmasound_q40.c new file mode 100644 index 000000000000..92c25a0174db --- /dev/null +++ b/sound/oss/dmasound/dmasound_q40.c @@ -0,0 +1,634 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#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, int 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, struct pt_regs *fp); +static irqreturn_t Q40MonoInterrupt(int irq, void *dummy, struct pt_regs *fp); +static void Q40Interrupt(void); + + +/*** Mid level stuff *********************************************************/ + + + +/* userCount, frameUsed, frameLeft == byte counts */ +static ssize_t q40_ct_law(const u_char *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 *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 *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 *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 *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 *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 *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 *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 *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, int 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. */ + request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, + "DMA sound", Q40Interrupt); + + 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; + + /* 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) + request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, + "Q40 sound", Q40Interrupt); + else + request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0, + "Q40 sound", Q40Interrupt); + + 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, struct pt_regs *fp) +{ + 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, struct pt_regs *fp) +{ + 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 **********************************************************/ + + +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/dmasound/tas3001c.c b/sound/oss/dmasound/tas3001c.c new file mode 100644 index 000000000000..f227c9f688cc --- /dev/null +++ b/sound/oss/dmasound/tas3001c.c @@ -0,0 +1,850 @@ +/* + * Driver for the i2c/i2s based TA3004 sound chip used + * on some Apple hardware. Also known as "snapper". + * + * Tobias Sargeant + * Based upon, tas3001c.c by Christopher C. Chimelis : + * + * TODO: + * ----- + * * Enable control over input line 2 (is this connected?) + * * Implement sleep support (at least mute everything and + * * set gains to minimum during sleep) + * * Look into some of Darwin's tweaks regarding the mute + * * lines (delays & different behaviour on some HW) + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dmasound.h" +#include "tas_common.h" +#include "tas3001c.h" + +#include "tas_ioctl.h" + +#define TAS3001C_BIQUAD_FILTER_COUNT 6 +#define TAS3001C_BIQUAD_CHANNEL_COUNT 2 + +#define VOL_DEFAULT (100 * 4 / 5) +#define INPUT_DEFAULT (100 * 4 / 5) +#define BASS_DEFAULT (100 / 2) +#define TREBLE_DEFAULT (100 / 2) + +struct tas3001c_data_t { + struct tas_data_t super; + int device_id; + int output_id; + int speaker_id; + struct tas_drce_t drce_state; +}; + + +static const union tas_biquad_t +tas3001c_eq_unity={ + .buf = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } +}; + + +static inline unsigned char db_to_regval(short db) { + int r=0; + + r=(db+0x59a0) / 0x60; + + if (r < 0x91) return 0x91; + if (r > 0xef) return 0xef; + return r; +} + +static inline short quantize_db(short db) { + return db_to_regval(db) * 0x60 - 0x59a0; +} + + +static inline int +register_width(enum tas3001c_reg_t r) +{ + switch(r) { + case TAS3001C_REG_MCR: + case TAS3001C_REG_TREBLE: + case TAS3001C_REG_BASS: + return 1; + + case TAS3001C_REG_DRC: + return 2; + + case TAS3001C_REG_MIXER1: + case TAS3001C_REG_MIXER2: + return 3; + + case TAS3001C_REG_VOLUME: + return 6; + + case TAS3001C_REG_LEFT_BIQUAD0: + case TAS3001C_REG_LEFT_BIQUAD1: + case TAS3001C_REG_LEFT_BIQUAD2: + case TAS3001C_REG_LEFT_BIQUAD3: + case TAS3001C_REG_LEFT_BIQUAD4: + case TAS3001C_REG_LEFT_BIQUAD5: + case TAS3001C_REG_LEFT_BIQUAD6: + + case TAS3001C_REG_RIGHT_BIQUAD0: + case TAS3001C_REG_RIGHT_BIQUAD1: + case TAS3001C_REG_RIGHT_BIQUAD2: + case TAS3001C_REG_RIGHT_BIQUAD3: + case TAS3001C_REG_RIGHT_BIQUAD4: + case TAS3001C_REG_RIGHT_BIQUAD5: + case TAS3001C_REG_RIGHT_BIQUAD6: + return 15; + + default: + return 0; + } +} + +static int +tas3001c_write_register( struct tas3001c_data_t *self, + enum tas3001c_reg_t reg_num, + char *data, + uint write_mode) +{ + if (reg_num==TAS3001C_REG_MCR || + reg_num==TAS3001C_REG_BASS || + reg_num==TAS3001C_REG_TREBLE) { + return tas_write_byte_register(&self->super, + (uint)reg_num, + *data, + write_mode); + } else { + return tas_write_register(&self->super, + (uint)reg_num, + register_width(reg_num), + data, + write_mode); + } +} + +static int +tas3001c_sync_register( struct tas3001c_data_t *self, + enum tas3001c_reg_t reg_num) +{ + if (reg_num==TAS3001C_REG_MCR || + reg_num==TAS3001C_REG_BASS || + reg_num==TAS3001C_REG_TREBLE) { + return tas_sync_byte_register(&self->super, + (uint)reg_num, + register_width(reg_num)); + } else { + return tas_sync_register(&self->super, + (uint)reg_num, + register_width(reg_num)); + } +} + +static int +tas3001c_read_register( struct tas3001c_data_t *self, + enum tas3001c_reg_t reg_num, + char *data, + uint write_mode) +{ + return tas_read_register(&self->super, + (uint)reg_num, + register_width(reg_num), + data); +} + +static inline int +tas3001c_fast_load(struct tas3001c_data_t *self, int fast) +{ + if (fast) + self->super.shadow[TAS3001C_REG_MCR][0] |= 0x80; + else + self->super.shadow[TAS3001C_REG_MCR][0] &= 0x7f; + return tas3001c_sync_register(self,TAS3001C_REG_MCR); +} + +static uint +tas3001c_supported_mixers(struct tas3001c_data_t *self) +{ + return SOUND_MASK_VOLUME | + SOUND_MASK_PCM | + SOUND_MASK_ALTPCM | + SOUND_MASK_TREBLE | + SOUND_MASK_BASS; +} + +static int +tas3001c_mixer_is_stereo(struct tas3001c_data_t *self,int mixer) +{ + switch(mixer) { + case SOUND_MIXER_VOLUME: + return 1; + default: + return 0; + } +} + +static uint +tas3001c_stereo_mixers(struct tas3001c_data_t *self) +{ + uint r=tas3001c_supported_mixers(self); + uint i; + + for (i=1; isuper.mixer[mixer]; + + return 0; +} + +static int +tas3001c_set_mixer_level(struct tas3001c_data_t *self,int mixer,uint level) +{ + int rc; + tas_shadow_t *shadow; + + uint temp; + uint offset=0; + + if (!self) + return -1; + + shadow=self->super.shadow; + + if (!tas3001c_mixer_is_stereo(self,mixer)) + level = tas_mono_to_stereo(level); + + switch(mixer) { + case SOUND_MIXER_VOLUME: + temp = tas3001c_gain.master[level&0xff]; + shadow[TAS3001C_REG_VOLUME][0] = (temp >> 16) & 0xff; + shadow[TAS3001C_REG_VOLUME][1] = (temp >> 8) & 0xff; + shadow[TAS3001C_REG_VOLUME][2] = (temp >> 0) & 0xff; + temp = tas3001c_gain.master[(level>>8)&0xff]; + shadow[TAS3001C_REG_VOLUME][3] = (temp >> 16) & 0xff; + shadow[TAS3001C_REG_VOLUME][4] = (temp >> 8) & 0xff; + shadow[TAS3001C_REG_VOLUME][5] = (temp >> 0) & 0xff; + rc = tas3001c_sync_register(self,TAS3001C_REG_VOLUME); + break; + case SOUND_MIXER_ALTPCM: + /* tas3001c_fast_load(self, 1); */ + level = tas_mono_to_stereo(level); + temp = tas3001c_gain.mixer[level&0xff]; + shadow[TAS3001C_REG_MIXER2][offset+0] = (temp >> 16) & 0xff; + shadow[TAS3001C_REG_MIXER2][offset+1] = (temp >> 8) & 0xff; + shadow[TAS3001C_REG_MIXER2][offset+2] = (temp >> 0) & 0xff; + rc = tas3001c_sync_register(self,TAS3001C_REG_MIXER2); + /* tas3001c_fast_load(self, 0); */ + break; + case SOUND_MIXER_PCM: + /* tas3001c_fast_load(self, 1); */ + level = tas_mono_to_stereo(level); + temp = tas3001c_gain.mixer[level&0xff]; + shadow[TAS3001C_REG_MIXER1][offset+0] = (temp >> 16) & 0xff; + shadow[TAS3001C_REG_MIXER1][offset+1] = (temp >> 8) & 0xff; + shadow[TAS3001C_REG_MIXER1][offset+2] = (temp >> 0) & 0xff; + rc = tas3001c_sync_register(self,TAS3001C_REG_MIXER1); + /* tas3001c_fast_load(self, 0); */ + break; + case SOUND_MIXER_TREBLE: + temp = tas3001c_gain.treble[level&0xff]; + shadow[TAS3001C_REG_TREBLE][0]=temp&0xff; + rc = tas3001c_sync_register(self,TAS3001C_REG_TREBLE); + break; + case SOUND_MIXER_BASS: + temp = tas3001c_gain.bass[level&0xff]; + shadow[TAS3001C_REG_BASS][0]=temp&0xff; + rc = tas3001c_sync_register(self,TAS3001C_REG_BASS); + break; + default: + rc = -1; + break; + } + if (rc < 0) + return rc; + self->super.mixer[mixer]=level; + return 0; +} + +static int +tas3001c_leave_sleep(struct tas3001c_data_t *self) +{ + unsigned char mcr = (1<<6)+(2<<4)+(2<<2); + + if (!self) + return -1; + + /* Make sure something answers on the i2c bus */ + if (tas3001c_write_register(self, TAS3001C_REG_MCR, &mcr, + WRITE_NORMAL|FORCE_WRITE) < 0) + return -1; + + tas3001c_fast_load(self, 1); + + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD0); + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD1); + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD2); + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD3); + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD4); + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD5); + + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD0); + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD1); + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD2); + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD3); + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD4); + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD5); + + tas3001c_fast_load(self, 0); + + (void)tas3001c_sync_register(self,TAS3001C_REG_BASS); + (void)tas3001c_sync_register(self,TAS3001C_REG_TREBLE); + (void)tas3001c_sync_register(self,TAS3001C_REG_MIXER1); + (void)tas3001c_sync_register(self,TAS3001C_REG_MIXER2); + (void)tas3001c_sync_register(self,TAS3001C_REG_VOLUME); + + return 0; +} + +static int +tas3001c_enter_sleep(struct tas3001c_data_t *self) +{ + /* Stub for now, but I have the details on low-power mode */ + if (!self) + return -1; + return 0; +} + +static int +tas3001c_sync_biquad( struct tas3001c_data_t *self, + u_int channel, + u_int filter) +{ + enum tas3001c_reg_t reg; + + if (channel >= TAS3001C_BIQUAD_CHANNEL_COUNT || + filter >= TAS3001C_BIQUAD_FILTER_COUNT) return -EINVAL; + + reg=( channel ? TAS3001C_REG_RIGHT_BIQUAD0 : TAS3001C_REG_LEFT_BIQUAD0 ) + filter; + + return tas3001c_sync_register(self,reg); +} + +static int +tas3001c_write_biquad_shadow( struct tas3001c_data_t *self, + u_int channel, + u_int filter, + const union tas_biquad_t *biquad) +{ + tas_shadow_t *shadow=self->super.shadow; + enum tas3001c_reg_t reg; + + if (channel >= TAS3001C_BIQUAD_CHANNEL_COUNT || + filter >= TAS3001C_BIQUAD_FILTER_COUNT) return -EINVAL; + + reg=( channel ? TAS3001C_REG_RIGHT_BIQUAD0 : TAS3001C_REG_LEFT_BIQUAD0 ) + filter; + + SET_4_20(shadow[reg], 0,biquad->coeff.b0); + SET_4_20(shadow[reg], 3,biquad->coeff.b1); + SET_4_20(shadow[reg], 6,biquad->coeff.b2); + SET_4_20(shadow[reg], 9,biquad->coeff.a1); + SET_4_20(shadow[reg],12,biquad->coeff.a2); + + return 0; +} + +static int +tas3001c_write_biquad( struct tas3001c_data_t *self, + u_int channel, + u_int filter, + const union tas_biquad_t *biquad) +{ + int rc; + + rc=tas3001c_write_biquad_shadow(self, channel, filter, biquad); + if (rc < 0) return rc; + + return tas3001c_sync_biquad(self, channel, filter); +} + +static int +tas3001c_write_biquad_list( struct tas3001c_data_t *self, + u_int filter_count, + u_int flags, + struct tas_biquad_ctrl_t *biquads) +{ + int i; + int rc; + + if (flags & TAS_BIQUAD_FAST_LOAD) tas3001c_fast_load(self,1); + + for (i=0; isuper.shadow; + enum tas3001c_reg_t reg; + + if (channel >= TAS3001C_BIQUAD_CHANNEL_COUNT || + filter >= TAS3001C_BIQUAD_FILTER_COUNT) return -EINVAL; + + reg=( channel ? TAS3001C_REG_RIGHT_BIQUAD0 : TAS3001C_REG_LEFT_BIQUAD0 ) + filter; + + biquad->coeff.b0=GET_4_20(shadow[reg], 0); + biquad->coeff.b1=GET_4_20(shadow[reg], 3); + biquad->coeff.b2=GET_4_20(shadow[reg], 6); + biquad->coeff.a1=GET_4_20(shadow[reg], 9); + biquad->coeff.a2=GET_4_20(shadow[reg],12); + + return 0; +} + +static int +tas3001c_eq_rw( struct tas3001c_data_t *self, + u_int cmd, + u_long arg) +{ + int rc; + struct tas_biquad_ctrl_t biquad; + void __user *argp = (void __user *)arg; + + if (copy_from_user(&biquad, argp, sizeof(struct tas_biquad_ctrl_t))) { + return -EFAULT; + } + + if (cmd & SIOC_IN) { + rc=tas3001c_write_biquad(self, biquad.channel, biquad.filter, &biquad.data); + if (rc != 0) return rc; + } + + if (cmd & SIOC_OUT) { + rc=tas3001c_read_biquad(self, biquad.channel, biquad.filter, &biquad.data); + if (rc != 0) return rc; + + if (copy_to_user(argp, &biquad, sizeof(struct tas_biquad_ctrl_t))) { + return -EFAULT; + } + + } + return 0; +} + +static int +tas3001c_eq_list_rw( struct tas3001c_data_t *self, + u_int cmd, + u_long arg) +{ + int rc; + int filter_count; + int flags; + int i,j; + char sync_required[2][6]; + struct tas_biquad_ctrl_t biquad; + struct tas_biquad_ctrl_list_t __user *argp = (void __user *)arg; + + memset(sync_required,0,sizeof(sync_required)); + + if (copy_from_user(&filter_count, &argp->filter_count, sizeof(int))) + return -EFAULT; + + if (copy_from_user(&flags, &argp->flags, sizeof(int))) + return -EFAULT; + + if (cmd & SIOC_IN) { + } + + for (i=0; i < filter_count; i++) { + if (copy_from_user(&biquad, &argp->biquads[i], + sizeof(struct tas_biquad_ctrl_t))) { + return -EFAULT; + } + + if (cmd & SIOC_IN) { + sync_required[biquad.channel][biquad.filter]=1; + rc=tas3001c_write_biquad_shadow(self, biquad.channel, biquad.filter, &biquad.data); + if (rc != 0) return rc; + } + + if (cmd & SIOC_OUT) { + rc=tas3001c_read_biquad(self, biquad.channel, biquad.filter, &biquad.data); + if (rc != 0) return rc; + + if (copy_to_user(&argp->biquads[i], &biquad, + sizeof(struct tas_biquad_ctrl_t))) { + return -EFAULT; + } + } + } + + if (cmd & SIOC_IN) { + if (flags & TAS_BIQUAD_FAST_LOAD) tas3001c_fast_load(self,1); + for (i=0; i<2; i++) { + for (j=0; j<6; j++) { + if (sync_required[i][j]) { + rc=tas3001c_sync_biquad(self, i, j); + if (rc < 0) return rc; + } + } + } + if (flags & TAS_BIQUAD_FAST_LOAD) { + tas3001c_fast_load(self,0); + /* now we need to set up the mixers again, + because leaving fast mode resets them. */ + (void)tas3001c_sync_register(self,TAS3001C_REG_BASS); + (void)tas3001c_sync_register(self,TAS3001C_REG_TREBLE); + (void)tas3001c_sync_register(self,TAS3001C_REG_MIXER1); + (void)tas3001c_sync_register(self,TAS3001C_REG_MIXER2); + (void)tas3001c_sync_register(self,TAS3001C_REG_VOLUME); + } + } + + return 0; +} + +static int +tas3001c_update_drce( struct tas3001c_data_t *self, + int flags, + struct tas_drce_t *drce) +{ + tas_shadow_t *shadow; + shadow=self->super.shadow; + + shadow[TAS3001C_REG_DRC][1] = 0xc1; + + if (flags & TAS_DRCE_THRESHOLD) { + self->drce_state.threshold=quantize_db(drce->threshold); + shadow[TAS3001C_REG_DRC][2] = db_to_regval(self->drce_state.threshold); + } + + if (flags & TAS_DRCE_ENABLE) { + self->drce_state.enable = drce->enable; + } + + if (!self->drce_state.enable) { + shadow[TAS3001C_REG_DRC][0] = 0xf0; + } + +#ifdef DEBUG_DRCE + printk("DRCE IOCTL: set [ ENABLE:%x THRESH:%x\n", + self->drce_state.enable, + self->drce_state.threshold); + + printk("DRCE IOCTL: reg [ %02x %02x ]\n", + (unsigned char)shadow[TAS3001C_REG_DRC][0], + (unsigned char)shadow[TAS3001C_REG_DRC][1]); +#endif + + return tas3001c_sync_register(self, TAS3001C_REG_DRC); +} + +static int +tas3001c_drce_rw( struct tas3001c_data_t *self, + u_int cmd, + u_long arg) +{ + int rc; + struct tas_drce_ctrl_t drce_ctrl; + void __user *argp = (void __user *)arg; + + if (copy_from_user(&drce_ctrl, argp, sizeof(struct tas_drce_ctrl_t))) + return -EFAULT; + +#ifdef DEBUG_DRCE + printk("DRCE IOCTL: input [ FLAGS:%x ENABLE:%x THRESH:%x\n", + drce_ctrl.flags, + drce_ctrl.data.enable, + drce_ctrl.data.threshold); +#endif + + if (cmd & SIOC_IN) { + rc = tas3001c_update_drce(self, drce_ctrl.flags, &drce_ctrl.data); + if (rc < 0) + return rc; + } + + if (cmd & SIOC_OUT) { + if (drce_ctrl.flags & TAS_DRCE_ENABLE) + drce_ctrl.data.enable = self->drce_state.enable; + + if (drce_ctrl.flags & TAS_DRCE_THRESHOLD) + drce_ctrl.data.threshold = self->drce_state.threshold; + + if (copy_to_user(argp, &drce_ctrl, + sizeof(struct tas_drce_ctrl_t))) { + return -EFAULT; + } + } + + return 0; +} + +static void +tas3001c_update_device_parameters(struct tas3001c_data_t *self) +{ + int i,j; + + if (!self) return; + + if (self->output_id == TAS_OUTPUT_HEADPHONES) { + tas3001c_fast_load(self, 1); + + for (i=0; idevice_id == self->device_id && + (eq->output_id == 0 || eq->output_id == self->output_id) && + (eq->speaker_id == 0 || eq->speaker_id == self->speaker_id)) { + + tas3001c_update_drce(self, TAS_DRCE_ALL, eq->drce); + tas3001c_write_biquad_list(self, eq->filter_count, TAS_BIQUAD_FAST_LOAD, eq->biquads); + + break; + } + } +} + +static void +tas3001c_device_change_handler(void *self) +{ + if (self) + tas3001c_update_device_parameters(self); +} + +static struct work_struct device_change; + +static int +tas3001c_output_device_change( struct tas3001c_data_t *self, + int device_id, + int output_id, + int speaker_id) +{ + self->device_id=device_id; + self->output_id=output_id; + self->speaker_id=speaker_id; + + schedule_work(&device_change); + return 0; +} + +static int +tas3001c_device_ioctl( struct tas3001c_data_t *self, + u_int cmd, + u_long arg) +{ + uint __user *argp = (void __user *)arg; + switch (cmd) { + case TAS_READ_EQ: + case TAS_WRITE_EQ: + return tas3001c_eq_rw(self, cmd, arg); + + case TAS_READ_EQ_LIST: + case TAS_WRITE_EQ_LIST: + return tas3001c_eq_list_rw(self, cmd, arg); + + case TAS_READ_EQ_FILTER_COUNT: + put_user(TAS3001C_BIQUAD_FILTER_COUNT, argp); + return 0; + + case TAS_READ_EQ_CHANNEL_COUNT: + put_user(TAS3001C_BIQUAD_CHANNEL_COUNT, argp); + return 0; + + case TAS_READ_DRCE: + case TAS_WRITE_DRCE: + return tas3001c_drce_rw(self, cmd, arg); + + case TAS_READ_DRCE_CAPS: + put_user(TAS_DRCE_ENABLE | TAS_DRCE_THRESHOLD, argp); + return 0; + + case TAS_READ_DRCE_MIN: + case TAS_READ_DRCE_MAX: { + struct tas_drce_ctrl_t drce_ctrl; + + if (copy_from_user(&drce_ctrl, argp, + sizeof(struct tas_drce_ctrl_t))) { + return -EFAULT; + } + + if (drce_ctrl.flags & TAS_DRCE_THRESHOLD) { + if (cmd == TAS_READ_DRCE_MIN) { + drce_ctrl.data.threshold=-36<<8; + } else { + drce_ctrl.data.threshold=-6<<8; + } + } + + if (copy_to_user(argp, &drce_ctrl, + sizeof(struct tas_drce_ctrl_t))) { + return -EFAULT; + } + } + } + + return -EINVAL; +} + +static int +tas3001c_init_mixer(struct tas3001c_data_t *self) +{ + unsigned char mcr = (1<<6)+(2<<4)+(2<<2); + + /* Make sure something answers on the i2c bus */ + if (tas3001c_write_register(self, TAS3001C_REG_MCR, &mcr, + WRITE_NORMAL|FORCE_WRITE) < 0) + return -1; + + tas3001c_fast_load(self, 1); + + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD0); + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD1); + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD2); + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD3); + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD4); + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD5); + (void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD6); + + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD0); + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD1); + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD2); + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD3); + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD4); + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD5); + (void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD6); + + tas3001c_fast_load(self, 0); + + tas3001c_set_mixer_level(self, SOUND_MIXER_VOLUME, VOL_DEFAULT<<8 | VOL_DEFAULT); + tas3001c_set_mixer_level(self, SOUND_MIXER_PCM, INPUT_DEFAULT<<8 | INPUT_DEFAULT); + tas3001c_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0); + + tas3001c_set_mixer_level(self, SOUND_MIXER_BASS, BASS_DEFAULT); + tas3001c_set_mixer_level(self, SOUND_MIXER_TREBLE, TREBLE_DEFAULT); + + return 0; +} + +static int +tas3001c_uninit_mixer(struct tas3001c_data_t *self) +{ + tas3001c_set_mixer_level(self, SOUND_MIXER_VOLUME, 0); + tas3001c_set_mixer_level(self, SOUND_MIXER_PCM, 0); + tas3001c_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0); + + tas3001c_set_mixer_level(self, SOUND_MIXER_BASS, 0); + tas3001c_set_mixer_level(self, SOUND_MIXER_TREBLE, 0); + + return 0; +} + +static int +tas3001c_init(struct i2c_client *client) +{ + struct tas3001c_data_t *self; + size_t sz = sizeof(*self) + (TAS3001C_REG_MAX*sizeof(tas_shadow_t)); + int i, j; + + self = kmalloc(sz, GFP_KERNEL); + if (!self) + return -ENOMEM; + memset(self, 0, sz); + + self->super.client = client; + self->super.shadow = (tas_shadow_t *)(self+1); + self->output_id = TAS_OUTPUT_HEADPHONES; + + dev_set_drvdata(&client->dev, self); + + for (i = 0; i < TAS3001C_BIQUAD_CHANNEL_COUNT; i++) + for (j = 0; j < TAS3001C_BIQUAD_FILTER_COUNT; j++) + tas3001c_write_biquad_shadow(self, i, j, + &tas3001c_eq_unity); + + INIT_WORK(&device_change, tas3001c_device_change_handler, self); + return 0; +} + +static void +tas3001c_uninit(struct tas3001c_data_t *self) +{ + tas3001c_uninit_mixer(self); + kfree(self); +} + +struct tas_driver_hooks_t tas3001c_hooks = { + .init = (tas_hook_init_t)tas3001c_init, + .post_init = (tas_hook_post_init_t)tas3001c_init_mixer, + .uninit = (tas_hook_uninit_t)tas3001c_uninit, + .get_mixer_level = (tas_hook_get_mixer_level_t)tas3001c_get_mixer_level, + .set_mixer_level = (tas_hook_set_mixer_level_t)tas3001c_set_mixer_level, + .enter_sleep = (tas_hook_enter_sleep_t)tas3001c_enter_sleep, + .leave_sleep = (tas_hook_leave_sleep_t)tas3001c_leave_sleep, + .supported_mixers = (tas_hook_supported_mixers_t)tas3001c_supported_mixers, + .mixer_is_stereo = (tas_hook_mixer_is_stereo_t)tas3001c_mixer_is_stereo, + .stereo_mixers = (tas_hook_stereo_mixers_t)tas3001c_stereo_mixers, + .output_device_change = (tas_hook_output_device_change_t)tas3001c_output_device_change, + .device_ioctl = (tas_hook_device_ioctl_t)tas3001c_device_ioctl +}; diff --git a/sound/oss/dmasound/tas3001c.h b/sound/oss/dmasound/tas3001c.h new file mode 100644 index 000000000000..3660da33a2db --- /dev/null +++ b/sound/oss/dmasound/tas3001c.h @@ -0,0 +1,64 @@ +/* + * Header file for the i2c/i2s based TA3001c sound chip used + * on some Apple hardware. Also known as "tumbler". + * + * 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. + * + * Written by Christopher C. Chimelis + */ + +#ifndef _TAS3001C_H_ +#define _TAS3001C_H_ + +#include + +#include "tas_common.h" +#include "tas_eq_prefs.h" + +/* + * Macros that correspond to the registers that we write to + * when setting the various values. + */ + +#define TAS3001C_VERSION "0.3" +#define TAS3001C_DATE "20011214" + +#define I2C_DRIVERNAME_TAS3001C "TAS3001c driver V " TAS3001C_VERSION +#define I2C_DRIVERID_TAS3001C (I2C_DRIVERID_TAS_BASE+0) + +extern struct tas_driver_hooks_t tas3001c_hooks; +extern struct tas_gain_t tas3001c_gain; +extern struct tas_eq_pref_t *tas3001c_eq_prefs[]; + +enum tas3001c_reg_t { + TAS3001C_REG_MCR = 0x01, + TAS3001C_REG_DRC = 0x02, + + TAS3001C_REG_VOLUME = 0x04, + TAS3001C_REG_TREBLE = 0x05, + TAS3001C_REG_BASS = 0x06, + TAS3001C_REG_MIXER1 = 0x07, + TAS3001C_REG_MIXER2 = 0x08, + + TAS3001C_REG_LEFT_BIQUAD0 = 0x0a, + TAS3001C_REG_LEFT_BIQUAD1 = 0x0b, + TAS3001C_REG_LEFT_BIQUAD2 = 0x0c, + TAS3001C_REG_LEFT_BIQUAD3 = 0x0d, + TAS3001C_REG_LEFT_BIQUAD4 = 0x0e, + TAS3001C_REG_LEFT_BIQUAD5 = 0x0f, + TAS3001C_REG_LEFT_BIQUAD6 = 0x10, + + TAS3001C_REG_RIGHT_BIQUAD0 = 0x13, + TAS3001C_REG_RIGHT_BIQUAD1 = 0x14, + TAS3001C_REG_RIGHT_BIQUAD2 = 0x15, + TAS3001C_REG_RIGHT_BIQUAD3 = 0x16, + TAS3001C_REG_RIGHT_BIQUAD4 = 0x17, + TAS3001C_REG_RIGHT_BIQUAD5 = 0x18, + TAS3001C_REG_RIGHT_BIQUAD6 = 0x19, + + TAS3001C_REG_MAX = 0x20 +}; + +#endif /* _TAS3001C_H_ */ diff --git a/sound/oss/dmasound/tas3001c_tables.c b/sound/oss/dmasound/tas3001c_tables.c new file mode 100644 index 000000000000..1768fa95f25b --- /dev/null +++ b/sound/oss/dmasound/tas3001c_tables.c @@ -0,0 +1,375 @@ +#include "tas_common.h" +#include "tas_eq_prefs.h" + +static struct tas_drce_t eqp_0e_2_1_drce = { + .enable = 1, + .above = { .val = 3.0 * (1<<8), .expand = 0 }, + .below = { .val = 1.0 * (1<<8), .expand = 0 }, + .threshold = -15.33 * (1<<8), + .energy = 2.4 * (1<<12), + .attack = 0.013 * (1<<12), + .decay = 0.212 * (1<<12), +}; + +static struct tas_biquad_ctrl_t eqp_0e_2_1_biquads[]={ + { .channel = 0, .filter = 0, .data = { .coeff = { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } }, + { .channel = 0, .filter = 1, .data = { .coeff = { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } }, + { .channel = 0, .filter = 2, .data = { .coeff = { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } }, + { .channel = 0, .filter = 3, .data = { .coeff = { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } }, + { .channel = 0, .filter = 4, .data = { .coeff = { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } }, + { .channel = 0, .filter = 5, .data = { .coeff = { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } }, + + { .channel = 1, .filter = 0, .data = { .coeff = { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } }, + { .channel = 1, .filter = 1, .data = { .coeff = { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } }, + { .channel = 1, .filter = 2, .data = { .coeff = { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } }, + { .channel = 1, .filter = 3, .data = { .coeff = { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } }, + { .channel = 1, .filter = 4, .data = { .coeff = { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } }, + { .channel = 1, .filter = 5, .data = { .coeff = { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } }, +}; + +static struct tas_eq_pref_t eqp_0e_2_1 = { + .sample_rate = 44100, + .device_id = 0x0e, + .output_id = TAS_OUTPUT_EXTERNAL_SPKR, + .speaker_id = 0x01, + + .drce = &eqp_0e_2_1_drce, + + .filter_count = 12, + .biquads = eqp_0e_2_1_biquads +}; + +/* ======================================================================== */ + +static struct tas_drce_t eqp_10_1_0_drce={ + .enable = 1, + .above = { .val = 3.0 * (1<<8), .expand = 0 }, + .below = { .val = 1.0 * (1<<8), .expand = 0 }, + .threshold = -12.46 * (1<<8), + .energy = 2.4 * (1<<12), + .attack = 0.013 * (1<<12), + .decay = 0.212 * (1<<12), +}; + +static struct tas_biquad_ctrl_t eqp_10_1_0_biquads[]={ + { .channel = 0, .filter = 0, .data = { .coeff = { 0x0F4A12, 0xE16BDA, 0x0F4A12, 0xE173F0, 0x0E9C3A } } }, + { .channel = 0, .filter = 1, .data = { .coeff = { 0x02DD54, 0x05BAA8, 0x02DD54, 0xF8001D, 0x037532 } } }, + { .channel = 0, .filter = 2, .data = { .coeff = { 0x0E2FC7, 0xE4D5DC, 0x0D7477, 0xE4D5DC, 0x0BA43F } } }, + { .channel = 0, .filter = 3, .data = { .coeff = { 0x0E7899, 0xE67CCA, 0x0D0E93, 0xE67CCA, 0x0B872D } } }, + { .channel = 0, .filter = 4, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } }, + { .channel = 0, .filter = 5, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } }, + + { .channel = 1, .filter = 0, .data = { .coeff = { 0x0F4A12, 0xE16BDA, 0x0F4A12, 0xE173F0, 0x0E9C3A } } }, + { .channel = 1, .filter = 1, .data = { .coeff = { 0x02DD54, 0x05BAA8, 0x02DD54, 0xF8001D, 0x037532 } } }, + { .channel = 1, .filter = 2, .data = { .coeff = { 0x0E2FC7, 0xE4D5DC, 0x0D7477, 0xE4D5DC, 0x0BA43F } } }, + { .channel = 1, .filter = 3, .data = { .coeff = { 0x0E7899, 0xE67CCA, 0x0D0E93, 0xE67CCA, 0x0B872D } } }, + { .channel = 1, .filter = 4, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } }, + { .channel = 1, .filter = 5, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } }, +}; + +static struct tas_eq_pref_t eqp_10_1_0 = { + .sample_rate = 44100, + .device_id = 0x10, + .output_id = TAS_OUTPUT_INTERNAL_SPKR, + .speaker_id = 0x00, + + .drce = &eqp_10_1_0_drce, + + .filter_count = 12, + .biquads = eqp_10_1_0_biquads +}; + +/* ======================================================================== */ + +static struct tas_drce_t eqp_15_2_1_drce={ + .enable = 1, + .above = { .val = 3.0 * (1<<8), .expand = 0 }, + .below = { .val = 1.0 * (1<<8), .expand = 0 }, + .threshold = -15.33 * (1<<8), + .energy = 2.4 * (1<<12), + .attack = 0.013 * (1<<12), + .decay = 0.212 * (1<<12), +}; + +static struct tas_biquad_ctrl_t eqp_15_2_1_biquads[]={ + { .channel = 0, .filter = 0, .data = { .coeff = { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } }, + { .channel = 0, .filter = 1, .data = { .coeff = { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } }, + { .channel = 0, .filter = 2, .data = { .coeff = { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } }, + { .channel = 0, .filter = 3, .data = { .coeff = { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } }, + { .channel = 0, .filter = 4, .data = { .coeff = { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } }, + { .channel = 0, .filter = 5, .data = { .coeff = { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } }, + + { .channel = 1, .filter = 0, .data = { .coeff = { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } }, + { .channel = 1, .filter = 1, .data = { .coeff = { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } }, + { .channel = 1, .filter = 2, .data = { .coeff = { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } }, + { .channel = 1, .filter = 3, .data = { .coeff = { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } }, + { .channel = 1, .filter = 4, .data = { .coeff = { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } }, + { .channel = 1, .filter = 5, .data = { .coeff = { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } }, +}; + +static struct tas_eq_pref_t eqp_15_2_1 = { + .sample_rate = 44100, + .device_id = 0x15, + .output_id = TAS_OUTPUT_EXTERNAL_SPKR, + .speaker_id = 0x01, + + .drce = &eqp_15_2_1_drce, + + .filter_count = 12, + .biquads = eqp_15_2_1_biquads +}; + +/* ======================================================================== */ + +static struct tas_drce_t eqp_15_1_0_drce={ + .enable = 1, + .above = { .val = 3.0 * (1<<8), .expand = 0 }, + .below = { .val = 1.0 * (1<<8), .expand = 0 }, + .threshold = 0.0 * (1<<8), + .energy = 2.4 * (1<<12), + .attack = 0.013 * (1<<12), + .decay = 0.212 * (1<<12), +}; + +static struct tas_biquad_ctrl_t eqp_15_1_0_biquads[]={ + { .channel = 0, .filter = 0, .data = { .coeff = { 0x0FAD08, 0xE0A5EF, 0x0FAD08, 0xE0A79D, 0x0F5BBE } } }, + { .channel = 0, .filter = 1, .data = { .coeff = { 0x04B38D, 0x09671B, 0x04B38D, 0x000F71, 0x02BEC5 } } }, + { .channel = 0, .filter = 2, .data = { .coeff = { 0x0FDD32, 0xE0A56F, 0x0F8A69, 0xE0A56F, 0x0F679C } } }, + { .channel = 0, .filter = 3, .data = { .coeff = { 0x0FD284, 0xE135FB, 0x0F2161, 0xE135FB, 0x0EF3E5 } } }, + { .channel = 0, .filter = 4, .data = { .coeff = { 0x0E81B1, 0xE6283F, 0x0CE49D, 0xE6283F, 0x0B664F } } }, + { .channel = 0, .filter = 5, .data = { .coeff = { 0x0F2D62, 0xE98797, 0x0D1E19, 0xE98797, 0x0C4B7B } } }, + + { .channel = 1, .filter = 0, .data = { .coeff = { 0x0FAD08, 0xE0A5EF, 0x0FAD08, 0xE0A79D, 0x0F5BBE } } }, + { .channel = 1, .filter = 1, .data = { .coeff = { 0x04B38D, 0x09671B, 0x04B38D, 0x000F71, 0x02BEC5 } } }, + { .channel = 1, .filter = 2, .data = { .coeff = { 0x0FDD32, 0xE0A56F, 0x0F8A69, 0xE0A56F, 0x0F679C } } }, + { .channel = 1, .filter = 3, .data = { .coeff = { 0x0FD284, 0xE135FB, 0x0F2161, 0xE135FB, 0x0EF3E5 } } }, + { .channel = 1, .filter = 4, .data = { .coeff = { 0x0E81B1, 0xE6283F, 0x0CE49D, 0xE6283F, 0x0B664F } } }, + { .channel = 1, .filter = 5, .data = { .coeff = { 0x0F2D62, 0xE98797, 0x0D1E19, 0xE98797, 0x0C4B7B } } }, +}; + +static struct tas_eq_pref_t eqp_15_1_0 = { + .sample_rate = 44100, + .device_id = 0x15, + .output_id = TAS_OUTPUT_INTERNAL_SPKR, + .speaker_id = 0x00, + + .drce = &eqp_15_1_0_drce, + + .filter_count = 12, + .biquads = eqp_15_1_0_biquads +}; + +/* ======================================================================== */ + +static struct tas_drce_t eqp_0f_2_1_drce={ + .enable = 1, + .above = { .val = 3.0 * (1<<8), .expand = 0 }, + .below = { .val = 1.0 * (1<<8), .expand = 0 }, + .threshold = -15.33 * (1<<8), + .energy = 2.4 * (1<<12), + .attack = 0.013 * (1<<12), + .decay = 0.212 * (1<<12), +}; + +static struct tas_biquad_ctrl_t eqp_0f_2_1_biquads[]={ + { .channel = 0, .filter = 0, .data = { .coeff = { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } }, + { .channel = 0, .filter = 1, .data = { .coeff = { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } }, + { .channel = 0, .filter = 2, .data = { .coeff = { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } }, + { .channel = 0, .filter = 3, .data = { .coeff = { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } }, + { .channel = 0, .filter = 4, .data = { .coeff = { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } }, + { .channel = 0, .filter = 5, .data = { .coeff = { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } }, + + { .channel = 1, .filter = 0, .data = { .coeff = { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } }, + { .channel = 1, .filter = 1, .data = { .coeff = { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } }, + { .channel = 1, .filter = 2, .data = { .coeff = { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } }, + { .channel = 1, .filter = 3, .data = { .coeff = { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } }, + { .channel = 1, .filter = 4, .data = { .coeff = { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } }, + { .channel = 1, .filter = 5, .data = { .coeff = { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } }, +}; + +static struct tas_eq_pref_t eqp_0f_2_1 = { + .sample_rate = 44100, + .device_id = 0x0f, + .output_id = TAS_OUTPUT_EXTERNAL_SPKR, + .speaker_id = 0x01, + + .drce = &eqp_0f_2_1_drce, + + .filter_count = 12, + .biquads = eqp_0f_2_1_biquads +}; + +/* ======================================================================== */ + +static struct tas_drce_t eqp_0f_1_0_drce={ + .enable = 1, + .above = { .val = 3.0 * (1<<8), .expand = 0 }, + .below = { .val = 1.0 * (1<<8), .expand = 0 }, + .threshold = -15.33 * (1<<8), + .energy = 2.4 * (1<<12), + .attack = 0.013 * (1<<12), + .decay = 0.212 * (1<<12), +}; + +static struct tas_biquad_ctrl_t eqp_0f_1_0_biquads[]={ + { .channel = 0, .filter = 0, .data = { .coeff = { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } }, + { .channel = 0, .filter = 1, .data = { .coeff = { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } }, + { .channel = 0, .filter = 2, .data = { .coeff = { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } }, + { .channel = 0, .filter = 3, .data = { .coeff = { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } }, + { .channel = 0, .filter = 4, .data = { .coeff = { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } }, + { .channel = 0, .filter = 5, .data = { .coeff = { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } }, + + { .channel = 1, .filter = 0, .data = { .coeff = { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } }, + { .channel = 1, .filter = 1, .data = { .coeff = { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } }, + { .channel = 1, .filter = 2, .data = { .coeff = { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } }, + { .channel = 1, .filter = 3, .data = { .coeff = { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } }, + { .channel = 1, .filter = 4, .data = { .coeff = { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } }, + { .channel = 1, .filter = 5, .data = { .coeff = { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } }, +}; + +static struct tas_eq_pref_t eqp_0f_1_0 = { + .sample_rate = 44100, + .device_id = 0x0f, + .output_id = TAS_OUTPUT_INTERNAL_SPKR, + .speaker_id = 0x00, + + .drce = &eqp_0f_1_0_drce, + + .filter_count = 12, + .biquads = eqp_0f_1_0_biquads +}; + +/* ======================================================================== */ + +static uint tas3001c_master_tab[]={ + 0x0, 0x75, 0x9c, 0xbb, + 0xdb, 0xfb, 0x11e, 0x143, + 0x16b, 0x196, 0x1c3, 0x1f5, + 0x229, 0x263, 0x29f, 0x2e1, + 0x328, 0x373, 0x3c5, 0x41b, + 0x478, 0x4dc, 0x547, 0x5b8, + 0x633, 0x6b5, 0x740, 0x7d5, + 0x873, 0x91c, 0x9d2, 0xa92, + 0xb5e, 0xc39, 0xd22, 0xe19, + 0xf20, 0x1037, 0x1161, 0x129e, + 0x13ed, 0x1551, 0x16ca, 0x185d, + 0x1a08, 0x1bcc, 0x1dac, 0x1fa7, + 0x21c1, 0x23fa, 0x2655, 0x28d6, + 0x2b7c, 0x2e4a, 0x3141, 0x3464, + 0x37b4, 0x3b35, 0x3ee9, 0x42d3, + 0x46f6, 0x4b53, 0x4ff0, 0x54ce, + 0x59f2, 0x5f5f, 0x6519, 0x6b24, + 0x7183, 0x783c, 0x7f53, 0x86cc, + 0x8ead, 0x96fa, 0x9fba, 0xa8f2, + 0xb2a7, 0xbce1, 0xc7a5, 0xd2fa, + 0xdee8, 0xeb75, 0xf8aa, 0x1068e, + 0x1152a, 0x12487, 0x134ad, 0x145a5, + 0x1577b, 0x16a37, 0x17df5, 0x192bd, + 0x1a890, 0x1bf7b, 0x1d78d, 0x1f0d1, + 0x20b55, 0x22727, 0x24456, 0x262f2, + 0x2830b +}; + +static uint tas3001c_mixer_tab[]={ + 0x0, 0x748, 0x9be, 0xbaf, + 0xda4, 0xfb1, 0x11de, 0x1431, + 0x16ad, 0x1959, 0x1c37, 0x1f4b, + 0x2298, 0x2628, 0x29fb, 0x2e12, + 0x327d, 0x3734, 0x3c47, 0x41b4, + 0x4787, 0x4dbe, 0x546d, 0x5b86, + 0x632e, 0x6b52, 0x7400, 0x7d54, + 0x873b, 0x91c6, 0x9d1a, 0xa920, + 0xb5e5, 0xc38c, 0xd21b, 0xe18f, + 0xf1f5, 0x1036a, 0x1160f, 0x129d6, + 0x13ed0, 0x1550c, 0x16ca0, 0x185c9, + 0x1a07b, 0x1bcc3, 0x1dab9, 0x1fa75, + 0x21c0f, 0x23fa3, 0x26552, 0x28d64, + 0x2b7c9, 0x2e4a2, 0x31411, 0x3463b, + 0x37b44, 0x3b353, 0x3ee94, 0x42d30, + 0x46f55, 0x4b533, 0x4fefc, 0x54ce5, + 0x59f25, 0x5f5f6, 0x65193, 0x6b23c, + 0x71835, 0x783c3, 0x7f52c, 0x86cc0, + 0x8eacc, 0x96fa5, 0x9fba0, 0xa8f1a, + 0xb2a71, 0xbce0a, 0xc7a4a, 0xd2fa0, + 0xdee7b, 0xeb752, 0xf8a9f, 0x1068e4, + 0x1152a3, 0x12486a, 0x134ac8, 0x145a55, + 0x1577ac, 0x16a370, 0x17df51, 0x192bc2, + 0x1a88f8, 0x1bf7b7, 0x1d78c9, 0x1f0d04, + 0x20b542, 0x227268, 0x244564, 0x262f26, + 0x2830af +}; + +static uint tas3001c_treble_tab[]={ + 0x96, 0x95, 0x95, 0x94, + 0x93, 0x92, 0x92, 0x91, + 0x90, 0x90, 0x8f, 0x8e, + 0x8d, 0x8d, 0x8c, 0x8b, + 0x8a, 0x8a, 0x89, 0x88, + 0x88, 0x87, 0x86, 0x85, + 0x85, 0x84, 0x83, 0x83, + 0x82, 0x81, 0x80, 0x80, + 0x7f, 0x7e, 0x7e, 0x7d, + 0x7c, 0x7b, 0x7b, 0x7a, + 0x79, 0x78, 0x78, 0x77, + 0x76, 0x76, 0x75, 0x74, + 0x73, 0x73, 0x72, 0x71, + 0x71, 0x70, 0x6e, 0x6d, + 0x6d, 0x6c, 0x6b, 0x6a, + 0x69, 0x68, 0x67, 0x66, + 0x65, 0x63, 0x62, 0x62, + 0x60, 0x5f, 0x5d, 0x5c, + 0x5a, 0x58, 0x56, 0x55, + 0x53, 0x51, 0x4f, 0x4c, + 0x4a, 0x48, 0x45, 0x43, + 0x40, 0x3d, 0x3a, 0x37, + 0x35, 0x32, 0x2e, 0x2a, + 0x27, 0x22, 0x1e, 0x1a, + 0x15, 0x11, 0xc, 0x7, + 0x1 +}; + +static uint tas3001c_bass_tab[]={ + 0x86, 0x83, 0x81, 0x7f, + 0x7d, 0x7b, 0x79, 0x78, + 0x76, 0x75, 0x74, 0x72, + 0x71, 0x6f, 0x6e, 0x6d, + 0x6c, 0x6b, 0x69, 0x67, + 0x65, 0x64, 0x61, 0x60, + 0x5e, 0x5d, 0x5c, 0x5b, + 0x5a, 0x59, 0x58, 0x57, + 0x56, 0x55, 0x55, 0x54, + 0x53, 0x52, 0x50, 0x4f, + 0x4d, 0x4c, 0x4b, 0x49, + 0x47, 0x45, 0x44, 0x42, + 0x41, 0x3f, 0x3e, 0x3d, + 0x3c, 0x3b, 0x39, 0x38, + 0x37, 0x36, 0x35, 0x34, + 0x33, 0x31, 0x30, 0x2f, + 0x2e, 0x2c, 0x2b, 0x2b, + 0x29, 0x28, 0x27, 0x26, + 0x25, 0x24, 0x22, 0x21, + 0x20, 0x1e, 0x1c, 0x19, + 0x18, 0x18, 0x17, 0x16, + 0x15, 0x14, 0x13, 0x12, + 0x11, 0x10, 0xf, 0xe, + 0xd, 0xb, 0xa, 0x9, + 0x8, 0x6, 0x4, 0x2, + 0x1 +}; + +struct tas_gain_t tas3001c_gain = { + .master = tas3001c_master_tab, + .treble = tas3001c_treble_tab, + .bass = tas3001c_bass_tab, + .mixer = tas3001c_mixer_tab +}; + +struct tas_eq_pref_t *tas3001c_eq_prefs[]={ + &eqp_0e_2_1, + &eqp_10_1_0, + &eqp_15_2_1, + &eqp_15_1_0, + &eqp_0f_2_1, + &eqp_0f_1_0, + NULL +}; diff --git a/sound/oss/dmasound/tas3004.c b/sound/oss/dmasound/tas3004.c new file mode 100644 index 000000000000..82eaaca2db9a --- /dev/null +++ b/sound/oss/dmasound/tas3004.c @@ -0,0 +1,1140 @@ +/* + * Driver for the i2c/i2s based TA3004 sound chip used + * on some Apple hardware. Also known as "snapper". + * + * Tobias Sargeant + * Based upon tas3001c.c by Christopher C. Chimelis : + * + * Input support by Renzo Davoli + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "dmasound.h" +#include "tas_common.h" +#include "tas3004.h" + +#include "tas_ioctl.h" + +/* #define DEBUG_DRCE */ + +#define TAS3004_BIQUAD_FILTER_COUNT 7 +#define TAS3004_BIQUAD_CHANNEL_COUNT 2 + +#define VOL_DEFAULT (100 * 4 / 5) +#define INPUT_DEFAULT (100 * 4 / 5) +#define BASS_DEFAULT (100 / 2) +#define TREBLE_DEFAULT (100 / 2) + +struct tas3004_data_t { + struct tas_data_t super; + int device_id; + int output_id; + int speaker_id; + struct tas_drce_t drce_state; +}; + +#define MAKE_TIME(sec,usec) (((sec)<<12) + (50000+(usec/10)*(1<<12))/100000) + +#define MAKE_RATIO(i,f) (((i)<<8) + ((500+(f)*(1<<8))/1000)) + + +static const union tas_biquad_t tas3004_eq_unity = { + .buf = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 }, +}; + + +static const struct tas_drce_t tas3004_drce_min = { + .enable = 1, + .above = { .val = MAKE_RATIO(16,0), .expand = 0 }, + .below = { .val = MAKE_RATIO(2,0), .expand = 0 }, + .threshold = -0x59a0, + .energy = MAKE_TIME(0, 1700), + .attack = MAKE_TIME(0, 1700), + .decay = MAKE_TIME(0, 1700), +}; + + +static const struct tas_drce_t tas3004_drce_max = { + .enable = 1, + .above = { .val = MAKE_RATIO(1,500), .expand = 1 }, + .below = { .val = MAKE_RATIO(2,0), .expand = 1 }, + .threshold = -0x0, + .energy = MAKE_TIME(2,400000), + .attack = MAKE_TIME(2,400000), + .decay = MAKE_TIME(2,400000), +}; + + +static const unsigned short time_constants[]={ + MAKE_TIME(0, 1700), + MAKE_TIME(0, 3500), + MAKE_TIME(0, 6700), + MAKE_TIME(0, 13000), + MAKE_TIME(0, 26000), + MAKE_TIME(0, 53000), + MAKE_TIME(0,106000), + MAKE_TIME(0,212000), + MAKE_TIME(0,425000), + MAKE_TIME(0,850000), + MAKE_TIME(1,700000), + MAKE_TIME(2,400000), +}; + +static const unsigned short above_threshold_compression_ratio[]={ + MAKE_RATIO( 1, 70), + MAKE_RATIO( 1,140), + MAKE_RATIO( 1,230), + MAKE_RATIO( 1,330), + MAKE_RATIO( 1,450), + MAKE_RATIO( 1,600), + MAKE_RATIO( 1,780), + MAKE_RATIO( 2, 0), + MAKE_RATIO( 2,290), + MAKE_RATIO( 2,670), + MAKE_RATIO( 3,200), + MAKE_RATIO( 4, 0), + MAKE_RATIO( 5,330), + MAKE_RATIO( 8, 0), + MAKE_RATIO(16, 0), +}; + +static const unsigned short above_threshold_expansion_ratio[]={ + MAKE_RATIO(1, 60), + MAKE_RATIO(1,130), + MAKE_RATIO(1,190), + MAKE_RATIO(1,250), + MAKE_RATIO(1,310), + MAKE_RATIO(1,380), + MAKE_RATIO(1,440), + MAKE_RATIO(1,500) +}; + +static const unsigned short below_threshold_compression_ratio[]={ + MAKE_RATIO(1, 70), + MAKE_RATIO(1,140), + MAKE_RATIO(1,230), + MAKE_RATIO(1,330), + MAKE_RATIO(1,450), + MAKE_RATIO(1,600), + MAKE_RATIO(1,780), + MAKE_RATIO(2, 0) +}; + +static const unsigned short below_threshold_expansion_ratio[]={ + MAKE_RATIO(1, 60), + MAKE_RATIO(1,130), + MAKE_RATIO(1,190), + MAKE_RATIO(1,250), + MAKE_RATIO(1,310), + MAKE_RATIO(1,380), + MAKE_RATIO(1,440), + MAKE_RATIO(1,500), + MAKE_RATIO(1,560), + MAKE_RATIO(1,630), + MAKE_RATIO(1,690), + MAKE_RATIO(1,750), + MAKE_RATIO(1,810), + MAKE_RATIO(1,880), + MAKE_RATIO(1,940), + MAKE_RATIO(2, 0) +}; + +static inline int +search( unsigned short val, + const unsigned short *arr, + const int arrsize) { + /* + * This could be a binary search, but for small tables, + * a linear search is likely to be faster + */ + + int i; + + for (i=0; i < arrsize; i++) + if (arr[i] >= val) + goto _1; + return arrsize-1; + _1: + if (i == 0) + return 0; + return (arr[i]-val < val-arr[i-1]) ? i : i-1; +} + +#define SEARCH(a, b) search(a, b, ARRAY_SIZE(b)) + +static inline int +time_index(unsigned short time) +{ + return SEARCH(time, time_constants); +} + + +static inline int +above_threshold_compression_index(unsigned short ratio) +{ + return SEARCH(ratio, above_threshold_compression_ratio); +} + + +static inline int +above_threshold_expansion_index(unsigned short ratio) +{ + return SEARCH(ratio, above_threshold_expansion_ratio); +} + + +static inline int +below_threshold_compression_index(unsigned short ratio) +{ + return SEARCH(ratio, below_threshold_compression_ratio); +} + + +static inline int +below_threshold_expansion_index(unsigned short ratio) +{ + return SEARCH(ratio, below_threshold_expansion_ratio); +} + +static inline unsigned char db_to_regval(short db) { + int r=0; + + r=(db+0x59a0) / 0x60; + + if (r < 0x91) return 0x91; + if (r > 0xef) return 0xef; + return r; +} + +static inline short quantize_db(short db) +{ + return db_to_regval(db) * 0x60 - 0x59a0; +} + +static inline int +register_width(enum tas3004_reg_t r) +{ + switch(r) { + case TAS3004_REG_MCR: + case TAS3004_REG_TREBLE: + case TAS3004_REG_BASS: + case TAS3004_REG_ANALOG_CTRL: + case TAS3004_REG_TEST1: + case TAS3004_REG_TEST2: + case TAS3004_REG_MCR2: + return 1; + + case TAS3004_REG_LEFT_LOUD_BIQUAD_GAIN: + case TAS3004_REG_RIGHT_LOUD_BIQUAD_GAIN: + return 3; + + case TAS3004_REG_DRC: + case TAS3004_REG_VOLUME: + return 6; + + case TAS3004_REG_LEFT_MIXER: + case TAS3004_REG_RIGHT_MIXER: + return 9; + + case TAS3004_REG_TEST: + return 10; + + case TAS3004_REG_LEFT_BIQUAD0: + case TAS3004_REG_LEFT_BIQUAD1: + case TAS3004_REG_LEFT_BIQUAD2: + case TAS3004_REG_LEFT_BIQUAD3: + case TAS3004_REG_LEFT_BIQUAD4: + case TAS3004_REG_LEFT_BIQUAD5: + case TAS3004_REG_LEFT_BIQUAD6: + + case TAS3004_REG_RIGHT_BIQUAD0: + case TAS3004_REG_RIGHT_BIQUAD1: + case TAS3004_REG_RIGHT_BIQUAD2: + case TAS3004_REG_RIGHT_BIQUAD3: + case TAS3004_REG_RIGHT_BIQUAD4: + case TAS3004_REG_RIGHT_BIQUAD5: + case TAS3004_REG_RIGHT_BIQUAD6: + + case TAS3004_REG_LEFT_LOUD_BIQUAD: + case TAS3004_REG_RIGHT_LOUD_BIQUAD: + return 15; + + default: + return 0; + } +} + +static int +tas3004_write_register( struct tas3004_data_t *self, + enum tas3004_reg_t reg_num, + char *data, + uint write_mode) +{ + if (reg_num==TAS3004_REG_MCR || + reg_num==TAS3004_REG_BASS || + reg_num==TAS3004_REG_TREBLE || + reg_num==TAS3004_REG_ANALOG_CTRL) { + return tas_write_byte_register(&self->super, + (uint)reg_num, + *data, + write_mode); + } else { + return tas_write_register(&self->super, + (uint)reg_num, + register_width(reg_num), + data, + write_mode); + } +} + +static int +tas3004_sync_register( struct tas3004_data_t *self, + enum tas3004_reg_t reg_num) +{ + if (reg_num==TAS3004_REG_MCR || + reg_num==TAS3004_REG_BASS || + reg_num==TAS3004_REG_TREBLE || + reg_num==TAS3004_REG_ANALOG_CTRL) { + return tas_sync_byte_register(&self->super, + (uint)reg_num, + register_width(reg_num)); + } else { + return tas_sync_register(&self->super, + (uint)reg_num, + register_width(reg_num)); + } +} + +static int +tas3004_read_register( struct tas3004_data_t *self, + enum tas3004_reg_t reg_num, + char *data, + uint write_mode) +{ + return tas_read_register(&self->super, + (uint)reg_num, + register_width(reg_num), + data); +} + +static inline int +tas3004_fast_load(struct tas3004_data_t *self, int fast) +{ + if (fast) + self->super.shadow[TAS3004_REG_MCR][0] |= 0x80; + else + self->super.shadow[TAS3004_REG_MCR][0] &= 0x7f; + return tas3004_sync_register(self,TAS3004_REG_MCR); +} + +static uint +tas3004_supported_mixers(struct tas3004_data_t *self) +{ + return SOUND_MASK_VOLUME | + SOUND_MASK_PCM | + SOUND_MASK_ALTPCM | + SOUND_MASK_IMIX | + SOUND_MASK_TREBLE | + SOUND_MASK_BASS | + SOUND_MASK_MIC | + SOUND_MASK_LINE; +} + +static int +tas3004_mixer_is_stereo(struct tas3004_data_t *self, int mixer) +{ + switch(mixer) { + case SOUND_MIXER_VOLUME: + case SOUND_MIXER_PCM: + case SOUND_MIXER_ALTPCM: + case SOUND_MIXER_IMIX: + return 1; + default: + return 0; + } +} + +static uint +tas3004_stereo_mixers(struct tas3004_data_t *self) +{ + uint r = tas3004_supported_mixers(self); + uint i; + + for (i=1; isuper.mixer[mixer]; + + return 0; +} + +static int +tas3004_set_mixer_level(struct tas3004_data_t *self, int mixer, uint level) +{ + int rc; + tas_shadow_t *shadow; + uint temp; + uint offset=0; + + if (!self) + return -1; + + shadow = self->super.shadow; + + if (!tas3004_mixer_is_stereo(self,mixer)) + level = tas_mono_to_stereo(level); + switch(mixer) { + case SOUND_MIXER_VOLUME: + temp = tas3004_gain.master[level&0xff]; + SET_4_20(shadow[TAS3004_REG_VOLUME], 0, temp); + temp = tas3004_gain.master[(level>>8)&0xff]; + SET_4_20(shadow[TAS3004_REG_VOLUME], 3, temp); + rc = tas3004_sync_register(self,TAS3004_REG_VOLUME); + break; + case SOUND_MIXER_IMIX: + offset += 3; + case SOUND_MIXER_ALTPCM: + offset += 3; + case SOUND_MIXER_PCM: + /* + * Don't load these in fast mode. The documentation + * says it can be done in either mode, but testing it + * shows that fast mode produces ugly clicking. + */ + /* tas3004_fast_load(self,1); */ + temp = tas3004_gain.mixer[level&0xff]; + SET_4_20(shadow[TAS3004_REG_LEFT_MIXER], offset, temp); + temp = tas3004_gain.mixer[(level>>8)&0xff]; + SET_4_20(shadow[TAS3004_REG_RIGHT_MIXER], offset, temp); + rc = tas3004_sync_register(self,TAS3004_REG_LEFT_MIXER); + if (rc == 0) + rc=tas3004_sync_register(self,TAS3004_REG_RIGHT_MIXER); + /* tas3004_fast_load(self,0); */ + break; + case SOUND_MIXER_TREBLE: + temp = tas3004_gain.treble[level&0xff]; + shadow[TAS3004_REG_TREBLE][0]=temp&0xff; + rc = tas3004_sync_register(self,TAS3004_REG_TREBLE); + break; + case SOUND_MIXER_BASS: + temp = tas3004_gain.bass[level&0xff]; + shadow[TAS3004_REG_BASS][0]=temp&0xff; + rc = tas3004_sync_register(self,TAS3004_REG_BASS); + break; + case SOUND_MIXER_MIC: + if ((level&0xff)>0) { + software_input_volume = SW_INPUT_VOLUME_SCALE * (level&0xff); + if (self->super.mixer[mixer] == 0) { + self->super.mixer[SOUND_MIXER_LINE] = 0; + shadow[TAS3004_REG_ANALOG_CTRL][0]=0xc2; + rc = tas3004_sync_register(self,TAS3004_REG_ANALOG_CTRL); + } else rc=0; + } else { + self->super.mixer[SOUND_MIXER_LINE] = SW_INPUT_VOLUME_DEFAULT; + software_input_volume = SW_INPUT_VOLUME_SCALE * + (self->super.mixer[SOUND_MIXER_LINE]&0xff); + shadow[TAS3004_REG_ANALOG_CTRL][0]=0x00; + rc = tas3004_sync_register(self,TAS3004_REG_ANALOG_CTRL); + } + break; + case SOUND_MIXER_LINE: + if (self->super.mixer[SOUND_MIXER_MIC] == 0) { + software_input_volume = SW_INPUT_VOLUME_SCALE * (level&0xff); + rc=0; + } + break; + default: + rc = -1; + break; + } + if (rc < 0) + return rc; + self->super.mixer[mixer] = level; + + return 0; +} + +static int +tas3004_leave_sleep(struct tas3004_data_t *self) +{ + unsigned char mcr = (1<<6)+(2<<4)+(2<<2); + + if (!self) + return -1; + + /* Make sure something answers on the i2c bus */ + if (tas3004_write_register(self, TAS3004_REG_MCR, &mcr, + WRITE_NORMAL | FORCE_WRITE) < 0) + return -1; + + tas3004_fast_load(self, 1); + + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD0); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD1); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD2); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD3); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD4); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD5); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD6); + + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD0); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD1); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD2); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD3); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD4); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD5); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD6); + + tas3004_fast_load(self, 0); + + (void)tas3004_sync_register(self,TAS3004_REG_VOLUME); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_MIXER); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_MIXER); + (void)tas3004_sync_register(self,TAS3004_REG_TREBLE); + (void)tas3004_sync_register(self,TAS3004_REG_BASS); + (void)tas3004_sync_register(self,TAS3004_REG_ANALOG_CTRL); + + return 0; +} + +static int +tas3004_enter_sleep(struct tas3004_data_t *self) +{ + if (!self) + return -1; + return 0; +} + +static int +tas3004_sync_biquad( struct tas3004_data_t *self, + u_int channel, + u_int filter) +{ + enum tas3004_reg_t reg; + + if (channel >= TAS3004_BIQUAD_CHANNEL_COUNT || + filter >= TAS3004_BIQUAD_FILTER_COUNT) return -EINVAL; + + reg=( channel ? TAS3004_REG_RIGHT_BIQUAD0 : TAS3004_REG_LEFT_BIQUAD0 ) + filter; + + return tas3004_sync_register(self,reg); +} + +static int +tas3004_write_biquad_shadow( struct tas3004_data_t *self, + u_int channel, + u_int filter, + const union tas_biquad_t *biquad) +{ + tas_shadow_t *shadow=self->super.shadow; + enum tas3004_reg_t reg; + + if (channel >= TAS3004_BIQUAD_CHANNEL_COUNT || + filter >= TAS3004_BIQUAD_FILTER_COUNT) return -EINVAL; + + reg=( channel ? TAS3004_REG_RIGHT_BIQUAD0 : TAS3004_REG_LEFT_BIQUAD0 ) + filter; + + SET_4_20(shadow[reg], 0,biquad->coeff.b0); + SET_4_20(shadow[reg], 3,biquad->coeff.b1); + SET_4_20(shadow[reg], 6,biquad->coeff.b2); + SET_4_20(shadow[reg], 9,biquad->coeff.a1); + SET_4_20(shadow[reg],12,biquad->coeff.a2); + + return 0; +} + +static int +tas3004_write_biquad( struct tas3004_data_t *self, + u_int channel, + u_int filter, + const union tas_biquad_t *biquad) +{ + int rc; + + rc=tas3004_write_biquad_shadow(self, channel, filter, biquad); + if (rc < 0) return rc; + + return tas3004_sync_biquad(self, channel, filter); +} + +static int +tas3004_write_biquad_list( struct tas3004_data_t *self, + u_int filter_count, + u_int flags, + struct tas_biquad_ctrl_t *biquads) +{ + int i; + int rc; + + if (flags & TAS_BIQUAD_FAST_LOAD) tas3004_fast_load(self,1); + + for (i=0; isuper.shadow; + enum tas3004_reg_t reg; + + if (channel >= TAS3004_BIQUAD_CHANNEL_COUNT || + filter >= TAS3004_BIQUAD_FILTER_COUNT) return -EINVAL; + + reg=( channel ? TAS3004_REG_RIGHT_BIQUAD0 : TAS3004_REG_LEFT_BIQUAD0 ) + filter; + + biquad->coeff.b0=GET_4_20(shadow[reg], 0); + biquad->coeff.b1=GET_4_20(shadow[reg], 3); + biquad->coeff.b2=GET_4_20(shadow[reg], 6); + biquad->coeff.a1=GET_4_20(shadow[reg], 9); + biquad->coeff.a2=GET_4_20(shadow[reg],12); + + return 0; +} + +static int +tas3004_eq_rw( struct tas3004_data_t *self, + u_int cmd, + u_long arg) +{ + void __user *argp = (void __user *)arg; + int rc; + struct tas_biquad_ctrl_t biquad; + + if (copy_from_user((void *)&biquad, argp, sizeof(struct tas_biquad_ctrl_t))) { + return -EFAULT; + } + + if (cmd & SIOC_IN) { + rc=tas3004_write_biquad(self, biquad.channel, biquad.filter, &biquad.data); + if (rc != 0) return rc; + } + + if (cmd & SIOC_OUT) { + rc=tas3004_read_biquad(self, biquad.channel, biquad.filter, &biquad.data); + if (rc != 0) return rc; + + if (copy_to_user(argp, &biquad, sizeof(struct tas_biquad_ctrl_t))) { + return -EFAULT; + } + + } + return 0; +} + +static int +tas3004_eq_list_rw( struct tas3004_data_t *self, + u_int cmd, + u_long arg) +{ + int rc = 0; + int filter_count; + int flags; + int i,j; + char sync_required[TAS3004_BIQUAD_CHANNEL_COUNT][TAS3004_BIQUAD_FILTER_COUNT]; + struct tas_biquad_ctrl_t biquad; + struct tas_biquad_ctrl_list_t __user *argp = (void __user *)arg; + + memset(sync_required,0,sizeof(sync_required)); + + if (copy_from_user(&filter_count, &argp->filter_count, sizeof(int))) + return -EFAULT; + + if (copy_from_user(&flags, &argp->flags, sizeof(int))) + return -EFAULT; + + if (cmd & SIOC_IN) { + } + + for (i=0; i < filter_count; i++) { + if (copy_from_user(&biquad, &argp->biquads[i], + sizeof(struct tas_biquad_ctrl_t))) { + return -EFAULT; + } + + if (cmd & SIOC_IN) { + sync_required[biquad.channel][biquad.filter]=1; + rc=tas3004_write_biquad_shadow(self, biquad.channel, biquad.filter, &biquad.data); + if (rc != 0) return rc; + } + + if (cmd & SIOC_OUT) { + rc=tas3004_read_biquad(self, biquad.channel, biquad.filter, &biquad.data); + if (rc != 0) return rc; + + if (copy_to_user(&argp->biquads[i], &biquad, + sizeof(struct tas_biquad_ctrl_t))) { + return -EFAULT; + } + } + } + + if (cmd & SIOC_IN) { + /* + * This is OK for the tas3004. For the + * tas3001c, going into fast load mode causes + * the treble and bass to be reset to 0dB, and + * volume controls to be muted. + */ + if (flags & TAS_BIQUAD_FAST_LOAD) tas3004_fast_load(self,1); + for (i=0; isuper.shadow; + + if (flags & TAS_DRCE_ABOVE_RATIO) { + self->drce_state.above.expand = drce->above.expand; + if (drce->above.val == (1<<8)) { + self->drce_state.above.val = 1<<8; + shadow[TAS3004_REG_DRC][0] = 0x02; + + } else if (drce->above.expand) { + i=above_threshold_expansion_index(drce->above.val); + self->drce_state.above.val=above_threshold_expansion_ratio[i]; + shadow[TAS3004_REG_DRC][0] = 0x0a + (i<<3); + } else { + i=above_threshold_compression_index(drce->above.val); + self->drce_state.above.val=above_threshold_compression_ratio[i]; + shadow[TAS3004_REG_DRC][0] = 0x08 + (i<<3); + } + } + + if (flags & TAS_DRCE_BELOW_RATIO) { + self->drce_state.below.expand = drce->below.expand; + if (drce->below.val == (1<<8)) { + self->drce_state.below.val = 1<<8; + shadow[TAS3004_REG_DRC][1] = 0x02; + + } else if (drce->below.expand) { + i=below_threshold_expansion_index(drce->below.val); + self->drce_state.below.val=below_threshold_expansion_ratio[i]; + shadow[TAS3004_REG_DRC][1] = 0x08 + (i<<3); + } else { + i=below_threshold_compression_index(drce->below.val); + self->drce_state.below.val=below_threshold_compression_ratio[i]; + shadow[TAS3004_REG_DRC][1] = 0x0a + (i<<3); + } + } + + if (flags & TAS_DRCE_THRESHOLD) { + self->drce_state.threshold=quantize_db(drce->threshold); + shadow[TAS3004_REG_DRC][2] = db_to_regval(self->drce_state.threshold); + } + + if (flags & TAS_DRCE_ENERGY) { + i=time_index(drce->energy); + self->drce_state.energy=time_constants[i]; + shadow[TAS3004_REG_DRC][3] = 0x40 + (i<<4); + } + + if (flags & TAS_DRCE_ATTACK) { + i=time_index(drce->attack); + self->drce_state.attack=time_constants[i]; + shadow[TAS3004_REG_DRC][4] = 0x40 + (i<<4); + } + + if (flags & TAS_DRCE_DECAY) { + i=time_index(drce->decay); + self->drce_state.decay=time_constants[i]; + shadow[TAS3004_REG_DRC][5] = 0x40 + (i<<4); + } + + if (flags & TAS_DRCE_ENABLE) { + self->drce_state.enable = drce->enable; + } + + if (!self->drce_state.enable) { + shadow[TAS3004_REG_DRC][0] |= 0x01; + } + +#ifdef DEBUG_DRCE + printk("DRCE: set [ ENABLE:%x ABOVE:%x/%x BELOW:%x/%x THRESH:%x ENERGY:%x ATTACK:%x DECAY:%x\n", + self->drce_state.enable, + self->drce_state.above.expand,self->drce_state.above.val, + self->drce_state.below.expand,self->drce_state.below.val, + self->drce_state.threshold, + self->drce_state.energy, + self->drce_state.attack, + self->drce_state.decay); + + printk("DRCE: reg [ %02x %02x %02x %02x %02x %02x ]\n", + (unsigned char)shadow[TAS3004_REG_DRC][0], + (unsigned char)shadow[TAS3004_REG_DRC][1], + (unsigned char)shadow[TAS3004_REG_DRC][2], + (unsigned char)shadow[TAS3004_REG_DRC][3], + (unsigned char)shadow[TAS3004_REG_DRC][4], + (unsigned char)shadow[TAS3004_REG_DRC][5]); +#endif + + return tas3004_sync_register(self, TAS3004_REG_DRC); +} + +static int +tas3004_drce_rw( struct tas3004_data_t *self, + u_int cmd, + u_long arg) +{ + int rc; + struct tas_drce_ctrl_t drce_ctrl; + void __user *argp = (void __user *)arg; + + if (copy_from_user(&drce_ctrl, argp, sizeof(struct tas_drce_ctrl_t))) + return -EFAULT; + +#ifdef DEBUG_DRCE + printk("DRCE: input [ FLAGS:%x ENABLE:%x ABOVE:%x/%x BELOW:%x/%x THRESH:%x ENERGY:%x ATTACK:%x DECAY:%x\n", + drce_ctrl.flags, + drce_ctrl.data.enable, + drce_ctrl.data.above.expand,drce_ctrl.data.above.val, + drce_ctrl.data.below.expand,drce_ctrl.data.below.val, + drce_ctrl.data.threshold, + drce_ctrl.data.energy, + drce_ctrl.data.attack, + drce_ctrl.data.decay); +#endif + + if (cmd & SIOC_IN) { + rc = tas3004_update_drce(self, drce_ctrl.flags, &drce_ctrl.data); + if (rc < 0) return rc; + } + + if (cmd & SIOC_OUT) { + if (drce_ctrl.flags & TAS_DRCE_ENABLE) + drce_ctrl.data.enable = self->drce_state.enable; + if (drce_ctrl.flags & TAS_DRCE_ABOVE_RATIO) + drce_ctrl.data.above = self->drce_state.above; + if (drce_ctrl.flags & TAS_DRCE_BELOW_RATIO) + drce_ctrl.data.below = self->drce_state.below; + if (drce_ctrl.flags & TAS_DRCE_THRESHOLD) + drce_ctrl.data.threshold = self->drce_state.threshold; + if (drce_ctrl.flags & TAS_DRCE_ENERGY) + drce_ctrl.data.energy = self->drce_state.energy; + if (drce_ctrl.flags & TAS_DRCE_ATTACK) + drce_ctrl.data.attack = self->drce_state.attack; + if (drce_ctrl.flags & TAS_DRCE_DECAY) + drce_ctrl.data.decay = self->drce_state.decay; + + if (copy_to_user(argp, &drce_ctrl, + sizeof(struct tas_drce_ctrl_t))) { + return -EFAULT; + } + } + + return 0; +} + +static void +tas3004_update_device_parameters(struct tas3004_data_t *self) +{ + char data; + int i; + + if (!self) return; + + if (self->output_id == TAS_OUTPUT_HEADPHONES) { + /* turn on allPass when headphones are plugged in */ + data = 0x02; + } else { + data = 0x00; + } + + tas3004_write_register(self, TAS3004_REG_MCR2, &data, WRITE_NORMAL | FORCE_WRITE); + + for (i=0; tas3004_eq_prefs[i]; i++) { + struct tas_eq_pref_t *eq = tas3004_eq_prefs[i]; + + if (eq->device_id == self->device_id && + (eq->output_id == 0 || eq->output_id == self->output_id) && + (eq->speaker_id == 0 || eq->speaker_id == self->speaker_id)) { + + tas3004_update_drce(self, TAS_DRCE_ALL, eq->drce); + tas3004_write_biquad_list(self, eq->filter_count, TAS_BIQUAD_FAST_LOAD, eq->biquads); + + break; + } + } +} + +static void +tas3004_device_change_handler(void *self) +{ + if (!self) return; + + tas3004_update_device_parameters((struct tas3004_data_t *)self); +} + +static struct work_struct device_change; + +static int +tas3004_output_device_change( struct tas3004_data_t *self, + int device_id, + int output_id, + int speaker_id) +{ + self->device_id=device_id; + self->output_id=output_id; + self->speaker_id=speaker_id; + + schedule_work(&device_change); + + return 0; +} + +static int +tas3004_device_ioctl( struct tas3004_data_t *self, + u_int cmd, + u_long arg) +{ + uint __user *argp = (void __user *)arg; + switch (cmd) { + case TAS_READ_EQ: + case TAS_WRITE_EQ: + return tas3004_eq_rw(self, cmd, arg); + + case TAS_READ_EQ_LIST: + case TAS_WRITE_EQ_LIST: + return tas3004_eq_list_rw(self, cmd, arg); + + case TAS_READ_EQ_FILTER_COUNT: + put_user(TAS3004_BIQUAD_FILTER_COUNT, argp); + return 0; + + case TAS_READ_EQ_CHANNEL_COUNT: + put_user(TAS3004_BIQUAD_CHANNEL_COUNT, argp); + return 0; + + case TAS_READ_DRCE: + case TAS_WRITE_DRCE: + return tas3004_drce_rw(self, cmd, arg); + + case TAS_READ_DRCE_CAPS: + put_user(TAS_DRCE_ENABLE | + TAS_DRCE_ABOVE_RATIO | + TAS_DRCE_BELOW_RATIO | + TAS_DRCE_THRESHOLD | + TAS_DRCE_ENERGY | + TAS_DRCE_ATTACK | + TAS_DRCE_DECAY, + argp); + return 0; + + case TAS_READ_DRCE_MIN: + case TAS_READ_DRCE_MAX: { + struct tas_drce_ctrl_t drce_ctrl; + const struct tas_drce_t *drce_copy; + + if (copy_from_user(&drce_ctrl, argp, + sizeof(struct tas_drce_ctrl_t))) { + return -EFAULT; + } + + if (cmd == TAS_READ_DRCE_MIN) { + drce_copy=&tas3004_drce_min; + } else { + drce_copy=&tas3004_drce_max; + } + + if (drce_ctrl.flags & TAS_DRCE_ABOVE_RATIO) { + drce_ctrl.data.above=drce_copy->above; + } + if (drce_ctrl.flags & TAS_DRCE_BELOW_RATIO) { + drce_ctrl.data.below=drce_copy->below; + } + if (drce_ctrl.flags & TAS_DRCE_THRESHOLD) { + drce_ctrl.data.threshold=drce_copy->threshold; + } + if (drce_ctrl.flags & TAS_DRCE_ENERGY) { + drce_ctrl.data.energy=drce_copy->energy; + } + if (drce_ctrl.flags & TAS_DRCE_ATTACK) { + drce_ctrl.data.attack=drce_copy->attack; + } + if (drce_ctrl.flags & TAS_DRCE_DECAY) { + drce_ctrl.data.decay=drce_copy->decay; + } + + if (copy_to_user(argp, &drce_ctrl, + sizeof(struct tas_drce_ctrl_t))) { + return -EFAULT; + } + } + } + + return -EINVAL; +} + +static int +tas3004_init_mixer(struct tas3004_data_t *self) +{ + unsigned char mcr = (1<<6)+(2<<4)+(2<<2); + + /* Make sure something answers on the i2c bus */ + if (tas3004_write_register(self, TAS3004_REG_MCR, &mcr, + WRITE_NORMAL | FORCE_WRITE) < 0) + return -1; + + tas3004_fast_load(self, 1); + + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD0); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD1); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD2); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD3); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD4); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD5); + (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD6); + + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD0); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD1); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD2); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD3); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD4); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD5); + (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD6); + + tas3004_sync_register(self, TAS3004_REG_DRC); + + tas3004_sync_register(self, TAS3004_REG_MCR2); + + tas3004_fast_load(self, 0); + + tas3004_set_mixer_level(self, SOUND_MIXER_VOLUME, VOL_DEFAULT<<8 | VOL_DEFAULT); + tas3004_set_mixer_level(self, SOUND_MIXER_PCM, INPUT_DEFAULT<<8 | INPUT_DEFAULT); + tas3004_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0); + tas3004_set_mixer_level(self, SOUND_MIXER_IMIX, 0); + + tas3004_set_mixer_level(self, SOUND_MIXER_BASS, BASS_DEFAULT); + tas3004_set_mixer_level(self, SOUND_MIXER_TREBLE, TREBLE_DEFAULT); + + tas3004_set_mixer_level(self, SOUND_MIXER_LINE,SW_INPUT_VOLUME_DEFAULT); + + return 0; +} + +static int +tas3004_uninit_mixer(struct tas3004_data_t *self) +{ + tas3004_set_mixer_level(self, SOUND_MIXER_VOLUME, 0); + tas3004_set_mixer_level(self, SOUND_MIXER_PCM, 0); + tas3004_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0); + tas3004_set_mixer_level(self, SOUND_MIXER_IMIX, 0); + + tas3004_set_mixer_level(self, SOUND_MIXER_BASS, 0); + tas3004_set_mixer_level(self, SOUND_MIXER_TREBLE, 0); + + tas3004_set_mixer_level(self, SOUND_MIXER_LINE, 0); + + return 0; +} + +static int +tas3004_init(struct i2c_client *client) +{ + struct tas3004_data_t *self; + size_t sz = sizeof(*self) + (TAS3004_REG_MAX*sizeof(tas_shadow_t)); + char drce_init[] = { 0x69, 0x22, 0x9f, 0xb0, 0x60, 0xa0 }; + char mcr2 = 0; + int i, j; + + self = kmalloc(sz, GFP_KERNEL); + if (!self) + return -ENOMEM; + memset(self, 0, sz); + + self->super.client = client; + self->super.shadow = (tas_shadow_t *)(self+1); + self->output_id = TAS_OUTPUT_HEADPHONES; + + dev_set_drvdata(&client->dev, self); + + for (i = 0; i < TAS3004_BIQUAD_CHANNEL_COUNT; i++) + for (j = 0; j + */ + +#ifndef _TAS3004_H_ +#define _TAS3004_H_ + +#include + +#include "tas_common.h" +#include "tas_eq_prefs.h" + +/* + * Macros that correspond to the registers that we write to + * when setting the various values. + */ + +#define TAS3004_VERSION "0.3" +#define TAS3004_DATE "20011214" + +#define I2C_DRIVERNAME_TAS3004 "TAS3004 driver V " TAS3004_VERSION +#define I2C_DRIVERID_TAS3004 (I2C_DRIVERID_TAS_BASE+1) + +extern struct tas_driver_hooks_t tas3004_hooks; +extern struct tas_gain_t tas3004_gain; +extern struct tas_eq_pref_t *tas3004_eq_prefs[]; + +enum tas3004_reg_t { + TAS3004_REG_MCR = 0x01, + TAS3004_REG_DRC = 0x02, + + TAS3004_REG_VOLUME = 0x04, + TAS3004_REG_TREBLE = 0x05, + TAS3004_REG_BASS = 0x06, + TAS3004_REG_LEFT_MIXER = 0x07, + TAS3004_REG_RIGHT_MIXER = 0x08, + + TAS3004_REG_LEFT_BIQUAD0 = 0x0a, + TAS3004_REG_LEFT_BIQUAD1 = 0x0b, + TAS3004_REG_LEFT_BIQUAD2 = 0x0c, + TAS3004_REG_LEFT_BIQUAD3 = 0x0d, + TAS3004_REG_LEFT_BIQUAD4 = 0x0e, + TAS3004_REG_LEFT_BIQUAD5 = 0x0f, + TAS3004_REG_LEFT_BIQUAD6 = 0x10, + + TAS3004_REG_RIGHT_BIQUAD0 = 0x13, + TAS3004_REG_RIGHT_BIQUAD1 = 0x14, + TAS3004_REG_RIGHT_BIQUAD2 = 0x15, + TAS3004_REG_RIGHT_BIQUAD3 = 0x16, + TAS3004_REG_RIGHT_BIQUAD4 = 0x17, + TAS3004_REG_RIGHT_BIQUAD5 = 0x18, + TAS3004_REG_RIGHT_BIQUAD6 = 0x19, + + TAS3004_REG_LEFT_LOUD_BIQUAD = 0x21, + TAS3004_REG_RIGHT_LOUD_BIQUAD = 0x22, + + TAS3004_REG_LEFT_LOUD_BIQUAD_GAIN = 0x23, + TAS3004_REG_RIGHT_LOUD_BIQUAD_GAIN = 0x24, + + TAS3004_REG_TEST = 0x29, + + TAS3004_REG_ANALOG_CTRL = 0x40, + TAS3004_REG_TEST1 = 0x41, + TAS3004_REG_TEST2 = 0x42, + TAS3004_REG_MCR2 = 0x43, + + TAS3004_REG_MAX = 0x44 +}; + +#endif /* _TAS3004_H_ */ diff --git a/sound/oss/dmasound/tas3004_tables.c b/sound/oss/dmasound/tas3004_tables.c new file mode 100644 index 000000000000..b910e0a66775 --- /dev/null +++ b/sound/oss/dmasound/tas3004_tables.c @@ -0,0 +1,301 @@ +#include "tas3004.h" +#include "tas_eq_prefs.h" + +static struct tas_drce_t eqp_17_1_0_drce={ + .enable = 1, + .above = { .val = 3.0 * (1<<8), .expand = 0 }, + .below = { .val = 1.0 * (1<<8), .expand = 0 }, + .threshold = -19.12 * (1<<8), + .energy = 2.4 * (1<<12), + .attack = 0.013 * (1<<12), + .decay = 0.212 * (1<<12), +}; + +static struct tas_biquad_ctrl_t eqp_17_1_0_biquads[]={ + { .channel = 0, .filter = 0, .data = { .coeff = { 0x0fd0d4, 0xe05e56, 0x0fd0d4, 0xe05ee1, 0x0fa234 } } }, + { .channel = 0, .filter = 1, .data = { .coeff = { 0x0910d7, 0x088e1a, 0x030651, 0x01dcb1, 0x02c892 } } }, + { .channel = 0, .filter = 2, .data = { .coeff = { 0x0ff895, 0xe0970b, 0x0f7f00, 0xe0970b, 0x0f7795 } } }, + { .channel = 0, .filter = 3, .data = { .coeff = { 0x0fd1c4, 0xe1ac22, 0x0ec8cf, 0xe1ac22, 0x0e9a94 } } }, + { .channel = 0, .filter = 4, .data = { .coeff = { 0x0f7c1c, 0xe3cc03, 0x0df786, 0xe3cc03, 0x0d73a2 } } }, + { .channel = 0, .filter = 5, .data = { .coeff = { 0x11fb92, 0xf5a1a0, 0x073cd2, 0xf5a1a0, 0x093865 } } }, + { .channel = 0, .filter = 6, .data = { .coeff = { 0x0e17a9, 0x068b6c, 0x08a0e5, 0x068b6c, 0x06b88e } } }, + + { .channel = 1, .filter = 0, .data = { .coeff = { 0x0fd0d4, 0xe05e56, 0x0fd0d4, 0xe05ee1, 0x0fa234 } } }, + { .channel = 1, .filter = 1, .data = { .coeff = { 0x0910d7, 0x088e1a, 0x030651, 0x01dcb1, 0x02c892 } } }, + { .channel = 1, .filter = 2, .data = { .coeff = { 0x0ff895, 0xe0970b, 0x0f7f00, 0xe0970b, 0x0f7795 } } }, + { .channel = 1, .filter = 3, .data = { .coeff = { 0x0fd1c4, 0xe1ac22, 0x0ec8cf, 0xe1ac22, 0x0e9a94 } } }, + { .channel = 1, .filter = 4, .data = { .coeff = { 0x0f7c1c, 0xe3cc03, 0x0df786, 0xe3cc03, 0x0d73a2 } } }, + { .channel = 1, .filter = 5, .data = { .coeff = { 0x11fb92, 0xf5a1a0, 0x073cd2, 0xf5a1a0, 0x093865 } } }, + { .channel = 1, .filter = 6, .data = { .coeff = { 0x0e17a9, 0x068b6c, 0x08a0e5, 0x068b6c, 0x06b88e } } } +}; + +static struct tas_eq_pref_t eqp_17_1_0 = { + .sample_rate = 44100, + .device_id = 0x17, + .output_id = TAS_OUTPUT_INTERNAL_SPKR, + .speaker_id = 0x00, + + .drce = &eqp_17_1_0_drce, + + .filter_count = 14, + .biquads = eqp_17_1_0_biquads +}; + +/* ======================================================================== */ + +static struct tas_drce_t eqp_18_1_0_drce={ + .enable = 1, + .above = { .val = 3.0 * (1<<8), .expand = 0 }, + .below = { .val = 1.0 * (1<<8), .expand = 0 }, + .threshold = -13.14 * (1<<8), + .energy = 2.4 * (1<<12), + .attack = 0.013 * (1<<12), + .decay = 0.212 * (1<<12), +}; + +static struct tas_biquad_ctrl_t eqp_18_1_0_biquads[]={ + { .channel = 0, .filter = 0, .data = { .coeff = { 0x0f5514, 0xe155d7, 0x0f5514, 0xe15cfa, 0x0eb14b } } }, + { .channel = 0, .filter = 1, .data = { .coeff = { 0x06ec33, 0x02abe3, 0x015eef, 0xf764d9, 0x03922d } } }, + { .channel = 0, .filter = 2, .data = { .coeff = { 0x0ef5f2, 0xe67d1f, 0x0bcf37, 0xe67d1f, 0x0ac529 } } }, + { .channel = 0, .filter = 3, .data = { .coeff = { 0x0db050, 0xe5be4d, 0x0d0c78, 0xe5be4d, 0x0abcc8 } } }, + { .channel = 0, .filter = 4, .data = { .coeff = { 0x0f1298, 0xe64ec6, 0x0cc03e, 0xe64ec6, 0x0bd2d7 } } }, + { .channel = 0, .filter = 5, .data = { .coeff = { 0x0c641a, 0x06537a, 0x08d155, 0x06537a, 0x053570 } } }, + { .channel = 0, .filter = 6, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } }, + + { .channel = 1, .filter = 0, .data = { .coeff = { 0x0f5514, 0xe155d7, 0x0f5514, 0xe15cfa, 0x0eb14b } } }, + { .channel = 1, .filter = 1, .data = { .coeff = { 0x06ec33, 0x02abe3, 0x015eef, 0xf764d9, 0x03922d } } }, + { .channel = 1, .filter = 2, .data = { .coeff = { 0x0ef5f2, 0xe67d1f, 0x0bcf37, 0xe67d1f, 0x0ac529 } } }, + { .channel = 1, .filter = 3, .data = { .coeff = { 0x0db050, 0xe5be4d, 0x0d0c78, 0xe5be4d, 0x0abcc8 } } }, + { .channel = 1, .filter = 4, .data = { .coeff = { 0x0f1298, 0xe64ec6, 0x0cc03e, 0xe64ec6, 0x0bd2d7 } } }, + { .channel = 1, .filter = 5, .data = { .coeff = { 0x0c641a, 0x06537a, 0x08d155, 0x06537a, 0x053570 } } }, + { .channel = 1, .filter = 6, .data = { .coeff = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } } +}; + +static struct tas_eq_pref_t eqp_18_1_0 = { + .sample_rate = 44100, + .device_id = 0x18, + .output_id = TAS_OUTPUT_INTERNAL_SPKR, + .speaker_id = 0x00, + + .drce = &eqp_18_1_0_drce, + + .filter_count = 14, + .biquads = eqp_18_1_0_biquads +}; + +/* ======================================================================== */ + +static struct tas_drce_t eqp_1a_1_0_drce={ + .enable = 1, + .above = { .val = 3.0 * (1<<8), .expand = 0 }, + .below = { .val = 1.0 * (1<<8), .expand = 0 }, + .threshold = -10.75 * (1<<8), + .energy = 2.4 * (1<<12), + .attack = 0.013 * (1<<12), + .decay = 0.212 * (1<<12), +}; + +static struct tas_biquad_ctrl_t eqp_1a_1_0_biquads[]={ + { .channel = 0, .filter = 0, .data = { .coeff = { 0x0fb8fd, 0xe08e04, 0x0fb8fd, 0xe08f40, 0x0f7336 } } }, + { .channel = 0, .filter = 1, .data = { .coeff = { 0x06371d, 0x0c6e3a, 0x06371d, 0x05bfd3, 0x031ca2 } } }, + { .channel = 0, .filter = 2, .data = { .coeff = { 0x0fa1c0, 0xe18692, 0x0f030e, 0xe18692, 0x0ea4ce } } }, + { .channel = 0, .filter = 3, .data = { .coeff = { 0x0fe495, 0xe17eff, 0x0f0452, 0xe17eff, 0x0ee8e7 } } }, + { .channel = 0, .filter = 4, .data = { .coeff = { 0x100857, 0xe7e71c, 0x0e9599, 0xe7e71c, 0x0e9df1 } } }, + { .channel = 0, .filter = 5, .data = { .coeff = { 0x0fb26e, 0x06a82c, 0x0db2b4, 0x06a82c, 0x0d6522 } } }, + { .channel = 0, .filter = 6, .data = { .coeff = { 0x11419d, 0xf06cbf, 0x0a4f6e, 0xf06cbf, 0x0b910c } } }, + + { .channel = 1, .filter = 0, .data = { .coeff = { 0x0fb8fd, 0xe08e04, 0x0fb8fd, 0xe08f40, 0x0f7336 } } }, + { .channel = 1, .filter = 1, .data = { .coeff = { 0x06371d, 0x0c6e3a, 0x06371d, 0x05bfd3, 0x031ca2 } } }, + { .channel = 1, .filter = 2, .data = { .coeff = { 0x0fa1c0, 0xe18692, 0x0f030e, 0xe18692, 0x0ea4ce } } }, + { .channel = 1, .filter = 3, .data = { .coeff = { 0x0fe495, 0xe17eff, 0x0f0452, 0xe17eff, 0x0ee8e7 } } }, + { .channel = 1, .filter = 4, .data = { .coeff = { 0x100857, 0xe7e71c, 0x0e9599, 0xe7e71c, 0x0e9df1 } } }, + { .channel = 1, .filter = 5, .data = { .coeff = { 0x0fb26e, 0x06a82c, 0x0db2b4, 0x06a82c, 0x0d6522 } } }, + { .channel = 1, .filter = 6, .data = { .coeff = { 0x11419d, 0xf06cbf, 0x0a4f6e, 0xf06cbf, 0x0b910c } } } +}; + +static struct tas_eq_pref_t eqp_1a_1_0 = { + .sample_rate = 44100, + .device_id = 0x1a, + .output_id = TAS_OUTPUT_INTERNAL_SPKR, + .speaker_id = 0x00, + + .drce = &eqp_1a_1_0_drce, + + .filter_count = 14, + .biquads = eqp_1a_1_0_biquads +}; + +/* ======================================================================== */ + +static struct tas_drce_t eqp_1c_1_0_drce={ + .enable = 1, + .above = { .val = 3.0 * (1<<8), .expand = 0 }, + .below = { .val = 1.0 * (1<<8), .expand = 0 }, + .threshold = -14.34 * (1<<8), + .energy = 2.4 * (1<<12), + .attack = 0.013 * (1<<12), + .decay = 0.212 * (1<<12), +}; + +static struct tas_biquad_ctrl_t eqp_1c_1_0_biquads[]={ + { .channel = 0, .filter = 0, .data = { .coeff = { 0x0f4f95, 0xe160d4, 0x0f4f95, 0xe1686e, 0x0ea6c5 } } }, + { .channel = 0, .filter = 1, .data = { .coeff = { 0x066b92, 0x0290d4, 0x0148a0, 0xf6853f, 0x03bfc7 } } }, + { .channel = 0, .filter = 2, .data = { .coeff = { 0x0f57dc, 0xe51c91, 0x0dd1cb, 0xe51c91, 0x0d29a8 } } }, + { .channel = 0, .filter = 3, .data = { .coeff = { 0x0df1cb, 0xe4fa84, 0x0d7cdc, 0xe4fa84, 0x0b6ea7 } } }, + { .channel = 0, .filter = 4, .data = { .coeff = { 0x0eba36, 0xe6aa48, 0x0b9f52, 0xe6aa48, 0x0a5989 } } }, + { .channel = 0, .filter = 5, .data = { .coeff = { 0x0caf02, 0x05ef9d, 0x084beb, 0x05ef9d, 0x04faee } } }, + { .channel = 0, .filter = 6, .data = { .coeff = { 0x0fc686, 0xe22947, 0x0e4b5d, 0xe22947, 0x0e11e4 } } }, + + { .channel = 1, .filter = 0, .data = { .coeff = { 0x0f4f95, 0xe160d4, 0x0f4f95, 0xe1686e, 0x0ea6c5 } } }, + { .channel = 1, .filter = 1, .data = { .coeff = { 0x066b92, 0x0290d4, 0x0148a0, 0xf6853f, 0x03bfc7 } } }, + { .channel = 1, .filter = 2, .data = { .coeff = { 0x0f57dc, 0xe51c91, 0x0dd1cb, 0xe51c91, 0x0d29a8 } } }, + { .channel = 1, .filter = 3, .data = { .coeff = { 0x0df1cb, 0xe4fa84, 0x0d7cdc, 0xe4fa84, 0x0b6ea7 } } }, + { .channel = 1, .filter = 4, .data = { .coeff = { 0x0eba36, 0xe6aa48, 0x0b9f52, 0xe6aa48, 0x0a5989 } } }, + { .channel = 1, .filter = 5, .data = { .coeff = { 0x0caf02, 0x05ef9d, 0x084beb, 0x05ef9d, 0x04faee } } }, + { .channel = 1, .filter = 6, .data = { .coeff = { 0x0fc686, 0xe22947, 0x0e4b5d, 0xe22947, 0x0e11e4 } } } +}; + +static struct tas_eq_pref_t eqp_1c_1_0 = { + .sample_rate = 44100, + .device_id = 0x1c, + .output_id = TAS_OUTPUT_INTERNAL_SPKR, + .speaker_id = 0x00, + + .drce = &eqp_1c_1_0_drce, + + .filter_count = 14, + .biquads = eqp_1c_1_0_biquads +}; + +/* ======================================================================== */ + +static uint tas3004_master_tab[]={ + 0x0, 0x75, 0x9c, 0xbb, + 0xdb, 0xfb, 0x11e, 0x143, + 0x16b, 0x196, 0x1c3, 0x1f5, + 0x229, 0x263, 0x29f, 0x2e1, + 0x328, 0x373, 0x3c5, 0x41b, + 0x478, 0x4dc, 0x547, 0x5b8, + 0x633, 0x6b5, 0x740, 0x7d5, + 0x873, 0x91c, 0x9d2, 0xa92, + 0xb5e, 0xc39, 0xd22, 0xe19, + 0xf20, 0x1037, 0x1161, 0x129e, + 0x13ed, 0x1551, 0x16ca, 0x185d, + 0x1a08, 0x1bcc, 0x1dac, 0x1fa7, + 0x21c1, 0x23fa, 0x2655, 0x28d6, + 0x2b7c, 0x2e4a, 0x3141, 0x3464, + 0x37b4, 0x3b35, 0x3ee9, 0x42d3, + 0x46f6, 0x4b53, 0x4ff0, 0x54ce, + 0x59f2, 0x5f5f, 0x6519, 0x6b24, + 0x7183, 0x783c, 0x7f53, 0x86cc, + 0x8ead, 0x96fa, 0x9fba, 0xa8f2, + 0xb2a7, 0xbce1, 0xc7a5, 0xd2fa, + 0xdee8, 0xeb75, 0xf8aa, 0x1068e, + 0x1152a, 0x12487, 0x134ad, 0x145a5, + 0x1577b, 0x16a37, 0x17df5, 0x192bd, + 0x1a890, 0x1bf7b, 0x1d78d, 0x1f0d1, + 0x20b55, 0x22727, 0x24456, 0x262f2, + 0x2830b +}; + +static uint tas3004_mixer_tab[]={ + 0x0, 0x748, 0x9be, 0xbaf, + 0xda4, 0xfb1, 0x11de, 0x1431, + 0x16ad, 0x1959, 0x1c37, 0x1f4b, + 0x2298, 0x2628, 0x29fb, 0x2e12, + 0x327d, 0x3734, 0x3c47, 0x41b4, + 0x4787, 0x4dbe, 0x546d, 0x5b86, + 0x632e, 0x6b52, 0x7400, 0x7d54, + 0x873b, 0x91c6, 0x9d1a, 0xa920, + 0xb5e5, 0xc38c, 0xd21b, 0xe18f, + 0xf1f5, 0x1036a, 0x1160f, 0x129d6, + 0x13ed0, 0x1550c, 0x16ca0, 0x185c9, + 0x1a07b, 0x1bcc3, 0x1dab9, 0x1fa75, + 0x21c0f, 0x23fa3, 0x26552, 0x28d64, + 0x2b7c9, 0x2e4a2, 0x31411, 0x3463b, + 0x37b44, 0x3b353, 0x3ee94, 0x42d30, + 0x46f55, 0x4b533, 0x4fefc, 0x54ce5, + 0x59f25, 0x5f5f6, 0x65193, 0x6b23c, + 0x71835, 0x783c3, 0x7f52c, 0x86cc0, + 0x8eacc, 0x96fa5, 0x9fba0, 0xa8f1a, + 0xb2a71, 0xbce0a, 0xc7a4a, 0xd2fa0, + 0xdee7b, 0xeb752, 0xf8a9f, 0x1068e4, + 0x1152a3, 0x12486a, 0x134ac8, 0x145a55, + 0x1577ac, 0x16a370, 0x17df51, 0x192bc2, + 0x1a88f8, 0x1bf7b7, 0x1d78c9, 0x1f0d04, + 0x20b542, 0x227268, 0x244564, 0x262f26, + 0x2830af +}; + +static uint tas3004_treble_tab[]={ + 0x96, 0x95, 0x95, 0x94, + 0x93, 0x92, 0x92, 0x91, + 0x90, 0x90, 0x8f, 0x8e, + 0x8d, 0x8d, 0x8c, 0x8b, + 0x8a, 0x8a, 0x89, 0x88, + 0x88, 0x87, 0x86, 0x85, + 0x85, 0x84, 0x83, 0x83, + 0x82, 0x81, 0x80, 0x80, + 0x7f, 0x7e, 0x7e, 0x7d, + 0x7c, 0x7b, 0x7b, 0x7a, + 0x79, 0x78, 0x78, 0x77, + 0x76, 0x76, 0x75, 0x74, + 0x73, 0x73, 0x72, 0x71, + 0x71, 0x68, 0x45, 0x5b, + 0x6d, 0x6c, 0x6b, 0x6a, + 0x69, 0x68, 0x67, 0x66, + 0x65, 0x63, 0x62, 0x62, + 0x60, 0x5e, 0x5c, 0x5b, + 0x59, 0x57, 0x55, 0x53, + 0x52, 0x4f, 0x4d, 0x4a, + 0x48, 0x46, 0x43, 0x40, + 0x3d, 0x3a, 0x36, 0x33, + 0x2f, 0x2c, 0x27, 0x23, + 0x1f, 0x1a, 0x15, 0xf, + 0x8, 0x5, 0x2, 0x1, + 0x1 +}; + +static uint tas3004_bass_tab[]={ + 0x96, 0x95, 0x95, 0x94, + 0x93, 0x92, 0x92, 0x91, + 0x90, 0x90, 0x8f, 0x8e, + 0x8d, 0x8d, 0x8c, 0x8b, + 0x8a, 0x8a, 0x89, 0x88, + 0x88, 0x87, 0x86, 0x85, + 0x85, 0x84, 0x83, 0x83, + 0x82, 0x81, 0x80, 0x80, + 0x7f, 0x7e, 0x7e, 0x7d, + 0x7c, 0x7b, 0x7b, 0x7a, + 0x79, 0x78, 0x78, 0x77, + 0x76, 0x76, 0x75, 0x74, + 0x73, 0x73, 0x72, 0x71, + 0x70, 0x6f, 0x6e, 0x6d, + 0x6c, 0x6b, 0x6a, 0x6a, + 0x69, 0x67, 0x66, 0x66, + 0x65, 0x63, 0x62, 0x62, + 0x61, 0x60, 0x5e, 0x5d, + 0x5b, 0x59, 0x57, 0x55, + 0x53, 0x51, 0x4f, 0x4c, + 0x4a, 0x48, 0x46, 0x44, + 0x41, 0x3e, 0x3b, 0x38, + 0x36, 0x33, 0x2f, 0x2b, + 0x28, 0x24, 0x20, 0x1c, + 0x17, 0x12, 0xd, 0x7, + 0x1 +}; + +struct tas_gain_t tas3004_gain={ + .master = tas3004_master_tab, + .treble = tas3004_treble_tab, + .bass = tas3004_bass_tab, + .mixer = tas3004_mixer_tab +}; + +struct tas_eq_pref_t *tas3004_eq_prefs[]={ + &eqp_17_1_0, + &eqp_18_1_0, + &eqp_1a_1_0, + &eqp_1c_1_0, + NULL +}; diff --git a/sound/oss/dmasound/tas_common.c b/sound/oss/dmasound/tas_common.c new file mode 100644 index 000000000000..d36a1fe2fcf3 --- /dev/null +++ b/sound/oss/dmasound/tas_common.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tas_common.h" + +#define CALL0(proc) \ + do { \ + struct tas_data_t *self; \ + if (!tas_client || driver_hooks == NULL) \ + return -1; \ + self = dev_get_drvdata(&tas_client->dev); \ + if (driver_hooks->proc) \ + return driver_hooks->proc(self); \ + else \ + return -EINVAL; \ + } while (0) + +#define CALL(proc,arg...) \ + do { \ + struct tas_data_t *self; \ + if (!tas_client || driver_hooks == NULL) \ + return -1; \ + self = dev_get_drvdata(&tas_client->dev); \ + if (driver_hooks->proc) \ + return driver_hooks->proc(self, ## arg); \ + else \ + return -EINVAL; \ + } while (0) + + +static u8 tas_i2c_address = 0x34; +static struct i2c_client *tas_client; +static struct device_node* tas_node; + +static int tas_attach_adapter(struct i2c_adapter *); +static int tas_detach_client(struct i2c_client *); + +struct i2c_driver tas_driver = { + .owner = THIS_MODULE, + .name = "tas", + .flags = I2C_DF_NOTIFY, + .attach_adapter = tas_attach_adapter, + .detach_client = tas_detach_client, +}; + +struct tas_driver_hooks_t *driver_hooks; + +int +tas_register_driver(struct tas_driver_hooks_t *hooks) +{ + driver_hooks = hooks; + return 0; +} + +int +tas_get_mixer_level(int mixer, uint *level) +{ + CALL(get_mixer_level,mixer,level); +} + +int +tas_set_mixer_level(int mixer,uint level) +{ + CALL(set_mixer_level,mixer,level); +} + +int +tas_enter_sleep(void) +{ + CALL0(enter_sleep); +} + +int +tas_leave_sleep(void) +{ + CALL0(leave_sleep); +} + +int +tas_supported_mixers(void) +{ + CALL0(supported_mixers); +} + +int +tas_mixer_is_stereo(int mixer) +{ + CALL(mixer_is_stereo,mixer); +} + +int +tas_stereo_mixers(void) +{ + CALL0(stereo_mixers); +} + +int +tas_output_device_change(int device_id,int layout_id,int speaker_id) +{ + CALL(output_device_change,device_id,layout_id,speaker_id); +} + +int +tas_device_ioctl(u_int cmd, u_long arg) +{ + CALL(device_ioctl,cmd,arg); +} + +int +tas_post_init(void) +{ + CALL0(post_init); +} + +static int +tas_detect_client(struct i2c_adapter *adapter, int address) +{ + static const char *client_name = "tas Digital Equalizer"; + struct i2c_client *new_client; + int rc = -ENODEV; + + if (!driver_hooks) { + printk(KERN_ERR "tas_detect_client called with no hooks !\n"); + return -ENODEV; + } + + new_client = kmalloc(sizeof(*new_client), GFP_KERNEL); + if (!new_client) + return -ENOMEM; + memset(new_client, 0, sizeof(*new_client)); + + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &tas_driver; + strlcpy(new_client->name, client_name, DEVICE_NAME_SIZE); + + if (driver_hooks->init(new_client)) + goto bail; + + /* Tell the i2c layer a new client has arrived */ + if (i2c_attach_client(new_client)) { + driver_hooks->uninit(dev_get_drvdata(&new_client->dev)); + goto bail; + } + + tas_client = new_client; + return 0; + bail: + tas_client = NULL; + kfree(new_client); + return rc; +} + +static int +tas_attach_adapter(struct i2c_adapter *adapter) +{ + if (!strncmp(adapter->name, "mac-io", 6)) + return tas_detect_client(adapter, tas_i2c_address); + return 0; +} + +static int +tas_detach_client(struct i2c_client *client) +{ + if (client == tas_client) { + driver_hooks->uninit(dev_get_drvdata(&client->dev)); + + i2c_detach_client(client); + kfree(client); + } + return 0; +} + +void +tas_cleanup(void) +{ + i2c_del_driver(&tas_driver); +} + +int __init +tas_init(int driver_id, const char *driver_name) +{ + u32* paddr; + + printk(KERN_INFO "tas driver [%s])\n", driver_name); + +#ifndef CONFIG_I2C_KEYWEST + request_module("i2c-keywest"); +#endif + tas_node = find_devices("deq"); + if (tas_node == NULL) + return -ENODEV; + paddr = (u32 *)get_property(tas_node, "i2c-address", NULL); + if (paddr) { + tas_i2c_address = (*paddr) >> 1; + printk(KERN_INFO "using i2c address: 0x%x from device-tree\n", + tas_i2c_address); + } else + printk(KERN_INFO "using i2c address: 0x%x (default)\n", + tas_i2c_address); + + return i2c_add_driver(&tas_driver); +} diff --git a/sound/oss/dmasound/tas_common.h b/sound/oss/dmasound/tas_common.h new file mode 100644 index 000000000000..3a6d48666db0 --- /dev/null +++ b/sound/oss/dmasound/tas_common.h @@ -0,0 +1,284 @@ +#ifndef _TAS_COMMON_H_ +#define _TAS_COMMON_H_ + +#include +#include +#include + +#define I2C_DRIVERID_TAS_BASE (0xFEBA) + +#define SET_4_20(shadow, offset, val) \ + do { \ + (shadow)[(offset)+0] = ((val) >> 16) & 0xff; \ + (shadow)[(offset)+1] = ((val) >> 8) & 0xff; \ + (shadow)[(offset)+2] = ((val) >> 0) & 0xff; \ + } while (0) + +#define GET_4_20(shadow, offset) \ + (((u_int)((shadow)[(offset)+0]) << 16) | \ + ((u_int)((shadow)[(offset)+1]) << 8) | \ + ((u_int)((shadow)[(offset)+2]) << 0)) + + +#define TAS_BIQUAD_FAST_LOAD 0x01 + +#define TAS_DRCE_ENABLE 0x01 +#define TAS_DRCE_ABOVE_RATIO 0x02 +#define TAS_DRCE_BELOW_RATIO 0x04 +#define TAS_DRCE_THRESHOLD 0x08 +#define TAS_DRCE_ENERGY 0x10 +#define TAS_DRCE_ATTACK 0x20 +#define TAS_DRCE_DECAY 0x40 + +#define TAS_DRCE_ALL 0x7f + + +#define TAS_OUTPUT_HEADPHONES 0x00 +#define TAS_OUTPUT_INTERNAL_SPKR 0x01 +#define TAS_OUTPUT_EXTERNAL_SPKR 0x02 + + +union tas_biquad_t { + struct { + int b0,b1,b2,a1,a2; + } coeff; + int buf[5]; +}; + +struct tas_biquad_ctrl_t { + u_int channel:4; + u_int filter:4; + + union tas_biquad_t data; +}; + +struct tas_biquad_ctrl_list_t { + int flags; + int filter_count; + struct tas_biquad_ctrl_t biquads[0]; +}; + +struct tas_ratio_t { + unsigned short val; /* 8.8 */ + unsigned short expand; /* 0 = compress, !0 = expand. */ +}; + +struct tas_drce_t { + unsigned short enable; + struct tas_ratio_t above; + struct tas_ratio_t below; + short threshold; /* dB, 8.8 signed */ + unsigned short energy; /* seconds, 4.12 unsigned */ + unsigned short attack; /* seconds, 4.12 unsigned */ + unsigned short decay; /* seconds, 4.12 unsigned */ +}; + +struct tas_drce_ctrl_t { + uint flags; + + struct tas_drce_t data; +}; + +struct tas_gain_t +{ + unsigned int *master; + unsigned int *treble; + unsigned int *bass; + unsigned int *mixer; +}; + +typedef char tas_shadow_t[0x45]; + +struct tas_data_t +{ + struct i2c_client *client; + tas_shadow_t *shadow; + uint mixer[SOUND_MIXER_NRDEVICES]; +}; + +typedef int (*tas_hook_init_t)(struct i2c_client *); +typedef int (*tas_hook_post_init_t)(struct tas_data_t *); +typedef void (*tas_hook_uninit_t)(struct tas_data_t *); + +typedef int (*tas_hook_get_mixer_level_t)(struct tas_data_t *,int,uint *); +typedef int (*tas_hook_set_mixer_level_t)(struct tas_data_t *,int,uint); + +typedef int (*tas_hook_enter_sleep_t)(struct tas_data_t *); +typedef int (*tas_hook_leave_sleep_t)(struct tas_data_t *); + +typedef int (*tas_hook_supported_mixers_t)(struct tas_data_t *); +typedef int (*tas_hook_mixer_is_stereo_t)(struct tas_data_t *,int); +typedef int (*tas_hook_stereo_mixers_t)(struct tas_data_t *); + +typedef int (*tas_hook_output_device_change_t)(struct tas_data_t *,int,int,int); +typedef int (*tas_hook_device_ioctl_t)(struct tas_data_t *,u_int,u_long); + +struct tas_driver_hooks_t { + /* + * All hardware initialisation must be performed in + * post_init(), as tas_dmasound_init() does a hardware reset. + * + * init() is called before tas_dmasound_init() so that + * ouput_device_change() is always called after i2c driver + * initialisation. The implication is that + * output_device_change() must cope with the fact that it + * may be called before post_init(). + */ + + tas_hook_init_t init; + tas_hook_post_init_t post_init; + tas_hook_uninit_t uninit; + + tas_hook_get_mixer_level_t get_mixer_level; + tas_hook_set_mixer_level_t set_mixer_level; + + tas_hook_enter_sleep_t enter_sleep; + tas_hook_leave_sleep_t leave_sleep; + + tas_hook_supported_mixers_t supported_mixers; + tas_hook_mixer_is_stereo_t mixer_is_stereo; + tas_hook_stereo_mixers_t stereo_mixers; + + tas_hook_output_device_change_t output_device_change; + tas_hook_device_ioctl_t device_ioctl; +}; + +enum tas_write_mode_t { + WRITE_HW = 0x01, + WRITE_SHADOW = 0x02, + WRITE_NORMAL = 0x03, + FORCE_WRITE = 0x04 +}; + +static inline uint +tas_mono_to_stereo(uint mono) +{ + mono &=0xff; + return mono | (mono<<8); +} + +/* + * Todo: make these functions a bit more efficient ! + */ +static inline int +tas_write_register( struct tas_data_t *self, + uint reg_num, + uint reg_width, + char *data, + uint write_mode) +{ + int rc; + + if (reg_width==0 || data==NULL || self==NULL) + return -EINVAL; + if (!(write_mode & FORCE_WRITE) && + !memcmp(data,self->shadow[reg_num],reg_width)) + return 0; + + if (write_mode & WRITE_SHADOW) + memcpy(self->shadow[reg_num],data,reg_width); + if (write_mode & WRITE_HW) { + rc=i2c_smbus_write_block_data(self->client, + reg_num, + reg_width, + data); + if (rc < 0) { + printk("tas: I2C block write failed \n"); + return rc; + } + } + return 0; +} + +static inline int +tas_sync_register( struct tas_data_t *self, + uint reg_num, + uint reg_width) +{ + int rc; + + if (reg_width==0 || self==NULL) + return -EINVAL; + rc=i2c_smbus_write_block_data(self->client, + reg_num, + reg_width, + self->shadow[reg_num]); + if (rc < 0) { + printk("tas: I2C block write failed \n"); + return rc; + } + return 0; +} + +static inline int +tas_write_byte_register( struct tas_data_t *self, + uint reg_num, + char data, + uint write_mode) +{ + if (self==NULL) + return -1; + if (!(write_mode & FORCE_WRITE) && data != self->shadow[reg_num][0]) + return 0; + if (write_mode & WRITE_SHADOW) + self->shadow[reg_num][0]=data; + if (write_mode & WRITE_HW) { + if (i2c_smbus_write_byte_data(self->client, reg_num, data) < 0) { + printk("tas: I2C byte write failed \n"); + return -1; + } + } + return 0; +} + +static inline int +tas_sync_byte_register( struct tas_data_t *self, + uint reg_num, + uint reg_width) +{ + if (reg_width==0 || self==NULL) + return -1; + if (i2c_smbus_write_byte_data( + self->client, reg_num, self->shadow[reg_num][0]) < 0) { + printk("tas: I2C byte write failed \n"); + return -1; + } + return 0; +} + +static inline int +tas_read_register( struct tas_data_t *self, + uint reg_num, + uint reg_width, + char *data) +{ + if (reg_width==0 || data==NULL || self==NULL) + return -1; + memcpy(data,self->shadow[reg_num],reg_width); + return 0; +} + +extern int tas_register_driver(struct tas_driver_hooks_t *hooks); + +extern int tas_get_mixer_level(int mixer,uint *level); +extern int tas_set_mixer_level(int mixer,uint level); +extern int tas_enter_sleep(void); +extern int tas_leave_sleep(void); +extern int tas_supported_mixers(void); +extern int tas_mixer_is_stereo(int mixer); +extern int tas_stereo_mixers(void); +extern int tas_output_device_change(int,int,int); +extern int tas_device_ioctl(u_int, u_long); + +extern void tas_cleanup(void); +extern int tas_init(int driver_id,const char *driver_name); +extern int tas_post_init(void); + +#endif /* _TAS_COMMON_H_ */ +/* + * Local Variables: + * tab-width: 8 + * indent-tabs-mode: t + * c-basic-offset: 8 + * End: + */ diff --git a/sound/oss/dmasound/tas_eq_prefs.h b/sound/oss/dmasound/tas_eq_prefs.h new file mode 100644 index 000000000000..3a994eda6abc --- /dev/null +++ b/sound/oss/dmasound/tas_eq_prefs.h @@ -0,0 +1,24 @@ +#ifndef _TAS_EQ_PREFS_H_ +#define _TAS_EQ_PREFS_H_ + +struct tas_eq_pref_t { + u_int sample_rate; + u_int device_id; + u_int output_id; + u_int speaker_id; + + struct tas_drce_t *drce; + + u_int filter_count; + struct tas_biquad_ctrl_t *biquads; +}; + +#endif /* _TAS_EQ_PREFS_H_ */ + +/* + * Local Variables: + * tab-width: 8 + * indent-tabs-mode: t + * c-basic-offset: 8 + * End: + */ diff --git a/sound/oss/dmasound/tas_ioctl.h b/sound/oss/dmasound/tas_ioctl.h new file mode 100644 index 000000000000..dccae3a40e01 --- /dev/null +++ b/sound/oss/dmasound/tas_ioctl.h @@ -0,0 +1,24 @@ +#ifndef _TAS_IOCTL_H_ +#define _TAS_IOCTL_H_ + +#include +#include + + +#define TAS_READ_EQ _SIOR('t',0,struct tas_biquad_ctrl_t) +#define TAS_WRITE_EQ _SIOW('t',0,struct tas_biquad_ctrl_t) + +#define TAS_READ_EQ_LIST _SIOR('t',1,struct tas_biquad_ctrl_t) +#define TAS_WRITE_EQ_LIST _SIOW('t',1,struct tas_biquad_ctrl_t) + +#define TAS_READ_EQ_FILTER_COUNT _SIOR('t',2,int) +#define TAS_READ_EQ_CHANNEL_COUNT _SIOR('t',3,int) + +#define TAS_READ_DRCE _SIOR('t',4,struct tas_drce_ctrl_t) +#define TAS_WRITE_DRCE _SIOW('t',4,struct tas_drce_ctrl_t) + +#define TAS_READ_DRCE_CAPS _SIOR('t',5,int) +#define TAS_READ_DRCE_MIN _SIOR('t',6,int) +#define TAS_READ_DRCE_MAX _SIOR('t',7,int) + +#endif diff --git a/sound/oss/dmasound/trans_16.c b/sound/oss/dmasound/trans_16.c new file mode 100644 index 000000000000..23562e947806 --- /dev/null +++ b/sound/oss/dmasound/trans_16.c @@ -0,0 +1,897 @@ +/* + * linux/sound/oss/dmasound/trans_16.c + * + * 16 bit translation routines. Only used by Power mac at present. + * + * See linux/sound/oss/dmasound/dmasound_core.c for copyright and + * history prior to 08/02/2001. + * + * 08/02/2001 Iain Sandoe + * split from dmasound_awacs.c + * 11/29/2003 Renzo Davoli (King Enzo) + * - input resampling (for soft rate < hard rate) + * - software line in gain control + */ + +#include +#include +#include "dmasound.h" + +static short dmasound_alaw2dma16[] ; +static short dmasound_ulaw2dma16[] ; + +static ssize_t pmac_ct_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t pmac_ct_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t pmac_ct_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t pmac_ct_s16(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t pmac_ct_u16(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); + +static ssize_t pmac_ctx_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t pmac_ctx_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t pmac_ctx_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t pmac_ctx_s16(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t pmac_ctx_u16(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); + +static ssize_t pmac_ct_s16_read(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t pmac_ct_u16_read(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); + +/*** Translations ************************************************************/ + +static int expand_data; /* Data for expanding */ + +static ssize_t pmac_ct_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + short *table = dmasound.soft.format == AFMT_MU_LAW + ? dmasound_ulaw2dma16 : dmasound_alaw2dma16; + ssize_t count, used; + short *p = (short *) &frame[*frameUsed]; + int val, stereo = dmasound.soft.stereo; + + frameLeft >>= 2; + if (stereo) + userCount >>= 1; + used = count = min_t(unsigned long, userCount, frameLeft); + while (count > 0) { + u_char data; + if (get_user(data, userPtr++)) + return -EFAULT; + val = table[data]; + *p++ = val; + if (stereo) { + if (get_user(data, userPtr++)) + return -EFAULT; + val = table[data]; + } + *p++ = val; + count--; + } + *frameUsed += used * 4; + return stereo? used * 2: used; +} + + +static ssize_t pmac_ct_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + short *p = (short *) &frame[*frameUsed]; + int val, stereo = dmasound.soft.stereo; + + frameLeft >>= 2; + if (stereo) + userCount >>= 1; + used = count = min_t(unsigned long, userCount, frameLeft); + while (count > 0) { + u_char data; + if (get_user(data, userPtr++)) + return -EFAULT; + val = data << 8; + *p++ = val; + if (stereo) { + if (get_user(data, userPtr++)) + return -EFAULT; + val = data << 8; + } + *p++ = val; + count--; + } + *frameUsed += used * 4; + return stereo? used * 2: used; +} + + +static ssize_t pmac_ct_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + short *p = (short *) &frame[*frameUsed]; + int val, stereo = dmasound.soft.stereo; + + frameLeft >>= 2; + if (stereo) + userCount >>= 1; + used = count = min_t(unsigned long, userCount, frameLeft); + while (count > 0) { + u_char data; + if (get_user(data, userPtr++)) + return -EFAULT; + val = (data ^ 0x80) << 8; + *p++ = val; + if (stereo) { + if (get_user(data, userPtr++)) + return -EFAULT; + val = (data ^ 0x80) << 8; + } + *p++ = val; + count--; + } + *frameUsed += used * 4; + return stereo? used * 2: used; +} + + +static ssize_t pmac_ct_s16(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + int stereo = dmasound.soft.stereo; + short *fp = (short *) &frame[*frameUsed]; + + frameLeft >>= 2; + userCount >>= (stereo? 2: 1); + used = count = min_t(unsigned long, userCount, frameLeft); + if (!stereo) { + short __user *up = (short __user *) userPtr; + while (count > 0) { + short data; + if (get_user(data, up++)) + return -EFAULT; + *fp++ = data; + *fp++ = data; + count--; + } + } else { + if (copy_from_user(fp, userPtr, count * 4)) + return -EFAULT; + } + *frameUsed += used * 4; + return stereo? used * 4: used * 2; +} + +static ssize_t pmac_ct_u16(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000); + int stereo = dmasound.soft.stereo; + short *fp = (short *) &frame[*frameUsed]; + short __user *up = (short __user *) userPtr; + + frameLeft >>= 2; + userCount >>= (stereo? 2: 1); + used = count = min_t(unsigned long, userCount, frameLeft); + while (count > 0) { + short data; + if (get_user(data, up++)) + return -EFAULT; + data ^= mask; + *fp++ = data; + if (stereo) { + if (get_user(data, up++)) + return -EFAULT; + data ^= mask; + } + *fp++ = data; + count--; + } + *frameUsed += used * 4; + return stereo? used * 4: used * 2; +} + + +static ssize_t pmac_ctx_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + unsigned short *table = (unsigned short *) + (dmasound.soft.format == AFMT_MU_LAW + ? dmasound_ulaw2dma16 : dmasound_alaw2dma16); + unsigned int data = expand_data; + unsigned int *p = (unsigned int *) &frame[*frameUsed]; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + int stereo = dmasound.soft.stereo; + + frameLeft >>= 2; + if (stereo) + userCount >>= 1; + 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]; + if (stereo) { + if (get_user(c, userPtr++)) + return -EFAULT; + data = (data << 16) + table[c]; + } else + data = (data << 16) + data; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft) * 4; + utotal -= userCount; + return stereo? utotal * 2: utotal; +} + +static ssize_t pmac_ctx_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + unsigned int *p = (unsigned int *) &frame[*frameUsed]; + unsigned int data = expand_data; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int stereo = dmasound.soft.stereo; + int utotal, ftotal; + + frameLeft >>= 2; + if (stereo) + userCount >>= 1; + 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 << 8; + if (stereo) { + if (get_user(c, userPtr++)) + return -EFAULT; + data = (data << 16) + (c << 8); + } else + data = (data << 16) + data; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft) * 4; + utotal -= userCount; + return stereo? utotal * 2: utotal; +} + + +static ssize_t pmac_ctx_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + unsigned int *p = (unsigned int *) &frame[*frameUsed]; + unsigned int data = expand_data; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int stereo = dmasound.soft.stereo; + int utotal, ftotal; + + frameLeft >>= 2; + if (stereo) + userCount >>= 1; + 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 ^ 0x80) << 8; + if (stereo) { + if (get_user(c, userPtr++)) + return -EFAULT; + data = (data << 16) + ((c ^ 0x80) << 8); + } else + data = (data << 16) + data; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft) * 4; + utotal -= userCount; + return stereo? utotal * 2: utotal; +} + + +static ssize_t pmac_ctx_s16(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + unsigned int *p = (unsigned int *) &frame[*frameUsed]; + unsigned int data = expand_data; + unsigned short __user *up = (unsigned short __user *) userPtr; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int stereo = dmasound.soft.stereo; + int utotal, ftotal; + + frameLeft >>= 2; + userCount >>= (stereo? 2: 1); + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + unsigned short c; + if (bal < 0) { + if (userCount == 0) + break; + if (get_user(data, up++)) + return -EFAULT; + if (stereo) { + if (get_user(c, up++)) + return -EFAULT; + data = (data << 16) + c; + } else + data = (data << 16) + data; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft) * 4; + utotal -= userCount; + return stereo? utotal * 4: utotal * 2; +} + + +static ssize_t pmac_ctx_u16(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000); + unsigned int *p = (unsigned int *) &frame[*frameUsed]; + unsigned int data = expand_data; + unsigned short __user *up = (unsigned short __user *) userPtr; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int stereo = dmasound.soft.stereo; + int utotal, ftotal; + + frameLeft >>= 2; + userCount >>= (stereo? 2: 1); + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + unsigned short c; + if (bal < 0) { + if (userCount == 0) + break; + if (get_user(data, up++)) + return -EFAULT; + data ^= mask; + if (stereo) { + if (get_user(c, up++)) + return -EFAULT; + data = (data << 16) + (c ^ mask); + } else + data = (data << 16) + data; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft) * 4; + utotal -= userCount; + return stereo? utotal * 4: utotal * 2; +} + +/* data in routines... */ + +static ssize_t pmac_ct_s8_read(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + short *p = (short *) &frame[*frameUsed]; + int val, stereo = dmasound.soft.stereo; + + frameLeft >>= 2; + if (stereo) + userCount >>= 1; + used = count = min_t(unsigned long, userCount, frameLeft); + while (count > 0) { + u_char data; + + val = *p++; + val = (val * software_input_volume) >> 7; + data = val >> 8; + if (put_user(data, (u_char __user *)userPtr++)) + return -EFAULT; + if (stereo) { + val = *p; + val = (val * software_input_volume) >> 7; + data = val >> 8; + if (put_user(data, (u_char __user *)userPtr++)) + return -EFAULT; + } + p++; + count--; + } + *frameUsed += used * 4; + return stereo? used * 2: used; +} + + +static ssize_t pmac_ct_u8_read(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + short *p = (short *) &frame[*frameUsed]; + int val, stereo = dmasound.soft.stereo; + + frameLeft >>= 2; + if (stereo) + userCount >>= 1; + used = count = min_t(unsigned long, userCount, frameLeft); + while (count > 0) { + u_char data; + + val = *p++; + val = (val * software_input_volume) >> 7; + data = (val >> 8) ^ 0x80; + if (put_user(data, (u_char __user *)userPtr++)) + return -EFAULT; + if (stereo) { + val = *p; + val = (val * software_input_volume) >> 7; + data = (val >> 8) ^ 0x80; + if (put_user(data, (u_char __user *)userPtr++)) + return -EFAULT; + } + p++; + count--; + } + *frameUsed += used * 4; + return stereo? used * 2: used; +} + +static ssize_t pmac_ct_s16_read(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + int stereo = dmasound.soft.stereo; + short *fp = (short *) &frame[*frameUsed]; + short __user *up = (short __user *) userPtr; + + frameLeft >>= 2; + userCount >>= (stereo? 2: 1); + used = count = min_t(unsigned long, userCount, frameLeft); + while (count > 0) { + short data; + + data = *fp++; + data = (data * software_input_volume) >> 7; + if (put_user(data, up++)) + return -EFAULT; + if (stereo) { + data = *fp; + data = (data * software_input_volume) >> 7; + if (put_user(data, up++)) + return -EFAULT; + } + fp++; + count--; + } + *frameUsed += used * 4; + return stereo? used * 4: used * 2; +} + +static ssize_t pmac_ct_u16_read(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000); + int stereo = dmasound.soft.stereo; + short *fp = (short *) &frame[*frameUsed]; + short __user *up = (short __user *) userPtr; + + frameLeft >>= 2; + userCount >>= (stereo? 2: 1); + used = count = min_t(unsigned long, userCount, frameLeft); + while (count > 0) { + int data; + + data = *fp++; + data = (data * software_input_volume) >> 7; + data ^= mask; + if (put_user(data, up++)) + return -EFAULT; + if (stereo) { + data = *fp; + data = (data * software_input_volume) >> 7; + data ^= mask; + if (put_user(data, up++)) + return -EFAULT; + } + fp++; + count--; + } + *frameUsed += used * 4; + return stereo? used * 4: used * 2; +} + +/* data in routines (reducing speed)... */ + +static ssize_t pmac_ctx_s8_read(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + short *p = (short *) &frame[*frameUsed]; + int bal = expand_read_bal; + int vall,valr, stereo = dmasound.soft.stereo; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + frameLeft >>= 2; + if (stereo) + userCount >>= 1; + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char data; + + if (bal<0 && userCount == 0) + break; + vall = *p++; + vall = (vall * software_input_volume) >> 7; + if (stereo) { + valr = *p; + valr = (valr * software_input_volume) >> 7; + } + p++; + if (bal < 0) { + data = vall >> 8; + if (put_user(data, (u_char __user *)userPtr++)) + return -EFAULT; + if (stereo) { + data = valr >> 8; + if (put_user(data, (u_char __user *)userPtr++)) + return -EFAULT; + } + userCount--; + bal += hSpeed; + } + frameLeft--; + bal -= sSpeed; + } + expand_read_bal=bal; + *frameUsed += (ftotal - frameLeft) * 4; + utotal -= userCount; + return stereo? utotal * 2: utotal; +} + + +static ssize_t pmac_ctx_u8_read(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + short *p = (short *) &frame[*frameUsed]; + int bal = expand_read_bal; + int vall,valr, stereo = dmasound.soft.stereo; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + frameLeft >>= 2; + if (stereo) + userCount >>= 1; + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char data; + + if (bal<0 && userCount == 0) + break; + + vall = *p++; + vall = (vall * software_input_volume) >> 7; + if (stereo) { + valr = *p; + valr = (valr * software_input_volume) >> 7; + } + p++; + if (bal < 0) { + data = (vall >> 8) ^ 0x80; + if (put_user(data, (u_char __user *)userPtr++)) + return -EFAULT; + if (stereo) { + data = (valr >> 8) ^ 0x80; + if (put_user(data, (u_char __user *)userPtr++)) + return -EFAULT; + } + userCount--; + bal += hSpeed; + } + frameLeft--; + bal -= sSpeed; + } + expand_read_bal=bal; + *frameUsed += (ftotal - frameLeft) * 4; + utotal -= userCount; + return stereo? utotal * 2: utotal; +} + +static ssize_t pmac_ctx_s16_read(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + int bal = expand_read_bal; + short *fp = (short *) &frame[*frameUsed]; + short __user *up = (short __user *) userPtr; + int stereo = dmasound.soft.stereo; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + frameLeft >>= 2; + userCount >>= (stereo? 2: 1); + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + int datal,datar; + + if (bal<0 && userCount == 0) + break; + + datal = *fp++; + datal = (datal * software_input_volume) >> 7; + if (stereo) { + datar = *fp; + datar = (datar * software_input_volume) >> 7; + } + fp++; + if (bal < 0) { + if (put_user(datal, up++)) + return -EFAULT; + if (stereo) { + if (put_user(datar, up++)) + return -EFAULT; + } + userCount--; + bal += hSpeed; + } + frameLeft--; + bal -= sSpeed; + } + expand_read_bal=bal; + *frameUsed += (ftotal - frameLeft) * 4; + utotal -= userCount; + return stereo? utotal * 4: utotal * 2; +} + +static ssize_t pmac_ctx_u16_read(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + int bal = expand_read_bal; + int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000); + short *fp = (short *) &frame[*frameUsed]; + short __user *up = (short __user *) userPtr; + int stereo = dmasound.soft.stereo; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + frameLeft >>= 2; + userCount >>= (stereo? 2: 1); + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + int datal,datar; + + if (bal<0 && userCount == 0) + break; + + datal = *fp++; + datal = (datal * software_input_volume) >> 7; + datal ^= mask; + if (stereo) { + datar = *fp; + datar = (datar * software_input_volume) >> 7; + datar ^= mask; + } + fp++; + if (bal < 0) { + if (put_user(datal, up++)) + return -EFAULT; + if (stereo) { + if (put_user(datar, up++)) + return -EFAULT; + } + userCount--; + bal += hSpeed; + } + frameLeft--; + bal -= sSpeed; + } + expand_read_bal=bal; + *frameUsed += (ftotal - frameLeft) * 4; + utotal -= userCount; + return stereo? utotal * 4: utotal * 2; +} + + +TRANS transAwacsNormal = { + .ct_ulaw= pmac_ct_law, + .ct_alaw= pmac_ct_law, + .ct_s8= pmac_ct_s8, + .ct_u8= pmac_ct_u8, + .ct_s16be= pmac_ct_s16, + .ct_u16be= pmac_ct_u16, + .ct_s16le= pmac_ct_s16, + .ct_u16le= pmac_ct_u16, +}; + +TRANS transAwacsExpand = { + .ct_ulaw= pmac_ctx_law, + .ct_alaw= pmac_ctx_law, + .ct_s8= pmac_ctx_s8, + .ct_u8= pmac_ctx_u8, + .ct_s16be= pmac_ctx_s16, + .ct_u16be= pmac_ctx_u16, + .ct_s16le= pmac_ctx_s16, + .ct_u16le= pmac_ctx_u16, +}; + +TRANS transAwacsNormalRead = { + .ct_s8= pmac_ct_s8_read, + .ct_u8= pmac_ct_u8_read, + .ct_s16be= pmac_ct_s16_read, + .ct_u16be= pmac_ct_u16_read, + .ct_s16le= pmac_ct_s16_read, + .ct_u16le= pmac_ct_u16_read, +}; + +TRANS transAwacsExpandRead = { + .ct_s8= pmac_ctx_s8_read, + .ct_u8= pmac_ctx_u8_read, + .ct_s16be= pmac_ctx_s16_read, + .ct_u16be= pmac_ctx_u16_read, + .ct_s16le= pmac_ctx_s16_read, + .ct_u16le= pmac_ctx_u16_read, +}; + +/* translation tables */ +/* 16 bit mu-law */ + +static short dmasound_ulaw2dma16[] = { + -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, + -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, + -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, + -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0, +}; + +/* 16 bit A-law */ + +static short dmasound_alaw2dma16[] = { + -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, + -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, + -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, + -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, + -22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944, + -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, + -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, + -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, + -344, -328, -376, -360, -280, -264, -312, -296, + -472, -456, -504, -488, -408, -392, -440, -424, + -88, -72, -120, -104, -24, -8, -56, -40, + -216, -200, -248, -232, -152, -136, -184, -168, + -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, + -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, -592, + -944, -912, -1008, -976, -816, -784, -880, -848, + 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, + 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, + 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, + 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, + 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, + 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, + 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, + 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, + 344, 328, 376, 360, 280, 264, 312, 296, + 472, 456, 504, 488, 408, 392, 440, 424, + 88, 72, 120, 104, 24, 8, 56, 40, + 216, 200, 248, 232, 152, 136, 184, 168, + 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, + 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, + 688, 656, 752, 720, 560, 528, 624, 592, + 944, 912, 1008, 976, 816, 784, 880, 848, +}; diff --git a/sound/oss/emu10k1/8010.h b/sound/oss/emu10k1/8010.h new file mode 100644 index 000000000000..61c6c42bbc36 --- /dev/null +++ b/sound/oss/emu10k1/8010.h @@ -0,0 +1,737 @@ +/* + ********************************************************************** + * 8010.h + * Copyright 1999-2001 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * November 2, 1999 Alan Cox Cleaned of 8bit chars, DOS + * line endings + * December 8, 1999 Jon Taylor Added lots of new register info + * May 16, 2001 Daniel Bertrand Added unofficial DBG register info + * Oct-Nov 2001 D.B. Added unofficial Audigy registers + * + ********************************************************************** + * + * 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 _8010_H +#define _8010_H + +#include + +// Driver version: +#define MAJOR_VER 0 +#define MINOR_VER 20 +#define DRIVER_VERSION "0.20a" + + +// Audigy specify registers are prefixed with 'A_' + +/************************************************************************************************/ +/* PCI function 0 registers, address = + PCIBASE0 */ +/************************************************************************************************/ + +#define PTR 0x00 /* Indexed register set pointer register */ + /* NOTE: The CHANNELNUM and ADDRESS words can */ + /* be modified independently of each other. */ +#define PTR_CHANNELNUM_MASK 0x0000003f /* For each per-channel register, indicates the */ + /* channel number of the register to be */ + /* accessed. For non per-channel registers the */ + /* value should be set to zero. */ +#define PTR_ADDRESS_MASK 0x07ff0000 /* Register index */ + +#define DATA 0x04 /* Indexed register set data register */ + +#define IPR 0x08 /* Global interrupt pending register */ + /* Clear pending interrupts by writing a 1 to */ + /* the relevant bits and zero to the other bits */ + +/* The next two interrupts are for the midi port on the Audigy Drive (A_MPU1) */ +#define A_IPR_MIDITRANSBUFEMPTY2 0x10000000 /* MIDI UART transmit buffer empty */ +#define A_IPR_MIDIRECVBUFEMPTY2 0x08000000 /* MIDI UART receive buffer empty */ + +#define IPR_SAMPLERATETRACKER 0x01000000 /* Sample rate tracker lock status change */ +#define IPR_FXDSP 0x00800000 /* Enable FX DSP interrupts */ +#define IPR_FORCEINT 0x00400000 /* Force Sound Blaster interrupt */ +#define IPR_PCIERROR 0x00200000 /* PCI bus error */ +#define IPR_VOLINCR 0x00100000 /* Volume increment button pressed */ +#define IPR_VOLDECR 0x00080000 /* Volume decrement button pressed */ +#define IPR_MUTE 0x00040000 /* Mute button pressed */ +#define IPR_MICBUFFULL 0x00020000 /* Microphone buffer full */ +#define IPR_MICBUFHALFFULL 0x00010000 /* Microphone buffer half full */ +#define IPR_ADCBUFFULL 0x00008000 /* ADC buffer full */ +#define IPR_ADCBUFHALFFULL 0x00004000 /* ADC buffer half full */ +#define IPR_EFXBUFFULL 0x00002000 /* Effects buffer full */ +#define IPR_EFXBUFHALFFULL 0x00001000 /* Effects buffer half full */ +#define IPR_GPSPDIFSTATUSCHANGE 0x00000800 /* GPSPDIF channel status change */ +#define IPR_CDROMSTATUSCHANGE 0x00000400 /* CD-ROM channel status change */ +#define IPR_INTERVALTIMER 0x00000200 /* Interval timer terminal count */ +#define IPR_MIDITRANSBUFEMPTY 0x00000100 /* MIDI UART transmit buffer empty */ +#define IPR_MIDIRECVBUFEMPTY 0x00000080 /* MIDI UART receive buffer empty */ +#define IPR_CHANNELLOOP 0x00000040 /* One or more channel loop interrupts pending */ +#define IPR_CHANNELNUMBERMASK 0x0000003f /* When IPR_CHANNELLOOP is set, indicates the */ + /* Highest set channel in CLIPL or CLIPH. When */ + /* IP is written with CL set, the bit in CLIPL */ + /* or CLIPH corresponding to the CIN value */ + /* written will be cleared. */ +#define A_IPR_MIDITRANSBUFEMPTY1 IPR_MIDITRANSBUFEMPTY /* MIDI UART transmit buffer empty */ +#define A_IPR_MIDIRECVBUFEMPTY1 IPR_MIDIRECVBUFEMPTY /* MIDI UART receive buffer empty */ + + + +#define INTE 0x0c /* Interrupt enable register */ +#define INTE_VIRTUALSB_MASK 0xc0000000 /* Virtual Soundblaster I/O port capture */ +#define INTE_VIRTUALSB_220 0x00000000 /* Capture at I/O base address 0x220-0x22f */ +#define INTE_VIRTUALSB_240 0x40000000 /* Capture at I/O base address 0x240 */ +#define INTE_VIRTUALSB_260 0x80000000 /* Capture at I/O base address 0x260 */ +#define INTE_VIRTUALSB_280 0xc0000000 /* Capture at I/O base address 0x280 */ +#define INTE_VIRTUALMPU_MASK 0x30000000 /* Virtual MPU I/O port capture */ +#define INTE_VIRTUALMPU_300 0x00000000 /* Capture at I/O base address 0x300-0x301 */ +#define INTE_VIRTUALMPU_310 0x10000000 /* Capture at I/O base address 0x310 */ +#define INTE_VIRTUALMPU_320 0x20000000 /* Capture at I/O base address 0x320 */ +#define INTE_VIRTUALMPU_330 0x30000000 /* Capture at I/O base address 0x330 */ +#define INTE_MASTERDMAENABLE 0x08000000 /* Master DMA emulation at 0x000-0x00f */ +#define INTE_SLAVEDMAENABLE 0x04000000 /* Slave DMA emulation at 0x0c0-0x0df */ +#define INTE_MASTERPICENABLE 0x02000000 /* Master PIC emulation at 0x020-0x021 */ +#define INTE_SLAVEPICENABLE 0x01000000 /* Slave PIC emulation at 0x0a0-0x0a1 */ +#define INTE_VSBENABLE 0x00800000 /* Enable virtual Soundblaster */ +#define INTE_ADLIBENABLE 0x00400000 /* Enable AdLib emulation at 0x388-0x38b */ +#define INTE_MPUENABLE 0x00200000 /* Enable virtual MPU */ +#define INTE_FORCEINT 0x00100000 /* Continuously assert INTAN */ + +#define INTE_MRHANDENABLE 0x00080000 /* Enable the "Mr. Hand" logic */ + /* NOTE: There is no reason to use this under */ + /* Linux, and it will cause odd hardware */ + /* behavior and possibly random segfaults and */ + /* lockups if enabled. */ + +/* The next two interrupts are for the midi port on the Audigy Drive (A_MPU1) */ +#define A_INTE_MIDITXENABLE2 0x00020000 /* Enable MIDI transmit-buffer-empty interrupts */ +#define A_INTE_MIDIRXENABLE2 0x00010000 /* Enable MIDI receive-buffer-empty interrupts */ + + +#define INTE_SAMPLERATETRACKER 0x00002000 /* Enable sample rate tracker interrupts */ + /* NOTE: This bit must always be enabled */ +#define INTE_FXDSPENABLE 0x00001000 /* Enable FX DSP interrupts */ +#define INTE_PCIERRORENABLE 0x00000800 /* Enable PCI bus error interrupts */ +#define INTE_VOLINCRENABLE 0x00000400 /* Enable volume increment button interrupts */ +#define INTE_VOLDECRENABLE 0x00000200 /* Enable volume decrement button interrupts */ +#define INTE_MUTEENABLE 0x00000100 /* Enable mute button interrupts */ +#define INTE_MICBUFENABLE 0x00000080 /* Enable microphone buffer interrupts */ +#define INTE_ADCBUFENABLE 0x00000040 /* Enable ADC buffer interrupts */ +#define INTE_EFXBUFENABLE 0x00000020 /* Enable Effects buffer interrupts */ +#define INTE_GPSPDIFENABLE 0x00000010 /* Enable GPSPDIF status interrupts */ +#define INTE_CDSPDIFENABLE 0x00000008 /* Enable CDSPDIF status interrupts */ +#define INTE_INTERVALTIMERENB 0x00000004 /* Enable interval timer interrupts */ +#define INTE_MIDITXENABLE 0x00000002 /* Enable MIDI transmit-buffer-empty interrupts */ +#define INTE_MIDIRXENABLE 0x00000001 /* Enable MIDI receive-buffer-empty interrupts */ + +/* The next two interrupts are for the midi port on the Audigy (A_MPU2) */ +#define A_INTE_MIDITXENABLE1 INTE_MIDITXENABLE +#define A_INTE_MIDIRXENABLE1 INTE_MIDIRXENABLE + +#define WC 0x10 /* Wall Clock register */ +#define WC_SAMPLECOUNTER_MASK 0x03FFFFC0 /* Sample periods elapsed since reset */ +#define WC_SAMPLECOUNTER 0x14060010 +#define WC_CURRENTCHANNEL 0x0000003F /* Channel [0..63] currently being serviced */ + /* NOTE: Each channel takes 1/64th of a sample */ + /* period to be serviced. */ + +#define HCFG 0x14 /* Hardware config register */ + /* NOTE: There is no reason to use the legacy */ + /* SoundBlaster emulation stuff described below */ + /* under Linux, and all kinds of weird hardware */ + /* behavior can result if you try. Don't. */ +#define HCFG_LEGACYFUNC_MASK 0xe0000000 /* Legacy function number */ +#define HCFG_LEGACYFUNC_MPU 0x00000000 /* Legacy MPU */ +#define HCFG_LEGACYFUNC_SB 0x40000000 /* Legacy SB */ +#define HCFG_LEGACYFUNC_AD 0x60000000 /* Legacy AD */ +#define HCFG_LEGACYFUNC_MPIC 0x80000000 /* Legacy MPIC */ +#define HCFG_LEGACYFUNC_MDMA 0xa0000000 /* Legacy MDMA */ +#define HCFG_LEGACYFUNC_SPCI 0xc0000000 /* Legacy SPCI */ +#define HCFG_LEGACYFUNC_SDMA 0xe0000000 /* Legacy SDMA */ +#define HCFG_IOCAPTUREADDR 0x1f000000 /* The 4 LSBs of the captured I/O address. */ +#define HCFG_LEGACYWRITE 0x00800000 /* 1 = write, 0 = read */ +#define HCFG_LEGACYWORD 0x00400000 /* 1 = word, 0 = byte */ +#define HCFG_LEGACYINT 0x00200000 /* 1 = legacy event captured. Write 1 to clear. */ + /* NOTE: The rest of the bits in this register */ + /* _are_ relevant under Linux. */ +#define HCFG_CODECFORMAT_MASK 0x00070000 /* CODEC format */ +#define HCFG_CODECFORMAT_AC97 0x00000000 /* AC97 CODEC format -- Primary Output */ +#define HCFG_CODECFORMAT_I2S 0x00010000 /* I2S CODEC format -- Secondary (Rear) Output */ +#define HCFG_GPINPUT0 0x00004000 /* External pin112 */ +#define HCFG_GPINPUT1 0x00002000 /* External pin110 */ + +#define HCFG_GPOUTPUT_MASK 0x00001c00 /* External pins which may be controlled */ +#define HCFG_GPOUT0 0x00001000 /* set to enable digital out on 5.1 cards */ + +#define HCFG_JOYENABLE 0x00000200 /* Internal joystick enable */ +#define HCFG_PHASETRACKENABLE 0x00000100 /* Phase tracking enable */ + /* 1 = Force all 3 async digital inputs to use */ + /* the same async sample rate tracker (ZVIDEO) */ +#define HCFG_AC3ENABLE_MASK 0x0x0000e0 /* AC3 async input control - Not implemented */ +#define HCFG_AC3ENABLE_ZVIDEO 0x00000080 /* Channels 0 and 1 replace ZVIDEO */ +#define HCFG_AC3ENABLE_CDSPDIF 0x00000040 /* Channels 0 and 1 replace CDSPDIF */ +#define HCFG_AC3ENABLE_GPSPDIF 0x00000020 /* Channels 0 and 1 replace GPSPDIF */ +#define HCFG_AUTOMUTE 0x00000010 /* When set, the async sample rate convertors */ + /* will automatically mute their output when */ + /* they are not rate-locked to the external */ + /* async audio source */ +#define HCFG_LOCKSOUNDCACHE 0x00000008 /* 1 = Cancel bustmaster accesses to soundcache */ + /* NOTE: This should generally never be used. */ +#define HCFG_LOCKTANKCACHE_MASK 0x00000004 /* 1 = Cancel bustmaster accesses to tankcache */ + /* NOTE: This should generally never be used. */ +#define HCFG_LOCKTANKCACHE 0x01020014 +#define HCFG_MUTEBUTTONENABLE 0x00000002 /* 1 = Master mute button sets AUDIOENABLE = 0. */ + /* NOTE: This is a 'cheap' way to implement a */ + /* master mute function on the mute button, and */ + /* in general should not be used unless a more */ + /* sophisticated master mute function has not */ + /* been written. */ +#define HCFG_AUDIOENABLE 0x00000001 /* 0 = CODECs transmit zero-valued samples */ + /* Should be set to 1 when the EMU10K1 is */ + /* completely initialized. */ + +//For Audigy, MPU port move to 0x70-0x74 ptr register + +#define MUDATA 0x18 /* MPU401 data register (8 bits) */ + +#define MUCMD 0x19 /* MPU401 command register (8 bits) */ +#define MUCMD_RESET 0xff /* RESET command */ +#define MUCMD_ENTERUARTMODE 0x3f /* Enter_UART_mode command */ + /* NOTE: All other commands are ignored */ + +#define MUSTAT MUCMD /* MPU401 status register (8 bits) */ +#define MUSTAT_IRDYN 0x80 /* 0 = MIDI data or command ACK */ +#define MUSTAT_ORDYN 0x40 /* 0 = MUDATA can accept a command or data */ + +#define A_IOCFG 0x18 /* GPIO on Audigy card (16bits) */ +#define A_GPINPUT_MASK 0xff00 +#define A_GPOUTPUT_MASK 0x00ff + +#define TIMER 0x1a /* Timer terminal count register (16-bit) */ + /* NOTE: After the rate is changed, a maximum */ + /* of 1024 sample periods should be allowed */ + /* before the new rate is guaranteed accurate. */ +#define TIMER_RATE_MASK 0x03ff /* Timer interrupt rate in sample periods */ + /* 0 == 1024 periods, [1..4] are not useful */ + +#define AC97DATA 0x1c /* AC97 register set data register (16 bit) */ + +#define AC97ADDRESS 0x1e /* AC97 register set address register (8 bit) */ +#define AC97ADDRESS_READY 0x80 /* Read-only bit, reflects CODEC READY signal */ +#define AC97ADDRESS_ADDRESS 0x7f /* Address of indexed AC97 register */ + +/********************************************************************************************************/ +/* Emu10k1 pointer-offset register set, accessed through the PTR and DATA registers */ +/********************************************************************************************************/ + +#define CPF 0x00 /* Current pitch and fraction register */ +#define CPF_CURRENTPITCH_MASK 0xffff0000 /* Current pitch (linear, 0x4000 == unity pitch shift) */ +#define CPF_CURRENTPITCH 0x10100000 +#define CPF_STEREO_MASK 0x00008000 /* 1 = Even channel interleave, odd channel locked */ +#define CPF_STOP_MASK 0x00004000 /* 1 = Current pitch forced to 0 */ +#define CPF_FRACADDRESS_MASK 0x00003fff /* Linear fractional address of the current channel */ + +#define PTRX 0x01 /* Pitch target and send A/B amounts register */ +#define PTRX_PITCHTARGET_MASK 0xffff0000 /* Pitch target of specified channel */ +#define PTRX_PITCHTARGET 0x10100001 +#define PTRX_FXSENDAMOUNT_A_MASK 0x0000ff00 /* Linear level of channel output sent to FX send bus A */ +#define PTRX_FXSENDAMOUNT_A 0x08080001 +#define PTRX_FXSENDAMOUNT_B_MASK 0x000000ff /* Linear level of channel output sent to FX send bus B */ +#define PTRX_FXSENDAMOUNT_B 0x08000001 + +#define CVCF 0x02 /* Current volume and filter cutoff register */ +#define CVCF_CURRENTVOL_MASK 0xffff0000 /* Current linear volume of specified channel */ +#define CVCF_CURRENTVOL 0x10100002 +#define CVCF_CURRENTFILTER_MASK 0x0000ffff /* Current filter cutoff frequency of specified channel */ +#define CVCF_CURRENTFILTER 0x10000002 + +#define VTFT 0x03 /* Volume target and filter cutoff target register */ +#define VTFT_VOLUMETARGET_MASK 0xffff0000 /* Volume target of specified channel */ +#define VTFT_FILTERTARGET_MASK 0x0000ffff /* Filter cutoff target of specified channel */ + +#define Z1 0x05 /* Filter delay memory 1 register */ + +#define Z2 0x04 /* Filter delay memory 2 register */ + +#define PSST 0x06 /* Send C amount and loop start address register */ +#define PSST_FXSENDAMOUNT_C_MASK 0xff000000 /* Linear level of channel output sent to FX send bus C */ + +#define PSST_FXSENDAMOUNT_C 0x08180006 + +#define PSST_LOOPSTARTADDR_MASK 0x00ffffff /* Loop start address of the specified channel */ +#define PSST_LOOPSTARTADDR 0x18000006 + +#define DSL 0x07 /* Send D amount and loop start address register */ +#define DSL_FXSENDAMOUNT_D_MASK 0xff000000 /* Linear level of channel output sent to FX send bus D */ + +#define DSL_FXSENDAMOUNT_D 0x08180007 + +#define DSL_LOOPENDADDR_MASK 0x00ffffff /* Loop end address of the specified channel */ +#define DSL_LOOPENDADDR 0x18000007 + +#define CCCA 0x08 /* Filter Q, interp. ROM, byte size, cur. addr register */ +#define CCCA_RESONANCE 0xf0000000 /* Lowpass filter resonance (Q) height */ +#define CCCA_INTERPROMMASK 0x0e000000 /* Selects passband of interpolation ROM */ + /* 1 == full band, 7 == lowpass */ + /* ROM 0 is used when pitch shifting downward or less */ + /* then 3 semitones upward. Increasingly higher ROM */ + /* numbers are used, typically in steps of 3 semitones, */ + /* as upward pitch shifting is performed. */ +#define CCCA_INTERPROM_0 0x00000000 /* Select interpolation ROM 0 */ +#define CCCA_INTERPROM_1 0x02000000 /* Select interpolation ROM 1 */ +#define CCCA_INTERPROM_2 0x04000000 /* Select interpolation ROM 2 */ +#define CCCA_INTERPROM_3 0x06000000 /* Select interpolation ROM 3 */ +#define CCCA_INTERPROM_4 0x08000000 /* Select interpolation ROM 4 */ +#define CCCA_INTERPROM_5 0x0a000000 /* Select interpolation ROM 5 */ +#define CCCA_INTERPROM_6 0x0c000000 /* Select interpolation ROM 6 */ +#define CCCA_INTERPROM_7 0x0e000000 /* Select interpolation ROM 7 */ +#define CCCA_8BITSELECT 0x01000000 /* 1 = Sound memory for this channel uses 8-bit samples */ +#define CCCA_CURRADDR_MASK 0x00ffffff /* Current address of the selected channel */ +#define CCCA_CURRADDR 0x18000008 + +#define CCR 0x09 /* Cache control register */ +#define CCR_CACHEINVALIDSIZE 0x07190009 +#define CCR_CACHEINVALIDSIZE_MASK 0xfe000000 /* Number of invalid samples cache for this channel */ +#define CCR_CACHELOOPFLAG 0x01000000 /* 1 = Cache has a loop service pending */ +#define CCR_INTERLEAVEDSAMPLES 0x00800000 /* 1 = A cache service will fetch interleaved samples */ +#define CCR_WORDSIZEDSAMPLES 0x00400000 /* 1 = A cache service will fetch word sized samples */ +#define CCR_READADDRESS 0x06100009 +#define CCR_READADDRESS_MASK 0x003f0000 /* Location of cache just beyond current cache service */ +#define CCR_LOOPINVALSIZE 0x0000fe00 /* Number of invalid samples in cache prior to loop */ + /* NOTE: This is valid only if CACHELOOPFLAG is set */ +#define CCR_LOOPFLAG 0x00000100 /* Set for a single sample period when a loop occurs */ +#define CCR_CACHELOOPADDRHI 0x000000ff /* DSL_LOOPSTARTADDR's hi byte if CACHELOOPFLAG is set */ + +#define CLP 0x0a /* Cache loop register (valid if CCR_CACHELOOPFLAG = 1) */ + /* NOTE: This register is normally not used */ +#define CLP_CACHELOOPADDR 0x0000ffff /* Cache loop address (DSL_LOOPSTARTADDR [0..15]) */ + +#define FXRT 0x0b /* Effects send routing register */ + /* NOTE: It is illegal to assign the same routing to */ + /* two effects sends. */ +#define FXRT_CHANNELA 0x000f0000 /* Effects send bus number for channel's effects send A */ +#define FXRT_CHANNELB 0x00f00000 /* Effects send bus number for channel's effects send B */ +#define FXRT_CHANNELC 0x0f000000 /* Effects send bus number for channel's effects send C */ +#define FXRT_CHANNELD 0xf0000000 /* Effects send bus number for channel's effects send D */ + +#define MAPA 0x0c /* Cache map A */ + +#define MAPB 0x0d /* Cache map B */ + +#define MAP_PTE_MASK 0xffffe000 /* The 19 MSBs of the PTE indexed by the PTI */ +#define MAP_PTI_MASK 0x00001fff /* The 13 bit index to one of the 8192 PTE dwords */ + +#define ENVVOL 0x10 /* Volume envelope register */ +#define ENVVOL_MASK 0x0000ffff /* Current value of volume envelope state variable */ + /* 0x8000-n == 666*n usec delay */ + +#define ATKHLDV 0x11 /* Volume envelope hold and attack register */ +#define ATKHLDV_PHASE0 0x00008000 /* 0 = Begin attack phase */ +#define ATKHLDV_HOLDTIME_MASK 0x00007f00 /* Envelope hold time (127-n == n*88.2msec) */ +#define ATKHLDV_ATTACKTIME_MASK 0x0000007f /* Envelope attack time, log encoded */ + /* 0 = infinite, 1 = 10.9msec, ... 0x7f = 5.5msec */ + +#define DCYSUSV 0x12 /* Volume envelope sustain and decay register */ +#define DCYSUSV_PHASE1_MASK 0x00008000 /* 0 = Begin attack phase, 1 = begin release phase */ +#define DCYSUSV_SUSTAINLEVEL_MASK 0x00007f00 /* 127 = full, 0 = off, 0.75dB increments */ +#define DCYSUSV_CHANNELENABLE_MASK 0x00000080 /* 1 = Inhibit envelope engine from writing values in */ + /* this channel and from writing to pitch, filter and */ + /* volume targets. */ +#define DCYSUSV_DECAYTIME_MASK 0x0000007f /* Volume envelope decay time, log encoded */ + /* 0 = 43.7msec, 1 = 21.8msec, 0x7f = 22msec */ + +#define LFOVAL1 0x13 /* Modulation LFO value */ +#define LFOVAL_MASK 0x0000ffff /* Current value of modulation LFO state variable */ + /* 0x8000-n == 666*n usec delay */ + +#define ENVVAL 0x14 /* Modulation envelope register */ +#define ENVVAL_MASK 0x0000ffff /* Current value of modulation envelope state variable */ + /* 0x8000-n == 666*n usec delay */ + +#define ATKHLDM 0x15 /* Modulation envelope hold and attack register */ +#define ATKHLDM_PHASE0 0x00008000 /* 0 = Begin attack phase */ +#define ATKHLDM_HOLDTIME 0x00007f00 /* Envelope hold time (127-n == n*42msec) */ +#define ATKHLDM_ATTACKTIME 0x0000007f /* Envelope attack time, log encoded */ + /* 0 = infinite, 1 = 11msec, ... 0x7f = 5.5msec */ + +#define DCYSUSM 0x16 /* Modulation envelope decay and sustain register */ +#define DCYSUSM_PHASE1_MASK 0x00008000 /* 0 = Begin attack phase, 1 = begin release phase */ +#define DCYSUSM_SUSTAINLEVEL_MASK 0x00007f00 /* 127 = full, 0 = off, 0.75dB increments */ +#define DCYSUSM_DECAYTIME_MASK 0x0000007f /* Envelope decay time, log encoded */ + /* 0 = 43.7msec, 1 = 21.8msec, 0x7f = 22msec */ + +#define LFOVAL2 0x17 /* Vibrato LFO register */ +#define LFOVAL2_MASK 0x0000ffff /* Current value of vibrato LFO state variable */ + /* 0x8000-n == 666*n usec delay */ + +#define IP 0x18 /* Initial pitch register */ +#define IP_MASK 0x0000ffff /* Exponential initial pitch shift */ + /* 4 bits of octave, 12 bits of fractional octave */ +#define IP_UNITY 0x0000e000 /* Unity pitch shift */ + +#define IFATN 0x19 /* Initial filter cutoff and attenuation register */ +#define IFATN_FILTERCUTOFF_MASK 0x0000ff00 /* Initial filter cutoff frequency in exponential units */ + /* 6 most significant bits are semitones */ + /* 2 least significant bits are fractions */ +#define IFATN_FILTERCUTOFF 0x08080019 +#define IFATN_ATTENUATION_MASK 0x000000ff /* Initial attenuation in 0.375dB steps */ +#define IFATN_ATTENUATION 0x08000019 + + +#define PEFE 0x1a /* Pitch envelope and filter envelope amount register */ +#define PEFE_PITCHAMOUNT_MASK 0x0000ff00 /* Pitch envlope amount */ + /* Signed 2's complement, +/- one octave peak extremes */ +#define PEFE_PITCHAMOUNT 0x0808001a +#define PEFE_FILTERAMOUNT_MASK 0x000000ff /* Filter envlope amount */ + /* Signed 2's complement, +/- six octaves peak extremes */ +#define PEFE_FILTERAMOUNT 0x0800001a +#define FMMOD 0x1b /* Vibrato/filter modulation from LFO register */ +#define FMMOD_MODVIBRATO 0x0000ff00 /* Vibrato LFO modulation depth */ + /* Signed 2's complement, +/- one octave extremes */ +#define FMMOD_MOFILTER 0x000000ff /* Filter LFO modulation depth */ + /* Signed 2's complement, +/- three octave extremes */ + + +#define TREMFRQ 0x1c /* Tremolo amount and modulation LFO frequency register */ +#define TREMFRQ_DEPTH 0x0000ff00 /* Tremolo depth */ + /* Signed 2's complement, with +/- 12dB extremes */ +#define TREMFRQ_FREQUENCY 0x000000ff /* Tremolo LFO frequency */ + /* ??Hz steps, maximum of ?? Hz. */ + +#define FM2FRQ2 0x1d /* Vibrato amount and vibrato LFO frequency register */ +#define FM2FRQ2_DEPTH 0x0000ff00 /* Vibrato LFO vibrato depth */ + /* Signed 2's complement, +/- one octave extremes */ +#define FM2FRQ2_FREQUENCY 0x000000ff /* Vibrato LFO frequency */ + /* 0.039Hz steps, maximum of 9.85 Hz. */ + +#define TEMPENV 0x1e /* Tempory envelope register */ +#define TEMPENV_MASK 0x0000ffff /* 16-bit value */ + /* NOTE: All channels contain internal variables; do */ + /* not write to these locations. */ + +#define CD0 0x20 /* Cache data 0 register */ +#define CD1 0x21 /* Cache data 1 register */ +#define CD2 0x22 /* Cache data 2 register */ +#define CD3 0x23 /* Cache data 3 register */ +#define CD4 0x24 /* Cache data 4 register */ +#define CD5 0x25 /* Cache data 5 register */ +#define CD6 0x26 /* Cache data 6 register */ +#define CD7 0x27 /* Cache data 7 register */ +#define CD8 0x28 /* Cache data 8 register */ +#define CD9 0x29 /* Cache data 9 register */ +#define CDA 0x2a /* Cache data A register */ +#define CDB 0x2b /* Cache data B register */ +#define CDC 0x2c /* Cache data C register */ +#define CDD 0x2d /* Cache data D register */ +#define CDE 0x2e /* Cache data E register */ +#define CDF 0x2f /* Cache data F register */ + +#define PTB 0x40 /* Page table base register */ +#define PTB_MASK 0xfffff000 /* Physical address of the page table in host memory */ + +#define TCB 0x41 /* Tank cache base register */ +#define TCB_MASK 0xfffff000 /* Physical address of the bottom of host based TRAM */ + +#define ADCCR 0x42 /* ADC sample rate/stereo control register */ +#define ADCCR_RCHANENABLE 0x00000010 /* Enables right channel for writing to the host */ +#define ADCCR_LCHANENABLE 0x00000008 /* Enables left channel for writing to the host */ + /* NOTE: To guarantee phase coherency, both channels */ + /* must be disabled prior to enabling both channels. */ +#define A_ADCCR_RCHANENABLE 0x00000020 +#define A_ADCCR_LCHANENABLE 0x00000010 + +#define A_ADCCR_SAMPLERATE_MASK 0x0000000F /* Audigy sample rate convertor output rate */ +#define ADCCR_SAMPLERATE_MASK 0x00000007 /* Sample rate convertor output rate */ + +#define ADCCR_SAMPLERATE_48 0x00000000 /* 48kHz sample rate */ +#define ADCCR_SAMPLERATE_44 0x00000001 /* 44.1kHz sample rate */ +#define ADCCR_SAMPLERATE_32 0x00000002 /* 32kHz sample rate */ +#define ADCCR_SAMPLERATE_24 0x00000003 /* 24kHz sample rate */ +#define ADCCR_SAMPLERATE_22 0x00000004 /* 22.05kHz sample rate */ +#define ADCCR_SAMPLERATE_16 0x00000005 /* 16kHz sample rate */ +#define ADCCR_SAMPLERATE_11 0x00000006 /* 11.025kHz sample rate */ +#define ADCCR_SAMPLERATE_8 0x00000007 /* 8kHz sample rate */ + +#define A_ADCCR_SAMPLERATE_12 0x00000006 /* 12kHz sample rate */ +#define A_ADCCR_SAMPLERATE_11 0x00000007 /* 11.025kHz sample rate */ +#define A_ADCCR_SAMPLERATE_8 0x00000008 /* 8kHz sample rate */ + +#define FXWC 0x43 /* FX output write channels register */ + /* When set, each bit enables the writing of the */ + /* corresponding FX output channel (internal registers */ + /* 0x20-0x3f) into host memory. This mode of recording */ + /* is 16bit, 48KHz only. All 32 channels can be enabled */ + /* simultaneously. */ +#define TCBS 0x44 /* Tank cache buffer size register */ +#define TCBS_MASK 0x00000007 /* Tank cache buffer size field */ +#define TCBS_BUFFSIZE_16K 0x00000000 +#define TCBS_BUFFSIZE_32K 0x00000001 +#define TCBS_BUFFSIZE_64K 0x00000002 +#define TCBS_BUFFSIZE_128K 0x00000003 +#define TCBS_BUFFSIZE_256K 0x00000004 +#define TCBS_BUFFSIZE_512K 0x00000005 +#define TCBS_BUFFSIZE_1024K 0x00000006 +#define TCBS_BUFFSIZE_2048K 0x00000007 + +#define MICBA 0x45 /* AC97 microphone buffer address register */ +#define MICBA_MASK 0xfffff000 /* 20 bit base address */ + +#define ADCBA 0x46 /* ADC buffer address register */ +#define ADCBA_MASK 0xfffff000 /* 20 bit base address */ + +#define FXBA 0x47 /* FX Buffer Address */ +#define FXBA_MASK 0xfffff000 /* 20 bit base address */ + +#define MICBS 0x49 /* Microphone buffer size register */ + +#define ADCBS 0x4a /* ADC buffer size register */ + +#define FXBS 0x4b /* FX buffer size register */ + +/* The following mask values define the size of the ADC, MIX and FX buffers in bytes */ +#define ADCBS_BUFSIZE_NONE 0x00000000 +#define ADCBS_BUFSIZE_384 0x00000001 +#define ADCBS_BUFSIZE_448 0x00000002 +#define ADCBS_BUFSIZE_512 0x00000003 +#define ADCBS_BUFSIZE_640 0x00000004 +#define ADCBS_BUFSIZE_768 0x00000005 +#define ADCBS_BUFSIZE_896 0x00000006 +#define ADCBS_BUFSIZE_1024 0x00000007 +#define ADCBS_BUFSIZE_1280 0x00000008 +#define ADCBS_BUFSIZE_1536 0x00000009 +#define ADCBS_BUFSIZE_1792 0x0000000a +#define ADCBS_BUFSIZE_2048 0x0000000b +#define ADCBS_BUFSIZE_2560 0x0000000c +#define ADCBS_BUFSIZE_3072 0x0000000d +#define ADCBS_BUFSIZE_3584 0x0000000e +#define ADCBS_BUFSIZE_4096 0x0000000f +#define ADCBS_BUFSIZE_5120 0x00000010 +#define ADCBS_BUFSIZE_6144 0x00000011 +#define ADCBS_BUFSIZE_7168 0x00000012 +#define ADCBS_BUFSIZE_8192 0x00000013 +#define ADCBS_BUFSIZE_10240 0x00000014 +#define ADCBS_BUFSIZE_12288 0x00000015 +#define ADCBS_BUFSIZE_14366 0x00000016 +#define ADCBS_BUFSIZE_16384 0x00000017 +#define ADCBS_BUFSIZE_20480 0x00000018 +#define ADCBS_BUFSIZE_24576 0x00000019 +#define ADCBS_BUFSIZE_28672 0x0000001a +#define ADCBS_BUFSIZE_32768 0x0000001b +#define ADCBS_BUFSIZE_40960 0x0000001c +#define ADCBS_BUFSIZE_49152 0x0000001d +#define ADCBS_BUFSIZE_57344 0x0000001e +#define ADCBS_BUFSIZE_65536 0x0000001f + + +#define CDCS 0x50 /* CD-ROM digital channel status register */ + +#define GPSCS 0x51 /* General Purpose SPDIF channel status register*/ + +#define DBG 0x52 /* DO NOT PROGRAM THIS REGISTER!!! MAY DESTROY CHIP */ + +/* definitions for debug register - taken from the alsa drivers */ +#define DBG_ZC 0x80000000 /* zero tram counter */ +#define DBG_SATURATION_OCCURED 0x02000000 /* saturation control */ +#define DBG_SATURATION_ADDR 0x01ff0000 /* saturation address */ +#define DBG_SINGLE_STEP 0x00008000 /* single step mode */ +#define DBG_STEP 0x00004000 /* start single step */ +#define DBG_CONDITION_CODE 0x00003e00 /* condition code */ +#define DBG_SINGLE_STEP_ADDR 0x000001ff /* single step address */ + + +#define REG53 0x53 /* DO NOT PROGRAM THIS REGISTER!!! MAY DESTROY CHIP */ + +#define A_DBG 0x53 +#define A_DBG_SINGLE_STEP 0x00020000 /* Set to zero to start dsp */ +#define A_DBG_ZC 0x40000000 /* zero tram counter */ +#define A_DBG_STEP_ADDR 0x000003ff +#define A_DBG_SATURATION_OCCURED 0x20000000 +#define A_DBG_SATURATION_ADDR 0x0ffc0000 + +#define SPCS0 0x54 /* SPDIF output Channel Status 0 register */ + +#define SPCS1 0x55 /* SPDIF output Channel Status 1 register */ + +#define SPCS2 0x56 /* SPDIF output Channel Status 2 register */ + +#define SPCS_CLKACCYMASK 0x30000000 /* Clock accuracy */ +#define SPCS_CLKACCY_1000PPM 0x00000000 /* 1000 parts per million */ +#define SPCS_CLKACCY_50PPM 0x10000000 /* 50 parts per million */ +#define SPCS_CLKACCY_VARIABLE 0x20000000 /* Variable accuracy */ +#define SPCS_SAMPLERATEMASK 0x0f000000 /* Sample rate */ +#define SPCS_SAMPLERATE_44 0x00000000 /* 44.1kHz sample rate */ +#define SPCS_SAMPLERATE_48 0x02000000 /* 48kHz sample rate */ +#define SPCS_SAMPLERATE_32 0x03000000 /* 32kHz sample rate */ +#define SPCS_CHANNELNUMMASK 0x00f00000 /* Channel number */ +#define SPCS_CHANNELNUM_UNSPEC 0x00000000 /* Unspecified channel number */ +#define SPCS_CHANNELNUM_LEFT 0x00100000 /* Left channel */ +#define SPCS_CHANNELNUM_RIGHT 0x00200000 /* Right channel */ +#define SPCS_SOURCENUMMASK 0x000f0000 /* Source number */ +#define SPCS_SOURCENUM_UNSPEC 0x00000000 /* Unspecified source number */ +#define SPCS_GENERATIONSTATUS 0x00008000 /* Originality flag (see IEC-958 spec) */ +#define SPCS_CATEGORYCODEMASK 0x00007f00 /* Category code (see IEC-958 spec) */ +#define SPCS_MODEMASK 0x000000c0 /* Mode (see IEC-958 spec) */ +#define SPCS_EMPHASISMASK 0x00000038 /* Emphasis */ +#define SPCS_EMPHASIS_NONE 0x00000000 /* No emphasis */ +#define SPCS_EMPHASIS_50_15 0x00000008 /* 50/15 usec 2 channel */ +#define SPCS_COPYRIGHT 0x00000004 /* Copyright asserted flag -- do not modify */ +#define SPCS_NOTAUDIODATA 0x00000002 /* 0 = Digital audio, 1 = not audio */ +#define SPCS_PROFESSIONAL 0x00000001 /* 0 = Consumer (IEC-958), 1 = pro (AES3-1992) */ + +/* The 32-bit CLIx and SOLx registers all have one bit per channel control/status */ +#define CLIEL 0x58 /* Channel loop interrupt enable low register */ + +#define CLIEH 0x59 /* Channel loop interrupt enable high register */ + +#define CLIPL 0x5a /* Channel loop interrupt pending low register */ + +#define CLIPH 0x5b /* Channel loop interrupt pending high register */ + +#define SOLEL 0x5c /* Stop on loop enable low register */ + +#define SOLEH 0x5d /* Stop on loop enable high register */ + +#define SPBYPASS 0x5e /* SPDIF BYPASS mode register */ +#define SPBYPASS_ENABLE 0x00000001 /* Enable SPDIF bypass mode */ + +#define AC97SLOT 0x5f /* additional AC97 slots enable bits */ +#define AC97SLOT_CNTR 0x10 /* Center enable */ +#define AC97SLOT_LFE 0x20 /* LFE enable */ + +#define CDSRCS 0x60 /* CD-ROM Sample Rate Converter status register */ + +#define GPSRCS 0x61 /* General Purpose SPDIF sample rate cvt status */ + +#define ZVSRCS 0x62 /* ZVideo sample rate converter status */ + /* NOTE: This one has no SPDIFLOCKED field */ + /* Assumes sample lock */ + +/* These three bitfields apply to CDSRCS, GPSRCS, and (except as noted) ZVSRCS. */ +#define SRCS_SPDIFLOCKED 0x02000000 /* SPDIF stream locked */ +#define SRCS_RATELOCKED 0x01000000 /* Sample rate locked */ +#define SRCS_ESTSAMPLERATE 0x0007ffff /* Do not modify this field. */ + + +/* Note that these values can vary +/- by a small amount */ +#define SRCS_SPDIFRATE_44 0x0003acd9 +#define SRCS_SPDIFRATE_48 0x00040000 +#define SRCS_SPDIFRATE_96 0x00080000 + +#define MICIDX 0x63 /* Microphone recording buffer index register */ +#define MICIDX_MASK 0x0000ffff /* 16-bit value */ +#define MICIDX_IDX 0x10000063 + +#define A_ADCIDX 0x63 +#define A_ADCIDX_IDX 0x10000063 + +#define ADCIDX 0x64 /* ADC recording buffer index register */ +#define ADCIDX_MASK 0x0000ffff /* 16 bit index field */ +#define ADCIDX_IDX 0x10000064 + +#define FXIDX 0x65 /* FX recording buffer index register */ +#define FXIDX_MASK 0x0000ffff /* 16-bit value */ +#define FXIDX_IDX 0x10000065 + +/* This is the MPU port on the card (via the game port) */ +#define A_MUDATA1 0x70 +#define A_MUCMD1 0x71 +#define A_MUSTAT1 A_MUCMD1 + +/* This is the MPU port on the Audigy Drive */ +#define A_MUDATA2 0x72 +#define A_MUCMD2 0x73 +#define A_MUSTAT2 A_MUCMD2 + +/* The next two are the Audigy equivalent of FXWC */ +/* the Audigy can record any output (16bit, 48kHz, up to 64 channel simultaneously) */ +/* Each bit selects a channel for recording */ +#define A_FXWC1 0x74 /* Selects 0x7f-0x60 for FX recording */ +#define A_FXWC2 0x75 /* Selects 0x9f-0x80 for FX recording */ + +#define A_SPDIF_SAMPLERATE 0x76 /* Set the sample rate of SPDIF output */ +#define A_SPDIF_48000 0x00000080 +#define A_SPDIF_44100 0x00000000 +#define A_SPDIF_96000 0x00000040 + +#define A_FXRT2 0x7c +#define A_FXRT_CHANNELE 0x0000003f /* Effects send bus number for channel's effects send E */ +#define A_FXRT_CHANNELF 0x00003f00 /* Effects send bus number for channel's effects send F */ +#define A_FXRT_CHANNELG 0x003f0000 /* Effects send bus number for channel's effects send G */ +#define A_FXRT_CHANNELH 0x3f000000 /* Effects send bus number for channel's effects send H */ + +#define A_SENDAMOUNTS 0x7d +#define A_FXSENDAMOUNT_E_MASK 0xff000000 +#define A_FXSENDAMOUNT_F_MASK 0x00ff0000 +#define A_FXSENDAMOUNT_G_MASK 0x0000ff00 +#define A_FXSENDAMOUNT_H_MASK 0x000000ff + +/* The send amounts for this one are the same as used with the emu10k1 */ +#define A_FXRT1 0x7e +#define A_FXRT_CHANNELA 0x0000003f +#define A_FXRT_CHANNELB 0x00003f00 +#define A_FXRT_CHANNELC 0x003f0000 +#define A_FXRT_CHANNELD 0x3f000000 + + +/* Each FX general purpose register is 32 bits in length, all bits are used */ +#define FXGPREGBASE 0x100 /* FX general purpose registers base */ +#define A_FXGPREGBASE 0x400 /* Audigy GPRs, 0x400 to 0x5ff */ +/* Tank audio data is logarithmically compressed down to 16 bits before writing to TRAM and is */ +/* decompressed back to 20 bits on a read. There are a total of 160 locations, the last 32 */ +/* locations are for external TRAM. */ +#define TANKMEMDATAREGBASE 0x200 /* Tank memory data registers base */ +#define TANKMEMDATAREG_MASK 0x000fffff /* 20 bit tank audio data field */ + +/* Combined address field and memory opcode or flag field. 160 locations, last 32 are external */ +#define TANKMEMADDRREGBASE 0x300 /* Tank memory address registers base */ +#define TANKMEMADDRREG_ADDR_MASK 0x000fffff /* 20 bit tank address field */ +#define TANKMEMADDRREG_CLEAR 0x00800000 /* Clear tank memory */ +#define TANKMEMADDRREG_ALIGN 0x00400000 /* Align read or write relative to tank access */ +#define TANKMEMADDRREG_WRITE 0x00200000 /* Write to tank memory */ +#define TANKMEMADDRREG_READ 0x00100000 /* Read from tank memory */ + +#define MICROCODEBASE 0x400 /* Microcode data base address */ + +/* Each DSP microcode instruction is mapped into 2 doublewords */ +/* NOTE: When writing, always write the LO doubleword first. Reads can be in either order. */ +#define LOWORD_OPX_MASK 0x000ffc00 /* Instruction operand X */ +#define LOWORD_OPY_MASK 0x000003ff /* Instruction operand Y */ +#define HIWORD_OPCODE_MASK 0x00f00000 /* Instruction opcode */ +#define HIWORD_RESULT_MASK 0x000ffc00 /* Instruction result */ +#define HIWORD_OPA_MASK 0x000003ff /* Instruction operand A */ + + +/* Audigy Soundcard have a different instruction format */ +#define AUDIGY_CODEBASE 0x600 +#define A_LOWORD_OPY_MASK 0x000007ff +#define A_LOWORD_OPX_MASK 0x007ff000 +#define A_HIWORD_OPCODE_MASK 0x0f000000 +#define A_HIWORD_RESULT_MASK 0x007ff000 +#define A_HIWORD_OPA_MASK 0x000007ff + + +#endif /* _8010_H */ diff --git a/sound/oss/emu10k1/Makefile b/sound/oss/emu10k1/Makefile new file mode 100644 index 000000000000..b3af9ccb0579 --- /dev/null +++ b/sound/oss/emu10k1/Makefile @@ -0,0 +1,17 @@ +# Makefile for Creative Labs EMU10K1 +# +# 12 Apr 2000 Rui Sousa + +obj-$(CONFIG_SOUND_EMU10K1) += emu10k1.o + +emu10k1-objs := audio.o cardmi.o cardmo.o cardwi.o cardwo.o ecard.o \ + efxmgr.o emuadxmg.o hwaccess.o irqmgr.o main.o midi.o \ + mixer.o passthrough.o recmgr.o timer.o voicemgr.o + +ifdef DEBUG + EXTRA_CFLAGS += -DEMU10K1_DEBUG +endif + +ifdef CONFIG_MIDI_EMU10K1 + EXTRA_CFLAGS += -DEMU10K1_SEQUENCER +endif diff --git a/sound/oss/emu10k1/audio.c b/sound/oss/emu10k1/audio.c new file mode 100644 index 000000000000..cde4d59d5430 --- /dev/null +++ b/sound/oss/emu10k1/audio.c @@ -0,0 +1,1588 @@ +/* + ********************************************************************** + * audio.c -- /dev/dsp interface for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * November 2, 1999 Alan Cox cleaned up types/leaks + * + ********************************************************************** + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "hwaccess.h" +#include "cardwo.h" +#include "cardwi.h" +#include "recmgr.h" +#include "irqmgr.h" +#include "audio.h" +#include "8010.h" + +static void calculate_ofrag(struct woinst *); +static void calculate_ifrag(struct wiinst *); + +static void emu10k1_waveout_bh(unsigned long refdata); +static void emu10k1_wavein_bh(unsigned long refdata); + +/* Audio file operations */ +static ssize_t emu10k1_audio_read(struct file *file, char __user *buffer, size_t count, loff_t * ppos) +{ + struct emu10k1_wavedevice *wave_dev = (struct emu10k1_wavedevice *) file->private_data; + struct wiinst *wiinst = wave_dev->wiinst; + ssize_t ret = 0; + unsigned long flags; + + DPD(3, "emu10k1_audio_read(), buffer=%p, count=%d\n", buffer, (u32) count); + + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + + spin_lock_irqsave(&wiinst->lock, flags); + + if (wiinst->mmapped) { + spin_unlock_irqrestore(&wiinst->lock, flags); + return -ENXIO; + } + + if (wiinst->state == WAVE_STATE_CLOSED) { + calculate_ifrag(wiinst); + + while (emu10k1_wavein_open(wave_dev) < 0) { + spin_unlock_irqrestore(&wiinst->lock, flags); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + interruptible_sleep_on(&wave_dev->card->open_wait); + + if (signal_pending(current)) + return -ERESTARTSYS; + + spin_lock_irqsave(&wiinst->lock, flags); + } + } + + spin_unlock_irqrestore(&wiinst->lock, flags); + + while (count > 0) { + u32 bytestocopy; + + spin_lock_irqsave(&wiinst->lock, flags); + + if (!(wiinst->state & WAVE_STATE_STARTED) + && (wave_dev->enablebits & PCM_ENABLE_INPUT)) + emu10k1_wavein_start(wave_dev); + + emu10k1_wavein_update(wave_dev->card, wiinst); + emu10k1_wavein_getxfersize(wiinst, &bytestocopy); + + spin_unlock_irqrestore(&wiinst->lock, flags); + + DPD(3, "bytestocopy --> %d\n", bytestocopy); + + if ((bytestocopy >= wiinst->buffer.fragment_size) + || (bytestocopy >= count)) { + bytestocopy = min_t(u32, bytestocopy, count); + + emu10k1_wavein_xferdata(wiinst, (u8 __user *)buffer, &bytestocopy); + + count -= bytestocopy; + buffer += bytestocopy; + ret += bytestocopy; + } + + if (count > 0) { + if ((file->f_flags & O_NONBLOCK) + || (!(wave_dev->enablebits & PCM_ENABLE_INPUT))) + return (ret ? ret : -EAGAIN); + + interruptible_sleep_on(&wiinst->wait_queue); + + if (signal_pending(current)) + return (ret ? ret : -ERESTARTSYS); + + } + } + + DPD(3, "bytes copied -> %d\n", (u32) ret); + + return ret; +} + +static ssize_t emu10k1_audio_write(struct file *file, const char __user *buffer, size_t count, loff_t * ppos) +{ + struct emu10k1_wavedevice *wave_dev = (struct emu10k1_wavedevice *) file->private_data; + struct woinst *woinst = wave_dev->woinst; + ssize_t ret; + unsigned long flags; + + DPD(3, "emu10k1_audio_write(), buffer=%p, count=%d\n", buffer, (u32) count); + + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + + spin_lock_irqsave(&woinst->lock, flags); + + if (woinst->mmapped) { + spin_unlock_irqrestore(&woinst->lock, flags); + return -ENXIO; + } + // This is for emu10k1 revs less than 7, we need to go through tram + if (woinst->format.passthrough == 1) { + int r; + + woinst->buffer.ossfragshift = PT_BLOCKSIZE_LOG2; + woinst->buffer.numfrags = PT_BLOCKCOUNT; + calculate_ofrag(woinst); + + r = emu10k1_pt_write(file, buffer, count); + spin_unlock_irqrestore(&woinst->lock, flags); + return r; + } + + if (woinst->state == WAVE_STATE_CLOSED) { + calculate_ofrag(woinst); + + while (emu10k1_waveout_open(wave_dev) < 0) { + spin_unlock_irqrestore(&woinst->lock, flags); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + interruptible_sleep_on(&wave_dev->card->open_wait); + + if (signal_pending(current)) + return -ERESTARTSYS; + + spin_lock_irqsave(&woinst->lock, flags); + } + } + + spin_unlock_irqrestore(&woinst->lock, flags); + + ret = 0; + if (count % woinst->format.bytespersample) + return -EINVAL; + + count /= woinst->num_voices; + + while (count > 0) { + u32 bytestocopy; + + spin_lock_irqsave(&woinst->lock, flags); + emu10k1_waveout_update(woinst); + emu10k1_waveout_getxfersize(woinst, &bytestocopy); + spin_unlock_irqrestore(&woinst->lock, flags); + + DPD(3, "bytestocopy --> %d\n", bytestocopy); + + if ((bytestocopy >= woinst->buffer.fragment_size) + || (bytestocopy >= count)) { + + bytestocopy = min_t(u32, bytestocopy, count); + + emu10k1_waveout_xferdata(woinst, (u8 __user *) buffer, &bytestocopy); + + count -= bytestocopy; + buffer += bytestocopy * woinst->num_voices; + ret += bytestocopy * woinst->num_voices; + + spin_lock_irqsave(&woinst->lock, flags); + woinst->total_copied += bytestocopy; + + if (!(woinst->state & WAVE_STATE_STARTED) + && (wave_dev->enablebits & PCM_ENABLE_OUTPUT) + && (woinst->total_copied >= woinst->buffer.fragment_size)) + emu10k1_waveout_start(wave_dev); + + spin_unlock_irqrestore(&woinst->lock, flags); + } + + if (count > 0) { + if ((file->f_flags & O_NONBLOCK) + || (!(wave_dev->enablebits & PCM_ENABLE_OUTPUT))) + return (ret ? ret : -EAGAIN); + + interruptible_sleep_on(&woinst->wait_queue); + + if (signal_pending(current)) + return (ret ? ret : -ERESTARTSYS); + } + } + + DPD(3, "bytes copied -> %d\n", (u32) ret); + + return ret; +} + +static int emu10k1_audio_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct emu10k1_wavedevice *wave_dev = (struct emu10k1_wavedevice *) file->private_data; + struct woinst *woinst = NULL; + struct wiinst *wiinst = NULL; + int val = 0; + u32 bytestocopy; + unsigned long flags; + int __user *p = (int __user *)arg; + + DPF(4, "emu10k1_audio_ioctl()\n"); + + if (file->f_mode & FMODE_WRITE) + woinst = wave_dev->woinst; + + if (file->f_mode & FMODE_READ) + wiinst = wave_dev->wiinst; + + switch (cmd) { + case OSS_GETVERSION: + DPF(2, "OSS_GETVERSION:\n"); + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_RESET: + DPF(2, "SNDCTL_DSP_RESET:\n"); + wave_dev->enablebits = PCM_ENABLE_OUTPUT | PCM_ENABLE_INPUT; + + if (file->f_mode & FMODE_WRITE) { + spin_lock_irqsave(&woinst->lock, flags); + + if (woinst->state & WAVE_STATE_OPEN) { + emu10k1_waveout_close(wave_dev); + } + + woinst->mmapped = 0; + woinst->total_copied = 0; + woinst->total_played = 0; + woinst->blocks = 0; + + spin_unlock_irqrestore(&woinst->lock, flags); + } + + if (file->f_mode & FMODE_READ) { + spin_lock_irqsave(&wiinst->lock, flags); + + if (wiinst->state & WAVE_STATE_OPEN) { + emu10k1_wavein_close(wave_dev); + } + + wiinst->mmapped = 0; + wiinst->total_recorded = 0; + wiinst->blocks = 0; + spin_unlock_irqrestore(&wiinst->lock, flags); + } + + break; + + case SNDCTL_DSP_SYNC: + DPF(2, "SNDCTL_DSP_SYNC:\n"); + + if (file->f_mode & FMODE_WRITE) { + + spin_lock_irqsave(&woinst->lock, flags); + + if (woinst->state & WAVE_STATE_OPEN) { + + if (woinst->state & WAVE_STATE_STARTED) + while ((woinst->total_played < woinst->total_copied) + && !signal_pending(current)) { + spin_unlock_irqrestore(&woinst->lock, flags); + interruptible_sleep_on(&woinst->wait_queue); + spin_lock_irqsave(&woinst->lock, flags); + } + emu10k1_waveout_close(wave_dev); + } + + woinst->mmapped = 0; + woinst->total_copied = 0; + woinst->total_played = 0; + woinst->blocks = 0; + + spin_unlock_irqrestore(&woinst->lock, flags); + } + + if (file->f_mode & FMODE_READ) { + spin_lock_irqsave(&wiinst->lock, flags); + + if (wiinst->state & WAVE_STATE_OPEN) { + emu10k1_wavein_close(wave_dev); + } + + wiinst->mmapped = 0; + wiinst->total_recorded = 0; + wiinst->blocks = 0; + spin_unlock_irqrestore(&wiinst->lock, flags); + } + + break; + + case SNDCTL_DSP_SETDUPLEX: + DPF(2, "SNDCTL_DSP_SETDUPLEX:\n"); + break; + + case SNDCTL_DSP_GETCAPS: + DPF(2, "SNDCTL_DSP_GETCAPS:\n"); + return put_user(DSP_CAP_DUPLEX | DSP_CAP_REALTIME | + DSP_CAP_TRIGGER | DSP_CAP_MMAP | + DSP_CAP_COPROC| DSP_CAP_MULTI, p); + case SNDCTL_DSP_SPEED: + DPF(2, "SNDCTL_DSP_SPEED:\n"); + + if (get_user(val, p)) + return -EFAULT; + + DPD(2, "val is %d\n", val); + + if (val > 0) { + if (file->f_mode & FMODE_READ) { + struct wave_format format; + + spin_lock_irqsave(&wiinst->lock, flags); + + format = wiinst->format; + format.samplingrate = val; + + if (emu10k1_wavein_setformat(wave_dev, &format) < 0) { + spin_unlock_irqrestore(&wiinst->lock, flags); + return -EINVAL; + } + + val = wiinst->format.samplingrate; + + spin_unlock_irqrestore(&wiinst->lock, flags); + + DPD(2, "set recording sampling rate -> %d\n", val); + } + + if (file->f_mode & FMODE_WRITE) { + struct wave_format format; + + spin_lock_irqsave(&woinst->lock, flags); + + format = woinst->format; + format.samplingrate = val; + + if (emu10k1_waveout_setformat(wave_dev, &format) < 0) { + spin_unlock_irqrestore(&woinst->lock, flags); + return -EINVAL; + } + + val = woinst->format.samplingrate; + + spin_unlock_irqrestore(&woinst->lock, flags); + + DPD(2, "set playback sampling rate -> %d\n", val); + } + + return put_user(val, p); + } else { + if (file->f_mode & FMODE_READ) + val = wiinst->format.samplingrate; + else if (file->f_mode & FMODE_WRITE) + val = woinst->format.samplingrate; + + return put_user(val, p); + } + break; + + case SNDCTL_DSP_STEREO: + DPF(2, "SNDCTL_DSP_STEREO:\n"); + + if (get_user(val, p)) + return -EFAULT; + + DPD(2, " val is %d\n", val); + + if (file->f_mode & FMODE_READ) { + struct wave_format format; + + spin_lock_irqsave(&wiinst->lock, flags); + + format = wiinst->format; + format.channels = val ? 2 : 1; + + if (emu10k1_wavein_setformat(wave_dev, &format) < 0) { + spin_unlock_irqrestore(&wiinst->lock, flags); + return -EINVAL; + } + + val = wiinst->format.channels - 1; + + spin_unlock_irqrestore(&wiinst->lock, flags); + DPD(2, "set recording stereo -> %d\n", val); + } + + if (file->f_mode & FMODE_WRITE) { + struct wave_format format; + + spin_lock_irqsave(&woinst->lock, flags); + + format = woinst->format; + format.channels = val ? 2 : 1; + + if (emu10k1_waveout_setformat(wave_dev, &format) < 0) { + spin_unlock_irqrestore(&woinst->lock, flags); + return -EINVAL; + } + + val = woinst->format.channels - 1; + + spin_unlock_irqrestore(&woinst->lock, flags); + + DPD(2, "set playback stereo -> %d\n", val); + } + + return put_user(val, p); + + break; + + case SNDCTL_DSP_CHANNELS: + DPF(2, "SNDCTL_DSP_CHANNELS:\n"); + + if (get_user(val, p)) + return -EFAULT; + + DPD(2, " val is %d\n", val); + + if (val > 0) { + if (file->f_mode & FMODE_READ) { + struct wave_format format; + + spin_lock_irqsave(&wiinst->lock, flags); + + format = wiinst->format; + format.channels = val; + + if (emu10k1_wavein_setformat(wave_dev, &format) < 0) { + spin_unlock_irqrestore(&wiinst->lock, flags); + return -EINVAL; + } + val = wiinst->format.channels; + + spin_unlock_irqrestore(&wiinst->lock, flags); + DPD(2, "set recording number of channels -> %d\n", val); + } + + if (file->f_mode & FMODE_WRITE) { + struct wave_format format; + + spin_lock_irqsave(&woinst->lock, flags); + + format = woinst->format; + format.channels = val; + + if (emu10k1_waveout_setformat(wave_dev, &format) < 0) { + spin_unlock_irqrestore(&woinst->lock, flags); + return -EINVAL; + } + + val = woinst->format.channels; + + spin_unlock_irqrestore(&woinst->lock, flags); + DPD(2, "set playback number of channels -> %d\n", val); + } + + return put_user(val, p); + } else { + if (file->f_mode & FMODE_READ) + val = wiinst->format.channels; + else if (file->f_mode & FMODE_WRITE) + val = woinst->format.channels; + + return put_user(val, p); + } + break; + + case SNDCTL_DSP_GETFMTS: + DPF(2, "SNDCTL_DSP_GETFMTS:\n"); + + if (file->f_mode & FMODE_READ) + val = AFMT_S16_LE; + else if (file->f_mode & FMODE_WRITE) { + val = AFMT_S16_LE | AFMT_U8; + if (emu10k1_find_control_gpr(&wave_dev->card->mgr, + wave_dev->card->pt.patch_name, + wave_dev->card->pt.enable_gpr_name) >= 0) + val |= AFMT_AC3; + } + return put_user(val, p); + + case SNDCTL_DSP_SETFMT: /* Same as SNDCTL_DSP_SAMPLESIZE */ + DPF(2, "SNDCTL_DSP_SETFMT:\n"); + + if (get_user(val, p)) + return -EFAULT; + + DPD(2, " val is %d\n", val); + + if (val != AFMT_QUERY) { + if (file->f_mode & FMODE_READ) { + struct wave_format format; + + spin_lock_irqsave(&wiinst->lock, flags); + + format = wiinst->format; + format.id = val; + + if (emu10k1_wavein_setformat(wave_dev, &format) < 0) { + spin_unlock_irqrestore(&wiinst->lock, flags); + return -EINVAL; + } + + val = wiinst->format.id; + + spin_unlock_irqrestore(&wiinst->lock, flags); + DPD(2, "set recording format -> %d\n", val); + } + + if (file->f_mode & FMODE_WRITE) { + struct wave_format format; + + spin_lock_irqsave(&woinst->lock, flags); + + format = woinst->format; + format.id = val; + + if (emu10k1_waveout_setformat(wave_dev, &format) < 0) { + spin_unlock_irqrestore(&woinst->lock, flags); + return -EINVAL; + } + + val = woinst->format.id; + + spin_unlock_irqrestore(&woinst->lock, flags); + DPD(2, "set playback format -> %d\n", val); + } + + return put_user(val, p); + } else { + if (file->f_mode & FMODE_READ) + val = wiinst->format.id; + else if (file->f_mode & FMODE_WRITE) + val = woinst->format.id; + + return put_user(val, p); + } + break; + + case SOUND_PCM_READ_BITS: + + if (file->f_mode & FMODE_READ) + val = wiinst->format.bitsperchannel; + else if (file->f_mode & FMODE_WRITE) + val = woinst->format.bitsperchannel; + + return put_user(val, p); + + case SOUND_PCM_READ_RATE: + + if (file->f_mode & FMODE_READ) + val = wiinst->format.samplingrate; + else if (file->f_mode & FMODE_WRITE) + val = woinst->format.samplingrate; + + return put_user(val, p); + + case SOUND_PCM_READ_CHANNELS: + + if (file->f_mode & FMODE_READ) + val = wiinst->format.channels; + else if (file->f_mode & FMODE_WRITE) + val = woinst->format.channels; + + return put_user(val, p); + + case SOUND_PCM_WRITE_FILTER: + DPF(2, "SOUND_PCM_WRITE_FILTER: not implemented\n"); + break; + + case SOUND_PCM_READ_FILTER: + DPF(2, "SOUND_PCM_READ_FILTER: not implemented\n"); + break; + + case SNDCTL_DSP_SETSYNCRO: + DPF(2, "SNDCTL_DSP_SETSYNCRO: not implemented\n"); + break; + + case SNDCTL_DSP_GETTRIGGER: + DPF(2, "SNDCTL_DSP_GETTRIGGER:\n"); + + if (file->f_mode & FMODE_WRITE && (wave_dev->enablebits & PCM_ENABLE_OUTPUT)) + val |= PCM_ENABLE_OUTPUT; + + if (file->f_mode & FMODE_READ && (wave_dev->enablebits & PCM_ENABLE_INPUT)) + val |= PCM_ENABLE_INPUT; + + return put_user(val, p); + + case SNDCTL_DSP_SETTRIGGER: + DPF(2, "SNDCTL_DSP_SETTRIGGER:\n"); + + if (get_user(val, p)) + return -EFAULT; + + if (file->f_mode & FMODE_WRITE) { + spin_lock_irqsave(&woinst->lock, flags); + + if (val & PCM_ENABLE_OUTPUT) { + wave_dev->enablebits |= PCM_ENABLE_OUTPUT; + if (woinst->state & WAVE_STATE_OPEN) + emu10k1_waveout_start(wave_dev); + } else { + wave_dev->enablebits &= ~PCM_ENABLE_OUTPUT; + if (woinst->state & WAVE_STATE_STARTED) + emu10k1_waveout_stop(wave_dev); + } + + spin_unlock_irqrestore(&woinst->lock, flags); + } + + if (file->f_mode & FMODE_READ) { + spin_lock_irqsave(&wiinst->lock, flags); + + if (val & PCM_ENABLE_INPUT) { + wave_dev->enablebits |= PCM_ENABLE_INPUT; + if (wiinst->state & WAVE_STATE_OPEN) + emu10k1_wavein_start(wave_dev); + } else { + wave_dev->enablebits &= ~PCM_ENABLE_INPUT; + if (wiinst->state & WAVE_STATE_STARTED) + emu10k1_wavein_stop(wave_dev); + } + + spin_unlock_irqrestore(&wiinst->lock, flags); + } + break; + + case SNDCTL_DSP_GETOSPACE: + { + audio_buf_info info; + + DPF(4, "SNDCTL_DSP_GETOSPACE:\n"); + + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + + spin_lock_irqsave(&woinst->lock, flags); + + if (woinst->state & WAVE_STATE_OPEN) { + emu10k1_waveout_update(woinst); + emu10k1_waveout_getxfersize(woinst, &bytestocopy); + info.bytes = bytestocopy; + } else { + calculate_ofrag(woinst); + info.bytes = woinst->buffer.size; + } + spin_unlock_irqrestore(&woinst->lock, flags); + + info.bytes *= woinst->num_voices; + info.fragsize = woinst->buffer.fragment_size * woinst->num_voices; + info.fragstotal = woinst->buffer.numfrags * woinst->num_voices; + info.fragments = info.bytes / info.fragsize; + + if (copy_to_user(p, &info, sizeof(info))) + return -EFAULT; + } + break; + + case SNDCTL_DSP_GETISPACE: + { + audio_buf_info info; + + DPF(4, "SNDCTL_DSP_GETISPACE:\n"); + + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + + spin_lock_irqsave(&wiinst->lock, flags); + if (wiinst->state & WAVE_STATE_OPEN) { + emu10k1_wavein_update(wave_dev->card, wiinst); + emu10k1_wavein_getxfersize(wiinst, &bytestocopy); + info.bytes = bytestocopy; + } else { + calculate_ifrag(wiinst); + info.bytes = 0; + } + spin_unlock_irqrestore(&wiinst->lock, flags); + + info.fragstotal = wiinst->buffer.numfrags; + info.fragments = info.bytes / wiinst->buffer.fragment_size; + info.fragsize = wiinst->buffer.fragment_size; + + if (copy_to_user(p, &info, sizeof(info))) + return -EFAULT; + } + break; + + case SNDCTL_DSP_NONBLOCK: + DPF(2, "SNDCTL_DSP_NONBLOCK:\n"); + + file->f_flags |= O_NONBLOCK; + break; + + case SNDCTL_DSP_GETODELAY: + DPF(4, "SNDCTL_DSP_GETODELAY:\n"); + + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + + spin_lock_irqsave(&woinst->lock, flags); + if (woinst->state & WAVE_STATE_OPEN) { + emu10k1_waveout_update(woinst); + emu10k1_waveout_getxfersize(woinst, &bytestocopy); + val = woinst->buffer.size - bytestocopy; + } else + val = 0; + + val *= woinst->num_voices; + spin_unlock_irqrestore(&woinst->lock, flags); + + return put_user(val, p); + + case SNDCTL_DSP_GETIPTR: + { + count_info cinfo; + + DPF(4, "SNDCTL_DSP_GETIPTR: \n"); + + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + + spin_lock_irqsave(&wiinst->lock, flags); + + if (wiinst->state & WAVE_STATE_OPEN) { + emu10k1_wavein_update(wave_dev->card, wiinst); + cinfo.ptr = wiinst->buffer.hw_pos; + cinfo.bytes = cinfo.ptr + wiinst->total_recorded - wiinst->total_recorded % wiinst->buffer.size; + cinfo.blocks = cinfo.bytes / wiinst->buffer.fragment_size - wiinst->blocks; + wiinst->blocks = cinfo.bytes / wiinst->buffer.fragment_size; + } else { + cinfo.ptr = 0; + cinfo.bytes = 0; + cinfo.blocks = 0; + } + + if (wiinst->mmapped) + wiinst->buffer.bytestocopy %= wiinst->buffer.fragment_size; + + spin_unlock_irqrestore(&wiinst->lock, flags); + + if (copy_to_user(p, &cinfo, sizeof(cinfo))) + return -EFAULT; + } + break; + + case SNDCTL_DSP_GETOPTR: + { + count_info cinfo; + + DPF(4, "SNDCTL_DSP_GETOPTR:\n"); + + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + + spin_lock_irqsave(&woinst->lock, flags); + + if (woinst->state & WAVE_STATE_OPEN || + ((woinst->format.passthrough == 1) && wave_dev->card->pt.state)) { + int num_fragments; + + if (woinst->format.passthrough == 1) { + emu10k1_pt_waveout_update(wave_dev); + cinfo.bytes = woinst->total_played; + } else { + emu10k1_waveout_update(woinst); + cinfo.bytes = woinst->total_played; + } + + cinfo.ptr = woinst->buffer.hw_pos; + num_fragments = cinfo.bytes / woinst->buffer.fragment_size; + cinfo.blocks = num_fragments - woinst->blocks; + woinst->blocks = num_fragments; + + cinfo.bytes *= woinst->num_voices; + cinfo.ptr *= woinst->num_voices; + } else { + cinfo.ptr = 0; + cinfo.bytes = 0; + cinfo.blocks = 0; + } + + if (woinst->mmapped) + woinst->buffer.free_bytes %= woinst->buffer.fragment_size; + + spin_unlock_irqrestore(&woinst->lock, flags); + + if (copy_to_user(p, &cinfo, sizeof(cinfo))) + return -EFAULT; + } + break; + + case SNDCTL_DSP_GETBLKSIZE: + DPF(2, "SNDCTL_DSP_GETBLKSIZE:\n"); + + if (file->f_mode & FMODE_WRITE) { + spin_lock_irqsave(&woinst->lock, flags); + + calculate_ofrag(woinst); + val = woinst->buffer.fragment_size * woinst->num_voices; + + spin_unlock_irqrestore(&woinst->lock, flags); + } + + if (file->f_mode & FMODE_READ) { + spin_lock_irqsave(&wiinst->lock, flags); + + calculate_ifrag(wiinst); + val = wiinst->buffer.fragment_size; + + spin_unlock_irqrestore(&wiinst->lock, flags); + } + + return put_user(val, p); + + break; + + case SNDCTL_DSP_POST: + if (file->f_mode & FMODE_WRITE) { + spin_lock_irqsave(&woinst->lock, flags); + + if (!(woinst->state & WAVE_STATE_STARTED) + && (wave_dev->enablebits & PCM_ENABLE_OUTPUT) + && (woinst->total_copied > 0)) + emu10k1_waveout_start(wave_dev); + + spin_unlock_irqrestore(&woinst->lock, flags); + } + + break; + + case SNDCTL_DSP_SUBDIVIDE: + DPF(2, "SNDCTL_DSP_SUBDIVIDE: not implemented\n"); + break; + + case SNDCTL_DSP_SETFRAGMENT: + DPF(2, "SNDCTL_DSP_SETFRAGMENT:\n"); + + if (get_user(val, p)) + return -EFAULT; + + DPD(2, "val is %#x\n", val); + + if (val == 0) + return -EIO; + + if (file->f_mode & FMODE_WRITE) { + /* digital pass-through fragment count and size are fixed values */ + if (woinst->state & WAVE_STATE_OPEN || (woinst->format.passthrough == 1)) + return -EINVAL; /* too late to change */ + + woinst->buffer.ossfragshift = val & 0xffff; + woinst->buffer.numfrags = (val >> 16) & 0xffff; + } + + if (file->f_mode & FMODE_READ) { + if (wiinst->state & WAVE_STATE_OPEN) + return -EINVAL; /* too late to change */ + + wiinst->buffer.ossfragshift = val & 0xffff; + wiinst->buffer.numfrags = (val >> 16) & 0xffff; + } + + break; + + case SNDCTL_COPR_LOAD: + { + copr_buffer *buf; + u32 i; + + DPF(4, "SNDCTL_COPR_LOAD:\n"); + + buf = kmalloc(sizeof(copr_buffer), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, p, sizeof(copr_buffer))) { + kfree (buf); + return -EFAULT; + } + + if ((buf->command != CMD_READ) && (buf->command != CMD_WRITE)) { + kfree (buf); + return -EINVAL; + } + + if (buf->command == CMD_WRITE) { + +#ifdef DBGEMU + if ((buf->offs < 0) || (buf->offs + buf->len > 0xe00) || (buf->len > 1000)) { +#else + if (((buf->offs < 0x100) || (buf->offs + buf->len > (wave_dev->card->is_audigy ? 0xe00 : 0x800)) || (buf->len > 1000) + ) && !( + //any register allowed raw access to users goes here: + (buf->offs == DBG || + buf->offs == A_DBG) + && (buf->len == 1))) { +#endif + kfree(buf); + return -EINVAL; + } + } else { + if ((buf->offs < 0) || (buf->offs + buf->len > 0xe00) || (buf->len > 1000)) { + kfree(buf); + return -EINVAL; + } + } + + if (((unsigned)buf->flags) > 0x3f) + buf->flags = 0; + + if (buf->command == CMD_READ) { + for (i = 0; i < buf->len; i++) + ((u32 *) buf->data)[i] = sblive_readptr(wave_dev->card, buf->offs + i, buf->flags); + + if (copy_to_user(p, buf, sizeof(copr_buffer))) { + kfree(buf); + return -EFAULT; + } + } else { + for (i = 0; i < buf->len; i++) + sblive_writeptr(wave_dev->card, buf->offs + i, buf->flags, ((u32 *) buf->data)[i]); + } + + kfree (buf); + break; + } + + default: /* Default is unrecognized command */ + DPD(2, "default: %#x\n", cmd); + return -EINVAL; + } + return 0; +} + +static struct page *emu10k1_mm_nopage (struct vm_area_struct * vma, unsigned long address, int *type) +{ + struct emu10k1_wavedevice *wave_dev = vma->vm_private_data; + struct woinst *woinst = wave_dev->woinst; + struct wiinst *wiinst = wave_dev->wiinst; + struct page *dmapage; + unsigned long pgoff; + int rd, wr; + + DPF(3, "emu10k1_mm_nopage()\n"); + DPD(3, "addr: %#lx\n", address); + + if (address > vma->vm_end) { + DPF(1, "EXIT, returning NOPAGE_SIGBUS\n"); + return NOPAGE_SIGBUS; /* Disallow mremap */ + } + + pgoff = vma->vm_pgoff + ((address - vma->vm_start) >> PAGE_SHIFT); + if (woinst != NULL) + wr = woinst->mmapped; + else + wr = 0; + + if (wiinst != NULL) + rd = wiinst->mmapped; + else + rd = 0; + + /* if full-duplex (read+write) and we have two sets of bufs, + * then the playback buffers come first, sez soundcard.c */ + if (wr) { + if (pgoff >= woinst->buffer.pages) { + pgoff -= woinst->buffer.pages; + dmapage = virt_to_page ((u8 *) wiinst->buffer.addr + pgoff * PAGE_SIZE); + } else + dmapage = virt_to_page (woinst->voice[0].mem.addr[pgoff]); + } else { + dmapage = virt_to_page ((u8 *) wiinst->buffer.addr + pgoff * PAGE_SIZE); + } + + get_page (dmapage); + + DPD(3, "page: %#lx\n", (unsigned long) dmapage); + if (type) + *type = VM_FAULT_MINOR; + return dmapage; +} + +static struct vm_operations_struct emu10k1_mm_ops = { + .nopage = emu10k1_mm_nopage, +}; + +static int emu10k1_audio_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct emu10k1_wavedevice *wave_dev = (struct emu10k1_wavedevice *) file->private_data; + unsigned long max_pages, n_pages, pgoffset; + struct woinst *woinst = NULL; + struct wiinst *wiinst = NULL; + unsigned long flags; + + DPF(2, "emu10k1_audio_mmap()\n"); + + max_pages = 0; + if (vma->vm_flags & VM_WRITE) { + woinst = wave_dev->woinst; + + spin_lock_irqsave(&woinst->lock, flags); + + /* No m'mapping possible for multichannel */ + if (woinst->num_voices > 1) { + spin_unlock_irqrestore(&woinst->lock, flags); + return -EINVAL; + } + + if (woinst->state == WAVE_STATE_CLOSED) { + calculate_ofrag(woinst); + + if (emu10k1_waveout_open(wave_dev) < 0) { + spin_unlock_irqrestore(&woinst->lock, flags); + ERROR(); + return -EINVAL; + } + } + + woinst->mmapped = 1; + max_pages += woinst->buffer.pages; + spin_unlock_irqrestore(&woinst->lock, flags); + } + + if (vma->vm_flags & VM_READ) { + wiinst = wave_dev->wiinst; + + spin_lock_irqsave(&wiinst->lock, flags); + if (wiinst->state == WAVE_STATE_CLOSED) { + calculate_ifrag(wiinst); + + if (emu10k1_wavein_open(wave_dev) < 0) { + spin_unlock_irqrestore(&wiinst->lock, flags); + ERROR(); + return -EINVAL; + } + } + + wiinst->mmapped = 1; + max_pages += wiinst->buffer.pages; + spin_unlock_irqrestore(&wiinst->lock, flags); + } + + n_pages = ((vma->vm_end - vma->vm_start) + PAGE_SIZE - 1) >> PAGE_SHIFT; + pgoffset = vma->vm_pgoff; + + DPD(2, "vma_start: %#lx, vma_end: %#lx, vma_offset: %ld\n", vma->vm_start, vma->vm_end, pgoffset); + DPD(2, "n_pages: %ld, max_pages: %ld\n", n_pages, max_pages); + + if (pgoffset + n_pages > max_pages) + return -EINVAL; + + vma->vm_flags |= VM_RESERVED; + vma->vm_ops = &emu10k1_mm_ops; + vma->vm_private_data = wave_dev; + return 0; +} + +static int emu10k1_audio_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct emu10k1_card *card = NULL; + struct list_head *entry; + struct emu10k1_wavedevice *wave_dev; + + DPF(2, "emu10k1_audio_open()\n"); + + /* Check for correct device to open */ + + list_for_each(entry, &emu10k1_devs) { + card = list_entry(entry, struct emu10k1_card, list); + + if (!((card->audio_dev ^ minor) & ~0xf) || !((card->audio_dev1 ^ minor) & ~0xf)) + goto match; + } + + return -ENODEV; + +match: + + wave_dev = (struct emu10k1_wavedevice *) kmalloc(sizeof(struct emu10k1_wavedevice), GFP_KERNEL); + + if (wave_dev == NULL) { + ERROR(); + return -ENOMEM; + } + + wave_dev->card = card; + wave_dev->wiinst = NULL; + wave_dev->woinst = NULL; + wave_dev->enablebits = PCM_ENABLE_OUTPUT | PCM_ENABLE_INPUT; /* Default */ + + if (file->f_mode & FMODE_READ) { + /* Recording */ + struct wiinst *wiinst; + + if ((wiinst = (struct wiinst *) kmalloc(sizeof(struct wiinst), GFP_KERNEL)) == NULL) { + ERROR(); + kfree(wave_dev); + return -ENOMEM; + } + + wiinst->recsrc = card->wavein.recsrc; + wiinst->fxwc = card->wavein.fxwc; + + switch (wiinst->recsrc) { + case WAVERECORD_AC97: + wiinst->format.id = AFMT_S16_LE; + wiinst->format.samplingrate = 8000; + wiinst->format.bitsperchannel = 16; + wiinst->format.channels = 1; + break; + case WAVERECORD_MIC: + wiinst->format.id = AFMT_S16_LE; + wiinst->format.samplingrate = 8000; + wiinst->format.bitsperchannel = 16; + wiinst->format.channels = 1; + break; + case WAVERECORD_FX: + wiinst->format.id = AFMT_S16_LE; + wiinst->format.samplingrate = 48000; + wiinst->format.bitsperchannel = 16; + wiinst->format.channels = hweight32(wiinst->fxwc); + break; + default: + kfree(wave_dev); + kfree(wiinst); + BUG(); + break; + } + + wiinst->state = WAVE_STATE_CLOSED; + + wiinst->buffer.ossfragshift = 0; + wiinst->buffer.fragment_size = 0; + wiinst->buffer.numfrags = 0; + + init_waitqueue_head(&wiinst->wait_queue); + + wiinst->mmapped = 0; + wiinst->total_recorded = 0; + wiinst->blocks = 0; + spin_lock_init(&wiinst->lock); + tasklet_init(&wiinst->timer.tasklet, emu10k1_wavein_bh, (unsigned long) wave_dev); + wave_dev->wiinst = wiinst; + emu10k1_wavein_setformat(wave_dev, &wiinst->format); + } + + if (file->f_mode & FMODE_WRITE) { + struct woinst *woinst; + int i; + + if ((woinst = (struct woinst *) kmalloc(sizeof(struct woinst), GFP_KERNEL)) == NULL) { + ERROR(); + kfree(wave_dev); + return -ENOMEM; + } + + if (wave_dev->wiinst != NULL) { + woinst->format = wave_dev->wiinst->format; + } else { + woinst->format.id = AFMT_U8; + woinst->format.samplingrate = 8000; + woinst->format.bitsperchannel = 8; + woinst->format.channels = 1; + } + + woinst->state = WAVE_STATE_CLOSED; + + woinst->buffer.fragment_size = 0; + woinst->buffer.ossfragshift = 0; + woinst->buffer.numfrags = 0; + woinst->device = (card->audio_dev1 == minor); + woinst->timer.state = TIMER_STATE_UNINSTALLED; + woinst->num_voices = 1; + for (i = 0; i < WAVEOUT_MAXVOICES; i++) { + woinst->voice[i].usage = VOICE_USAGE_FREE; + woinst->voice[i].mem.emupageindex = -1; + } + + init_waitqueue_head(&woinst->wait_queue); + + woinst->mmapped = 0; + woinst->total_copied = 0; + woinst->total_played = 0; + woinst->blocks = 0; + spin_lock_init(&woinst->lock); + tasklet_init(&woinst->timer.tasklet, emu10k1_waveout_bh, (unsigned long) wave_dev); + wave_dev->woinst = woinst; + emu10k1_waveout_setformat(wave_dev, &woinst->format); + } + + file->private_data = (void *) wave_dev; + + return nonseekable_open(inode, file); +} + +static int emu10k1_audio_release(struct inode *inode, struct file *file) +{ + struct emu10k1_wavedevice *wave_dev = (struct emu10k1_wavedevice *) file->private_data; + struct emu10k1_card *card; + unsigned long flags; + + card = wave_dev->card; + + DPF(2, "emu10k1_audio_release()\n"); + + if (file->f_mode & FMODE_WRITE) { + struct woinst *woinst = wave_dev->woinst; + + spin_lock_irqsave(&woinst->lock, flags); + if(woinst->format.passthrough==2) + card->pt.state=PT_STATE_PLAYING; + if (woinst->format.passthrough && card->pt.state != PT_STATE_INACTIVE){ + spin_lock(&card->pt.lock); + emu10k1_pt_stop(card); + spin_unlock(&card->pt.lock); + } + if (woinst->state & WAVE_STATE_OPEN) { + if (woinst->state & WAVE_STATE_STARTED) { + if (!(file->f_flags & O_NONBLOCK)) { + while (!signal_pending(current) + && (woinst->total_played < woinst->total_copied)) { + DPF(4, "Buffer hasn't been totally played, sleep....\n"); + spin_unlock_irqrestore(&woinst->lock, flags); + interruptible_sleep_on(&woinst->wait_queue); + spin_lock_irqsave(&woinst->lock, flags); + } + } + } + emu10k1_waveout_close(wave_dev); + } + + spin_unlock_irqrestore(&woinst->lock, flags); + /* remove the tasklet */ + tasklet_kill(&woinst->timer.tasklet); + kfree(wave_dev->woinst); + } + + if (file->f_mode & FMODE_READ) { + struct wiinst *wiinst = wave_dev->wiinst; + + spin_lock_irqsave(&wiinst->lock, flags); + + if (wiinst->state & WAVE_STATE_OPEN) { + emu10k1_wavein_close(wave_dev); + } + + spin_unlock_irqrestore(&wiinst->lock, flags); + tasklet_kill(&wiinst->timer.tasklet); + kfree(wave_dev->wiinst); + } + + kfree(wave_dev); + + if (waitqueue_active(&card->open_wait)) + wake_up_interruptible(&card->open_wait); + + return 0; +} + +/* FIXME sort out poll() + mmap() */ +static unsigned int emu10k1_audio_poll(struct file *file, struct poll_table_struct *wait) +{ + struct emu10k1_wavedevice *wave_dev = (struct emu10k1_wavedevice *) file->private_data; + struct woinst *woinst = wave_dev->woinst; + struct wiinst *wiinst = wave_dev->wiinst; + unsigned int mask = 0; + u32 bytestocopy; + unsigned long flags; + + DPF(4, "emu10k1_audio_poll()\n"); + + if (file->f_mode & FMODE_WRITE) + poll_wait(file, &woinst->wait_queue, wait); + + if (file->f_mode & FMODE_READ) + poll_wait(file, &wiinst->wait_queue, wait); + + if (file->f_mode & FMODE_WRITE) { + spin_lock_irqsave(&woinst->lock, flags); + + if (woinst->state & WAVE_STATE_OPEN) { + emu10k1_waveout_update(woinst); + emu10k1_waveout_getxfersize(woinst, &bytestocopy); + + if (bytestocopy >= woinst->buffer.fragment_size) + mask |= POLLOUT | POLLWRNORM; + } else + mask |= POLLOUT | POLLWRNORM; + + spin_unlock_irqrestore(&woinst->lock, flags); + } + + if (file->f_mode & FMODE_READ) { + spin_lock_irqsave(&wiinst->lock, flags); + + if (wiinst->state & WAVE_STATE_OPEN) { + emu10k1_wavein_update(wave_dev->card, wiinst); + emu10k1_wavein_getxfersize(wiinst, &bytestocopy); + + if (bytestocopy >= wiinst->buffer.fragment_size) + mask |= POLLIN | POLLRDNORM; + } + + spin_unlock_irqrestore(&wiinst->lock, flags); + } + + return mask; +} + +static void calculate_ofrag(struct woinst *woinst) +{ + struct waveout_buffer *buffer = &woinst->buffer; + u32 fragsize; + + if (buffer->fragment_size) + return; + + if (!buffer->ossfragshift) { + fragsize = (woinst->format.bytespervoicesample * woinst->format.samplingrate * WAVEOUT_DEFAULTFRAGLEN) / 1000 - 1; + + while (fragsize) { + fragsize >>= 1; + buffer->ossfragshift++; + } + } + + if (buffer->ossfragshift < WAVEOUT_MINFRAGSHIFT) + buffer->ossfragshift = WAVEOUT_MINFRAGSHIFT; + + buffer->fragment_size = 1 << buffer->ossfragshift; + + while (buffer->fragment_size * WAVEOUT_MINFRAGS > WAVEOUT_MAXBUFSIZE) + buffer->fragment_size >>= 1; + + /* now we are sure that: + (2^WAVEOUT_MINFRAGSHIFT) <= (fragment_size = 2^n) <= (WAVEOUT_MAXBUFSIZE / WAVEOUT_MINFRAGS) + */ + + if (!buffer->numfrags) { + u32 numfrags; + + numfrags = (woinst->format.bytespervoicesample * woinst->format.samplingrate * WAVEOUT_DEFAULTBUFLEN) / + (buffer->fragment_size * 1000) - 1; + + buffer->numfrags = 1; + + while (numfrags) { + numfrags >>= 1; + buffer->numfrags <<= 1; + } + } + + if (buffer->numfrags < WAVEOUT_MINFRAGS) + buffer->numfrags = WAVEOUT_MINFRAGS; + + if (buffer->numfrags * buffer->fragment_size > WAVEOUT_MAXBUFSIZE) + buffer->numfrags = WAVEOUT_MAXBUFSIZE / buffer->fragment_size; + + if (buffer->numfrags < WAVEOUT_MINFRAGS) + BUG(); + + buffer->size = buffer->fragment_size * buffer->numfrags; + buffer->pages = buffer->size / PAGE_SIZE + ((buffer->size % PAGE_SIZE) ? 1 : 0); + + DPD(2, " calculated playback fragment_size -> %d\n", buffer->fragment_size); + DPD(2, " calculated playback numfrags -> %d\n", buffer->numfrags); + + return; +} + +static void calculate_ifrag(struct wiinst *wiinst) +{ + struct wavein_buffer *buffer = &wiinst->buffer; + u32 fragsize, bufsize, size[4]; + int i, j; + + if (buffer->fragment_size) + return; + + if (!buffer->ossfragshift) { + fragsize = (wiinst->format.bytespersec * WAVEIN_DEFAULTFRAGLEN) / 1000 - 1; + + while (fragsize) { + fragsize >>= 1; + buffer->ossfragshift++; + } + } + + if (buffer->ossfragshift < WAVEIN_MINFRAGSHIFT) + buffer->ossfragshift = WAVEIN_MINFRAGSHIFT; + + buffer->fragment_size = 1 << buffer->ossfragshift; + + while (buffer->fragment_size * WAVEIN_MINFRAGS > WAVEIN_MAXBUFSIZE) + buffer->fragment_size >>= 1; + + /* now we are sure that: + (2^WAVEIN_MINFRAGSHIFT) <= (fragment_size = 2^n) <= (WAVEIN_MAXBUFSIZE / WAVEIN_MINFRAGS) + */ + + + if (!buffer->numfrags) + buffer->numfrags = (wiinst->format.bytespersec * WAVEIN_DEFAULTBUFLEN) / (buffer->fragment_size * 1000) - 1; + + if (buffer->numfrags < WAVEIN_MINFRAGS) + buffer->numfrags = WAVEIN_MINFRAGS; + + if (buffer->numfrags * buffer->fragment_size > WAVEIN_MAXBUFSIZE) + buffer->numfrags = WAVEIN_MAXBUFSIZE / buffer->fragment_size; + + if (buffer->numfrags < WAVEIN_MINFRAGS) + BUG(); + + bufsize = buffer->fragment_size * buffer->numfrags; + + /* the buffer size for recording is restricted to certain values, adjust it now */ + if (bufsize >= 0x10000) { + buffer->size = 0x10000; + buffer->sizeregval = 0x1f; + } else { + buffer->size = 0; + size[0] = 384; + size[1] = 448; + size[2] = 512; + size[3] = 640; + + for (i = 0; i < 8; i++) + for (j = 0; j < 4; j++) + if (bufsize >= size[j]) { + buffer->size = size[j]; + size[j] *= 2; + buffer->sizeregval = i * 4 + j + 1; + } else + goto exitloop; + exitloop: + if (buffer->size == 0) { + buffer->size = 384; + buffer->sizeregval = 0x01; + } + } + + /* adjust the fragment size so that buffer size is an integer multiple */ + while (buffer->size % buffer->fragment_size) + buffer->fragment_size >>= 1; + + buffer->numfrags = buffer->size / buffer->fragment_size; + buffer->pages = buffer->size / PAGE_SIZE + ((buffer->size % PAGE_SIZE) ? 1 : 0); + + DPD(2, " calculated recording fragment_size -> %d\n", buffer->fragment_size); + DPD(2, " calculated recording numfrags -> %d\n", buffer->numfrags); + DPD(2, " buffer size register -> %#04x\n", buffer->sizeregval); + + return; +} + +static void emu10k1_wavein_bh(unsigned long refdata) +{ + struct emu10k1_wavedevice *wave_dev = (struct emu10k1_wavedevice *) refdata; + struct wiinst *wiinst = wave_dev->wiinst; + u32 bytestocopy; + unsigned long flags; + + if (!wiinst) + return; + + spin_lock_irqsave(&wiinst->lock, flags); + + if (!(wiinst->state & WAVE_STATE_STARTED)) { + spin_unlock_irqrestore(&wiinst->lock, flags); + return; + } + + emu10k1_wavein_update(wave_dev->card, wiinst); + emu10k1_wavein_getxfersize(wiinst, &bytestocopy); + + spin_unlock_irqrestore(&wiinst->lock, flags); + + if (bytestocopy >= wiinst->buffer.fragment_size) { + if (waitqueue_active(&wiinst->wait_queue)) + wake_up_interruptible(&wiinst->wait_queue); + } else + DPD(3, "Not enough transfer size, %d\n", bytestocopy); + + return; +} + +static void emu10k1_waveout_bh(unsigned long refdata) +{ + struct emu10k1_wavedevice *wave_dev = (struct emu10k1_wavedevice *) refdata; + struct woinst *woinst = wave_dev->woinst; + u32 bytestocopy; + unsigned long flags; + + if (!woinst) + return; + + spin_lock_irqsave(&woinst->lock, flags); + + if (!(woinst->state & WAVE_STATE_STARTED)) { + spin_unlock_irqrestore(&woinst->lock, flags); + return; + } + + emu10k1_waveout_update(woinst); + emu10k1_waveout_getxfersize(woinst, &bytestocopy); + + if (woinst->buffer.fill_silence) { + spin_unlock_irqrestore(&woinst->lock, flags); + emu10k1_waveout_fillsilence(woinst); + } else + spin_unlock_irqrestore(&woinst->lock, flags); + + if (bytestocopy >= woinst->buffer.fragment_size) { + if (waitqueue_active(&woinst->wait_queue)) + wake_up_interruptible(&woinst->wait_queue); + } else + DPD(3, "Not enough transfer size -> %d\n", bytestocopy); + + return; +} + +struct file_operations emu10k1_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = emu10k1_audio_read, + .write = emu10k1_audio_write, + .poll = emu10k1_audio_poll, + .ioctl = emu10k1_audio_ioctl, + .mmap = emu10k1_audio_mmap, + .open = emu10k1_audio_open, + .release = emu10k1_audio_release, +}; diff --git a/sound/oss/emu10k1/audio.h b/sound/oss/emu10k1/audio.h new file mode 100644 index 000000000000..26ee81bbd6c6 --- /dev/null +++ b/sound/oss/emu10k1/audio.h @@ -0,0 +1,44 @@ +/* + ********************************************************************** + * audio.c -- /dev/dsp interface for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * November 2, 1999 Alan Cox cleaned up types/leaks + * + ********************************************************************** + * + * 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 _AUDIO_H +#define _AUDIO_H + +struct emu10k1_wavedevice +{ + struct emu10k1_card *card; + struct wiinst *wiinst; + struct woinst *woinst; + u16 enablebits; +}; + +#endif /* _AUDIO_H */ diff --git a/sound/oss/emu10k1/cardmi.c b/sound/oss/emu10k1/cardmi.c new file mode 100644 index 000000000000..0545814cc67d --- /dev/null +++ b/sound/oss/emu10k1/cardmi.c @@ -0,0 +1,832 @@ +/* + ********************************************************************** + * sblive_mi.c - MIDI UART input HAL for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * November 2, 1999 Alan Cox clean up + * + ********************************************************************** + * + * 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 +#include + +#include "hwaccess.h" +#include "8010.h" +#include "cardmi.h" +#include "irqmgr.h" + + +static int emu10k1_mpuin_callback(struct emu10k1_mpuin *card_mpuin, u32 msg, unsigned long data, u32 bytesvalid); + +static int sblive_miStateInit(struct emu10k1_mpuin *); +static int sblive_miStateEntry(struct emu10k1_mpuin *, u8); +static int sblive_miStateParse(struct emu10k1_mpuin *, u8); +static int sblive_miState3Byte(struct emu10k1_mpuin *, u8); +static int sblive_miState3ByteKey(struct emu10k1_mpuin *, u8); +static int sblive_miState3ByteVel(struct emu10k1_mpuin *, u8); +static int sblive_miState2Byte(struct emu10k1_mpuin *, u8); +static int sblive_miState2ByteKey(struct emu10k1_mpuin *, u8); +static int sblive_miStateSysCommon2(struct emu10k1_mpuin *, u8); +static int sblive_miStateSysCommon2Key(struct emu10k1_mpuin *, u8); +static int sblive_miStateSysCommon3(struct emu10k1_mpuin *, u8); +static int sblive_miStateSysCommon3Key(struct emu10k1_mpuin *, u8); +static int sblive_miStateSysCommon3Vel(struct emu10k1_mpuin *, u8); +static int sblive_miStateSysExNorm(struct emu10k1_mpuin *, u8); +static int sblive_miStateSysReal(struct emu10k1_mpuin *, u8); + + +static struct { + int (*Fn) (struct emu10k1_mpuin *, u8); +} midistatefn[] = { + + { + sblive_miStateParse}, { + sblive_miState3Byte}, /* 0x8n, 0x9n, 0xAn, 0xBn, 0xEn */ + { + sblive_miState3ByteKey}, /* Byte 1 */ + { + sblive_miState3ByteVel}, /* Byte 2 */ + { + sblive_miState2Byte}, /* 0xCn, 0xDn */ + { + sblive_miState2ByteKey}, /* Byte 1 */ + { + sblive_miStateSysCommon2}, /* 0xF1 , 0xF3 */ + { + sblive_miStateSysCommon2Key}, /* 0xF1 , 0xF3, Byte 1 */ + { + sblive_miStateSysCommon3}, /* 0xF2 */ + { + sblive_miStateSysCommon3Key}, /* 0xF2 , Byte 1 */ + { + sblive_miStateSysCommon3Vel}, /* 0xF2 , Byte 2 */ + { + sblive_miStateSysExNorm}, /* 0xF0, 0xF7, Normal mode */ + { + sblive_miStateSysReal} /* 0xF4 - 0xF6 ,0xF8 - 0xFF */ +}; + + +/* Installs the IRQ handler for the MPU in port */ + +/* and initialize parameters */ + +int emu10k1_mpuin_open(struct emu10k1_card *card, struct midi_openinfo *openinfo) +{ + struct emu10k1_mpuin *card_mpuin = card->mpuin; + + DPF(2, "emu10k1_mpuin_open\n"); + + if (!(card_mpuin->status & FLAGS_AVAILABLE)) + return -1; + + /* Copy open info and mark channel as in use */ + card_mpuin->openinfo = *openinfo; + card_mpuin->status &= ~FLAGS_AVAILABLE; /* clear */ + card_mpuin->status |= FLAGS_READY; /* set */ + card_mpuin->status &= ~FLAGS_MIDM_STARTED; /* clear */ + card_mpuin->firstmidiq = NULL; + card_mpuin->lastmidiq = NULL; + card_mpuin->qhead = 0; + card_mpuin->qtail = 0; + + sblive_miStateInit(card_mpuin); + + emu10k1_mpu_reset(card); + emu10k1_mpu_acquire(card); + + return 0; +} + +int emu10k1_mpuin_close(struct emu10k1_card *card) +{ + struct emu10k1_mpuin *card_mpuin = card->mpuin; + + DPF(2, "emu10k1_mpuin_close()\n"); + + /* Check if there are pending input SysEx buffers */ + if (card_mpuin->firstmidiq != NULL) { + ERROR(); + return -1; + } + + /* Disable RX interrupt */ + emu10k1_irq_disable(card, card->is_audigy ? A_INTE_MIDIRXENABLE : INTE_MIDIRXENABLE); + + emu10k1_mpu_release(card); + + card_mpuin->status |= FLAGS_AVAILABLE; /* set */ + card_mpuin->status &= ~FLAGS_MIDM_STARTED; /* clear */ + + return 0; +} + +/* Adds MIDI buffer to local queue list */ + +int emu10k1_mpuin_add_buffer(struct emu10k1_mpuin *card_mpuin, struct midi_hdr *midihdr) +{ + struct midi_queue *midiq; + unsigned long flags; + + DPF(2, "emu10k1_mpuin_add_buffer()\n"); + + /* Update MIDI buffer flags */ + midihdr->flags |= MIDIBUF_INQUEUE; /* set */ + midihdr->flags &= ~MIDIBUF_DONE; /* clear */ + + if ((midiq = (struct midi_queue *) kmalloc(sizeof(struct midi_queue), GFP_ATOMIC)) == NULL) { + /* Message lost */ + return -1; + } + + midiq->next = NULL; + midiq->qtype = 1; + midiq->length = midihdr->bufferlength; + midiq->sizeLeft = midihdr->bufferlength; + midiq->midibyte = midihdr->data; + midiq->refdata = (unsigned long) midihdr; + + spin_lock_irqsave(&card_mpuin->lock, flags); + + if (card_mpuin->firstmidiq == NULL) { + card_mpuin->firstmidiq = midiq; + card_mpuin->lastmidiq = midiq; + } else { + (card_mpuin->lastmidiq)->next = midiq; + card_mpuin->lastmidiq = midiq; + } + + spin_unlock_irqrestore(&card_mpuin->lock, flags); + + return 0; +} + +/* First set the Time Stamp if MIDI IN has not started. */ + +/* Then enable RX Irq. */ + +int emu10k1_mpuin_start(struct emu10k1_card *card) +{ + struct emu10k1_mpuin *card_mpuin = card->mpuin; + u8 dummy; + + DPF(2, "emu10k1_mpuin_start()\n"); + + /* Set timestamp if not set */ + if (card_mpuin->status & FLAGS_MIDM_STARTED) { + DPF(2, "Time Stamp not changed\n"); + } else { + while (!emu10k1_mpu_read_data(card, &dummy)); + + card_mpuin->status |= FLAGS_MIDM_STARTED; /* set */ + + /* Set new time stamp */ + card_mpuin->timestart = (jiffies * 1000) / HZ; + DPD(2, "New Time Stamp = %d\n", card_mpuin->timestart); + + card_mpuin->qhead = 0; + card_mpuin->qtail = 0; + + emu10k1_irq_enable(card, card->is_audigy ? A_INTE_MIDIRXENABLE : INTE_MIDIRXENABLE); + } + + return 0; +} + +/* Disable the RX Irq. If a partial recorded buffer */ + +/* exist, send it up to IMIDI level. */ + +int emu10k1_mpuin_stop(struct emu10k1_card *card) +{ + struct emu10k1_mpuin *card_mpuin = card->mpuin; + struct midi_queue *midiq; + unsigned long flags; + + DPF(2, "emu10k1_mpuin_stop()\n"); + + emu10k1_irq_disable(card, card->is_audigy ? A_INTE_MIDIRXENABLE : INTE_MIDIRXENABLE); + + card_mpuin->status &= ~FLAGS_MIDM_STARTED; /* clear */ + + if (card_mpuin->firstmidiq) { + spin_lock_irqsave(&card_mpuin->lock, flags); + + midiq = card_mpuin->firstmidiq; + if (midiq != NULL) { + if (midiq->sizeLeft == midiq->length) + midiq = NULL; + else { + card_mpuin->firstmidiq = midiq->next; + if (card_mpuin->firstmidiq == NULL) + card_mpuin->lastmidiq = NULL; + } + } + + spin_unlock_irqrestore(&card_mpuin->lock, flags); + + if (midiq) { + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INLONGERROR, (unsigned long) midiq, 0); + kfree(midiq); + } + } + + return 0; +} + +/* Disable the RX Irq. If any buffer */ + +/* exist, send it up to IMIDI level. */ +int emu10k1_mpuin_reset(struct emu10k1_card *card) +{ + struct emu10k1_mpuin *card_mpuin = card->mpuin; + struct midi_queue *midiq; + + DPF(2, "emu10k1_mpuin_reset()\n"); + + emu10k1_irq_disable(card, card->is_audigy ? A_INTE_MIDIRXENABLE : INTE_MIDIRXENABLE); + + while (card_mpuin->firstmidiq) { + midiq = card_mpuin->firstmidiq; + card_mpuin->firstmidiq = midiq->next; + + if (midiq->sizeLeft == midiq->length) + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INLONGDATA, (unsigned long) midiq, 0); + else + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INLONGERROR, (unsigned long) midiq, 0); + + kfree(midiq); + } + + card_mpuin->lastmidiq = NULL; + card_mpuin->status &= ~FLAGS_MIDM_STARTED; + + return 0; +} + +/* Passes the message with the data back to the client */ + +/* via IRQ & DPC callbacks to Ring 3 */ +static int emu10k1_mpuin_callback(struct emu10k1_mpuin *card_mpuin, u32 msg, unsigned long data, u32 bytesvalid) +{ + unsigned long timein; + struct midi_queue *midiq; + unsigned long callback_msg[3]; + struct midi_hdr *midihdr; + + /* Called during ISR. The data & code touched are: + * 1. card_mpuin + * 2. The function to be called + */ + + timein = card_mpuin->timein; + if (card_mpuin->timestart <= timein) + callback_msg[0] = timein - card_mpuin->timestart; + else + callback_msg[0] = (~0x0L - card_mpuin->timestart) + timein; + + if (msg == ICARDMIDI_INDATA || msg == ICARDMIDI_INDATAERROR) { + callback_msg[1] = data; + callback_msg[2] = bytesvalid; + DPD(2, "emu10k1_mpuin_callback: midimsg = %#lx\n", data); + } else { + midiq = (struct midi_queue *) data; + midihdr = (struct midi_hdr *) midiq->refdata; + + callback_msg[1] = midiq->length - midiq->sizeLeft; + callback_msg[2] = midiq->refdata; + midihdr->flags &= ~MIDIBUF_INQUEUE; + midihdr->flags |= MIDIBUF_DONE; + + midihdr->bytesrecorded = midiq->length - midiq->sizeLeft; + } + + /* Notify client that Sysex buffer has been sent */ + emu10k1_midi_callback(msg, card_mpuin->openinfo.refdata, callback_msg); + + return 0; +} + +void emu10k1_mpuin_bh(unsigned long refdata) +{ + u8 data; + unsigned idx; + struct emu10k1_mpuin *card_mpuin = (struct emu10k1_mpuin *) refdata; + unsigned long flags; + + while (card_mpuin->qhead != card_mpuin->qtail) { + spin_lock_irqsave(&card_mpuin->lock, flags); + idx = card_mpuin->qhead; + data = card_mpuin->midiq[idx].data; + card_mpuin->timein = card_mpuin->midiq[idx].timein; + idx = (idx + 1) % MIDIIN_MAX_BUFFER_SIZE; + card_mpuin->qhead = idx; + spin_unlock_irqrestore(&card_mpuin->lock, flags); + + sblive_miStateEntry(card_mpuin, data); + } + + return; +} + +/* IRQ callback handler routine for the MPU in port */ + +int emu10k1_mpuin_irqhandler(struct emu10k1_card *card) +{ + unsigned idx; + unsigned count; + u8 MPUIvalue; + struct emu10k1_mpuin *card_mpuin = card->mpuin; + + /* IRQ service routine. The data and code touched are: + * 1. card_mpuin + */ + + count = 0; + idx = card_mpuin->qtail; + + while (1) { + if (emu10k1_mpu_read_data(card, &MPUIvalue) < 0) { + break; + } else { + ++count; + card_mpuin->midiq[idx].data = MPUIvalue; + card_mpuin->midiq[idx].timein = (jiffies * 1000) / HZ; + idx = (idx + 1) % MIDIIN_MAX_BUFFER_SIZE; + } + } + + if (count) { + card_mpuin->qtail = idx; + + tasklet_hi_schedule(&card_mpuin->tasklet); + } + + return 0; +} + +/*****************************************************************************/ + +/* Supporting functions for Midi-In Interpretation State Machine */ + +/*****************************************************************************/ + +/* FIXME: This should be a macro */ +static int sblive_miStateInit(struct emu10k1_mpuin *card_mpuin) +{ + card_mpuin->status = 0; /* For MIDI running status */ + card_mpuin->fstatus = 0; /* For 0xFn status only */ + card_mpuin->curstate = STIN_PARSE; + card_mpuin->laststate = STIN_PARSE; + card_mpuin->data = 0; + card_mpuin->timestart = 0; + card_mpuin->timein = 0; + + return 0; +} + +/* FIXME: This should be a macro */ +static int sblive_miStateEntry(struct emu10k1_mpuin *card_mpuin, u8 data) +{ + return midistatefn[card_mpuin->curstate].Fn(card_mpuin, data); +} + +static int sblive_miStateParse(struct emu10k1_mpuin *card_mpuin, u8 data) +{ + switch (data & 0xf0) { + case 0x80: + case 0x90: + case 0xA0: + case 0xB0: + case 0xE0: + card_mpuin->curstate = STIN_3BYTE; + break; + + case 0xC0: + case 0xD0: + card_mpuin->curstate = STIN_2BYTE; + break; + + case 0xF0: + /* System messages do not affect the previous running status! */ + switch (data & 0x0f) { + case 0x0: + card_mpuin->laststate = card_mpuin->curstate; + card_mpuin->curstate = STIN_SYS_EX_NORM; + + if (card_mpuin->firstmidiq) { + struct midi_queue *midiq; + + midiq = card_mpuin->firstmidiq; + *midiq->midibyte = data; + --midiq->sizeLeft; + ++midiq->midibyte; + } + + return CTSTATUS_NEXT_BYTE; + + case 0x7: + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATAERROR, 0xf7, 0); + return -1; + + case 0x2: + card_mpuin->laststate = card_mpuin->curstate; + card_mpuin->curstate = STIN_SYS_COMMON_3; + break; + + case 0x1: + case 0x3: + card_mpuin->laststate = card_mpuin->curstate; + card_mpuin->curstate = STIN_SYS_COMMON_2; + break; + + default: + /* includes 0xF4 - 0xF6, 0xF8 - 0xFF */ + return midistatefn[STIN_SYS_REAL].Fn(card_mpuin, data); + } + + break; + + default: + DPF(2, "BUG: default case hit\n"); + return -1; + } + + return midistatefn[card_mpuin->curstate].Fn(card_mpuin, data); +} + +static int sblive_miState3Byte(struct emu10k1_mpuin *card_mpuin, u8 data) +{ + u8 temp = data & 0xf0; + + if (temp < 0x80) { + return midistatefn[STIN_3BYTE_KEY].Fn(card_mpuin, data); + } else if (temp <= 0xe0 && temp != 0xc0 && temp != 0xd0) { + card_mpuin->status = data; + card_mpuin->curstate = STIN_3BYTE_KEY; + + return CTSTATUS_NEXT_BYTE; + } + + return midistatefn[STIN_PARSE].Fn(card_mpuin, data); +} + +static int sblive_miState3ByteKey(struct emu10k1_mpuin *card_mpuin, u8 data) +/* byte 1 */ +{ + unsigned long tmp; + + if (data > 0x7f) { + /* Real-time messages check */ + if (data > 0xf7) + return midistatefn[STIN_SYS_REAL].Fn(card_mpuin, data); + + /* Invalid data! */ + DPF(2, "Invalid data!\n"); + + card_mpuin->curstate = STIN_PARSE; + tmp = ((unsigned long) data) << 8; + tmp |= (unsigned long) card_mpuin->status; + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATAERROR, tmp, 0); + + return -1; + } + + card_mpuin->data = data; + card_mpuin->curstate = STIN_3BYTE_VEL; + + return CTSTATUS_NEXT_BYTE; +} + +static int sblive_miState3ByteVel(struct emu10k1_mpuin *card_mpuin, u8 data) +/* byte 2 */ +{ + unsigned long tmp; + + if (data > 0x7f) { + /* Real-time messages check */ + if (data > 0xf7) + return midistatefn[STIN_SYS_REAL].Fn(card_mpuin, data); + + /* Invalid data! */ + DPF(2, "Invalid data!\n"); + + card_mpuin->curstate = STIN_PARSE; + tmp = ((unsigned long) data) << 8; + tmp |= card_mpuin->data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->status; + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATAERROR, tmp, 0); + + return -1; + } + + card_mpuin->curstate = STIN_3BYTE; + tmp = (unsigned long) data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->status; + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATA, tmp, 3); + + return 0; +} + +static int sblive_miState2Byte(struct emu10k1_mpuin *card_mpuin, u8 data) +{ + u8 temp = data & 0xf0; + + if ((temp == 0xc0) || (temp == 0xd0)) { + card_mpuin->status = data; + card_mpuin->curstate = STIN_2BYTE_KEY; + + return CTSTATUS_NEXT_BYTE; + } + + if (temp < 0x80) + return midistatefn[STIN_2BYTE_KEY].Fn(card_mpuin, data); + + return midistatefn[STIN_PARSE].Fn(card_mpuin, data); +} + +static int sblive_miState2ByteKey(struct emu10k1_mpuin *card_mpuin, u8 data) +/* byte 1 */ +{ + unsigned long tmp; + + if (data > 0x7f) { + /* Real-time messages check */ + if (data > 0xf7) + return midistatefn[STIN_SYS_REAL].Fn(card_mpuin, data); + + /* Invalid data! */ + DPF(2, "Invalid data!\n"); + + card_mpuin->curstate = STIN_PARSE; + tmp = (unsigned long) data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->status; + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATAERROR, tmp, 0); + + return -1; + } + + card_mpuin->curstate = STIN_2BYTE; + tmp = (unsigned long) data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->status; + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATA, tmp, 2); + + return 0; +} + +static int sblive_miStateSysCommon2(struct emu10k1_mpuin *card_mpuin, u8 data) +{ + card_mpuin->fstatus = data; + card_mpuin->curstate = STIN_SYS_COMMON_2_KEY; + + return CTSTATUS_NEXT_BYTE; +} + +static int sblive_miStateSysCommon2Key(struct emu10k1_mpuin *card_mpuin, u8 data) +/* byte 1 */ +{ + unsigned long tmp; + + if (data > 0x7f) { + /* Real-time messages check */ + if (data > 0xf7) + return midistatefn[STIN_SYS_REAL].Fn(card_mpuin, data); + + /* Invalid data! */ + DPF(2, "Invalid data!\n"); + + card_mpuin->curstate = card_mpuin->laststate; + tmp = (unsigned long) data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->fstatus; + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATAERROR, tmp, 0); + + return -1; + } + + card_mpuin->curstate = card_mpuin->laststate; + tmp = (unsigned long) data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->fstatus; + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATA, tmp, 2); + + return 0; +} + +static int sblive_miStateSysCommon3(struct emu10k1_mpuin *card_mpuin, u8 data) +{ + card_mpuin->fstatus = data; + card_mpuin->curstate = STIN_SYS_COMMON_3_KEY; + + return CTSTATUS_NEXT_BYTE; +} + +static int sblive_miStateSysCommon3Key(struct emu10k1_mpuin *card_mpuin, u8 data) +/* byte 1 */ +{ + unsigned long tmp; + + if (data > 0x7f) { + /* Real-time messages check */ + if (data > 0xf7) + return midistatefn[STIN_SYS_REAL].Fn(card_mpuin, data); + + /* Invalid data! */ + DPF(2, "Invalid data!\n"); + + card_mpuin->curstate = card_mpuin->laststate; + tmp = (unsigned long) data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->fstatus; + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATAERROR, tmp, 0); + + return -1; + } + + card_mpuin->data = data; + card_mpuin->curstate = STIN_SYS_COMMON_3_VEL; + + return CTSTATUS_NEXT_BYTE; +} + +static int sblive_miStateSysCommon3Vel(struct emu10k1_mpuin *card_mpuin, u8 data) +/* byte 2 */ +{ + unsigned long tmp; + + if (data > 0x7f) { + /* Real-time messages check */ + if (data > 0xf7) + return midistatefn[STIN_SYS_REAL].Fn(card_mpuin, data); + + /* Invalid data! */ + DPF(2, "Invalid data!\n"); + + card_mpuin->curstate = card_mpuin->laststate; + tmp = (unsigned long) data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->fstatus; + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATAERROR, tmp, 0); + + return -1; + } + + card_mpuin->curstate = card_mpuin->laststate; + tmp = (unsigned long) data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->data; + tmp = tmp << 8; + tmp |= (unsigned long) card_mpuin->fstatus; + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATA, tmp, 3); + + return 0; +} + +static int sblive_miStateSysExNorm(struct emu10k1_mpuin *card_mpuin, u8 data) +{ + unsigned long flags; + + if ((data > 0x7f) && (data != 0xf7)) { + /* Real-time messages check */ + if (data > 0xf7) + return midistatefn[STIN_SYS_REAL].Fn(card_mpuin, data); + + /* Invalid Data! */ + DPF(2, "Invalid data!\n"); + + card_mpuin->curstate = card_mpuin->laststate; + + if (card_mpuin->firstmidiq) { + struct midi_queue *midiq; + + midiq = card_mpuin->firstmidiq; + *midiq->midibyte = data; + --midiq->sizeLeft; + ++midiq->midibyte; + + spin_lock_irqsave(&card_mpuin->lock, flags); + + card_mpuin->firstmidiq = midiq->next; + if (card_mpuin->firstmidiq == NULL) + card_mpuin->lastmidiq = NULL; + + spin_unlock_irqrestore(&card_mpuin->lock, flags); + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INLONGERROR, (unsigned long) midiq, 0); + + kfree(midiq); + } + + return -1; + } + + if (card_mpuin->firstmidiq) { + struct midi_queue *midiq; + + midiq = card_mpuin->firstmidiq; + *midiq->midibyte = data; + --midiq->sizeLeft; + ++midiq->midibyte; + } + + if (data == 0xf7) { + /* End of Sysex buffer */ + /* Send down the buffer */ + + card_mpuin->curstate = card_mpuin->laststate; + + if (card_mpuin->firstmidiq) { + struct midi_queue *midiq; + + midiq = card_mpuin->firstmidiq; + + spin_lock_irqsave(&card_mpuin->lock, flags); + + card_mpuin->firstmidiq = midiq->next; + if (card_mpuin->firstmidiq == NULL) + card_mpuin->lastmidiq = NULL; + + spin_unlock_irqrestore(&card_mpuin->lock, flags); + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INLONGDATA, (unsigned long) midiq, 0); + + kfree(midiq); + } + + return 0; + } + + if (card_mpuin->firstmidiq) { + struct midi_queue *midiq; + + midiq = card_mpuin->firstmidiq; + + if (midiq->sizeLeft == 0) { + /* Special case */ + + spin_lock_irqsave(&card_mpuin->lock, flags); + + card_mpuin->firstmidiq = midiq->next; + if (card_mpuin->firstmidiq == NULL) + card_mpuin->lastmidiq = NULL; + + spin_unlock_irqrestore(&card_mpuin->lock, flags); + + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INLONGDATA, (unsigned long) midiq, 0); + + kfree(midiq); + + return CTSTATUS_NEXT_BYTE; + } + } + + return CTSTATUS_NEXT_BYTE; +} + +static int sblive_miStateSysReal(struct emu10k1_mpuin *card_mpuin, u8 data) +{ + emu10k1_mpuin_callback(card_mpuin, ICARDMIDI_INDATA, data, 1); + + return CTSTATUS_NEXT_BYTE; +} diff --git a/sound/oss/emu10k1/cardmi.h b/sound/oss/emu10k1/cardmi.h new file mode 100644 index 000000000000..d12c24116307 --- /dev/null +++ b/sound/oss/emu10k1/cardmi.h @@ -0,0 +1,97 @@ +/* + ********************************************************************** + * sblive_mi.h + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * November 2, 1999 Alan Cox cleaned up + * + ********************************************************************** + * + * 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 _CARDMI_H +#define _CARDMI_H + +#include "icardmid.h" +#include + +typedef enum +{ + STIN_PARSE = 0, + STIN_3BYTE, /* 0x80, 0x90, 0xA0, 0xB0, 0xE0 */ + STIN_3BYTE_KEY, /* Byte 1 */ + STIN_3BYTE_VEL, /* Byte 1 */ + STIN_2BYTE, /* 0xC0, 0xD0 */ + STIN_2BYTE_KEY, /* Byte 1 */ + STIN_SYS_COMMON_2, /* 0xF1, 0xF3 */ + STIN_SYS_COMMON_2_KEY, + STIN_SYS_COMMON_3, /* 0xF2 */ + STIN_SYS_COMMON_3_KEY, + STIN_SYS_COMMON_3_VEL, + STIN_SYS_EX_NORM, /* 0xF0, Normal mode */ + STIN_SYS_REAL +} midi_in_state; + + +/* flags for card MIDI in object */ +#define FLAGS_MIDM_STARTED 0x00001000 // Data has started to come in after Midm Start +#define MIDIIN_MAX_BUFFER_SIZE 200 // Definition for struct emu10k1_mpuin + +struct midi_data +{ + u8 data; + u32 timein; +}; + +struct emu10k1_mpuin +{ + spinlock_t lock; + struct midi_queue *firstmidiq; + struct midi_queue *lastmidiq; + unsigned qhead, qtail; + struct midi_data midiq[MIDIIN_MAX_BUFFER_SIZE]; + struct tasklet_struct tasklet; + struct midi_openinfo openinfo; + + /* For MIDI state machine */ + u8 status; /* For MIDI running status */ + u8 fstatus; /* For 0xFn status only */ + midi_in_state curstate; + midi_in_state laststate; + u32 timestart; + u32 timein; + u8 data; +}; + +int emu10k1_mpuin_open(struct emu10k1_card *, struct midi_openinfo *); +int emu10k1_mpuin_close(struct emu10k1_card *); +int emu10k1_mpuin_add_buffer(struct emu10k1_mpuin *, struct midi_hdr *); +int emu10k1_mpuin_start(struct emu10k1_card *); +int emu10k1_mpuin_stop(struct emu10k1_card *); +int emu10k1_mpuin_reset(struct emu10k1_card *); + +int emu10k1_mpuin_irqhandler(struct emu10k1_card *); +void emu10k1_mpuin_bh(unsigned long); + +#endif /* _CARDMI_H */ diff --git a/sound/oss/emu10k1/cardmo.c b/sound/oss/emu10k1/cardmo.c new file mode 100644 index 000000000000..5938d31f9e21 --- /dev/null +++ b/sound/oss/emu10k1/cardmo.c @@ -0,0 +1,229 @@ +/* + ********************************************************************** + * cardmo.c - MIDI UART output HAL for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * November 2, 1999 Alan Cox cleaned up + * + ********************************************************************** + * + * 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 + +#include "hwaccess.h" +#include "8010.h" +#include "cardmo.h" +#include "irqmgr.h" + +/* Installs the IRQ handler for the MPU out port * + * and initialize parameters */ + +int emu10k1_mpuout_open(struct emu10k1_card *card, struct midi_openinfo *openinfo) +{ + struct emu10k1_mpuout *card_mpuout = card->mpuout; + + DPF(2, "emu10k1_mpuout_open()\n"); + + if (!(card_mpuout->status & FLAGS_AVAILABLE)) + return -1; + + /* Copy open info and mark channel as in use */ + card_mpuout->intr = 0; + card_mpuout->openinfo = *openinfo; + card_mpuout->status &= ~FLAGS_AVAILABLE; + card_mpuout->laststatus = 0x80; + card_mpuout->firstmidiq = NULL; + card_mpuout->lastmidiq = NULL; + + emu10k1_mpu_reset(card); + emu10k1_mpu_acquire(card); + + return 0; +} + +int emu10k1_mpuout_close(struct emu10k1_card *card) +{ + struct emu10k1_mpuout *card_mpuout = card->mpuout; + struct midi_queue *midiq; + struct midi_hdr *midihdr; + unsigned long flags; + + DPF(2, "emu10k1_mpuout_close()\n"); + + emu10k1_irq_disable(card, card->is_audigy ? A_INTE_MIDITXENABLE : INTE_MIDITXENABLE); + + spin_lock_irqsave(&card_mpuout->lock, flags); + + while (card_mpuout->firstmidiq != NULL) { + midiq = card_mpuout->firstmidiq; + midihdr = (struct midi_hdr *) midiq->refdata; + + card_mpuout->firstmidiq = midiq->next; + + kfree(midihdr->data); + kfree(midihdr); + kfree(midiq); + } + + card_mpuout->lastmidiq = NULL; + + emu10k1_mpu_release(card); + + card_mpuout->status |= FLAGS_AVAILABLE; + + spin_unlock_irqrestore(&card_mpuout->lock, flags); + + return 0; +} + +/* If there isn't enough buffer space, reject Midi Buffer. * +* Otherwise, disable TX, create object to hold Midi * +* uffer, update buffer flags and other parameters * +* before enabling TX again. */ + +int emu10k1_mpuout_add_buffer(struct emu10k1_card *card, struct midi_hdr *midihdr) +{ + struct emu10k1_mpuout *card_mpuout = card->mpuout; + struct midi_queue *midiq; + unsigned long flags; + + DPF(2, "emu10k1_mpuout_add_buffer()\n"); + + if (card_mpuout->state == CARDMIDIOUT_STATE_SUSPEND) + return 0; + + midihdr->flags |= MIDIBUF_INQUEUE; + midihdr->flags &= ~MIDIBUF_DONE; + + if ((midiq = (struct midi_queue *) kmalloc(sizeof(struct midi_queue), GFP_KERNEL)) == NULL) { + /* Message lost */ + return -1; + } + + midiq->next = NULL; + midiq->qtype = 1; + midiq->length = midihdr->bufferlength; + midiq->sizeLeft = midihdr->bufferlength; + midiq->midibyte = midihdr->data; + + midiq->refdata = (unsigned long) midihdr; + + spin_lock_irqsave(&card_mpuout->lock, flags); + + if (card_mpuout->firstmidiq == NULL) { + card_mpuout->firstmidiq = midiq; + card_mpuout->lastmidiq = midiq; + } else { + (card_mpuout->lastmidiq)->next = midiq; + card_mpuout->lastmidiq = midiq; + } + + card_mpuout->intr = 0; + + emu10k1_irq_enable(card, card->is_audigy ? A_INTE_MIDITXENABLE : INTE_MIDITXENABLE); + + spin_unlock_irqrestore(&card_mpuout->lock, flags); + + return 0; +} + +void emu10k1_mpuout_bh(unsigned long refdata) +{ + struct emu10k1_card *card = (struct emu10k1_card *) refdata; + struct emu10k1_mpuout *card_mpuout = card->mpuout; + int cByteSent = 0; + struct midi_queue *midiq; + struct midi_queue *doneq = NULL; + unsigned long flags; + + spin_lock_irqsave(&card_mpuout->lock, flags); + + while (card_mpuout->firstmidiq != NULL) { + midiq = card_mpuout->firstmidiq; + + while (cByteSent < 4 && midiq->sizeLeft) { + if (emu10k1_mpu_write_data(card, *midiq->midibyte) < 0) { + DPF(2, "emu10k1_mpuoutDpcCallback error!!\n"); + } else { + ++cByteSent; + --midiq->sizeLeft; + ++midiq->midibyte; + } + } + + if (midiq->sizeLeft == 0) { + if (doneq == NULL) + doneq = midiq; + card_mpuout->firstmidiq = midiq->next; + } else + break; + } + + if (card_mpuout->firstmidiq == NULL) + card_mpuout->lastmidiq = NULL; + + if (doneq != NULL) { + while (doneq != card_mpuout->firstmidiq) { + unsigned long callback_msg[3]; + + midiq = doneq; + doneq = midiq->next; + + if (midiq->qtype) { + callback_msg[0] = 0; + callback_msg[1] = midiq->length; + callback_msg[2] = midiq->refdata; + + emu10k1_midi_callback(ICARDMIDI_OUTLONGDATA, card_mpuout->openinfo.refdata, callback_msg); + } else if (((u8) midiq->refdata) < 0xF0 && ((u8) midiq->refdata) > 0x7F) + card_mpuout->laststatus = (u8) midiq->refdata; + + kfree(midiq); + } + } + + if ((card_mpuout->firstmidiq != NULL) || cByteSent) { + card_mpuout->intr = 0; + emu10k1_irq_enable(card, card->is_audigy ? A_INTE_MIDITXENABLE : INTE_MIDITXENABLE); + } + + spin_unlock_irqrestore(&card_mpuout->lock, flags); + + return; +} + +int emu10k1_mpuout_irqhandler(struct emu10k1_card *card) +{ + struct emu10k1_mpuout *card_mpuout = card->mpuout; + + DPF(4, "emu10k1_mpuout_irqhandler\n"); + + card_mpuout->intr = 1; + emu10k1_irq_disable(card, card->is_audigy ? A_INTE_MIDITXENABLE : INTE_MIDITXENABLE); + + tasklet_hi_schedule(&card_mpuout->tasklet); + + return 0; +} diff --git a/sound/oss/emu10k1/cardmo.h b/sound/oss/emu10k1/cardmo.h new file mode 100644 index 000000000000..7026eb3a85af --- /dev/null +++ b/sound/oss/emu10k1/cardmo.h @@ -0,0 +1,62 @@ +/* + ********************************************************************** + * cardmo.h + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * November 2, 1999 Alan Cox cleaned up + * + ********************************************************************** + * + * 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 _CARDMO_H +#define _CARDMO_H + +#include "icardmid.h" +#include + +#define CARDMIDIOUT_STATE_DEFAULT 0x00000000 +#define CARDMIDIOUT_STATE_SUSPEND 0x00000001 + +struct emu10k1_mpuout +{ + u32 status; + u32 state; + volatile int intr; + struct midi_queue *firstmidiq; + struct midi_queue *lastmidiq; + u8 laststatus; + struct tasklet_struct tasklet; + spinlock_t lock; + struct midi_openinfo openinfo; +}; + +int emu10k1_mpuout_open(struct emu10k1_card *, struct midi_openinfo *); +int emu10k1_mpuout_close(struct emu10k1_card *); +int emu10k1_mpuout_add_buffer(struct emu10k1_card *, struct midi_hdr *); + +int emu10k1_mpuout_irqhandler(struct emu10k1_card *); +void emu10k1_mpuout_bh(unsigned long); + +#endif /* _CARDMO_H */ diff --git a/sound/oss/emu10k1/cardwi.c b/sound/oss/emu10k1/cardwi.c new file mode 100644 index 000000000000..8bbf44b881b4 --- /dev/null +++ b/sound/oss/emu10k1/cardwi.c @@ -0,0 +1,373 @@ +/* + ********************************************************************** + * cardwi.c - PCM input HAL for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 +#include "hwaccess.h" +#include "timer.h" +#include "recmgr.h" +#include "audio.h" +#include "cardwi.h" + +/** + * query_format - returns a valid sound format + * + * This function will return a valid sound format as close + * to the requested one as possible. + */ +static void query_format(int recsrc, struct wave_format *wave_fmt) +{ + + switch (recsrc) { + case WAVERECORD_AC97: + + if ((wave_fmt->channels != 1) && (wave_fmt->channels != 2)) + wave_fmt->channels = 2; + + if (wave_fmt->samplingrate >= (0xBB80 + 0xAC44) / 2) + wave_fmt->samplingrate = 0xBB80; + else if (wave_fmt->samplingrate >= (0xAC44 + 0x7D00) / 2) + wave_fmt->samplingrate = 0xAC44; + else if (wave_fmt->samplingrate >= (0x7D00 + 0x5DC0) / 2) + wave_fmt->samplingrate = 0x7D00; + else if (wave_fmt->samplingrate >= (0x5DC0 + 0x5622) / 2) + wave_fmt->samplingrate = 0x5DC0; + else if (wave_fmt->samplingrate >= (0x5622 + 0x3E80) / 2) + wave_fmt->samplingrate = 0x5622; + else if (wave_fmt->samplingrate >= (0x3E80 + 0x2B11) / 2) + wave_fmt->samplingrate = 0x3E80; + else if (wave_fmt->samplingrate >= (0x2B11 + 0x1F40) / 2) + wave_fmt->samplingrate = 0x2B11; + else + wave_fmt->samplingrate = 0x1F40; + + switch (wave_fmt->id) { + case AFMT_S16_LE: + wave_fmt->bitsperchannel = 16; + break; + case AFMT_U8: + wave_fmt->bitsperchannel = 8; + break; + default: + wave_fmt->id = AFMT_S16_LE; + wave_fmt->bitsperchannel = 16; + break; + } + + break; + + /* these can't be changed from the original values */ + case WAVERECORD_MIC: + case WAVERECORD_FX: + break; + + default: + BUG(); + break; + } + + wave_fmt->bytesperchannel = wave_fmt->bitsperchannel >> 3; + wave_fmt->bytespersample = wave_fmt->channels * wave_fmt->bytesperchannel; + wave_fmt->bytespersec = wave_fmt->bytespersample * wave_fmt->samplingrate; + wave_fmt->bytespervoicesample = wave_fmt->bytespersample; +} + +static int alloc_buffer(struct emu10k1_card *card, struct wavein_buffer *buffer) +{ + buffer->addr = pci_alloc_consistent(card->pci_dev, buffer->size * buffer->cov, + &buffer->dma_handle); + if (buffer->addr == NULL) + return -1; + + return 0; +} + +static void free_buffer(struct emu10k1_card *card, struct wavein_buffer *buffer) +{ + if (buffer->addr != NULL) + pci_free_consistent(card->pci_dev, buffer->size * buffer->cov, + buffer->addr, buffer->dma_handle); +} + +int emu10k1_wavein_open(struct emu10k1_wavedevice *wave_dev) +{ + struct emu10k1_card *card = wave_dev->card; + struct wiinst *wiinst = wave_dev->wiinst; + struct wiinst **wiinst_tmp = NULL; + u16 delay; + unsigned long flags; + + DPF(2, "emu10k1_wavein_open()\n"); + + switch (wiinst->recsrc) { + case WAVERECORD_AC97: + wiinst_tmp = &card->wavein.ac97; + break; + case WAVERECORD_MIC: + wiinst_tmp = &card->wavein.mic; + break; + case WAVERECORD_FX: + wiinst_tmp = &card->wavein.fx; + break; + default: + BUG(); + break; + } + + spin_lock_irqsave(&card->lock, flags); + if (*wiinst_tmp != NULL) { + spin_unlock_irqrestore(&card->lock, flags); + return -1; + } + + *wiinst_tmp = wiinst; + spin_unlock_irqrestore(&card->lock, flags); + + /* handle 8 bit recording */ + if (wiinst->format.bytesperchannel == 1) { + if (wiinst->buffer.size > 0x8000) { + wiinst->buffer.size = 0x8000; + wiinst->buffer.sizeregval = 0x1f; + } else + wiinst->buffer.sizeregval += 4; + + wiinst->buffer.cov = 2; + } else + wiinst->buffer.cov = 1; + + if (alloc_buffer(card, &wiinst->buffer) < 0) { + ERROR(); + return -1; + } + + emu10k1_set_record_src(card, wiinst); + + emu10k1_reset_record(card, &wiinst->buffer); + + wiinst->buffer.hw_pos = 0; + wiinst->buffer.pos = 0; + wiinst->buffer.bytestocopy = 0; + + delay = (48000 * wiinst->buffer.fragment_size) / wiinst->format.bytespersec; + + emu10k1_timer_install(card, &wiinst->timer, delay / 2); + + wiinst->state = WAVE_STATE_OPEN; + + return 0; +} + +void emu10k1_wavein_close(struct emu10k1_wavedevice *wave_dev) +{ + struct emu10k1_card *card = wave_dev->card; + struct wiinst *wiinst = wave_dev->wiinst; + unsigned long flags; + + DPF(2, "emu10k1_wavein_close()\n"); + + emu10k1_wavein_stop(wave_dev); + + emu10k1_timer_uninstall(card, &wiinst->timer); + + free_buffer(card, &wiinst->buffer); + + spin_lock_irqsave(&card->lock, flags); + switch (wave_dev->wiinst->recsrc) { + case WAVERECORD_AC97: + card->wavein.ac97 = NULL; + break; + case WAVERECORD_MIC: + card->wavein.mic = NULL; + break; + case WAVERECORD_FX: + card->wavein.fx = NULL; + break; + default: + BUG(); + break; + } + spin_unlock_irqrestore(&card->lock, flags); + + wiinst->state = WAVE_STATE_CLOSED; +} + +void emu10k1_wavein_start(struct emu10k1_wavedevice *wave_dev) +{ + struct emu10k1_card *card = wave_dev->card; + struct wiinst *wiinst = wave_dev->wiinst; + + DPF(2, "emu10k1_wavein_start()\n"); + + emu10k1_start_record(card, &wiinst->buffer); + emu10k1_timer_enable(wave_dev->card, &wiinst->timer); + + wiinst->state |= WAVE_STATE_STARTED; +} + +void emu10k1_wavein_stop(struct emu10k1_wavedevice *wave_dev) +{ + struct emu10k1_card *card = wave_dev->card; + struct wiinst *wiinst = wave_dev->wiinst; + + DPF(2, "emu10k1_wavein_stop()\n"); + + if (!(wiinst->state & WAVE_STATE_STARTED)) + return; + + emu10k1_timer_disable(card, &wiinst->timer); + emu10k1_stop_record(card, &wiinst->buffer); + + wiinst->state &= ~WAVE_STATE_STARTED; +} + +int emu10k1_wavein_setformat(struct emu10k1_wavedevice *wave_dev, struct wave_format *format) +{ + struct emu10k1_card *card = wave_dev->card; + struct wiinst *wiinst = wave_dev->wiinst; + u16 delay; + + DPF(2, "emu10k1_wavein_setformat()\n"); + + if (wiinst->state & WAVE_STATE_STARTED) + return -1; + + query_format(wiinst->recsrc, format); + + if ((wiinst->format.samplingrate != format->samplingrate) + || (wiinst->format.bitsperchannel != format->bitsperchannel) + || (wiinst->format.channels != format->channels)) { + + wiinst->format = *format; + + if (wiinst->state == WAVE_STATE_CLOSED) + return 0; + + wiinst->buffer.size *= wiinst->buffer.cov; + + if (wiinst->format.bytesperchannel == 1) { + wiinst->buffer.cov = 2; + wiinst->buffer.size /= wiinst->buffer.cov; + } else + wiinst->buffer.cov = 1; + + emu10k1_timer_uninstall(card, &wiinst->timer); + + delay = (48000 * wiinst->buffer.fragment_size) / wiinst->format.bytespersec; + + emu10k1_timer_install(card, &wiinst->timer, delay / 2); + } + + return 0; +} + +void emu10k1_wavein_getxfersize(struct wiinst *wiinst, u32 * size) +{ + struct wavein_buffer *buffer = &wiinst->buffer; + + *size = buffer->bytestocopy; + + if (wiinst->mmapped) + return; + + if (*size > buffer->size) { + *size = buffer->size; + buffer->pos = buffer->hw_pos; + buffer->bytestocopy = buffer->size; + DPF(1, "buffer overrun\n"); + } +} + +static void copy_block(u8 __user *dst, u8 * src, u32 str, u32 len, u8 cov) +{ + if (cov == 1) + __copy_to_user(dst, src + str, len); + else { + u8 byte; + u32 i; + + src += 1 + 2 * str; + + for (i = 0; i < len; i++) { + byte = src[2 * i] ^ 0x80; + __copy_to_user(dst + i, &byte, 1); + } + } +} + +void emu10k1_wavein_xferdata(struct wiinst *wiinst, u8 __user *data, u32 * size) +{ + struct wavein_buffer *buffer = &wiinst->buffer; + u32 sizetocopy, sizetocopy_now, start; + unsigned long flags; + + sizetocopy = min_t(u32, buffer->size, *size); + *size = sizetocopy; + + if (!sizetocopy) + return; + + spin_lock_irqsave(&wiinst->lock, flags); + start = buffer->pos; + buffer->pos += sizetocopy; + buffer->pos %= buffer->size; + buffer->bytestocopy -= sizetocopy; + sizetocopy_now = buffer->size - start; + + spin_unlock_irqrestore(&wiinst->lock, flags); + + if (sizetocopy > sizetocopy_now) { + sizetocopy -= sizetocopy_now; + + copy_block(data, buffer->addr, start, sizetocopy_now, buffer->cov); + copy_block(data + sizetocopy_now, buffer->addr, 0, sizetocopy, buffer->cov); + } else { + copy_block(data, buffer->addr, start, sizetocopy, buffer->cov); + } +} + +void emu10k1_wavein_update(struct emu10k1_card *card, struct wiinst *wiinst) +{ + u32 hw_pos; + u32 diff; + + /* There is no actual start yet */ + if (!(wiinst->state & WAVE_STATE_STARTED)) { + hw_pos = wiinst->buffer.hw_pos; + } else { + /* hw_pos in byte units */ + hw_pos = sblive_readptr(card, wiinst->buffer.idxreg, 0) / wiinst->buffer.cov; + } + + diff = (wiinst->buffer.size + hw_pos - wiinst->buffer.hw_pos) % wiinst->buffer.size; + wiinst->total_recorded += diff; + wiinst->buffer.bytestocopy += diff; + + wiinst->buffer.hw_pos = hw_pos; +} diff --git a/sound/oss/emu10k1/cardwi.h b/sound/oss/emu10k1/cardwi.h new file mode 100644 index 000000000000..15cfb9b35596 --- /dev/null +++ b/sound/oss/emu10k1/cardwi.h @@ -0,0 +1,91 @@ +/* + ********************************************************************** + * cardwi.h -- header file for card wave input functions + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 _CARDWI_H +#define _CARDWI_H + +#include "icardwav.h" +#include "audio.h" +#include "timer.h" + +struct wavein_buffer { + u16 ossfragshift; + u32 fragment_size; + u32 numfrags; + u32 hw_pos; /* hardware cursor position */ + u32 pos; /* software cursor position */ + u32 bytestocopy; /* bytes of recorded data available */ + u32 size; + u32 pages; + u32 sizereg; + u32 sizeregval; + u32 addrreg; + u32 idxreg; + u32 adcctl; + void *addr; + u8 cov; + dma_addr_t dma_handle; +}; + +struct wiinst +{ + u8 state; + struct emu_timer timer; + struct wave_format format; + struct wavein_buffer buffer; + wait_queue_head_t wait_queue; + u8 mmapped; + u32 total_recorded; /* total bytes read() from device */ + u32 blocks; + spinlock_t lock; + u8 recsrc; + u16 fxwc; +}; + +#define WAVEIN_MAXBUFSIZE 65536 +#define WAVEIN_MINBUFSIZE 368 + +#define WAVEIN_DEFAULTFRAGLEN 100 +#define WAVEIN_DEFAULTBUFLEN 1000 + +#define WAVEIN_MINFRAGSHIFT 8 +#define WAVEIN_MINFRAGS 2 + +int emu10k1_wavein_open(struct emu10k1_wavedevice *); +void emu10k1_wavein_close(struct emu10k1_wavedevice *); +void emu10k1_wavein_start(struct emu10k1_wavedevice *); +void emu10k1_wavein_stop(struct emu10k1_wavedevice *); +void emu10k1_wavein_getxfersize(struct wiinst *, u32 *); +void emu10k1_wavein_xferdata(struct wiinst *, u8 __user *, u32 *); +int emu10k1_wavein_setformat(struct emu10k1_wavedevice *, struct wave_format *); +void emu10k1_wavein_update(struct emu10k1_card *, struct wiinst *); + + +#endif /* _CARDWI_H */ diff --git a/sound/oss/emu10k1/cardwo.c b/sound/oss/emu10k1/cardwo.c new file mode 100644 index 000000000000..54daca4f57b4 --- /dev/null +++ b/sound/oss/emu10k1/cardwo.c @@ -0,0 +1,643 @@ +/* + ********************************************************************** + * cardwo.c - PCM output HAL for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 +#include "hwaccess.h" +#include "8010.h" +#include "voicemgr.h" +#include "cardwo.h" +#include "audio.h" + +static u32 samplerate_to_linearpitch(u32 samplingrate) +{ + samplingrate = (samplingrate << 8) / 375; + return (samplingrate >> 1) + (samplingrate & 1); +} + +static void query_format(struct emu10k1_wavedevice *wave_dev, struct wave_format *wave_fmt) +{ + int i, j, do_passthrough = 0, is_ac3 = 0; + struct emu10k1_card *card = wave_dev->card; + struct woinst *woinst = wave_dev->woinst; + + if ((wave_fmt->channels > 2) && (wave_fmt->id != AFMT_S16_LE) && (wave_fmt->id != AFMT_U8)) + wave_fmt->channels = 2; + + if ((wave_fmt->channels < 1) || (wave_fmt->channels > WAVEOUT_MAXVOICES)) + wave_fmt->channels = 2; + + if (wave_fmt->channels == 2) + woinst->num_voices = 1; + else + woinst->num_voices = wave_fmt->channels; + + if (wave_fmt->samplingrate >= 0x2ee00) + wave_fmt->samplingrate = 0x2ee00; + + wave_fmt->passthrough = 0; + do_passthrough = is_ac3 = 0; + + if (card->pt.selected) + do_passthrough = 1; + + switch (wave_fmt->id) { + case AFMT_S16_LE: + wave_fmt->bitsperchannel = 16; + break; + case AFMT_U8: + wave_fmt->bitsperchannel = 8; + break; + case AFMT_AC3: + do_passthrough = 1; + is_ac3 = 1; + break; + default: + wave_fmt->id = AFMT_S16_LE; + wave_fmt->bitsperchannel = 16; + break; + } + if (do_passthrough) { + /* currently only one waveout instance may use pass-through */ + if (woinst->state != WAVE_STATE_CLOSED || + card->pt.state != PT_STATE_INACTIVE || + (wave_fmt->samplingrate != 48000 && !is_ac3)) { + DPF(2, "unable to set pass-through mode\n"); + } else if (USE_PT_METHOD1) { + i = emu10k1_find_control_gpr(&card->mgr, card->pt.patch_name, card->pt.intr_gpr_name); + j = emu10k1_find_control_gpr(&card->mgr, card->pt.patch_name, card->pt.enable_gpr_name); + if (i < 0 || j < 0) + DPF(2, "unable to set pass-through mode\n"); + else { + wave_fmt->samplingrate = 48000; + wave_fmt->channels = 2; + card->pt.pos_gpr = emu10k1_find_control_gpr(&card->mgr, card->pt.patch_name, + card->pt.pos_gpr_name); + wave_fmt->passthrough = 1; + card->pt.intr_gpr = i; + card->pt.enable_gpr = j; + card->pt.state = PT_STATE_INACTIVE; + + DPD(2, "is_ac3 is %d\n", is_ac3); + card->pt.ac3data = is_ac3; + wave_fmt->bitsperchannel = 16; + } + }else{ + DPF(2, "Using Passthrough Method 2\n"); + card->pt.enable_gpr = emu10k1_find_control_gpr(&card->mgr, card->pt.patch_name, + card->pt.enable_gpr_name); + wave_fmt->passthrough = 2; + wave_fmt->bitsperchannel = 16; + } + } + + wave_fmt->bytesperchannel = wave_fmt->bitsperchannel >> 3; + wave_fmt->bytespersample = wave_fmt->channels * wave_fmt->bytesperchannel; + wave_fmt->bytespersec = wave_fmt->bytespersample * wave_fmt->samplingrate; + + if (wave_fmt->channels == 2) + wave_fmt->bytespervoicesample = wave_fmt->channels * wave_fmt->bytesperchannel; + else + wave_fmt->bytespervoicesample = wave_fmt->bytesperchannel; +} + +static int get_voice(struct emu10k1_card *card, struct woinst *woinst, unsigned int voicenum) +{ + struct emu_voice *voice = &woinst->voice[voicenum]; + + /* Allocate voices here, if no voices available, return error. */ + + voice->usage = VOICE_USAGE_PLAYBACK; + + voice->flags = 0; + + if (woinst->format.channels == 2) + voice->flags |= VOICE_FLAGS_STEREO; + + if (woinst->format.bitsperchannel == 16) + voice->flags |= VOICE_FLAGS_16BIT; + + if (emu10k1_voice_alloc(card, voice) < 0) { + voice->usage = VOICE_USAGE_FREE; + return -1; + } + + /* Calculate pitch */ + voice->initial_pitch = (u16) (srToPitch(woinst->format.samplingrate) >> 8); + voice->pitch_target = samplerate_to_linearpitch(woinst->format.samplingrate); + + DPD(2, "Initial pitch --> %#x\n", voice->initial_pitch); + + voice->startloop = (voice->mem.emupageindex << 12) / + woinst->format.bytespervoicesample; + voice->endloop = voice->startloop + woinst->buffer.size / woinst->format.bytespervoicesample; + voice->start = voice->startloop; + + + voice->params[0].volume_target = 0xffff; + voice->params[0].initial_fc = 0xff; + voice->params[0].initial_attn = 0x00; + voice->params[0].byampl_env_sustain = 0x7f; + voice->params[0].byampl_env_decay = 0x7f; + + + if (voice->flags & VOICE_FLAGS_STEREO) { + if (woinst->format.passthrough == 2) { + voice->params[0].send_routing = voice->params[1].send_routing = card->waveout.send_routing[ROUTE_PT]; + voice->params[0].send_routing2 = voice->params[1].send_routing2 = card->waveout.send_routing2[ROUTE_PT]; + voice->params[0].send_dcba = 0xff; + voice->params[1].send_dcba = 0xff00; + voice->params[0].send_hgfe = voice->params[1].send_hgfe=0; + } else { + voice->params[0].send_dcba = card->waveout.send_dcba[SEND_LEFT]; + voice->params[0].send_hgfe = card->waveout.send_hgfe[SEND_LEFT]; + voice->params[1].send_dcba = card->waveout.send_dcba[SEND_RIGHT]; + voice->params[1].send_hgfe = card->waveout.send_hgfe[SEND_RIGHT]; + + if (woinst->device) { + // /dev/dps1 + voice->params[0].send_routing = voice->params[1].send_routing = card->waveout.send_routing[ROUTE_PCM1]; + voice->params[0].send_routing2 = voice->params[1].send_routing2 = card->waveout.send_routing2[ROUTE_PCM1]; + } else { + voice->params[0].send_routing = voice->params[1].send_routing = card->waveout.send_routing[ROUTE_PCM]; + voice->params[0].send_routing2 = voice->params[1].send_routing2 = card->waveout.send_routing2[ROUTE_PCM]; + } + } + + voice->params[1].volume_target = 0xffff; + voice->params[1].initial_fc = 0xff; + voice->params[1].initial_attn = 0x00; + voice->params[1].byampl_env_sustain = 0x7f; + voice->params[1].byampl_env_decay = 0x7f; + } else { + if (woinst->num_voices > 1) { + // Multichannel pcm + voice->params[0].send_dcba=0xff; + voice->params[0].send_hgfe=0; + if (card->is_audigy) { + voice->params[0].send_routing = 0x3f3f3f00 + card->mchannel_fx + voicenum; + voice->params[0].send_routing2 = 0x3f3f3f3f; + } else { + voice->params[0].send_routing = 0xfff0 + card->mchannel_fx + voicenum; + } + + } else { + voice->params[0].send_dcba = card->waveout.send_dcba[SEND_MONO]; + voice->params[0].send_hgfe = card->waveout.send_hgfe[SEND_MONO]; + + if (woinst->device) { + voice->params[0].send_routing = card->waveout.send_routing[ROUTE_PCM1]; + voice->params[0].send_routing2 = card->waveout.send_routing2[ROUTE_PCM1]; + } else { + voice->params[0].send_routing = card->waveout.send_routing[ROUTE_PCM]; + voice->params[0].send_routing2 = card->waveout.send_routing2[ROUTE_PCM]; + } + } + } + + DPD(2, "voice: startloop=%#x, endloop=%#x\n", voice->startloop, voice->endloop); + + emu10k1_voice_playback_setup(voice); + + return 0; +} + +int emu10k1_waveout_open(struct emu10k1_wavedevice *wave_dev) +{ + struct emu10k1_card *card = wave_dev->card; + struct woinst *woinst = wave_dev->woinst; + struct waveout_buffer *buffer = &woinst->buffer; + unsigned int voicenum; + u16 delay; + + DPF(2, "emu10k1_waveout_open()\n"); + + for (voicenum = 0; voicenum < woinst->num_voices; voicenum++) { + if (emu10k1_voice_alloc_buffer(card, &woinst->voice[voicenum].mem, woinst->buffer.pages) < 0) { + ERROR(); + emu10k1_waveout_close(wave_dev); + return -1; + } + + if (get_voice(card, woinst, voicenum) < 0) { + ERROR(); + emu10k1_waveout_close(wave_dev); + return -1; + } + } + + buffer->fill_silence = 0; + buffer->silence_bytes = 0; + buffer->silence_pos = 0; + buffer->hw_pos = 0; + buffer->free_bytes = woinst->buffer.size; + + delay = (48000 * woinst->buffer.fragment_size) / + (woinst->format.samplingrate * woinst->format.bytespervoicesample); + + emu10k1_timer_install(card, &woinst->timer, delay); + + woinst->state = WAVE_STATE_OPEN; + + return 0; +} + +void emu10k1_waveout_close(struct emu10k1_wavedevice *wave_dev) +{ + struct emu10k1_card *card = wave_dev->card; + struct woinst *woinst = wave_dev->woinst; + unsigned int voicenum; + + DPF(2, "emu10k1_waveout_close()\n"); + + emu10k1_waveout_stop(wave_dev); + + emu10k1_timer_uninstall(card, &woinst->timer); + + for (voicenum = 0; voicenum < woinst->num_voices; voicenum++) { + emu10k1_voice_free(&woinst->voice[voicenum]); + emu10k1_voice_free_buffer(card, &woinst->voice[voicenum].mem); + } + + woinst->state = WAVE_STATE_CLOSED; +} + +void emu10k1_waveout_start(struct emu10k1_wavedevice *wave_dev) +{ + struct emu10k1_card *card = wave_dev->card; + struct woinst *woinst = wave_dev->woinst; + struct pt_data *pt = &card->pt; + + DPF(2, "emu10k1_waveout_start()\n"); + + if (woinst->format.passthrough == 2) { + emu10k1_pt_setup(wave_dev); + sblive_writeptr(card, (card->is_audigy ? A_GPR_BASE : GPR_BASE) + pt->enable_gpr, 0, 1); + pt->state = PT_STATE_PLAYING; + } + + /* Actual start */ + emu10k1_voices_start(woinst->voice, woinst->num_voices, woinst->total_played); + + emu10k1_timer_enable(card, &woinst->timer); + + woinst->state |= WAVE_STATE_STARTED; +} + +int emu10k1_waveout_setformat(struct emu10k1_wavedevice *wave_dev, struct wave_format *format) +{ + struct emu10k1_card *card = wave_dev->card; + struct woinst *woinst = wave_dev->woinst; + unsigned int voicenum; + u16 delay; + + DPF(2, "emu10k1_waveout_setformat()\n"); + + if (woinst->state & WAVE_STATE_STARTED) + return -1; + + query_format(wave_dev, format); + + if (woinst->format.samplingrate != format->samplingrate || + woinst->format.channels != format->channels || + woinst->format.bitsperchannel != format->bitsperchannel) { + + woinst->format = *format; + + if (woinst->state == WAVE_STATE_CLOSED) + return 0; + + emu10k1_timer_uninstall(card, &woinst->timer); + + for (voicenum = 0; voicenum < woinst->num_voices; voicenum++) { + emu10k1_voice_free(&woinst->voice[voicenum]); + + if (get_voice(card, woinst, voicenum) < 0) { + ERROR(); + emu10k1_waveout_close(wave_dev); + return -1; + } + } + + delay = (48000 * woinst->buffer.fragment_size) / + (woinst->format.samplingrate * woinst->format.bytespervoicesample); + + emu10k1_timer_install(card, &woinst->timer, delay); + } + + return 0; +} + +void emu10k1_waveout_stop(struct emu10k1_wavedevice *wave_dev) +{ + struct emu10k1_card *card = wave_dev->card; + struct woinst *woinst = wave_dev->woinst; + + DPF(2, "emu10k1_waveout_stop()\n"); + + if (!(woinst->state & WAVE_STATE_STARTED)) + return; + + emu10k1_timer_disable(card, &woinst->timer); + + /* Stop actual voices */ + emu10k1_voices_stop(woinst->voice, woinst->num_voices); + + emu10k1_waveout_update(woinst); + + woinst->state &= ~WAVE_STATE_STARTED; +} + +/** + * emu10k1_waveout_getxfersize - + * + * gives the total free bytes on the voice buffer, including silence bytes + * (basically: total_free_bytes = free_bytes + silence_bytes). + * + */ +void emu10k1_waveout_getxfersize(struct woinst *woinst, u32 *total_free_bytes) +{ + struct waveout_buffer *buffer = &woinst->buffer; + int pending_bytes; + + if (woinst->mmapped) { + *total_free_bytes = buffer->free_bytes; + return; + } + + pending_bytes = buffer->size - buffer->free_bytes; + + buffer->fill_silence = (pending_bytes < (signed) buffer->fragment_size * 2) ? 1 : 0; + + if (pending_bytes > (signed) buffer->silence_bytes) { + *total_free_bytes = (buffer->free_bytes + buffer->silence_bytes); + } else { + *total_free_bytes = buffer->size; + buffer->silence_bytes = pending_bytes; + if (pending_bytes < 0) { + buffer->silence_pos = buffer->hw_pos; + buffer->silence_bytes = 0; + buffer->free_bytes = buffer->size; + DPF(1, "buffer underrun\n"); + } + } +} + +/** + * copy_block - + * + * copies a block of pcm data to a voice buffer. + * Notice that the voice buffer is actually a set of disjointed memory pages. + * + */ +static void copy_block(void **dst, u32 str, u8 __user *src, u32 len) +{ + unsigned int pg; + unsigned int pgoff; + unsigned int k; + + pg = str / PAGE_SIZE; + pgoff = str % PAGE_SIZE; + + if (len > PAGE_SIZE - pgoff) { + k = PAGE_SIZE - pgoff; + if (__copy_from_user((u8 *)dst[pg] + pgoff, src, k)) + return; + len -= k; + while (len > PAGE_SIZE) { + if (__copy_from_user(dst[++pg], src + k, PAGE_SIZE)) + return; + k += PAGE_SIZE; + len -= PAGE_SIZE; + } + if (__copy_from_user(dst[++pg], src + k, len)) + return; + + } else + __copy_from_user((u8 *)dst[pg] + pgoff, src, len); +} + +/** + * copy_ilv_block - + * + * copies a block of pcm data containing n interleaved channels to n mono voice buffers. + * Notice that the voice buffer is actually a set of disjointed memory pages. + * + */ +static void copy_ilv_block(struct woinst *woinst, u32 str, u8 __user *src, u32 len) +{ + unsigned int pg; + unsigned int pgoff; + unsigned int voice_num; + struct emu_voice *voice = woinst->voice; + + pg = str / PAGE_SIZE; + pgoff = str % PAGE_SIZE; + + while (len) { + for (voice_num = 0; voice_num < woinst->num_voices; voice_num++) { + if (__copy_from_user((u8 *)(voice[voice_num].mem.addr[pg]) + pgoff, src, woinst->format.bytespervoicesample)) + return; + src += woinst->format.bytespervoicesample; + } + + len -= woinst->format.bytespervoicesample; + + pgoff += woinst->format.bytespervoicesample; + if (pgoff >= PAGE_SIZE) { + pgoff = 0; + pg++; + } + } +} + +/** + * fill_block - + * + * fills a set voice buffers with a block of a given sample. + * + */ +static void fill_block(struct woinst *woinst, u32 str, u8 data, u32 len) +{ + unsigned int pg; + unsigned int pgoff; + unsigned int voice_num; + struct emu_voice *voice = woinst->voice; + unsigned int k; + + pg = str / PAGE_SIZE; + pgoff = str % PAGE_SIZE; + + if (len > PAGE_SIZE - pgoff) { + k = PAGE_SIZE - pgoff; + for (voice_num = 0; voice_num < woinst->num_voices; voice_num++) + memset((u8 *)voice[voice_num].mem.addr[pg] + pgoff, data, k); + len -= k; + while (len > PAGE_SIZE) { + pg++; + for (voice_num = 0; voice_num < woinst->num_voices; voice_num++) + memset(voice[voice_num].mem.addr[pg], data, PAGE_SIZE); + + len -= PAGE_SIZE; + } + pg++; + for (voice_num = 0; voice_num < woinst->num_voices; voice_num++) + memset(voice[voice_num].mem.addr[pg], data, len); + + } else { + for (voice_num = 0; voice_num < woinst->num_voices; voice_num++) + memset((u8 *)voice[voice_num].mem.addr[pg] + pgoff, data, len); + } +} + +/** + * emu10k1_waveout_xferdata - + * + * copies pcm data to the voice buffer. Silence samples + * previously added to the buffer are overwritten. + * + */ +void emu10k1_waveout_xferdata(struct woinst *woinst, u8 __user *data, u32 *size) +{ + struct waveout_buffer *buffer = &woinst->buffer; + struct voice_mem *mem = &woinst->voice[0].mem; + u32 sizetocopy, sizetocopy_now, start; + unsigned long flags; + + sizetocopy = min_t(u32, buffer->size, *size); + *size = sizetocopy; + + if (!sizetocopy) + return; + + spin_lock_irqsave(&woinst->lock, flags); + start = (buffer->size + buffer->silence_pos - buffer->silence_bytes) % buffer->size; + + if (sizetocopy > buffer->silence_bytes) { + buffer->silence_pos += sizetocopy - buffer->silence_bytes; + buffer->free_bytes -= sizetocopy - buffer->silence_bytes; + buffer->silence_bytes = 0; + } else + buffer->silence_bytes -= sizetocopy; + + spin_unlock_irqrestore(&woinst->lock, flags); + + sizetocopy_now = buffer->size - start; + if (sizetocopy > sizetocopy_now) { + sizetocopy -= sizetocopy_now; + if (woinst->num_voices > 1) { + copy_ilv_block(woinst, start, data, sizetocopy_now); + copy_ilv_block(woinst, 0, data + sizetocopy_now * woinst->num_voices, sizetocopy); + } else { + copy_block(mem->addr, start, data, sizetocopy_now); + copy_block(mem->addr, 0, data + sizetocopy_now, sizetocopy); + } + } else { + if (woinst->num_voices > 1) + copy_ilv_block(woinst, start, data, sizetocopy); + else + copy_block(mem->addr, start, data, sizetocopy); + } +} + +/** + * emu10k1_waveout_fillsilence - + * + * adds samples of silence to the voice buffer so that we + * don't loop over stale pcm data. + * + */ +void emu10k1_waveout_fillsilence(struct woinst *woinst) +{ + struct waveout_buffer *buffer = &woinst->buffer; + u32 sizetocopy, sizetocopy_now, start; + u8 filldata; + unsigned long flags; + + sizetocopy = buffer->fragment_size; + + if (woinst->format.bitsperchannel == 16) + filldata = 0x00; + else + filldata = 0x80; + + spin_lock_irqsave(&woinst->lock, flags); + buffer->silence_bytes += sizetocopy; + buffer->free_bytes -= sizetocopy; + buffer->silence_pos %= buffer->size; + start = buffer->silence_pos; + buffer->silence_pos += sizetocopy; + spin_unlock_irqrestore(&woinst->lock, flags); + + sizetocopy_now = buffer->size - start; + + if (sizetocopy > sizetocopy_now) { + sizetocopy -= sizetocopy_now; + fill_block(woinst, start, filldata, sizetocopy_now); + fill_block(woinst, 0, filldata, sizetocopy); + } else { + fill_block(woinst, start, filldata, sizetocopy); + } +} + +/** + * emu10k1_waveout_update - + * + * updates the position of the voice buffer hardware pointer (hw_pos) + * and the number of free bytes on the buffer (free_bytes). + * The free bytes _don't_ include silence bytes that may have been + * added to the buffer. + * + */ +void emu10k1_waveout_update(struct woinst *woinst) +{ + u32 hw_pos; + u32 diff; + + /* There is no actual start yet */ + if (!(woinst->state & WAVE_STATE_STARTED)) { + hw_pos = woinst->buffer.hw_pos; + } else { + /* hw_pos in sample units */ + hw_pos = sblive_readptr(woinst->voice[0].card, CCCA_CURRADDR, woinst->voice[0].num); + + if(hw_pos < woinst->voice[0].start) + hw_pos += woinst->buffer.size / woinst->format.bytespervoicesample - woinst->voice[0].start; + else + hw_pos -= woinst->voice[0].start; + + hw_pos *= woinst->format.bytespervoicesample; + } + + diff = (woinst->buffer.size + hw_pos - woinst->buffer.hw_pos) % woinst->buffer.size; + woinst->total_played += diff; + woinst->buffer.free_bytes += diff; + woinst->buffer.hw_pos = hw_pos; +} diff --git a/sound/oss/emu10k1/cardwo.h b/sound/oss/emu10k1/cardwo.h new file mode 100644 index 000000000000..1dece8853e5c --- /dev/null +++ b/sound/oss/emu10k1/cardwo.h @@ -0,0 +1,90 @@ +/* + ********************************************************************** + * cardwo.h -- header file for card wave out functions + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 _CARDWO_H +#define _CARDWO_H + +#include "icardwav.h" +#include "audio.h" +#include "voicemgr.h" +#include "timer.h" + +/* setting this to other than a power of two may break some applications */ +#define WAVEOUT_MAXBUFSIZE MAXBUFSIZE + +#define WAVEOUT_DEFAULTFRAGLEN 20 /* Time to play a fragment in ms (latency) */ +#define WAVEOUT_DEFAULTBUFLEN 500 /* Time to play the entire buffer in ms */ + +#define WAVEOUT_MINFRAGSHIFT 6 /* Minimum fragment size in bytes is 2^6 */ +#define WAVEOUT_MINFRAGS 3 /* _don't_ go bellow 3, it would break silence filling */ +#define WAVEOUT_MAXVOICES 6 + +struct waveout_buffer { + u16 ossfragshift; + u32 numfrags; + u32 fragment_size; /* in bytes units */ + u32 size; /* in bytes units */ + u32 pages; /* buffer size in page units*/ + u32 silence_pos; /* software cursor position (including silence bytes) */ + u32 hw_pos; /* hardware cursor position */ + u32 free_bytes; /* free bytes available on the buffer (not including silence bytes) */ + u8 fill_silence; + u32 silence_bytes; /* silence bytes on the buffer */ +}; + +struct woinst +{ + u8 state; + u8 num_voices; + struct emu_voice voice[WAVEOUT_MAXVOICES]; + struct emu_timer timer; + struct wave_format format; + struct waveout_buffer buffer; + wait_queue_head_t wait_queue; + u8 mmapped; + u32 total_copied; /* total number of bytes written() to the buffer (excluding silence) */ + u32 total_played; /* total number of bytes played including silence */ + u32 blocks; + u8 device; + spinlock_t lock; +}; + +int emu10k1_waveout_open(struct emu10k1_wavedevice *); +void emu10k1_waveout_close(struct emu10k1_wavedevice *); +void emu10k1_waveout_start(struct emu10k1_wavedevice *); +void emu10k1_waveout_stop(struct emu10k1_wavedevice *); +void emu10k1_waveout_getxfersize(struct woinst*, u32 *); +void emu10k1_waveout_xferdata(struct woinst*, u8 __user *, u32 *); +void emu10k1_waveout_fillsilence(struct woinst*); +int emu10k1_waveout_setformat(struct emu10k1_wavedevice*, struct wave_format*); +void emu10k1_waveout_update(struct woinst*); + +#endif /* _CARDWO_H */ diff --git a/sound/oss/emu10k1/ecard.c b/sound/oss/emu10k1/ecard.c new file mode 100644 index 000000000000..4ae635fe1402 --- /dev/null +++ b/sound/oss/emu10k1/ecard.c @@ -0,0 +1,157 @@ +/* + ********************************************************************** + * ecard.c - E-card initialization code + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 "ecard.h" +#include "hwaccess.h" + +/* Private routines */ +static void ecard_setadcgain(struct emu10k1_card *, struct ecard_state *, u16); +static void ecard_write(struct emu10k1_card *, u32); + +/************************************************************************** + * @func Set the gain of the ECARD's CS3310 Trim/gain controller. The + * trim value consists of a 16bit value which is composed of two + * 8 bit gain/trim values, one for the left channel and one for the + * right channel. The following table maps from the Gain/Attenuation + * value in decibels into the corresponding bit pattern for a single + * channel. + */ + +static void ecard_setadcgain(struct emu10k1_card *card, struct ecard_state *ecard, u16 gain) +{ + u32 currbit; + ecard->adc_gain = gain; + + /* Enable writing to the TRIM registers */ + ecard_write(card, ecard->control_bits & ~EC_TRIM_CSN); + + /* Do it again to insure that we meet hold time requirements */ + ecard_write(card, ecard->control_bits & ~EC_TRIM_CSN); + + for (currbit = (1L << 15); currbit; currbit >>= 1) { + + u32 value = ecard->control_bits & ~(EC_TRIM_CSN|EC_TRIM_SDATA); + + if (gain & currbit) + value |= EC_TRIM_SDATA; + + /* Clock the bit */ + ecard_write(card, value); + ecard_write(card, value | EC_TRIM_SCLK); + ecard_write(card, value); + } + + ecard_write(card, ecard->control_bits); +} + +/************************************************************************** + * @func Clock bits into the Ecard's control latch. The Ecard uses a + * control latch will is loaded bit-serially by toggling the Modem control + * lines from function 2 on the E8010. This function hides these details + * and presents the illusion that we are actually writing to a distinct + * register. + */ +static void ecard_write(struct emu10k1_card *card, u32 value) +{ + u16 count; + u32 data, hcvalue; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + + hcvalue = inl(card->iobase + HCFG) & ~(HOOKN_BIT|HANDN_BIT|PULSEN_BIT); + + outl(card->iobase + HCFG, hcvalue); + + for (count = 0 ; count < EC_NUM_CONTROL_BITS; count++) { + + /* Set up the value */ + data = ((value & 0x1) ? PULSEN_BIT : 0); + value >>= 1; + + outl(card->iobase + HCFG, hcvalue | data); + + /* Clock the shift register */ + outl(card->iobase + HCFG, hcvalue | data | HANDN_BIT); + outl(card->iobase + HCFG, hcvalue | data); + } + + /* Latch the bits */ + outl(card->iobase + HCFG, hcvalue | HOOKN_BIT); + outl(card->iobase + HCFG, hcvalue); + + spin_unlock_irqrestore(&card->lock, flags); +} + +void __devinit emu10k1_ecard_init(struct emu10k1_card *card) +{ + u32 hcvalue; + struct ecard_state ecard; + + /* Set up the initial settings */ + ecard.mux0_setting = EC_DEFAULT_SPDIF0_SEL; + ecard.mux1_setting = EC_DEFAULT_SPDIF1_SEL; + ecard.mux2_setting = 0; + ecard.adc_gain = EC_DEFAULT_ADC_GAIN; + ecard.control_bits = EC_RAW_RUN_MODE | + EC_SPDIF0_SELECT(ecard.mux0_setting) | + EC_SPDIF1_SELECT(ecard.mux1_setting); + + + /* Step 0: Set the codec type in the hardware control register + * and enable audio output */ + hcvalue = emu10k1_readfn0(card, HCFG); + emu10k1_writefn0(card, HCFG, hcvalue | HCFG_AUDIOENABLE | HCFG_CODECFORMAT_I2S); + + /* Step 1: Turn off the led and deassert TRIM_CS */ + ecard_write(card, EC_ADCCAL | EC_LEDN | EC_TRIM_CSN); + + /* Step 2: Calibrate the ADC and DAC */ + ecard_write(card, EC_DACCAL | EC_LEDN | EC_TRIM_CSN); + + /* Step 3: Wait for awhile; FIXME: Is this correct? */ + + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ); + + /* Step 4: Switch off the DAC and ADC calibration. Note + * That ADC_CAL is actually an inverted signal, so we assert + * it here to stop calibration. */ + ecard_write(card, EC_ADCCAL | EC_LEDN | EC_TRIM_CSN); + + /* Step 4: Switch into run mode */ + ecard_write(card, ecard.control_bits); + + /* Step 5: Set the analog input gain */ + ecard_setadcgain(card, &ecard, ecard.adc_gain); +} + + diff --git a/sound/oss/emu10k1/ecard.h b/sound/oss/emu10k1/ecard.h new file mode 100644 index 000000000000..67aead16e8ec --- /dev/null +++ b/sound/oss/emu10k1/ecard.h @@ -0,0 +1,113 @@ +/* + ********************************************************************** + * ecard.h + * Copyright 1999, 2000 Creative Labs, 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 _ECARD_H +#define _ECARD_H + +#include "8010.h" +#include "hwaccess.h" +#include + +/* In A1 Silicon, these bits are in the HC register */ +#define HOOKN_BIT (1L << 12) +#define HANDN_BIT (1L << 11) +#define PULSEN_BIT (1L << 10) + +#define EC_GDI1 (1 << 13) +#define EC_GDI0 (1 << 14) + +#define EC_NUM_CONTROL_BITS 20 + +#define EC_AC3_DATA_SELN 0x0001L +#define EC_EE_DATA_SEL 0x0002L +#define EC_EE_CNTRL_SELN 0x0004L +#define EC_EECLK 0x0008L +#define EC_EECS 0x0010L +#define EC_EESDO 0x0020L +#define EC_TRIM_CSN 0x0040L +#define EC_TRIM_SCLK 0x0080L +#define EC_TRIM_SDATA 0x0100L +#define EC_TRIM_MUTEN 0x0200L +#define EC_ADCCAL 0x0400L +#define EC_ADCRSTN 0x0800L +#define EC_DACCAL 0x1000L +#define EC_DACMUTEN 0x2000L +#define EC_LEDN 0x4000L + +#define EC_SPDIF0_SEL_SHIFT 15 +#define EC_SPDIF1_SEL_SHIFT 17 +#define EC_SPDIF0_SEL_MASK (0x3L << EC_SPDIF0_SEL_SHIFT) +#define EC_SPDIF1_SEL_MASK (0x7L << EC_SPDIF1_SEL_SHIFT) +#define EC_SPDIF0_SELECT(_x) (((_x) << EC_SPDIF0_SEL_SHIFT) & EC_SPDIF0_SEL_MASK) +#define EC_SPDIF1_SELECT(_x) (((_x) << EC_SPDIF1_SEL_SHIFT) & EC_SPDIF1_SEL_MASK) +#define EC_CURRENT_PROM_VERSION 0x01 /* Self-explanatory. This should + * be incremented any time the EEPROM's + * format is changed. */ + +#define EC_EEPROM_SIZE 0x40 /* ECARD EEPROM has 64 16-bit words */ + +/* Addresses for special values stored in to EEPROM */ +#define EC_PROM_VERSION_ADDR 0x20 /* Address of the current prom version */ +#define EC_BOARDREV0_ADDR 0x21 /* LSW of board rev */ +#define EC_BOARDREV1_ADDR 0x22 /* MSW of board rev */ + +#define EC_LAST_PROMFILE_ADDR 0x2f + +#define EC_SERIALNUM_ADD 0x30 /* First word of serial number. The number + * can be up to 30 characters in length + * and is stored as a NULL-terminated + * ASCII string. Any unused bytes must be + * filled with zeros */ +#define EC_CHECKSUM_ADDR 0x3f /* Location at which checksum is stored */ + + + +/* Most of this stuff is pretty self-evident. According to the hardware + * dudes, we need to leave the ADCCAL bit low in order to avoid a DC + * offset problem. Weird. + */ +#define EC_RAW_RUN_MODE (EC_DACMUTEN | EC_ADCRSTN | EC_TRIM_MUTEN | EC_TRIM_CSN) + + +#define EC_DEFAULT_ADC_GAIN 0xC4C4 +#define EC_DEFAULT_SPDIF0_SEL 0x0 +#define EC_DEFAULT_SPDIF1_SEL 0x4 + +#define HC_EA 0x01L + +/* ECARD state structure. This structure maintains the state + * for various portions of the ECARD's onboard hardware. + */ +struct ecard_state { + u32 control_bits; + u16 adc_gain; + u16 mux0_setting; + u16 mux1_setting; + u16 mux2_setting; +}; + +void emu10k1_ecard_init(struct emu10k1_card *) __devinit; + +#endif /* _ECARD_H */ diff --git a/sound/oss/emu10k1/efxmgr.c b/sound/oss/emu10k1/efxmgr.c new file mode 100644 index 000000000000..7d5865de4c2e --- /dev/null +++ b/sound/oss/emu10k1/efxmgr.c @@ -0,0 +1,220 @@ +/* + ********************************************************************** + * efxmgr.c + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 +#include "hwaccess.h" +#include "efxmgr.h" + +int emu10k1_find_control_gpr(struct patch_manager *mgr, const char *patch_name, const char *gpr_name) +{ + struct dsp_patch *patch; + struct dsp_rpatch *rpatch; + char s[PATCH_NAME_SIZE + 4]; + unsigned long *gpr_used; + int i; + + DPD(2, "emu10k1_find_control_gpr(): %s %s\n", patch_name, gpr_name); + + rpatch = &mgr->rpatch; + if (!strcmp(rpatch->name, patch_name)) { + gpr_used = rpatch->gpr_used; + goto match; + } + + for (i = 0; i < mgr->current_pages * PATCHES_PER_PAGE; i++) { + patch = PATCH(mgr, i); + sprintf(s,"%s", patch->name); + + if (!strcmp(s, patch_name)) { + gpr_used = patch->gpr_used; + goto match; + } + } + + return -1; + + match: + for (i = 0; i < NUM_GPRS; i++) + if (mgr->gpr[i].type == GPR_TYPE_CONTROL && + test_bit(i, gpr_used) && + !strcmp(mgr->gpr[i].name, gpr_name)) + return i; + + return -1; +} + +void emu10k1_set_control_gpr(struct emu10k1_card *card, int addr, s32 val, int flag) +{ + struct patch_manager *mgr = &card->mgr; + + DPD(2, "emu10k1_set_control_gpr(): %d %x\n", addr, val); + + if (addr < 0 || addr >= NUM_GPRS) + return; + + //fixme: once patch manager is up, remember to fix this for the audigy + if (card->is_audigy) { + sblive_writeptr(card, A_GPR_BASE + addr, 0, val); + } else { + if (flag) + val += sblive_readptr(card, GPR_BASE + addr, 0); + if (val > mgr->gpr[addr].max) + val = mgr->gpr[addr].max; + else if (val < mgr->gpr[addr].min) + val = mgr->gpr[addr].min; + sblive_writeptr(card, GPR_BASE + addr, 0, val); + } + + +} + +//TODO: make this configurable: +#define VOLCTRL_CHANNEL SOUND_MIXER_VOLUME +#define VOLCTRL_STEP_SIZE 5 + +//An internal function for setting OSS mixer controls. +static void emu10k1_set_oss_vol(struct emu10k1_card *card, int oss_mixer, + unsigned int left, unsigned int right) +{ + extern char volume_params[SOUND_MIXER_NRDEVICES]; + + card->ac97->mixer_state[oss_mixer] = (right << 8) | left; + + if (!card->is_aps) + card->ac97->write_mixer(card->ac97, oss_mixer, left, right); + + emu10k1_set_volume_gpr(card, card->mgr.ctrl_gpr[oss_mixer][0], left, + volume_params[oss_mixer]); + + emu10k1_set_volume_gpr(card, card->mgr.ctrl_gpr[oss_mixer][1], right, + volume_params[oss_mixer]); +} + +//FIXME: mute should unmute when pressed a second time +void emu10k1_mute_irqhandler(struct emu10k1_card *card) +{ + int oss_channel = VOLCTRL_CHANNEL; + int left, right; + static int val; + + if (val) { + left = val & 0xff; + right = (val >> 8) & 0xff; + val = 0; + } else { + val = card->ac97->mixer_state[oss_channel]; + left = 0; + right = 0; + } + + emu10k1_set_oss_vol(card, oss_channel, left, right); +} + +void emu10k1_volincr_irqhandler(struct emu10k1_card *card) +{ + int oss_channel = VOLCTRL_CHANNEL; + int left, right; + + left = card->ac97->mixer_state[oss_channel] & 0xff; + right = (card->ac97->mixer_state[oss_channel] >> 8) & 0xff; + + if ((left += VOLCTRL_STEP_SIZE) > 100) + left = 100; + + if ((right += VOLCTRL_STEP_SIZE) > 100) + right = 100; + + emu10k1_set_oss_vol(card, oss_channel, left, right); +} + +void emu10k1_voldecr_irqhandler(struct emu10k1_card *card) +{ + int oss_channel = VOLCTRL_CHANNEL; + int left, right; + + left = card->ac97->mixer_state[oss_channel] & 0xff; + right = (card->ac97->mixer_state[oss_channel] >> 8) & 0xff; + + if ((left -= VOLCTRL_STEP_SIZE) < 0) + left = 0; + + if ((right -= VOLCTRL_STEP_SIZE) < 0) + right = 0; + + emu10k1_set_oss_vol(card, oss_channel, left, right); +} + +void emu10k1_set_volume_gpr(struct emu10k1_card *card, int addr, s32 vol, int scale) +{ + struct patch_manager *mgr = &card->mgr; + unsigned long flags; + + static const s32 log2lin[4] ={ // attenuation (dB) + 0x7fffffff, // 0.0 + 0x7fffffff * 0.840896415253715 , // 1.5 + 0x7fffffff * 0.707106781186548, // 3.0 + 0x7fffffff * 0.594603557501361 , // 4.5 + }; + + if (addr < 0) + return; + + vol = (100 - vol ) * scale / 100; + + // Thanks to the comp.dsp newsgroup for this neat trick: + vol = (vol >= scale) ? 0 : (log2lin[vol & 3] >> (vol >> 2)); + + spin_lock_irqsave(&mgr->lock, flags); + emu10k1_set_control_gpr(card, addr, vol, 0); + spin_unlock_irqrestore(&mgr->lock, flags); +} + +void emu10k1_dsp_irqhandler(struct emu10k1_card *card) +{ + unsigned long flags; + + if (card->pt.state != PT_STATE_INACTIVE) { + u32 bc; + bc = sblive_readptr(card, GPR_BASE + card->pt.intr_gpr, 0); + if (bc != 0) { + DPD(3, "pt interrupt, bc = %d\n", bc); + spin_lock_irqsave(&card->pt.lock, flags); + card->pt.blocks_played = bc; + if (card->pt.blocks_played >= card->pt.blocks_copied) { + DPF(1, "buffer underrun in passthrough playback\n"); + emu10k1_pt_stop(card); + } + wake_up_interruptible(&card->pt.wait); + spin_unlock_irqrestore(&card->pt.lock, flags); + } + } +} + diff --git a/sound/oss/emu10k1/efxmgr.h b/sound/oss/emu10k1/efxmgr.h new file mode 100644 index 000000000000..ef48e5c70d1f --- /dev/null +++ b/sound/oss/emu10k1/efxmgr.h @@ -0,0 +1,270 @@ +/* + ********************************************************************** + * sblive_fx.h + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 _EFXMGR_H +#define _EFXMGR_H + +struct emu_efx_info_t{ + int opcode_shift; + int high_operand_shift; + int instruction_start; + int gpr_base; + int output_base; +}; + + +#define WRITE_EFX(a, b, c) sblive_writeptr((a), emu_efx_info[card->is_audigy].instruction_start + (b), 0, (c)) + +#define OP(op, z, w, x, y) \ + do { WRITE_EFX(card, (pc) * 2, ((x) << emu_efx_info[card->is_audigy].high_operand_shift) | (y)); \ + WRITE_EFX(card, (pc) * 2 + 1, ((op) << emu_efx_info[card->is_audigy].opcode_shift ) | ((z) << emu_efx_info[card->is_audigy].high_operand_shift) | (w)); \ + ++pc; } while (0) + +#define NUM_INPUTS 0x20 +#define NUM_OUTPUTS 0x20 +#define NUM_GPRS 0x100 + +#define A_NUM_INPUTS 0x60 +#define A_NUM_OUTPUTS 0x60 //fixme: this may or may not be true +#define A_NUM_GPRS 0x200 + +#define GPR_NAME_SIZE 32 +#define PATCH_NAME_SIZE 32 + +struct dsp_rpatch { + char name[PATCH_NAME_SIZE]; + u16 code_start; + u16 code_size; + + unsigned long gpr_used[NUM_GPRS / (sizeof(unsigned long) * 8) + 1]; + unsigned long gpr_input[NUM_GPRS / (sizeof(unsigned long) * 8) + 1]; + unsigned long route[NUM_OUTPUTS]; + unsigned long route_v[NUM_OUTPUTS]; +}; + +struct dsp_patch { + char name[PATCH_NAME_SIZE]; + u8 id; + unsigned long input; /* bitmap of the lines used as inputs */ + unsigned long output; /* bitmap of the lines used as outputs */ + u16 code_start; + u16 code_size; + + unsigned long gpr_used[NUM_GPRS / (sizeof(unsigned long) * 8) + 1]; /* bitmap of used gprs */ + unsigned long gpr_input[NUM_GPRS / (sizeof(unsigned long) * 8) + 1]; + u8 traml_istart; /* starting address of the internal tram lines used */ + u8 traml_isize; /* number of internal tram lines used */ + + u8 traml_estart; + u8 traml_esize; + + u16 tramb_istart; /* starting address of the internal tram memory used */ + u16 tramb_isize; /* amount of internal memory used */ + u32 tramb_estart; + u32 tramb_esize; +}; + +struct dsp_gpr { + u8 type; /* gpr type, STATIC, DYNAMIC, INPUT, OUTPUT, CONTROL */ + char name[GPR_NAME_SIZE]; /* gpr value, only valid for control gprs */ + s32 min, max; /* value range for this gpr, only valid for control gprs */ + u8 line; /* which input/output line is the gpr attached, only valid for input/output gprs */ + u8 usage; +}; + +enum { + GPR_TYPE_NULL = 0, + GPR_TYPE_IO, + GPR_TYPE_STATIC, + GPR_TYPE_DYNAMIC, + GPR_TYPE_CONTROL, + GPR_TYPE_CONSTANT +}; + +#define GPR_BASE 0x100 +#define OUTPUT_BASE 0x20 + +#define A_GPR_BASE 0x400 +#define A_OUTPUT_BASE 0x60 + +#define MAX_PATCHES_PAGES 32 + +struct patch_manager { + void *patch[MAX_PATCHES_PAGES]; + int current_pages; + struct dsp_rpatch rpatch; + struct dsp_gpr gpr[NUM_GPRS]; /* gpr usage table */ + spinlock_t lock; + s16 ctrl_gpr[SOUND_MIXER_NRDEVICES][2]; +}; + +#define PATCHES_PER_PAGE (PAGE_SIZE / sizeof(struct dsp_patch)) + +#define PATCH(mgr, i) ((struct dsp_patch *) (mgr)->patch[(i) / PATCHES_PER_PAGE] + (i) % PATCHES_PER_PAGE) + +/* PCM volume control */ +#define TMP_PCM_L 0x100 //temp PCM L (after the vol control) +#define TMP_PCM_R 0x101 +#define VOL_PCM_L 0x102 //vol PCM +#define VOL_PCM_R 0x103 + +/* Routing patch */ +#define TMP_AC_L 0x104 //tmp ac97 out +#define TMP_AC_R 0x105 +#define TMP_REAR_L 0x106 //output - Temp Rear +#define TMP_REAR_R 0x107 +#define TMP_DIGI_L 0x108 //output - Temp digital +#define TMP_DIGI_R 0x109 +#define DSP_VOL_L 0x10a // main dsp volume +#define DSP_VOL_R 0x10b + +/* hw inputs */ +#define PCM_IN_L 0x00 +#define PCM_IN_R 0x01 + +#define PCM1_IN_L 0x04 +#define PCM1_IN_R 0x05 +//mutilchannel playback stream appear here: + +#define MULTI_FRONT_L 0x08 +#define MULTI_FRONT_R 0x09 +#define MULTI_REAR_L 0x0a +#define MULTI_REAR_R 0x0b +#define MULTI_CENTER 0x0c +#define MULTI_LFE 0x0d + +#define AC97_IN_L 0x10 +#define AC97_IN_R 0x11 +#define SPDIF_CD_L 0x12 +#define SPDIF_CD_R 0x13 + +/* hw outputs */ +#define AC97_FRONT_L 0x20 +#define AC97_FRONT_R 0x21 +#define DIGITAL_OUT_L 0x22 +#define DIGITAL_OUT_R 0x23 +#define DIGITAL_CENTER 0x24 +#define DIGITAL_LFE 0x25 + +#define ANALOG_REAR_L 0x28 +#define ANALOG_REAR_R 0x29 +#define ADC_REC_L 0x2a +#define ADC_REC_R 0x2b + +#define ANALOG_CENTER 0x31 +#define ANALOG_LFE 0x32 + + +#define INPUT_PATCH_START(patch, nm, ln, i) \ +do { \ + patch = PATCH(mgr, patch_n); \ + strcpy(patch->name, nm); \ + patch->code_start = pc * 2; \ + patch->input = (1<<(0x1f&ln)); \ + patch->output= (1<<(0x1f&ln)); \ + patch->id = i; \ +} while(0) + +#define INPUT_PATCH_END(patch) \ +do { \ + patch->code_size = pc * 2 - patch->code_start; \ + patch_n++; \ +} while(0) + + +#define ROUTING_PATCH_START(patch, nm) \ +do { \ + patch = &mgr->rpatch; \ + strcpy(patch->name, nm); \ + patch->code_start = pc * 2; \ +} while(0) + +#define ROUTING_PATCH_END(patch) \ +do { \ + patch->code_size = pc * 2 - patch->code_start; \ +} while(0) + +#define CONNECT(input, output) set_bit(input, &rpatch->route[(output) - OUTPUT_BASE]); + +#define CONNECT_V(input, output) set_bit(input, &rpatch->route_v[(output) - OUTPUT_BASE]); + +#define OUTPUT_PATCH_START(patch, nm, ln, i) \ +do { \ + patch = PATCH(mgr, patch_n); \ + strcpy(patch->name, nm); \ + patch->code_start = pc * 2; \ + patch->input = (1<<(0x1f&ln)); \ + patch->output= (1<<(0x1f&ln)); \ + patch->id = i; \ +} while(0) + +#define OUTPUT_PATCH_END(patch) \ +do { \ + patch->code_size = pc * 2 - patch->code_start; \ + patch_n++; \ +} while(0) + +#define GET_OUTPUT_GPR(patch, g, ln) \ +do { \ + mgr->gpr[(g) - GPR_BASE].type = GPR_TYPE_IO; \ + mgr->gpr[(g) - GPR_BASE].usage++; \ + mgr->gpr[(g) - GPR_BASE].line = ln; \ + set_bit((g) - GPR_BASE, patch->gpr_used); \ +} while(0) + +#define GET_INPUT_GPR(patch, g, ln) \ +do { \ + mgr->gpr[(g) - GPR_BASE].type = GPR_TYPE_IO; \ + mgr->gpr[(g) - GPR_BASE].usage++; \ + mgr->gpr[(g) - GPR_BASE].line = ln; \ + set_bit((g) - GPR_BASE, patch->gpr_used); \ + set_bit((g) - GPR_BASE, patch->gpr_input); \ +} while(0) + +#define GET_DYNAMIC_GPR(patch, g) \ +do { \ + mgr->gpr[(g) - GPR_BASE].type = GPR_TYPE_DYNAMIC; \ + mgr->gpr[(g) - GPR_BASE].usage++; \ + set_bit((g) - GPR_BASE, patch->gpr_used); \ +} while(0) + +#define GET_CONTROL_GPR(patch, g, nm, a, b) \ +do { \ + strcpy(mgr->gpr[(g) - GPR_BASE].name, nm); \ + mgr->gpr[(g) - GPR_BASE].type = GPR_TYPE_CONTROL; \ + mgr->gpr[(g) - GPR_BASE].usage++; \ + mgr->gpr[(g) - GPR_BASE].min = a; \ + mgr->gpr[(g) - GPR_BASE].max = b; \ + sblive_writeptr(card, g, 0, b); \ + set_bit((g) - GPR_BASE, patch->gpr_used); \ +} while(0) + +#endif /* _EFXMGR_H */ diff --git a/sound/oss/emu10k1/emuadxmg.c b/sound/oss/emu10k1/emuadxmg.c new file mode 100644 index 000000000000..d7d2d4caf7ba --- /dev/null +++ b/sound/oss/emu10k1/emuadxmg.c @@ -0,0 +1,104 @@ + +/* + ********************************************************************** + * emuadxmg.c - Address space manager for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 "hwaccess.h" + +/* Allocates emu address space */ + +int emu10k1_addxmgr_alloc(u32 size, struct emu10k1_card *card) +{ + u16 *pagetable = card->emupagetable; + u16 index = 0; + u16 numpages; + unsigned long flags; + + /* Convert bytes to pages */ + numpages = (size / EMUPAGESIZE) + ((size % EMUPAGESIZE) ? 1 : 0); + + spin_lock_irqsave(&card->lock, flags); + + while (index < (MAXPAGES - 1)) { + if (pagetable[index] & 0x8000) { + /* This block of pages is in use, jump to the start of the next block. */ + index += (pagetable[index] & 0x7fff); + } else { + /* Found free block */ + if (pagetable[index] >= numpages) { + + /* Block is large enough */ + + /* If free block is larger than the block requested + * then adjust the size of the block remaining */ + if (pagetable[index] > numpages) + pagetable[index + numpages] = pagetable[index] - numpages; + + pagetable[index] = (numpages | 0x8000); /* Mark block as used */ + + spin_unlock_irqrestore(&card->lock, flags); + + return index; + } else { + /* Block too small, jump to the start of the next block */ + index += pagetable[index]; + } + } + } + + spin_unlock_irqrestore(&card->lock, flags); + + return -1; +} + +/* Frees a previously allocated emu address space. */ + +void emu10k1_addxmgr_free(struct emu10k1_card *card, int index) +{ + u16 *pagetable = card->emupagetable; + u16 origsize = 0; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + + if (pagetable[index] & 0x8000) { + /* Block is allocated - mark block as free */ + origsize = pagetable[index] & 0x7fff; + pagetable[index] = origsize; + + /* If next block is free, we concat both blocks */ + if (!(pagetable[index + origsize] & 0x8000)) + pagetable[index] += pagetable[index + origsize] & 0x7fff; + } + + spin_unlock_irqrestore(&card->lock, flags); + + return; +} diff --git a/sound/oss/emu10k1/hwaccess.c b/sound/oss/emu10k1/hwaccess.c new file mode 100644 index 000000000000..2dc16a841fa1 --- /dev/null +++ b/sound/oss/emu10k1/hwaccess.c @@ -0,0 +1,507 @@ +/* + ********************************************************************** + * hwaccess.c -- Hardware access layer + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * December 9, 1999 Jon Taylor rewrote the I/O subsystem + * + ********************************************************************** + * + * 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 + +#include "hwaccess.h" +#include "8010.h" +#include "icardmid.h" + +/************************************************************************* +* Function : srToPitch * +* Input : sampleRate - sampling rate * +* Return : pitch value * +* About : convert sampling rate to pitch * +* Note : for 8010, sampling rate is at 48kHz, this function should * +* be changed. * +*************************************************************************/ +u32 srToPitch(u32 sampleRate) +{ + int i; + + /* FIXME: These tables should be defined in a headerfile */ + static u32 logMagTable[128] = { + 0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2, + 0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5, + 0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081, + 0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191, + 0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7, + 0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829, + 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e, + 0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26, + 0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d, + 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885, + 0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899, + 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c, + 0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3, + 0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3, + 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83, + 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df + }; + + static char logSlopeTable[128] = { + 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58, + 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53, + 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, + 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b, + 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47, + 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, + 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41, + 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e, + 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, + 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37, + 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35, + 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f + }; + + if (sampleRate == 0) + return 0; /* Bail out if no leading "1" */ + + sampleRate *= 11185; /* Scale 48000 to 0x20002380 */ + + for (i = 31; i > 0; i--) { + if (sampleRate & 0x80000000) { /* Detect leading "1" */ + return (u32) (((s32) (i - 15) << 20) + + logMagTable[0x7f & (sampleRate >> 24)] + + (0x7f & (sampleRate >> 17)) * logSlopeTable[0x7f & (sampleRate >> 24)]); + } + sampleRate = sampleRate << 1; + } + + DPF(2, "srToPitch: BUG!\n"); + return 0; /* Should never reach this point */ +} + +/******************************************* +* write/read PCI function 0 registers * +********************************************/ +void emu10k1_writefn0(struct emu10k1_card *card, u32 reg, u32 data) +{ + unsigned long flags; + + if (reg & 0xff000000) { + u32 mask; + u8 size, offset; + + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = ((1 << size) - 1) << offset; + data = (data << offset) & mask; + reg &= 0x7f; + + spin_lock_irqsave(&card->lock, flags); + data |= inl(card->iobase + reg) & ~mask; + outl(data, card->iobase + reg); + spin_unlock_irqrestore(&card->lock, flags); + } else { + spin_lock_irqsave(&card->lock, flags); + outl(data, card->iobase + reg); + spin_unlock_irqrestore(&card->lock, flags); + } + + return; +} + +#ifdef DBGEMU +void emu10k1_writefn0_2(struct emu10k1_card *card, u32 reg, u32 data, int size) +{ + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + + if (size == 32) + outl(data, card->iobase + (reg & 0x1F)); + else if (size == 16) + outw(data, card->iobase + (reg & 0x1F)); + else + outb(data, card->iobase + (reg & 0x1F)); + + spin_unlock_irqrestore(&card->lock, flags); + + return; +} +#endif /* DBGEMU */ + +u32 emu10k1_readfn0(struct emu10k1_card * card, u32 reg) +{ + u32 val; + unsigned long flags; + + if (reg & 0xff000000) { + u32 mask; + u8 size, offset; + + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = ((1 << size) - 1) << offset; + reg &= 0x7f; + + spin_lock_irqsave(&card->lock, flags); + val = inl(card->iobase + reg); + spin_unlock_irqrestore(&card->lock, flags); + + return (val & mask) >> offset; + } else { + spin_lock_irqsave(&card->lock, flags); + val = inl(card->iobase + reg); + spin_unlock_irqrestore(&card->lock, flags); + return val; + } +} + +void emu10k1_timer_set(struct emu10k1_card * card, u16 data) +{ + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + outw(data & TIMER_RATE_MASK, card->iobase + TIMER); + spin_unlock_irqrestore(&card->lock, flags); +} + +/************************************************************************ +* write/read Emu10k1 pointer-offset register set, accessed through * +* the PTR and DATA registers * +*************************************************************************/ +#define A_PTR_ADDRESS_MASK 0x0fff0000 +void sblive_writeptr(struct emu10k1_card *card, u32 reg, u32 channel, u32 data) +{ + u32 regptr; + unsigned long flags; + + regptr = ((reg << 16) & A_PTR_ADDRESS_MASK) | (channel & PTR_CHANNELNUM_MASK); + + if (reg & 0xff000000) { + u32 mask; + u8 size, offset; + + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = ((1 << size) - 1) << offset; + data = (data << offset) & mask; + + spin_lock_irqsave(&card->lock, flags); + outl(regptr, card->iobase + PTR); + data |= inl(card->iobase + DATA) & ~mask; + outl(data, card->iobase + DATA); + spin_unlock_irqrestore(&card->lock, flags); + } else { + spin_lock_irqsave(&card->lock, flags); + outl(regptr, card->iobase + PTR); + outl(data, card->iobase + DATA); + spin_unlock_irqrestore(&card->lock, flags); + } +} + +/* ... : data, reg, ... , TAGLIST_END */ +void sblive_writeptr_tag(struct emu10k1_card *card, u32 channel, ...) +{ + va_list args; + + unsigned long flags; + u32 reg; + + va_start(args, channel); + + spin_lock_irqsave(&card->lock, flags); + while ((reg = va_arg(args, u32)) != TAGLIST_END) { + u32 data = va_arg(args, u32); + u32 regptr = (((reg << 16) & A_PTR_ADDRESS_MASK) + | (channel & PTR_CHANNELNUM_MASK)); + outl(regptr, card->iobase + PTR); + if (reg & 0xff000000) { + int size = (reg >> 24) & 0x3f; + int offset = (reg >> 16) & 0x1f; + u32 mask = ((1 << size) - 1) << offset; + data = (data << offset) & mask; + + data |= inl(card->iobase + DATA) & ~mask; + } + outl(data, card->iobase + DATA); + } + spin_unlock_irqrestore(&card->lock, flags); + + va_end(args); + + return; +} + +u32 sblive_readptr(struct emu10k1_card * card, u32 reg, u32 channel) +{ + u32 regptr, val; + unsigned long flags; + + regptr = ((reg << 16) & A_PTR_ADDRESS_MASK) | (channel & PTR_CHANNELNUM_MASK); + + if (reg & 0xff000000) { + u32 mask; + u8 size, offset; + + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = ((1 << size) - 1) << offset; + + spin_lock_irqsave(&card->lock, flags); + outl(regptr, card->iobase + PTR); + val = inl(card->iobase + DATA); + spin_unlock_irqrestore(&card->lock, flags); + + return (val & mask) >> offset; + } else { + spin_lock_irqsave(&card->lock, flags); + outl(regptr, card->iobase + PTR); + val = inl(card->iobase + DATA); + spin_unlock_irqrestore(&card->lock, flags); + + return val; + } +} + +void emu10k1_irq_enable(struct emu10k1_card *card, u32 irq_mask) +{ + u32 val; + unsigned long flags; + + DPF(2,"emu10k1_irq_enable()\n"); + + spin_lock_irqsave(&card->lock, flags); + val = inl(card->iobase + INTE) | irq_mask; + outl(val, card->iobase + INTE); + spin_unlock_irqrestore(&card->lock, flags); + return; +} + +void emu10k1_irq_disable(struct emu10k1_card *card, u32 irq_mask) +{ + u32 val; + unsigned long flags; + + DPF(2,"emu10k1_irq_disable()\n"); + + spin_lock_irqsave(&card->lock, flags); + val = inl(card->iobase + INTE) & ~irq_mask; + outl(val, card->iobase + INTE); + spin_unlock_irqrestore(&card->lock, flags); + return; +} + +void emu10k1_clear_stop_on_loop(struct emu10k1_card *card, u32 voicenum) +{ + /* Voice interrupt */ + if (voicenum >= 32) + sblive_writeptr(card, SOLEH | ((0x0100 | (voicenum - 32)) << 16), 0, 0); + else + sblive_writeptr(card, SOLEL | ((0x0100 | voicenum) << 16), 0, 0); + + return; +} + +static void sblive_wcwait(struct emu10k1_card *card, u32 wait) +{ + volatile unsigned uCount; + u32 newtime = 0, curtime; + + curtime = emu10k1_readfn0(card, WC_SAMPLECOUNTER); + while (wait--) { + uCount = 0; + while (uCount++ < TIMEOUT) { + newtime = emu10k1_readfn0(card, WC_SAMPLECOUNTER); + if (newtime != curtime) + break; + } + + if (uCount >= TIMEOUT) + break; + + curtime = newtime; + } +} + +u16 emu10k1_ac97_read(struct ac97_codec *codec, u8 reg) +{ + struct emu10k1_card *card = codec->private_data; + u16 data; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + + outb(reg, card->iobase + AC97ADDRESS); + data = inw(card->iobase + AC97DATA); + + spin_unlock_irqrestore(&card->lock, flags); + + return data; +} + +void emu10k1_ac97_write(struct ac97_codec *codec, u8 reg, u16 value) +{ + struct emu10k1_card *card = codec->private_data; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + + outb(reg, card->iobase + AC97ADDRESS); + outw(value, card->iobase + AC97DATA); + outb( AC97_EXTENDED_ID, card->iobase + AC97ADDRESS); + spin_unlock_irqrestore(&card->lock, flags); +} + +/********************************************************* +* MPU access functions * +**********************************************************/ + +int emu10k1_mpu_write_data(struct emu10k1_card *card, u8 data) +{ + unsigned long flags; + int ret; + + if (card->is_audigy) { + if ((sblive_readptr(card, A_MUSTAT,0) & MUSTAT_ORDYN) == 0) { + sblive_writeptr(card, A_MUDATA, 0, data); + ret = 0; + } else + ret = -1; + } else { + spin_lock_irqsave(&card->lock, flags); + + if ((inb(card->iobase + MUSTAT) & MUSTAT_ORDYN) == 0) { + outb(data, card->iobase + MUDATA); + ret = 0; + } else + ret = -1; + + spin_unlock_irqrestore(&card->lock, flags); + } + + return ret; +} + +int emu10k1_mpu_read_data(struct emu10k1_card *card, u8 * data) +{ + unsigned long flags; + int ret; + + if (card->is_audigy) { + if ((sblive_readptr(card, A_MUSTAT,0) & MUSTAT_IRDYN) == 0) { + *data = sblive_readptr(card, A_MUDATA,0); + ret = 0; + } else + ret = -1; + } else { + spin_lock_irqsave(&card->lock, flags); + + if ((inb(card->iobase + MUSTAT) & MUSTAT_IRDYN) == 0) { + *data = inb(card->iobase + MUDATA); + ret = 0; + } else + ret = -1; + + spin_unlock_irqrestore(&card->lock, flags); + } + + return ret; +} + +int emu10k1_mpu_reset(struct emu10k1_card *card) +{ + u8 status; + unsigned long flags; + + DPF(2, "emu10k1_mpu_reset()\n"); + if (card->is_audigy) { + if (card->mpuacqcount == 0) { + sblive_writeptr(card, A_MUCMD, 0, MUCMD_RESET); + sblive_wcwait(card, 8); + sblive_writeptr(card, A_MUCMD, 0, MUCMD_RESET); + sblive_wcwait(card, 8); + sblive_writeptr(card, A_MUCMD, 0, MUCMD_ENTERUARTMODE); + sblive_wcwait(card, 8); + status = sblive_readptr(card, A_MUDATA, 0); + if (status == 0xfe) + return 0; + else + return -1; + } + + return 0; + } else { + if (card->mpuacqcount == 0) { + spin_lock_irqsave(&card->lock, flags); + outb(MUCMD_RESET, card->iobase + MUCMD); + spin_unlock_irqrestore(&card->lock, flags); + + sblive_wcwait(card, 8); + + spin_lock_irqsave(&card->lock, flags); + outb(MUCMD_RESET, card->iobase + MUCMD); + spin_unlock_irqrestore(&card->lock, flags); + + sblive_wcwait(card, 8); + + spin_lock_irqsave(&card->lock, flags); + outb(MUCMD_ENTERUARTMODE, card->iobase + MUCMD); + spin_unlock_irqrestore(&card->lock, flags); + + sblive_wcwait(card, 8); + + spin_lock_irqsave(&card->lock, flags); + status = inb(card->iobase + MUDATA); + spin_unlock_irqrestore(&card->lock, flags); + + if (status == 0xfe) + return 0; + else + return -1; + } + + return 0; + } +} + +int emu10k1_mpu_acquire(struct emu10k1_card *card) +{ + /* FIXME: This should be a macro */ + ++card->mpuacqcount; + + return 0; +} + +int emu10k1_mpu_release(struct emu10k1_card *card) +{ + /* FIXME: this should be a macro */ + --card->mpuacqcount; + + return 0; +} diff --git a/sound/oss/emu10k1/hwaccess.h b/sound/oss/emu10k1/hwaccess.h new file mode 100644 index 000000000000..104223a192aa --- /dev/null +++ b/sound/oss/emu10k1/hwaccess.h @@ -0,0 +1,247 @@ +/* + ********************************************************************** + * hwaccess.h + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 _HWACCESS_H +#define _HWACCESS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "efxmgr.h" +#include "passthrough.h" +#include "midi.h" + +#define EMUPAGESIZE 4096 /* don't change */ +#define NUM_G 64 /* use all channels */ +#define NUM_FXSENDS 4 /* don't change */ +/* setting this to other than a power of two may break some applications */ +#define MAXBUFSIZE 65536 +#define MAXPAGES 8192 +#define BUFMAXPAGES (MAXBUFSIZE / PAGE_SIZE) + +#define FLAGS_AVAILABLE 0x0001 +#define FLAGS_READY 0x0002 + +struct memhandle +{ + dma_addr_t dma_handle; + void *addr; + u32 size; +}; + +#define DEBUG_LEVEL 2 + +#ifdef EMU10K1_DEBUG +# define DPD(level,x,y...) do {if(level <= DEBUG_LEVEL) printk( KERN_NOTICE "emu10k1: %s: %d: " x , __FILE__ , __LINE__ , y );} while(0) +# define DPF(level,x) do {if(level <= DEBUG_LEVEL) printk( KERN_NOTICE "emu10k1: %s: %d: " x , __FILE__ , __LINE__ );} while(0) +#else +# define DPD(level,x,y...) do { } while (0) /* not debugging: nothing */ +# define DPF(level,x) do { } while (0) +#endif /* EMU10K1_DEBUG */ + +#define ERROR() DPF(1,"error\n") + +/* DATA STRUCTURES */ + +struct emu10k1_waveout +{ + u32 send_routing[3]; + // audigy only: + u32 send_routing2[3]; + + u32 send_dcba[3]; + // audigy only: + u32 send_hgfe[3]; +}; +#define ROUTE_PCM 0 +#define ROUTE_PT 1 +#define ROUTE_PCM1 2 + +#define SEND_MONO 0 +#define SEND_LEFT 1 +#define SEND_RIGHT 2 + +struct emu10k1_wavein +{ + struct wiinst *ac97; + struct wiinst *mic; + struct wiinst *fx; + + u8 recsrc; + u32 fxwc; +}; + +#define CMD_READ 1 +#define CMD_WRITE 2 + +struct mixer_private_ioctl { + u32 cmd; + u32 val[90]; +}; + +/* bogus ioctls numbers to escape from OSS mixer limitations */ +#define CMD_WRITEFN0 _IOW('D', 0, struct mixer_private_ioctl) +#define CMD_READFN0 _IOR('D', 1, struct mixer_private_ioctl) +#define CMD_WRITEPTR _IOW('D', 2, struct mixer_private_ioctl) +#define CMD_READPTR _IOR('D', 3, struct mixer_private_ioctl) +#define CMD_SETRECSRC _IOW('D', 4, struct mixer_private_ioctl) +#define CMD_GETRECSRC _IOR('D', 5, struct mixer_private_ioctl) +#define CMD_GETVOICEPARAM _IOR('D', 6, struct mixer_private_ioctl) +#define CMD_SETVOICEPARAM _IOW('D', 7, struct mixer_private_ioctl) +#define CMD_GETPATCH _IOR('D', 8, struct mixer_private_ioctl) +#define CMD_GETGPR _IOR('D', 9, struct mixer_private_ioctl) +#define CMD_GETCTLGPR _IOR('D', 10, struct mixer_private_ioctl) +#define CMD_SETPATCH _IOW('D', 11, struct mixer_private_ioctl) +#define CMD_SETGPR _IOW('D', 12, struct mixer_private_ioctl) +#define CMD_SETCTLGPR _IOW('D', 13, struct mixer_private_ioctl) +#define CMD_SETGPOUT _IOW('D', 14, struct mixer_private_ioctl) +#define CMD_GETGPR2OSS _IOR('D', 15, struct mixer_private_ioctl) +#define CMD_SETGPR2OSS _IOW('D', 16, struct mixer_private_ioctl) +#define CMD_SETMCH_FX _IOW('D', 17, struct mixer_private_ioctl) +#define CMD_SETPASSTHROUGH _IOW('D', 18, struct mixer_private_ioctl) +#define CMD_PRIVATE3_VERSION _IOW('D', 19, struct mixer_private_ioctl) +#define CMD_AC97_BOOST _IOW('D', 20, struct mixer_private_ioctl) + +//up this number when breaking compatibility +#define PRIVATE3_VERSION 2 + +struct emu10k1_card +{ + struct list_head list; + + struct memhandle virtualpagetable; + struct memhandle tankmem; + struct memhandle silentpage; + + spinlock_t lock; + + u8 voicetable[NUM_G]; + u16 emupagetable[MAXPAGES]; + + struct list_head timers; + u16 timer_delay; + spinlock_t timer_lock; + + struct pci_dev *pci_dev; + unsigned long iobase; + unsigned long length; + unsigned short model; + unsigned int irq; + + int audio_dev; + int audio_dev1; + int midi_dev; +#ifdef EMU10K1_SEQUENCER + int seq_dev; + struct emu10k1_mididevice *seq_mididev; +#endif + + struct ac97_codec *ac97; + int ac97_supported_mixers; + int ac97_stereo_mixers; + + /* Number of first fx voice for multichannel output */ + u8 mchannel_fx; + struct emu10k1_waveout waveout; + struct emu10k1_wavein wavein; + struct emu10k1_mpuout *mpuout; + struct emu10k1_mpuin *mpuin; + + struct semaphore open_sem; + mode_t open_mode; + wait_queue_head_t open_wait; + + u32 mpuacqcount; // Mpu acquire count + u32 has_toslink; // TOSLink detection + + u8 chiprev; /* Chip revision */ + u8 is_audigy; + u8 is_aps; + + struct patch_manager mgr; + struct pt_data pt; +}; + +int emu10k1_addxmgr_alloc(u32, struct emu10k1_card *); +void emu10k1_addxmgr_free(struct emu10k1_card *, int); + +int emu10k1_find_control_gpr(struct patch_manager *, const char *, const char *); +void emu10k1_set_control_gpr(struct emu10k1_card *, int , s32, int ); + +void emu10k1_set_volume_gpr(struct emu10k1_card *, int, s32, int); + + +#define VOL_6BIT 0x40 +#define VOL_5BIT 0x20 +#define VOL_4BIT 0x10 + +#define TIMEOUT 16384 + +u32 srToPitch(u32); + +extern struct list_head emu10k1_devs; + +/* Hardware Abstraction Layer access functions */ + +void emu10k1_writefn0(struct emu10k1_card *, u32, u32); +void emu10k1_writefn0_2(struct emu10k1_card *, u32, u32, int); +u32 emu10k1_readfn0(struct emu10k1_card *, u32); + +void emu10k1_timer_set(struct emu10k1_card *, u16); + +void sblive_writeptr(struct emu10k1_card *, u32, u32, u32); +void sblive_writeptr_tag(struct emu10k1_card *, u32, ...); +#define TAGLIST_END 0 + +u32 sblive_readptr(struct emu10k1_card *, u32 , u32 ); + +void emu10k1_irq_enable(struct emu10k1_card *, u32); +void emu10k1_irq_disable(struct emu10k1_card *, u32); +void emu10k1_clear_stop_on_loop(struct emu10k1_card *, u32); + +/* AC97 Codec register access function */ +u16 emu10k1_ac97_read(struct ac97_codec *, u8); +void emu10k1_ac97_write(struct ac97_codec *, u8, u16); + +/* MPU access function*/ +int emu10k1_mpu_write_data(struct emu10k1_card *, u8); +int emu10k1_mpu_read_data(struct emu10k1_card *, u8 *); +int emu10k1_mpu_reset(struct emu10k1_card *); +int emu10k1_mpu_acquire(struct emu10k1_card *); +int emu10k1_mpu_release(struct emu10k1_card *); + +#endif /* _HWACCESS_H */ diff --git a/sound/oss/emu10k1/icardmid.h b/sound/oss/emu10k1/icardmid.h new file mode 100644 index 000000000000..6a6ef419401f --- /dev/null +++ b/sound/oss/emu10k1/icardmid.h @@ -0,0 +1,163 @@ +/* + ********************************************************************** + * isblive_mid.h + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 _ICARDMIDI_H +#define _ICARDMIDI_H + +/* MIDI defines */ +#define MIDI_DATA_FIRST 0x00 +#define MIDI_DATA_LAST 0x7F +#define MIDI_STATUS_FIRST 0x80 +#define MIDI_STATUS_LAST 0xFF + +/* Channel status bytes */ +#define MIDI_STATUS_CHANNEL_FIRST 0x80 +#define MIDI_STATUS_CHANNEL_LAST 0xE0 +#define MIDI_STATUS_CHANNEL_MASK 0xF0 + +/* Channel voice messages */ +#define MIDI_VOICE_NOTE_OFF 0x80 +#define MIDI_VOICE_NOTE_ON 0x90 +#define MIDI_VOICE_POLY_PRESSURE 0xA0 +#define MIDI_VOICE_CONTROL_CHANGE 0xB0 +#define MIDI_VOICE_PROGRAM_CHANGE 0xC0 +#define MIDI_VOICE_CHANNEL_PRESSURE 0xD0 +#define MIDI_VOICE_PITCH_BEND 0xE0 + +/* Channel mode messages */ +#define MIDI_MODE_CHANNEL MIDI_VOICE_CONTROL_CHANGE + +/* System status bytes */ +#define MIDI_STATUS_SYSTEM_FIRST 0xF0 +#define MIDI_STATUS_SYSTEM_LAST 0xFF + +/* System exclusive messages */ +#define MIDI_SYSEX_BEGIN 0xF0 +#define MIDI_SYSEX_EOX 0xF7 + +/* System common messages */ +#define MIDI_COMMON_TCQF 0xF1 /* Time code quarter frame */ +#define MIDI_COMMON_SONG_POSITION 0xF2 +#define MIDI_COMMON_SONG_SELECT 0xF3 +#define MIDI_COMMON_UNDEFINED_F4 0xF4 +#define MIDI_COMMON_UNDEFINED_F5 0xF5 +#define MIDI_COMMON_TUNE_REQUEST 0xF6 + +/* System real-time messages */ +#define MIDI_RTIME_TIMING_CLOCK 0xF8 +#define MIDI_RTIME_UNDEFINED_F9 0xF9 +#define MIDI_RTIME_START 0xFA +#define MIDI_RTIME_CONTINUE 0xFB +#define MIDI_RTIME_STOP 0xFC +#define MIDI_RTIME_UNDEFINED_FD 0xFD +#define MIDI_RTIME_ACTIVE_SENSING 0xFE +#define MIDI_RTIME_SYSTEM_RESET 0xFF + +/* Flags for flags parm of midiOutCachePatches(), midiOutCacheDrumPatches() */ +#define MIDI_CACHE_ALL 1 +#define MIDI_CACHE_BESTFIT 2 +#define MIDI_CACHE_QUERY 3 +#define MIDI_UNCACHE 4 + +/* Event declarations for MPU IRQ Callbacks */ +#define ICARDMIDI_INLONGDATA 0x00000001 /* MIM_LONGDATA */ +#define ICARDMIDI_INLONGERROR 0x00000002 /* MIM_LONGERROR */ +#define ICARDMIDI_OUTLONGDATA 0x00000004 /* MOM_DONE for MPU OUT buffer */ +#define ICARDMIDI_INDATA 0x00000010 /* MIM_DATA */ +#define ICARDMIDI_INDATAERROR 0x00000020 /* MIM_ERROR */ + +/* Declaration for flags in CARDMIDIBUFFERHDR */ +/* Make it the same as MHDR_DONE, MHDR_INQUEUE in mmsystem.h */ +#define MIDIBUF_DONE 0x00000001 +#define MIDIBUF_INQUEUE 0x00000004 + +/* Declaration for msg parameter in midiCallbackFn */ +#define ICARDMIDI_OUTBUFFEROK 0x00000001 +#define ICARDMIDI_INMIDIOK 0x00000002 + +/* Declaration for technology in struct midi_caps */ +#define MT_MIDIPORT 0x00000001 /* In original MIDIOUTCAPS structure */ +#define MT_FMSYNTH 0x00000004 /* In original MIDIOUTCAPS structure */ +#define MT_AWESYNTH 0x00001000 +#define MT_PCISYNTH 0x00002000 +#define MT_PCISYNTH64 0x00004000 +#define CARDMIDI_AWEMASK 0x0000F000 + +enum LocalErrorCode +{ + CTSTATUS_NOTENABLED = 0x7000, + CTSTATUS_READY, + CTSTATUS_BUSY, + CTSTATUS_DATAAVAIL, + CTSTATUS_NODATA, + CTSTATUS_NEXT_BYTE +}; + +/* MIDI data block header */ +struct midi_hdr +{ + u8 *reserved; /* Pointer to original locked data block */ + u32 bufferlength; /* Length of data in data block */ + u32 bytesrecorded; /* Used for input only */ + u32 user; /* For client's use */ + u32 flags; /* Assorted flags (see defines) */ + struct list_head list; /* Reserved for driver */ + u8 *data; /* Second copy of first pointer */ +}; + +/* Enumeration for SetControl */ +enum +{ + MIDIOBJVOLUME = 0x1, + MIDIQUERYACTIVEINST +}; + +struct midi_queue +{ + struct midi_queue *next; + u32 qtype; /* 0 = short message, 1 = long data */ + u32 length; + u32 sizeLeft; + u8 *midibyte; + unsigned long refdata; +}; + +struct midi_openinfo +{ + u32 cbsize; + u32 flags; + unsigned long refdata; + u32 streamid; +}; + +int emu10k1_midi_callback(unsigned long , unsigned long, unsigned long *); + +#endif /* _ICARDMIDI_H */ diff --git a/sound/oss/emu10k1/icardwav.h b/sound/oss/emu10k1/icardwav.h new file mode 100644 index 000000000000..25be40928b4d --- /dev/null +++ b/sound/oss/emu10k1/icardwav.h @@ -0,0 +1,53 @@ +/* + ********************************************************************** + * icardwav.h + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 _ICARDWAV_H +#define _ICARDWAV_H + +struct wave_format +{ + int id; + int samplingrate; + u8 bitsperchannel; + u8 channels; /* 1 = Mono, 2 = Stereo, 3, ... = Multichannel */ + u8 bytesperchannel; + u8 bytespervoicesample; + u8 bytespersample; + int bytespersec; + u8 passthrough; +}; + +/* emu10k1_wave states */ +#define WAVE_STATE_OPEN 0x01 +#define WAVE_STATE_STARTED 0x02 +#define WAVE_STATE_CLOSED 0x04 + +#endif /* _ICARDWAV_H */ diff --git a/sound/oss/emu10k1/irqmgr.c b/sound/oss/emu10k1/irqmgr.c new file mode 100644 index 000000000000..d19b464ba01b --- /dev/null +++ b/sound/oss/emu10k1/irqmgr.c @@ -0,0 +1,113 @@ +/* + ********************************************************************** + * irqmgr.c - IRQ manager for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 "hwaccess.h" +#include "8010.h" +#include "cardmi.h" +#include "cardmo.h" +#include "irqmgr.h" + +/* Interrupt handler */ + +irqreturn_t emu10k1_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct emu10k1_card *card = (struct emu10k1_card *) dev_id; + u32 irqstatus, irqstatus_tmp; + int handled = 0; + + DPD(4, "emu10k1_interrupt called, irq = %u\n", irq); + + /* + ** NOTE : + ** We do a 'while loop' here cos on certain machines, with both + ** playback and recording going on at the same time, IRQs will + ** stop coming in after a while. Checking IPND indeed shows that + ** there are interrupts pending but the PIC says no IRQs pending. + ** I suspect that some boards need edge-triggered IRQs but are not + ** getting that condition if we don't completely clear the IPND + ** (make sure no more interrupts are pending). + ** - Eric + */ + + while ((irqstatus = inl(card->iobase + IPR))) { + DPD(4, "irq status %#x\n", irqstatus); + + irqstatus_tmp = irqstatus; + + if (irqstatus & IRQTYPE_TIMER) { + emu10k1_timer_irqhandler(card); + irqstatus &= ~IRQTYPE_TIMER; + } + + if (irqstatus & IRQTYPE_DSP) { + emu10k1_dsp_irqhandler(card); + irqstatus &= ~IRQTYPE_DSP; + } + + if (irqstatus & IRQTYPE_MPUIN) { + emu10k1_mpuin_irqhandler(card); + irqstatus &= ~IRQTYPE_MPUIN; + } + + if (irqstatus & IRQTYPE_MPUOUT) { + emu10k1_mpuout_irqhandler(card); + irqstatus &= ~IRQTYPE_MPUOUT; + } + + if (irqstatus & IPR_MUTE) { + emu10k1_mute_irqhandler(card); + irqstatus &=~IPR_MUTE; + } + + if (irqstatus & IPR_VOLINCR) { + emu10k1_volincr_irqhandler(card); + irqstatus &=~IPR_VOLINCR; + } + + if (irqstatus & IPR_VOLDECR) { + emu10k1_voldecr_irqhandler(card); + irqstatus &=~IPR_VOLDECR; + } + + if (irqstatus){ + printk(KERN_ERR "emu10k1: Warning, unhandled interrupt: %#08x\n", irqstatus); + //make sure any interrupts we don't handle are disabled: + emu10k1_irq_disable(card, ~(INTE_MIDIRXENABLE | INTE_MIDITXENABLE | INTE_INTERVALTIMERENB | + INTE_VOLDECRENABLE | INTE_VOLINCRENABLE | INTE_MUTEENABLE | + INTE_FXDSPENABLE)); + } + + /* acknowledge interrupt */ + outl(irqstatus_tmp, card->iobase + IPR); + handled = 1; + } + return IRQ_RETVAL(handled); +} diff --git a/sound/oss/emu10k1/irqmgr.h b/sound/oss/emu10k1/irqmgr.h new file mode 100644 index 000000000000..7e7c9ca1098c --- /dev/null +++ b/sound/oss/emu10k1/irqmgr.h @@ -0,0 +1,52 @@ +/* + ********************************************************************** + * irq.h + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 _IRQ_H +#define _IRQ_H + +/* EMU Irq Types */ +#define IRQTYPE_PCIBUSERROR IPR_PCIERROR +#define IRQTYPE_MIXERBUTTON (IPR_VOLINCR | IPR_VOLDECR | IPR_MUTE) +#define IRQTYPE_VOICE (IPR_CHANNELLOOP | IPR_CHANNELNUMBERMASK) +#define IRQTYPE_RECORD (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL | IPR_MICBUFFULL | IPR_MICBUFHALFFULL | IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL) +#define IRQTYPE_MPUOUT (IPR_MIDITRANSBUFEMPTY | A_IPR_MIDITRANSBUFEMPTY2) +#define IRQTYPE_MPUIN (IPR_MIDIRECVBUFEMPTY | A_IPR_MIDIRECVBUFEMPTY2) +#define IRQTYPE_TIMER IPR_INTERVALTIMER +#define IRQTYPE_SPDIF (IPR_GPSPDIFSTATUSCHANGE | IPR_CDROMSTATUSCHANGE) +#define IRQTYPE_DSP IPR_FXDSP + +void emu10k1_timer_irqhandler(struct emu10k1_card *); +void emu10k1_dsp_irqhandler(struct emu10k1_card *); +void emu10k1_mute_irqhandler(struct emu10k1_card *); +void emu10k1_volincr_irqhandler(struct emu10k1_card *); +void emu10k1_voldecr_irqhandler(struct emu10k1_card *); + +#endif /* _IRQ_H */ diff --git a/sound/oss/emu10k1/main.c b/sound/oss/emu10k1/main.c new file mode 100644 index 000000000000..9b905bae423e --- /dev/null +++ b/sound/oss/emu10k1/main.c @@ -0,0 +1,1475 @@ + /* + ********************************************************************** + * main.c - Creative EMU10K1 audio driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * November 2, 1999 Alan Cox cleaned up stuff + * + ********************************************************************** + * + * 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. + * + ********************************************************************** + * + * Supported devices: + * /dev/dsp: Standard /dev/dsp device, OSS-compatible + * /dev/dsp1: Routes to rear speakers only + * /dev/mixer: Standard /dev/mixer device, OSS-compatible + * /dev/midi: Raw MIDI UART device, mostly OSS-compatible + * /dev/sequencer: Sequencer Interface (requires sound.o) + * + * Revision history: + * 0.1 beta Initial release + * 0.2 Lowered initial mixer vol. Improved on stuttering wave playback. Added MIDI UART support. + * 0.3 Fixed mixer routing bug, added APS, joystick support. + * 0.4 Added rear-channel, SPDIF support. + * 0.5 Source cleanup, SMP fixes, multiopen support, 64 bit arch fixes, + * moved bh's to tasklets, moved to the new PCI driver initialization style. + * 0.6 Make use of pci_alloc_consistent, improve compatibility layer for 2.2 kernels, + * code reorganization and cleanup. + * 0.7 Support for the Emu-APS. Bug fixes for voice cache setup, mmaped sound + poll(). + * Support for setting external TRAM size. + * 0.8 Make use of the kernel ac97 interface. Support for a dsp patch manager. + * 0.9 Re-enables rear speakers volume controls + * 0.10 Initializes rear speaker volume. + * Dynamic patch storage allocation. + * New private ioctls to change control gpr values. + * Enable volume control interrupts. + * By default enable dsp routes to digital out. + * 0.11 Fixed fx / 4 problem. + * 0.12 Implemented mmaped for recording. + * Fixed bug: not unreserving mmaped buffer pages. + * IRQ handler cleanup. + * 0.13 Fixed problem with dsp1 + * Simplified dsp patch writing (inside the driver) + * Fixed several bugs found by the Stanford tools + * 0.14 New control gpr to oss mixer mapping feature (Chris Purnell) + * Added AC3 Passthrough Support (Juha Yrjola) + * Added Support for 5.1 cards (digital out and the third analog out) + * 0.15 Added Sequencer Support (Daniel Mack) + * Support for multichannel pcm playback (Eduard Hasenleithner) + * 0.16 Mixer improvements, added old treble/bass support (Daniel Bertrand) + * Small code format cleanup. + * Deadlock bug fix for emu10k1_volxxx_irqhandler(). + * 0.17 Fix for mixer SOUND_MIXER_INFO ioctl. + * Fix for HIGHMEM machines (emu10k1 can only do 31 bit bus master) + * midi poll initial implementation. + * Small mixer fixes/cleanups. + * Improved support for 5.1 cards. + * 0.18 Fix for possible leak in pci_alloc_consistent() + * Cleaned up poll() functions (audio and midi). Don't start input. + * Restrict DMA pages used to 512Mib range. + * New AC97_BOOST mixer ioctl. + * 0.19a Added Support for Audigy Cards + * Real fix for kernel with highmem support (cast dma_handle to u32). + * Fix recording buffering parameters calculation. + * Use unsigned long for variables in bit ops. + * 0.20a Fixed recording startup + * Fixed timer rate setting (it's a 16-bit register) + * 0.21 Converted code to use pci_name() instead of accessing slot_name + * directly (Eugene Teo) + *********************************************************************/ + +/* These are only included once per module */ +#include +#include +#include +#include +#include + +#include "hwaccess.h" +#include "8010.h" +#include "efxmgr.h" +#include "cardwo.h" +#include "cardwi.h" +#include "cardmo.h" +#include "cardmi.h" +#include "recmgr.h" +#include "ecard.h" + + +#ifdef EMU10K1_SEQUENCER +#define MIDI_SYNTH_NAME "EMU10K1 MIDI" +#define MIDI_SYNTH_CAPS SYNTH_CAP_INPUT + +#include "../sound_config.h" +#include "../midi_synth.h" + +/* this should be in dev_table.h */ +#define SNDCARD_EMU10K1 46 +#endif + + +/* the emu10k1 _seems_ to only supports 29 bit (512MiB) bit bus master */ +#define EMU10K1_DMA_MASK 0x1fffffff /* DMA buffer mask for pci_alloc_consist */ + +#ifndef PCI_VENDOR_ID_CREATIVE +#define PCI_VENDOR_ID_CREATIVE 0x1102 +#endif + +#ifndef PCI_DEVICE_ID_CREATIVE_EMU10K1 +#define PCI_DEVICE_ID_CREATIVE_EMU10K1 0x0002 +#endif +#ifndef PCI_DEVICE_ID_CREATIVE_AUDIGY +#define PCI_DEVICE_ID_CREATIVE_AUDIGY 0x0004 +#endif + +#define EMU_APS_SUBID 0x40011102 + +enum { + EMU10K1 = 0, + AUDIGY, +}; + +static char *card_names[] __devinitdata = { + "EMU10K1", + "Audigy", +}; + +static struct pci_device_id emu10k1_pci_tbl[] = { + {PCI_VENDOR_ID_CREATIVE, PCI_DEVICE_ID_CREATIVE_EMU10K1, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, EMU10K1}, + {PCI_VENDOR_ID_CREATIVE, PCI_DEVICE_ID_CREATIVE_AUDIGY, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, AUDIGY}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, emu10k1_pci_tbl); + +/* Global var instantiation */ + +LIST_HEAD(emu10k1_devs); + +extern struct file_operations emu10k1_audio_fops; +extern struct file_operations emu10k1_mixer_fops; +extern struct file_operations emu10k1_midi_fops; + +#ifdef EMU10K1_SEQUENCER +static struct midi_operations emu10k1_midi_operations; +#endif + +extern irqreturn_t emu10k1_interrupt(int, void *, struct pt_regs *s); + +static int __devinit emu10k1_audio_init(struct emu10k1_card *card) +{ + /* Assign default playback voice parameters */ + if (card->is_audigy) + card->mchannel_fx = 0; + else + card->mchannel_fx = 8; + + + if (card->is_audigy) { + /* mono voice */ + card->waveout.send_dcba[SEND_MONO] = 0xffffffff; + card->waveout.send_hgfe[SEND_MONO] = 0x0000ffff; + + /* stereo voice */ + /* left */ + card->waveout.send_dcba[SEND_LEFT] = 0x00ff00ff; + card->waveout.send_hgfe[SEND_LEFT] = 0x00007f7f; + /* right */ + card->waveout.send_dcba[SEND_RIGHT] = 0xff00ff00; + card->waveout.send_hgfe[SEND_RIGHT] = 0x00007f7f; + + card->waveout.send_routing[ROUTE_PCM] = 0x03020100; // Regular pcm + card->waveout.send_routing2[ROUTE_PCM] = 0x07060504; + + card->waveout.send_routing[ROUTE_PT] = 0x3f3f3d3c; // Passthrough + card->waveout.send_routing2[ROUTE_PT] = 0x3f3f3f3f; + + card->waveout.send_routing[ROUTE_PCM1] = 0x03020100; // Spare + card->waveout.send_routing2[ROUTE_PCM1] = 0x07060404; + + } else { + /* mono voice */ + card->waveout.send_dcba[SEND_MONO] = 0x0000ffff; + + /* stereo voice */ + /* left */ + card->waveout.send_dcba[SEND_LEFT] = 0x000000ff; + /* right */ + card->waveout.send_dcba[SEND_RIGHT] = 0x0000ff00; + + card->waveout.send_routing[ROUTE_PCM] = 0x3210; // pcm + card->waveout.send_routing[ROUTE_PT] = 0x3210; // passthrough + card->waveout.send_routing[ROUTE_PCM1] = 0x7654; // /dev/dsp1 + } + + /* Assign default recording parameters */ + /* FIXME */ + if (card->is_aps) + card->wavein.recsrc = WAVERECORD_FX; + else + card->wavein.recsrc = WAVERECORD_AC97; + + card->wavein.fxwc = 0x0003; + return 0; +} + +static void emu10k1_audio_cleanup(struct emu10k1_card *card) +{ +} + +static int __devinit emu10k1_register_devices(struct emu10k1_card *card) +{ + card->audio_dev = register_sound_dsp(&emu10k1_audio_fops, -1); + if (card->audio_dev < 0) { + printk(KERN_ERR "emu10k1: cannot register first audio device!\n"); + goto err_dev; + } + + card->audio_dev1 = register_sound_dsp(&emu10k1_audio_fops, -1); + if (card->audio_dev1 < 0) { + printk(KERN_ERR "emu10k1: cannot register second audio device!\n"); + goto err_dev1; + } + + card->ac97->dev_mixer = register_sound_mixer(&emu10k1_mixer_fops, -1); + if (card->ac97->dev_mixer < 0) { + printk(KERN_ERR "emu10k1: cannot register mixer device\n"); + goto err_mixer; + } + + card->midi_dev = register_sound_midi(&emu10k1_midi_fops, -1); + if (card->midi_dev < 0) { + printk(KERN_ERR "emu10k1: cannot register midi device!\n"); + goto err_midi; + } + +#ifdef EMU10K1_SEQUENCER + card->seq_dev = sound_alloc_mididev(); + if (card->seq_dev == -1) + printk(KERN_WARNING "emu10k1: unable to register sequencer device!"); + else { + std_midi_synth.midi_dev = card->seq_dev; + midi_devs[card->seq_dev] = + (struct midi_operations *) + kmalloc(sizeof(struct midi_operations), GFP_KERNEL); + + if (midi_devs[card->seq_dev] == NULL) { + printk(KERN_ERR "emu10k1: unable to allocate memory!"); + sound_unload_mididev(card->seq_dev); + card->seq_dev = -1; + /* return without error */ + } else { + memcpy((char *)midi_devs[card->seq_dev], + (char *)&emu10k1_midi_operations, + sizeof(struct midi_operations)); + midi_devs[card->seq_dev]->devc = card; + sequencer_init(); + card->seq_mididev = NULL; + } + } +#endif + return 0; + +err_midi: + unregister_sound_mixer(card->ac97->dev_mixer); +err_mixer: + unregister_sound_dsp(card->audio_dev); +err_dev1: + unregister_sound_dsp(card->audio_dev); +err_dev: + return -ENODEV; +} + +static void emu10k1_unregister_devices(struct emu10k1_card *card) +{ +#ifdef EMU10K1_SEQUENCER + if (card->seq_dev > -1) { + kfree(midi_devs[card->seq_dev]); + midi_devs[card->seq_dev] = NULL; + sound_unload_mididev(card->seq_dev); + card->seq_dev = -1; + } +#endif + + unregister_sound_midi(card->midi_dev); + unregister_sound_mixer(card->ac97->dev_mixer); + unregister_sound_dsp(card->audio_dev1); + unregister_sound_dsp(card->audio_dev); +} + +static int emu10k1_info_proc (char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + struct emu10k1_card *card = data; + int len = 0; + + if (card == NULL) + return -ENODEV; + + len += sprintf (page + len, "Driver Version : %s\n", DRIVER_VERSION); + len += sprintf (page + len, "Card type : %s\n", card->is_aps ? "Aps" : (card->is_audigy ? "Audigy" : "Emu10k1")); + len += sprintf (page + len, "Revision : %d\n", card->chiprev); + len += sprintf (page + len, "Model : %#06x\n", card->model); + len += sprintf (page + len, "IO : %#06lx-%#06lx\n", card->iobase, card->iobase + card->length - 1); + len += sprintf (page + len, "IRQ : %d\n\n", card->irq); + + len += sprintf (page + len, "Registered /dev Entries:\n"); + len += sprintf (page + len, "/dev/dsp%d\n", card->audio_dev / 16); + len += sprintf (page + len, "/dev/dsp%d\n", card->audio_dev1 / 16); + len += sprintf (page + len, "/dev/mixer%d\n", card->ac97->dev_mixer / 16); + len += sprintf (page + len, "/dev/midi%d\n", card->midi_dev / 16); + +#ifdef EMU10K1_SEQUENCER + len += sprintf (page + len, "/dev/sequencer\n"); +#endif + + return len; +} + +static int __devinit emu10k1_proc_init(struct emu10k1_card *card) +{ + char s[48]; + + if (!proc_mkdir ("driver/emu10k1", NULL)) { + printk(KERN_ERR "emu10k1: unable to create proc directory driver/emu10k1\n"); + goto err_out; + } + + sprintf(s, "driver/emu10k1/%s", pci_name(card->pci_dev)); + if (!proc_mkdir (s, NULL)) { + printk(KERN_ERR "emu10k1: unable to create proc directory %s\n", s); + goto err_emu10k1_proc; + } + + sprintf(s, "driver/emu10k1/%s/info", pci_name(card->pci_dev)); + if (!create_proc_read_entry (s, 0, NULL, emu10k1_info_proc, card)) { + printk(KERN_ERR "emu10k1: unable to create proc entry %s\n", s); + goto err_dev_proc; + } + + if (!card->is_aps) { + sprintf(s, "driver/emu10k1/%s/ac97", pci_name(card->pci_dev)); + if (!create_proc_read_entry (s, 0, NULL, ac97_read_proc, card->ac97)) { + printk(KERN_ERR "emu10k1: unable to create proc entry %s\n", s); + goto err_proc_ac97; + } + } + + return 0; + +err_proc_ac97: + sprintf(s, "driver/emu10k1/%s/info", pci_name(card->pci_dev)); + remove_proc_entry(s, NULL); + +err_dev_proc: + sprintf(s, "driver/emu10k1/%s", pci_name(card->pci_dev)); + remove_proc_entry(s, NULL); + +err_emu10k1_proc: + remove_proc_entry("driver/emu10k1", NULL); + +err_out: + return -EIO; +} + +static void emu10k1_proc_cleanup(struct emu10k1_card *card) +{ + char s[48]; + + if (!card->is_aps) { + sprintf(s, "driver/emu10k1/%s/ac97", pci_name(card->pci_dev)); + remove_proc_entry(s, NULL); + } + + sprintf(s, "driver/emu10k1/%s/info", pci_name(card->pci_dev)); + remove_proc_entry(s, NULL); + + sprintf(s, "driver/emu10k1/%s", pci_name(card->pci_dev)); + remove_proc_entry(s, NULL); + + remove_proc_entry("driver/emu10k1", NULL); +} + +static int __devinit emu10k1_mixer_init(struct emu10k1_card *card) +{ + struct ac97_codec *codec = ac97_alloc_codec(); + + if(codec == NULL) + { + printk(KERN_ERR "emu10k1: cannot allocate mixer\n"); + return -EIO; + } + card->ac97 = codec; + card->ac97->private_data = card; + + if (!card->is_aps) { + card->ac97->id = 0; + card->ac97->codec_read = emu10k1_ac97_read; + card->ac97->codec_write = emu10k1_ac97_write; + + if (ac97_probe_codec (card->ac97) == 0) { + printk(KERN_ERR "emu10k1: unable to probe AC97 codec\n"); + goto err_out; + } + /* 5.1: Enable the additional AC97 Slots and unmute extra channels on AC97 codec */ + if (codec->codec_read(codec, AC97_EXTENDED_ID) & 0x0080){ + printk(KERN_INFO "emu10k1: SBLive! 5.1 card detected\n"); + sblive_writeptr(card, AC97SLOT, 0, AC97SLOT_CNTR | AC97SLOT_LFE); + codec->codec_write(codec, AC97_SURROUND_MASTER, 0x0); + } + + // Force 5bit: + //card->ac97->bit_resolution=5; + + /* these will store the original values and never be modified */ + card->ac97_supported_mixers = card->ac97->supported_mixers; + card->ac97_stereo_mixers = card->ac97->stereo_mixers; + } + + return 0; + + err_out: + ac97_release_codec(card->ac97); + return -EIO; +} + +static void emu10k1_mixer_cleanup(struct emu10k1_card *card) +{ + ac97_release_codec(card->ac97); +} + +static int __devinit emu10k1_midi_init(struct emu10k1_card *card) +{ + int ret; + + card->mpuout = kmalloc(sizeof(struct emu10k1_mpuout), GFP_KERNEL); + if (card->mpuout == NULL) { + printk(KERN_WARNING "emu10k1: Unable to allocate emu10k1_mpuout: out of memory\n"); + ret = -ENOMEM; + goto err_out1; + } + + memset(card->mpuout, 0, sizeof(struct emu10k1_mpuout)); + + card->mpuout->intr = 1; + card->mpuout->status = FLAGS_AVAILABLE; + card->mpuout->state = CARDMIDIOUT_STATE_DEFAULT; + + tasklet_init(&card->mpuout->tasklet, emu10k1_mpuout_bh, (unsigned long) card); + + spin_lock_init(&card->mpuout->lock); + + card->mpuin = kmalloc(sizeof(struct emu10k1_mpuin), GFP_KERNEL); + if (card->mpuin == NULL) { + printk(KERN_WARNING "emu10k1: Unable to allocate emu10k1_mpuin: out of memory\n"); + ret = -ENOMEM; + goto err_out2; + } + + memset(card->mpuin, 0, sizeof(struct emu10k1_mpuin)); + + card->mpuin->status = FLAGS_AVAILABLE; + + tasklet_init(&card->mpuin->tasklet, emu10k1_mpuin_bh, (unsigned long) card->mpuin); + + spin_lock_init(&card->mpuin->lock); + + /* Reset the MPU port */ + if (emu10k1_mpu_reset(card) < 0) { + ERROR(); + ret = -EIO; + goto err_out3; + } + + return 0; + +err_out3: + kfree(card->mpuin); +err_out2: + kfree(card->mpuout); +err_out1: + return ret; +} + +static void emu10k1_midi_cleanup(struct emu10k1_card *card) +{ + tasklet_kill(&card->mpuout->tasklet); + kfree(card->mpuout); + + tasklet_kill(&card->mpuin->tasklet); + kfree(card->mpuin); +} + +static void __devinit voice_init(struct emu10k1_card *card) +{ + int i; + + for (i = 0; i < NUM_G; i++) + card->voicetable[i] = VOICE_USAGE_FREE; +} + +static void __devinit timer_init(struct emu10k1_card *card) +{ + INIT_LIST_HEAD(&card->timers); + card->timer_delay = TIMER_STOPPED; + spin_lock_init(&card->timer_lock); +} + +static void __devinit addxmgr_init(struct emu10k1_card *card) +{ + u32 count; + + for (count = 0; count < MAXPAGES; count++) + card->emupagetable[count] = 0; + + /* Mark first page as used */ + /* This page is reserved by the driver */ + card->emupagetable[0] = 0x8001; + card->emupagetable[1] = MAXPAGES - 1; +} + +static void fx_cleanup(struct patch_manager *mgr) +{ + int i; + for(i = 0; i < mgr->current_pages; i++) + free_page((unsigned long) mgr->patch[i]); +} + +static int __devinit fx_init(struct emu10k1_card *card) +{ + struct patch_manager *mgr = &card->mgr; + struct dsp_patch *patch; + struct dsp_rpatch *rpatch; + s32 left, right; + int i; + u32 pc = 0; + u32 patch_n=0; + struct emu_efx_info_t emu_efx_info[2]= + {{ 20, 10, 0x400, 0x100, 0x20 }, + { 24, 12, 0x600, 0x400, 0x60 }, + }; + + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + mgr->ctrl_gpr[i][0] = -1; + mgr->ctrl_gpr[i][1] = -1; + } + + + if (card->is_audigy) + mgr->current_pages = (2 + PATCHES_PER_PAGE - 1) / PATCHES_PER_PAGE; + else + /* !! The number below must equal the number of patches, currently 11 !! */ + mgr->current_pages = (11 + PATCHES_PER_PAGE - 1) / PATCHES_PER_PAGE; + + for (i = 0; i < mgr->current_pages; i++) { + mgr->patch[i] = (void *)__get_free_page(GFP_KERNEL); + if (mgr->patch[i] == NULL) { + mgr->current_pages = i; + fx_cleanup(mgr); + return -ENOMEM; + } + memset(mgr->patch[i], 0, PAGE_SIZE); + } + + if (card->is_audigy) { + for (i = 0; i < 1024; i++) + OP(0xf, 0x0c0, 0x0c0, 0x0cf, 0x0c0); + + for (i = 0; i < 512 ; i++) + sblive_writeptr(card, A_GPR_BASE+i,0,0); + + pc=0; + + //Pcm input volume + OP(0, 0x402, 0x0c0, 0x406, 0x000); + OP(0, 0x403, 0x0c0, 0x407, 0x001); + + //CD-Digital input Volume + OP(0, 0x404, 0x0c0, 0x40d, 0x42); + OP(0, 0x405, 0x0c0, 0x40f, 0x43); + + // CD + PCM + OP(6, 0x400, 0x0c0, 0x402, 0x404); + OP(6, 0x401, 0x0c0, 0x403, 0x405); + + // Front Output + Master Volume + OP(0, 0x68, 0x0c0, 0x408, 0x400); + OP(0, 0x69, 0x0c0, 0x409, 0x401); + + // Add-in analog inputs for other speakers + OP(6, 0x400, 0x40, 0x400, 0xc0); + OP(6, 0x401, 0x41, 0x401, 0xc0); + + // Digital Front + Master Volume + OP(0, 0x60, 0x0c0, 0x408, 0x400); + OP(0, 0x61, 0x0c0, 0x409, 0x401); + + // Rear Output + Rear Volume + OP(0, 0x06e, 0x0c0, 0x419, 0x400); + OP(0, 0x06f, 0x0c0, 0x41a, 0x401); + + // Digital Rear Output + Rear Volume + OP(0, 0x066, 0x0c0, 0x419, 0x400); + OP(0, 0x067, 0x0c0, 0x41a, 0x401); + + // Audigy Drive, Headphone out + OP(6, 0x64, 0x0c0, 0x0c0, 0x400); + OP(6, 0x65, 0x0c0, 0x0c0, 0x401); + + // ac97 Recording + OP(6, 0x76, 0x0c0, 0x0c0, 0x40); + OP(6, 0x77, 0x0c0, 0x0c0, 0x41); + + // Center = sub = Left/2 + Right/2 + OP(0xe, 0x400, 0x401, 0xcd, 0x400); + + // center/sub Volume (master) + OP(0, 0x06a, 0x0c0, 0x408, 0x400); + OP(0, 0x06b, 0x0c0, 0x409, 0x400); + + // Digital center/sub Volume (master) + OP(0, 0x062, 0x0c0, 0x408, 0x400); + OP(0, 0x063, 0x0c0, 0x409, 0x400); + + ROUTING_PATCH_START(rpatch, "Routing"); + ROUTING_PATCH_END(rpatch); + + /* delimiter patch */ + patch = PATCH(mgr, patch_n); + patch->code_size = 0; + + + sblive_writeptr(card, 0x53, 0, 0); + } else { + for (i = 0; i < 512 ; i++) + OP(6, 0x40, 0x40, 0x40, 0x40); + + for (i = 0; i < 256; i++) + sblive_writeptr_tag(card, 0, + FXGPREGBASE + i, 0, + TANKMEMADDRREGBASE + i, 0, + TAGLIST_END); + + + pc = 0; + + //first free GPR = 0x11b + + + /* FX volume correction and Volume control*/ + INPUT_PATCH_START(patch, "Pcm L vol", 0x0, 0); + GET_OUTPUT_GPR(patch, 0x100, 0x0); + GET_CONTROL_GPR(patch, 0x106, "Vol", 0, 0x7fffffff); + GET_DYNAMIC_GPR(patch, 0x112); + + OP(4, 0x112, 0x40, PCM_IN_L, 0x44); //*4 + OP(0, 0x100, 0x040, 0x112, 0x106); //*vol + INPUT_PATCH_END(patch); + + + INPUT_PATCH_START(patch, "Pcm R vol", 0x1, 0); + GET_OUTPUT_GPR(patch, 0x101, 0x1); + GET_CONTROL_GPR(patch, 0x107, "Vol", 0, 0x7fffffff); + GET_DYNAMIC_GPR(patch, 0x112); + + OP(4, 0x112, 0x40, PCM_IN_R, 0x44); + OP(0, 0x101, 0x040, 0x112, 0x107); + + INPUT_PATCH_END(patch); + + + // CD-Digital In Volume control + INPUT_PATCH_START(patch, "CD-Digital Vol L", 0x12, 0); + GET_OUTPUT_GPR(patch, 0x10c, 0x12); + GET_CONTROL_GPR(patch, 0x10d, "Vol", 0, 0x7fffffff); + + OP(0, 0x10c, 0x040, SPDIF_CD_L, 0x10d); + INPUT_PATCH_END(patch); + + INPUT_PATCH_START(patch, "CD-Digital Vol R", 0x13, 0); + GET_OUTPUT_GPR(patch, 0x10e, 0x13); + GET_CONTROL_GPR(patch, 0x10f, "Vol", 0, 0x7fffffff); + + OP(0, 0x10e, 0x040, SPDIF_CD_R, 0x10f); + INPUT_PATCH_END(patch); + + //Volume Correction for Multi-channel Inputs + INPUT_PATCH_START(patch, "Multi-Channel Gain", 0x08, 0); + patch->input=patch->output=0x3F00; + + GET_OUTPUT_GPR(patch, 0x113, MULTI_FRONT_L); + GET_OUTPUT_GPR(patch, 0x114, MULTI_FRONT_R); + GET_OUTPUT_GPR(patch, 0x115, MULTI_REAR_L); + GET_OUTPUT_GPR(patch, 0x116, MULTI_REAR_R); + GET_OUTPUT_GPR(patch, 0x117, MULTI_CENTER); + GET_OUTPUT_GPR(patch, 0x118, MULTI_LFE); + + OP(4, 0x113, 0x40, MULTI_FRONT_L, 0x44); + OP(4, 0x114, 0x40, MULTI_FRONT_R, 0x44); + OP(4, 0x115, 0x40, MULTI_REAR_L, 0x44); + OP(4, 0x116, 0x40, MULTI_REAR_R, 0x44); + OP(4, 0x117, 0x40, MULTI_CENTER, 0x44); + OP(4, 0x118, 0x40, MULTI_LFE, 0x44); + + INPUT_PATCH_END(patch); + + + //Routing patch start + ROUTING_PATCH_START(rpatch, "Routing"); + GET_INPUT_GPR(rpatch, 0x100, 0x0); + GET_INPUT_GPR(rpatch, 0x101, 0x1); + GET_INPUT_GPR(rpatch, 0x10c, 0x12); + GET_INPUT_GPR(rpatch, 0x10e, 0x13); + GET_INPUT_GPR(rpatch, 0x113, MULTI_FRONT_L); + GET_INPUT_GPR(rpatch, 0x114, MULTI_FRONT_R); + GET_INPUT_GPR(rpatch, 0x115, MULTI_REAR_L); + GET_INPUT_GPR(rpatch, 0x116, MULTI_REAR_R); + GET_INPUT_GPR(rpatch, 0x117, MULTI_CENTER); + GET_INPUT_GPR(rpatch, 0x118, MULTI_LFE); + + GET_DYNAMIC_GPR(rpatch, 0x102); + GET_DYNAMIC_GPR(rpatch, 0x103); + + GET_OUTPUT_GPR(rpatch, 0x104, 0x8); + GET_OUTPUT_GPR(rpatch, 0x105, 0x9); + GET_OUTPUT_GPR(rpatch, 0x10a, 0x2); + GET_OUTPUT_GPR(rpatch, 0x10b, 0x3); + + + /* input buffer */ + OP(6, 0x102, AC97_IN_L, 0x40, 0x40); + OP(6, 0x103, AC97_IN_R, 0x40, 0x40); + + + /* Digital In + PCM + MULTI_FRONT-> AC97 out (front speakers)*/ + OP(6, AC97_FRONT_L, 0x100, 0x10c, 0x113); + + CONNECT(MULTI_FRONT_L, AC97_FRONT_L); + CONNECT(PCM_IN_L, AC97_FRONT_L); + CONNECT(SPDIF_CD_L, AC97_FRONT_L); + + OP(6, AC97_FRONT_R, 0x101, 0x10e, 0x114); + + CONNECT(MULTI_FRONT_R, AC97_FRONT_R); + CONNECT(PCM_IN_R, AC97_FRONT_R); + CONNECT(SPDIF_CD_R, AC97_FRONT_R); + + /* Digital In + PCM + AC97 In + PCM1 + MULTI_REAR --> Rear Channel */ + OP(6, 0x104, PCM1_IN_L, 0x100, 0x115); + OP(6, 0x104, 0x104, 0x10c, 0x102); + + CONNECT(MULTI_REAR_L, ANALOG_REAR_L); + CONNECT(AC97_IN_L, ANALOG_REAR_L); + CONNECT(PCM_IN_L, ANALOG_REAR_L); + CONNECT(SPDIF_CD_L, ANALOG_REAR_L); + CONNECT(PCM1_IN_L, ANALOG_REAR_L); + + OP(6, 0x105, PCM1_IN_R, 0x101, 0x116); + OP(6, 0x105, 0x105, 0x10e, 0x103); + + CONNECT(MULTI_REAR_R, ANALOG_REAR_R); + CONNECT(AC97_IN_R, ANALOG_REAR_R); + CONNECT(PCM_IN_R, ANALOG_REAR_R); + CONNECT(SPDIF_CD_R, ANALOG_REAR_R); + CONNECT(PCM1_IN_R, ANALOG_REAR_R); + + /* Digital In + PCM + AC97 In + MULTI_FRONT --> Digital out */ + OP(6, 0x10b, 0x100, 0x102, 0x10c); + OP(6, 0x10b, 0x10b, 0x113, 0x40); + + CONNECT(MULTI_FRONT_L, DIGITAL_OUT_L); + CONNECT(PCM_IN_L, DIGITAL_OUT_L); + CONNECT(AC97_IN_L, DIGITAL_OUT_L); + CONNECT(SPDIF_CD_L, DIGITAL_OUT_L); + + OP(6, 0x10a, 0x101, 0x103, 0x10e); + OP(6, 0x10b, 0x10b, 0x114, 0x40); + + CONNECT(MULTI_FRONT_R, DIGITAL_OUT_R); + CONNECT(PCM_IN_R, DIGITAL_OUT_R); + CONNECT(AC97_IN_R, DIGITAL_OUT_R); + CONNECT(SPDIF_CD_R, DIGITAL_OUT_R); + + /* AC97 In --> ADC Recording Buffer */ + OP(6, ADC_REC_L, 0x102, 0x40, 0x40); + + CONNECT(AC97_IN_L, ADC_REC_L); + + OP(6, ADC_REC_R, 0x103, 0x40, 0x40); + + CONNECT(AC97_IN_R, ADC_REC_R); + + + /* fx12:Analog-Center */ + OP(6, ANALOG_CENTER, 0x117, 0x40, 0x40); + CONNECT(MULTI_CENTER, ANALOG_CENTER); + + /* fx11:Analog-LFE */ + OP(6, ANALOG_LFE, 0x118, 0x40, 0x40); + CONNECT(MULTI_LFE, ANALOG_LFE); + + /* fx12:Digital-Center */ + OP(6, DIGITAL_CENTER, 0x117, 0x40, 0x40); + CONNECT(MULTI_CENTER, DIGITAL_CENTER); + + /* fx11:Analog-LFE */ + OP(6, DIGITAL_LFE, 0x118, 0x40, 0x40); + CONNECT(MULTI_LFE, DIGITAL_LFE); + + ROUTING_PATCH_END(rpatch); + + + // Rear volume control + OUTPUT_PATCH_START(patch, "Vol Rear L", 0x8, 0); + GET_INPUT_GPR(patch, 0x104, 0x8); + GET_CONTROL_GPR(patch, 0x119, "Vol", 0, 0x7fffffff); + + OP(0, ANALOG_REAR_L, 0x040, 0x104, 0x119); + OUTPUT_PATCH_END(patch); + + OUTPUT_PATCH_START(patch, "Vol Rear R", 0x9, 0); + GET_INPUT_GPR(patch, 0x105, 0x9); + GET_CONTROL_GPR(patch, 0x11a, "Vol", 0, 0x7fffffff); + + OP(0, ANALOG_REAR_R, 0x040, 0x105, 0x11a); + OUTPUT_PATCH_END(patch); + + + //Master volume control on front-digital + OUTPUT_PATCH_START(patch, "Vol Master L", 0x2, 1); + GET_INPUT_GPR(patch, 0x10a, 0x2); + GET_CONTROL_GPR(patch, 0x108, "Vol", 0, 0x7fffffff); + + OP(0, DIGITAL_OUT_L, 0x040, 0x10a, 0x108); + OUTPUT_PATCH_END(patch); + + + OUTPUT_PATCH_START(patch, "Vol Master R", 0x3, 1); + GET_INPUT_GPR(patch, 0x10b, 0x3); + GET_CONTROL_GPR(patch, 0x109, "Vol", 0, 0x7fffffff); + + OP(0, DIGITAL_OUT_R, 0x040, 0x10b, 0x109); + OUTPUT_PATCH_END(patch); + + + /* delimiter patch */ + patch = PATCH(mgr, patch_n); + patch->code_size = 0; + + + sblive_writeptr(card, DBG, 0, 0); + } + + spin_lock_init(&mgr->lock); + + // Set up Volume controls, try to keep this the same for both Audigy and Live + + //Master volume + mgr->ctrl_gpr[SOUND_MIXER_VOLUME][0] = 8; + mgr->ctrl_gpr[SOUND_MIXER_VOLUME][1] = 9; + + left = card->ac97->mixer_state[SOUND_MIXER_VOLUME] & 0xff; + right = (card->ac97->mixer_state[SOUND_MIXER_VOLUME] >> 8) & 0xff; + + emu10k1_set_volume_gpr(card, 8, left, 1 << card->ac97->bit_resolution); + emu10k1_set_volume_gpr(card, 9, right, 1 << card->ac97->bit_resolution); + + //Rear volume + mgr->ctrl_gpr[ SOUND_MIXER_OGAIN ][0] = 0x19; + mgr->ctrl_gpr[ SOUND_MIXER_OGAIN ][1] = 0x1a; + + left = right = 67; + card->ac97->mixer_state[SOUND_MIXER_OGAIN] = (right << 8) | left; + + card->ac97->supported_mixers |= SOUND_MASK_OGAIN; + card->ac97->stereo_mixers |= SOUND_MASK_OGAIN; + + emu10k1_set_volume_gpr(card, 0x19, left, VOL_5BIT); + emu10k1_set_volume_gpr(card, 0x1a, right, VOL_5BIT); + + //PCM Volume + mgr->ctrl_gpr[SOUND_MIXER_PCM][0] = 6; + mgr->ctrl_gpr[SOUND_MIXER_PCM][1] = 7; + + left = card->ac97->mixer_state[SOUND_MIXER_PCM] & 0xff; + right = (card->ac97->mixer_state[SOUND_MIXER_PCM] >> 8) & 0xff; + + emu10k1_set_volume_gpr(card, 6, left, VOL_5BIT); + emu10k1_set_volume_gpr(card, 7, right, VOL_5BIT); + + //CD-Digital Volume + mgr->ctrl_gpr[SOUND_MIXER_DIGITAL1][0] = 0xd; + mgr->ctrl_gpr[SOUND_MIXER_DIGITAL1][1] = 0xf; + + left = right = 67; + card->ac97->mixer_state[SOUND_MIXER_DIGITAL1] = (right << 8) | left; + + card->ac97->supported_mixers |= SOUND_MASK_DIGITAL1; + card->ac97->stereo_mixers |= SOUND_MASK_DIGITAL1; + + emu10k1_set_volume_gpr(card, 0xd, left, VOL_5BIT); + emu10k1_set_volume_gpr(card, 0xf, right, VOL_5BIT); + + + //hard wire the ac97's pcm, pcm volume is done above using dsp code. + if (card->is_audigy) + //for Audigy, we mute it and use the philips 6 channel DAC instead + emu10k1_ac97_write(card->ac97, 0x18, 0x8000); + else + //For the Live we hardwire it to full volume + emu10k1_ac97_write(card->ac97, 0x18, 0x0); + + //remove it from the ac97_codec's control + card->ac97_supported_mixers &= ~SOUND_MASK_PCM; + card->ac97_stereo_mixers &= ~SOUND_MASK_PCM; + + //set Igain to 0dB by default, maybe consider hardwiring it here. + emu10k1_ac97_write(card->ac97, AC97_RECORD_GAIN, 0x0000); + card->ac97->mixer_state[SOUND_MIXER_IGAIN] = 0x101; + + return 0; +} + +static int __devinit hw_init(struct emu10k1_card *card) +{ + int nCh; + u32 pagecount; /* tmp */ + int ret; + + /* Disable audio and lock cache */ + emu10k1_writefn0(card, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE); + + /* Reset recording buffers */ + sblive_writeptr_tag(card, 0, + MICBS, ADCBS_BUFSIZE_NONE, + MICBA, 0, + FXBS, ADCBS_BUFSIZE_NONE, + FXBA, 0, + ADCBS, ADCBS_BUFSIZE_NONE, + ADCBA, 0, + TAGLIST_END); + + /* Disable channel interrupt */ + emu10k1_writefn0(card, INTE, 0); + sblive_writeptr_tag(card, 0, + CLIEL, 0, + CLIEH, 0, + SOLEL, 0, + SOLEH, 0, + TAGLIST_END); + + if (card->is_audigy) { + sblive_writeptr_tag(card,0, + 0x5e,0xf00, + 0x5f,0x3, + TAGLIST_END); + } + + /* Init envelope engine */ + for (nCh = 0; nCh < NUM_G; nCh++) { + sblive_writeptr_tag(card, nCh, + DCYSUSV, 0, + IP, 0, + VTFT, 0xffff, + CVCF, 0xffff, + PTRX, 0, + //CPF, 0, + CCR, 0, + + PSST, 0, + DSL, 0x10, + CCCA, 0, + Z1, 0, + Z2, 0, + FXRT, 0xd01c0000, + + ATKHLDM, 0, + DCYSUSM, 0, + IFATN, 0xffff, + PEFE, 0, + FMMOD, 0, + TREMFRQ, 24, /* 1 Hz */ + FM2FRQ2, 24, /* 1 Hz */ + TEMPENV, 0, + + /*** These are last so OFF prevents writing ***/ + LFOVAL2, 0, + LFOVAL1, 0, + ATKHLDV, 0, + ENVVOL, 0, + ENVVAL, 0, + TAGLIST_END); + sblive_writeptr(card, CPF, nCh, 0); + /* + Audigy FXRT initialization + reversed eng'd, may not be accurate. + */ + if (card->is_audigy) { + sblive_writeptr_tag(card,nCh, + 0x4c,0x0, + 0x4d,0x0, + 0x4e,0x0, + 0x4f,0x0, + A_FXRT1, 0x3f3f3f3f, + A_FXRT2, 0x3f3f3f3f, + A_SENDAMOUNTS, 0, + TAGLIST_END); + } + } + + + /* + ** Init to 0x02109204 : + ** Clock accuracy = 0 (1000ppm) + ** Sample Rate = 2 (48kHz) + ** Audio Channel = 1 (Left of 2) + ** Source Number = 0 (Unspecified) + ** Generation Status = 1 (Original for Cat Code 12) + ** Cat Code = 12 (Digital Signal Mixer) + ** Mode = 0 (Mode 0) + ** Emphasis = 0 (None) + ** CP = 1 (Copyright unasserted) + ** AN = 0 (Digital audio) + ** P = 0 (Consumer) + */ + + sblive_writeptr_tag(card, 0, + + /* SPDIF0 */ + SPCS0, (SPCS_CLKACCY_1000PPM | 0x002000000 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | SPCS_GENERATIONSTATUS | 0x00001200 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT), + + /* SPDIF1 */ + SPCS1, (SPCS_CLKACCY_1000PPM | 0x002000000 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | SPCS_GENERATIONSTATUS | 0x00001200 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT), + + /* SPDIF2 & SPDIF3 */ + SPCS2, (SPCS_CLKACCY_1000PPM | 0x002000000 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | SPCS_GENERATIONSTATUS | 0x00001200 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT), + + TAGLIST_END); + + if (card->is_audigy && (card->chiprev == 4)) { + /* Hacks for Alice3 to work independent of haP16V driver */ + u32 tmp; + + //Setup SRCMulti_I2S SamplingRate + tmp = sblive_readptr(card, A_SPDIF_SAMPLERATE, 0); + tmp &= 0xfffff1ff; + tmp |= (0x2<<9); + sblive_writeptr(card, A_SPDIF_SAMPLERATE, 0, tmp); + + /* Setup SRCSel (Enable Spdif,I2S SRCMulti) */ + emu10k1_writefn0(card, 0x20, 0x600000); + emu10k1_writefn0(card, 0x24, 0x14); + + /* Setup SRCMulti Input Audio Enable */ + emu10k1_writefn0(card, 0x20, 0x6E0000); + emu10k1_writefn0(card, 0x24, 0xFF00FF00); + } + + ret = fx_init(card); /* initialize effects engine */ + if (ret < 0) + return ret; + + card->tankmem.size = 0; + + card->virtualpagetable.size = MAXPAGES * sizeof(u32); + + card->virtualpagetable.addr = pci_alloc_consistent(card->pci_dev, card->virtualpagetable.size, &card->virtualpagetable.dma_handle); + if (card->virtualpagetable.addr == NULL) { + ERROR(); + ret = -ENOMEM; + goto err0; + } + + card->silentpage.size = EMUPAGESIZE; + + card->silentpage.addr = pci_alloc_consistent(card->pci_dev, card->silentpage.size, &card->silentpage.dma_handle); + if (card->silentpage.addr == NULL) { + ERROR(); + ret = -ENOMEM; + goto err1; + } + + for (pagecount = 0; pagecount < MAXPAGES; pagecount++) + ((u32 *) card->virtualpagetable.addr)[pagecount] = cpu_to_le32(((u32) card->silentpage.dma_handle * 2) | pagecount); + + /* Init page table & tank memory base register */ + sblive_writeptr_tag(card, 0, + PTB, (u32) card->virtualpagetable.dma_handle, + TCB, 0, + TCBS, 0, + TAGLIST_END); + + for (nCh = 0; nCh < NUM_G; nCh++) { + sblive_writeptr_tag(card, nCh, + MAPA, MAP_PTI_MASK | ((u32) card->silentpage.dma_handle * 2), + MAPB, MAP_PTI_MASK | ((u32) card->silentpage.dma_handle * 2), + TAGLIST_END); + } + + /* Hokay, now enable the AUD bit */ + /* Enable Audio = 1 */ + /* Mute Disable Audio = 0 */ + /* Lock Tank Memory = 1 */ + /* Lock Sound Memory = 0 */ + /* Auto Mute = 1 */ + if (card->is_audigy) { + if (card->chiprev == 4) + emu10k1_writefn0(card, HCFG, HCFG_AUDIOENABLE | HCFG_AC3ENABLE_CDSPDIF | HCFG_AC3ENABLE_GPSPDIF | HCFG_AUTOMUTE | HCFG_JOYENABLE); + else + emu10k1_writefn0(card, HCFG, HCFG_AUDIOENABLE | HCFG_AUTOMUTE | HCFG_JOYENABLE); + } else { + if (card->model == 0x20 || card->model == 0xc400 || + (card->model == 0x21 && card->chiprev < 6)) + emu10k1_writefn0(card, HCFG, HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE_MASK | HCFG_AUTOMUTE); + else + emu10k1_writefn0(card, HCFG, HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE_MASK | HCFG_AUTOMUTE | HCFG_JOYENABLE); + } + /* Enable Vol_Ctrl irqs */ + emu10k1_irq_enable(card, INTE_VOLINCRENABLE | INTE_VOLDECRENABLE | INTE_MUTEENABLE | INTE_FXDSPENABLE); + + if (card->is_audigy && (card->chiprev == 4)) { + /* Unmute Analog now. Set GPO6 to 1 for Apollo. + * This has to be done after init ALice3 I2SOut beyond 48KHz. + * So, sequence is important. */ + u32 tmp = emu10k1_readfn0(card, A_IOCFG); + tmp |= 0x0040; + emu10k1_writefn0(card, A_IOCFG, tmp); + } + + /* FIXME: TOSLink detection */ + card->has_toslink = 0; + + /* Initialize digital passthrough variables */ + card->pt.pos_gpr = card->pt.intr_gpr = card->pt.enable_gpr = -1; + card->pt.selected = 0; + card->pt.state = PT_STATE_INACTIVE; + card->pt.spcs_to_use = 0x01; + card->pt.patch_name = "AC3pass"; + card->pt.intr_gpr_name = "count"; + card->pt.enable_gpr_name = "enable"; + card->pt.pos_gpr_name = "ptr"; + spin_lock_init(&card->pt.lock); + init_waitqueue_head(&card->pt.wait); + +/* tmp = sblive_readfn0(card, HCFG); + if (tmp & (HCFG_GPINPUT0 | HCFG_GPINPUT1)) { + sblive_writefn0(card, HCFG, tmp | 0x800); + + udelay(512); + + if (tmp != (sblive_readfn0(card, HCFG) & ~0x800)) { + card->has_toslink = 1; + sblive_writefn0(card, HCFG, tmp); + } + } +*/ + return 0; + + err1: + pci_free_consistent(card->pci_dev, card->virtualpagetable.size, card->virtualpagetable.addr, card->virtualpagetable.dma_handle); + err0: + fx_cleanup(&card->mgr); + + return ret; +} + +static int __devinit emu10k1_init(struct emu10k1_card *card) +{ + /* Init Card */ + if (hw_init(card) < 0) + return -1; + + voice_init(card); + timer_init(card); + addxmgr_init(card); + + DPD(2, " hw control register -> %#x\n", emu10k1_readfn0(card, HCFG)); + + return 0; +} + +static void emu10k1_cleanup(struct emu10k1_card *card) +{ + int ch; + + emu10k1_writefn0(card, INTE, 0); + + /** Shutdown the chip **/ + for (ch = 0; ch < NUM_G; ch++) + sblive_writeptr(card, DCYSUSV, ch, 0); + + for (ch = 0; ch < NUM_G; ch++) { + sblive_writeptr_tag(card, ch, + VTFT, 0, + CVCF, 0, + PTRX, 0, + //CPF, 0, + TAGLIST_END); + sblive_writeptr(card, CPF, ch, 0); + } + + /* Disable audio and lock cache */ + emu10k1_writefn0(card, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE); + + sblive_writeptr_tag(card, 0, + PTB, 0, + + /* Reset recording buffers */ + MICBS, ADCBS_BUFSIZE_NONE, + MICBA, 0, + FXBS, ADCBS_BUFSIZE_NONE, + FXBA, 0, + FXWC, 0, + ADCBS, ADCBS_BUFSIZE_NONE, + ADCBA, 0, + TCBS, 0, + TCB, 0, + DBG, 0x8000, + + /* Disable channel interrupt */ + CLIEL, 0, + CLIEH, 0, + SOLEL, 0, + SOLEH, 0, + TAGLIST_END); + + if (card->is_audigy) + sblive_writeptr(card, 0, A_DBG, A_DBG_SINGLE_STEP); + + pci_free_consistent(card->pci_dev, card->virtualpagetable.size, card->virtualpagetable.addr, card->virtualpagetable.dma_handle); + pci_free_consistent(card->pci_dev, card->silentpage.size, card->silentpage.addr, card->silentpage.dma_handle); + + if(card->tankmem.size != 0) + pci_free_consistent(card->pci_dev, card->tankmem.size, card->tankmem.addr, card->tankmem.dma_handle); + + /* release patch storage memory */ + fx_cleanup(&card->mgr); +} + +/* Driver initialization routine */ +static int __devinit emu10k1_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id) +{ + struct emu10k1_card *card; + u32 subsysvid; + int ret; + + if (pci_set_dma_mask(pci_dev, EMU10K1_DMA_MASK)) { + printk(KERN_ERR "emu10k1: architecture does not support 29bit PCI busmaster DMA\n"); + return -ENODEV; + } + + if (pci_enable_device(pci_dev)) + return -EIO; + + pci_set_master(pci_dev); + + if ((card = kmalloc(sizeof(struct emu10k1_card), GFP_KERNEL)) == NULL) { + printk(KERN_ERR "emu10k1: out of memory\n"); + return -ENOMEM; + } + memset(card, 0, sizeof(struct emu10k1_card)); + + card->iobase = pci_resource_start(pci_dev, 0); + card->length = pci_resource_len(pci_dev, 0); + + if (request_region(card->iobase, card->length, card_names[pci_id->driver_data]) == NULL) { + printk(KERN_ERR "emu10k1: IO space in use\n"); + ret = -EBUSY; + goto err_region; + } + + pci_set_drvdata(pci_dev, card); + + card->irq = pci_dev->irq; + card->pci_dev = pci_dev; + + /* Reserve IRQ Line */ + if (request_irq(card->irq, emu10k1_interrupt, SA_SHIRQ, card_names[pci_id->driver_data], card)) { + printk(KERN_ERR "emu10k1: IRQ in use\n"); + ret = -EBUSY; + goto err_irq; + } + + pci_read_config_byte(pci_dev, PCI_REVISION_ID, &card->chiprev); + pci_read_config_word(pci_dev, PCI_SUBSYSTEM_ID, &card->model); + + printk(KERN_INFO "emu10k1: %s rev %d model %#04x found, IO at %#04lx-%#04lx, IRQ %d\n", + card_names[pci_id->driver_data], card->chiprev, card->model, card->iobase, + card->iobase + card->length - 1, card->irq); + + if (pci_id->device == PCI_DEVICE_ID_CREATIVE_AUDIGY) + card->is_audigy = 1; + + pci_read_config_dword(pci_dev, PCI_SUBSYSTEM_VENDOR_ID, &subsysvid); + card->is_aps = (subsysvid == EMU_APS_SUBID); + + spin_lock_init(&card->lock); + init_MUTEX(&card->open_sem); + card->open_mode = 0; + init_waitqueue_head(&card->open_wait); + + ret = emu10k1_audio_init(card); + if (ret < 0) { + printk(KERN_ERR "emu10k1: cannot initialize audio devices\n"); + goto err_audio; + } + + ret = emu10k1_mixer_init(card); + if (ret < 0) { + printk(KERN_ERR "emu10k1: cannot initialize AC97 codec\n"); + goto err_mixer; + } + + ret = emu10k1_midi_init(card); + if (ret < 0) { + printk(KERN_ERR "emu10k1: cannot register midi device\n"); + goto err_midi; + } + + ret = emu10k1_init(card); + if (ret < 0) { + printk(KERN_ERR "emu10k1: cannot initialize device\n"); + goto err_emu10k1_init; + } + + if (card->is_aps) + emu10k1_ecard_init(card); + + ret = emu10k1_register_devices(card); + if (ret < 0) + goto err_register; + + /* proc entries must be created after registering devices, as + * emu10k1_info_proc prints card->audio_dev &co. */ + ret = emu10k1_proc_init(card); + if (ret < 0) { + printk(KERN_ERR "emu10k1: cannot initialize proc directory\n"); + goto err_proc; + } + + list_add(&card->list, &emu10k1_devs); + + return 0; + +err_proc: + emu10k1_unregister_devices(card); + +err_register: + emu10k1_cleanup(card); + +err_emu10k1_init: + emu10k1_midi_cleanup(card); + +err_midi: + emu10k1_mixer_cleanup(card); + +err_mixer: + emu10k1_audio_cleanup(card); + +err_audio: + free_irq(card->irq, card); + +err_irq: + release_region(card->iobase, card->length); + pci_set_drvdata(pci_dev, NULL); + +err_region: + kfree(card); + + return ret; +} + +static void __devexit emu10k1_remove(struct pci_dev *pci_dev) +{ + struct emu10k1_card *card = pci_get_drvdata(pci_dev); + + list_del(&card->list); + + emu10k1_unregister_devices(card); + emu10k1_cleanup(card); + emu10k1_midi_cleanup(card); + emu10k1_mixer_cleanup(card); + emu10k1_proc_cleanup(card); + emu10k1_audio_cleanup(card); + free_irq(card->irq, card); + release_region(card->iobase, card->length); + kfree(card); + pci_set_drvdata(pci_dev, NULL); +} + +MODULE_AUTHOR("Bertrand Lee, Cai Ying. (Email to: emu10k1-devel@lists.sourceforge.net)"); +MODULE_DESCRIPTION("Creative EMU10K1 PCI Audio Driver v" DRIVER_VERSION "\nCopyright (C) 1999 Creative Technology Ltd."); +MODULE_LICENSE("GPL"); + +static struct pci_driver emu10k1_pci_driver = { + .name = "emu10k1", + .id_table = emu10k1_pci_tbl, + .probe = emu10k1_probe, + .remove = __devexit_p(emu10k1_remove), +}; + +static int __init emu10k1_init_module(void) +{ + printk(KERN_INFO "Creative EMU10K1 PCI Audio Driver, version " DRIVER_VERSION ", " __TIME__ " " __DATE__ "\n"); + + return pci_module_init(&emu10k1_pci_driver); +} + +static void __exit emu10k1_cleanup_module(void) +{ + pci_unregister_driver(&emu10k1_pci_driver); + + return; +} + +module_init(emu10k1_init_module); +module_exit(emu10k1_cleanup_module); + +#ifdef EMU10K1_SEQUENCER + +/* in midi.c */ +extern int emu10k1_seq_midi_open(int dev, int mode, + void (*input)(int dev, unsigned char midi_byte), + void (*output)(int dev)); +extern void emu10k1_seq_midi_close(int dev); +extern int emu10k1_seq_midi_out(int dev, unsigned char midi_byte); +extern int emu10k1_seq_midi_start_read(int dev); +extern int emu10k1_seq_midi_end_read(int dev); +extern void emu10k1_seq_midi_kick(int dev); +extern int emu10k1_seq_midi_buffer_status(int dev); + +static struct midi_operations emu10k1_midi_operations = +{ + THIS_MODULE, + {"EMU10K1 MIDI", 0, 0, SNDCARD_EMU10K1}, + &std_midi_synth, + {0}, + emu10k1_seq_midi_open, + emu10k1_seq_midi_close, + NULL, + emu10k1_seq_midi_out, + emu10k1_seq_midi_start_read, + emu10k1_seq_midi_end_read, + emu10k1_seq_midi_kick, + NULL, + emu10k1_seq_midi_buffer_status, + NULL +}; + +#endif diff --git a/sound/oss/emu10k1/midi.c b/sound/oss/emu10k1/midi.c new file mode 100644 index 000000000000..33dea3d56c1e --- /dev/null +++ b/sound/oss/emu10k1/midi.c @@ -0,0 +1,613 @@ +/* + ********************************************************************** + * midi.c - /dev/midi interface for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 +#include +#include +#include +#include +#include + +#include "hwaccess.h" +#include "cardmo.h" +#include "cardmi.h" +#include "midi.h" + +#ifdef EMU10K1_SEQUENCER +#include "../sound_config.h" +#endif + +static DEFINE_SPINLOCK(midi_spinlock __attribute((unused))); + +static void init_midi_hdr(struct midi_hdr *midihdr) +{ + midihdr->bufferlength = MIDIIN_BUFLEN; + midihdr->bytesrecorded = 0; + midihdr->flags = 0; +} + +static int midiin_add_buffer(struct emu10k1_mididevice *midi_dev, struct midi_hdr **midihdrptr) +{ + struct midi_hdr *midihdr; + + if ((midihdr = (struct midi_hdr *) kmalloc(sizeof(struct midi_hdr), GFP_KERNEL)) == NULL) { + ERROR(); + return -EINVAL; + } + + init_midi_hdr(midihdr); + + if ((midihdr->data = (u8 *) kmalloc(MIDIIN_BUFLEN, GFP_KERNEL)) == NULL) { + ERROR(); + kfree(midihdr); + return -1; + } + + if (emu10k1_mpuin_add_buffer(midi_dev->card->mpuin, midihdr) < 0) { + ERROR(); + kfree(midihdr->data); + kfree(midihdr); + return -1; + } + + *midihdrptr = midihdr; + list_add_tail(&midihdr->list, &midi_dev->mid_hdrs); + + return 0; +} + +static int emu10k1_midi_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct emu10k1_card *card = NULL; + struct emu10k1_mididevice *midi_dev; + struct list_head *entry; + + DPF(2, "emu10k1_midi_open()\n"); + + /* Check for correct device to open */ + list_for_each(entry, &emu10k1_devs) { + card = list_entry(entry, struct emu10k1_card, list); + + if (card->midi_dev == minor) + goto match; + } + + return -ENODEV; + +match: +#ifdef EMU10K1_SEQUENCER + if (card->seq_mididev) /* card is opened by sequencer */ + return -EBUSY; +#endif + + /* Wait for device to become free */ + down(&card->open_sem); + while (card->open_mode & (file->f_mode << FMODE_MIDI_SHIFT)) { + if (file->f_flags & O_NONBLOCK) { + up(&card->open_sem); + return -EBUSY; + } + + up(&card->open_sem); + interruptible_sleep_on(&card->open_wait); + + if (signal_pending(current)) { + return -ERESTARTSYS; + } + + down(&card->open_sem); + } + + if ((midi_dev = (struct emu10k1_mididevice *) kmalloc(sizeof(*midi_dev), GFP_KERNEL)) == NULL) + return -EINVAL; + + midi_dev->card = card; + midi_dev->mistate = MIDIIN_STATE_STOPPED; + init_waitqueue_head(&midi_dev->oWait); + init_waitqueue_head(&midi_dev->iWait); + midi_dev->ird = 0; + midi_dev->iwr = 0; + midi_dev->icnt = 0; + INIT_LIST_HEAD(&midi_dev->mid_hdrs); + + if (file->f_mode & FMODE_READ) { + struct midi_openinfo dsCardMidiOpenInfo; + struct midi_hdr *midihdr1; + struct midi_hdr *midihdr2; + + dsCardMidiOpenInfo.refdata = (unsigned long) midi_dev; + + if (emu10k1_mpuin_open(card, &dsCardMidiOpenInfo) < 0) { + ERROR(); + kfree(midi_dev); + return -ENODEV; + } + + /* Add two buffers to receive sysex buffer */ + if (midiin_add_buffer(midi_dev, &midihdr1) < 0) { + kfree(midi_dev); + return -ENODEV; + } + + if (midiin_add_buffer(midi_dev, &midihdr2) < 0) { + list_del(&midihdr1->list); + kfree(midihdr1->data); + kfree(midihdr1); + kfree(midi_dev); + return -ENODEV; + } + } + + if (file->f_mode & FMODE_WRITE) { + struct midi_openinfo dsCardMidiOpenInfo; + + dsCardMidiOpenInfo.refdata = (unsigned long) midi_dev; + + if (emu10k1_mpuout_open(card, &dsCardMidiOpenInfo) < 0) { + ERROR(); + kfree(midi_dev); + return -ENODEV; + } + } + + file->private_data = (void *) midi_dev; + + card->open_mode |= (file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ | FMODE_MIDI_WRITE); + + up(&card->open_sem); + + return nonseekable_open(inode, file); +} + +static int emu10k1_midi_release(struct inode *inode, struct file *file) +{ + struct emu10k1_mididevice *midi_dev = (struct emu10k1_mididevice *) file->private_data; + struct emu10k1_card *card; + + lock_kernel(); + + card = midi_dev->card; + DPF(2, "emu10k1_midi_release()\n"); + + if (file->f_mode & FMODE_WRITE) { + if (!(file->f_flags & O_NONBLOCK)) { + + while (!signal_pending(current) && (card->mpuout->firstmidiq != NULL)) { + DPF(4, "Cannot close - buffers not empty\n"); + + interruptible_sleep_on(&midi_dev->oWait); + + } + } + + emu10k1_mpuout_close(card); + } + + if (file->f_mode & FMODE_READ) { + struct midi_hdr *midihdr; + + if (midi_dev->mistate == MIDIIN_STATE_STARTED) { + emu10k1_mpuin_stop(card); + midi_dev->mistate = MIDIIN_STATE_STOPPED; + } + + emu10k1_mpuin_reset(card); + emu10k1_mpuin_close(card); + + while (!list_empty(&midi_dev->mid_hdrs)) { + midihdr = list_entry(midi_dev->mid_hdrs.next, struct midi_hdr, list); + + list_del(midi_dev->mid_hdrs.next); + kfree(midihdr->data); + kfree(midihdr); + } + } + + kfree(midi_dev); + + down(&card->open_sem); + card->open_mode &= ~((file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ | FMODE_MIDI_WRITE)); + up(&card->open_sem); + wake_up_interruptible(&card->open_wait); + + unlock_kernel(); + + return 0; +} + +static ssize_t emu10k1_midi_read(struct file *file, char __user *buffer, size_t count, loff_t * pos) +{ + struct emu10k1_mididevice *midi_dev = (struct emu10k1_mididevice *) file->private_data; + ssize_t ret = 0; + u16 cnt; + unsigned long flags; + + DPD(4, "emu10k1_midi_read(), count %#x\n", (u32) count); + + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + + if (midi_dev->mistate == MIDIIN_STATE_STOPPED) { + if (emu10k1_mpuin_start(midi_dev->card) < 0) { + ERROR(); + return -EINVAL; + } + + midi_dev->mistate = MIDIIN_STATE_STARTED; + } + + while (count > 0) { + cnt = MIDIIN_BUFLEN - midi_dev->ird; + + spin_lock_irqsave(&midi_spinlock, flags); + + if (midi_dev->icnt < cnt) + cnt = midi_dev->icnt; + + spin_unlock_irqrestore(&midi_spinlock, flags); + + if (cnt > count) + cnt = count; + + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) + return ret ? ret : -EAGAIN; + DPF(2, " Go to sleep...\n"); + + interruptible_sleep_on(&midi_dev->iWait); + + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + + continue; + } + + if (copy_to_user(buffer, midi_dev->iBuf + midi_dev->ird, cnt)) { + ERROR(); + return ret ? ret : -EFAULT; + } + + midi_dev->ird += cnt; + midi_dev->ird %= MIDIIN_BUFLEN; + + spin_lock_irqsave(&midi_spinlock, flags); + + midi_dev->icnt -= cnt; + + spin_unlock_irqrestore(&midi_spinlock, flags); + + count -= cnt; + buffer += cnt; + ret += cnt; + + if (midi_dev->icnt == 0) + break; + } + + return ret; +} + +static ssize_t emu10k1_midi_write(struct file *file, const char __user *buffer, size_t count, loff_t * pos) +{ + struct emu10k1_mididevice *midi_dev = (struct emu10k1_mididevice *) file->private_data; + struct midi_hdr *midihdr; + unsigned long flags; + + DPD(4, "emu10k1_midi_write(), count=%#x\n", (u32) count); + + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + + if ((midihdr = (struct midi_hdr *) kmalloc(sizeof(struct midi_hdr), GFP_KERNEL)) == NULL) + return -EINVAL; + + midihdr->bufferlength = count; + midihdr->bytesrecorded = 0; + midihdr->flags = 0; + + if ((midihdr->data = (u8 *) kmalloc(count, GFP_KERNEL)) == NULL) { + ERROR(); + kfree(midihdr); + return -EINVAL; + } + + if (copy_from_user(midihdr->data, buffer, count)) { + kfree(midihdr->data); + kfree(midihdr); + return -EFAULT; + } + + spin_lock_irqsave(&midi_spinlock, flags); + + if (emu10k1_mpuout_add_buffer(midi_dev->card, midihdr) < 0) { + ERROR(); + kfree(midihdr->data); + kfree(midihdr); + spin_unlock_irqrestore(&midi_spinlock, flags); + return -EINVAL; + } + + spin_unlock_irqrestore(&midi_spinlock, flags); + + return count; +} + +static unsigned int emu10k1_midi_poll(struct file *file, struct poll_table_struct *wait) +{ + struct emu10k1_mididevice *midi_dev = (struct emu10k1_mididevice *) file->private_data; + unsigned long flags; + unsigned int mask = 0; + + DPF(4, "emu10k1_midi_poll() called\n"); + + if (file->f_mode & FMODE_WRITE) + poll_wait(file, &midi_dev->oWait, wait); + + if (file->f_mode & FMODE_READ) + poll_wait(file, &midi_dev->iWait, wait); + + spin_lock_irqsave(&midi_spinlock, flags); + + if (file->f_mode & FMODE_WRITE) + mask |= POLLOUT | POLLWRNORM; + + if (file->f_mode & FMODE_READ) { + if (midi_dev->mistate == MIDIIN_STATE_STARTED) + if (midi_dev->icnt > 0) + mask |= POLLIN | POLLRDNORM; + } + + spin_unlock_irqrestore(&midi_spinlock, flags); + + return mask; +} + +int emu10k1_midi_callback(unsigned long msg, unsigned long refdata, unsigned long *pmsg) +{ + struct emu10k1_mididevice *midi_dev = (struct emu10k1_mididevice *) refdata; + struct midi_hdr *midihdr = NULL; + unsigned long flags; + int i; + + DPF(4, "emu10k1_midi_callback()\n"); + + spin_lock_irqsave(&midi_spinlock, flags); + + switch (msg) { + case ICARDMIDI_OUTLONGDATA: + midihdr = (struct midi_hdr *) pmsg[2]; + + kfree(midihdr->data); + kfree(midihdr); + wake_up_interruptible(&midi_dev->oWait); + + break; + + case ICARDMIDI_INLONGDATA: + midihdr = (struct midi_hdr *) pmsg[2]; + + for (i = 0; i < midihdr->bytesrecorded; i++) { + midi_dev->iBuf[midi_dev->iwr++] = midihdr->data[i]; + midi_dev->iwr %= MIDIIN_BUFLEN; + } + + midi_dev->icnt += midihdr->bytesrecorded; + + if (midi_dev->mistate == MIDIIN_STATE_STARTED) { + init_midi_hdr(midihdr); + emu10k1_mpuin_add_buffer(midi_dev->card->mpuin, midihdr); + wake_up_interruptible(&midi_dev->iWait); + } + break; + + case ICARDMIDI_INDATA: + { + u8 *pBuf = (u8 *) & pmsg[1]; + u16 bytesvalid = pmsg[2]; + + for (i = 0; i < bytesvalid; i++) { + midi_dev->iBuf[midi_dev->iwr++] = pBuf[i]; + midi_dev->iwr %= MIDIIN_BUFLEN; + } + + midi_dev->icnt += bytesvalid; + } + + wake_up_interruptible(&midi_dev->iWait); + break; + + default: /* Unknown message */ + spin_unlock_irqrestore(&midi_spinlock, flags); + return -1; + } + + spin_unlock_irqrestore(&midi_spinlock, flags); + + return 0; +} + +/* MIDI file operations */ +struct file_operations emu10k1_midi_fops = { + .owner = THIS_MODULE, + .read = emu10k1_midi_read, + .write = emu10k1_midi_write, + .poll = emu10k1_midi_poll, + .open = emu10k1_midi_open, + .release = emu10k1_midi_release, +}; + + +#ifdef EMU10K1_SEQUENCER + +/* functions used for sequencer access */ + +int emu10k1_seq_midi_open(int dev, int mode, + void (*input) (int dev, unsigned char data), + void (*output) (int dev)) +{ + struct emu10k1_card *card; + struct midi_openinfo dsCardMidiOpenInfo; + struct emu10k1_mididevice *midi_dev; + + if (midi_devs[dev] == NULL || midi_devs[dev]->devc == NULL) + return -EINVAL; + + card = midi_devs[dev]->devc; + + if (card->open_mode) /* card is opened native */ + return -EBUSY; + + DPF(2, "emu10k1_seq_midi_open()\n"); + + if ((midi_dev = (struct emu10k1_mididevice *) kmalloc(sizeof(*midi_dev), GFP_KERNEL)) == NULL) + return -EINVAL; + + midi_dev->card = card; + midi_dev->mistate = MIDIIN_STATE_STOPPED; + init_waitqueue_head(&midi_dev->oWait); + init_waitqueue_head(&midi_dev->iWait); + midi_dev->ird = 0; + midi_dev->iwr = 0; + midi_dev->icnt = 0; + INIT_LIST_HEAD(&midi_dev->mid_hdrs); + + dsCardMidiOpenInfo.refdata = (unsigned long) midi_dev; + + if (emu10k1_mpuout_open(card, &dsCardMidiOpenInfo) < 0) { + ERROR(); + return -ENODEV; + } + + card->seq_mididev = midi_dev; + + return 0; +} + +void emu10k1_seq_midi_close(int dev) +{ + struct emu10k1_card *card; + + DPF(2, "emu10k1_seq_midi_close()\n"); + if (midi_devs[dev] == NULL || midi_devs[dev]->devc == NULL) + return; + + card = midi_devs[dev]->devc; + emu10k1_mpuout_close(card); + + if (card->seq_mididev) { + kfree(card->seq_mididev); + card->seq_mididev = NULL; + } +} + +int emu10k1_seq_midi_out(int dev, unsigned char midi_byte) +{ + struct emu10k1_card *card; + struct midi_hdr *midihdr; + unsigned long flags; + + if (midi_devs[dev] == NULL || midi_devs[dev]->devc == NULL) + return -EINVAL; + + card = midi_devs[dev]->devc; + + if ((midihdr = (struct midi_hdr *) kmalloc(sizeof(struct midi_hdr), GFP_KERNEL)) == NULL) + return -EINVAL; + + midihdr->bufferlength = 1; + midihdr->bytesrecorded = 0; + midihdr->flags = 0; + + if ((midihdr->data = (u8 *) kmalloc(1, GFP_KERNEL)) == NULL) { + ERROR(); + kfree(midihdr); + return -EINVAL; + } + + *(midihdr->data) = midi_byte; + + spin_lock_irqsave(&midi_spinlock, flags); + + if (emu10k1_mpuout_add_buffer(card, midihdr) < 0) { + ERROR(); + kfree(midihdr->data); + kfree(midihdr); + spin_unlock_irqrestore(&midi_spinlock, flags); + return -EINVAL; + } + + spin_unlock_irqrestore(&midi_spinlock, flags); + + return 1; +} + +int emu10k1_seq_midi_start_read(int dev) +{ + return 0; +} + +int emu10k1_seq_midi_end_read(int dev) +{ + return 0; +} + +void emu10k1_seq_midi_kick(int dev) +{ +} + +int emu10k1_seq_midi_buffer_status(int dev) +{ + int count; + struct midi_queue *queue; + struct emu10k1_card *card; + + if (midi_devs[dev] == NULL || midi_devs[dev]->devc == NULL) + return -EINVAL; + + count = 0; + + card = midi_devs[dev]->devc; + queue = card->mpuout->firstmidiq; + + while (queue != NULL) { + count++; + if (queue == card->mpuout->lastmidiq) + break; + + queue = queue->next; + } + + return count; +} + +#endif + diff --git a/sound/oss/emu10k1/midi.h b/sound/oss/emu10k1/midi.h new file mode 100644 index 000000000000..2459ec929e8d --- /dev/null +++ b/sound/oss/emu10k1/midi.h @@ -0,0 +1,78 @@ +/* + ********************************************************************** + * midi.h + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 _MIDI_H +#define _MIDI_H + +#define FMODE_MIDI_SHIFT 3 +#define FMODE_MIDI_READ (FMODE_READ << FMODE_MIDI_SHIFT) +#define FMODE_MIDI_WRITE (FMODE_WRITE << FMODE_MIDI_SHIFT) + +#define MIDIIN_STATE_STARTED 0x00000001 +#define MIDIIN_STATE_STOPPED 0x00000002 + +#define MIDIIN_BUFLEN 1024 + +struct emu10k1_mididevice +{ + struct emu10k1_card *card; + u32 mistate; + wait_queue_head_t oWait; + wait_queue_head_t iWait; + s8 iBuf[MIDIIN_BUFLEN]; + u16 ird, iwr, icnt; + struct list_head mid_hdrs; +}; + +/* uncomment next line to use midi port on Audigy drive */ +//#define USE_AUDIGY_DRIVE_MIDI + +#ifdef USE_AUDIGY_DRIVE_MIDI +#define A_MUDATA A_MUDATA2 +#define A_MUCMD A_MUCMD2 +#define A_MUSTAT A_MUCMD2 +#define A_IPR_MIDITRANSBUFEMPTY A_IPR_MIDITRANSBUFEMPTY2 +#define A_IPR_MIDIRECVBUFEMPTY A_IPR_MIDIRECVBUFEMPTY2 +#define A_INTE_MIDITXENABLE A_INTE_MIDITXENABLE2 +#define A_INTE_MIDIRXENABLE A_INTE_MIDIRXENABLE2 +#else +#define A_MUDATA A_MUDATA1 +#define A_MUCMD A_MUCMD1 +#define A_MUSTAT A_MUCMD1 +#define A_IPR_MIDITRANSBUFEMPTY A_IPR_MIDITRANSBUFEMPTY1 +#define A_IPR_MIDIRECVBUFEMPTY A_IPR_MIDIRECVBUFEMPTY1 +#define A_INTE_MIDITXENABLE A_INTE_MIDITXENABLE1 +#define A_INTE_MIDIRXENABLE A_INTE_MIDIRXENABLE1 +#endif + + +#endif /* _MIDI_H */ + diff --git a/sound/oss/emu10k1/mixer.c b/sound/oss/emu10k1/mixer.c new file mode 100644 index 000000000000..cbcaaa34189a --- /dev/null +++ b/sound/oss/emu10k1/mixer.c @@ -0,0 +1,690 @@ +/* + ********************************************************************** + * mixer.c - /dev/mixer interface for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * November 2, 1999 Alan Cox cleaned up stuff + * + ********************************************************************** + * + * 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 +#include +#include + +#include "hwaccess.h" +#include "8010.h" +#include "recmgr.h" + + +static const u32 bass_table[41][5] = { + { 0x3e4f844f, 0x84ed4cc3, 0x3cc69927, 0x7b03553a, 0xc4da8486 }, + { 0x3e69a17a, 0x84c280fb, 0x3cd77cd4, 0x7b2f2a6f, 0xc4b08d1d }, + { 0x3e82ff42, 0x849991d5, 0x3ce7466b, 0x7b5917c6, 0xc48863ee }, + { 0x3e9bab3c, 0x847267f0, 0x3cf5ffe8, 0x7b813560, 0xc461f22c }, + { 0x3eb3b275, 0x844ced29, 0x3d03b295, 0x7ba79a1c, 0xc43d223b }, + { 0x3ecb2174, 0x84290c8b, 0x3d106714, 0x7bcc5ba3, 0xc419dfa5 }, + { 0x3ee2044b, 0x8406b244, 0x3d1c2561, 0x7bef8e77, 0xc3f8170f }, + { 0x3ef86698, 0x83e5cb96, 0x3d26f4d8, 0x7c114600, 0xc3d7b625 }, + { 0x3f0e5390, 0x83c646c9, 0x3d30dc39, 0x7c319498, 0xc3b8ab97 }, + { 0x3f23d60b, 0x83a81321, 0x3d39e1af, 0x7c508b9c, 0xc39ae704 }, + { 0x3f38f884, 0x838b20d2, 0x3d420ad2, 0x7c6e3b75, 0xc37e58f1 }, + { 0x3f4dc52c, 0x836f60ef, 0x3d495cab, 0x7c8ab3a6, 0xc362f2be }, + { 0x3f6245e8, 0x8354c565, 0x3d4fdbb8, 0x7ca602d6, 0xc348a69b }, + { 0x3f76845f, 0x833b40ec, 0x3d558bf0, 0x7cc036df, 0xc32f677c }, + { 0x3f8a8a03, 0x8322c6fb, 0x3d5a70c4, 0x7cd95cd7, 0xc317290b }, + { 0x3f9e6014, 0x830b4bc3, 0x3d5e8d25, 0x7cf1811a, 0xc2ffdfa5 }, + { 0x3fb20fae, 0x82f4c420, 0x3d61e37f, 0x7d08af56, 0xc2e9804a }, + { 0x3fc5a1cc, 0x82df2592, 0x3d6475c3, 0x7d1ef294, 0xc2d40096 }, + { 0x3fd91f55, 0x82ca6632, 0x3d664564, 0x7d345541, 0xc2bf56b9 }, + { 0x3fec9120, 0x82b67cac, 0x3d675356, 0x7d48e138, 0xc2ab796e }, + { 0x40000000, 0x82a36037, 0x3d67a012, 0x7d5c9fc9, 0xc2985fee }, + { 0x401374c7, 0x8291088a, 0x3d672b93, 0x7d6f99c3, 0xc28601f2 }, + { 0x4026f857, 0x827f6dd7, 0x3d65f559, 0x7d81d77c, 0xc27457a3 }, + { 0x403a939f, 0x826e88c5, 0x3d63fc63, 0x7d9360d4, 0xc2635996 }, + { 0x404e4faf, 0x825e5266, 0x3d613f32, 0x7da43d42, 0xc25300c6 }, + { 0x406235ba, 0x824ec434, 0x3d5dbbc3, 0x7db473d7, 0xc243468e }, + { 0x40764f1f, 0x823fd80c, 0x3d596f8f, 0x7dc40b44, 0xc23424a2 }, + { 0x408aa576, 0x82318824, 0x3d545787, 0x7dd309e2, 0xc2259509 }, + { 0x409f4296, 0x8223cf0b, 0x3d4e7012, 0x7de175b5, 0xc2179218 }, + { 0x40b430a0, 0x8216a7a1, 0x3d47b505, 0x7def5475, 0xc20a1670 }, + { 0x40c97a0a, 0x820a0d12, 0x3d4021a1, 0x7dfcab8d, 0xc1fd1cf5 }, + { 0x40df29a6, 0x81fdfad6, 0x3d37b08d, 0x7e098028, 0xc1f0a0ca }, + { 0x40f54ab1, 0x81f26ca9, 0x3d2e5bd1, 0x7e15d72b, 0xc1e49d52 }, + { 0x410be8da, 0x81e75e89, 0x3d241cce, 0x7e21b544, 0xc1d90e24 }, + { 0x41231051, 0x81dcccb3, 0x3d18ec37, 0x7e2d1ee6, 0xc1cdef10 }, + { 0x413acdd0, 0x81d2b39e, 0x3d0cc20a, 0x7e38184e, 0xc1c33c13 }, + { 0x41532ea7, 0x81c90ffb, 0x3cff9585, 0x7e42a58b, 0xc1b8f15a }, + { 0x416c40cd, 0x81bfdeb2, 0x3cf15d21, 0x7e4cca7c, 0xc1af0b3f }, + { 0x418612ea, 0x81b71cdc, 0x3ce20e85, 0x7e568ad3, 0xc1a58640 }, + { 0x41a0b465, 0x81aec7c5, 0x3cd19e7c, 0x7e5fea1e, 0xc19c5f03 }, + { 0x41bc3573, 0x81a6dcea, 0x3cc000e9, 0x7e68ebc2, 0xc1939250 } +}; + +static const u32 treble_table[41][5] = { + { 0x0125cba9, 0xfed5debd, 0x00599b6c, 0x0d2506da, 0xfa85b354 }, + { 0x0142f67e, 0xfeb03163, 0x0066cd0f, 0x0d14c69d, 0xfa914473 }, + { 0x016328bd, 0xfe860158, 0x0075b7f2, 0x0d03eb27, 0xfa9d32d2 }, + { 0x0186b438, 0xfe56c982, 0x00869234, 0x0cf27048, 0xfaa97fca }, + { 0x01adf358, 0xfe21f5fe, 0x00999842, 0x0ce051c2, 0xfab62ca5 }, + { 0x01d949fa, 0xfde6e287, 0x00af0d8d, 0x0ccd8b4a, 0xfac33aa7 }, + { 0x02092669, 0xfda4d8bf, 0x00c73d4c, 0x0cba1884, 0xfad0ab07 }, + { 0x023e0268, 0xfd5b0e4a, 0x00e27b54, 0x0ca5f509, 0xfade7ef2 }, + { 0x0278645c, 0xfd08a2b0, 0x01012509, 0x0c911c63, 0xfaecb788 }, + { 0x02b8e091, 0xfcac9d1a, 0x0123a262, 0x0c7b8a14, 0xfafb55df }, + { 0x03001a9a, 0xfc45e9ce, 0x014a6709, 0x0c65398f, 0xfb0a5aff }, + { 0x034ec6d7, 0xfbd3576b, 0x0175f397, 0x0c4e2643, 0xfb19c7e4 }, + { 0x03a5ac15, 0xfb5393ee, 0x01a6d6ed, 0x0c364b94, 0xfb299d7c }, + { 0x0405a562, 0xfac52968, 0x01ddafae, 0x0c1da4e2, 0xfb39dca5 }, + { 0x046fa3fe, 0xfa267a66, 0x021b2ddd, 0x0c042d8d, 0xfb4a8631 }, + { 0x04e4b17f, 0xf975be0f, 0x0260149f, 0x0be9e0f2, 0xfb5b9ae0 }, + { 0x0565f220, 0xf8b0fbe5, 0x02ad3c29, 0x0bceba73, 0xfb6d1b60 }, + { 0x05f4a745, 0xf7d60722, 0x030393d4, 0x0bb2b578, 0xfb7f084d }, + { 0x06923236, 0xf6e279bd, 0x03642465, 0x0b95cd75, 0xfb916233 }, + { 0x07401713, 0xf5d3aef9, 0x03d01283, 0x0b77fded, 0xfba42984 }, + { 0x08000000, 0xf4a6bd88, 0x0448a161, 0x0b594278, 0xfbb75e9f }, + { 0x08d3c097, 0xf3587131, 0x04cf35a4, 0x0b3996c9, 0xfbcb01cb }, + { 0x09bd59a2, 0xf1e543f9, 0x05655880, 0x0b18f6b2, 0xfbdf1333 }, + { 0x0abefd0f, 0xf04956ca, 0x060cbb12, 0x0af75e2c, 0xfbf392e8 }, + { 0x0bdb123e, 0xee806984, 0x06c739fe, 0x0ad4c962, 0xfc0880dd }, + { 0x0d143a94, 0xec85d287, 0x0796e150, 0x0ab134b0, 0xfc1ddce5 }, + { 0x0e6d5664, 0xea547598, 0x087df0a0, 0x0a8c9cb6, 0xfc33a6ad }, + { 0x0fe98a2a, 0xe7e6ba35, 0x097edf83, 0x0a66fe5b, 0xfc49ddc2 }, + { 0x118c4421, 0xe536813a, 0x0a9c6248, 0x0a4056d7, 0xfc608185 }, + { 0x1359422e, 0xe23d19eb, 0x0bd96efb, 0x0a18a3bf, 0xfc77912c }, + { 0x1554982b, 0xdef33645, 0x0d3942bd, 0x09efe312, 0xfc8f0bc1 }, + { 0x1782b68a, 0xdb50deb1, 0x0ebf676d, 0x09c6133f, 0xfca6f019 }, + { 0x19e8715d, 0xd74d64fd, 0x106fb999, 0x099b3337, 0xfcbf3cd6 }, + { 0x1c8b07b8, 0xd2df56ab, 0x124e6ec8, 0x096f4274, 0xfcd7f060 }, + { 0x1f702b6d, 0xcdfc6e92, 0x14601c10, 0x0942410b, 0xfcf108e5 }, + { 0x229e0933, 0xc89985cd, 0x16a9bcfa, 0x09142fb5, 0xfd0a8451 }, + { 0x261b5118, 0xc2aa8409, 0x1930bab6, 0x08e50fdc, 0xfd24604d }, + { 0x29ef3f5d, 0xbc224f28, 0x1bfaf396, 0x08b4e3aa, 0xfd3e9a3b }, + { 0x2e21a59b, 0xb4f2ba46, 0x1f0ec2d6, 0x0883ae15, 0xfd592f33 }, + { 0x32baf44b, 0xad0c7429, 0x227308a3, 0x085172eb, 0xfd741bfd }, + { 0x37c4448b, 0xa45ef51d, 0x262f3267, 0x081e36dc, 0xfd8f5d14 } +}; + + +static void set_bass(struct emu10k1_card *card, int l, int r) +{ + int i; + + l = (l * 40 + 50) / 100; + r = (r * 40 + 50) / 100; + + for (i = 0; i < 5; i++) + sblive_writeptr(card, (card->is_audigy ? A_GPR_BASE : GPR_BASE) + card->mgr.ctrl_gpr[SOUND_MIXER_BASS][0] + i, 0, bass_table[l][i]); +} + +static void set_treble(struct emu10k1_card *card, int l, int r) +{ + int i; + + l = (l * 40 + 50) / 100; + r = (r * 40 + 50) / 100; + + for (i = 0; i < 5; i++) + sblive_writeptr(card, (card->is_audigy ? A_GPR_BASE : GPR_BASE) + card->mgr.ctrl_gpr[SOUND_MIXER_TREBLE][0] + i , 0, treble_table[l][i]); +} + +const char volume_params[SOUND_MIXER_NRDEVICES]= { +/* Used by the ac97 driver */ + [SOUND_MIXER_VOLUME] = VOL_6BIT, + [SOUND_MIXER_BASS] = VOL_4BIT, + [SOUND_MIXER_TREBLE] = VOL_4BIT, + [SOUND_MIXER_PCM] = VOL_5BIT, + [SOUND_MIXER_SPEAKER] = VOL_4BIT, + [SOUND_MIXER_LINE] = VOL_5BIT, + [SOUND_MIXER_MIC] = VOL_5BIT, + [SOUND_MIXER_CD] = VOL_5BIT, + [SOUND_MIXER_ALTPCM] = VOL_6BIT, + [SOUND_MIXER_IGAIN] = VOL_4BIT, + [SOUND_MIXER_LINE1] = VOL_5BIT, + [SOUND_MIXER_PHONEIN] = VOL_5BIT, + [SOUND_MIXER_PHONEOUT] = VOL_6BIT, + [SOUND_MIXER_VIDEO] = VOL_5BIT, +/* Not used by the ac97 driver */ + [SOUND_MIXER_SYNTH] = VOL_5BIT, + [SOUND_MIXER_IMIX] = VOL_5BIT, + [SOUND_MIXER_RECLEV] = VOL_5BIT, + [SOUND_MIXER_OGAIN] = VOL_5BIT, + [SOUND_MIXER_LINE2] = VOL_5BIT, + [SOUND_MIXER_LINE3] = VOL_5BIT, + [SOUND_MIXER_DIGITAL1] = VOL_5BIT, + [SOUND_MIXER_DIGITAL2] = VOL_5BIT, + [SOUND_MIXER_DIGITAL3] = VOL_5BIT, + [SOUND_MIXER_RADIO] = VOL_5BIT, + [SOUND_MIXER_MONITOR] = VOL_5BIT +}; + +/* Mixer file operations */ +static int emu10k1_private_mixer(struct emu10k1_card *card, unsigned int cmd, unsigned long arg) +{ + struct mixer_private_ioctl *ctl; + struct dsp_patch *patch; + u32 size, page; + int addr, size_reg, i, ret; + unsigned int id, ch; + void __user *argp = (void __user *)arg; + + switch (cmd) { + + case SOUND_MIXER_PRIVATE3: + + ctl = (struct mixer_private_ioctl *) kmalloc(sizeof(struct mixer_private_ioctl), GFP_KERNEL); + if (ctl == NULL) + return -ENOMEM; + + if (copy_from_user(ctl, argp, sizeof(struct mixer_private_ioctl))) { + kfree(ctl); + return -EFAULT; + } + + ret = 0; + switch (ctl->cmd) { +#ifdef DBGEMU + case CMD_WRITEFN0: + emu10k1_writefn0_2(card, ctl->val[0], ctl->val[1], ctl->val[2]); + break; +#endif + case CMD_WRITEPTR: +#ifdef DBGEMU + if (ctl->val[1] >= 0x40 || ctl->val[0] >= 0x1000) { +#else + if (ctl->val[1] >= 0x40 || ctl->val[0] >= 0x1000 || ((ctl->val[0] < 0x100 ) && + //Any register allowed raw access goes here: + (ctl->val[0] != A_SPDIF_SAMPLERATE) && (ctl->val[0] != A_DBG) + ) + ) { +#endif + ret = -EINVAL; + break; + } + sblive_writeptr(card, ctl->val[0], ctl->val[1], ctl->val[2]); + break; + + case CMD_READFN0: + ctl->val[2] = emu10k1_readfn0(card, ctl->val[0]); + + if (copy_to_user(argp, ctl, sizeof(struct mixer_private_ioctl))) + ret = -EFAULT; + + break; + + case CMD_READPTR: + if (ctl->val[1] >= 0x40 || (ctl->val[0] & 0x7ff) > 0xff) { + ret = -EINVAL; + break; + } + + if ((ctl->val[0] & 0x7ff) > 0x3f) + ctl->val[1] = 0x00; + + ctl->val[2] = sblive_readptr(card, ctl->val[0], ctl->val[1]); + + if (copy_to_user(argp, ctl, sizeof(struct mixer_private_ioctl))) + ret = -EFAULT; + + break; + + case CMD_SETRECSRC: + switch (ctl->val[0]) { + case WAVERECORD_AC97: + if (card->is_aps) { + ret = -EINVAL; + break; + } + + card->wavein.recsrc = WAVERECORD_AC97; + break; + + case WAVERECORD_MIC: + card->wavein.recsrc = WAVERECORD_MIC; + break; + + case WAVERECORD_FX: + card->wavein.recsrc = WAVERECORD_FX; + card->wavein.fxwc = ctl->val[1] & 0xffff; + + if (!card->wavein.fxwc) + ret = -EINVAL; + + break; + + default: + ret = -EINVAL; + break; + } + break; + + case CMD_GETRECSRC: + ctl->val[0] = card->wavein.recsrc; + ctl->val[1] = card->wavein.fxwc; + if (copy_to_user(argp, ctl, sizeof(struct mixer_private_ioctl))) + ret = -EFAULT; + + break; + + case CMD_GETVOICEPARAM: + ctl->val[0] = card->waveout.send_routing[0]; + ctl->val[1] = card->waveout.send_dcba[0]; + + ctl->val[2] = card->waveout.send_routing[1]; + ctl->val[3] = card->waveout.send_dcba[1]; + + ctl->val[4] = card->waveout.send_routing[2]; + ctl->val[5] = card->waveout.send_dcba[2]; + + if (copy_to_user(argp, ctl, sizeof(struct mixer_private_ioctl))) + ret = -EFAULT; + + break; + + case CMD_SETVOICEPARAM: + card->waveout.send_routing[0] = ctl->val[0]; + card->waveout.send_dcba[0] = ctl->val[1]; + + card->waveout.send_routing[1] = ctl->val[2]; + card->waveout.send_dcba[1] = ctl->val[3]; + + card->waveout.send_routing[2] = ctl->val[4]; + card->waveout.send_dcba[2] = ctl->val[5]; + + break; + + case CMD_SETMCH_FX: + card->mchannel_fx = ctl->val[0] & 0x000f; + break; + + case CMD_GETPATCH: + if (ctl->val[0] == 0) { + if (copy_to_user(argp, &card->mgr.rpatch, sizeof(struct dsp_rpatch))) + ret = -EFAULT; + } else { + if ((ctl->val[0] - 1) / PATCHES_PER_PAGE >= card->mgr.current_pages) { + ret = -EINVAL; + break; + } + + if (copy_to_user(argp, PATCH(&card->mgr, ctl->val[0] - 1), sizeof(struct dsp_patch))) + ret = -EFAULT; + } + + break; + + case CMD_GETGPR: + id = ctl->val[0]; + + if (id > NUM_GPRS) { + ret = -EINVAL; + break; + } + + if (copy_to_user(argp, &card->mgr.gpr[id], sizeof(struct dsp_gpr))) + ret = -EFAULT; + + break; + + case CMD_GETCTLGPR: + addr = emu10k1_find_control_gpr(&card->mgr, (char *) ctl->val, &((char *) ctl->val)[PATCH_NAME_SIZE]); + ctl->val[0] = sblive_readptr(card, addr, 0); + + if (copy_to_user(argp, ctl, sizeof(struct mixer_private_ioctl))) + ret = -EFAULT; + + break; + + case CMD_SETPATCH: + if (ctl->val[0] == 0) + memcpy(&card->mgr.rpatch, &ctl->val[1], sizeof(struct dsp_rpatch)); + else { + page = (ctl->val[0] - 1) / PATCHES_PER_PAGE; + if (page > MAX_PATCHES_PAGES) { + ret = -EINVAL; + break; + } + + if (page >= card->mgr.current_pages) { + for (i = card->mgr.current_pages; i < page + 1; i++) { + card->mgr.patch[i] = (void *)__get_free_page(GFP_KERNEL); + if(card->mgr.patch[i] == NULL) { + card->mgr.current_pages = i; + ret = -ENOMEM; + break; + } + memset(card->mgr.patch[i], 0, PAGE_SIZE); + } + card->mgr.current_pages = page + 1; + } + + patch = PATCH(&card->mgr, ctl->val[0] - 1); + + memcpy(patch, &ctl->val[1], sizeof(struct dsp_patch)); + + if (patch->code_size == 0) { + for(i = page + 1; i < card->mgr.current_pages; i++) + free_page((unsigned long) card->mgr.patch[i]); + + card->mgr.current_pages = page + 1; + } + } + break; + + case CMD_SETGPR: + if (ctl->val[0] > NUM_GPRS) { + ret = -EINVAL; + break; + } + + memcpy(&card->mgr.gpr[ctl->val[0]], &ctl->val[1], sizeof(struct dsp_gpr)); + break; + + case CMD_SETCTLGPR: + addr = emu10k1_find_control_gpr(&card->mgr, (char *) ctl->val, (char *) ctl->val + PATCH_NAME_SIZE); + emu10k1_set_control_gpr(card, addr, *((s32 *)((char *) ctl->val + 2 * PATCH_NAME_SIZE)), 0); + break; + + case CMD_SETGPOUT: + if ( ((ctl->val[0] > 2) && (!card->is_audigy)) + || (ctl->val[0] > 15) || ctl->val[1] > 1) { + ret= -EINVAL; + break; + } + + if (card->is_audigy) + emu10k1_writefn0(card, (1 << 24) | ((ctl->val[0]) << 16) | A_IOCFG, ctl->val[1]); + else + emu10k1_writefn0(card, (1 << 24) | (((ctl->val[0]) + 10) << 16) | HCFG, ctl->val[1]); + break; + + case CMD_GETGPR2OSS: + id = ctl->val[0]; + ch = ctl->val[1]; + + if (id >= SOUND_MIXER_NRDEVICES || ch >= 2) { + ret = -EINVAL; + break; + } + + ctl->val[2] = card->mgr.ctrl_gpr[id][ch]; + + if (copy_to_user(argp, ctl, sizeof(struct mixer_private_ioctl))) + ret = -EFAULT; + + break; + + case CMD_SETGPR2OSS: + id = ctl->val[0]; + /* 0 == left, 1 == right */ + ch = ctl->val[1]; + addr = ctl->val[2]; + + if (id >= SOUND_MIXER_NRDEVICES || ch >= 2) { + ret = -EINVAL; + break; + } + + card->mgr.ctrl_gpr[id][ch] = addr; + + if (card->is_aps) + break; + + if (addr >= 0) { + unsigned int state = card->ac97->mixer_state[id]; + + if (ch == 1) { + state >>= 8; + card->ac97->stereo_mixers |= (1 << id); + } + + card->ac97->supported_mixers |= (1 << id); + + if (id == SOUND_MIXER_TREBLE) { + set_treble(card, card->ac97->mixer_state[id] & 0xff, (card->ac97->mixer_state[id] >> 8) & 0xff); + } else if (id == SOUND_MIXER_BASS) { + set_bass(card, card->ac97->mixer_state[id] & 0xff, (card->ac97->mixer_state[id] >> 8) & 0xff); + } else + emu10k1_set_volume_gpr(card, addr, state & 0xff, + volume_params[id]); + } else { + card->ac97->stereo_mixers &= ~(1 << id); + card->ac97->stereo_mixers |= card->ac97_stereo_mixers; + + if (ch == 0) { + card->ac97->supported_mixers &= ~(1 << id); + card->ac97->supported_mixers |= card->ac97_supported_mixers; + } + } + break; + + case CMD_SETPASSTHROUGH: + card->pt.selected = ctl->val[0] ? 1 : 0; + if (card->pt.state != PT_STATE_INACTIVE) + break; + + card->pt.spcs_to_use = ctl->val[0] & 0x07; + break; + + case CMD_PRIVATE3_VERSION: + ctl->val[0] = PRIVATE3_VERSION; //private3 version + ctl->val[1] = MAJOR_VER; //major driver version + ctl->val[2] = MINOR_VER; //minor driver version + ctl->val[3] = card->is_audigy; //1=card is audigy + + if (card->is_audigy) + ctl->val[4]=emu10k1_readfn0(card, 0x18); + + if (copy_to_user(argp, ctl, sizeof(struct mixer_private_ioctl))) + ret = -EFAULT; + break; + + case CMD_AC97_BOOST: + if (ctl->val[0]) + emu10k1_ac97_write(card->ac97, 0x18, 0x0); + else + emu10k1_ac97_write(card->ac97, 0x18, 0x0808); + break; + default: + ret = -EINVAL; + break; + } + + kfree(ctl); + return ret; + break; + + case SOUND_MIXER_PRIVATE4: + + if (copy_from_user(&size, argp, sizeof(size))) + return -EFAULT; + + DPD(2, "External tram size %#x\n", size); + + if (size > 0x1fffff) + return -EINVAL; + + size_reg = 0; + + if (size != 0) { + size = (size - 1) >> 14; + + while (size) { + size >>= 1; + size_reg++; + } + + size = 0x4000 << size_reg; + } + + DPD(2, "External tram size %#x %#x\n", size, size_reg); + + if (size != card->tankmem.size) { + if (card->tankmem.size > 0) { + emu10k1_writefn0(card, HCFG_LOCKTANKCACHE, 1); + + sblive_writeptr_tag(card, 0, TCB, 0, TCBS, 0, TAGLIST_END); + + pci_free_consistent(card->pci_dev, card->tankmem.size, card->tankmem.addr, card->tankmem.dma_handle); + + card->tankmem.size = 0; + } + + if (size != 0) { + card->tankmem.addr = pci_alloc_consistent(card->pci_dev, size, &card->tankmem.dma_handle); + if (card->tankmem.addr == NULL) + return -ENOMEM; + + card->tankmem.size = size; + + sblive_writeptr_tag(card, 0, TCB, (u32) card->tankmem.dma_handle, TCBS,(u32) size_reg, TAGLIST_END); + + emu10k1_writefn0(card, HCFG_LOCKTANKCACHE, 0); + } + } + return 0; + break; + + default: + break; + } + + return -EINVAL; +} + +static int emu10k1_dsp_mixer(struct emu10k1_card *card, unsigned int oss_mixer, unsigned long arg) +{ + unsigned int left, right; + int val; + int scale; + + card->ac97->modcnt++; + + if (get_user(val, (int __user *)arg)) + return -EFAULT; + + /* cleanse input a little */ + right = ((val >> 8) & 0xff); + left = (val & 0xff); + + if (right > 100) right = 100; + if (left > 100) left = 100; + + card->ac97->mixer_state[oss_mixer] = (right << 8) | left; + if (oss_mixer == SOUND_MIXER_TREBLE) { + set_treble(card, left, right); + return 0; + } if (oss_mixer == SOUND_MIXER_BASS) { + set_bass(card, left, right); + return 0; + } + + if (oss_mixer == SOUND_MIXER_VOLUME) + scale = 1 << card->ac97->bit_resolution; + else + scale = volume_params[oss_mixer]; + + emu10k1_set_volume_gpr(card, card->mgr.ctrl_gpr[oss_mixer][0], left, scale); + emu10k1_set_volume_gpr(card, card->mgr.ctrl_gpr[oss_mixer][1], right, scale); + + if (card->ac97_supported_mixers & (1 << oss_mixer)) + card->ac97->write_mixer(card->ac97, oss_mixer, left, right); + + return 0; +} + +static int emu10k1_mixer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret; + struct emu10k1_card *card = file->private_data; + unsigned int oss_mixer = _IOC_NR(cmd); + + ret = -EINVAL; + if (!card->is_aps) { + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + + strlcpy(info.id, card->ac97->name, sizeof(info.id)); + + if (card->is_audigy) + strlcpy(info.name, "Audigy - Emu10k1", sizeof(info.name)); + else + strlcpy(info.name, "Creative SBLive - Emu10k1", sizeof(info.name)); + + info.modify_counter = card->ac97->modcnt; + + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + + return 0; + } + + if ((_SIOC_DIR(cmd) == (_SIOC_WRITE|_SIOC_READ)) && oss_mixer <= SOUND_MIXER_NRDEVICES) + ret = emu10k1_dsp_mixer(card, oss_mixer, arg); + else + ret = card->ac97->mixer_ioctl(card->ac97, cmd, arg); + } + + if (ret < 0) + ret = emu10k1_private_mixer(card, cmd, arg); + + return ret; +} + +static int emu10k1_mixer_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct emu10k1_card *card = NULL; + struct list_head *entry; + + DPF(4, "emu10k1_mixer_open()\n"); + + list_for_each(entry, &emu10k1_devs) { + card = list_entry(entry, struct emu10k1_card, list); + + if (card->ac97->dev_mixer == minor) + goto match; + } + + return -ENODEV; + + match: + file->private_data = card; + return 0; +} + +static int emu10k1_mixer_release(struct inode *inode, struct file *file) +{ + DPF(4, "emu10k1_mixer_release()\n"); + return 0; +} + +struct file_operations emu10k1_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = emu10k1_mixer_ioctl, + .open = emu10k1_mixer_open, + .release = emu10k1_mixer_release, +}; diff --git a/sound/oss/emu10k1/passthrough.c b/sound/oss/emu10k1/passthrough.c new file mode 100644 index 000000000000..4094be55f3be --- /dev/null +++ b/sound/oss/emu10k1/passthrough.c @@ -0,0 +1,236 @@ +/* + ********************************************************************** + * passthrough.c -- Emu10k1 digital passthrough + * Copyright (C) 2001 Juha Yrjölä + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * May 15, 2001 Juha Yrjölä base code release + * + ********************************************************************** + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "hwaccess.h" +#include "cardwo.h" +#include "cardwi.h" +#include "recmgr.h" +#include "irqmgr.h" +#include "audio.h" +#include "8010.h" + +static void pt_putsamples(struct pt_data *pt, u16 *ptr, u16 left, u16 right) +{ + unsigned int idx; + + ptr[pt->copyptr] = left; + idx = pt->copyptr + PT_SAMPLES/2; + idx %= PT_SAMPLES; + ptr[idx] = right; +} + +static inline int pt_can_write(struct pt_data *pt) +{ + return pt->blocks_copied < pt->blocks_played + 8; +} + +static int pt_wait_for_write(struct emu10k1_wavedevice *wavedev, int nonblock) +{ + struct emu10k1_card *card = wavedev->card; + struct pt_data *pt = &card->pt; + + if (nonblock && !pt_can_write(pt)) + return -EAGAIN; + while (!pt_can_write(pt) && pt->state != PT_STATE_INACTIVE) { + interruptible_sleep_on(&pt->wait); + if (signal_pending(current)) + return -ERESTARTSYS; + } + if (pt->state == PT_STATE_INACTIVE) + return -EAGAIN; + + return 0; +} + +static int pt_putblock(struct emu10k1_wavedevice *wave_dev, u16 *block, int nonblock) +{ + struct woinst *woinst = wave_dev->woinst; + struct emu10k1_card *card = wave_dev->card; + struct pt_data *pt = &card->pt; + u16 *ptr = (u16 *) card->tankmem.addr; + int i = 0, r; + unsigned long flags; + + r = pt_wait_for_write(wave_dev, nonblock); + if (r < 0) + return r; + spin_lock_irqsave(&card->pt.lock, flags); + while (i < PT_BLOCKSAMPLES) { + pt_putsamples(pt, ptr, block[2*i], block[2*i+1]); + if (pt->copyptr == 0) + pt->copyptr = PT_SAMPLES; + pt->copyptr--; + i++; + } + woinst->total_copied += PT_BLOCKSIZE; + pt->blocks_copied++; + if (pt->blocks_copied >= 4 && pt->state != PT_STATE_PLAYING) { + DPF(2, "activating digital pass-through playback\n"); + sblive_writeptr(card, GPR_BASE + pt->enable_gpr, 0, 1); + pt->state = PT_STATE_PLAYING; + } + spin_unlock_irqrestore(&card->pt.lock, flags); + return 0; +} + +int emu10k1_pt_setup(struct emu10k1_wavedevice *wave_dev) +{ + u32 bits; + struct emu10k1_card *card = wave_dev->card; + struct pt_data *pt = &card->pt; + int i; + + for (i = 0; i < 3; i++) { + pt->old_spcs[i] = sblive_readptr(card, SPCS0 + i, 0); + if (pt->spcs_to_use & (1 << i)) { + DPD(2, "using S/PDIF port %d\n", i); + bits = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | SPCS_GENERATIONSTATUS | + 0x00001200 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT; + if (pt->ac3data) + bits |= SPCS_NOTAUDIODATA; + sblive_writeptr(card, SPCS0 + i, 0, bits); + } + } + return 0; +} + +ssize_t emu10k1_pt_write(struct file *file, const char __user *buffer, size_t count) +{ + struct emu10k1_wavedevice *wave_dev = (struct emu10k1_wavedevice *) file->private_data; + struct emu10k1_card *card = wave_dev->card; + struct pt_data *pt = &card->pt; + int nonblock, i, r, blocks, blocks_copied, bytes_copied = 0; + + DPD(3, "emu10k1_pt_write(): %d bytes\n", count); + + nonblock = file->f_flags & O_NONBLOCK; + + if (card->tankmem.size < PT_SAMPLES*2) + return -EFAULT; + if (pt->state == PT_STATE_INACTIVE) { + DPF(2, "bufptr init\n"); + pt->playptr = PT_SAMPLES-1; + pt->copyptr = PT_INITPTR; + pt->blocks_played = pt->blocks_copied = 0; + memset(card->tankmem.addr, 0, card->tankmem.size); + pt->state = PT_STATE_ACTIVATED; + pt->buf = kmalloc(PT_BLOCKSIZE, GFP_KERNEL); + pt->prepend_size = 0; + if (pt->buf == NULL) + return -ENOMEM; + emu10k1_pt_setup(wave_dev); + } + if (pt->prepend_size) { + int needed = PT_BLOCKSIZE - pt->prepend_size; + + DPD(3, "prepend size %d, prepending %d bytes\n", pt->prepend_size, needed); + if (count < needed) { + copy_from_user(pt->buf + pt->prepend_size, buffer, count); + pt->prepend_size += count; + DPD(3, "prepend size now %d\n", pt->prepend_size); + return count; + } + copy_from_user(pt->buf + pt->prepend_size, buffer, needed); + r = pt_putblock(wave_dev, (u16 *) pt->buf, nonblock); + if (r) + return r; + bytes_copied += needed; + pt->prepend_size = 0; + } + blocks = (count-bytes_copied)/PT_BLOCKSIZE; + blocks_copied = 0; + while (blocks > 0) { + u16 __user *bufptr = (u16 __user *) buffer + (bytes_copied/2); + copy_from_user(pt->buf, bufptr, PT_BLOCKSIZE); + r = pt_putblock(wave_dev, (u16 *)pt->buf, nonblock); + if (r) { + if (bytes_copied) + return bytes_copied; + else + return r; + } + bytes_copied += PT_BLOCKSIZE; + blocks--; + blocks_copied++; + } + i = count - bytes_copied; + if (i) { + pt->prepend_size = i; + copy_from_user(pt->buf, buffer + bytes_copied, i); + bytes_copied += i; + DPD(3, "filling prepend buffer with %d bytes", i); + } + return bytes_copied; +} + +void emu10k1_pt_stop(struct emu10k1_card *card) +{ + struct pt_data *pt = &card->pt; + int i; + + if (pt->state != PT_STATE_INACTIVE) { + DPF(2, "digital pass-through stopped\n"); + sblive_writeptr(card, (card->is_audigy ? A_GPR_BASE : GPR_BASE) + pt->enable_gpr, 0, 0); + for (i = 0; i < 3; i++) { + if (pt->spcs_to_use & (1 << i)) + sblive_writeptr(card, SPCS0 + i, 0, pt->old_spcs[i]); + } + pt->state = PT_STATE_INACTIVE; + if(pt->buf) + kfree(pt->buf); + } +} + +void emu10k1_pt_waveout_update(struct emu10k1_wavedevice *wave_dev) +{ + struct woinst *woinst = wave_dev->woinst; + struct pt_data *pt = &wave_dev->card->pt; + u32 pos; + + if (pt->state == PT_STATE_PLAYING && pt->pos_gpr >= 0) { + pos = sblive_readptr(wave_dev->card, GPR_BASE + pt->pos_gpr, 0); + if (pos > PT_BLOCKSAMPLES) + pos = PT_BLOCKSAMPLES; + pos = 4 * (PT_BLOCKSAMPLES - pos); + } else + pos = 0; + woinst->total_played = pt->blocks_played * woinst->buffer.fragment_size + pos; + woinst->buffer.hw_pos = pos; +} diff --git a/sound/oss/emu10k1/passthrough.h b/sound/oss/emu10k1/passthrough.h new file mode 100644 index 000000000000..420cc9784251 --- /dev/null +++ b/sound/oss/emu10k1/passthrough.h @@ -0,0 +1,99 @@ +/* + ********************************************************************** + * passthrough.h -- Emu10k1 digital passthrough header file + * Copyright (C) 2001 Juha Yrjölä + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * May 15, 2001 Juha Yrjölä base code release + * + ********************************************************************** + * + * 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 _PASSTHROUGH_H +#define _PASSTHROUGH_H + +#include "audio.h" + +/* number of 16-bit stereo samples in XTRAM buffer */ +#define PT_SAMPLES 0x8000 +#define PT_BLOCKSAMPLES 0x400 +#define PT_BLOCKSIZE (PT_BLOCKSAMPLES*4) +#define PT_BLOCKSIZE_LOG2 12 +#define PT_BLOCKCOUNT (PT_SAMPLES/PT_BLOCKSAMPLES) +#define PT_INITPTR (PT_SAMPLES/2-1) + +#define PT_STATE_INACTIVE 0 +#define PT_STATE_ACTIVATED 1 +#define PT_STATE_PLAYING 2 + +/* passthrough struct */ +struct pt_data +{ + u8 selected, state, spcs_to_use; + int intr_gpr, enable_gpr, pos_gpr; + u32 blocks_played, blocks_copied, old_spcs[3]; + u32 playptr, copyptr; + u32 prepend_size; + u8 *buf; + u8 ac3data; + + char *patch_name, *intr_gpr_name, *enable_gpr_name, *pos_gpr_name; + + wait_queue_head_t wait; + spinlock_t lock; +}; + +/* + Passthrough can be done in two methods: + + Method 1 : tram + In original emu10k1, we couldn't bypass the sample rate converters. Even at 48kHz + (the internal sample rate of the emu10k1) the samples would get messed up. + To over come this, samples are copied into the tram and a special dsp patch copies + the samples out and generates interrupts when a block has finnished playing. + + Method 2 : Interpolator bypass + + Creative fixed the sample rate convert problem in emu10k1 rev 7 and higher + (including the emu10k2 (audigy)). This allows us to use the regular, and much simpler + playback method. + + + In both methods, dsp code is used to mux audio and passthrough. This ensures that the spdif + doesn't receive audio and pasthrough data at the same time. The spdif flag SPCS_NOTAUDIODATA + is set to tell + + */ + +// emu10k1 revs greater than or equal to 7 can use method2 + +#define USE_PT_METHOD2 (card->is_audigy) +#define USE_PT_METHOD1 !USE_PT_METHOD2 + +ssize_t emu10k1_pt_write(struct file *file, const char __user *buf, size_t count); + +int emu10k1_pt_setup(struct emu10k1_wavedevice *wave_dev); +void emu10k1_pt_stop(struct emu10k1_card *card); +void emu10k1_pt_waveout_update(struct emu10k1_wavedevice *wave_dev); + +#endif /* _PASSTHROUGH_H */ diff --git a/sound/oss/emu10k1/recmgr.c b/sound/oss/emu10k1/recmgr.c new file mode 100644 index 000000000000..67c3fd04cfdd --- /dev/null +++ b/sound/oss/emu10k1/recmgr.c @@ -0,0 +1,147 @@ +/* + ********************************************************************** + * recmgr.c -- Recording manager for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 +#include "8010.h" +#include "recmgr.h" + +void emu10k1_reset_record(struct emu10k1_card *card, struct wavein_buffer *buffer) +{ + DPF(2, "emu10k1_reset_record()\n"); + + sblive_writeptr(card, buffer->sizereg, 0, ADCBS_BUFSIZE_NONE); + + sblive_writeptr(card, buffer->sizereg, 0, buffer->sizeregval); + + while (sblive_readptr(card, buffer->idxreg, 0)) + udelay(5); +} + +void emu10k1_start_record(struct emu10k1_card *card, struct wavein_buffer *buffer) +{ + DPF(2, "emu10k1_start_record()\n"); + + if (buffer->adcctl) + sblive_writeptr(card, ADCCR, 0, buffer->adcctl); +} + +void emu10k1_stop_record(struct emu10k1_card *card, struct wavein_buffer *buffer) +{ + DPF(2, "emu10k1_stop_record()\n"); + + /* Disable record transfer */ + if (buffer->adcctl) + sblive_writeptr(card, ADCCR, 0, 0); +} + +void emu10k1_set_record_src(struct emu10k1_card *card, struct wiinst *wiinst) +{ + struct wavein_buffer *buffer = &wiinst->buffer; + + DPF(2, "emu10k1_set_record_src()\n"); + + switch (wiinst->recsrc) { + + case WAVERECORD_AC97: + DPF(2, "recording source: AC97\n"); + buffer->sizereg = ADCBS; + buffer->addrreg = ADCBA; + buffer->idxreg = card->is_audigy ? A_ADCIDX_IDX : ADCIDX_IDX; + + switch (wiinst->format.samplingrate) { + case 0xBB80: + buffer->adcctl = ADCCR_SAMPLERATE_48; + break; + case 0xAC44: + buffer->adcctl = ADCCR_SAMPLERATE_44; + break; + case 0x7D00: + buffer->adcctl = ADCCR_SAMPLERATE_32; + break; + case 0x5DC0: + buffer->adcctl = ADCCR_SAMPLERATE_24; + break; + case 0x5622: + buffer->adcctl = ADCCR_SAMPLERATE_22; + break; + case 0x3E80: + buffer->adcctl = ADCCR_SAMPLERATE_16; + break; + // FIXME: audigy supports 12kHz recording + /* + case ????: + buffer->adcctl = A_ADCCR_SAMPLERATE_12; + break; + */ + case 0x2B11: + buffer->adcctl = card->is_audigy ? A_ADCCR_SAMPLERATE_11 : ADCCR_SAMPLERATE_11; + break; + case 0x1F40: + buffer->adcctl = card->is_audigy ? A_ADCCR_SAMPLERATE_8 : ADCCR_SAMPLERATE_8; + break; + default: + BUG(); + break; + } + + buffer->adcctl |= card->is_audigy ? A_ADCCR_LCHANENABLE : ADCCR_LCHANENABLE; + + if (wiinst->format.channels == 2) + buffer->adcctl |= card->is_audigy ? A_ADCCR_RCHANENABLE : ADCCR_RCHANENABLE; + + break; + + case WAVERECORD_MIC: + DPF(2, "recording source: MIC\n"); + buffer->sizereg = MICBS; + buffer->addrreg = MICBA; + buffer->idxreg = MICIDX_IDX; + buffer->adcctl = 0; + break; + + case WAVERECORD_FX: + DPF(2, "recording source: FX\n"); + buffer->sizereg = FXBS; + buffer->addrreg = FXBA; + buffer->idxreg = FXIDX_IDX; + buffer->adcctl = 0; + + sblive_writeptr(card, FXWC, 0, wiinst->fxwc); + break; + default: + BUG(); + break; + } + + DPD(2, "bus addx: %#lx\n", (unsigned long) buffer->dma_handle); + + sblive_writeptr(card, buffer->addrreg, 0, (u32)buffer->dma_handle); +} diff --git a/sound/oss/emu10k1/recmgr.h b/sound/oss/emu10k1/recmgr.h new file mode 100644 index 000000000000..a68766ac4fd5 --- /dev/null +++ b/sound/oss/emu10k1/recmgr.h @@ -0,0 +1,48 @@ +/* + ********************************************************************** + * recmgr.h + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 _RECORDMGR_H +#define _RECORDMGR_H + +#include "hwaccess.h" +#include "cardwi.h" + +/* Recording resources */ +#define WAVERECORD_AC97 0x01 +#define WAVERECORD_MIC 0x02 +#define WAVERECORD_FX 0x03 + +void emu10k1_reset_record(struct emu10k1_card *card, struct wavein_buffer *buffer); +void emu10k1_start_record(struct emu10k1_card *, struct wavein_buffer *); +void emu10k1_stop_record(struct emu10k1_card *, struct wavein_buffer *); +void emu10k1_set_record_src(struct emu10k1_card *, struct wiinst *wiinst); + +#endif /* _RECORDMGR_H */ diff --git a/sound/oss/emu10k1/timer.c b/sound/oss/emu10k1/timer.c new file mode 100644 index 000000000000..d10d30739f41 --- /dev/null +++ b/sound/oss/emu10k1/timer.c @@ -0,0 +1,176 @@ + +/* + ********************************************************************** + * timer.c + * Copyright (C) 1999, 2000 Creative Labs, 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. + * + ********************************************************************** + */ + +/* 3/6/2000 Improved support for different timer delays Rui Sousa */ + +/* 4/3/2000 Implemented timer list using list.h Rui Sousa */ + +#include "hwaccess.h" +#include "8010.h" +#include "irqmgr.h" +#include "timer.h" + +/* Try to schedule only once per fragment */ + +void emu10k1_timer_irqhandler(struct emu10k1_card *card) +{ + struct emu_timer *t; + struct list_head *entry; + + spin_lock(&card->timer_lock); + + list_for_each(entry, &card->timers) { + t = list_entry(entry, struct emu_timer, list); + + if (t->state & TIMER_STATE_ACTIVE) { + t->count++; + if (t->count == t->count_max) { + t->count = 0; + tasklet_hi_schedule(&t->tasklet); + } + } + } + + spin_unlock(&card->timer_lock); + + return; +} + +void emu10k1_timer_install(struct emu10k1_card *card, struct emu_timer *timer, u16 delay) +{ + struct emu_timer *t; + struct list_head *entry; + unsigned long flags; + + if (delay < 5) + delay = 5; + + timer->delay = delay; + timer->state = TIMER_STATE_INSTALLED; + + spin_lock_irqsave(&card->timer_lock, flags); + + timer->count_max = timer->delay / (card->timer_delay < 1024 ? card->timer_delay : 1024); + timer->count = timer->count_max - 1; + + list_add(&timer->list, &card->timers); + + if (card->timer_delay > delay) { + if (card->timer_delay == TIMER_STOPPED) + emu10k1_irq_enable(card, INTE_INTERVALTIMERENB); + + card->timer_delay = delay; + delay = (delay < 1024 ? delay : 1024); + + emu10k1_timer_set(card, delay); + + list_for_each(entry, &card->timers) { + t = list_entry(entry, struct emu_timer, list); + + t->count_max = t->delay / delay; + /* don't want to think much, just force scheduling + on the next interrupt */ + t->count = t->count_max - 1; + } + + DPD(2, "timer rate --> %u\n", delay); + } + + spin_unlock_irqrestore(&card->timer_lock, flags); + + return; +} + +void emu10k1_timer_uninstall(struct emu10k1_card *card, struct emu_timer *timer) +{ + struct emu_timer *t; + struct list_head *entry; + u16 delay = TIMER_STOPPED; + unsigned long flags; + + if (timer->state == TIMER_STATE_UNINSTALLED) + return; + + spin_lock_irqsave(&card->timer_lock, flags); + + list_del(&timer->list); + + list_for_each(entry, &card->timers) { + t = list_entry(entry, struct emu_timer, list); + + if (t->delay < delay) + delay = t->delay; + } + + if (card->timer_delay != delay) { + card->timer_delay = delay; + + if (delay == TIMER_STOPPED) + emu10k1_irq_disable(card, INTE_INTERVALTIMERENB); + else { + delay = (delay < 1024 ? delay : 1024); + + emu10k1_timer_set(card, delay); + + list_for_each(entry, &card->timers) { + t = list_entry(entry, struct emu_timer, list); + + t->count_max = t->delay / delay; + t->count = t->count_max - 1; + } + } + + DPD(2, "timer rate --> %u\n", delay); + } + + spin_unlock_irqrestore(&card->timer_lock, flags); + + timer->state = TIMER_STATE_UNINSTALLED; + + return; +} + +void emu10k1_timer_enable(struct emu10k1_card *card, struct emu_timer *timer) +{ + unsigned long flags; + + spin_lock_irqsave(&card->timer_lock, flags); + timer->state |= TIMER_STATE_ACTIVE; + spin_unlock_irqrestore(&card->timer_lock, flags); + + return; +} + +void emu10k1_timer_disable(struct emu10k1_card *card, struct emu_timer *timer) +{ + unsigned long flags; + + spin_lock_irqsave(&card->timer_lock, flags); + timer->state &= ~TIMER_STATE_ACTIVE; + spin_unlock_irqrestore(&card->timer_lock, flags); + + return; +} diff --git a/sound/oss/emu10k1/timer.h b/sound/oss/emu10k1/timer.h new file mode 100644 index 000000000000..b2543b4d53a8 --- /dev/null +++ b/sound/oss/emu10k1/timer.h @@ -0,0 +1,54 @@ +/* + ********************************************************************** + * timer.h + * Copyright (C) 1999, 2000 Creative Labs, 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 _TIMER_H +#define _TIMER_H + +#include +#include +#include "hwaccess.h" + +struct emu_timer +{ + struct list_head list; + struct tasklet_struct tasklet; + u8 state; + u16 count; /* current number of interrupts */ + u16 count_max; /* number of interrupts needed to schedule the bh */ + u16 delay; /* timer delay */ +}; + +void emu10k1_timer_install(struct emu10k1_card *, struct emu_timer *, u16); +void emu10k1_timer_uninstall(struct emu10k1_card *, struct emu_timer *); +void emu10k1_timer_enable(struct emu10k1_card *, struct emu_timer *); +void emu10k1_timer_disable(struct emu10k1_card *, struct emu_timer *); + +#define TIMER_STOPPED 0xffff +#define TIMER_STATE_INSTALLED 0x01 +#define TIMER_STATE_ACTIVE 0x02 +#define TIMER_STATE_UNINSTALLED 0x04 + +#endif /* _TIMER_H */ diff --git a/sound/oss/emu10k1/voicemgr.c b/sound/oss/emu10k1/voicemgr.c new file mode 100644 index 000000000000..d88b602c07c2 --- /dev/null +++ b/sound/oss/emu10k1/voicemgr.c @@ -0,0 +1,398 @@ +/* + ********************************************************************** + * voicemgr.c - Voice manager for emu10k1 driver + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 "voicemgr.h" +#include "8010.h" + +#define PITCH_48000 0x00004000 +#define PITCH_96000 0x00008000 +#define PITCH_85000 0x00007155 +#define PITCH_80726 0x00006ba2 +#define PITCH_67882 0x00005a82 +#define PITCH_57081 0x00004c1c + +static u32 emu10k1_select_interprom(struct emu10k1_card *card, + struct emu_voice *voice) +{ + if(voice->pitch_target==PITCH_48000) + return CCCA_INTERPROM_0; + else if(voice->pitch_targetpitch_target>=PITCH_96000) + return CCCA_INTERPROM_0; + else if(voice->pitch_target>=PITCH_85000) + return CCCA_INTERPROM_6; + else if(voice->pitch_target>=PITCH_80726) + return CCCA_INTERPROM_5; + else if(voice->pitch_target>=PITCH_67882) + return CCCA_INTERPROM_4; + else if(voice->pitch_target>=PITCH_57081) + return CCCA_INTERPROM_3; + else + return CCCA_INTERPROM_2; +} + + +/** + * emu10k1_voice_alloc_buffer - + * + * allocates the memory buffer for a voice. Two page tables are kept for each buffer. + * One (dma_handle) keeps track of the host memory pages used and the other (virtualpagetable) + * is passed to the device so that it can do DMA to host memory. + * + */ +int emu10k1_voice_alloc_buffer(struct emu10k1_card *card, struct voice_mem *mem, u32 pages) +{ + u32 pageindex, pagecount; + u32 busaddx; + int i; + + DPD(2, "requested pages is: %d\n", pages); + + if ((mem->emupageindex = emu10k1_addxmgr_alloc(pages * PAGE_SIZE, card)) < 0) + { + DPF(1, "couldn't allocate emu10k1 address space\n"); + return -1; + } + + /* Fill in virtual memory table */ + for (pagecount = 0; pagecount < pages; pagecount++) { + if ((mem->addr[pagecount] = pci_alloc_consistent(card->pci_dev, PAGE_SIZE, &mem->dma_handle[pagecount])) + == NULL) { + mem->pages = pagecount; + DPF(1, "couldn't allocate dma memory\n"); + return -1; + } + + DPD(2, "Virtual Addx: %p\n", mem->addr[pagecount]); + + for (i = 0; i < PAGE_SIZE / EMUPAGESIZE; i++) { + busaddx = (u32) mem->dma_handle[pagecount] + i * EMUPAGESIZE; + + DPD(3, "Bus Addx: %#x\n", busaddx); + + pageindex = mem->emupageindex + pagecount * PAGE_SIZE / EMUPAGESIZE + i; + + ((u32 *) card->virtualpagetable.addr)[pageindex] = cpu_to_le32((busaddx * 2) | pageindex); + } + } + + mem->pages = pagecount; + + return 0; +} + +/** + * emu10k1_voice_free_buffer - + * + * frees the memory buffer for a voice. + */ +void emu10k1_voice_free_buffer(struct emu10k1_card *card, struct voice_mem *mem) +{ + u32 pagecount, pageindex; + int i; + + if (mem->emupageindex < 0) + return; + + for (pagecount = 0; pagecount < mem->pages; pagecount++) { + pci_free_consistent(card->pci_dev, PAGE_SIZE, + mem->addr[pagecount], + mem->dma_handle[pagecount]); + + for (i = 0; i < PAGE_SIZE / EMUPAGESIZE; i++) { + pageindex = mem->emupageindex + pagecount * PAGE_SIZE / EMUPAGESIZE + i; + ((u32 *) card->virtualpagetable.addr)[pageindex] = + cpu_to_le32(((u32) card->silentpage.dma_handle * 2) | pageindex); + } + } + + emu10k1_addxmgr_free(card, mem->emupageindex); + mem->emupageindex = -1; +} + +int emu10k1_voice_alloc(struct emu10k1_card *card, struct emu_voice *voice) +{ + u8 *voicetable = card->voicetable; + int i; + unsigned long flags; + + DPF(2, "emu10k1_voice_alloc()\n"); + + spin_lock_irqsave(&card->lock, flags); + + if (voice->flags & VOICE_FLAGS_STEREO) { + for (i = 0; i < NUM_G; i += 2) + if ((voicetable[i] == VOICE_USAGE_FREE) && (voicetable[i + 1] == VOICE_USAGE_FREE)) { + voicetable[i] = voice->usage; + voicetable[i + 1] = voice->usage; + break; + } + } else { + for (i = 0; i < NUM_G; i++) + if (voicetable[i] == VOICE_USAGE_FREE) { + voicetable[i] = voice->usage; + break; + } + } + + spin_unlock_irqrestore(&card->lock, flags); + + if (i >= NUM_G) + return -1; + + voice->card = card; + voice->num = i; + + for (i = 0; i < (voice->flags & VOICE_FLAGS_STEREO ? 2 : 1); i++) { + DPD(2, " voice allocated -> %d\n", voice->num + i); + + sblive_writeptr_tag(card, voice->num + i, IFATN, 0xffff, + DCYSUSV, 0, + VTFT, 0x0000ffff, + PTRX, 0, + TAGLIST_END); + } + + return 0; +} + +void emu10k1_voice_free(struct emu_voice *voice) +{ + struct emu10k1_card *card = voice->card; + int i; + unsigned long flags; + + DPF(2, "emu10k1_voice_free()\n"); + + if (voice->usage == VOICE_USAGE_FREE) + return; + + for (i = 0; i < (voice->flags & VOICE_FLAGS_STEREO ? 2 : 1); i++) { + DPD(2, " voice released -> %d\n", voice->num + i); + + sblive_writeptr_tag(card, voice->num + i, DCYSUSV, 0, + VTFT, 0x0000ffff, + PTRX_PITCHTARGET, 0, + CVCF, 0x0000ffff, + //CPF, 0, + TAGLIST_END); + + sblive_writeptr(card, CPF, voice->num + i, 0); + } + + voice->usage = VOICE_USAGE_FREE; + + spin_lock_irqsave(&card->lock, flags); + + card->voicetable[voice->num] = VOICE_USAGE_FREE; + + if (voice->flags & VOICE_FLAGS_STEREO) + card->voicetable[voice->num + 1] = VOICE_USAGE_FREE; + + spin_unlock_irqrestore(&card->lock, flags); +} + +void emu10k1_voice_playback_setup(struct emu_voice *voice) +{ + struct emu10k1_card *card = voice->card; + u32 start; + int i; + + DPF(2, "emu10k1_voice_playback_setup()\n"); + + if (voice->flags & VOICE_FLAGS_STEREO) { + /* Set stereo bit */ + start = 28; + sblive_writeptr(card, CPF, voice->num, CPF_STEREO_MASK); + sblive_writeptr(card, CPF, voice->num + 1, CPF_STEREO_MASK); + } else { + start = 30; + sblive_writeptr(card, CPF, voice->num, 0); + } + + if(!(voice->flags & VOICE_FLAGS_16BIT)) + start *= 2; + + voice->start += start; + + for (i = 0; i < (voice->flags & VOICE_FLAGS_STEREO ? 2 : 1); i++) { + if (card->is_audigy) { + sblive_writeptr(card, A_FXRT1, voice->num + i, voice->params[i].send_routing); + sblive_writeptr(card, A_FXRT2, voice->num + i, voice->params[i].send_routing2); + sblive_writeptr(card, A_SENDAMOUNTS, voice->num + i, voice->params[i].send_hgfe); + } else { + sblive_writeptr(card, FXRT, voice->num + i, voice->params[i].send_routing << 16); + } + + /* Stop CA */ + /* Assumption that PT is already 0 so no harm overwriting */ + sblive_writeptr(card, PTRX, voice->num + i, ((voice->params[i].send_dcba & 0xff) << 8) + | ((voice->params[i].send_dcba & 0xff00) >> 8)); + + sblive_writeptr_tag(card, voice->num + i, + /* CSL, ST, CA */ + DSL, voice->endloop | (voice->params[i].send_dcba & 0xff000000), + PSST, voice->startloop | ((voice->params[i].send_dcba & 0x00ff0000) << 8), + CCCA, (voice->start) | emu10k1_select_interprom(card,voice) | + ((voice->flags & VOICE_FLAGS_16BIT) ? 0 : CCCA_8BITSELECT), + /* Clear filter delay memory */ + Z1, 0, + Z2, 0, + /* Invalidate maps */ + MAPA, MAP_PTI_MASK | ((u32) card->silentpage.dma_handle * 2), + MAPB, MAP_PTI_MASK | ((u32) card->silentpage.dma_handle * 2), + /* modulation envelope */ + CVCF, 0x0000ffff, + VTFT, 0x0000ffff, + ATKHLDM, 0, + DCYSUSM, 0x007f, + LFOVAL1, 0x8000, + LFOVAL2, 0x8000, + FMMOD, 0, + TREMFRQ, 0, + FM2FRQ2, 0, + ENVVAL, 0x8000, + /* volume envelope */ + ATKHLDV, 0x7f7f, + ENVVOL, 0x8000, + /* filter envelope */ + PEFE_FILTERAMOUNT, 0x7f, + /* pitch envelope */ + PEFE_PITCHAMOUNT, 0, TAGLIST_END); + + voice->params[i].fc_target = 0xffff; + } +} + +void emu10k1_voices_start(struct emu_voice *first_voice, unsigned int num_voices, int set) +{ + struct emu10k1_card *card = first_voice->card; + struct emu_voice *voice; + unsigned int voicenum; + int j; + + DPF(2, "emu10k1_voices_start()\n"); + + for (voicenum = 0; voicenum < num_voices; voicenum++) + { + voice = first_voice + voicenum; + + if (!set) { + u32 cra, ccis, cs, sample; + if (voice->flags & VOICE_FLAGS_STEREO) { + cra = 64; + ccis = 28; + cs = 4; + } else { + cra = 64; + ccis = 30; + cs = 2; + } + + if(voice->flags & VOICE_FLAGS_16BIT) { + sample = 0x00000000; + } else { + sample = 0x80808080; + ccis *= 2; + } + + for(j = 0; j < cs; j++) + sblive_writeptr(card, CD0 + j, voice->num, sample); + + /* Reset cache */ + sblive_writeptr(card, CCR_CACHEINVALIDSIZE, voice->num, 0); + if (voice->flags & VOICE_FLAGS_STEREO) + sblive_writeptr(card, CCR_CACHEINVALIDSIZE, voice->num + 1, 0); + + sblive_writeptr(card, CCR_READADDRESS, voice->num, cra); + + if (voice->flags & VOICE_FLAGS_STEREO) + sblive_writeptr(card, CCR_READADDRESS, voice->num + 1, cra); + + /* Fill cache */ + sblive_writeptr(card, CCR_CACHEINVALIDSIZE, voice->num, ccis); + } + + for (j = 0; j < (voice->flags & VOICE_FLAGS_STEREO ? 2 : 1); j++) { + sblive_writeptr_tag(card, voice->num + j, + IFATN, (voice->params[j].initial_fc << 8) | voice->params[j].initial_attn, + VTFT, (voice->params[j].volume_target << 16) | voice->params[j].fc_target, + CVCF, (voice->params[j].volume_target << 16) | voice->params[j].fc_target, + DCYSUSV, (voice->params[j].byampl_env_sustain << 8) | voice->params[j].byampl_env_decay, + TAGLIST_END); + + emu10k1_clear_stop_on_loop(card, voice->num + j); + } + } + + + for (voicenum = 0; voicenum < num_voices; voicenum++) + { + voice = first_voice + voicenum; + + for (j = 0; j < (voice->flags & VOICE_FLAGS_STEREO ? 2 : 1); j++) { + sblive_writeptr(card, PTRX_PITCHTARGET, voice->num + j, voice->pitch_target); + + if (j == 0) + sblive_writeptr(card, CPF_CURRENTPITCH, voice->num, voice->pitch_target); + + sblive_writeptr(card, IP, voice->num + j, voice->initial_pitch); + } + } +} + +void emu10k1_voices_stop(struct emu_voice *first_voice, int num_voices) +{ + struct emu10k1_card *card = first_voice->card; + struct emu_voice *voice; + unsigned int voice_num; + int j; + + DPF(2, "emu10k1_voice_stop()\n"); + + for (voice_num = 0; voice_num < num_voices; voice_num++) + { + voice = first_voice + voice_num; + + for (j = 0; j < (voice->flags & VOICE_FLAGS_STEREO ? 2 : 1); j++) { + sblive_writeptr_tag(card, voice->num + j, + PTRX_PITCHTARGET, 0, + CPF_CURRENTPITCH, 0, + IFATN, 0xffff, + VTFT, 0x0000ffff, + CVCF, 0x0000ffff, + IP, 0, + TAGLIST_END); + } + } +} + diff --git a/sound/oss/emu10k1/voicemgr.h b/sound/oss/emu10k1/voicemgr.h new file mode 100644 index 000000000000..099a8cb7f2c5 --- /dev/null +++ b/sound/oss/emu10k1/voicemgr.h @@ -0,0 +1,103 @@ +/* + ********************************************************************** + * sblive_voice.h -- EMU Voice Resource Manager header file + * Copyright 1999, 2000 Creative Labs, Inc. + * + ********************************************************************** + * + * Date Author Summary of changes + * ---- ------ ------------------ + * October 20, 1999 Bertrand Lee base code release + * + ********************************************************************** + * + * 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 _VOICEMGR_H +#define _VOICEMGR_H + +#include "hwaccess.h" + +/* struct emu_voice.usage flags */ +#define VOICE_USAGE_FREE 0x01 +#define VOICE_USAGE_MIDI 0x02 +#define VOICE_USAGE_PLAYBACK 0x04 + +/* struct emu_voice.flags flags */ +#define VOICE_FLAGS_STEREO 0x02 +#define VOICE_FLAGS_16BIT 0x04 + +struct voice_param +{ + /* FX bus amount send */ + + u32 send_routing; + // audigy only: + u32 send_routing2; + + u32 send_dcba; + // audigy only: + u32 send_hgfe; + + + u32 initial_fc; + u32 fc_target; + + u32 initial_attn; + u32 volume_target; + + u32 byampl_env_sustain; + u32 byampl_env_decay; +}; + +struct voice_mem { + int emupageindex; + void *addr[BUFMAXPAGES]; + dma_addr_t dma_handle[BUFMAXPAGES]; + u32 pages; +}; + +struct emu_voice +{ + struct emu10k1_card *card; + u8 usage; /* Free, MIDI, playback */ + u8 num; /* Voice ID */ + u8 flags; /* Stereo/mono, 8/16 bit */ + + u32 startloop; + u32 endloop; + u32 start; + + u32 initial_pitch; + u32 pitch_target; + + struct voice_param params[2]; + + struct voice_mem mem; +}; + +int emu10k1_voice_alloc_buffer(struct emu10k1_card *, struct voice_mem *, u32); +void emu10k1_voice_free_buffer(struct emu10k1_card *, struct voice_mem *); +int emu10k1_voice_alloc(struct emu10k1_card *, struct emu_voice *); +void emu10k1_voice_free(struct emu_voice *); +void emu10k1_voice_playback_setup(struct emu_voice *); +void emu10k1_voices_start(struct emu_voice *, unsigned int, int); +void emu10k1_voices_stop(struct emu_voice *, int); + +#endif /* _VOICEMGR_H */ diff --git a/sound/oss/es1370.c b/sound/oss/es1370.c new file mode 100644 index 000000000000..056091cff266 --- /dev/null +++ b/sound/oss/es1370.c @@ -0,0 +1,2789 @@ +/*****************************************************************************/ + +/* + * es1370.c -- Ensoniq ES1370/Asahi Kasei AK4531 audio driver. + * + * Copyright (C) 1998-2001, 2003 Thomas Sailer (t.sailer@alumni.ethz.ch) + * + * 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. + * + * Special thanks to David C. Niemi + * + * + * Module command line parameters: + * lineout if 1 the LINE jack is used as an output instead of an input. + * LINE then contains the unmixed dsp output. This can be used + * to make the card a four channel one: use dsp to output two + * channels to LINE and dac to output the other two channels to + * SPKR. Set the mixer to only output synth to SPKR. + * micbias sets the +5V bias to the mic if using an electretmic. + * + * + * Note: sync mode is not yet supported (i.e. running dsp and dac from the same + * clock source) + * + * Supported devices: + * /dev/dsp standard /dev/dsp device, (mostly) OSS compatible + * /dev/mixer standard /dev/mixer device, (mostly) OSS compatible + * /dev/dsp1 additional DAC, like /dev/dsp, but output only, + * only 5512, 11025, 22050 and 44100 samples/s, + * outputs to mixer "SYNTH" setting + * /dev/midi simple MIDI UART interface, no ioctl + * + * NOTE: the card does not have any FM/Wavetable synthesizer, it is supposed + * to be done in software. That is what /dev/dac is for. By now (Q2 1998) + * there are several MIDI to PCM (WAV) packages, one of them is timidity. + * + * Revision history + * 26.03.1998 0.1 Initial release + * 31.03.1998 0.2 Fix bug in GETOSPACE + * 04.04.1998 0.3 Make it work (again) under 2.0.33 + * Fix mixer write operation not returning the actual + * settings + * 05.04.1998 0.4 First attempt at using the new PCI stuff + * 29.04.1998 0.5 Fix hang when ^C is pressed on amp + * 07.05.1998 0.6 Don't double lock around stop_*() in *_release() + * 10.05.1998 0.7 First stab at a simple midi interface (no bells&whistles) + * 14.05.1998 0.8 Don't allow excessive interrupt rates + * 08.06.1998 0.9 First release using Alan Cox' soundcore instead of + * miscdevice + * 05.07.1998 0.10 Fixed the driver to correctly maintin OSS style volume + * settings (not sure if this should be standard) + * Fixed many references: f_flags should be f_mode + * -- Gerald Britton + * 03.08.1998 0.11 Now mixer behaviour can basically be selected between + * "OSS documented" and "OSS actual" behaviour + * Fixed mixer table thanks to Hakan.Lennestal@lu.erisoft.se + * On module startup, set DAC2 to 11kSPS instead of 5.5kSPS, + * as it produces an annoying ssssh in the lower sampling rate + * Do not include modversions.h + * 22.08.1998 0.12 Mixer registers actually have 5 instead of 4 bits + * pointed out by Itai Nahshon + * 31.08.1998 0.13 Fix realplayer problems - dac.count issues + * 08.10.1998 0.14 Joystick support fixed + * -- Oliver Neukum + * 10.12.1998 0.15 Fix drain_dac trying to wait on not yet initialized DMA + * 16.12.1998 0.16 Don't wake up app until there are fragsize bytes to read/write + * 06.01.1999 0.17 remove the silly SA_INTERRUPT flag. + * hopefully killed the egcs section type conflict + * 12.03.1999 0.18 cinfo.blocks should be reset after GETxPTR ioctl. + * reported by Johan Maes + * 22.03.1999 0.19 return EAGAIN instead of EBUSY when O_NONBLOCK + * read/write cannot be executed + * 07.04.1999 0.20 implemented the following ioctl's: SOUND_PCM_READ_RATE, + * SOUND_PCM_READ_CHANNELS, SOUND_PCM_READ_BITS; + * Alpha fixes reported by Peter Jones + * Note: joystick address handling might still be wrong on archs + * other than i386 + * 10.05.1999 0.21 Added support for an electret mic for SB PCI64 + * to the Linux kernel sound driver. This mod also straighten + * out the question marks around the mic impedance setting + * (micz). From Kim.Berts@fisub.mail.abb.com + * 11.05.1999 0.22 Implemented the IMIX call to mute recording monitor. + * Guenter Geiger + * 15.06.1999 0.23 Fix bad allocation bug. + * Thanks to Deti Fliegl + * 28.06.1999 0.24 Add pci_set_master + * 02.08.1999 0.25 Added workaround for the "phantom write" bug first + * documented by Dave Sharpless from Anchor Games + * 03.08.1999 0.26 adapt to Linus' new __setup/__initcall + * added kernel command line option "es1370=joystick[,lineout[,micbias]]" + * removed CONFIG_SOUND_ES1370_JOYPORT_BOOT kludge + * 12.08.1999 0.27 module_init/__setup fixes + * 19.08.1999 0.28 SOUND_MIXER_IMIX fixes, reported by Gianluca + * 31.08.1999 0.29 add spin_lock_init + * replaced current->state = x with set_current_state(x) + * 03.09.1999 0.30 change read semantics for MIDI to match + * OSS more closely; remove possible wakeup race + * 28.10.1999 0.31 More waitqueue races fixed + * 08.01.2000 0.32 Prevent some ioctl's from returning bad count values on underrun/overrun; + * Tim Janik's BSE (Bedevilled Sound Engine) found this + * 07.02.2000 0.33 Use pci_alloc_consistent and pci_register_driver + * 21.11.2000 0.34 Initialize dma buffers in poll, otherwise poll may return a bogus mask + * 12.12.2000 0.35 More dma buffer initializations, patch from + * Tjeerd Mulder + * 07.01.2001 0.36 Timeout change in wrcodec as requested by Frank Klemm + * 31.01.2001 0.37 Register/Unregister gameport + * Fix SETTRIGGER non OSS API conformity + * 03.01.2003 0.38 open_mode fixes from Georg Acher + * + * some important things missing in Ensoniq documentation: + * + * Experimental PCLKDIV results: play the same waveforms on both DAC1 and DAC2 + * and vary PCLKDIV to obtain zero beat. + * 5512sps: 254 + * 44100sps: 30 + * seems to be fs = 1411200/(PCLKDIV+2) + * + * should find out when curr_sample_ct is cleared and + * where exactly the CCB fetches data + * + * The card uses a 22.5792 MHz crystal. + * The LINEIN jack may be converted to an AOUT jack by + * setting pin 47 (XCTL0) of the ES1370 to high. + * Pin 48 (XCTL1) of the ES1370 sets the +5V bias for an electretmic + * + * + */ + +/*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* --------------------------------------------------------------------- */ + +#undef OSS_DOCUMENTED_MIXER_SEMANTICS +#define DBG(x) {} +/*#define DBG(x) {x}*/ + +/* --------------------------------------------------------------------- */ + +#ifndef PCI_VENDOR_ID_ENSONIQ +#define PCI_VENDOR_ID_ENSONIQ 0x1274 +#endif + +#ifndef PCI_DEVICE_ID_ENSONIQ_ES1370 +#define PCI_DEVICE_ID_ENSONIQ_ES1370 0x5000 +#endif + +#define ES1370_MAGIC ((PCI_VENDOR_ID_ENSONIQ<<16)|PCI_DEVICE_ID_ENSONIQ_ES1370) + +#define ES1370_EXTENT 0x40 +#define JOY_EXTENT 8 + +#define ES1370_REG_CONTROL 0x00 +#define ES1370_REG_STATUS 0x04 +#define ES1370_REG_UART_DATA 0x08 +#define ES1370_REG_UART_STATUS 0x09 +#define ES1370_REG_UART_CONTROL 0x09 +#define ES1370_REG_UART_TEST 0x0a +#define ES1370_REG_MEMPAGE 0x0c +#define ES1370_REG_CODEC 0x10 +#define ES1370_REG_SERIAL_CONTROL 0x20 +#define ES1370_REG_DAC1_SCOUNT 0x24 +#define ES1370_REG_DAC2_SCOUNT 0x28 +#define ES1370_REG_ADC_SCOUNT 0x2c + +#define ES1370_REG_DAC1_FRAMEADR 0xc30 +#define ES1370_REG_DAC1_FRAMECNT 0xc34 +#define ES1370_REG_DAC2_FRAMEADR 0xc38 +#define ES1370_REG_DAC2_FRAMECNT 0xc3c +#define ES1370_REG_ADC_FRAMEADR 0xd30 +#define ES1370_REG_ADC_FRAMECNT 0xd34 +#define ES1370_REG_PHANTOM_FRAMEADR 0xd38 +#define ES1370_REG_PHANTOM_FRAMECNT 0xd3c + +#define ES1370_FMT_U8_MONO 0 +#define ES1370_FMT_U8_STEREO 1 +#define ES1370_FMT_S16_MONO 2 +#define ES1370_FMT_S16_STEREO 3 +#define ES1370_FMT_STEREO 1 +#define ES1370_FMT_S16 2 +#define ES1370_FMT_MASK 3 + +static const unsigned sample_size[] = { 1, 2, 2, 4 }; +static const unsigned sample_shift[] = { 0, 1, 1, 2 }; + +static const unsigned dac1_samplerate[] = { 5512, 11025, 22050, 44100 }; + +#define DAC2_SRTODIV(x) (((1411200+(x)/2)/(x))-2) +#define DAC2_DIVTOSR(x) (1411200/((x)+2)) + +#define CTRL_ADC_STOP 0x80000000 /* 1 = ADC stopped */ +#define CTRL_XCTL1 0x40000000 /* electret mic bias */ +#define CTRL_OPEN 0x20000000 /* no function, can be read and written */ +#define CTRL_PCLKDIV 0x1fff0000 /* ADC/DAC2 clock divider */ +#define CTRL_SH_PCLKDIV 16 +#define CTRL_MSFMTSEL 0x00008000 /* MPEG serial data fmt: 0 = Sony, 1 = I2S */ +#define CTRL_M_SBB 0x00004000 /* DAC2 clock: 0 = PCLKDIV, 1 = MPEG */ +#define CTRL_WTSRSEL 0x00003000 /* DAC1 clock freq: 0=5512, 1=11025, 2=22050, 3=44100 */ +#define CTRL_SH_WTSRSEL 12 +#define CTRL_DAC_SYNC 0x00000800 /* 1 = DAC2 runs off DAC1 clock */ +#define CTRL_CCB_INTRM 0x00000400 /* 1 = CCB "voice" ints enabled */ +#define CTRL_M_CB 0x00000200 /* recording source: 0 = ADC, 1 = MPEG */ +#define CTRL_XCTL0 0x00000100 /* 0 = Line in, 1 = Line out */ +#define CTRL_BREQ 0x00000080 /* 1 = test mode (internal mem test) */ +#define CTRL_DAC1_EN 0x00000040 /* enable DAC1 */ +#define CTRL_DAC2_EN 0x00000020 /* enable DAC2 */ +#define CTRL_ADC_EN 0x00000010 /* enable ADC */ +#define CTRL_UART_EN 0x00000008 /* enable MIDI uart */ +#define CTRL_JYSTK_EN 0x00000004 /* enable Joystick port (presumably at address 0x200) */ +#define CTRL_CDC_EN 0x00000002 /* enable serial (CODEC) interface */ +#define CTRL_SERR_DIS 0x00000001 /* 1 = disable PCI SERR signal */ + +#define STAT_INTR 0x80000000 /* wired or of all interrupt bits */ +#define STAT_CSTAT 0x00000400 /* 1 = codec busy or codec write in progress */ +#define STAT_CBUSY 0x00000200 /* 1 = codec busy */ +#define STAT_CWRIP 0x00000100 /* 1 = codec write in progress */ +#define STAT_VC 0x00000060 /* CCB int source, 0=DAC1, 1=DAC2, 2=ADC, 3=undef */ +#define STAT_SH_VC 5 +#define STAT_MCCB 0x00000010 /* CCB int pending */ +#define STAT_UART 0x00000008 /* UART int pending */ +#define STAT_DAC1 0x00000004 /* DAC1 int pending */ +#define STAT_DAC2 0x00000002 /* DAC2 int pending */ +#define STAT_ADC 0x00000001 /* ADC int pending */ + +#define USTAT_RXINT 0x80 /* UART rx int pending */ +#define USTAT_TXINT 0x04 /* UART tx int pending */ +#define USTAT_TXRDY 0x02 /* UART tx ready */ +#define USTAT_RXRDY 0x01 /* UART rx ready */ + +#define UCTRL_RXINTEN 0x80 /* 1 = enable RX ints */ +#define UCTRL_TXINTEN 0x60 /* TX int enable field mask */ +#define UCTRL_ENA_TXINT 0x20 /* enable TX int */ +#define UCTRL_CNTRL 0x03 /* control field */ +#define UCTRL_CNTRL_SWR 0x03 /* software reset command */ + +#define SCTRL_P2ENDINC 0x00380000 /* */ +#define SCTRL_SH_P2ENDINC 19 +#define SCTRL_P2STINC 0x00070000 /* */ +#define SCTRL_SH_P2STINC 16 +#define SCTRL_R1LOOPSEL 0x00008000 /* 0 = loop mode */ +#define SCTRL_P2LOOPSEL 0x00004000 /* 0 = loop mode */ +#define SCTRL_P1LOOPSEL 0x00002000 /* 0 = loop mode */ +#define SCTRL_P2PAUSE 0x00001000 /* 1 = pause mode */ +#define SCTRL_P1PAUSE 0x00000800 /* 1 = pause mode */ +#define SCTRL_R1INTEN 0x00000400 /* enable interrupt */ +#define SCTRL_P2INTEN 0x00000200 /* enable interrupt */ +#define SCTRL_P1INTEN 0x00000100 /* enable interrupt */ +#define SCTRL_P1SCTRLD 0x00000080 /* reload sample count register for DAC1 */ +#define SCTRL_P2DACSEN 0x00000040 /* 1 = DAC2 play back last sample when disabled */ +#define SCTRL_R1SEB 0x00000020 /* 1 = 16bit */ +#define SCTRL_R1SMB 0x00000010 /* 1 = stereo */ +#define SCTRL_R1FMT 0x00000030 /* format mask */ +#define SCTRL_SH_R1FMT 4 +#define SCTRL_P2SEB 0x00000008 /* 1 = 16bit */ +#define SCTRL_P2SMB 0x00000004 /* 1 = stereo */ +#define SCTRL_P2FMT 0x0000000c /* format mask */ +#define SCTRL_SH_P2FMT 2 +#define SCTRL_P1SEB 0x00000002 /* 1 = 16bit */ +#define SCTRL_P1SMB 0x00000001 /* 1 = stereo */ +#define SCTRL_P1FMT 0x00000003 /* format mask */ +#define SCTRL_SH_P1FMT 0 + +/* misc stuff */ + +#define FMODE_DAC 4 /* slight misuse of mode_t */ + +/* MIDI buffer sizes */ + +#define MIDIINBUF 256 +#define MIDIOUTBUF 256 + +#define FMODE_MIDI_SHIFT 3 +#define FMODE_MIDI_READ (FMODE_READ << FMODE_MIDI_SHIFT) +#define FMODE_MIDI_WRITE (FMODE_WRITE << FMODE_MIDI_SHIFT) + +/* --------------------------------------------------------------------- */ + +struct es1370_state { + /* magic */ + unsigned int magic; + + /* list of es1370 devices */ + struct list_head devs; + + /* the corresponding pci_dev structure */ + struct pci_dev *dev; + + /* soundcore stuff */ + int dev_audio; + int dev_mixer; + int dev_dac; + int dev_midi; + + /* hardware resources */ + unsigned long io; /* long for SPARC */ + unsigned int irq; + + /* mixer registers; there is no HW readback */ + struct { + unsigned short vol[10]; + unsigned int recsrc; + unsigned int modcnt; + unsigned short micpreamp; + unsigned int imix; + } mix; + + /* wave stuff */ + unsigned ctrl; + unsigned sctrl; + + spinlock_t lock; + struct semaphore open_sem; + mode_t open_mode; + wait_queue_head_t open_wait; + + struct dmabuf { + void *rawbuf; + dma_addr_t dmaaddr; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + unsigned hwptr, swptr; + unsigned total_bytes; + int count; + unsigned error; /* over/underrun */ + wait_queue_head_t wait; + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dmasize; + unsigned fragsamples; + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned endcleared:1; + unsigned enabled:1; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + } dma_dac1, dma_dac2, dma_adc; + + /* The following buffer is used to point the phantom write channel to. */ + unsigned char *bugbuf_cpu; + dma_addr_t bugbuf_dma; + + /* midi stuff */ + struct { + unsigned ird, iwr, icnt; + unsigned ord, owr, ocnt; + wait_queue_head_t iwait; + wait_queue_head_t owait; + unsigned char ibuf[MIDIINBUF]; + unsigned char obuf[MIDIOUTBUF]; + } midi; + + struct gameport *gameport; + struct semaphore sem; +}; + +/* --------------------------------------------------------------------- */ + +static LIST_HEAD(devs); + +/* --------------------------------------------------------------------- */ + +static inline unsigned ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +/* --------------------------------------------------------------------- */ + +static void wrcodec(struct es1370_state *s, unsigned char idx, unsigned char data) +{ + unsigned long tmo = jiffies + HZ/10, j; + + do { + j = jiffies; + if (!(inl(s->io+ES1370_REG_STATUS) & STAT_CSTAT)) { + outw((((unsigned short)idx)<<8)|data, s->io+ES1370_REG_CODEC); + return; + } + schedule(); + } while ((signed)(tmo-j) > 0); + printk(KERN_ERR "es1370: write to codec register timeout\n"); +} + +/* --------------------------------------------------------------------- */ + +static inline void stop_adc(struct es1370_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->ctrl &= ~CTRL_ADC_EN; + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); +} + +static inline void stop_dac1(struct es1370_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->ctrl &= ~CTRL_DAC1_EN; + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); +} + +static inline void stop_dac2(struct es1370_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->ctrl &= ~CTRL_DAC2_EN; + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_dac1(struct es1370_state *s) +{ + unsigned long flags; + unsigned fragremain, fshift; + + spin_lock_irqsave(&s->lock, flags); + if (!(s->ctrl & CTRL_DAC1_EN) && (s->dma_dac1.mapped || s->dma_dac1.count > 0) + && s->dma_dac1.ready) { + s->ctrl |= CTRL_DAC1_EN; + s->sctrl = (s->sctrl & ~(SCTRL_P1LOOPSEL | SCTRL_P1PAUSE | SCTRL_P1SCTRLD)) | SCTRL_P1INTEN; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + fragremain = ((- s->dma_dac1.hwptr) & (s->dma_dac1.fragsize-1)); + fshift = sample_shift[(s->sctrl & SCTRL_P1FMT) >> SCTRL_SH_P1FMT]; + if (fragremain < 2*fshift) + fragremain = s->dma_dac1.fragsize; + outl((fragremain >> fshift) - 1, s->io+ES1370_REG_DAC1_SCOUNT); + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + outl((s->dma_dac1.fragsize >> fshift) - 1, s->io+ES1370_REG_DAC1_SCOUNT); + } + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_dac2(struct es1370_state *s) +{ + unsigned long flags; + unsigned fragremain, fshift; + + spin_lock_irqsave(&s->lock, flags); + if (!(s->ctrl & CTRL_DAC2_EN) && (s->dma_dac2.mapped || s->dma_dac2.count > 0) + && s->dma_dac2.ready) { + s->ctrl |= CTRL_DAC2_EN; + s->sctrl = (s->sctrl & ~(SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | SCTRL_P2DACSEN | + SCTRL_P2ENDINC | SCTRL_P2STINC)) | SCTRL_P2INTEN | + (((s->sctrl & SCTRL_P2FMT) ? 2 : 1) << SCTRL_SH_P2ENDINC) | + (0 << SCTRL_SH_P2STINC); + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + fragremain = ((- s->dma_dac2.hwptr) & (s->dma_dac2.fragsize-1)); + fshift = sample_shift[(s->sctrl & SCTRL_P2FMT) >> SCTRL_SH_P2FMT]; + if (fragremain < 2*fshift) + fragremain = s->dma_dac2.fragsize; + outl((fragremain >> fshift) - 1, s->io+ES1370_REG_DAC2_SCOUNT); + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + outl((s->dma_dac2.fragsize >> fshift) - 1, s->io+ES1370_REG_DAC2_SCOUNT); + } + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_adc(struct es1370_state *s) +{ + unsigned long flags; + unsigned fragremain, fshift; + + spin_lock_irqsave(&s->lock, flags); + if (!(s->ctrl & CTRL_ADC_EN) && (s->dma_adc.mapped || s->dma_adc.count < (signed)(s->dma_adc.dmasize - 2*s->dma_adc.fragsize)) + && s->dma_adc.ready) { + s->ctrl |= CTRL_ADC_EN; + s->sctrl = (s->sctrl & ~SCTRL_R1LOOPSEL) | SCTRL_R1INTEN; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + fragremain = ((- s->dma_adc.hwptr) & (s->dma_adc.fragsize-1)); + fshift = sample_shift[(s->sctrl & SCTRL_R1FMT) >> SCTRL_SH_R1FMT]; + if (fragremain < 2*fshift) + fragremain = s->dma_adc.fragsize; + outl((fragremain >> fshift) - 1, s->io+ES1370_REG_ADC_SCOUNT); + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + outl((s->dma_adc.fragsize >> fshift) - 1, s->io+ES1370_REG_ADC_SCOUNT); + } + spin_unlock_irqrestore(&s->lock, flags); +} + +/* --------------------------------------------------------------------- */ + +#define DMABUF_DEFAULTORDER (17-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +static inline void dealloc_dmabuf(struct es1370_state *s, struct dmabuf *db) +{ + struct page *page, *pend; + + if (db->rawbuf) { + /* undo marking the pages as reserved */ + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + ClearPageReserved(page); + pci_free_consistent(s->dev, PAGE_SIZE << db->buforder, db->rawbuf, db->dmaaddr); + } + db->rawbuf = NULL; + db->mapped = db->ready = 0; +} + +static int prog_dmabuf(struct es1370_state *s, struct dmabuf *db, unsigned rate, unsigned fmt, unsigned reg) +{ + int order; + unsigned bytepersec; + unsigned bufs; + struct page *page, *pend; + + db->hwptr = db->swptr = db->total_bytes = db->count = db->error = db->endcleared = 0; + if (!db->rawbuf) { + db->ready = db->mapped = 0; + for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--) + if ((db->rawbuf = pci_alloc_consistent(s->dev, PAGE_SIZE << order, &db->dmaaddr))) + break; + if (!db->rawbuf) + return -ENOMEM; + db->buforder = order; + /* now mark the pages as reserved; otherwise remap_pfn_range doesn't do what we want */ + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + SetPageReserved(page); + } + fmt &= ES1370_FMT_MASK; + bytepersec = rate << sample_shift[fmt]; + bufs = PAGE_SIZE << db->buforder; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < bytepersec) + db->fragshift = ld2(bytepersec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(bytepersec/100/(db->subdivision ? db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + db->numfrag = bufs >> db->fragshift; + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->numfrag = bufs >> db->fragshift; + } + db->fragsize = 1 << db->fragshift; + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; + db->fragsamples = db->fragsize >> sample_shift[fmt]; + db->dmasize = db->numfrag << db->fragshift; + memset(db->rawbuf, (fmt & ES1370_FMT_S16) ? 0 : 0x80, db->dmasize); + outl((reg >> 8) & 15, s->io+ES1370_REG_MEMPAGE); + outl(db->dmaaddr, s->io+(reg & 0xff)); + outl((db->dmasize >> 2)-1, s->io+((reg + 4) & 0xff)); + db->enabled = 1; + db->ready = 1; + return 0; +} + +static inline int prog_dmabuf_adc(struct es1370_state *s) +{ + stop_adc(s); + return prog_dmabuf(s, &s->dma_adc, DAC2_DIVTOSR((s->ctrl & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV), + (s->sctrl >> SCTRL_SH_R1FMT) & ES1370_FMT_MASK, ES1370_REG_ADC_FRAMEADR); +} + +static inline int prog_dmabuf_dac2(struct es1370_state *s) +{ + stop_dac2(s); + return prog_dmabuf(s, &s->dma_dac2, DAC2_DIVTOSR((s->ctrl & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV), + (s->sctrl >> SCTRL_SH_P2FMT) & ES1370_FMT_MASK, ES1370_REG_DAC2_FRAMEADR); +} + +static inline int prog_dmabuf_dac1(struct es1370_state *s) +{ + stop_dac1(s); + return prog_dmabuf(s, &s->dma_dac1, dac1_samplerate[(s->ctrl & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL], + (s->sctrl >> SCTRL_SH_P1FMT) & ES1370_FMT_MASK, ES1370_REG_DAC1_FRAMEADR); +} + +static inline unsigned get_hwptr(struct es1370_state *s, struct dmabuf *db, unsigned reg) +{ + unsigned hwptr, diff; + + outl((reg >> 8) & 15, s->io+ES1370_REG_MEMPAGE); + hwptr = (inl(s->io+(reg & 0xff)) >> 14) & 0x3fffc; + diff = (db->dmasize + hwptr - db->hwptr) % db->dmasize; + db->hwptr = hwptr; + return diff; +} + +static inline 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; + } + memset(((char *)buf) + bptr, c, len); +} + +/* call with spinlock held! */ +static void es1370_update_ptr(struct es1370_state *s) +{ + int diff; + + /* update ADC pointer */ + if (s->ctrl & CTRL_ADC_EN) { + diff = get_hwptr(s, &s->dma_adc, ES1370_REG_ADC_FRAMECNT); + s->dma_adc.total_bytes += diff; + s->dma_adc.count += diff; + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + wake_up(&s->dma_adc.wait); + if (!s->dma_adc.mapped) { + if (s->dma_adc.count > (signed)(s->dma_adc.dmasize - ((3 * s->dma_adc.fragsize) >> 1))) { + s->ctrl &= ~CTRL_ADC_EN; + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + s->dma_adc.error++; + } + } + } + /* update DAC1 pointer */ + if (s->ctrl & CTRL_DAC1_EN) { + diff = get_hwptr(s, &s->dma_dac1, ES1370_REG_DAC1_FRAMECNT); + s->dma_dac1.total_bytes += diff; + if (s->dma_dac1.mapped) { + s->dma_dac1.count += diff; + if (s->dma_dac1.count >= (signed)s->dma_dac1.fragsize) + wake_up(&s->dma_dac1.wait); + } else { + s->dma_dac1.count -= diff; + if (s->dma_dac1.count <= 0) { + s->ctrl &= ~CTRL_DAC1_EN; + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + s->dma_dac1.error++; + } else if (s->dma_dac1.count <= (signed)s->dma_dac1.fragsize && !s->dma_dac1.endcleared) { + clear_advance(s->dma_dac1.rawbuf, s->dma_dac1.dmasize, s->dma_dac1.swptr, + s->dma_dac1.fragsize, (s->sctrl & SCTRL_P1SEB) ? 0 : 0x80); + s->dma_dac1.endcleared = 1; + } + if (s->dma_dac1.count + (signed)s->dma_dac1.fragsize <= (signed)s->dma_dac1.dmasize) + wake_up(&s->dma_dac1.wait); + } + } + /* update DAC2 pointer */ + if (s->ctrl & CTRL_DAC2_EN) { + diff = get_hwptr(s, &s->dma_dac2, ES1370_REG_DAC2_FRAMECNT); + s->dma_dac2.total_bytes += diff; + if (s->dma_dac2.mapped) { + s->dma_dac2.count += diff; + if (s->dma_dac2.count >= (signed)s->dma_dac2.fragsize) + wake_up(&s->dma_dac2.wait); + } else { + s->dma_dac2.count -= diff; + if (s->dma_dac2.count <= 0) { + s->ctrl &= ~CTRL_DAC2_EN; + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + s->dma_dac2.error++; + } else if (s->dma_dac2.count <= (signed)s->dma_dac2.fragsize && !s->dma_dac2.endcleared) { + clear_advance(s->dma_dac2.rawbuf, s->dma_dac2.dmasize, s->dma_dac2.swptr, + s->dma_dac2.fragsize, (s->sctrl & SCTRL_P2SEB) ? 0 : 0x80); + s->dma_dac2.endcleared = 1; + } + if (s->dma_dac2.count + (signed)s->dma_dac2.fragsize <= (signed)s->dma_dac2.dmasize) + wake_up(&s->dma_dac2.wait); + } + } +} + +/* hold spinlock for the following! */ +static void es1370_handle_midi(struct es1370_state *s) +{ + unsigned char ch; + int wake; + + if (!(s->ctrl & CTRL_UART_EN)) + return; + wake = 0; + while (inb(s->io+ES1370_REG_UART_STATUS) & USTAT_RXRDY) { + ch = inb(s->io+ES1370_REG_UART_DATA); + if (s->midi.icnt < MIDIINBUF) { + s->midi.ibuf[s->midi.iwr] = ch; + s->midi.iwr = (s->midi.iwr + 1) % MIDIINBUF; + s->midi.icnt++; + } + wake = 1; + } + if (wake) + wake_up(&s->midi.iwait); + wake = 0; + while ((inb(s->io+ES1370_REG_UART_STATUS) & USTAT_TXRDY) && s->midi.ocnt > 0) { + outb(s->midi.obuf[s->midi.ord], s->io+ES1370_REG_UART_DATA); + s->midi.ord = (s->midi.ord + 1) % MIDIOUTBUF; + s->midi.ocnt--; + if (s->midi.ocnt < MIDIOUTBUF-16) + wake = 1; + } + if (wake) + wake_up(&s->midi.owait); + outb((s->midi.ocnt > 0) ? UCTRL_RXINTEN | UCTRL_ENA_TXINT : UCTRL_RXINTEN, s->io+ES1370_REG_UART_CONTROL); +} + +static irqreturn_t es1370_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct es1370_state *s = (struct es1370_state *)dev_id; + unsigned int intsrc, sctl; + + /* fastpath out, to ease interrupt sharing */ + intsrc = inl(s->io+ES1370_REG_STATUS); + if (!(intsrc & 0x80000000)) + return IRQ_NONE; + spin_lock(&s->lock); + /* clear audio interrupts first */ + sctl = s->sctrl; + if (intsrc & STAT_ADC) + sctl &= ~SCTRL_R1INTEN; + if (intsrc & STAT_DAC1) + sctl &= ~SCTRL_P1INTEN; + if (intsrc & STAT_DAC2) + sctl &= ~SCTRL_P2INTEN; + outl(sctl, s->io+ES1370_REG_SERIAL_CONTROL); + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + es1370_update_ptr(s); + es1370_handle_midi(s); + spin_unlock(&s->lock); + return IRQ_HANDLED; +} + +/* --------------------------------------------------------------------- */ + +static const char invalid_magic[] = KERN_CRIT "es1370: invalid magic value\n"; + +#define VALIDATE_STATE(s) \ +({ \ + if (!(s) || (s)->magic != ES1370_MAGIC) { \ + printk(invalid_magic); \ + return -ENXIO; \ + } \ +}) + +/* --------------------------------------------------------------------- */ + +static const struct { + unsigned volidx:4; + unsigned left:4; + unsigned right:4; + unsigned stereo:1; + unsigned recmask:13; + unsigned avail:1; +} mixtable[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = { 0, 0x0, 0x1, 1, 0x0000, 1 }, /* master */ + [SOUND_MIXER_PCM] = { 1, 0x2, 0x3, 1, 0x0400, 1 }, /* voice */ + [SOUND_MIXER_SYNTH] = { 2, 0x4, 0x5, 1, 0x0060, 1 }, /* FM */ + [SOUND_MIXER_CD] = { 3, 0x6, 0x7, 1, 0x0006, 1 }, /* CD */ + [SOUND_MIXER_LINE] = { 4, 0x8, 0x9, 1, 0x0018, 1 }, /* Line */ + [SOUND_MIXER_LINE1] = { 5, 0xa, 0xb, 1, 0x1800, 1 }, /* AUX */ + [SOUND_MIXER_LINE2] = { 6, 0xc, 0x0, 0, 0x0100, 1 }, /* Mono1 */ + [SOUND_MIXER_LINE3] = { 7, 0xd, 0x0, 0, 0x0200, 1 }, /* Mono2 */ + [SOUND_MIXER_MIC] = { 8, 0xe, 0x0, 0, 0x0001, 1 }, /* Mic */ + [SOUND_MIXER_OGAIN] = { 9, 0xf, 0x0, 0, 0x0000, 1 } /* mono out */ +}; + +static void set_recsrc(struct es1370_state *s, unsigned int val) +{ + unsigned int i, j; + + for (j = i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!(val & (1 << i))) + continue; + if (!mixtable[i].recmask) { + val &= ~(1 << i); + continue; + } + j |= mixtable[i].recmask; + } + s->mix.recsrc = val; + wrcodec(s, 0x12, j & 0xd5); + wrcodec(s, 0x13, j & 0xaa); + wrcodec(s, 0x14, (j >> 8) & 0x17); + wrcodec(s, 0x15, (j >> 8) & 0x0f); + i = (j & 0x37f) | ((j << 1) & 0x3000) | 0xc60; + if (!s->mix.imix) { + i &= 0xff60; /* mute record and line monitor */ + } + wrcodec(s, 0x10, i); + wrcodec(s, 0x11, i >> 8); +} + +static int mixer_ioctl(struct es1370_state *s, unsigned int cmd, unsigned long arg) +{ + unsigned long flags; + int i, val; + unsigned char l, r, rl, rr; + int __user *p = (int __user *)arg; + + VALIDATE_STATE(s); + if (cmd == SOUND_MIXER_PRIVATE1) { + /* enable/disable/query mixer preamp */ + if (get_user(val, p)) + return -EFAULT; + if (val != -1) { + s->mix.micpreamp = !!val; + wrcodec(s, 0x19, s->mix.micpreamp); + } + return put_user(s->mix.micpreamp, p); + } + if (cmd == SOUND_MIXER_PRIVATE2) { + /* enable/disable/query use of linein as second lineout */ + if (get_user(val, p)) + return -EFAULT; + if (val != -1) { + spin_lock_irqsave(&s->lock, flags); + if (val) + s->ctrl |= CTRL_XCTL0; + else + s->ctrl &= ~CTRL_XCTL0; + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + return put_user((s->ctrl & CTRL_XCTL0) ? 1 : 0, p); + } + if (cmd == SOUND_MIXER_PRIVATE3) { + /* enable/disable/query microphone impedance setting */ + if (get_user(val, p)) + return -EFAULT; + if (val != -1) { + spin_lock_irqsave(&s->lock, flags); + if (val) + s->ctrl |= CTRL_XCTL1; + else + s->ctrl &= ~CTRL_XCTL1; + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + return put_user((s->ctrl & CTRL_XCTL1) ? 1 : 0, p); + } + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + strncpy(info.id, "ES1370", sizeof(info.id)); + strncpy(info.name, "Ensoniq ES1370", sizeof(info.name)); + info.modify_counter = s->mix.modcnt; + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == SOUND_OLD_MIXER_INFO) { + _old_mixer_info info; + strncpy(info.id, "ES1370", sizeof(info.id)); + strncpy(info.name, "Ensoniq ES1370", sizeof(info.name)); + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, p); + if (_IOC_TYPE(cmd) != 'M' || _SIOC_SIZE(cmd) != sizeof(int)) + return -EINVAL; + if (_SIOC_DIR(cmd) == _SIOC_READ) { + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + return put_user(s->mix.recsrc, p); + + case SOUND_MIXER_DEVMASK: /* Arg contains a bit for each supported device */ + val = SOUND_MASK_IMIX; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].avail) + val |= 1 << i; + return put_user(val, p); + + case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].recmask) + val |= 1 << i; + return put_user(val, p); + + case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].stereo) + val |= 1 << i; + return put_user(val, p); + + case SOUND_MIXER_CAPS: + return put_user(0, p); + + case SOUND_MIXER_IMIX: + return put_user(s->mix.imix, p); + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES || !mixtable[i].avail) + return -EINVAL; + return put_user(s->mix.vol[mixtable[i].volidx], p); + } + } + if (_SIOC_DIR(cmd) != (_SIOC_READ|_SIOC_WRITE)) + return -EINVAL; + s->mix.modcnt++; + switch (_IOC_NR(cmd)) { + + case SOUND_MIXER_IMIX: + if (get_user(s->mix.imix, p)) + return -EFAULT; + set_recsrc(s, s->mix.recsrc); + return 0; + + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + if (get_user(val, p)) + return -EFAULT; + set_recsrc(s, val); + return 0; + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES || !mixtable[i].avail) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + l = val & 0xff; + if (l > 100) + l = 100; + if (mixtable[i].stereo) { + r = (val >> 8) & 0xff; + if (r > 100) + r = 100; + if (l < 7) { + rl = 0x80; + l = 0; + } else { + rl = 31 - ((l - 7) / 3); + l = (31 - rl) * 3 + 7; + } + if (r < 7) { + rr = 0x80; + r = 0; + } else { + rr = 31 - ((r - 7) / 3); + r = (31 - rr) * 3 + 7; + } + wrcodec(s, mixtable[i].right, rr); + } else { + if (mixtable[i].left == 15) { + if (l < 2) { + rr = rl = 0x80; + r = l = 0; + } else { + rl = 7 - ((l - 2) / 14); + r = l = (7 - rl) * 14 + 2; + } + } else { + if (l < 7) { + rl = 0x80; + r = l = 0; + } else { + rl = 31 - ((l - 7) / 3); + r = l = (31 - rl) * 3 + 7; + } + } + } + wrcodec(s, mixtable[i].left, rl); +#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS + s->mix.vol[mixtable[i].volidx] = ((unsigned int)r << 8) | l; +#else + s->mix.vol[mixtable[i].volidx] = val; +#endif + return put_user(s->mix.vol[mixtable[i].volidx], p); + } +} + +/* --------------------------------------------------------------------- */ + +static int es1370_open_mixdev(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct list_head *list; + struct es1370_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct es1370_state, devs); + if (s->dev_mixer == minor) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + return nonseekable_open(inode, file); +} + +static int es1370_release_mixdev(struct inode *inode, struct file *file) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + + VALIDATE_STATE(s); + return 0; +} + +static int es1370_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + return mixer_ioctl((struct es1370_state *)file->private_data, cmd, arg); +} + +static /*const*/ struct file_operations es1370_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = es1370_ioctl_mixdev, + .open = es1370_open_mixdev, + .release = es1370_release_mixdev, +}; + +/* --------------------------------------------------------------------- */ + +static int drain_dac1(struct es1370_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int count, tmo; + + if (s->dma_dac1.mapped || !s->dma_dac1.ready) + return 0; + add_wait_queue(&s->dma_dac1.wait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac1.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_dac1.wait, &wait); + set_current_state(TASK_RUNNING); + return -EBUSY; + } + tmo = 3 * HZ * (count + s->dma_dac1.fragsize) / 2 + / dac1_samplerate[(s->ctrl & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL]; + tmo >>= sample_shift[(s->sctrl & SCTRL_P1FMT) >> SCTRL_SH_P1FMT]; + if (!schedule_timeout(tmo + 1)) + DBG(printk(KERN_DEBUG "es1370: dma timed out??\n");) + } + remove_wait_queue(&s->dma_dac1.wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +static int drain_dac2(struct es1370_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int count, tmo; + + if (s->dma_dac2.mapped || !s->dma_dac2.ready) + return 0; + add_wait_queue(&s->dma_dac2.wait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac2.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_dac2.wait, &wait); + set_current_state(TASK_RUNNING); + return -EBUSY; + } + tmo = 3 * HZ * (count + s->dma_dac2.fragsize) / 2 + / DAC2_DIVTOSR((s->ctrl & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV); + tmo >>= sample_shift[(s->sctrl & SCTRL_P2FMT) >> SCTRL_SH_P2FMT]; + if (!schedule_timeout(tmo + 1)) + DBG(printk(KERN_DEBUG "es1370: dma timed out??\n");) + } + remove_wait_queue(&s->dma_dac2.wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static ssize_t es1370_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret = 0; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_adc.mapped) + return -ENXIO; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + down(&s->sem); + if (!s->dma_adc.ready && (ret = prog_dmabuf_adc(s))) + goto out; + + add_wait_queue(&s->dma_adc.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + swptr = s->dma_adc.swptr; + cnt = s->dma_adc.dmasize-swptr; + if (s->dma_adc.count < cnt) + cnt = s->dma_adc.count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (s->dma_adc.enabled) + start_adc(s); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + up(&s->sem); + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out; + } + down(&s->sem); + if (s->dma_adc.mapped) + { + ret = -ENXIO; + goto out; + } + continue; + } + if (copy_to_user(buffer, s->dma_adc.rawbuf + swptr, cnt)) { + if (!ret) + ret = -EFAULT; + goto out; + } + swptr = (swptr + cnt) % s->dma_adc.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_adc.swptr = swptr; + s->dma_adc.count -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->dma_adc.enabled) + start_adc(s); + } +out: + up(&s->sem); + remove_wait_queue(&s->dma_adc.wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +static ssize_t es1370_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret = 0; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_dac2.mapped) + return -ENXIO; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + down(&s->sem); + if (!s->dma_dac2.ready && (ret = prog_dmabuf_dac2(s))) + goto out; + ret = 0; + add_wait_queue(&s->dma_dac2.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + if (s->dma_dac2.count < 0) { + s->dma_dac2.count = 0; + s->dma_dac2.swptr = s->dma_dac2.hwptr; + } + swptr = s->dma_dac2.swptr; + cnt = s->dma_dac2.dmasize-swptr; + if (s->dma_dac2.count + cnt > s->dma_dac2.dmasize) + cnt = s->dma_dac2.dmasize - s->dma_dac2.count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (s->dma_dac2.enabled) + start_dac2(s); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + up(&s->sem); + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out; + } + down(&s->sem); + if (s->dma_dac2.mapped) + { + ret = -ENXIO; + goto out; + } + continue; + } + if (copy_from_user(s->dma_dac2.rawbuf + swptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + goto out; + } + swptr = (swptr + cnt) % s->dma_dac2.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_dac2.swptr = swptr; + s->dma_dac2.count += cnt; + s->dma_dac2.endcleared = 0; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->dma_dac2.enabled) + start_dac2(s); + } +out: + up(&s->sem); + remove_wait_queue(&s->dma_dac2.wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int es1370_poll(struct file *file, struct poll_table_struct *wait) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) { + if (!s->dma_dac2.ready && prog_dmabuf_dac2(s)) + return 0; + poll_wait(file, &s->dma_dac2.wait, wait); + } + if (file->f_mode & FMODE_READ) { + if (!s->dma_adc.ready && prog_dmabuf_adc(s)) + return 0; + poll_wait(file, &s->dma_adc.wait, wait); + } + spin_lock_irqsave(&s->lock, flags); + es1370_update_ptr(s); + if (file->f_mode & FMODE_READ) { + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->dma_dac2.mapped) { + if (s->dma_dac2.count >= (signed)s->dma_dac2.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)s->dma_dac2.dmasize >= s->dma_dac2.count + (signed)s->dma_dac2.fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int es1370_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + struct dmabuf *db; + int ret = 0; + unsigned long size; + + VALIDATE_STATE(s); + lock_kernel(); + down(&s->sem); + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf_dac2(s)) != 0) { + goto out; + } + db = &s->dma_dac2; + } else if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf_adc(s)) != 0) { + goto out; + } + db = &s->dma_adc; + } else { + ret = -EINVAL; + goto out; + } + if (vma->vm_pgoff != 0) { + ret = -EINVAL; + goto out; + } + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << db->buforder)) { + ret = -EINVAL; + goto out; + } + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(db->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) { + ret = -EAGAIN; + goto out; + } + db->mapped = 1; +out: + up(&s->sem); + unlock_kernel(); + return ret; +} + +static int es1370_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int count; + int val, mapped, ret; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + VALIDATE_STATE(s); + mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac2.mapped) || + ((file->f_mode & FMODE_READ) && s->dma_adc.mapped); + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_SYNC: + if (file->f_mode & FMODE_WRITE) + return drain_dac2(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, p); + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_WRITE) { + stop_dac2(s); + synchronize_irq(s->irq); + s->dma_dac2.swptr = s->dma_dac2.hwptr = s->dma_dac2.count = s->dma_dac2.total_bytes = 0; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(s->irq); + s->dma_adc.swptr = s->dma_adc.hwptr = s->dma_adc.count = s->dma_adc.total_bytes = 0; + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { + if (s->open_mode & (~file->f_mode) & (FMODE_READ|FMODE_WRITE)) + return -EINVAL; + if (val < 4000) + val = 4000; + if (val > 50000) + val = 50000; + stop_adc(s); + stop_dac2(s); + s->dma_adc.ready = s->dma_dac2.ready = 0; + spin_lock_irqsave(&s->lock, flags); + s->ctrl = (s->ctrl & ~CTRL_PCLKDIV) | (DAC2_SRTODIV(val) << CTRL_SH_PCLKDIV); + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + return put_user(DAC2_DIVTOSR((s->ctrl & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV), p); + + case SNDCTL_DSP_STEREO: + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val) + s->sctrl |= SCTRL_R1SMB; + else + s->sctrl &= ~SCTRL_R1SMB; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac2(s); + s->dma_dac2.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val) + s->sctrl |= SCTRL_P2SMB; + else + s->sctrl &= ~SCTRL_P2SMB; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + if (val != 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val >= 2) + s->sctrl |= SCTRL_R1SMB; + else + s->sctrl &= ~SCTRL_R1SMB; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac2(s); + s->dma_dac2.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val >= 2) + s->sctrl |= SCTRL_P2SMB; + else + s->sctrl &= ~SCTRL_P2SMB; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + } + return put_user((s->sctrl & ((file->f_mode & FMODE_READ) ? SCTRL_R1SMB : SCTRL_P2SMB)) ? 2 : 1, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_LE|AFMT_U8, p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, p)) + return -EFAULT; + if (val != AFMT_QUERY) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val == AFMT_S16_LE) + s->sctrl |= SCTRL_R1SEB; + else + s->sctrl &= ~SCTRL_R1SEB; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac2(s); + s->dma_dac2.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val == AFMT_S16_LE) + s->sctrl |= SCTRL_P2SEB; + else + s->sctrl &= ~SCTRL_P2SEB; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + } + return put_user((s->sctrl & ((file->f_mode & FMODE_READ) ? SCTRL_R1SEB : SCTRL_P2SEB)) ? + AFMT_S16_LE : AFMT_U8, p); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if (file->f_mode & FMODE_READ && s->ctrl & CTRL_ADC_EN) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && s->ctrl & CTRL_DAC2_EN) + val |= PCM_ENABLE_OUTPUT; + return put_user(val, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + 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; + s->dma_adc.enabled = 1; + start_adc(s); + } else { + s->dma_adc.enabled = 0; + stop_adc(s); + } + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) { + if (!s->dma_dac2.ready && (ret = prog_dmabuf_dac2(s))) + return ret; + s->dma_dac2.enabled = 1; + start_dac2(s); + } else { + s->dma_dac2.enabled = 0; + stop_dac2(s); + } + } + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac2.ready && (val = prog_dmabuf_dac2(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1370_update_ptr(s); + abinfo.fragsize = s->dma_dac2.fragsize; + count = s->dma_dac2.count; + if (count < 0) + count = 0; + abinfo.bytes = s->dma_dac2.dmasize - count; + abinfo.fragstotal = s->dma_dac2.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac2.fragshift; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user(argp, &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)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1370_update_ptr(s); + abinfo.fragsize = s->dma_adc.fragsize; + count = s->dma_adc.count; + if (count < 0) + count = 0; + abinfo.bytes = 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(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac2.ready && (val = prog_dmabuf_dac2(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1370_update_ptr(s); + count = s->dma_dac2.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + return put_user(count, p); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!s->dma_adc.ready && (val = prog_dmabuf_adc(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1370_update_ptr(s); + cinfo.bytes = s->dma_adc.total_bytes; + count = s->dma_adc.count; + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_adc.fragshift; + 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); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac2.ready && (val = prog_dmabuf_dac2(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1370_update_ptr(s); + cinfo.bytes = s->dma_dac2.total_bytes; + count = s->dma_dac2.count; + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_dac2.fragshift; + cinfo.ptr = s->dma_dac2.hwptr; + if (s->dma_dac2.mapped) + s->dma_dac2.count &= s->dma_dac2.fragsize-1; + spin_unlock_irqrestore(&s->lock, flags); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + if ((val = prog_dmabuf_dac2(s))) + return val; + return put_user(s->dma_dac2.fragsize, p); + } + if ((val = prog_dmabuf_adc(s))) + return val; + return put_user(s->dma_adc.fragsize, p); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = val & 0xffff; + s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_adc.ossfragshift < 4) + s->dma_adc.ossfragshift = 4; + if (s->dma_adc.ossfragshift > 15) + s->dma_adc.ossfragshift = 15; + if (s->dma_adc.ossmaxfrags < 4) + s->dma_adc.ossmaxfrags = 4; + } + if (file->f_mode & FMODE_WRITE) { + s->dma_dac2.ossfragshift = val & 0xffff; + s->dma_dac2.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac2.ossfragshift < 4) + s->dma_dac2.ossfragshift = 4; + if (s->dma_dac2.ossfragshift > 15) + s->dma_dac2.ossfragshift = 15; + if (s->dma_dac2.ossmaxfrags < 4) + s->dma_dac2.ossmaxfrags = 4; + } + return 0; + + case SNDCTL_DSP_SUBDIVIDE: + if ((file->f_mode & FMODE_READ && s->dma_adc.subdivision) || + (file->f_mode & FMODE_WRITE && s->dma_dac2.subdivision)) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + if (file->f_mode & FMODE_READ) + s->dma_adc.subdivision = val; + if (file->f_mode & FMODE_WRITE) + s->dma_dac2.subdivision = val; + return 0; + + case SOUND_PCM_READ_RATE: + return put_user(DAC2_DIVTOSR((s->ctrl & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV), p); + + case SOUND_PCM_READ_CHANNELS: + return put_user((s->sctrl & ((file->f_mode & FMODE_READ) ? SCTRL_R1SMB : SCTRL_P2SMB)) ? + 2 : 1, p); + + case SOUND_PCM_READ_BITS: + return put_user((s->sctrl & ((file->f_mode & FMODE_READ) ? SCTRL_R1SEB : SCTRL_P2SEB)) ? + 16 : 8, p); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + + } + return mixer_ioctl(s, cmd, arg); +} + +static int es1370_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + struct list_head *list; + struct es1370_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct es1370_state, devs); + if (!((s->dev_audio ^ minor) & ~0xf)) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & file->f_mode) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_READ|FMODE_WRITE))) + s->ctrl = (s->ctrl & ~CTRL_PCLKDIV) | (DAC2_SRTODIV(8000) << CTRL_SH_PCLKDIV); + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = s->dma_adc.subdivision = 0; + s->dma_adc.enabled = 1; + s->sctrl &= ~SCTRL_R1FMT; + if ((minor & 0xf) == SND_DEV_DSP16) + s->sctrl |= ES1370_FMT_S16_MONO << SCTRL_SH_R1FMT; + else + s->sctrl |= ES1370_FMT_U8_MONO << SCTRL_SH_R1FMT; + } + if (file->f_mode & FMODE_WRITE) { + s->dma_dac2.ossfragshift = s->dma_dac2.ossmaxfrags = s->dma_dac2.subdivision = 0; + s->dma_dac2.enabled = 1; + s->sctrl &= ~SCTRL_P2FMT; + if ((minor & 0xf) == SND_DEV_DSP16) + s->sctrl |= ES1370_FMT_S16_MONO << SCTRL_SH_P2FMT; + else + s->sctrl |= ES1370_FMT_U8_MONO << SCTRL_SH_P2FMT; + } + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + up(&s->open_sem); + init_MUTEX(&s->sem); + return nonseekable_open(inode, file); +} + +static int es1370_release(struct inode *inode, struct file *file) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + + VALIDATE_STATE(s); + lock_kernel(); + if (file->f_mode & FMODE_WRITE) + drain_dac2(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem); + if (file->f_mode & FMODE_WRITE) { + stop_dac2(s); + synchronize_irq(s->irq); + dealloc_dmabuf(s, &s->dma_dac2); + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + dealloc_dmabuf(s, &s->dma_adc); + } + s->open_mode &= ~(file->f_mode & (FMODE_READ|FMODE_WRITE)); + wake_up(&s->open_wait); + up(&s->open_sem); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations es1370_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = es1370_read, + .write = es1370_write, + .poll = es1370_poll, + .ioctl = es1370_ioctl, + .mmap = es1370_mmap, + .open = es1370_open, + .release = es1370_release, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t es1370_write_dac(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret = 0; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_dac1.mapped) + return -ENXIO; + if (!s->dma_dac1.ready && (ret = prog_dmabuf_dac1(s))) + return ret; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + add_wait_queue(&s->dma_dac1.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + if (s->dma_dac1.count < 0) { + s->dma_dac1.count = 0; + s->dma_dac1.swptr = s->dma_dac1.hwptr; + } + swptr = s->dma_dac1.swptr; + cnt = s->dma_dac1.dmasize-swptr; + if (s->dma_dac1.count + cnt > s->dma_dac1.dmasize) + cnt = s->dma_dac1.dmasize - s->dma_dac1.count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (s->dma_dac1.enabled) + start_dac1(s); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_from_user(s->dma_dac1.rawbuf + swptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + swptr = (swptr + cnt) % s->dma_dac1.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_dac1.swptr = swptr; + s->dma_dac1.count += cnt; + s->dma_dac1.endcleared = 0; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->dma_dac1.enabled) + start_dac1(s); + } + remove_wait_queue(&s->dma_dac1.wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int es1370_poll_dac(struct file *file, struct poll_table_struct *wait) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (!s->dma_dac1.ready && prog_dmabuf_dac1(s)) + return 0; + poll_wait(file, &s->dma_dac1.wait, wait); + spin_lock_irqsave(&s->lock, flags); + es1370_update_ptr(s); + if (s->dma_dac1.mapped) { + if (s->dma_dac1.count >= (signed)s->dma_dac1.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)s->dma_dac1.dmasize >= s->dma_dac1.count + (signed)s->dma_dac1.fragsize) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int es1370_mmap_dac(struct file *file, struct vm_area_struct *vma) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + int ret; + unsigned long size; + + VALIDATE_STATE(s); + if (!(vma->vm_flags & VM_WRITE)) + return -EINVAL; + lock_kernel(); + if ((ret = prog_dmabuf_dac1(s)) != 0) + goto out; + ret = -EINVAL; + if (vma->vm_pgoff != 0) + goto out; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << s->dma_dac1.buforder)) + goto out; + ret = -EAGAIN; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(s->dma_dac1.rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + goto out; + s->dma_dac1.mapped = 1; + ret = 0; +out: + unlock_kernel(); + return ret; +} + +static int es1370_ioctl_dac(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int count; + unsigned ctrl; + int val, ret; + int __user *p = (int __user *)arg; + + VALIDATE_STATE(s); + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_SYNC: + return drain_dac1(s, 0/*file->f_flags & O_NONBLOCK*/); + + case SNDCTL_DSP_SETDUPLEX: + return -EINVAL; + + case SNDCTL_DSP_GETCAPS: + return put_user(DSP_CAP_REALTIME | DSP_CAP_TRIGGER | DSP_CAP_MMAP, p); + + case SNDCTL_DSP_RESET: + stop_dac1(s); + synchronize_irq(s->irq); + s->dma_dac1.swptr = s->dma_dac1.hwptr = s->dma_dac1.count = s->dma_dac1.total_bytes = 0; + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { + stop_dac1(s); + s->dma_dac1.ready = 0; + for (ctrl = 0; ctrl <= 2; ctrl++) + if (val < (dac1_samplerate[ctrl] + dac1_samplerate[ctrl+1]) / 2) + break; + spin_lock_irqsave(&s->lock, flags); + s->ctrl = (s->ctrl & ~CTRL_WTSRSEL) | (ctrl << CTRL_SH_WTSRSEL); + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + return put_user(dac1_samplerate[(s->ctrl & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL], p); + + case SNDCTL_DSP_STEREO: + if (get_user(val, p)) + return -EFAULT; + stop_dac1(s); + s->dma_dac1.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val) + s->sctrl |= SCTRL_P1SMB; + else + s->sctrl &= ~SCTRL_P1SMB; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + if (val != 0) { + if (s->dma_dac1.mapped) + return -EINVAL; + stop_dac1(s); + s->dma_dac1.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val >= 2) + s->sctrl |= SCTRL_P1SMB; + else + s->sctrl &= ~SCTRL_P1SMB; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + return put_user((s->sctrl & SCTRL_P1SMB) ? 2 : 1, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_LE|AFMT_U8, p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, p)) + return -EFAULT; + if (val != AFMT_QUERY) { + stop_dac1(s); + s->dma_dac1.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val == AFMT_S16_LE) + s->sctrl |= SCTRL_P1SEB; + else + s->sctrl &= ~SCTRL_P1SEB; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + return put_user((s->sctrl & SCTRL_P1SEB) ? AFMT_S16_LE : AFMT_U8, p); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + return put_user((s->ctrl & CTRL_DAC1_EN) ? PCM_ENABLE_OUTPUT : 0, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + return -EFAULT; + if (val & PCM_ENABLE_OUTPUT) { + if (!s->dma_dac1.ready && (ret = prog_dmabuf_dac1(s))) + return ret; + s->dma_dac1.enabled = 1; + start_dac1(s); + } else { + s->dma_dac1.enabled = 0; + stop_dac1(s); + } + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!s->dma_dac1.ready && (val = prog_dmabuf_dac1(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1370_update_ptr(s); + abinfo.fragsize = s->dma_dac1.fragsize; + count = s->dma_dac1.count; + if (count < 0) + count = 0; + abinfo.bytes = s->dma_dac1.dmasize - count; + abinfo.fragstotal = s->dma_dac1.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac1.fragshift; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user((void __user *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!s->dma_dac1.ready && (val = prog_dmabuf_dac1(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1370_update_ptr(s); + count = s->dma_dac1.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + return put_user(count, p); + + case SNDCTL_DSP_GETOPTR: + if (!s->dma_dac1.ready && (val = prog_dmabuf_dac1(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1370_update_ptr(s); + cinfo.bytes = s->dma_dac1.total_bytes; + count = s->dma_dac1.count; + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_dac1.fragshift; + cinfo.ptr = s->dma_dac1.hwptr; + if (s->dma_dac1.mapped) + s->dma_dac1.count &= s->dma_dac1.fragsize-1; + spin_unlock_irqrestore(&s->lock, flags); + if (copy_to_user((void __user *)arg, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + if ((val = prog_dmabuf_dac1(s))) + return val; + return put_user(s->dma_dac1.fragsize, p); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + s->dma_dac1.ossfragshift = val & 0xffff; + s->dma_dac1.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac1.ossfragshift < 4) + s->dma_dac1.ossfragshift = 4; + if (s->dma_dac1.ossfragshift > 15) + s->dma_dac1.ossfragshift = 15; + if (s->dma_dac1.ossmaxfrags < 4) + s->dma_dac1.ossmaxfrags = 4; + return 0; + + case SNDCTL_DSP_SUBDIVIDE: + if (s->dma_dac1.subdivision) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + s->dma_dac1.subdivision = val; + return 0; + + case SOUND_PCM_READ_RATE: + return put_user(dac1_samplerate[(s->ctrl & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL], p); + + case SOUND_PCM_READ_CHANNELS: + return put_user((s->sctrl & SCTRL_P1SMB) ? 2 : 1, p); + + case SOUND_PCM_READ_BITS: + return put_user((s->sctrl & SCTRL_P1SEB) ? 16 : 8, p); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + + } + return mixer_ioctl(s, cmd, arg); +} + +static int es1370_open_dac(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + struct list_head *list; + struct es1370_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct es1370_state, devs); + if (!((s->dev_dac ^ minor) & ~0xf)) + break; + } + VALIDATE_STATE(s); + /* we allow opening with O_RDWR, most programs do it although they will only write */ +#if 0 + if (file->f_mode & FMODE_READ) + return -EPERM; +#endif + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & FMODE_DAC) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + s->dma_dac1.ossfragshift = s->dma_dac1.ossmaxfrags = s->dma_dac1.subdivision = 0; + s->dma_dac1.enabled = 1; + spin_lock_irqsave(&s->lock, flags); + s->ctrl = (s->ctrl & ~CTRL_WTSRSEL) | (1 << CTRL_SH_WTSRSEL); + s->sctrl &= ~SCTRL_P1FMT; + if ((minor & 0xf) == SND_DEV_DSP16) + s->sctrl |= ES1370_FMT_S16_MONO << SCTRL_SH_P1FMT; + else + s->sctrl |= ES1370_FMT_U8_MONO << SCTRL_SH_P1FMT; + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + s->open_mode |= FMODE_DAC; + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int es1370_release_dac(struct inode *inode, struct file *file) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + + VALIDATE_STATE(s); + lock_kernel(); + drain_dac1(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem); + stop_dac1(s); + dealloc_dmabuf(s, &s->dma_dac1); + s->open_mode &= ~FMODE_DAC; + wake_up(&s->open_wait); + up(&s->open_sem); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations es1370_dac_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = es1370_write_dac, + .poll = es1370_poll_dac, + .ioctl = es1370_ioctl_dac, + .mmap = es1370_mmap_dac, + .open = es1370_open_dac, + .release = es1370_release_dac, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t es1370_midi_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + if (count == 0) + return 0; + ret = 0; + add_wait_queue(&s->midi.iwait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.ird; + cnt = MIDIINBUF - ptr; + if (s->midi.icnt < cnt) + cnt = s->midi.icnt; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_to_user(buffer, s->midi.ibuf + ptr, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + ptr = (ptr + cnt) % MIDIINBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.ird = ptr; + s->midi.icnt -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + break; + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&s->midi.iwait, &wait); + return ret; +} + +static ssize_t es1370_midi_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + if (count == 0) + return 0; + ret = 0; + add_wait_queue(&s->midi.owait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.owr; + cnt = MIDIOUTBUF - ptr; + if (s->midi.ocnt + cnt > MIDIOUTBUF) + cnt = MIDIOUTBUF - s->midi.ocnt; + if (cnt <= 0) { + __set_current_state(TASK_INTERRUPTIBLE); + es1370_handle_midi(s); + } + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_from_user(s->midi.obuf + ptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + ptr = (ptr + cnt) % MIDIOUTBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.owr = ptr; + s->midi.ocnt += cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + spin_lock_irqsave(&s->lock, flags); + es1370_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&s->midi.owait, &wait); + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int es1370_midi_poll(struct file *file, struct poll_table_struct *wait) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) + poll_wait(file, &s->midi.owait, wait); + if (file->f_mode & FMODE_READ) + poll_wait(file, &s->midi.iwait, wait); + spin_lock_irqsave(&s->lock, flags); + if (file->f_mode & FMODE_READ) { + if (s->midi.icnt > 0) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->midi.ocnt < MIDIOUTBUF) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int es1370_midi_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + struct list_head *list; + struct es1370_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct es1370_state, devs); + if (s->dev_midi == minor) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & (file->f_mode << FMODE_MIDI_SHIFT)) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + outb(UCTRL_CNTRL_SWR, s->io+ES1370_REG_UART_CONTROL); + outb(0, s->io+ES1370_REG_UART_CONTROL); + outb(0, s->io+ES1370_REG_UART_TEST); + } + if (file->f_mode & FMODE_READ) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + } + if (file->f_mode & FMODE_WRITE) { + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + } + s->ctrl |= CTRL_UART_EN; + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + es1370_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + s->open_mode |= (file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ | FMODE_MIDI_WRITE); + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int es1370_midi_release(struct inode *inode, struct file *file) +{ + struct es1370_state *s = (struct es1370_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + unsigned count, tmo; + + VALIDATE_STATE(s); + + lock_kernel(); + if (file->f_mode & FMODE_WRITE) { + add_wait_queue(&s->midi.owait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->midi.ocnt; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (file->f_flags & O_NONBLOCK) + break; + tmo = (count * HZ) / 3100; + if (!schedule_timeout(tmo ? : 1) && tmo) + DBG(printk(KERN_DEBUG "es1370: midi timed out??\n");) + } + remove_wait_queue(&s->midi.owait, &wait); + set_current_state(TASK_RUNNING); + } + down(&s->open_sem); + s->open_mode &= ~((file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ|FMODE_MIDI_WRITE)); + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + s->ctrl &= ~CTRL_UART_EN; + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + } + spin_unlock_irqrestore(&s->lock, flags); + wake_up(&s->open_wait); + up(&s->open_sem); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations es1370_midi_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = es1370_midi_read, + .write = es1370_midi_write, + .poll = es1370_midi_poll, + .open = es1370_midi_open, + .release = es1370_midi_release, +}; + +/* --------------------------------------------------------------------- */ + +/* maximum number of devices; only used for command line params */ +#define NR_DEVICE 5 + +static int lineout[NR_DEVICE]; +static int micbias[NR_DEVICE]; + +static unsigned int devindex; + +module_param_array(lineout, bool, NULL, 0); +MODULE_PARM_DESC(lineout, "if 1 the LINE input is converted to LINE out"); +module_param_array(micbias, bool, NULL, 0); +MODULE_PARM_DESC(micbias, "sets the +5V bias for an electret microphone"); + +MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu"); +MODULE_DESCRIPTION("ES1370 AudioPCI Driver"); +MODULE_LICENSE("GPL"); + + +/* --------------------------------------------------------------------- */ + +static struct initvol { + int mixch; + int vol; +} initvol[] __devinitdata = { + { 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_LINE2, 0x4040 }, + { SOUND_MIXER_WRITE_LINE3, 0x4040 }, + { SOUND_MIXER_WRITE_MIC, 0x4040 }, + { SOUND_MIXER_WRITE_OGAIN, 0x4040 } +}; + +static int __devinit es1370_probe(struct pci_dev *pcidev, const struct pci_device_id *pciid) +{ + struct es1370_state *s; + struct gameport *gp = NULL; + mm_segment_t fs; + int i, val, ret; + + if ((ret=pci_enable_device(pcidev))) + return ret; + + if ( !(pci_resource_flags(pcidev, 0) & IORESOURCE_IO) || + !pci_resource_start(pcidev, 0) + ) + return -ENODEV; + if (pcidev->irq == 0) + return -ENODEV; + i = pci_set_dma_mask(pcidev, 0xffffffff); + if (i) { + printk(KERN_WARNING "es1370: architecture does not support 32bit PCI busmaster DMA\n"); + return i; + } + if (!(s = kmalloc(sizeof(struct es1370_state), GFP_KERNEL))) { + printk(KERN_WARNING "es1370: out of memory\n"); + return -ENOMEM; + } + memset(s, 0, sizeof(struct es1370_state)); + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac1.wait); + init_waitqueue_head(&s->dma_dac2.wait); + init_waitqueue_head(&s->open_wait); + init_waitqueue_head(&s->midi.iwait); + init_waitqueue_head(&s->midi.owait); + init_MUTEX(&s->open_sem); + spin_lock_init(&s->lock); + s->magic = ES1370_MAGIC; + s->dev = pcidev; + s->io = pci_resource_start(pcidev, 0); + s->irq = pcidev->irq; + if (!request_region(s->io, ES1370_EXTENT, "es1370")) { + printk(KERN_ERR "es1370: io ports %#lx-%#lx in use\n", s->io, s->io+ES1370_EXTENT-1); + ret = -EBUSY; + goto err_region; + } + if ((ret=request_irq(s->irq, es1370_interrupt, SA_SHIRQ, "es1370",s))) { + printk(KERN_ERR "es1370: irq %u in use\n", s->irq); + goto err_irq; + } + + /* initialize codec registers */ + /* note: setting CTRL_SERR_DIS is reported to break + * mic bias setting (by Kim.Berts@fisub.mail.abb.com) */ + s->ctrl = CTRL_CDC_EN | (DAC2_SRTODIV(8000) << CTRL_SH_PCLKDIV) | (1 << CTRL_SH_WTSRSEL); + if (!request_region(0x200, JOY_EXTENT, "es1370")) { + printk(KERN_ERR "es1370: joystick io port 0x200 in use\n"); + } else if (!(s->gameport = gp = gameport_allocate_port())) { + printk(KERN_ERR "es1370: can not allocate memory for gameport\n"); + release_region(0x200, JOY_EXTENT); + } else { + gameport_set_name(gp, "ESS1370"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(s->dev)); + gp->dev.parent = &s->dev->dev; + gp->io = 0x200; + s->ctrl |= CTRL_JYSTK_EN; + } + if (lineout[devindex]) + s->ctrl |= CTRL_XCTL0; + if (micbias[devindex]) + s->ctrl |= CTRL_XCTL1; + s->sctrl = 0; + printk(KERN_INFO "es1370: found adapter at io %#lx irq %u\n" + KERN_INFO "es1370: features: joystick %s, line %s, mic impedance %s\n", + s->io, s->irq, (s->ctrl & CTRL_JYSTK_EN) ? "on" : "off", + (s->ctrl & CTRL_XCTL0) ? "out" : "in", + (s->ctrl & CTRL_XCTL1) ? "1" : "0"); + /* register devices */ + if ((s->dev_audio = register_sound_dsp(&es1370_audio_fops, -1)) < 0) { + ret = s->dev_audio; + goto err_dev1; + } + if ((s->dev_mixer = register_sound_mixer(&es1370_mixer_fops, -1)) < 0) { + ret = s->dev_mixer; + goto err_dev2; + } + if ((s->dev_dac = register_sound_dsp(&es1370_dac_fops, -1)) < 0) { + ret = s->dev_dac; + goto err_dev3; + } + if ((s->dev_midi = register_sound_midi(&es1370_midi_fops, -1)) < 0) { + ret = s->dev_midi; + goto err_dev4; + } + /* initialize the chips */ + outl(s->ctrl, s->io+ES1370_REG_CONTROL); + outl(s->sctrl, s->io+ES1370_REG_SERIAL_CONTROL); + /* point phantom write channel to "bugbuf" */ + s->bugbuf_cpu = pci_alloc_consistent(pcidev,16,&s->bugbuf_dma); + if (!s->bugbuf_cpu) { + ret = -ENOMEM; + goto err_dev5; + } + outl((ES1370_REG_PHANTOM_FRAMEADR >> 8) & 15, s->io+ES1370_REG_MEMPAGE); + outl(s->bugbuf_dma, s->io+(ES1370_REG_PHANTOM_FRAMEADR & 0xff)); + outl(0, s->io+(ES1370_REG_PHANTOM_FRAMECNT & 0xff)); + pci_set_master(pcidev); /* enable bus mastering */ + wrcodec(s, 0x16, 3); /* no RST, PD */ + wrcodec(s, 0x17, 0); /* CODEC ADC and CODEC DAC use {LR,B}CLK2 and run off the LRCLK2 PLL; program DAC_SYNC=0!! */ + wrcodec(s, 0x18, 0); /* recording source is mixer */ + wrcodec(s, 0x19, s->mix.micpreamp = 1); /* turn on MIC preamp */ + s->mix.imix = 1; + fs = get_fs(); + set_fs(KERNEL_DS); + val = SOUND_MASK_LINE|SOUND_MASK_SYNTH|SOUND_MASK_CD; + mixer_ioctl(s, SOUND_MIXER_WRITE_RECSRC, (unsigned long)&val); + for (i = 0; i < sizeof(initvol)/sizeof(initvol[0]); i++) { + val = initvol[i].vol; + mixer_ioctl(s, initvol[i].mixch, (unsigned long)&val); + } + set_fs(fs); + + /* register gameport */ + if (gp) + gameport_register_port(gp); + + /* store it in the driver field */ + pci_set_drvdata(pcidev, s); + /* put it into driver list */ + list_add_tail(&s->devs, &devs); + /* increment devindex */ + if (devindex < NR_DEVICE-1) + devindex++; + return 0; + + err_dev5: + unregister_sound_midi(s->dev_midi); + err_dev4: + unregister_sound_dsp(s->dev_dac); + err_dev3: + unregister_sound_mixer(s->dev_mixer); + err_dev2: + unregister_sound_dsp(s->dev_audio); + err_dev1: + printk(KERN_ERR "es1370: cannot register misc device\n"); + free_irq(s->irq, s); + if (s->gameport) { + release_region(s->gameport->io, JOY_EXTENT); + gameport_free_port(s->gameport); + } + err_irq: + release_region(s->io, ES1370_EXTENT); + err_region: + kfree(s); + return ret; +} + +static void __devexit es1370_remove(struct pci_dev *dev) +{ + struct es1370_state *s = pci_get_drvdata(dev); + + if (!s) + return; + list_del(&s->devs); + outl(CTRL_SERR_DIS | (1 << CTRL_SH_WTSRSEL), s->io+ES1370_REG_CONTROL); /* switch everything off */ + outl(0, s->io+ES1370_REG_SERIAL_CONTROL); /* clear serial interrupts */ + synchronize_irq(s->irq); + free_irq(s->irq, s); + if (s->gameport) { + int gpio = s->gameport->io; + gameport_unregister_port(s->gameport); + release_region(gpio, JOY_EXTENT); + } + release_region(s->io, ES1370_EXTENT); + unregister_sound_dsp(s->dev_audio); + unregister_sound_mixer(s->dev_mixer); + unregister_sound_dsp(s->dev_dac); + unregister_sound_midi(s->dev_midi); + pci_free_consistent(dev, 16, s->bugbuf_cpu, s->bugbuf_dma); + kfree(s); + pci_set_drvdata(dev, NULL); +} + +static struct pci_device_id id_table[] = { + { PCI_VENDOR_ID_ENSONIQ, PCI_DEVICE_ID_ENSONIQ_ES1370, PCI_ANY_ID, PCI_ANY_ID, 0, 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, id_table); + +static struct pci_driver es1370_driver = { + .name = "es1370", + .id_table = id_table, + .probe = es1370_probe, + .remove = __devexit_p(es1370_remove), +}; + +static int __init init_es1370(void) +{ + printk(KERN_INFO "es1370: version v0.38 time " __TIME__ " " __DATE__ "\n"); + return pci_module_init(&es1370_driver); +} + +static void __exit cleanup_es1370(void) +{ + printk(KERN_INFO "es1370: unloading\n"); + pci_unregister_driver(&es1370_driver); +} + +module_init(init_es1370); +module_exit(cleanup_es1370); + +/* --------------------------------------------------------------------- */ + +#ifndef MODULE + +/* format is: es1370=lineout[,micbias]] */ + +static int __init es1370_setup(char *str) +{ + static unsigned __initdata nr_dev = 0; + + if (nr_dev >= NR_DEVICE) + return 0; + + (void) + ((get_option(&str,&lineout [nr_dev]) == 2) + && get_option(&str,&micbias [nr_dev]) + ); + + nr_dev++; + return 1; +} + +__setup("es1370=", es1370_setup); + +#endif /* MODULE */ diff --git a/sound/oss/es1371.c b/sound/oss/es1371.c new file mode 100644 index 000000000000..a50fddaeea21 --- /dev/null +++ b/sound/oss/es1371.c @@ -0,0 +1,3097 @@ +/*****************************************************************************/ + +/* + * es1371.c -- Creative Ensoniq ES1371. + * + * Copyright (C) 1998-2001, 2003 Thomas Sailer (t.sailer@alumni.ethz.ch) + * + * 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. + * + * Special thanks to Ensoniq + * + * Supported devices: + * /dev/dsp standard /dev/dsp device, (mostly) OSS compatible + * /dev/mixer standard /dev/mixer device, (mostly) OSS compatible + * /dev/dsp1 additional DAC, like /dev/dsp, but outputs to mixer "SYNTH" setting + * /dev/midi simple MIDI UART interface, no ioctl + * + * NOTE: the card does not have any FM/Wavetable synthesizer, it is supposed + * to be done in software. That is what /dev/dac is for. By now (Q2 1998) + * there are several MIDI to PCM (WAV) packages, one of them is timidity. + * + * Revision history + * 04.06.1998 0.1 Initial release + * Mixer stuff should be overhauled; especially optional AC97 mixer bits + * should be detected. This results in strange behaviour of some mixer + * settings, like master volume and mic. + * 08.06.1998 0.2 First release using Alan Cox' soundcore instead of miscdevice + * 03.08.1998 0.3 Do not include modversions.h + * Now mixer behaviour can basically be selected between + * "OSS documented" and "OSS actual" behaviour + * 31.08.1998 0.4 Fix realplayer problems - dac.count issues + * 27.10.1998 0.5 Fix joystick support + * -- Oliver Neukum (c188@org.chemie.uni-muenchen.de) + * 10.12.1998 0.6 Fix drain_dac trying to wait on not yet initialized DMA + * 23.12.1998 0.7 Fix a few f_file & FMODE_ bugs + * Don't wake up app until there are fragsize bytes to read/write + * 06.01.1999 0.8 remove the silly SA_INTERRUPT flag. + * hopefully killed the egcs section type conflict + * 12.03.1999 0.9 cinfo.blocks should be reset after GETxPTR ioctl. + * reported by Johan Maes + * 22.03.1999 0.10 return EAGAIN instead of EBUSY when O_NONBLOCK + * read/write cannot be executed + * 07.04.1999 0.11 implemented the following ioctl's: SOUND_PCM_READ_RATE, + * SOUND_PCM_READ_CHANNELS, SOUND_PCM_READ_BITS; + * Alpha fixes reported by Peter Jones + * Another Alpha fix (wait_src_ready in init routine) + * reported by "Ivan N. Kokshaysky" + * Note: joystick address handling might still be wrong on archs + * other than i386 + * 15.06.1999 0.12 Fix bad allocation bug. + * Thanks to Deti Fliegl + * 28.06.1999 0.13 Add pci_set_master + * 03.08.1999 0.14 adapt to Linus' new __setup/__initcall + * added kernel command line option "es1371=joystickaddr" + * removed CONFIG_SOUND_ES1371_JOYPORT_BOOT kludge + * 10.08.1999 0.15 (Re)added S/PDIF module option for cards revision >= 4. + * Initial version by Dave Platt . + * module_init/__setup fixes + * 08.16.1999 0.16 Joe Cotellese + * Added detection for ES1371 revision ID so that we can + * detect the ES1373 and later parts. + * added AC97 #defines for readability + * added a /proc file system for dumping hardware state + * updated SRC and CODEC w/r functions to accommodate bugs + * in some versions of the ES137x chips. + * 31.08.1999 0.17 add spin_lock_init + * replaced current->state = x with set_current_state(x) + * 03.09.1999 0.18 change read semantics for MIDI to match + * OSS more closely; remove possible wakeup race + * 21.10.1999 0.19 Round sampling rates, requested by + * Kasamatsu Kenichi + * 27.10.1999 0.20 Added SigmaTel 3D enhancement string + * Codec ID printing changes + * 28.10.1999 0.21 More waitqueue races fixed + * Joe Cotellese + * Changed PCI detection routine so we can more easily + * detect ES137x chip and derivatives. + * 05.01.2000 0.22 Should now work with rev7 boards; patch by + * Eric Lemar, elemar@cs.washington.edu + * 08.01.2000 0.23 Prevent some ioctl's from returning bad count values on underrun/overrun; + * Tim Janik's BSE (Bedevilled Sound Engine) found this + * 07.02.2000 0.24 Use pci_alloc_consistent and pci_register_driver + * 07.02.2000 0.25 Use ac97_codec + * 01.03.2000 0.26 SPDIF patch by Mikael Bouillot + * Use pci_module_init + * 21.11.2000 0.27 Initialize dma buffers in poll, otherwise poll may return a bogus mask + * 12.12.2000 0.28 More dma buffer initializations, patch from + * Tjeerd Mulder + * 05.01.2001 0.29 Hopefully updates will not be required anymore when Creative bumps + * the CT5880 revision. + * suggested by Stephan Müller + * 31.01.2001 0.30 Register/Unregister gameport + * Fix SETTRIGGER non OSS API conformity + * 14.07.2001 0.31 Add list of laptops needing amplifier control + * 03.01.2003 0.32 open_mode fixes from Georg Acher + */ + +/*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* --------------------------------------------------------------------- */ + +#undef OSS_DOCUMENTED_MIXER_SEMANTICS +#define ES1371_DEBUG +#define DBG(x) {} +/*#define DBG(x) {x}*/ + +/* --------------------------------------------------------------------- */ + +#ifndef PCI_VENDOR_ID_ENSONIQ +#define PCI_VENDOR_ID_ENSONIQ 0x1274 +#endif + +#ifndef PCI_VENDOR_ID_ECTIVA +#define PCI_VENDOR_ID_ECTIVA 0x1102 +#endif + +#ifndef PCI_DEVICE_ID_ENSONIQ_ES1371 +#define PCI_DEVICE_ID_ENSONIQ_ES1371 0x1371 +#endif + +#ifndef PCI_DEVICE_ID_ENSONIQ_CT5880 +#define PCI_DEVICE_ID_ENSONIQ_CT5880 0x5880 +#endif + +#ifndef PCI_DEVICE_ID_ECTIVA_EV1938 +#define PCI_DEVICE_ID_ECTIVA_EV1938 0x8938 +#endif + +/* ES1371 chip ID */ +/* This is a little confusing because all ES1371 compatible chips have the + same DEVICE_ID, the only thing differentiating them is the REV_ID field. + This is only significant if you want to enable features on the later parts. + Yes, I know it's stupid and why didn't we use the sub IDs? +*/ +#define ES1371REV_ES1373_A 0x04 +#define ES1371REV_ES1373_B 0x06 +#define ES1371REV_CT5880_A 0x07 +#define CT5880REV_CT5880_C 0x02 +#define CT5880REV_CT5880_D 0x03 +#define ES1371REV_ES1371_B 0x09 +#define EV1938REV_EV1938_A 0x00 +#define ES1371REV_ES1373_8 0x08 + +#define ES1371_MAGIC ((PCI_VENDOR_ID_ENSONIQ<<16)|PCI_DEVICE_ID_ENSONIQ_ES1371) + +#define ES1371_EXTENT 0x40 +#define JOY_EXTENT 8 + +#define ES1371_REG_CONTROL 0x00 +#define ES1371_REG_STATUS 0x04 /* on the 5880 it is control/status */ +#define ES1371_REG_UART_DATA 0x08 +#define ES1371_REG_UART_STATUS 0x09 +#define ES1371_REG_UART_CONTROL 0x09 +#define ES1371_REG_UART_TEST 0x0a +#define ES1371_REG_MEMPAGE 0x0c +#define ES1371_REG_SRCONV 0x10 +#define ES1371_REG_CODEC 0x14 +#define ES1371_REG_LEGACY 0x18 +#define ES1371_REG_SERIAL_CONTROL 0x20 +#define ES1371_REG_DAC1_SCOUNT 0x24 +#define ES1371_REG_DAC2_SCOUNT 0x28 +#define ES1371_REG_ADC_SCOUNT 0x2c + +#define ES1371_REG_DAC1_FRAMEADR 0xc30 +#define ES1371_REG_DAC1_FRAMECNT 0xc34 +#define ES1371_REG_DAC2_FRAMEADR 0xc38 +#define ES1371_REG_DAC2_FRAMECNT 0xc3c +#define ES1371_REG_ADC_FRAMEADR 0xd30 +#define ES1371_REG_ADC_FRAMECNT 0xd34 + +#define ES1371_FMT_U8_MONO 0 +#define ES1371_FMT_U8_STEREO 1 +#define ES1371_FMT_S16_MONO 2 +#define ES1371_FMT_S16_STEREO 3 +#define ES1371_FMT_STEREO 1 +#define ES1371_FMT_S16 2 +#define ES1371_FMT_MASK 3 + +static const unsigned sample_size[] = { 1, 2, 2, 4 }; +static const unsigned sample_shift[] = { 0, 1, 1, 2 }; + +#define CTRL_RECEN_B 0x08000000 /* 1 = don't mix analog in to digital out */ +#define CTRL_SPDIFEN_B 0x04000000 +#define CTRL_JOY_SHIFT 24 +#define CTRL_JOY_MASK 3 +#define CTRL_JOY_200 0x00000000 /* joystick base address */ +#define CTRL_JOY_208 0x01000000 +#define CTRL_JOY_210 0x02000000 +#define CTRL_JOY_218 0x03000000 +#define CTRL_GPIO_IN0 0x00100000 /* general purpose inputs/outputs */ +#define CTRL_GPIO_IN1 0x00200000 +#define CTRL_GPIO_IN2 0x00400000 +#define CTRL_GPIO_IN3 0x00800000 +#define CTRL_GPIO_OUT0 0x00010000 +#define CTRL_GPIO_OUT1 0x00020000 +#define CTRL_GPIO_OUT2 0x00040000 +#define CTRL_GPIO_OUT3 0x00080000 +#define CTRL_MSFMTSEL 0x00008000 /* MPEG serial data fmt: 0 = Sony, 1 = I2S */ +#define CTRL_SYNCRES 0x00004000 /* AC97 warm reset */ +#define CTRL_ADCSTOP 0x00002000 /* stop ADC transfers */ +#define CTRL_PWR_INTRM 0x00001000 /* 1 = power level ints enabled */ +#define CTRL_M_CB 0x00000800 /* recording source: 0 = ADC, 1 = MPEG */ +#define CTRL_CCB_INTRM 0x00000400 /* 1 = CCB "voice" ints enabled */ +#define CTRL_PDLEV0 0x00000000 /* power down level */ +#define CTRL_PDLEV1 0x00000100 +#define CTRL_PDLEV2 0x00000200 +#define CTRL_PDLEV3 0x00000300 +#define CTRL_BREQ 0x00000080 /* 1 = test mode (internal mem test) */ +#define CTRL_DAC1_EN 0x00000040 /* enable DAC1 */ +#define CTRL_DAC2_EN 0x00000020 /* enable DAC2 */ +#define CTRL_ADC_EN 0x00000010 /* enable ADC */ +#define CTRL_UART_EN 0x00000008 /* enable MIDI uart */ +#define CTRL_JYSTK_EN 0x00000004 /* enable Joystick port */ +#define CTRL_XTALCLKDIS 0x00000002 /* 1 = disable crystal clock input */ +#define CTRL_PCICLKDIS 0x00000001 /* 1 = disable PCI clock distribution */ + + +#define STAT_INTR 0x80000000 /* wired or of all interrupt bits */ +#define CSTAT_5880_AC97_RST 0x20000000 /* CT5880 Reset bit */ +#define STAT_EN_SPDIF 0x00040000 /* enable S/PDIF circuitry */ +#define STAT_TS_SPDIF 0x00020000 /* test S/PDIF circuitry */ +#define STAT_TESTMODE 0x00010000 /* test ASIC */ +#define STAT_SYNC_ERR 0x00000100 /* 1 = codec sync error */ +#define STAT_VC 0x000000c0 /* CCB int source, 0=DAC1, 1=DAC2, 2=ADC, 3=undef */ +#define STAT_SH_VC 6 +#define STAT_MPWR 0x00000020 /* power level interrupt */ +#define STAT_MCCB 0x00000010 /* CCB int pending */ +#define STAT_UART 0x00000008 /* UART int pending */ +#define STAT_DAC1 0x00000004 /* DAC1 int pending */ +#define STAT_DAC2 0x00000002 /* DAC2 int pending */ +#define STAT_ADC 0x00000001 /* ADC int pending */ + +#define USTAT_RXINT 0x80 /* UART rx int pending */ +#define USTAT_TXINT 0x04 /* UART tx int pending */ +#define USTAT_TXRDY 0x02 /* UART tx ready */ +#define USTAT_RXRDY 0x01 /* UART rx ready */ + +#define UCTRL_RXINTEN 0x80 /* 1 = enable RX ints */ +#define UCTRL_TXINTEN 0x60 /* TX int enable field mask */ +#define UCTRL_ENA_TXINT 0x20 /* enable TX int */ +#define UCTRL_CNTRL 0x03 /* control field */ +#define UCTRL_CNTRL_SWR 0x03 /* software reset command */ + +/* sample rate converter */ +#define SRC_OKSTATE 1 + +#define SRC_RAMADDR_MASK 0xfe000000 +#define SRC_RAMADDR_SHIFT 25 +#define SRC_DAC1FREEZE (1UL << 21) +#define SRC_DAC2FREEZE (1UL << 20) +#define SRC_ADCFREEZE (1UL << 19) + + +#define SRC_WE 0x01000000 /* read/write control for SRC RAM */ +#define SRC_BUSY 0x00800000 /* SRC busy */ +#define SRC_DIS 0x00400000 /* 1 = disable SRC */ +#define SRC_DDAC1 0x00200000 /* 1 = disable accum update for DAC1 */ +#define SRC_DDAC2 0x00100000 /* 1 = disable accum update for DAC2 */ +#define SRC_DADC 0x00080000 /* 1 = disable accum update for ADC2 */ +#define SRC_CTLMASK 0x00780000 +#define SRC_RAMDATA_MASK 0x0000ffff +#define SRC_RAMDATA_SHIFT 0 + +#define SRCREG_ADC 0x78 +#define SRCREG_DAC1 0x70 +#define SRCREG_DAC2 0x74 +#define SRCREG_VOL_ADC 0x6c +#define SRCREG_VOL_DAC1 0x7c +#define SRCREG_VOL_DAC2 0x7e + +#define SRCREG_TRUNC_N 0x00 +#define SRCREG_INT_REGS 0x01 +#define SRCREG_ACCUM_FRAC 0x02 +#define SRCREG_VFREQ_FRAC 0x03 + +#define CODEC_PIRD 0x00800000 /* 0 = write AC97 register */ +#define CODEC_PIADD_MASK 0x007f0000 +#define CODEC_PIADD_SHIFT 16 +#define CODEC_PIDAT_MASK 0x0000ffff +#define CODEC_PIDAT_SHIFT 0 + +#define CODEC_RDY 0x80000000 /* AC97 read data valid */ +#define CODEC_WIP 0x40000000 /* AC97 write in progress */ +#define CODEC_PORD 0x00800000 /* 0 = write AC97 register */ +#define CODEC_POADD_MASK 0x007f0000 +#define CODEC_POADD_SHIFT 16 +#define CODEC_PODAT_MASK 0x0000ffff +#define CODEC_PODAT_SHIFT 0 + + +#define LEGACY_JFAST 0x80000000 /* fast joystick timing */ +#define LEGACY_FIRQ 0x01000000 /* force IRQ */ + +#define SCTRL_DACTEST 0x00400000 /* 1 = DAC test, test vector generation purposes */ +#define SCTRL_P2ENDINC 0x00380000 /* */ +#define SCTRL_SH_P2ENDINC 19 +#define SCTRL_P2STINC 0x00070000 /* */ +#define SCTRL_SH_P2STINC 16 +#define SCTRL_R1LOOPSEL 0x00008000 /* 0 = loop mode */ +#define SCTRL_P2LOOPSEL 0x00004000 /* 0 = loop mode */ +#define SCTRL_P1LOOPSEL 0x00002000 /* 0 = loop mode */ +#define SCTRL_P2PAUSE 0x00001000 /* 1 = pause mode */ +#define SCTRL_P1PAUSE 0x00000800 /* 1 = pause mode */ +#define SCTRL_R1INTEN 0x00000400 /* enable interrupt */ +#define SCTRL_P2INTEN 0x00000200 /* enable interrupt */ +#define SCTRL_P1INTEN 0x00000100 /* enable interrupt */ +#define SCTRL_P1SCTRLD 0x00000080 /* reload sample count register for DAC1 */ +#define SCTRL_P2DACSEN 0x00000040 /* 1 = DAC2 play back last sample when disabled */ +#define SCTRL_R1SEB 0x00000020 /* 1 = 16bit */ +#define SCTRL_R1SMB 0x00000010 /* 1 = stereo */ +#define SCTRL_R1FMT 0x00000030 /* format mask */ +#define SCTRL_SH_R1FMT 4 +#define SCTRL_P2SEB 0x00000008 /* 1 = 16bit */ +#define SCTRL_P2SMB 0x00000004 /* 1 = stereo */ +#define SCTRL_P2FMT 0x0000000c /* format mask */ +#define SCTRL_SH_P2FMT 2 +#define SCTRL_P1SEB 0x00000002 /* 1 = 16bit */ +#define SCTRL_P1SMB 0x00000001 /* 1 = stereo */ +#define SCTRL_P1FMT 0x00000003 /* format mask */ +#define SCTRL_SH_P1FMT 0 + + +/* misc stuff */ +#define POLL_COUNT 0x1000 +#define FMODE_DAC 4 /* slight misuse of mode_t */ + +/* MIDI buffer sizes */ + +#define MIDIINBUF 256 +#define MIDIOUTBUF 256 + +#define FMODE_MIDI_SHIFT 3 +#define FMODE_MIDI_READ (FMODE_READ << FMODE_MIDI_SHIFT) +#define FMODE_MIDI_WRITE (FMODE_WRITE << FMODE_MIDI_SHIFT) + +#define ES1371_MODULE_NAME "es1371" +#define PFX ES1371_MODULE_NAME ": " + +/* --------------------------------------------------------------------- */ + +struct es1371_state { + /* magic */ + unsigned int magic; + + /* list of es1371 devices */ + struct list_head devs; + + /* the corresponding pci_dev structure */ + struct pci_dev *dev; + + /* soundcore stuff */ + int dev_audio; + int dev_dac; + int dev_midi; + + /* hardware resources */ + unsigned long io; /* long for SPARC */ + unsigned int irq; + + /* PCI ID's */ + u16 vendor; + u16 device; + u8 rev; /* the chip revision */ + + /* options */ + int spdif_volume; /* S/PDIF output is enabled if != -1 */ + +#ifdef ES1371_DEBUG + /* debug /proc entry */ + struct proc_dir_entry *ps; +#endif /* ES1371_DEBUG */ + + struct ac97_codec *codec; + + /* wave stuff */ + unsigned ctrl; + unsigned sctrl; + unsigned dac1rate, dac2rate, adcrate; + + spinlock_t lock; + struct semaphore open_sem; + mode_t open_mode; + wait_queue_head_t open_wait; + + struct dmabuf { + void *rawbuf; + dma_addr_t dmaaddr; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + unsigned hwptr, swptr; + unsigned total_bytes; + int count; + unsigned error; /* over/underrun */ + wait_queue_head_t wait; + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dmasize; + unsigned fragsamples; + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned endcleared:1; + unsigned enabled:1; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + } dma_dac1, dma_dac2, dma_adc; + + /* midi stuff */ + struct { + unsigned ird, iwr, icnt; + unsigned ord, owr, ocnt; + wait_queue_head_t iwait; + wait_queue_head_t owait; + unsigned char ibuf[MIDIINBUF]; + unsigned char obuf[MIDIOUTBUF]; + } midi; + + struct gameport *gameport; + struct semaphore sem; +}; + +/* --------------------------------------------------------------------- */ + +static LIST_HEAD(devs); + +/* --------------------------------------------------------------------- */ + +static inline unsigned ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +/* --------------------------------------------------------------------- */ + +static unsigned wait_src_ready(struct es1371_state *s) +{ + unsigned int t, r; + + for (t = 0; t < POLL_COUNT; t++) { + if (!((r = inl(s->io + ES1371_REG_SRCONV)) & SRC_BUSY)) + return r; + udelay(1); + } + printk(KERN_DEBUG PFX "sample rate converter timeout r = 0x%08x\n", r); + return r; +} + +static unsigned src_read(struct es1371_state *s, unsigned reg) +{ + unsigned int temp,i,orig; + + /* wait for ready */ + temp = wait_src_ready (s); + + /* we can only access the SRC at certain times, make sure + we're allowed to before we read */ + + orig = temp; + /* expose the SRC state bits */ + outl ( (temp & SRC_CTLMASK) | (reg << SRC_RAMADDR_SHIFT) | 0x10000UL, + s->io + ES1371_REG_SRCONV); + + /* now, wait for busy and the correct time to read */ + temp = wait_src_ready (s); + + if ( (temp & 0x00870000UL ) != ( SRC_OKSTATE << 16 )){ + /* wait for the right state */ + for (i=0; iio + ES1371_REG_SRCONV); + if ( (temp & 0x00870000UL ) == ( SRC_OKSTATE << 16 )) + break; + } + } + + /* hide the state bits */ + outl ((orig & SRC_CTLMASK) | (reg << SRC_RAMADDR_SHIFT), s->io + ES1371_REG_SRCONV); + return temp; + + +} + +static void src_write(struct es1371_state *s, unsigned reg, unsigned data) +{ + + unsigned int r; + + r = wait_src_ready(s) & (SRC_DIS | SRC_DDAC1 | SRC_DDAC2 | SRC_DADC); + r |= (reg << SRC_RAMADDR_SHIFT) & SRC_RAMADDR_MASK; + r |= (data << SRC_RAMDATA_SHIFT) & SRC_RAMDATA_MASK; + outl(r | SRC_WE, s->io + ES1371_REG_SRCONV); + +} + +/* --------------------------------------------------------------------- */ + +/* most of the following here is black magic */ +static void set_adc_rate(struct es1371_state *s, unsigned rate) +{ + unsigned long flags; + unsigned int n, truncm, freq; + + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; + n = rate / 3000; + if ((1 << n) & ((1 << 15) | (1 << 13) | (1 << 11) | (1 << 9))) + n--; + truncm = (21 * n - 1) | 1; + freq = ((48000UL << 15) / rate) * n; + s->adcrate = (48000UL << 15) / (freq / n); + spin_lock_irqsave(&s->lock, flags); + if (rate >= 24000) { + if (truncm > 239) + truncm = 239; + src_write(s, SRCREG_ADC+SRCREG_TRUNC_N, + (((239 - truncm) >> 1) << 9) | (n << 4)); + } else { + if (truncm > 119) + truncm = 119; + src_write(s, SRCREG_ADC+SRCREG_TRUNC_N, + 0x8000 | (((119 - truncm) >> 1) << 9) | (n << 4)); + } + src_write(s, SRCREG_ADC+SRCREG_INT_REGS, + (src_read(s, SRCREG_ADC+SRCREG_INT_REGS) & 0x00ff) | + ((freq >> 5) & 0xfc00)); + src_write(s, SRCREG_ADC+SRCREG_VFREQ_FRAC, freq & 0x7fff); + src_write(s, SRCREG_VOL_ADC, n << 8); + src_write(s, SRCREG_VOL_ADC+1, n << 8); + spin_unlock_irqrestore(&s->lock, flags); +} + + +static void set_dac1_rate(struct es1371_state *s, unsigned rate) +{ + unsigned long flags; + unsigned int freq, r; + + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; + freq = ((rate << 15) + 1500) / 3000; + s->dac1rate = (freq * 3000 + 16384) >> 15; + spin_lock_irqsave(&s->lock, flags); + r = (wait_src_ready(s) & (SRC_DIS | SRC_DDAC2 | SRC_DADC)) | SRC_DDAC1; + outl(r, s->io + ES1371_REG_SRCONV); + src_write(s, SRCREG_DAC1+SRCREG_INT_REGS, + (src_read(s, SRCREG_DAC1+SRCREG_INT_REGS) & 0x00ff) | + ((freq >> 5) & 0xfc00)); + src_write(s, SRCREG_DAC1+SRCREG_VFREQ_FRAC, freq & 0x7fff); + r = (wait_src_ready(s) & (SRC_DIS | SRC_DDAC2 | SRC_DADC)); + outl(r, s->io + ES1371_REG_SRCONV); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void set_dac2_rate(struct es1371_state *s, unsigned rate) +{ + unsigned long flags; + unsigned int freq, r; + + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; + freq = ((rate << 15) + 1500) / 3000; + s->dac2rate = (freq * 3000 + 16384) >> 15; + spin_lock_irqsave(&s->lock, flags); + r = (wait_src_ready(s) & (SRC_DIS | SRC_DDAC1 | SRC_DADC)) | SRC_DDAC2; + outl(r, s->io + ES1371_REG_SRCONV); + src_write(s, SRCREG_DAC2+SRCREG_INT_REGS, + (src_read(s, SRCREG_DAC2+SRCREG_INT_REGS) & 0x00ff) | + ((freq >> 5) & 0xfc00)); + src_write(s, SRCREG_DAC2+SRCREG_VFREQ_FRAC, freq & 0x7fff); + r = (wait_src_ready(s) & (SRC_DIS | SRC_DDAC1 | SRC_DADC)); + outl(r, s->io + ES1371_REG_SRCONV); + spin_unlock_irqrestore(&s->lock, flags); +} + +/* --------------------------------------------------------------------- */ + +static void __devinit src_init(struct es1371_state *s) +{ + unsigned int i; + + /* before we enable or disable the SRC we need + to wait for it to become ready */ + wait_src_ready(s); + + outl(SRC_DIS, s->io + ES1371_REG_SRCONV); + + for (i = 0; i < 0x80; i++) + src_write(s, i, 0); + + src_write(s, SRCREG_DAC1+SRCREG_TRUNC_N, 16 << 4); + src_write(s, SRCREG_DAC1+SRCREG_INT_REGS, 16 << 10); + src_write(s, SRCREG_DAC2+SRCREG_TRUNC_N, 16 << 4); + src_write(s, SRCREG_DAC2+SRCREG_INT_REGS, 16 << 10); + src_write(s, SRCREG_VOL_ADC, 1 << 12); + src_write(s, SRCREG_VOL_ADC+1, 1 << 12); + src_write(s, SRCREG_VOL_DAC1, 1 << 12); + src_write(s, SRCREG_VOL_DAC1+1, 1 << 12); + src_write(s, SRCREG_VOL_DAC2, 1 << 12); + src_write(s, SRCREG_VOL_DAC2+1, 1 << 12); + set_adc_rate(s, 22050); + set_dac1_rate(s, 22050); + set_dac2_rate(s, 22050); + + /* WARNING: + * enabling the sample rate converter without properly programming + * its parameters causes the chip to lock up (the SRC busy bit will + * be stuck high, and I've found no way to rectify this other than + * power cycle) + */ + wait_src_ready(s); + outl(0, s->io+ES1371_REG_SRCONV); +} + +/* --------------------------------------------------------------------- */ + +static void wrcodec(struct ac97_codec *codec, u8 addr, u16 data) +{ + struct es1371_state *s = (struct es1371_state *)codec->private_data; + unsigned long flags; + unsigned t, x; + + spin_lock_irqsave(&s->lock, flags); + for (t = 0; t < POLL_COUNT; t++) + if (!(inl(s->io+ES1371_REG_CODEC) & CODEC_WIP)) + break; + + /* save the current state for later */ + x = wait_src_ready(s); + + /* enable SRC state data in SRC mux */ + outl((x & (SRC_DIS | SRC_DDAC1 | SRC_DDAC2 | SRC_DADC)) | 0x00010000, + s->io+ES1371_REG_SRCONV); + + /* wait for not busy (state 0) first to avoid + transition states */ + for (t=0; tio+ES1371_REG_SRCONV) & 0x00870000) ==0 ) + break; + udelay(1); + } + + /* wait for a SAFE time to write addr/data and then do it, dammit */ + for (t=0; tio+ES1371_REG_SRCONV) & 0x00870000) ==0x00010000) + break; + udelay(1); + } + + outl(((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | + ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK), s->io+ES1371_REG_CODEC); + + /* restore SRC reg */ + wait_src_ready(s); + outl(x, s->io+ES1371_REG_SRCONV); + spin_unlock_irqrestore(&s->lock, flags); +} + +static u16 rdcodec(struct ac97_codec *codec, u8 addr) +{ + struct es1371_state *s = (struct es1371_state *)codec->private_data; + unsigned long flags; + unsigned t, x; + + spin_lock_irqsave(&s->lock, flags); + + /* wait for WIP to go away */ + for (t = 0; t < 0x1000; t++) + if (!(inl(s->io+ES1371_REG_CODEC) & CODEC_WIP)) + break; + + /* save the current state for later */ + x = (wait_src_ready(s) & (SRC_DIS | SRC_DDAC1 | SRC_DDAC2 | SRC_DADC)); + + /* enable SRC state data in SRC mux */ + outl( x | 0x00010000, + s->io+ES1371_REG_SRCONV); + + /* wait for not busy (state 0) first to avoid + transition states */ + for (t=0; tio+ES1371_REG_SRCONV) & 0x00870000) ==0 ) + break; + udelay(1); + } + + /* wait for a SAFE time to write addr/data and then do it, dammit */ + for (t=0; tio+ES1371_REG_SRCONV) & 0x00870000) ==0x00010000) + break; + udelay(1); + } + + outl(((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | CODEC_PORD, s->io+ES1371_REG_CODEC); + /* restore SRC reg */ + wait_src_ready(s); + outl(x, s->io+ES1371_REG_SRCONV); + + /* wait for WIP again */ + for (t = 0; t < 0x1000; t++) + if (!(inl(s->io+ES1371_REG_CODEC) & CODEC_WIP)) + break; + + /* now wait for the stinkin' data (RDY) */ + for (t = 0; t < POLL_COUNT; t++) + if ((x = inl(s->io+ES1371_REG_CODEC)) & CODEC_RDY) + break; + + spin_unlock_irqrestore(&s->lock, flags); + return ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT); +} + +/* --------------------------------------------------------------------- */ + +static inline void stop_adc(struct es1371_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->ctrl &= ~CTRL_ADC_EN; + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); +} + +static inline void stop_dac1(struct es1371_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->ctrl &= ~CTRL_DAC1_EN; + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); +} + +static inline void stop_dac2(struct es1371_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->ctrl &= ~CTRL_DAC2_EN; + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_dac1(struct es1371_state *s) +{ + unsigned long flags; + unsigned fragremain, fshift; + + spin_lock_irqsave(&s->lock, flags); + if (!(s->ctrl & CTRL_DAC1_EN) && (s->dma_dac1.mapped || s->dma_dac1.count > 0) + && s->dma_dac1.ready) { + s->ctrl |= CTRL_DAC1_EN; + s->sctrl = (s->sctrl & ~(SCTRL_P1LOOPSEL | SCTRL_P1PAUSE | SCTRL_P1SCTRLD)) | SCTRL_P1INTEN; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + fragremain = ((- s->dma_dac1.hwptr) & (s->dma_dac1.fragsize-1)); + fshift = sample_shift[(s->sctrl & SCTRL_P1FMT) >> SCTRL_SH_P1FMT]; + if (fragremain < 2*fshift) + fragremain = s->dma_dac1.fragsize; + outl((fragremain >> fshift) - 1, s->io+ES1371_REG_DAC1_SCOUNT); + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + outl((s->dma_dac1.fragsize >> fshift) - 1, s->io+ES1371_REG_DAC1_SCOUNT); + } + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_dac2(struct es1371_state *s) +{ + unsigned long flags; + unsigned fragremain, fshift; + + spin_lock_irqsave(&s->lock, flags); + if (!(s->ctrl & CTRL_DAC2_EN) && (s->dma_dac2.mapped || s->dma_dac2.count > 0) + && s->dma_dac2.ready) { + s->ctrl |= CTRL_DAC2_EN; + s->sctrl = (s->sctrl & ~(SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | SCTRL_P2DACSEN | + SCTRL_P2ENDINC | SCTRL_P2STINC)) | SCTRL_P2INTEN | + (((s->sctrl & SCTRL_P2FMT) ? 2 : 1) << SCTRL_SH_P2ENDINC) | + (0 << SCTRL_SH_P2STINC); + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + fragremain = ((- s->dma_dac2.hwptr) & (s->dma_dac2.fragsize-1)); + fshift = sample_shift[(s->sctrl & SCTRL_P2FMT) >> SCTRL_SH_P2FMT]; + if (fragremain < 2*fshift) + fragremain = s->dma_dac2.fragsize; + outl((fragremain >> fshift) - 1, s->io+ES1371_REG_DAC2_SCOUNT); + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + outl((s->dma_dac2.fragsize >> fshift) - 1, s->io+ES1371_REG_DAC2_SCOUNT); + } + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_adc(struct es1371_state *s) +{ + unsigned long flags; + unsigned fragremain, fshift; + + spin_lock_irqsave(&s->lock, flags); + if (!(s->ctrl & CTRL_ADC_EN) && (s->dma_adc.mapped || s->dma_adc.count < (signed)(s->dma_adc.dmasize - 2*s->dma_adc.fragsize)) + && s->dma_adc.ready) { + s->ctrl |= CTRL_ADC_EN; + s->sctrl = (s->sctrl & ~SCTRL_R1LOOPSEL) | SCTRL_R1INTEN; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + fragremain = ((- s->dma_adc.hwptr) & (s->dma_adc.fragsize-1)); + fshift = sample_shift[(s->sctrl & SCTRL_R1FMT) >> SCTRL_SH_R1FMT]; + if (fragremain < 2*fshift) + fragremain = s->dma_adc.fragsize; + outl((fragremain >> fshift) - 1, s->io+ES1371_REG_ADC_SCOUNT); + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + outl((s->dma_adc.fragsize >> fshift) - 1, s->io+ES1371_REG_ADC_SCOUNT); + } + spin_unlock_irqrestore(&s->lock, flags); +} + +/* --------------------------------------------------------------------- */ + +#define DMABUF_DEFAULTORDER (17-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + + +static inline void dealloc_dmabuf(struct es1371_state *s, struct dmabuf *db) +{ + struct page *page, *pend; + + if (db->rawbuf) { + /* undo marking the pages as reserved */ + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + ClearPageReserved(page); + pci_free_consistent(s->dev, PAGE_SIZE << db->buforder, db->rawbuf, db->dmaaddr); + } + db->rawbuf = NULL; + db->mapped = db->ready = 0; +} + +static int prog_dmabuf(struct es1371_state *s, struct dmabuf *db, unsigned rate, unsigned fmt, unsigned reg) +{ + int order; + unsigned bytepersec; + unsigned bufs; + struct page *page, *pend; + + db->hwptr = db->swptr = db->total_bytes = db->count = db->error = db->endcleared = 0; + if (!db->rawbuf) { + db->ready = db->mapped = 0; + for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--) + if ((db->rawbuf = pci_alloc_consistent(s->dev, PAGE_SIZE << order, &db->dmaaddr))) + break; + if (!db->rawbuf) + return -ENOMEM; + db->buforder = order; + /* now mark the pages as reserved; otherwise remap_pfn_range doesn't do what we want */ + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + SetPageReserved(page); + } + fmt &= ES1371_FMT_MASK; + bytepersec = rate << sample_shift[fmt]; + bufs = PAGE_SIZE << db->buforder; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < bytepersec) + db->fragshift = ld2(bytepersec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(bytepersec/100/(db->subdivision ? db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + db->numfrag = bufs >> db->fragshift; + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->numfrag = bufs >> db->fragshift; + } + db->fragsize = 1 << db->fragshift; + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; + db->fragsamples = db->fragsize >> sample_shift[fmt]; + db->dmasize = db->numfrag << db->fragshift; + memset(db->rawbuf, (fmt & ES1371_FMT_S16) ? 0 : 0x80, db->dmasize); + outl((reg >> 8) & 15, s->io+ES1371_REG_MEMPAGE); + outl(db->dmaaddr, s->io+(reg & 0xff)); + outl((db->dmasize >> 2)-1, s->io+((reg + 4) & 0xff)); + db->enabled = 1; + db->ready = 1; + return 0; +} + +static inline int prog_dmabuf_adc(struct es1371_state *s) +{ + stop_adc(s); + return prog_dmabuf(s, &s->dma_adc, s->adcrate, (s->sctrl >> SCTRL_SH_R1FMT) & ES1371_FMT_MASK, + ES1371_REG_ADC_FRAMEADR); +} + +static inline int prog_dmabuf_dac2(struct es1371_state *s) +{ + stop_dac2(s); + return prog_dmabuf(s, &s->dma_dac2, s->dac2rate, (s->sctrl >> SCTRL_SH_P2FMT) & ES1371_FMT_MASK, + ES1371_REG_DAC2_FRAMEADR); +} + +static inline int prog_dmabuf_dac1(struct es1371_state *s) +{ + stop_dac1(s); + return prog_dmabuf(s, &s->dma_dac1, s->dac1rate, (s->sctrl >> SCTRL_SH_P1FMT) & ES1371_FMT_MASK, + ES1371_REG_DAC1_FRAMEADR); +} + +static inline unsigned get_hwptr(struct es1371_state *s, struct dmabuf *db, unsigned reg) +{ + unsigned hwptr, diff; + + outl((reg >> 8) & 15, s->io+ES1371_REG_MEMPAGE); + hwptr = (inl(s->io+(reg & 0xff)) >> 14) & 0x3fffc; + diff = (db->dmasize + hwptr - db->hwptr) % db->dmasize; + db->hwptr = hwptr; + return diff; +} + +static inline 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; + } + memset(((char *)buf) + bptr, c, len); +} + +/* call with spinlock held! */ +static void es1371_update_ptr(struct es1371_state *s) +{ + int diff; + + /* update ADC pointer */ + if (s->ctrl & CTRL_ADC_EN) { + diff = get_hwptr(s, &s->dma_adc, ES1371_REG_ADC_FRAMECNT); + s->dma_adc.total_bytes += diff; + s->dma_adc.count += diff; + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + wake_up(&s->dma_adc.wait); + if (!s->dma_adc.mapped) { + if (s->dma_adc.count > (signed)(s->dma_adc.dmasize - ((3 * s->dma_adc.fragsize) >> 1))) { + s->ctrl &= ~CTRL_ADC_EN; + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + s->dma_adc.error++; + } + } + } + /* update DAC1 pointer */ + if (s->ctrl & CTRL_DAC1_EN) { + diff = get_hwptr(s, &s->dma_dac1, ES1371_REG_DAC1_FRAMECNT); + s->dma_dac1.total_bytes += diff; + if (s->dma_dac1.mapped) { + s->dma_dac1.count += diff; + if (s->dma_dac1.count >= (signed)s->dma_dac1.fragsize) + wake_up(&s->dma_dac1.wait); + } else { + s->dma_dac1.count -= diff; + if (s->dma_dac1.count <= 0) { + s->ctrl &= ~CTRL_DAC1_EN; + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + s->dma_dac1.error++; + } else if (s->dma_dac1.count <= (signed)s->dma_dac1.fragsize && !s->dma_dac1.endcleared) { + clear_advance(s->dma_dac1.rawbuf, s->dma_dac1.dmasize, s->dma_dac1.swptr, + s->dma_dac1.fragsize, (s->sctrl & SCTRL_P1SEB) ? 0 : 0x80); + s->dma_dac1.endcleared = 1; + } + if (s->dma_dac1.count + (signed)s->dma_dac1.fragsize <= (signed)s->dma_dac1.dmasize) + wake_up(&s->dma_dac1.wait); + } + } + /* update DAC2 pointer */ + if (s->ctrl & CTRL_DAC2_EN) { + diff = get_hwptr(s, &s->dma_dac2, ES1371_REG_DAC2_FRAMECNT); + s->dma_dac2.total_bytes += diff; + if (s->dma_dac2.mapped) { + s->dma_dac2.count += diff; + if (s->dma_dac2.count >= (signed)s->dma_dac2.fragsize) + wake_up(&s->dma_dac2.wait); + } else { + s->dma_dac2.count -= diff; + if (s->dma_dac2.count <= 0) { + s->ctrl &= ~CTRL_DAC2_EN; + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + s->dma_dac2.error++; + } else if (s->dma_dac2.count <= (signed)s->dma_dac2.fragsize && !s->dma_dac2.endcleared) { + clear_advance(s->dma_dac2.rawbuf, s->dma_dac2.dmasize, s->dma_dac2.swptr, + s->dma_dac2.fragsize, (s->sctrl & SCTRL_P2SEB) ? 0 : 0x80); + s->dma_dac2.endcleared = 1; + } + if (s->dma_dac2.count + (signed)s->dma_dac2.fragsize <= (signed)s->dma_dac2.dmasize) + wake_up(&s->dma_dac2.wait); + } + } +} + +/* hold spinlock for the following! */ +static void es1371_handle_midi(struct es1371_state *s) +{ + unsigned char ch; + int wake; + + if (!(s->ctrl & CTRL_UART_EN)) + return; + wake = 0; + while (inb(s->io+ES1371_REG_UART_STATUS) & USTAT_RXRDY) { + ch = inb(s->io+ES1371_REG_UART_DATA); + if (s->midi.icnt < MIDIINBUF) { + s->midi.ibuf[s->midi.iwr] = ch; + s->midi.iwr = (s->midi.iwr + 1) % MIDIINBUF; + s->midi.icnt++; + } + wake = 1; + } + if (wake) + wake_up(&s->midi.iwait); + wake = 0; + while ((inb(s->io+ES1371_REG_UART_STATUS) & USTAT_TXRDY) && s->midi.ocnt > 0) { + outb(s->midi.obuf[s->midi.ord], s->io+ES1371_REG_UART_DATA); + s->midi.ord = (s->midi.ord + 1) % MIDIOUTBUF; + s->midi.ocnt--; + if (s->midi.ocnt < MIDIOUTBUF-16) + wake = 1; + } + if (wake) + wake_up(&s->midi.owait); + outb((s->midi.ocnt > 0) ? UCTRL_RXINTEN | UCTRL_ENA_TXINT : UCTRL_RXINTEN, s->io+ES1371_REG_UART_CONTROL); +} + +static irqreturn_t es1371_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct es1371_state *s = (struct es1371_state *)dev_id; + unsigned int intsrc, sctl; + + /* fastpath out, to ease interrupt sharing */ + intsrc = inl(s->io+ES1371_REG_STATUS); + if (!(intsrc & 0x80000000)) + return IRQ_NONE; + spin_lock(&s->lock); + /* clear audio interrupts first */ + sctl = s->sctrl; + if (intsrc & STAT_ADC) + sctl &= ~SCTRL_R1INTEN; + if (intsrc & STAT_DAC1) + sctl &= ~SCTRL_P1INTEN; + if (intsrc & STAT_DAC2) + sctl &= ~SCTRL_P2INTEN; + outl(sctl, s->io+ES1371_REG_SERIAL_CONTROL); + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + es1371_update_ptr(s); + es1371_handle_midi(s); + spin_unlock(&s->lock); + return IRQ_HANDLED; +} + +/* --------------------------------------------------------------------- */ + +static const char invalid_magic[] = KERN_CRIT PFX "invalid magic value\n"; + +#define VALIDATE_STATE(s) \ +({ \ + if (!(s) || (s)->magic != ES1371_MAGIC) { \ + printk(invalid_magic); \ + return -ENXIO; \ + } \ +}) + +/* --------------------------------------------------------------------- */ + +/* Conversion table for S/PDIF PCM volume emulation through the SRC */ +/* dB-linear table of DAC vol values; -0dB to -46.5dB with mute */ +static const unsigned short DACVolTable[101] = +{ + 0x1000, 0x0f2a, 0x0e60, 0x0da0, 0x0cea, 0x0c3e, 0x0b9a, 0x0aff, + 0x0a6d, 0x09e1, 0x095e, 0x08e1, 0x086a, 0x07fa, 0x078f, 0x072a, + 0x06cb, 0x0670, 0x061a, 0x05c9, 0x057b, 0x0532, 0x04ed, 0x04ab, + 0x046d, 0x0432, 0x03fa, 0x03c5, 0x0392, 0x0363, 0x0335, 0x030b, + 0x02e2, 0x02bc, 0x0297, 0x0275, 0x0254, 0x0235, 0x0217, 0x01fb, + 0x01e1, 0x01c8, 0x01b0, 0x0199, 0x0184, 0x0170, 0x015d, 0x014b, + 0x0139, 0x0129, 0x0119, 0x010b, 0x00fd, 0x00f0, 0x00e3, 0x00d7, + 0x00cc, 0x00c1, 0x00b7, 0x00ae, 0x00a5, 0x009c, 0x0094, 0x008c, + 0x0085, 0x007e, 0x0077, 0x0071, 0x006b, 0x0066, 0x0060, 0x005b, + 0x0057, 0x0052, 0x004e, 0x004a, 0x0046, 0x0042, 0x003f, 0x003c, + 0x0038, 0x0036, 0x0033, 0x0030, 0x002e, 0x002b, 0x0029, 0x0027, + 0x0025, 0x0023, 0x0021, 0x001f, 0x001e, 0x001c, 0x001b, 0x0019, + 0x0018, 0x0017, 0x0016, 0x0014, 0x0000 +}; + +/* + * when we are in S/PDIF mode, we want to disable any analog output so + * we filter the mixer ioctls + */ +static int mixdev_ioctl(struct ac97_codec *codec, unsigned int cmd, unsigned long arg) +{ + struct es1371_state *s = (struct es1371_state *)codec->private_data; + int val; + unsigned long flags; + unsigned int left, right; + + VALIDATE_STATE(s); + /* filter mixer ioctls to catch PCM and MASTER volume when in S/PDIF mode */ + if (s->spdif_volume == -1) + return codec->mixer_ioctl(codec, cmd, arg); + switch (cmd) { + case SOUND_MIXER_WRITE_VOLUME: + return 0; + + case SOUND_MIXER_WRITE_PCM: /* use SRC for PCM volume */ + if (get_user(val, (int __user *)arg)) + return -EFAULT; + right = ((val >> 8) & 0xff); + left = (val & 0xff); + if (right > 100) + right = 100; + if (left > 100) + left = 100; + s->spdif_volume = (right << 8) | left; + spin_lock_irqsave(&s->lock, flags); + src_write(s, SRCREG_VOL_DAC2, DACVolTable[100 - left]); + src_write(s, SRCREG_VOL_DAC2+1, DACVolTable[100 - right]); + spin_unlock_irqrestore(&s->lock, flags); + return 0; + + case SOUND_MIXER_READ_PCM: + return put_user(s->spdif_volume, (int __user *)arg); + } + return codec->mixer_ioctl(codec, cmd, arg); +} + +/* --------------------------------------------------------------------- */ + +/* + * AC97 Mixer Register to Connections mapping of the Concert 97 board + * + * AC97_MASTER_VOL_STEREO Line Out + * AC97_MASTER_VOL_MONO TAD Output + * AC97_PCBEEP_VOL none + * AC97_PHONE_VOL TAD Input (mono) + * AC97_MIC_VOL MIC Input (mono) + * AC97_LINEIN_VOL Line Input (stereo) + * AC97_CD_VOL CD Input (stereo) + * AC97_VIDEO_VOL none + * AC97_AUX_VOL Aux Input (stereo) + * AC97_PCMOUT_VOL Wave Output (stereo) + */ + +static int es1371_open_mixdev(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct list_head *list; + struct es1371_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct es1371_state, devs); + if (s->codec->dev_mixer == minor) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + return nonseekable_open(inode, file); +} + +static int es1371_release_mixdev(struct inode *inode, struct file *file) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + + VALIDATE_STATE(s); + return 0; +} + +static int es1371_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + struct ac97_codec *codec = s->codec; + + return mixdev_ioctl(codec, cmd, arg); +} + +static /*const*/ struct file_operations es1371_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = es1371_ioctl_mixdev, + .open = es1371_open_mixdev, + .release = es1371_release_mixdev, +}; + +/* --------------------------------------------------------------------- */ + +static int drain_dac1(struct es1371_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int count, tmo; + + if (s->dma_dac1.mapped || !s->dma_dac1.ready) + return 0; + add_wait_queue(&s->dma_dac1.wait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac1.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_dac1.wait, &wait); + set_current_state(TASK_RUNNING); + return -EBUSY; + } + tmo = 3 * HZ * (count + s->dma_dac1.fragsize) / 2 / s->dac1rate; + tmo >>= sample_shift[(s->sctrl & SCTRL_P1FMT) >> SCTRL_SH_P1FMT]; + if (!schedule_timeout(tmo + 1)) + DBG(printk(KERN_DEBUG PFX "dac1 dma timed out??\n");) + } + remove_wait_queue(&s->dma_dac1.wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +static int drain_dac2(struct es1371_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int count, tmo; + + if (s->dma_dac2.mapped || !s->dma_dac2.ready) + return 0; + add_wait_queue(&s->dma_dac2.wait, &wait); + for (;;) { + __set_current_state(TASK_UNINTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac2.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_dac2.wait, &wait); + set_current_state(TASK_RUNNING); + return -EBUSY; + } + tmo = 3 * HZ * (count + s->dma_dac2.fragsize) / 2 / s->dac2rate; + tmo >>= sample_shift[(s->sctrl & SCTRL_P2FMT) >> SCTRL_SH_P2FMT]; + if (!schedule_timeout(tmo + 1)) + DBG(printk(KERN_DEBUG PFX "dac2 dma timed out??\n");) + } + remove_wait_queue(&s->dma_dac2.wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static ssize_t es1371_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret = 0; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_adc.mapped) + return -ENXIO; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + down(&s->sem); + if (!s->dma_adc.ready && (ret = prog_dmabuf_adc(s))) + goto out2; + + add_wait_queue(&s->dma_adc.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + swptr = s->dma_adc.swptr; + cnt = s->dma_adc.dmasize-swptr; + if (s->dma_adc.count < cnt) + cnt = s->dma_adc.count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (s->dma_adc.enabled) + start_adc(s); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + up(&s->sem); + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out2; + } + down(&s->sem); + if (s->dma_adc.mapped) + { + ret = -ENXIO; + goto out; + } + continue; + } + if (copy_to_user(buffer, s->dma_adc.rawbuf + swptr, cnt)) { + if (!ret) + ret = -EFAULT; + goto out; + } + swptr = (swptr + cnt) % s->dma_adc.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_adc.swptr = swptr; + s->dma_adc.count -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->dma_adc.enabled) + start_adc(s); + } +out: + up(&s->sem); +out2: + remove_wait_queue(&s->dma_adc.wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +static ssize_t es1371_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_dac2.mapped) + return -ENXIO; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + down(&s->sem); + if (!s->dma_dac2.ready && (ret = prog_dmabuf_dac2(s))) + goto out3; + ret = 0; + add_wait_queue(&s->dma_dac2.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + if (s->dma_dac2.count < 0) { + s->dma_dac2.count = 0; + s->dma_dac2.swptr = s->dma_dac2.hwptr; + } + swptr = s->dma_dac2.swptr; + cnt = s->dma_dac2.dmasize-swptr; + if (s->dma_dac2.count + cnt > s->dma_dac2.dmasize) + cnt = s->dma_dac2.dmasize - s->dma_dac2.count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (s->dma_dac2.enabled) + start_dac2(s); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + up(&s->sem); + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out2; + } + down(&s->sem); + if (s->dma_dac2.mapped) + { + ret = -ENXIO; + goto out; + } + continue; + } + if (copy_from_user(s->dma_dac2.rawbuf + swptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + goto out; + } + swptr = (swptr + cnt) % s->dma_dac2.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_dac2.swptr = swptr; + s->dma_dac2.count += cnt; + s->dma_dac2.endcleared = 0; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->dma_dac2.enabled) + start_dac2(s); + } +out: + up(&s->sem); +out2: + remove_wait_queue(&s->dma_dac2.wait, &wait); +out3: + set_current_state(TASK_RUNNING); + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int es1371_poll(struct file *file, struct poll_table_struct *wait) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) { + if (!s->dma_dac2.ready && prog_dmabuf_dac2(s)) + return 0; + poll_wait(file, &s->dma_dac2.wait, wait); + } + if (file->f_mode & FMODE_READ) { + if (!s->dma_adc.ready && prog_dmabuf_adc(s)) + return 0; + poll_wait(file, &s->dma_adc.wait, wait); + } + spin_lock_irqsave(&s->lock, flags); + es1371_update_ptr(s); + if (file->f_mode & FMODE_READ) { + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->dma_dac2.mapped) { + if (s->dma_dac2.count >= (signed)s->dma_dac2.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)s->dma_dac2.dmasize >= s->dma_dac2.count + (signed)s->dma_dac2.fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int es1371_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + struct dmabuf *db; + int ret = 0; + unsigned long size; + + VALIDATE_STATE(s); + lock_kernel(); + down(&s->sem); + + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf_dac2(s)) != 0) { + goto out; + } + db = &s->dma_dac2; + } else if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf_adc(s)) != 0) { + goto out; + } + db = &s->dma_adc; + } else { + ret = -EINVAL; + goto out; + } + if (vma->vm_pgoff != 0) { + ret = -EINVAL; + goto out; + } + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << db->buforder)) { + ret = -EINVAL; + goto out; + } + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(db->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) { + ret = -EAGAIN; + goto out; + } + db->mapped = 1; +out: + up(&s->sem); + unlock_kernel(); + return ret; +} + +static int es1371_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int count; + int val, mapped, ret; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + VALIDATE_STATE(s); + mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac2.mapped) || + ((file->f_mode & FMODE_READ) && s->dma_adc.mapped); + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_SYNC: + if (file->f_mode & FMODE_WRITE) + return drain_dac2(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, p); + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_WRITE) { + stop_dac2(s); + synchronize_irq(s->irq); + s->dma_dac2.swptr = s->dma_dac2.hwptr = s->dma_dac2.count = s->dma_dac2.total_bytes = 0; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(s->irq); + s->dma_adc.swptr = s->dma_adc.hwptr = s->dma_adc.count = s->dma_adc.total_bytes = 0; + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + set_adc_rate(s, val); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac2(s); + s->dma_dac2.ready = 0; + set_dac2_rate(s, val); + } + } + return put_user((file->f_mode & FMODE_READ) ? s->adcrate : s->dac2rate, p); + + case SNDCTL_DSP_STEREO: + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val) + s->sctrl |= SCTRL_R1SMB; + else + s->sctrl &= ~SCTRL_R1SMB; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac2(s); + s->dma_dac2.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val) + s->sctrl |= SCTRL_P2SMB; + else + s->sctrl &= ~SCTRL_P2SMB; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + if (val != 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val >= 2) + s->sctrl |= SCTRL_R1SMB; + else + s->sctrl &= ~SCTRL_R1SMB; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac2(s); + s->dma_dac2.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val >= 2) + s->sctrl |= SCTRL_P2SMB; + else + s->sctrl &= ~SCTRL_P2SMB; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + } + return put_user((s->sctrl & ((file->f_mode & FMODE_READ) ? SCTRL_R1SMB : SCTRL_P2SMB)) ? 2 : 1, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_LE|AFMT_U8, p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, p)) + return -EFAULT; + if (val != AFMT_QUERY) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val == AFMT_S16_LE) + s->sctrl |= SCTRL_R1SEB; + else + s->sctrl &= ~SCTRL_R1SEB; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac2(s); + s->dma_dac2.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val == AFMT_S16_LE) + s->sctrl |= SCTRL_P2SEB; + else + s->sctrl &= ~SCTRL_P2SEB; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + } + return put_user((s->sctrl & ((file->f_mode & FMODE_READ) ? SCTRL_R1SEB : SCTRL_P2SEB)) ? + AFMT_S16_LE : AFMT_U8, p); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if (file->f_mode & FMODE_READ && s->ctrl & CTRL_ADC_EN) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && s->ctrl & CTRL_DAC2_EN) + val |= PCM_ENABLE_OUTPUT; + return put_user(val, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + 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; + s->dma_adc.enabled = 1; + start_adc(s); + } else { + s->dma_adc.enabled = 0; + stop_adc(s); + } + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) { + if (!s->dma_dac2.ready && (ret = prog_dmabuf_dac2(s))) + return ret; + s->dma_dac2.enabled = 1; + start_dac2(s); + } else { + s->dma_dac2.enabled = 0; + stop_dac2(s); + } + } + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac2.ready && (val = prog_dmabuf_dac2(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1371_update_ptr(s); + abinfo.fragsize = s->dma_dac2.fragsize; + count = s->dma_dac2.count; + if (count < 0) + count = 0; + abinfo.bytes = s->dma_dac2.dmasize - count; + abinfo.fragstotal = s->dma_dac2.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac2.fragshift; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user(argp, &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)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1371_update_ptr(s); + abinfo.fragsize = s->dma_adc.fragsize; + count = s->dma_adc.count; + if (count < 0) + count = 0; + abinfo.bytes = 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(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac2.ready && (val = prog_dmabuf_dac2(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1371_update_ptr(s); + count = s->dma_dac2.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + return put_user(count, p); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!s->dma_adc.ready && (val = prog_dmabuf_adc(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1371_update_ptr(s); + cinfo.bytes = s->dma_adc.total_bytes; + count = s->dma_adc.count; + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_adc.fragshift; + 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); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac2.ready && (val = prog_dmabuf_dac2(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1371_update_ptr(s); + cinfo.bytes = s->dma_dac2.total_bytes; + count = s->dma_dac2.count; + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_dac2.fragshift; + cinfo.ptr = s->dma_dac2.hwptr; + if (s->dma_dac2.mapped) + s->dma_dac2.count &= s->dma_dac2.fragsize-1; + spin_unlock_irqrestore(&s->lock, flags); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + if ((val = prog_dmabuf_dac2(s))) + return val; + return put_user(s->dma_dac2.fragsize, p); + } + if ((val = prog_dmabuf_adc(s))) + return val; + return put_user(s->dma_adc.fragsize, p); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = val & 0xffff; + s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_adc.ossfragshift < 4) + s->dma_adc.ossfragshift = 4; + if (s->dma_adc.ossfragshift > 15) + s->dma_adc.ossfragshift = 15; + if (s->dma_adc.ossmaxfrags < 4) + s->dma_adc.ossmaxfrags = 4; + } + if (file->f_mode & FMODE_WRITE) { + s->dma_dac2.ossfragshift = val & 0xffff; + s->dma_dac2.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac2.ossfragshift < 4) + s->dma_dac2.ossfragshift = 4; + if (s->dma_dac2.ossfragshift > 15) + s->dma_dac2.ossfragshift = 15; + if (s->dma_dac2.ossmaxfrags < 4) + s->dma_dac2.ossmaxfrags = 4; + } + return 0; + + case SNDCTL_DSP_SUBDIVIDE: + if ((file->f_mode & FMODE_READ && s->dma_adc.subdivision) || + (file->f_mode & FMODE_WRITE && s->dma_dac2.subdivision)) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + if (file->f_mode & FMODE_READ) + s->dma_adc.subdivision = val; + if (file->f_mode & FMODE_WRITE) + s->dma_dac2.subdivision = val; + return 0; + + case SOUND_PCM_READ_RATE: + return put_user((file->f_mode & FMODE_READ) ? s->adcrate : s->dac2rate, p); + + case SOUND_PCM_READ_CHANNELS: + return put_user((s->sctrl & ((file->f_mode & FMODE_READ) ? SCTRL_R1SMB : SCTRL_P2SMB)) ? 2 : 1, p); + + case SOUND_PCM_READ_BITS: + return put_user((s->sctrl & ((file->f_mode & FMODE_READ) ? SCTRL_R1SEB : SCTRL_P2SEB)) ? 16 : 8, p); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + + } + return mixdev_ioctl(s->codec, cmd, arg); +} + +static int es1371_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + struct list_head *list; + struct es1371_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct es1371_state, devs); + if (!((s->dev_audio ^ minor) & ~0xf)) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & file->f_mode) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = s->dma_adc.subdivision = 0; + s->dma_adc.enabled = 1; + set_adc_rate(s, 8000); + } + if (file->f_mode & FMODE_WRITE) { + s->dma_dac2.ossfragshift = s->dma_dac2.ossmaxfrags = s->dma_dac2.subdivision = 0; + s->dma_dac2.enabled = 1; + set_dac2_rate(s, 8000); + } + spin_lock_irqsave(&s->lock, flags); + if (file->f_mode & FMODE_READ) { + s->sctrl &= ~SCTRL_R1FMT; + if ((minor & 0xf) == SND_DEV_DSP16) + s->sctrl |= ES1371_FMT_S16_MONO << SCTRL_SH_R1FMT; + else + s->sctrl |= ES1371_FMT_U8_MONO << SCTRL_SH_R1FMT; + } + if (file->f_mode & FMODE_WRITE) { + s->sctrl &= ~SCTRL_P2FMT; + if ((minor & 0xf) == SND_DEV_DSP16) + s->sctrl |= ES1371_FMT_S16_MONO << SCTRL_SH_P2FMT; + else + s->sctrl |= ES1371_FMT_U8_MONO << SCTRL_SH_P2FMT; + } + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + up(&s->open_sem); + init_MUTEX(&s->sem); + return nonseekable_open(inode, file); +} + +static int es1371_release(struct inode *inode, struct file *file) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + + VALIDATE_STATE(s); + lock_kernel(); + if (file->f_mode & FMODE_WRITE) + drain_dac2(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem); + if (file->f_mode & FMODE_WRITE) { + stop_dac2(s); + dealloc_dmabuf(s, &s->dma_dac2); + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + dealloc_dmabuf(s, &s->dma_adc); + } + s->open_mode &= ~(file->f_mode & (FMODE_READ|FMODE_WRITE)); + up(&s->open_sem); + wake_up(&s->open_wait); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations es1371_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = es1371_read, + .write = es1371_write, + .poll = es1371_poll, + .ioctl = es1371_ioctl, + .mmap = es1371_mmap, + .open = es1371_open, + .release = es1371_release, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t es1371_write_dac(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret = 0; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_dac1.mapped) + return -ENXIO; + if (!s->dma_dac1.ready && (ret = prog_dmabuf_dac1(s))) + return ret; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + add_wait_queue(&s->dma_dac1.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + if (s->dma_dac1.count < 0) { + s->dma_dac1.count = 0; + s->dma_dac1.swptr = s->dma_dac1.hwptr; + } + swptr = s->dma_dac1.swptr; + cnt = s->dma_dac1.dmasize-swptr; + if (s->dma_dac1.count + cnt > s->dma_dac1.dmasize) + cnt = s->dma_dac1.dmasize - s->dma_dac1.count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (s->dma_dac1.enabled) + start_dac1(s); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_from_user(s->dma_dac1.rawbuf + swptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + swptr = (swptr + cnt) % s->dma_dac1.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_dac1.swptr = swptr; + s->dma_dac1.count += cnt; + s->dma_dac1.endcleared = 0; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->dma_dac1.enabled) + start_dac1(s); + } + remove_wait_queue(&s->dma_dac1.wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int es1371_poll_dac(struct file *file, struct poll_table_struct *wait) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (!s->dma_dac1.ready && prog_dmabuf_dac1(s)) + return 0; + poll_wait(file, &s->dma_dac1.wait, wait); + spin_lock_irqsave(&s->lock, flags); + es1371_update_ptr(s); + if (s->dma_dac1.mapped) { + if (s->dma_dac1.count >= (signed)s->dma_dac1.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)s->dma_dac1.dmasize >= s->dma_dac1.count + (signed)s->dma_dac1.fragsize) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int es1371_mmap_dac(struct file *file, struct vm_area_struct *vma) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + int ret; + unsigned long size; + + VALIDATE_STATE(s); + if (!(vma->vm_flags & VM_WRITE)) + return -EINVAL; + lock_kernel(); + if ((ret = prog_dmabuf_dac1(s)) != 0) + goto out; + ret = -EINVAL; + if (vma->vm_pgoff != 0) + goto out; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << s->dma_dac1.buforder)) + goto out; + ret = -EAGAIN; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(s->dma_dac1.rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + goto out; + s->dma_dac1.mapped = 1; + ret = 0; +out: + unlock_kernel(); + return ret; +} + +static int es1371_ioctl_dac(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int count; + int val, ret; + int __user *p = (int __user *)arg; + + VALIDATE_STATE(s); + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_SYNC: + return drain_dac1(s, 0/*file->f_flags & O_NONBLOCK*/); + + case SNDCTL_DSP_SETDUPLEX: + return -EINVAL; + + case SNDCTL_DSP_GETCAPS: + return put_user(DSP_CAP_REALTIME | DSP_CAP_TRIGGER | DSP_CAP_MMAP, p); + + case SNDCTL_DSP_RESET: + stop_dac1(s); + synchronize_irq(s->irq); + s->dma_dac1.swptr = s->dma_dac1.hwptr = s->dma_dac1.count = s->dma_dac1.total_bytes = 0; + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { + stop_dac1(s); + s->dma_dac1.ready = 0; + set_dac1_rate(s, val); + } + return put_user(s->dac1rate, p); + + case SNDCTL_DSP_STEREO: + if (get_user(val, p)) + return -EFAULT; + stop_dac1(s); + s->dma_dac1.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val) + s->sctrl |= SCTRL_P1SMB; + else + s->sctrl &= ~SCTRL_P1SMB; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + if (val != 0) { + stop_dac1(s); + s->dma_dac1.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val >= 2) + s->sctrl |= SCTRL_P1SMB; + else + s->sctrl &= ~SCTRL_P1SMB; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + return put_user((s->sctrl & SCTRL_P1SMB) ? 2 : 1, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_LE|AFMT_U8, p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, p)) + return -EFAULT; + if (val != AFMT_QUERY) { + stop_dac1(s); + s->dma_dac1.ready = 0; + spin_lock_irqsave(&s->lock, flags); + if (val == AFMT_S16_LE) + s->sctrl |= SCTRL_P1SEB; + else + s->sctrl &= ~SCTRL_P1SEB; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + } + return put_user((s->sctrl & SCTRL_P1SEB) ? AFMT_S16_LE : AFMT_U8, p); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + return put_user((s->ctrl & CTRL_DAC1_EN) ? PCM_ENABLE_OUTPUT : 0, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + return -EFAULT; + if (val & PCM_ENABLE_OUTPUT) { + if (!s->dma_dac1.ready && (ret = prog_dmabuf_dac1(s))) + return ret; + s->dma_dac1.enabled = 1; + start_dac1(s); + } else { + s->dma_dac1.enabled = 0; + stop_dac1(s); + } + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!s->dma_dac1.ready && (val = prog_dmabuf_dac1(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1371_update_ptr(s); + abinfo.fragsize = s->dma_dac1.fragsize; + count = s->dma_dac1.count; + if (count < 0) + count = 0; + abinfo.bytes = s->dma_dac1.dmasize - count; + abinfo.fragstotal = s->dma_dac1.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac1.fragshift; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user((void __user *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!s->dma_dac1.ready && (val = prog_dmabuf_dac1(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1371_update_ptr(s); + count = s->dma_dac1.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + return put_user(count, p); + + case SNDCTL_DSP_GETOPTR: + if (!s->dma_dac1.ready && (val = prog_dmabuf_dac1(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + es1371_update_ptr(s); + cinfo.bytes = s->dma_dac1.total_bytes; + count = s->dma_dac1.count; + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_dac1.fragshift; + cinfo.ptr = s->dma_dac1.hwptr; + if (s->dma_dac1.mapped) + s->dma_dac1.count &= s->dma_dac1.fragsize-1; + spin_unlock_irqrestore(&s->lock, flags); + if (copy_to_user((void __user *)arg, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + if ((val = prog_dmabuf_dac1(s))) + return val; + return put_user(s->dma_dac1.fragsize, p); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + s->dma_dac1.ossfragshift = val & 0xffff; + s->dma_dac1.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac1.ossfragshift < 4) + s->dma_dac1.ossfragshift = 4; + if (s->dma_dac1.ossfragshift > 15) + s->dma_dac1.ossfragshift = 15; + if (s->dma_dac1.ossmaxfrags < 4) + s->dma_dac1.ossmaxfrags = 4; + return 0; + + case SNDCTL_DSP_SUBDIVIDE: + if (s->dma_dac1.subdivision) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + s->dma_dac1.subdivision = val; + return 0; + + case SOUND_PCM_READ_RATE: + return put_user(s->dac1rate, p); + + case SOUND_PCM_READ_CHANNELS: + return put_user((s->sctrl & SCTRL_P1SMB) ? 2 : 1, p); + + case SOUND_PCM_READ_BITS: + return put_user((s->sctrl & SCTRL_P1SEB) ? 16 : 8, p); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + + } + return mixdev_ioctl(s->codec, cmd, arg); +} + +static int es1371_open_dac(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + struct list_head *list; + struct es1371_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct es1371_state, devs); + if (!((s->dev_dac ^ minor) & ~0xf)) + break; + } + VALIDATE_STATE(s); + /* we allow opening with O_RDWR, most programs do it although they will only write */ +#if 0 + if (file->f_mode & FMODE_READ) + return -EPERM; +#endif + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & FMODE_DAC) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + s->dma_dac1.ossfragshift = s->dma_dac1.ossmaxfrags = s->dma_dac1.subdivision = 0; + s->dma_dac1.enabled = 1; + set_dac1_rate(s, 8000); + spin_lock_irqsave(&s->lock, flags); + s->sctrl &= ~SCTRL_P1FMT; + if ((minor & 0xf) == SND_DEV_DSP16) + s->sctrl |= ES1371_FMT_S16_MONO << SCTRL_SH_P1FMT; + else + s->sctrl |= ES1371_FMT_U8_MONO << SCTRL_SH_P1FMT; + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + spin_unlock_irqrestore(&s->lock, flags); + s->open_mode |= FMODE_DAC; + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int es1371_release_dac(struct inode *inode, struct file *file) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + + VALIDATE_STATE(s); + lock_kernel(); + drain_dac1(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem); + stop_dac1(s); + dealloc_dmabuf(s, &s->dma_dac1); + s->open_mode &= ~FMODE_DAC; + up(&s->open_sem); + wake_up(&s->open_wait); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations es1371_dac_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = es1371_write_dac, + .poll = es1371_poll_dac, + .ioctl = es1371_ioctl_dac, + .mmap = es1371_mmap_dac, + .open = es1371_open_dac, + .release = es1371_release_dac, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t es1371_midi_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + if (count == 0) + return 0; + ret = 0; + add_wait_queue(&s->midi.iwait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.ird; + cnt = MIDIINBUF - ptr; + if (s->midi.icnt < cnt) + cnt = s->midi.icnt; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_to_user(buffer, s->midi.ibuf + ptr, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + ptr = (ptr + cnt) % MIDIINBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.ird = ptr; + s->midi.icnt -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + break; + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&s->midi.iwait, &wait); + return ret; +} + +static ssize_t es1371_midi_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + if (count == 0) + return 0; + ret = 0; + add_wait_queue(&s->midi.owait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.owr; + cnt = MIDIOUTBUF - ptr; + if (s->midi.ocnt + cnt > MIDIOUTBUF) + cnt = MIDIOUTBUF - s->midi.ocnt; + if (cnt <= 0) { + __set_current_state(TASK_INTERRUPTIBLE); + es1371_handle_midi(s); + } + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_from_user(s->midi.obuf + ptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + ptr = (ptr + cnt) % MIDIOUTBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.owr = ptr; + s->midi.ocnt += cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + spin_lock_irqsave(&s->lock, flags); + es1371_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&s->midi.owait, &wait); + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int es1371_midi_poll(struct file *file, struct poll_table_struct *wait) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) + poll_wait(file, &s->midi.owait, wait); + if (file->f_mode & FMODE_READ) + poll_wait(file, &s->midi.iwait, wait); + spin_lock_irqsave(&s->lock, flags); + if (file->f_mode & FMODE_READ) { + if (s->midi.icnt > 0) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->midi.ocnt < MIDIOUTBUF) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int es1371_midi_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + struct list_head *list; + struct es1371_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct es1371_state, devs); + if (s->dev_midi == minor) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & (file->f_mode << FMODE_MIDI_SHIFT)) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + outb(UCTRL_CNTRL_SWR, s->io+ES1371_REG_UART_CONTROL); + outb(0, s->io+ES1371_REG_UART_CONTROL); + outb(0, s->io+ES1371_REG_UART_TEST); + } + if (file->f_mode & FMODE_READ) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + } + if (file->f_mode & FMODE_WRITE) { + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + } + s->ctrl |= CTRL_UART_EN; + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + es1371_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + s->open_mode |= (file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ | FMODE_MIDI_WRITE); + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int es1371_midi_release(struct inode *inode, struct file *file) +{ + struct es1371_state *s = (struct es1371_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + unsigned count, tmo; + + VALIDATE_STATE(s); + lock_kernel(); + if (file->f_mode & FMODE_WRITE) { + add_wait_queue(&s->midi.owait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->midi.ocnt; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (file->f_flags & O_NONBLOCK) + break; + tmo = (count * HZ) / 3100; + if (!schedule_timeout(tmo ? : 1) && tmo) + printk(KERN_DEBUG PFX "midi timed out??\n"); + } + remove_wait_queue(&s->midi.owait, &wait); + set_current_state(TASK_RUNNING); + } + down(&s->open_sem); + s->open_mode &= ~((file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ|FMODE_MIDI_WRITE)); + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + s->ctrl &= ~CTRL_UART_EN; + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + } + spin_unlock_irqrestore(&s->lock, flags); + up(&s->open_sem); + wake_up(&s->open_wait); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations es1371_midi_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = es1371_midi_read, + .write = es1371_midi_write, + .poll = es1371_midi_poll, + .open = es1371_midi_open, + .release = es1371_midi_release, +}; + +/* --------------------------------------------------------------------- */ + +/* + * for debugging purposes, we'll create a proc device that dumps the + * CODEC chipstate + */ + +#ifdef ES1371_DEBUG +static int proc_es1371_dump (char *buf, char **start, off_t fpos, int length, int *eof, void *data) +{ + struct es1371_state *s; + int cnt, len = 0; + + if (list_empty(&devs)) + return 0; + s = list_entry(devs.next, struct es1371_state, devs); + /* print out header */ + len += sprintf(buf + len, "\t\tCreative ES137x Debug Dump-o-matic\n"); + + /* print out CODEC state */ + len += sprintf (buf + len, "AC97 CODEC state\n"); + for (cnt=0; cnt <= 0x7e; cnt = cnt +2) + len+= sprintf (buf + len, "reg:0x%02x val:0x%04x\n", cnt, rdcodec(s->codec, cnt)); + + if (fpos >=len){ + *start = buf; + *eof =1; + return 0; + } + *start = buf + fpos; + if ((len -= fpos) > length) + return length; + *eof =1; + return len; + +} +#endif /* ES1371_DEBUG */ + +/* --------------------------------------------------------------------- */ + +/* maximum number of devices; only used for command line params */ +#define NR_DEVICE 5 + +static int spdif[NR_DEVICE]; +static int nomix[NR_DEVICE]; +static int amplifier[NR_DEVICE]; + +static unsigned int devindex; + +module_param_array(spdif, bool, NULL, 0); +MODULE_PARM_DESC(spdif, "if 1 the output is in S/PDIF digital mode"); +module_param_array(nomix, bool, NULL, 0); +MODULE_PARM_DESC(nomix, "if 1 no analog audio is mixed to the digital output"); +module_param_array(amplifier, bool, NULL, 0); +MODULE_PARM_DESC(amplifier, "Set to 1 if the machine needs the amp control enabling (many laptops)"); + +MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu"); +MODULE_DESCRIPTION("ES1371 AudioPCI97 Driver"); +MODULE_LICENSE("GPL"); + + +/* --------------------------------------------------------------------- */ + +static struct initvol { + int mixch; + int vol; +} initvol[] __devinitdata = { + { SOUND_MIXER_WRITE_LINE, 0x4040 }, + { SOUND_MIXER_WRITE_CD, 0x4040 }, + { MIXER_WRITE(SOUND_MIXER_VIDEO), 0x4040 }, + { SOUND_MIXER_WRITE_LINE1, 0x4040 }, + { SOUND_MIXER_WRITE_PCM, 0x4040 }, + { SOUND_MIXER_WRITE_VOLUME, 0x4040 }, + { MIXER_WRITE(SOUND_MIXER_PHONEOUT), 0x4040 }, + { SOUND_MIXER_WRITE_OGAIN, 0x4040 }, + { MIXER_WRITE(SOUND_MIXER_PHONEIN), 0x4040 }, + { SOUND_MIXER_WRITE_SPEAKER, 0x4040 }, + { SOUND_MIXER_WRITE_MIC, 0x4040 }, + { SOUND_MIXER_WRITE_RECLEV, 0x4040 }, + { SOUND_MIXER_WRITE_IGAIN, 0x4040 } +}; + +static struct +{ + short svid, sdid; +} amplifier_needed[] = +{ + { 0x107B, 0x2150 }, /* Gateway Solo 2150 */ + { 0x13BD, 0x100C }, /* Mebius PC-MJ100V */ + { 0x1102, 0x5938 }, /* Targa Xtender 300 */ + { 0x1102, 0x8938 }, /* IPC notebook */ + { PCI_ANY_ID, PCI_ANY_ID } +}; + +static int __devinit es1371_probe(struct pci_dev *pcidev, const struct pci_device_id *pciid) +{ + struct es1371_state *s; + struct gameport *gp; + mm_segment_t fs; + int i, gpio, val, res = -1; + int idx; + unsigned long tmo; + signed long tmo2; + unsigned int cssr; + + if ((res=pci_enable_device(pcidev))) + return res; + + if (!(pci_resource_flags(pcidev, 0) & IORESOURCE_IO)) + return -ENODEV; + if (pcidev->irq == 0) + return -ENODEV; + i = pci_set_dma_mask(pcidev, 0xffffffff); + if (i) { + printk(KERN_WARNING "es1371: architecture does not support 32bit PCI busmaster DMA\n"); + return i; + } + if (!(s = kmalloc(sizeof(struct es1371_state), GFP_KERNEL))) { + printk(KERN_WARNING PFX "out of memory\n"); + return -ENOMEM; + } + memset(s, 0, sizeof(struct es1371_state)); + + s->codec = ac97_alloc_codec(); + if(s->codec == NULL) + goto err_codec; + + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac1.wait); + init_waitqueue_head(&s->dma_dac2.wait); + init_waitqueue_head(&s->open_wait); + init_waitqueue_head(&s->midi.iwait); + init_waitqueue_head(&s->midi.owait); + init_MUTEX(&s->open_sem); + spin_lock_init(&s->lock); + s->magic = ES1371_MAGIC; + s->dev = pcidev; + s->io = pci_resource_start(pcidev, 0); + s->irq = pcidev->irq; + s->vendor = pcidev->vendor; + s->device = pcidev->device; + pci_read_config_byte(pcidev, PCI_REVISION_ID, &s->rev); + s->codec->private_data = s; + s->codec->id = 0; + s->codec->codec_read = rdcodec; + s->codec->codec_write = wrcodec; + printk(KERN_INFO PFX "found chip, vendor id 0x%04x device id 0x%04x revision 0x%02x\n", + s->vendor, s->device, s->rev); + if (!request_region(s->io, ES1371_EXTENT, "es1371")) { + printk(KERN_ERR PFX "io ports %#lx-%#lx in use\n", s->io, s->io+ES1371_EXTENT-1); + res = -EBUSY; + goto err_region; + } + if ((res=request_irq(s->irq, es1371_interrupt, SA_SHIRQ, "es1371",s))) { + printk(KERN_ERR PFX "irq %u in use\n", s->irq); + goto err_irq; + } + printk(KERN_INFO PFX "found es1371 rev %d at io %#lx irq %u\n", + s->rev, s->io, s->irq); + /* register devices */ + if ((res=(s->dev_audio = register_sound_dsp(&es1371_audio_fops,-1)))<0) + goto err_dev1; + if ((res=(s->codec->dev_mixer = register_sound_mixer(&es1371_mixer_fops, -1))) < 0) + goto err_dev2; + if ((res=(s->dev_dac = register_sound_dsp(&es1371_dac_fops, -1))) < 0) + goto err_dev3; + if ((res=(s->dev_midi = register_sound_midi(&es1371_midi_fops, -1)))<0 ) + goto err_dev4; +#ifdef ES1371_DEBUG + /* initialize the debug proc device */ + s->ps = create_proc_read_entry("es1371",0,NULL,proc_es1371_dump,NULL); +#endif /* ES1371_DEBUG */ + + /* initialize codec registers */ + s->ctrl = 0; + + /* Check amplifier requirements */ + + if (amplifier[devindex]) + s->ctrl |= CTRL_GPIO_OUT0; + else for(idx = 0; amplifier_needed[idx].svid != PCI_ANY_ID; idx++) + { + if(pcidev->subsystem_vendor == amplifier_needed[idx].svid && + pcidev->subsystem_device == amplifier_needed[idx].sdid) + { + s->ctrl |= CTRL_GPIO_OUT0; /* turn internal amplifier on */ + printk(KERN_INFO PFX "Enabling internal amplifier.\n"); + } + } + + for (gpio = 0x218; gpio >= 0x200; gpio -= 0x08) + if (request_region(gpio, JOY_EXTENT, "es1371")) + break; + + if (gpio < 0x200) { + printk(KERN_ERR PFX "no free joystick address found\n"); + } else if (!(s->gameport = gp = gameport_allocate_port())) { + printk(KERN_ERR PFX "can not allocate memory for gameport\n"); + release_region(gpio, JOY_EXTENT); + } else { + gameport_set_name(gp, "ESS1371 Gameport"); + gameport_set_phys(gp, "isa%04x/gameport0", gpio); + gp->dev.parent = &s->dev->dev; + gp->io = gpio; + s->ctrl |= CTRL_JYSTK_EN | (((gpio >> 3) & CTRL_JOY_MASK) << CTRL_JOY_SHIFT); + } + + s->sctrl = 0; + cssr = 0; + s->spdif_volume = -1; + /* check to see if s/pdif mode is being requested */ + if (spdif[devindex]) { + if (s->rev >= 4) { + printk(KERN_INFO PFX "enabling S/PDIF output\n"); + s->spdif_volume = 0; + cssr |= STAT_EN_SPDIF; + s->ctrl |= CTRL_SPDIFEN_B; + if (nomix[devindex]) /* don't mix analog inputs to s/pdif output */ + s->ctrl |= CTRL_RECEN_B; + } else { + printk(KERN_ERR PFX "revision %d does not support S/PDIF\n", s->rev); + } + } + /* initialize the chips */ + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + outl(s->sctrl, s->io+ES1371_REG_SERIAL_CONTROL); + outl(LEGACY_JFAST, s->io+ES1371_REG_LEGACY); + pci_set_master(pcidev); /* enable bus mastering */ + /* if we are a 5880 turn on the AC97 */ + if (s->vendor == PCI_VENDOR_ID_ENSONIQ && + ((s->device == PCI_DEVICE_ID_ENSONIQ_CT5880 && s->rev >= CT5880REV_CT5880_C) || + (s->device == PCI_DEVICE_ID_ENSONIQ_ES1371 && s->rev == ES1371REV_CT5880_A) || + (s->device == PCI_DEVICE_ID_ENSONIQ_ES1371 && s->rev == ES1371REV_ES1373_8))) { + cssr |= CSTAT_5880_AC97_RST; + outl(cssr, s->io+ES1371_REG_STATUS); + /* need to delay around 20ms(bleech) to give + some CODECs enough time to wakeup */ + tmo = jiffies + (HZ / 50) + 1; + for (;;) { + tmo2 = tmo - jiffies; + if (tmo2 <= 0) + break; + schedule_timeout(tmo2); + } + } + /* AC97 warm reset to start the bitclk */ + outl(s->ctrl | CTRL_SYNCRES, s->io+ES1371_REG_CONTROL); + udelay(2); + outl(s->ctrl, s->io+ES1371_REG_CONTROL); + /* init the sample rate converter */ + src_init(s); + /* codec init */ + if (!ac97_probe_codec(s->codec)) { + res = -ENODEV; + goto err_gp; + } + /* set default values */ + + fs = get_fs(); + set_fs(KERNEL_DS); + val = SOUND_MASK_LINE; + mixdev_ioctl(s->codec, SOUND_MIXER_WRITE_RECSRC, (unsigned long)&val); + for (i = 0; i < sizeof(initvol)/sizeof(initvol[0]); i++) { + val = initvol[i].vol; + mixdev_ioctl(s->codec, initvol[i].mixch, (unsigned long)&val); + } + /* mute master and PCM when in S/PDIF mode */ + if (s->spdif_volume != -1) { + val = 0x0000; + s->codec->mixer_ioctl(s->codec, SOUND_MIXER_WRITE_VOLUME, (unsigned long)&val); + s->codec->mixer_ioctl(s->codec, SOUND_MIXER_WRITE_PCM, (unsigned long)&val); + } + set_fs(fs); + /* turn on S/PDIF output driver if requested */ + outl(cssr, s->io+ES1371_REG_STATUS); + + /* register gameport */ + if (s->gameport) + gameport_register_port(s->gameport); + + /* store it in the driver field */ + pci_set_drvdata(pcidev, s); + /* put it into driver list */ + list_add_tail(&s->devs, &devs); + /* increment devindex */ + if (devindex < NR_DEVICE-1) + devindex++; + return 0; + + err_gp: + if (s->gameport) { + release_region(s->gameport->io, JOY_EXTENT); + gameport_free_port(s->gameport); + } +#ifdef ES1371_DEBUG + if (s->ps) + remove_proc_entry("es1371", NULL); +#endif + unregister_sound_midi(s->dev_midi); + err_dev4: + unregister_sound_dsp(s->dev_dac); + err_dev3: + unregister_sound_mixer(s->codec->dev_mixer); + err_dev2: + unregister_sound_dsp(s->dev_audio); + err_dev1: + printk(KERN_ERR PFX "cannot register misc device\n"); + free_irq(s->irq, s); + err_irq: + release_region(s->io, ES1371_EXTENT); + err_region: + err_codec: + ac97_release_codec(s->codec); + kfree(s); + return res; +} + +static void __devexit es1371_remove(struct pci_dev *dev) +{ + struct es1371_state *s = pci_get_drvdata(dev); + + if (!s) + return; + list_del(&s->devs); +#ifdef ES1371_DEBUG + if (s->ps) + remove_proc_entry("es1371", NULL); +#endif /* ES1371_DEBUG */ + outl(0, s->io+ES1371_REG_CONTROL); /* switch everything off */ + outl(0, s->io+ES1371_REG_SERIAL_CONTROL); /* clear serial interrupts */ + synchronize_irq(s->irq); + free_irq(s->irq, s); + if (s->gameport) { + int gpio = s->gameport->io; + gameport_unregister_port(s->gameport); + release_region(gpio, JOY_EXTENT); + } + release_region(s->io, ES1371_EXTENT); + unregister_sound_dsp(s->dev_audio); + unregister_sound_mixer(s->codec->dev_mixer); + unregister_sound_dsp(s->dev_dac); + unregister_sound_midi(s->dev_midi); + ac97_release_codec(s->codec); + kfree(s); + pci_set_drvdata(dev, NULL); +} + +static struct pci_device_id id_table[] = { + { PCI_VENDOR_ID_ENSONIQ, PCI_DEVICE_ID_ENSONIQ_ES1371, PCI_ANY_ID, PCI_ANY_ID, 0, 0 }, + { PCI_VENDOR_ID_ENSONIQ, PCI_DEVICE_ID_ENSONIQ_CT5880, PCI_ANY_ID, PCI_ANY_ID, 0, 0 }, + { PCI_VENDOR_ID_ECTIVA, PCI_DEVICE_ID_ECTIVA_EV1938, PCI_ANY_ID, PCI_ANY_ID, 0, 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, id_table); + +static struct pci_driver es1371_driver = { + .name = "es1371", + .id_table = id_table, + .probe = es1371_probe, + .remove = __devexit_p(es1371_remove), +}; + +static int __init init_es1371(void) +{ + printk(KERN_INFO PFX "version v0.32 time " __TIME__ " " __DATE__ "\n"); + return pci_module_init(&es1371_driver); +} + +static void __exit cleanup_es1371(void) +{ + printk(KERN_INFO PFX "unloading\n"); + pci_unregister_driver(&es1371_driver); +} + +module_init(init_es1371); +module_exit(cleanup_es1371); + +/* --------------------------------------------------------------------- */ + +#ifndef MODULE + +/* format is: es1371=[spdif,[nomix,[amplifier]]] */ + +static int __init es1371_setup(char *str) +{ + static unsigned __initdata nr_dev = 0; + + if (nr_dev >= NR_DEVICE) + return 0; + + (void) + ((get_option(&str, &spdif[nr_dev]) == 2) + && (get_option(&str, &nomix[nr_dev]) == 2) + && (get_option(&str, &lifier[nr_dev]))); + + nr_dev++; + return 1; +} + +__setup("es1371=", es1371_setup); + +#endif /* MODULE */ diff --git a/sound/oss/esssolo1.c b/sound/oss/esssolo1.c new file mode 100644 index 000000000000..6b3b9a99579d --- /dev/null +++ b/sound/oss/esssolo1.c @@ -0,0 +1,2497 @@ +/****************************************************************************/ + +/* + * esssolo1.c -- ESS Technology Solo1 (ES1946) audio driver. + * + * Copyright (C) 1998-2001, 2003 Thomas Sailer (t.sailer@alumni.ethz.ch) + * + * 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 so far + * + * 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 + * + * Revision history + * 10.11.1998 0.1 Initial release (without any hardware) + * 22.03.1999 0.2 cinfo.blocks should be reset after GETxPTR ioctl. + * reported by Johan Maes + * return EAGAIN instead of EBUSY when O_NONBLOCK + * read/write cannot be executed + * 07.04.1999 0.3 implemented the following ioctl's: SOUND_PCM_READ_RATE, + * SOUND_PCM_READ_CHANNELS, SOUND_PCM_READ_BITS; + * Alpha fixes reported by Peter Jones + * 15.06.1999 0.4 Fix bad allocation bug. + * Thanks to Deti Fliegl + * 28.06.1999 0.5 Add pci_set_master + * 12.08.1999 0.6 Fix MIDI UART crashing the driver + * Changed mixer semantics from OSS documented + * behaviour to OSS "code behaviour". + * Recording might actually work now. + * The real DDMA controller address register is at PCI config + * 0x60, while the register at 0x18 is used as a placeholder + * register for BIOS address allocation. This register + * is supposed to be copied into 0x60, according + * to the Solo1 datasheet. When I do that, I can access + * the DDMA registers except the mask bit, which + * is stuck at 1. When I copy the contents of 0x18 +0x10 + * to the DDMA base register, everything seems to work. + * The fun part is that the Windows Solo1 driver doesn't + * seem to do these tricks. + * Bugs remaining: plops and clicks when starting/stopping playback + * 31.08.1999 0.7 add spin_lock_init + * replaced current->state = x with set_current_state(x) + * 03.09.1999 0.8 change read semantics for MIDI to match + * OSS more closely; remove possible wakeup race + * 07.10.1999 0.9 Fix initialization; complain if sequencer writes time out + * Revised resource grabbing for the FM synthesizer + * 28.10.1999 0.10 More waitqueue races fixed + * 09.12.1999 0.11 Work around stupid Alpha port issue (virt_to_bus(kmalloc(GFP_DMA)) > 16M) + * Disabling recording on Alpha + * 12.01.2000 0.12 Prevent some ioctl's from returning bad count values on underrun/overrun; + * Tim Janik's BSE (Bedevilled Sound Engine) found this + * Integrated (aka redid 8-)) APM support patch by Zach Brown + * 07.02.2000 0.13 Use pci_alloc_consistent and pci_register_driver + * 19.02.2000 0.14 Use pci_dma_supported to determine if recording should be disabled + * 13.03.2000 0.15 Reintroduce initialization of a couple of PCI config space registers + * 21.11.2000 0.16 Initialize dma buffers in poll, otherwise poll may return a bogus mask + * 12.12.2000 0.17 More dma buffer initializations, patch from + * Tjeerd Mulder + * 31.01.2001 0.18 Register/Unregister gameport, original patch from + * Nathaniel Daw + * Fix SETTRIGGER non OSS API conformity + * 10.03.2001 provide abs function, prevent picking up a bogus kernel macro + * for abs. Bug report by Andrew Morton + * 15.05.2001 pci_enable_device moved, return values in probe cleaned + * up. Marcus Meissner + * 22.05.2001 0.19 more cleanups, changed PM to PCI 2.4 style, got rid + * of global list of devices, using pci device data. + * Marcus Meissner + * 03.01.2003 0.20 open_mode fixes from Georg Acher + */ + +/*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "dm.h" + +/* --------------------------------------------------------------------- */ + +#undef OSS_DOCUMENTED_MIXER_SEMANTICS + +/* --------------------------------------------------------------------- */ + +#ifndef PCI_VENDOR_ID_ESS +#define PCI_VENDOR_ID_ESS 0x125d +#endif +#ifndef PCI_DEVICE_ID_ESS_SOLO1 +#define PCI_DEVICE_ID_ESS_SOLO1 0x1969 +#endif + +#define SOLO1_MAGIC ((PCI_VENDOR_ID_ESS<<16)|PCI_DEVICE_ID_ESS_SOLO1) + +#define DDMABASE_OFFSET 0 /* chip bug workaround kludge */ +#define DDMABASE_EXTENT 16 + +#define IOBASE_EXTENT 16 +#define SBBASE_EXTENT 16 +#define VCBASE_EXTENT (DDMABASE_EXTENT+DDMABASE_OFFSET) +#define MPUBASE_EXTENT 4 +#define GPBASE_EXTENT 4 +#define GAMEPORT_EXTENT 4 + +#define FMSYNTH_EXTENT 4 + +/* MIDI buffer sizes */ + +#define MIDIINBUF 256 +#define MIDIOUTBUF 256 + +#define FMODE_MIDI_SHIFT 3 +#define FMODE_MIDI_READ (FMODE_READ << FMODE_MIDI_SHIFT) +#define FMODE_MIDI_WRITE (FMODE_WRITE << FMODE_MIDI_SHIFT) + +#define FMODE_DMFM 0x10 + +static struct pci_driver solo1_driver; + +/* --------------------------------------------------------------------- */ + +struct solo1_state { + /* magic */ + unsigned int magic; + + /* the corresponding pci_dev structure */ + struct pci_dev *dev; + + /* soundcore stuff */ + int dev_audio; + int dev_mixer; + int dev_midi; + int dev_dmfm; + + /* hardware resources */ + unsigned long iobase, sbbase, vcbase, ddmabase, mpubase; /* long for SPARC */ + unsigned int irq; + + /* mixer registers */ + struct { + unsigned short vol[10]; + unsigned int recsrc; + unsigned int modcnt; + unsigned short micpreamp; + } mix; + + /* wave stuff */ + unsigned fmt; + unsigned channels; + unsigned rate; + unsigned char clkdiv; + unsigned ena; + + spinlock_t lock; + struct semaphore open_sem; + mode_t open_mode; + wait_queue_head_t open_wait; + + struct dmabuf { + void *rawbuf; + dma_addr_t dmaaddr; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + unsigned hwptr, swptr; + unsigned total_bytes; + int count; + unsigned error; /* over/underrun */ + wait_queue_head_t wait; + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dmasize; + unsigned fragsamples; + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned endcleared:1; + unsigned enabled:1; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + } dma_dac, dma_adc; + + /* midi stuff */ + struct { + unsigned ird, iwr, icnt; + unsigned ord, owr, ocnt; + wait_queue_head_t iwait; + wait_queue_head_t owait; + struct timer_list timer; + unsigned char ibuf[MIDIINBUF]; + unsigned char obuf[MIDIOUTBUF]; + } midi; + + struct gameport *gameport; +}; + +/* --------------------------------------------------------------------- */ + +static inline void write_seq(struct solo1_state *s, unsigned char data) +{ + int i; + unsigned long flags; + + /* the local_irq_save stunt is to send the data within the command window */ + for (i = 0; i < 0xffff; i++) { + local_irq_save(flags); + if (!(inb(s->sbbase+0xc) & 0x80)) { + outb(data, s->sbbase+0xc); + local_irq_restore(flags); + return; + } + local_irq_restore(flags); + } + printk(KERN_ERR "esssolo1: write_seq timeout\n"); + outb(data, s->sbbase+0xc); +} + +static inline int read_seq(struct solo1_state *s, unsigned char *data) +{ + int i; + + if (!data) + return 0; + for (i = 0; i < 0xffff; i++) + if (inb(s->sbbase+0xe) & 0x80) { + *data = inb(s->sbbase+0xa); + return 1; + } + printk(KERN_ERR "esssolo1: read_seq timeout\n"); + return 0; +} + +static inline int reset_ctrl(struct solo1_state *s) +{ + int i; + + outb(3, s->sbbase+6); /* clear sequencer and FIFO */ + udelay(10); + outb(0, s->sbbase+6); + for (i = 0; i < 0xffff; i++) + if (inb(s->sbbase+0xe) & 0x80) + if (inb(s->sbbase+0xa) == 0xaa) { + write_seq(s, 0xc6); /* enter enhanced mode */ + return 1; + } + return 0; +} + +static void write_ctrl(struct solo1_state *s, unsigned char reg, unsigned char data) +{ + write_seq(s, reg); + write_seq(s, data); +} + +#if 0 /* unused */ +static unsigned char read_ctrl(struct solo1_state *s, unsigned char reg) +{ + unsigned char r; + + write_seq(s, 0xc0); + write_seq(s, reg); + read_seq(s, &r); + return r; +} +#endif /* unused */ + +static void write_mixer(struct solo1_state *s, unsigned char reg, unsigned char data) +{ + outb(reg, s->sbbase+4); + outb(data, s->sbbase+5); +} + +static unsigned char read_mixer(struct solo1_state *s, unsigned char reg) +{ + outb(reg, s->sbbase+4); + return inb(s->sbbase+5); +} + +/* --------------------------------------------------------------------- */ + +static inline unsigned ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +/* --------------------------------------------------------------------- */ + +static inline void stop_dac(struct solo1_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->ena &= ~FMODE_WRITE; + write_mixer(s, 0x78, 0x10); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_dac(struct solo1_state *s) +{ + unsigned long flags; + + 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; + write_mixer(s, 0x78, 0x12); + udelay(10); + write_mixer(s, 0x78, 0x13); + } + spin_unlock_irqrestore(&s->lock, flags); +} + +static inline void stop_adc(struct solo1_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->ena &= ~FMODE_READ; + write_ctrl(s, 0xb8, 0xe); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_adc(struct solo1_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + if (!(s->ena & FMODE_READ) && (s->dma_adc.mapped || s->dma_adc.count < (signed)(s->dma_adc.dmasize - 2*s->dma_adc.fragsize)) + && s->dma_adc.ready) { + s->ena |= FMODE_READ; + write_ctrl(s, 0xb8, 0xf); +#if 0 + printk(KERN_DEBUG "solo1: DMAbuffer: 0x%08lx\n", (long)s->dma_adc.rawbuf); + printk(KERN_DEBUG "solo1: DMA: mask: 0x%02x cnt: 0x%04x addr: 0x%08x stat: 0x%02x\n", + inb(s->ddmabase+0xf), inw(s->ddmabase+4), inl(s->ddmabase), inb(s->ddmabase+8)); +#endif + outb(0, s->ddmabase+0xd); /* master reset */ + outb(1, s->ddmabase+0xf); /* mask */ + outb(0x54/*0x14*/, s->ddmabase+0xb); /* DMA_MODE_READ | DMA_MODE_AUTOINIT */ + outl(virt_to_bus(s->dma_adc.rawbuf), s->ddmabase); + outw(s->dma_adc.dmasize-1, s->ddmabase+4); + outb(0, s->ddmabase+0xf); + } + spin_unlock_irqrestore(&s->lock, flags); +#if 0 + printk(KERN_DEBUG "solo1: start DMA: reg B8: 0x%02x SBstat: 0x%02x\n" + KERN_DEBUG "solo1: DMA: stat: 0x%02x cnt: 0x%04x mask: 0x%02x\n", + read_ctrl(s, 0xb8), inb(s->sbbase+0xc), + inb(s->ddmabase+8), inw(s->ddmabase+4), inb(s->ddmabase+0xf)); + printk(KERN_DEBUG "solo1: A1: 0x%02x A2: 0x%02x A4: 0x%02x A5: 0x%02x A8: 0x%02x\n" + KERN_DEBUG "solo1: B1: 0x%02x B2: 0x%02x B4: 0x%02x B7: 0x%02x B8: 0x%02x B9: 0x%02x\n", + read_ctrl(s, 0xa1), read_ctrl(s, 0xa2), read_ctrl(s, 0xa4), read_ctrl(s, 0xa5), read_ctrl(s, 0xa8), + read_ctrl(s, 0xb1), read_ctrl(s, 0xb2), read_ctrl(s, 0xb4), read_ctrl(s, 0xb7), read_ctrl(s, 0xb8), + read_ctrl(s, 0xb9)); +#endif +} + +/* --------------------------------------------------------------------- */ + +#define DMABUF_DEFAULTORDER (15-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +static inline void dealloc_dmabuf(struct solo1_state *s, struct dmabuf *db) +{ + struct page *page, *pend; + + if (db->rawbuf) { + /* undo marking the pages as reserved */ + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + ClearPageReserved(page); + pci_free_consistent(s->dev, PAGE_SIZE << db->buforder, db->rawbuf, db->dmaaddr); + } + db->rawbuf = NULL; + db->mapped = db->ready = 0; +} + +static int prog_dmabuf(struct solo1_state *s, struct dmabuf *db) +{ + int order; + unsigned bytespersec; + unsigned bufs, sample_shift = 0; + struct page *page, *pend; + + db->hwptr = db->swptr = db->total_bytes = db->count = db->error = db->endcleared = 0; + if (!db->rawbuf) { + db->ready = db->mapped = 0; + for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--) + if ((db->rawbuf = pci_alloc_consistent(s->dev, PAGE_SIZE << order, &db->dmaaddr))) + break; + if (!db->rawbuf) + return -ENOMEM; + db->buforder = order; + /* now mark the pages as reserved; otherwise remap_pfn_range doesn't do what we want */ + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + SetPageReserved(page); + } + if (s->fmt & (AFMT_S16_LE | AFMT_U16_LE)) + sample_shift++; + if (s->channels > 1) + sample_shift++; + bytespersec = s->rate << sample_shift; + bufs = PAGE_SIZE << db->buforder; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < bytespersec) + db->fragshift = ld2(bytespersec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(bytespersec/100/(db->subdivision ? db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + db->numfrag = bufs >> db->fragshift; + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->numfrag = bufs >> db->fragshift; + } + db->fragsize = 1 << db->fragshift; + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; + db->fragsamples = db->fragsize >> sample_shift; + db->dmasize = db->numfrag << db->fragshift; + db->enabled = 1; + return 0; +} + +static inline int prog_dmabuf_adc(struct solo1_state *s) +{ + unsigned long va; + int c; + + stop_adc(s); + /* check if PCI implementation supports 24bit busmaster DMA */ + if (s->dev->dma_mask > 0xffffff) + return -EIO; + if ((c = prog_dmabuf(s, &s->dma_adc))) + return c; + va = s->dma_adc.dmaaddr; + if ((va & ~((1<<24)-1))) + panic("solo1: buffer above 16M boundary"); + outb(0, s->ddmabase+0xd); /* clear */ + outb(1, s->ddmabase+0xf); /* mask */ + /*outb(0, s->ddmabase+8);*/ /* enable (enable is active low!) */ + outb(0x54, s->ddmabase+0xb); /* DMA_MODE_READ | DMA_MODE_AUTOINIT */ + outl(va, s->ddmabase); + outw(s->dma_adc.dmasize-1, s->ddmabase+4); + c = - s->dma_adc.fragsamples; + write_ctrl(s, 0xa4, c); + write_ctrl(s, 0xa5, c >> 8); + outb(0, s->ddmabase+0xf); + s->dma_adc.ready = 1; + return 0; +} + +static inline int prog_dmabuf_dac(struct solo1_state *s) +{ + unsigned long va; + int c; + + stop_dac(s); + if ((c = prog_dmabuf(s, &s->dma_dac))) + return c; + memset(s->dma_dac.rawbuf, (s->fmt & (AFMT_U8 | AFMT_U16_LE)) ? 0 : 0x80, s->dma_dac.dmasize); /* almost correct for U16 */ + va = s->dma_dac.dmaaddr; + if ((va ^ (va + s->dma_dac.dmasize - 1)) & ~((1<<20)-1)) + panic("solo1: buffer crosses 1M boundary"); + outl(va, s->iobase); + /* warning: s->dma_dac.dmasize & 0xffff must not be zero! i.e. this limits us to a 32k buffer */ + outw(s->dma_dac.dmasize, s->iobase+4); + c = - s->dma_dac.fragsamples; + write_mixer(s, 0x74, c); + write_mixer(s, 0x76, c >> 8); + outb(0xa, s->iobase+6); + s->dma_dac.ready = 1; + return 0; +} + +static inline 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; + } + memset(((char *)buf) + bptr, c, len); +} + +/* call with spinlock held! */ + +static void solo1_update_ptr(struct solo1_state *s) +{ + int diff; + unsigned hwptr; + + /* update ADC pointer */ + if (s->ena & FMODE_READ) { + hwptr = (s->dma_adc.dmasize - 1 - inw(s->ddmabase+4)) % s->dma_adc.dmasize; + diff = (s->dma_adc.dmasize + hwptr - s->dma_adc.hwptr) % s->dma_adc.dmasize; + s->dma_adc.hwptr = hwptr; + s->dma_adc.total_bytes += diff; + s->dma_adc.count += diff; +#if 0 + printk(KERN_DEBUG "solo1: rd: hwptr %u swptr %u dmasize %u count %u\n", + s->dma_adc.hwptr, s->dma_adc.swptr, s->dma_adc.dmasize, s->dma_adc.count); +#endif + if (s->dma_adc.mapped) { + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + wake_up(&s->dma_adc.wait); + } else { + if (s->dma_adc.count > (signed)(s->dma_adc.dmasize - ((3 * s->dma_adc.fragsize) >> 1))) { + s->ena &= ~FMODE_READ; + write_ctrl(s, 0xb8, 0xe); + s->dma_adc.error++; + } + if (s->dma_adc.count > 0) + wake_up(&s->dma_adc.wait); + } + } + /* update DAC pointer */ + if (s->ena & FMODE_WRITE) { + hwptr = (s->dma_dac.dmasize - inw(s->iobase+4)) % s->dma_dac.dmasize; + diff = (s->dma_dac.dmasize + hwptr - s->dma_dac.hwptr) % s->dma_dac.dmasize; + s->dma_dac.hwptr = hwptr; + s->dma_dac.total_bytes += diff; +#if 0 + printk(KERN_DEBUG "solo1: wr: hwptr %u swptr %u dmasize %u count %u\n", + s->dma_dac.hwptr, s->dma_dac.swptr, s->dma_dac.dmasize, s->dma_dac.count); +#endif + if (s->dma_dac.mapped) { + s->dma_dac.count += diff; + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) + wake_up(&s->dma_dac.wait); + } else { + s->dma_dac.count -= diff; + if (s->dma_dac.count <= 0) { + s->ena &= ~FMODE_WRITE; + write_mixer(s, 0x78, 0x12); + s->dma_dac.error++; + } else if (s->dma_dac.count <= (signed)s->dma_dac.fragsize && !s->dma_dac.endcleared) { + clear_advance(s->dma_dac.rawbuf, s->dma_dac.dmasize, s->dma_dac.swptr, + s->dma_dac.fragsize, (s->fmt & (AFMT_U8 | AFMT_U16_LE)) ? 0 : 0x80); + s->dma_dac.endcleared = 1; + } + if (s->dma_dac.count < (signed)s->dma_dac.dmasize) + wake_up(&s->dma_dac.wait); + } + } +} + +/* --------------------------------------------------------------------- */ + +static void prog_codec(struct solo1_state *s) +{ + unsigned long flags; + int fdiv, filter; + unsigned char c; + + reset_ctrl(s); + write_seq(s, 0xd3); + /* program sampling rates */ + filter = s->rate * 9 / 20; /* Set filter roll-off to 90% of rate/2 */ + fdiv = 256 - 7160000 / (filter * 82); + spin_lock_irqsave(&s->lock, flags); + write_ctrl(s, 0xa1, s->clkdiv); + write_ctrl(s, 0xa2, fdiv); + write_mixer(s, 0x70, s->clkdiv); + write_mixer(s, 0x72, fdiv); + /* program ADC parameters */ + write_ctrl(s, 0xb8, 0xe); + write_ctrl(s, 0xb9, /*0x1*/0); + write_ctrl(s, 0xa8, (s->channels > 1) ? 0x11 : 0x12); + c = 0xd0; + if (s->fmt & (AFMT_S16_LE | AFMT_U16_LE)) + c |= 0x04; + if (s->fmt & (AFMT_S16_LE | AFMT_S8)) + c |= 0x20; + if (s->channels > 1) + c ^= 0x48; + write_ctrl(s, 0xb7, (c & 0x70) | 1); + write_ctrl(s, 0xb7, c); + write_ctrl(s, 0xb1, 0x50); + write_ctrl(s, 0xb2, 0x50); + /* program DAC parameters */ + c = 0x40; + if (s->fmt & (AFMT_S16_LE | AFMT_U16_LE)) + c |= 1; + if (s->fmt & (AFMT_S16_LE | AFMT_S8)) + c |= 4; + if (s->channels > 1) + c |= 2; + write_mixer(s, 0x7a, c); + write_mixer(s, 0x78, 0x10); + s->ena = 0; + spin_unlock_irqrestore(&s->lock, flags); +} + +/* --------------------------------------------------------------------- */ + +static const char invalid_magic[] = KERN_CRIT "solo1: invalid magic value\n"; + +#define VALIDATE_STATE(s) \ +({ \ + if (!(s) || (s)->magic != SOLO1_MAGIC) { \ + printk(invalid_magic); \ + return -ENXIO; \ + } \ +}) + +/* --------------------------------------------------------------------- */ + +static int mixer_ioctl(struct solo1_state *s, unsigned int cmd, unsigned long arg) +{ + static const unsigned int mixer_src[8] = { + SOUND_MASK_MIC, SOUND_MASK_MIC, SOUND_MASK_CD, SOUND_MASK_VOLUME, + SOUND_MASK_MIC, 0, SOUND_MASK_LINE, 0 + }; + static const unsigned char mixtable1[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_PCM] = 1, /* voice */ + [SOUND_MIXER_SYNTH] = 2, /* FM */ + [SOUND_MIXER_CD] = 3, /* CD */ + [SOUND_MIXER_LINE] = 4, /* Line */ + [SOUND_MIXER_LINE1] = 5, /* AUX */ + [SOUND_MIXER_MIC] = 6, /* Mic */ + [SOUND_MIXER_LINE2] = 7, /* Mono in */ + [SOUND_MIXER_SPEAKER] = 8, /* Speaker */ + [SOUND_MIXER_RECLEV] = 9, /* Recording level */ + [SOUND_MIXER_VOLUME] = 10 /* Master Volume */ + }; + static const unsigned char mixreg[] = { + 0x7c, /* voice */ + 0x36, /* FM */ + 0x38, /* CD */ + 0x3e, /* Line */ + 0x3a, /* AUX */ + 0x1a, /* Mic */ + 0x6d /* Mono in */ + }; + unsigned char l, r, rl, rr, vidx; + int i, val; + int __user *p = (int __user *)arg; + + VALIDATE_STATE(s); + + if (cmd == SOUND_MIXER_PRIVATE1) { + /* enable/disable/query mixer preamp */ + if (get_user(val, p)) + return -EFAULT; + if (val != -1) { + val = val ? 0xff : 0xf7; + write_mixer(s, 0x7d, (read_mixer(s, 0x7d) | 0x08) & val); + } + val = (read_mixer(s, 0x7d) & 0x08) ? 1 : 0; + return put_user(val, p); + } + if (cmd == SOUND_MIXER_PRIVATE2) { + /* enable/disable/query spatializer */ + if (get_user(val, p)) + return -EFAULT; + if (val != -1) { + val &= 0x3f; + write_mixer(s, 0x52, val); + write_mixer(s, 0x50, val ? 0x08 : 0); + } + return put_user(read_mixer(s, 0x52), p); + } + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + strncpy(info.id, "Solo1", sizeof(info.id)); + strncpy(info.name, "ESS Solo1", sizeof(info.name)); + info.modify_counter = s->mix.modcnt; + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == SOUND_OLD_MIXER_INFO) { + _old_mixer_info info; + strncpy(info.id, "Solo1", sizeof(info.id)); + strncpy(info.name, "ESS Solo1", sizeof(info.name)); + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, p); + if (_IOC_TYPE(cmd) != 'M' || _SIOC_SIZE(cmd) != sizeof(int)) + return -EINVAL; + if (_SIOC_DIR(cmd) == _SIOC_READ) { + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + return put_user(mixer_src[read_mixer(s, 0x1c) & 7], p); + + case SOUND_MIXER_DEVMASK: /* Arg contains a bit for each supported device */ + return put_user(SOUND_MASK_PCM | SOUND_MASK_SYNTH | SOUND_MASK_CD | + SOUND_MASK_LINE | SOUND_MASK_LINE1 | SOUND_MASK_MIC | + SOUND_MASK_VOLUME | SOUND_MASK_LINE2 | SOUND_MASK_RECLEV | + SOUND_MASK_SPEAKER, p); + + case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */ + return put_user(SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_VOLUME, p); + + case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */ + return put_user(SOUND_MASK_PCM | SOUND_MASK_SYNTH | SOUND_MASK_CD | + SOUND_MASK_LINE | SOUND_MASK_LINE1 | SOUND_MASK_MIC | + SOUND_MASK_VOLUME | SOUND_MASK_LINE2 | SOUND_MASK_RECLEV, p); + + case SOUND_MIXER_CAPS: + return put_user(SOUND_CAP_EXCL_INPUT, p); + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES || !(vidx = mixtable1[i])) + return -EINVAL; + return put_user(s->mix.vol[vidx-1], p); + } + } + if (_SIOC_DIR(cmd) != (_SIOC_READ|_SIOC_WRITE)) + return -EINVAL; + s->mix.modcnt++; + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ +#if 0 + { + static const unsigned char regs[] = { + 0x1c, 0x1a, 0x36, 0x38, 0x3a, 0x3c, 0x3e, 0x60, 0x62, 0x6d, 0x7c + }; + int i; + + for (i = 0; i < sizeof(regs); i++) + printk(KERN_DEBUG "solo1: mixer reg 0x%02x: 0x%02x\n", + regs[i], read_mixer(s, regs[i])); + printk(KERN_DEBUG "solo1: ctrl reg 0x%02x: 0x%02x\n", + 0xb4, read_ctrl(s, 0xb4)); + } +#endif + if (get_user(val, p)) + return -EFAULT; + i = hweight32(val); + if (i == 0) + return 0; + else if (i > 1) + val &= ~mixer_src[read_mixer(s, 0x1c) & 7]; + for (i = 0; i < 8; i++) { + if (mixer_src[i] & val) + break; + } + if (i > 7) + return 0; + write_mixer(s, 0x1c, i); + return 0; + + case SOUND_MIXER_VOLUME: + if (get_user(val, p)) + return -EFAULT; + l = val & 0xff; + if (l > 100) + l = 100; + r = (val >> 8) & 0xff; + if (r > 100) + r = 100; + if (l < 6) { + rl = 0x40; + l = 0; + } else { + rl = (l * 2 - 11) / 3; + l = (rl * 3 + 11) / 2; + } + if (r < 6) { + rr = 0x40; + r = 0; + } else { + rr = (r * 2 - 11) / 3; + r = (rr * 3 + 11) / 2; + } + write_mixer(s, 0x60, rl); + write_mixer(s, 0x62, rr); +#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS + s->mix.vol[9] = ((unsigned int)r << 8) | l; +#else + s->mix.vol[9] = val; +#endif + return put_user(s->mix.vol[9], p); + + case SOUND_MIXER_SPEAKER: + if (get_user(val, p)) + return -EFAULT; + l = val & 0xff; + if (l > 100) + l = 100; + else if (l < 2) + l = 2; + rl = (l - 2) / 14; + l = rl * 14 + 2; + write_mixer(s, 0x3c, rl); +#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS + s->mix.vol[7] = l * 0x101; +#else + s->mix.vol[7] = val; +#endif + return put_user(s->mix.vol[7], p); + + case SOUND_MIXER_RECLEV: + if (get_user(val, p)) + return -EFAULT; + l = (val << 1) & 0x1fe; + if (l > 200) + l = 200; + else if (l < 5) + l = 5; + r = (val >> 7) & 0x1fe; + if (r > 200) + r = 200; + else if (r < 5) + r = 5; + rl = (l - 5) / 13; + rr = (r - 5) / 13; + r = (rl * 13 + 5) / 2; + l = (rr * 13 + 5) / 2; + write_ctrl(s, 0xb4, (rl << 4) | rr); +#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], p); + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES || !(vidx = mixtable1[i])) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + l = (val << 1) & 0x1fe; + if (l > 200) + l = 200; + else if (l < 5) + l = 5; + r = (val >> 7) & 0x1fe; + if (r > 200) + r = 200; + else if (r < 5) + r = 5; + rl = (l - 5) / 13; + rr = (r - 5) / 13; + r = (rl * 13 + 5) / 2; + l = (rr * 13 + 5) / 2; + write_mixer(s, mixreg[vidx-1], (rl << 4) | rr); +#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], p); + } +} + +/* --------------------------------------------------------------------- */ + +static int solo1_open_mixdev(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct solo1_state *s = NULL; + struct pci_dev *pci_dev = NULL; + + while ((pci_dev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pci_dev)) != NULL) { + struct pci_driver *drvr; + drvr = pci_dev_driver (pci_dev); + if (drvr != &solo1_driver) + continue; + s = (struct solo1_state*)pci_get_drvdata(pci_dev); + if (!s) + continue; + if (s->dev_mixer == minor) + break; + } + if (!s) + return -ENODEV; + VALIDATE_STATE(s); + file->private_data = s; + return nonseekable_open(inode, file); +} + +static int solo1_release_mixdev(struct inode *inode, struct file *file) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + + VALIDATE_STATE(s); + return 0; +} + +static int solo1_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + return mixer_ioctl((struct solo1_state *)file->private_data, cmd, arg); +} + +static /*const*/ struct file_operations solo1_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = solo1_ioctl_mixdev, + .open = solo1_open_mixdev, + .release = solo1_release_mixdev, +}; + +/* --------------------------------------------------------------------- */ + +static int drain_dac(struct solo1_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int count; + unsigned tmo; + + if (s->dma_dac.mapped) + return 0; + add_wait_queue(&s->dma_dac.wait, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_dac.wait, &wait); + set_current_state(TASK_RUNNING); + return -EBUSY; + } + tmo = 3 * HZ * (count + s->dma_dac.fragsize) / 2 / s->rate; + if (s->fmt & (AFMT_S16_LE | AFMT_U16_LE)) + tmo >>= 1; + if (s->channels > 1) + tmo >>= 1; + if (!schedule_timeout(tmo + 1)) + printk(KERN_DEBUG "solo1: dma timed out??\n"); + } + remove_wait_queue(&s->dma_dac.wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static ssize_t solo1_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + 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; + add_wait_queue(&s->dma_adc.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + swptr = s->dma_adc.swptr; + cnt = s->dma_adc.dmasize-swptr; + if (s->dma_adc.count < cnt) + cnt = s->dma_adc.count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; +#ifdef DEBUGREC + printk(KERN_DEBUG "solo1_read: reg B8: 0x%02x DMAstat: 0x%02x DMAcnt: 0x%04x SBstat: 0x%02x cnt: %u\n", + read_ctrl(s, 0xb8), inb(s->ddmabase+8), inw(s->ddmabase+4), inb(s->sbbase+0xc), cnt); +#endif + if (cnt <= 0) { + if (s->dma_adc.enabled) + start_adc(s); +#ifdef DEBUGREC + printk(KERN_DEBUG "solo1_read: regs: A1: 0x%02x A2: 0x%02x A4: 0x%02x A5: 0x%02x A8: 0x%02x\n" + KERN_DEBUG "solo1_read: regs: B1: 0x%02x B2: 0x%02x B7: 0x%02x B8: 0x%02x B9: 0x%02x\n" + KERN_DEBUG "solo1_read: DMA: addr: 0x%08x cnt: 0x%04x stat: 0x%02x mask: 0x%02x\n" + KERN_DEBUG "solo1_read: SBstat: 0x%02x cnt: %u\n", + read_ctrl(s, 0xa1), read_ctrl(s, 0xa2), read_ctrl(s, 0xa4), read_ctrl(s, 0xa5), read_ctrl(s, 0xa8), + read_ctrl(s, 0xb1), read_ctrl(s, 0xb2), read_ctrl(s, 0xb7), read_ctrl(s, 0xb8), read_ctrl(s, 0xb9), + inl(s->ddmabase), inw(s->ddmabase+4), inb(s->ddmabase+8), inb(s->ddmabase+15), inb(s->sbbase+0xc), cnt); +#endif + if (inb(s->ddmabase+15) & 1) + printk(KERN_ERR "solo1: cannot start recording, DDMA mask bit stuck at 1\n"); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); +#ifdef DEBUGREC + printk(KERN_DEBUG "solo1_read: regs: A1: 0x%02x A2: 0x%02x A4: 0x%02x A5: 0x%02x A8: 0x%02x\n" + KERN_DEBUG "solo1_read: regs: B1: 0x%02x B2: 0x%02x B7: 0x%02x B8: 0x%02x B9: 0x%02x\n" + KERN_DEBUG "solo1_read: DMA: addr: 0x%08x cnt: 0x%04x stat: 0x%02x mask: 0x%02x\n" + KERN_DEBUG "solo1_read: SBstat: 0x%02x cnt: %u\n", + read_ctrl(s, 0xa1), read_ctrl(s, 0xa2), read_ctrl(s, 0xa4), read_ctrl(s, 0xa5), read_ctrl(s, 0xa8), + read_ctrl(s, 0xb1), read_ctrl(s, 0xb2), read_ctrl(s, 0xb7), read_ctrl(s, 0xb8), read_ctrl(s, 0xb9), + inl(s->ddmabase), inw(s->ddmabase+4), inb(s->ddmabase+8), inb(s->ddmabase+15), inb(s->sbbase+0xc), cnt); +#endif + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_to_user(buffer, s->dma_adc.rawbuf + swptr, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + swptr = (swptr + cnt) % s->dma_adc.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_adc.swptr = swptr; + s->dma_adc.count -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->dma_adc.enabled) + start_adc(s); +#ifdef DEBUGREC + printk(KERN_DEBUG "solo1_read: reg B8: 0x%02x DMAstat: 0x%02x DMAcnt: 0x%04x SBstat: 0x%02x\n", + read_ctrl(s, 0xb8), inb(s->ddmabase+8), inw(s->ddmabase+4), inb(s->sbbase+0xc)); +#endif + } + remove_wait_queue(&s->dma_adc.wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +static ssize_t solo1_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + 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; +#if 0 + printk(KERN_DEBUG "solo1_write: reg 70: 0x%02x 71: 0x%02x 72: 0x%02x 74: 0x%02x 76: 0x%02x 78: 0x%02x 7A: 0x%02x\n" + KERN_DEBUG "solo1_write: DMA: addr: 0x%08x cnt: 0x%04x stat: 0x%02x SBstat: 0x%02x\n", + read_mixer(s, 0x70), read_mixer(s, 0x71), read_mixer(s, 0x72), read_mixer(s, 0x74), read_mixer(s, 0x76), + read_mixer(s, 0x78), read_mixer(s, 0x7a), inl(s->iobase), inw(s->iobase+4), inb(s->iobase+6), inb(s->sbbase+0xc)); + printk(KERN_DEBUG "solo1_write: reg 78: 0x%02x reg 7A: 0x%02x DMAcnt: 0x%04x DMAstat: 0x%02x SBstat: 0x%02x\n", + read_mixer(s, 0x78), read_mixer(s, 0x7a), inw(s->iobase+4), inb(s->iobase+6), inb(s->sbbase+0xc)); +#endif + ret = 0; + add_wait_queue(&s->dma_dac.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + if (s->dma_dac.count < 0) { + s->dma_dac.count = 0; + s->dma_dac.swptr = s->dma_dac.hwptr; + } + swptr = s->dma_dac.swptr; + cnt = s->dma_dac.dmasize-swptr; + if (s->dma_dac.count + cnt > s->dma_dac.dmasize) + cnt = s->dma_dac.dmasize - s->dma_dac.count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (s->dma_dac.enabled) + start_dac(s); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_from_user(s->dma_dac.rawbuf + swptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + swptr = (swptr + cnt) % s->dma_dac.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_dac.swptr = swptr; + s->dma_dac.count += cnt; + s->dma_dac.endcleared = 0; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->dma_dac.enabled) + start_dac(s); + } + remove_wait_queue(&s->dma_dac.wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int solo1_poll(struct file *file, struct poll_table_struct *wait) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) { + 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) { + if (!s->dma_adc.ready && prog_dmabuf_adc(s)) + return 0; + poll_wait(file, &s->dma_adc.wait, wait); + } + spin_lock_irqsave(&s->lock, flags); + solo1_update_ptr(s); + 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; + } + } + if (file->f_mode & FMODE_WRITE) { + if (s->dma_dac.mapped) { + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)s->dma_dac.dmasize > s->dma_dac.count) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + + +static int solo1_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + struct dmabuf *db; + int ret = -EINVAL; + unsigned long size; + + VALIDATE_STATE(s); + lock_kernel(); + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf_dac(s)) != 0) + goto out; + db = &s->dma_dac; + } else if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf_adc(s)) != 0) + goto out; + db = &s->dma_adc; + } else + goto out; + ret = -EINVAL; + if (vma->vm_pgoff != 0) + goto out; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << db->buforder)) + goto out; + ret = -EAGAIN; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(db->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + goto out; + db->mapped = 1; + ret = 0; +out: + unlock_kernel(); + return ret; +} + +static int solo1_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int val, mapped, ret, count; + int div1, div2; + unsigned rate1, rate2; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + 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: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_SYNC: + 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, p); + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + synchronize_irq(s->irq); + s->dma_dac.swptr = s->dma_dac.hwptr = s->dma_dac.count = s->dma_dac.total_bytes = 0; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(s->irq); + s->dma_adc.swptr = s->dma_adc.hwptr = s->dma_adc.count = s->dma_adc.total_bytes = 0; + } + prog_codec(s); + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { + stop_adc(s); + stop_dac(s); + s->dma_adc.ready = s->dma_dac.ready = 0; + /* program sampling rates */ + if (val > 48000) + val = 48000; + if (val < 6300) + val = 6300; + div1 = (768000 + val / 2) / val; + rate1 = (768000 + div1 / 2) / div1; + div1 = -div1; + div2 = (793800 + val / 2) / val; + rate2 = (793800 + div2 / 2) / div2; + div2 = (-div2) & 0x7f; + if (abs(val - rate2) < abs(val - rate1)) { + rate1 = rate2; + div1 = div2; + } + s->rate = rate1; + s->clkdiv = div1; + prog_codec(s); + } + return put_user(s->rate, p); + + case SNDCTL_DSP_STEREO: + if (get_user(val, p)) + return -EFAULT; + stop_adc(s); + stop_dac(s); + s->dma_adc.ready = s->dma_dac.ready = 0; + /* program channels */ + s->channels = val ? 2 : 1; + prog_codec(s); + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + if (val != 0) { + stop_adc(s); + stop_dac(s); + s->dma_adc.ready = s->dma_dac.ready = 0; + /* program channels */ + s->channels = (val >= 2) ? 2 : 1; + prog_codec(s); + } + return put_user(s->channels, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_LE|AFMT_U16_LE|AFMT_S8|AFMT_U8, p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, p)) + return -EFAULT; + if (val != AFMT_QUERY) { + stop_adc(s); + stop_dac(s); + s->dma_adc.ready = s->dma_dac.ready = 0; + /* program format */ + if (val != AFMT_S16_LE && val != AFMT_U16_LE && + val != AFMT_S8 && val != AFMT_U8) + val = AFMT_U8; + s->fmt = val; + prog_codec(s); + } + return put_user(s->fmt, p); + + case SNDCTL_DSP_POST: + 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, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + 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; + s->dma_dac.enabled = 1; + start_adc(s); + if (inb(s->ddmabase+15) & 1) + printk(KERN_ERR "solo1: cannot start recording, DDMA mask bit stuck at 1\n"); + } else { + s->dma_dac.enabled = 0; + 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; + s->dma_dac.enabled = 1; + start_dac(s); + } else { + s->dma_dac.enabled = 0; + 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)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + solo1_update_ptr(s); + abinfo.fragsize = s->dma_dac.fragsize; + count = s->dma_dac.count; + if (count < 0) + count = 0; + abinfo.bytes = s->dma_dac.dmasize - count; + abinfo.fragstotal = s->dma_dac.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user(argp, &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)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + solo1_update_ptr(s); + 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(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac.ready && (val = prog_dmabuf_dac(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + solo1_update_ptr(s); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + return put_user(count, p); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!s->dma_adc.ready && (val = prog_dmabuf_adc(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + solo1_update_ptr(s); + cinfo.bytes = s->dma_adc.total_bytes; + cinfo.blocks = s->dma_adc.count >> s->dma_adc.fragshift; + 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); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac.ready && (val = prog_dmabuf_dac(s)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + solo1_update_ptr(s); + cinfo.bytes = s->dma_dac.total_bytes; + count = s->dma_dac.count; + if (count < 0) + count = 0; + cinfo.blocks = 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); +#if 0 + printk(KERN_DEBUG "esssolo1: GETOPTR: bytes %u blocks %u ptr %u, buforder %u numfrag %u fragshift %u\n" + KERN_DEBUG "esssolo1: swptr %u count %u fragsize %u dmasize %u fragsamples %u\n", + cinfo.bytes, cinfo.blocks, cinfo.ptr, s->dma_dac.buforder, s->dma_dac.numfrag, s->dma_dac.fragshift, + s->dma_dac.swptr, s->dma_dac.count, s->dma_dac.fragsize, s->dma_dac.dmasize, s->dma_dac.fragsamples); +#endif + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 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, p); + } + if ((val = prog_dmabuf_adc(s))) + return val; + return put_user(s->dma_adc.fragsize, p); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = val & 0xffff; + s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_adc.ossfragshift < 4) + s->dma_adc.ossfragshift = 4; + if (s->dma_adc.ossfragshift > 15) + s->dma_adc.ossfragshift = 15; + if (s->dma_adc.ossmaxfrags < 4) + s->dma_adc.ossmaxfrags = 4; + } + if (file->f_mode & FMODE_WRITE) { + s->dma_dac.ossfragshift = val & 0xffff; + s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac.ossfragshift < 4) + s->dma_dac.ossfragshift = 4; + if (s->dma_dac.ossfragshift > 15) + s->dma_dac.ossfragshift = 15; + if (s->dma_dac.ossmaxfrags < 4) + s->dma_dac.ossmaxfrags = 4; + } + return 0; + + 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, p)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + if (file->f_mode & FMODE_READ) + s->dma_adc.subdivision = val; + if (file->f_mode & FMODE_WRITE) + s->dma_dac.subdivision = val; + return 0; + + case SOUND_PCM_READ_RATE: + return put_user(s->rate, p); + + case SOUND_PCM_READ_CHANNELS: + return put_user(s->channels, p); + + case SOUND_PCM_READ_BITS: + return put_user((s->fmt & (AFMT_S8|AFMT_U8)) ? 8 : 16, p); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + + } + return mixer_ioctl(s, cmd, arg); +} + +static int solo1_release(struct inode *inode, struct file *file) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + + VALIDATE_STATE(s); + lock_kernel(); + if (file->f_mode & FMODE_WRITE) + drain_dac(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem); + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + outb(0, s->iobase+6); /* disable DMA */ + dealloc_dmabuf(s, &s->dma_dac); + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + outb(1, s->ddmabase+0xf); /* mask DMA channel */ + outb(0, s->ddmabase+0xd); /* DMA master clear */ + dealloc_dmabuf(s, &s->dma_adc); + } + s->open_mode &= ~(FMODE_READ | FMODE_WRITE); + wake_up(&s->open_wait); + up(&s->open_sem); + unlock_kernel(); + return 0; +} + +static int solo1_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + struct solo1_state *s = NULL; + struct pci_dev *pci_dev = NULL; + + while ((pci_dev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pci_dev)) != NULL) { + struct pci_driver *drvr; + + drvr = pci_dev_driver(pci_dev); + if (drvr != &solo1_driver) + continue; + s = (struct solo1_state*)pci_get_drvdata(pci_dev); + if (!s) + continue; + if (!((s->dev_audio ^ minor) & ~0xf)) + break; + } + if (!s) + return -ENODEV; + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & (FMODE_READ | FMODE_WRITE)) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + s->fmt = AFMT_U8; + s->channels = 1; + s->rate = 8000; + s->clkdiv = 96 | 0x80; + s->ena = 0; + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = s->dma_adc.subdivision = 0; + s->dma_adc.enabled = 1; + s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = s->dma_dac.subdivision = 0; + s->dma_dac.enabled = 1; + s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + up(&s->open_sem); + prog_codec(s); + return nonseekable_open(inode, file); +} + +static /*const*/ struct file_operations solo1_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = solo1_read, + .write = solo1_write, + .poll = solo1_poll, + .ioctl = solo1_ioctl, + .mmap = solo1_mmap, + .open = solo1_open, + .release = solo1_release, +}; + +/* --------------------------------------------------------------------- */ + +/* hold spinlock for the following! */ +static void solo1_handle_midi(struct solo1_state *s) +{ + unsigned char ch; + int wake; + + if (!(s->mpubase)) + return; + wake = 0; + while (!(inb(s->mpubase+1) & 0x80)) { + ch = inb(s->mpubase); + if (s->midi.icnt < MIDIINBUF) { + s->midi.ibuf[s->midi.iwr] = ch; + s->midi.iwr = (s->midi.iwr + 1) % MIDIINBUF; + s->midi.icnt++; + } + wake = 1; + } + if (wake) + wake_up(&s->midi.iwait); + wake = 0; + while (!(inb(s->mpubase+1) & 0x40) && s->midi.ocnt > 0) { + outb(s->midi.obuf[s->midi.ord], s->mpubase); + s->midi.ord = (s->midi.ord + 1) % MIDIOUTBUF; + s->midi.ocnt--; + if (s->midi.ocnt < MIDIOUTBUF-16) + wake = 1; + } + if (wake) + wake_up(&s->midi.owait); +} + +static irqreturn_t solo1_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct solo1_state *s = (struct solo1_state *)dev_id; + unsigned int intsrc; + + /* fastpath out, to ease interrupt sharing */ + intsrc = inb(s->iobase+7); /* get interrupt source(s) */ + if (!intsrc) + return IRQ_NONE; + (void)inb(s->sbbase+0xe); /* clear interrupt */ + spin_lock(&s->lock); + /* clear audio interrupts first */ + if (intsrc & 0x20) + write_mixer(s, 0x7a, read_mixer(s, 0x7a) & 0x7f); + solo1_update_ptr(s); + solo1_handle_midi(s); + spin_unlock(&s->lock); + return IRQ_HANDLED; +} + +static void solo1_midi_timer(unsigned long data) +{ + struct solo1_state *s = (struct solo1_state *)data; + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + solo1_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + s->midi.timer.expires = jiffies+1; + add_timer(&s->midi.timer); +} + +/* --------------------------------------------------------------------- */ + +static ssize_t solo1_midi_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + if (count == 0) + return 0; + ret = 0; + add_wait_queue(&s->midi.iwait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.ird; + cnt = MIDIINBUF - ptr; + if (s->midi.icnt < cnt) + cnt = s->midi.icnt; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_to_user(buffer, s->midi.ibuf + ptr, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + ptr = (ptr + cnt) % MIDIINBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.ird = ptr; + s->midi.icnt -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + break; + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&s->midi.iwait, &wait); + return ret; +} + +static ssize_t solo1_midi_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + if (count == 0) + return 0; + ret = 0; + add_wait_queue(&s->midi.owait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.owr; + cnt = MIDIOUTBUF - ptr; + if (s->midi.ocnt + cnt > MIDIOUTBUF) + cnt = MIDIOUTBUF - s->midi.ocnt; + if (cnt <= 0) { + __set_current_state(TASK_INTERRUPTIBLE); + solo1_handle_midi(s); + } + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_from_user(s->midi.obuf + ptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + ptr = (ptr + cnt) % MIDIOUTBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.owr = ptr; + s->midi.ocnt += cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + spin_lock_irqsave(&s->lock, flags); + solo1_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&s->midi.owait, &wait); + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int solo1_midi_poll(struct file *file, struct poll_table_struct *wait) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_flags & FMODE_WRITE) + poll_wait(file, &s->midi.owait, wait); + if (file->f_flags & FMODE_READ) + poll_wait(file, &s->midi.iwait, wait); + spin_lock_irqsave(&s->lock, flags); + if (file->f_flags & FMODE_READ) { + if (s->midi.icnt > 0) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_flags & FMODE_WRITE) { + if (s->midi.ocnt < MIDIOUTBUF) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int solo1_midi_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + struct solo1_state *s = NULL; + struct pci_dev *pci_dev = NULL; + + while ((pci_dev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pci_dev)) != NULL) { + struct pci_driver *drvr; + + drvr = pci_dev_driver(pci_dev); + if (drvr != &solo1_driver) + continue; + s = (struct solo1_state*)pci_get_drvdata(pci_dev); + if (!s) + continue; + if (s->dev_midi == minor) + break; + } + if (!s) + return -ENODEV; + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & (file->f_mode << FMODE_MIDI_SHIFT)) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + outb(0xff, s->mpubase+1); /* reset command */ + outb(0x3f, s->mpubase+1); /* uart command */ + if (!(inb(s->mpubase+1) & 0x80)) + inb(s->mpubase); + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + outb(0xb0, s->iobase + 7); /* enable A1, A2, MPU irq's */ + init_timer(&s->midi.timer); + s->midi.timer.expires = jiffies+1; + s->midi.timer.data = (unsigned long)s; + s->midi.timer.function = solo1_midi_timer; + add_timer(&s->midi.timer); + } + if (file->f_mode & FMODE_READ) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + } + if (file->f_mode & FMODE_WRITE) { + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + } + spin_unlock_irqrestore(&s->lock, flags); + s->open_mode |= (file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ | FMODE_MIDI_WRITE); + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int solo1_midi_release(struct inode *inode, struct file *file) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + unsigned count, tmo; + + VALIDATE_STATE(s); + + lock_kernel(); + if (file->f_mode & FMODE_WRITE) { + add_wait_queue(&s->midi.owait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->midi.ocnt; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (file->f_flags & O_NONBLOCK) + break; + tmo = (count * HZ) / 3100; + if (!schedule_timeout(tmo ? : 1) && tmo) + printk(KERN_DEBUG "solo1: midi timed out??\n"); + } + remove_wait_queue(&s->midi.owait, &wait); + set_current_state(TASK_RUNNING); + } + down(&s->open_sem); + s->open_mode &= ~((file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ|FMODE_MIDI_WRITE)); + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + outb(0x30, s->iobase + 7); /* enable A1, A2 irq's */ + del_timer(&s->midi.timer); + } + spin_unlock_irqrestore(&s->lock, flags); + wake_up(&s->open_wait); + up(&s->open_sem); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations solo1_midi_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = solo1_midi_read, + .write = solo1_midi_write, + .poll = solo1_midi_poll, + .open = solo1_midi_open, + .release = solo1_midi_release, +}; + +/* --------------------------------------------------------------------- */ + +static int solo1_dmfm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + static const unsigned char op_offset[18] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 + }; + struct solo1_state *s = (struct solo1_state *)file->private_data; + struct dm_fm_voice v; + struct dm_fm_note n; + struct dm_fm_params p; + unsigned int io; + unsigned int regb; + + switch (cmd) { + case FM_IOCTL_RESET: + for (regb = 0xb0; regb < 0xb9; regb++) { + outb(regb, s->sbbase); + outb(0, s->sbbase+1); + outb(regb, s->sbbase+2); + outb(0, s->sbbase+3); + } + return 0; + + case FM_IOCTL_PLAY_NOTE: + if (copy_from_user(&n, (void __user *)arg, sizeof(n))) + return -EFAULT; + if (n.voice >= 18) + return -EINVAL; + if (n.voice >= 9) { + regb = n.voice - 9; + io = s->sbbase+2; + } else { + regb = n.voice; + io = s->sbbase; + } + outb(0xa0 + regb, io); + outb(n.fnum & 0xff, io+1); + outb(0xb0 + regb, io); + outb(((n.fnum >> 8) & 3) | ((n.octave & 7) << 2) | ((n.key_on & 1) << 5), io+1); + return 0; + + case FM_IOCTL_SET_VOICE: + if (copy_from_user(&v, (void __user *)arg, sizeof(v))) + return -EFAULT; + if (v.voice >= 18) + return -EINVAL; + regb = op_offset[v.voice]; + io = s->sbbase + ((v.op & 1) << 1); + outb(0x20 + regb, io); + outb(((v.am & 1) << 7) | ((v.vibrato & 1) << 6) | ((v.do_sustain & 1) << 5) | + ((v.kbd_scale & 1) << 4) | (v.harmonic & 0xf), io+1); + outb(0x40 + regb, io); + outb(((v.scale_level & 0x3) << 6) | (v.volume & 0x3f), io+1); + outb(0x60 + regb, io); + outb(((v.attack & 0xf) << 4) | (v.decay & 0xf), io+1); + outb(0x80 + regb, io); + outb(((v.sustain & 0xf) << 4) | (v.release & 0xf), io+1); + outb(0xe0 + regb, io); + outb(v.waveform & 0x7, io+1); + if (n.voice >= 9) { + regb = n.voice - 9; + io = s->sbbase+2; + } else { + regb = n.voice; + io = s->sbbase; + } + outb(0xc0 + regb, io); + outb(((v.right & 1) << 5) | ((v.left & 1) << 4) | ((v.feedback & 7) << 1) | + (v.connection & 1), io+1); + return 0; + + case FM_IOCTL_SET_PARAMS: + if (copy_from_user(&p, (void __user *)arg, sizeof(p))) + return -EFAULT; + outb(0x08, s->sbbase); + outb((p.kbd_split & 1) << 6, s->sbbase+1); + outb(0xbd, s->sbbase); + outb(((p.am_depth & 1) << 7) | ((p.vib_depth & 1) << 6) | ((p.rhythm & 1) << 5) | ((p.bass & 1) << 4) | + ((p.snare & 1) << 3) | ((p.tomtom & 1) << 2) | ((p.cymbal & 1) << 1) | (p.hihat & 1), s->sbbase+1); + return 0; + + case FM_IOCTL_SET_OPL: + outb(4, s->sbbase+2); + outb(arg, s->sbbase+3); + return 0; + + case FM_IOCTL_SET_MODE: + outb(5, s->sbbase+2); + outb(arg & 1, s->sbbase+3); + return 0; + + default: + return -EINVAL; + } +} + +static int solo1_dmfm_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + struct solo1_state *s = NULL; + struct pci_dev *pci_dev = NULL; + + while ((pci_dev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pci_dev)) != NULL) { + struct pci_driver *drvr; + + drvr = pci_dev_driver(pci_dev); + if (drvr != &solo1_driver) + continue; + s = (struct solo1_state*)pci_get_drvdata(pci_dev); + if (!s) + continue; + if (s->dev_dmfm == minor) + break; + } + if (!s) + return -ENODEV; + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & FMODE_DMFM) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + if (!request_region(s->sbbase, FMSYNTH_EXTENT, "ESS Solo1")) { + up(&s->open_sem); + printk(KERN_ERR "solo1: FM synth io ports in use, opl3 loaded?\n"); + return -EBUSY; + } + /* init the stuff */ + outb(1, s->sbbase); + outb(0x20, s->sbbase+1); /* enable waveforms */ + outb(4, s->sbbase+2); + outb(0, s->sbbase+3); /* no 4op enabled */ + outb(5, s->sbbase+2); + outb(1, s->sbbase+3); /* enable OPL3 */ + s->open_mode |= FMODE_DMFM; + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int solo1_dmfm_release(struct inode *inode, struct file *file) +{ + struct solo1_state *s = (struct solo1_state *)file->private_data; + unsigned int regb; + + VALIDATE_STATE(s); + lock_kernel(); + down(&s->open_sem); + s->open_mode &= ~FMODE_DMFM; + for (regb = 0xb0; regb < 0xb9; regb++) { + outb(regb, s->sbbase); + outb(0, s->sbbase+1); + outb(regb, s->sbbase+2); + outb(0, s->sbbase+3); + } + release_region(s->sbbase, FMSYNTH_EXTENT); + wake_up(&s->open_wait); + up(&s->open_sem); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations solo1_dmfm_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = solo1_dmfm_ioctl, + .open = solo1_dmfm_open, + .release = solo1_dmfm_release, +}; + +/* --------------------------------------------------------------------- */ + +static struct initvol { + int mixch; + int vol; +} initvol[] __devinitdata = { + { 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_LINE2, 0x4040 }, + { SOUND_MIXER_WRITE_RECLEV, 0x4040 }, + { SOUND_MIXER_WRITE_SPEAKER, 0x4040 }, + { SOUND_MIXER_WRITE_MIC, 0x4040 } +}; + +static int setup_solo1(struct solo1_state *s) +{ + struct pci_dev *pcidev = s->dev; + mm_segment_t fs; + int i, val; + + /* initialize DDMA base address */ + printk(KERN_DEBUG "solo1: ddma base address: 0x%lx\n", s->ddmabase); + pci_write_config_word(pcidev, 0x60, (s->ddmabase & (~0xf)) | 1); + /* set DMA policy to DDMA, IRQ emulation off (CLKRUN disabled for now) */ + pci_write_config_dword(pcidev, 0x50, 0); + /* disable legacy audio address decode */ + pci_write_config_word(pcidev, 0x40, 0x907f); + + /* initialize the chips */ + if (!reset_ctrl(s)) { + printk(KERN_ERR "esssolo1: cannot reset controller\n"); + return -1; + } + outb(0xb0, s->iobase+7); /* enable A1, A2, MPU irq's */ + + /* initialize mixer regs */ + write_mixer(s, 0x7f, 0); /* disable music digital recording */ + write_mixer(s, 0x7d, 0x0c); /* enable mic preamp, MONO_OUT is 2nd DAC right channel */ + write_mixer(s, 0x64, 0x45); /* volume control */ + write_mixer(s, 0x48, 0x10); /* enable music DAC/ES6xx interface */ + write_mixer(s, 0x50, 0); /* disable spatializer */ + write_mixer(s, 0x52, 0); + write_mixer(s, 0x14, 0); /* DAC1 minimum volume */ + write_mixer(s, 0x71, 0x20); /* enable new 0xA1 reg format */ + outb(0, s->ddmabase+0xd); /* DMA master clear */ + outb(1, s->ddmabase+0xf); /* mask channel */ + /*outb(0, s->ddmabase+0x8);*/ /* enable controller (enable is low active!!) */ + + pci_set_master(pcidev); /* enable bus mastering */ + + fs = get_fs(); + set_fs(KERNEL_DS); + val = SOUND_MASK_LINE; + mixer_ioctl(s, SOUND_MIXER_WRITE_RECSRC, (unsigned long)&val); + for (i = 0; i < sizeof(initvol)/sizeof(initvol[0]); i++) { + val = initvol[i].vol; + mixer_ioctl(s, initvol[i].mixch, (unsigned long)&val); + } + val = 1; /* enable mic preamp */ + mixer_ioctl(s, SOUND_MIXER_PRIVATE1, (unsigned long)&val); + set_fs(fs); + return 0; +} + +static int +solo1_suspend(struct pci_dev *pci_dev, pm_message_t state) { + struct solo1_state *s = (struct solo1_state*)pci_get_drvdata(pci_dev); + if (!s) + return 1; + outb(0, s->iobase+6); + /* DMA master clear */ + outb(0, s->ddmabase+0xd); + /* reset sequencer and FIFO */ + outb(3, s->sbbase+6); + /* turn off DDMA controller address space */ + pci_write_config_word(s->dev, 0x60, 0); + return 0; +} + +static int +solo1_resume(struct pci_dev *pci_dev) { + struct solo1_state *s = (struct solo1_state*)pci_get_drvdata(pci_dev); + if (!s) + return 1; + setup_solo1(s); + return 0; +} + +static int __devinit solo1_register_gameport(struct solo1_state *s, int io_port) +{ + struct gameport *gp; + + if (!request_region(io_port, GAMEPORT_EXTENT, "ESS Solo1")) { + printk(KERN_ERR "solo1: gameport io ports are in use\n"); + return -EBUSY; + } + + s->gameport = gp = gameport_allocate_port(); + if (!gp) { + printk(KERN_ERR "solo1: can not allocate memory for gameport\n"); + release_region(io_port, GAMEPORT_EXTENT); + return -ENOMEM; + } + + gameport_set_name(gp, "ESS Solo1 Gameport"); + gameport_set_phys(gp, "isa%04x/gameport0", io_port); + gp->dev.parent = &s->dev->dev; + gp->io = io_port; + + gameport_register_port(gp); + + return 0; +} + +static int __devinit solo1_probe(struct pci_dev *pcidev, const struct pci_device_id *pciid) +{ + struct solo1_state *s; + int gpio; + int ret; + + if ((ret=pci_enable_device(pcidev))) + return ret; + if (!(pci_resource_flags(pcidev, 0) & IORESOURCE_IO) || + !(pci_resource_flags(pcidev, 1) & IORESOURCE_IO) || + !(pci_resource_flags(pcidev, 2) & IORESOURCE_IO) || + !(pci_resource_flags(pcidev, 3) & IORESOURCE_IO)) + return -ENODEV; + if (pcidev->irq == 0) + return -ENODEV; + + /* Recording requires 24-bit DMA, so attempt to set dma mask + * to 24 bits first, then 32 bits (playback only) if that fails. + */ + if (pci_set_dma_mask(pcidev, 0x00ffffff) && + pci_set_dma_mask(pcidev, 0xffffffff)) { + printk(KERN_WARNING "solo1: architecture does not support 24bit or 32bit PCI busmaster DMA\n"); + return -ENODEV; + } + + if (!(s = kmalloc(sizeof(struct solo1_state), GFP_KERNEL))) { + printk(KERN_WARNING "solo1: out of memory\n"); + return -ENOMEM; + } + memset(s, 0, sizeof(struct solo1_state)); + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac.wait); + init_waitqueue_head(&s->open_wait); + init_waitqueue_head(&s->midi.iwait); + init_waitqueue_head(&s->midi.owait); + init_MUTEX(&s->open_sem); + spin_lock_init(&s->lock); + s->magic = SOLO1_MAGIC; + s->dev = pcidev; + s->iobase = pci_resource_start(pcidev, 0); + s->sbbase = pci_resource_start(pcidev, 1); + s->vcbase = pci_resource_start(pcidev, 2); + s->ddmabase = s->vcbase + DDMABASE_OFFSET; + s->mpubase = pci_resource_start(pcidev, 3); + gpio = pci_resource_start(pcidev, 4); + s->irq = pcidev->irq; + ret = -EBUSY; + if (!request_region(s->iobase, IOBASE_EXTENT, "ESS Solo1")) { + printk(KERN_ERR "solo1: io ports in use\n"); + goto err_region1; + } + if (!request_region(s->sbbase+FMSYNTH_EXTENT, SBBASE_EXTENT-FMSYNTH_EXTENT, "ESS Solo1")) { + printk(KERN_ERR "solo1: io ports in use\n"); + goto err_region2; + } + if (!request_region(s->ddmabase, DDMABASE_EXTENT, "ESS Solo1")) { + printk(KERN_ERR "solo1: io ports in use\n"); + goto err_region3; + } + if (!request_region(s->mpubase, MPUBASE_EXTENT, "ESS Solo1")) { + printk(KERN_ERR "solo1: io ports in use\n"); + goto err_region4; + } + if ((ret=request_irq(s->irq,solo1_interrupt,SA_SHIRQ,"ESS Solo1",s))) { + printk(KERN_ERR "solo1: irq %u in use\n", s->irq); + goto err_irq; + } + /* register devices */ + if ((s->dev_audio = register_sound_dsp(&solo1_audio_fops, -1)) < 0) { + ret = s->dev_audio; + goto err_dev1; + } + if ((s->dev_mixer = register_sound_mixer(&solo1_mixer_fops, -1)) < 0) { + ret = s->dev_mixer; + goto err_dev2; + } + if ((s->dev_midi = register_sound_midi(&solo1_midi_fops, -1)) < 0) { + ret = s->dev_midi; + goto err_dev3; + } + if ((s->dev_dmfm = register_sound_special(&solo1_dmfm_fops, 15 /* ?? */)) < 0) { + ret = s->dev_dmfm; + goto err_dev4; + } + if (setup_solo1(s)) { + ret = -EIO; + goto err; + } + /* register gameport */ + solo1_register_gameport(s, gpio); + /* store it in the driver field */ + pci_set_drvdata(pcidev, s); + return 0; + + err: + unregister_sound_special(s->dev_dmfm); + err_dev4: + unregister_sound_midi(s->dev_midi); + err_dev3: + unregister_sound_mixer(s->dev_mixer); + err_dev2: + unregister_sound_dsp(s->dev_audio); + err_dev1: + printk(KERN_ERR "solo1: initialisation error\n"); + free_irq(s->irq, s); + err_irq: + release_region(s->mpubase, MPUBASE_EXTENT); + err_region4: + release_region(s->ddmabase, DDMABASE_EXTENT); + err_region3: + release_region(s->sbbase+FMSYNTH_EXTENT, SBBASE_EXTENT-FMSYNTH_EXTENT); + err_region2: + release_region(s->iobase, IOBASE_EXTENT); + err_region1: + kfree(s); + return ret; +} + +static void __devexit solo1_remove(struct pci_dev *dev) +{ + struct solo1_state *s = pci_get_drvdata(dev); + + if (!s) + return; + /* stop DMA controller */ + outb(0, s->iobase+6); + outb(0, s->ddmabase+0xd); /* DMA master clear */ + outb(3, s->sbbase+6); /* reset sequencer and FIFO */ + synchronize_irq(s->irq); + pci_write_config_word(s->dev, 0x60, 0); /* turn off DDMA controller address space */ + free_irq(s->irq, s); + if (s->gameport) { + int gpio = s->gameport->io; + gameport_unregister_port(s->gameport); + release_region(gpio, GAMEPORT_EXTENT); + } + release_region(s->iobase, IOBASE_EXTENT); + release_region(s->sbbase+FMSYNTH_EXTENT, SBBASE_EXTENT-FMSYNTH_EXTENT); + release_region(s->ddmabase, DDMABASE_EXTENT); + release_region(s->mpubase, MPUBASE_EXTENT); + unregister_sound_dsp(s->dev_audio); + unregister_sound_mixer(s->dev_mixer); + unregister_sound_midi(s->dev_midi); + unregister_sound_special(s->dev_dmfm); + kfree(s); + pci_set_drvdata(dev, NULL); +} + +static struct pci_device_id id_table[] = { + { PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_SOLO1, PCI_ANY_ID, PCI_ANY_ID, 0, 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, id_table); + +static struct pci_driver solo1_driver = { + .name = "ESS Solo1", + .id_table = id_table, + .probe = solo1_probe, + .remove = __devexit_p(solo1_remove), + .suspend = solo1_suspend, + .resume = solo1_resume, +}; + + +static int __init init_solo1(void) +{ + printk(KERN_INFO "solo1: version v0.20 time " __TIME__ " " __DATE__ "\n"); + return pci_register_driver(&solo1_driver); +} + +/* --------------------------------------------------------------------- */ + +MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu"); +MODULE_DESCRIPTION("ESS Solo1 Driver"); +MODULE_LICENSE("GPL"); + + +static void __exit cleanup_solo1(void) +{ + printk(KERN_INFO "solo1: unloading\n"); + pci_unregister_driver(&solo1_driver); +} + +/* --------------------------------------------------------------------- */ + +module_init(init_solo1); +module_exit(cleanup_solo1); + diff --git a/sound/oss/forte.c b/sound/oss/forte.c new file mode 100644 index 000000000000..8406bc90c4ff --- /dev/null +++ b/sound/oss/forte.c @@ -0,0 +1,2137 @@ +/* + * forte.c - ForteMedia FM801 OSS Driver + * + * Written by Martin K. Petersen + * Copyright (C) 2002 Hewlett-Packard Company + * Portions Copyright (C) 2003 Martin K. Petersen + * + * Latest version: http://mkp.net/forte/ + * + * Based upon the ALSA FM801 driver by Jaroslav Kysela and OSS drivers + * by Thomas Sailer, Alan Cox, Zach Brown, and Jeff Garzik. Thanks + * guys! + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#define DRIVER_NAME "forte" +#define DRIVER_VERSION "$Id: forte.c,v 1.63 2003/03/01 05:32:42 mkp Exp $" +#define PFX DRIVER_NAME ": " + +#undef M_DEBUG + +#ifdef M_DEBUG +#define DPRINTK(args...) printk(KERN_WARNING args) +#else +#define DPRINTK(args...) +#endif + +/* Card capabilities */ +#define FORTE_CAPS (DSP_CAP_MMAP | DSP_CAP_TRIGGER) + +/* Supported audio formats */ +#define FORTE_FMTS (AFMT_U8 | AFMT_S16_LE) + +/* Buffers */ +#define FORTE_MIN_FRAG_SIZE 256 +#define FORTE_MAX_FRAG_SIZE PAGE_SIZE +#define FORTE_DEF_FRAG_SIZE 256 +#define FORTE_MIN_FRAGMENTS 2 +#define FORTE_MAX_FRAGMENTS 256 +#define FORTE_DEF_FRAGMENTS 2 +#define FORTE_MIN_BUF_MSECS 500 +#define FORTE_MAX_BUF_MSECS 1000 + +/* PCI BARs */ +#define FORTE_PCM_VOL 0x00 /* PCM Output Volume */ +#define FORTE_FM_VOL 0x02 /* FM Output Volume */ +#define FORTE_I2S_VOL 0x04 /* I2S Volume */ +#define FORTE_REC_SRC 0x06 /* Record Source */ +#define FORTE_PLY_CTRL 0x08 /* Playback Control */ +#define FORTE_PLY_COUNT 0x0a /* Playback Count */ +#define FORTE_PLY_BUF1 0x0c /* Playback Buffer I */ +#define FORTE_PLY_BUF2 0x10 /* Playback Buffer II */ +#define FORTE_CAP_CTRL 0x14 /* Capture Control */ +#define FORTE_CAP_COUNT 0x16 /* Capture Count */ +#define FORTE_CAP_BUF1 0x18 /* Capture Buffer I */ +#define FORTE_CAP_BUF2 0x1c /* Capture Buffer II */ +#define FORTE_CODEC_CTRL 0x22 /* Codec Control */ +#define FORTE_I2S_MODE 0x24 /* I2S Mode Control */ +#define FORTE_VOLUME 0x26 /* Volume Up/Down/Mute Status */ +#define FORTE_I2C_CTRL 0x29 /* I2C Control */ +#define FORTE_AC97_CMD 0x2a /* AC'97 Command */ +#define FORTE_AC97_DATA 0x2c /* AC'97 Data */ +#define FORTE_MPU401_DATA 0x30 /* MPU401 Data */ +#define FORTE_MPU401_CMD 0x31 /* MPU401 Command */ +#define FORTE_GPIO_CTRL 0x52 /* General Purpose I/O Control */ +#define FORTE_GEN_CTRL 0x54 /* General Control */ +#define FORTE_IRQ_MASK 0x56 /* Interrupt Mask */ +#define FORTE_IRQ_STATUS 0x5a /* Interrupt Status */ +#define FORTE_OPL3_BANK0 0x68 /* OPL3 Status Read / Bank 0 Write */ +#define FORTE_OPL3_DATA0 0x69 /* OPL3 Data 0 Write */ +#define FORTE_OPL3_BANK1 0x6a /* OPL3 Bank 1 Write */ +#define FORTE_OPL3_DATA1 0x6b /* OPL3 Bank 1 Write */ +#define FORTE_POWERDOWN 0x70 /* Blocks Power Down Control */ + +#define FORTE_CAP_OFFSET FORTE_CAP_CTRL - FORTE_PLY_CTRL + +#define FORTE_AC97_ADDR_SHIFT 10 + +/* Playback and record control register bits */ +#define FORTE_BUF1_LAST (1<<1) +#define FORTE_BUF2_LAST (1<<2) +#define FORTE_START (1<<5) +#define FORTE_PAUSE (1<<6) +#define FORTE_IMMED_STOP (1<<7) +#define FORTE_RATE_SHIFT 8 +#define FORTE_RATE_MASK (15 << FORTE_RATE_SHIFT) +#define FORTE_CHANNELS_4 (1<<12) /* Playback only */ +#define FORTE_CHANNELS_6 (2<<12) /* Playback only */ +#define FORTE_CHANNELS_6MS (3<<12) /* Playback only */ +#define FORTE_CHANNELS_MASK (3<<12) +#define FORTE_16BIT (1<<14) +#define FORTE_STEREO (1<<15) + +/* IRQ status bits */ +#define FORTE_IRQ_PLAYBACK (1<<8) +#define FORTE_IRQ_CAPTURE (1<<9) +#define FORTE_IRQ_VOLUME (1<<14) +#define FORTE_IRQ_MPU (1<<15) + +/* CODEC control */ +#define FORTE_CC_CODEC_RESET (1<<5) +#define FORTE_CC_AC97_RESET (1<<6) + +/* AC97 cmd */ +#define FORTE_AC97_WRITE (0<<7) +#define FORTE_AC97_READ (1<<7) +#define FORTE_AC97_DP_INVALID (0<<8) +#define FORTE_AC97_DP_VALID (1<<8) +#define FORTE_AC97_PORT_RDY (0<<9) +#define FORTE_AC97_PORT_BSY (1<<9) + + +struct forte_channel { + const char *name; + + unsigned short ctrl; /* Ctrl BAR contents */ + unsigned long iobase; /* Ctrl BAR address */ + + wait_queue_head_t wait; + + void *buf; /* Buffer */ + dma_addr_t buf_handle; /* Buffer handle */ + + unsigned int record; + unsigned int format; + unsigned int rate; + unsigned int stereo; + + unsigned int frag_sz; /* Current fragment size */ + unsigned int frag_num; /* Current # of fragments */ + unsigned int frag_msecs; /* Milliseconds per frag */ + unsigned int buf_sz; /* Current buffer size */ + + unsigned int hwptr; /* Tail */ + unsigned int swptr; /* Head */ + unsigned int filled_frags; /* Fragments currently full */ + unsigned int next_buf; /* Index of next buffer */ + + unsigned int active; /* Channel currently in use */ + unsigned int mapped; /* mmap */ + + unsigned int buf_pages; /* Real size of buffer */ + unsigned int nr_irqs; /* Number of interrupts */ + unsigned int bytes; /* Total bytes */ + unsigned int residue; /* Partial fragment */ +}; + + +struct forte_chip { + struct pci_dev *pci_dev; + unsigned long iobase; + int irq; + + struct semaphore open_sem; /* Device access */ + spinlock_t lock; /* State */ + + spinlock_t ac97_lock; + struct ac97_codec *ac97; + + int multichannel; + int dsp; /* OSS handle */ + int trigger; /* mmap I/O trigger */ + + struct forte_channel play; + struct forte_channel rec; +}; + + +static int channels[] = { 2, 4, 6, }; +static int rates[] = { 5500, 8000, 9600, 11025, 16000, 19200, + 22050, 32000, 38400, 44100, 48000, }; + +static struct forte_chip *forte; +static int found; + + +/* AC97 Codec -------------------------------------------------------------- */ + + +/** + * forte_ac97_wait: + * @chip: fm801 instance whose AC97 codec to wait on + * + * FIXME: + * Stop busy-waiting + */ + +static inline int +forte_ac97_wait (struct forte_chip *chip) +{ + int i = 10000; + + while ( (inw (chip->iobase + FORTE_AC97_CMD) & FORTE_AC97_PORT_BSY) + && i-- ) + cpu_relax(); + + return i == 0; +} + + +/** + * forte_ac97_read: + * @codec: AC97 codec to read from + * @reg: register to read + */ + +static u16 +forte_ac97_read (struct ac97_codec *codec, u8 reg) +{ + u16 ret = 0; + struct forte_chip *chip = codec->private_data; + + spin_lock (&chip->ac97_lock); + + /* Knock, knock */ + if (forte_ac97_wait (chip)) { + printk (KERN_ERR PFX "ac97_read: Serial bus busy\n"); + goto out; + } + + /* Send read command */ + outw (reg | (1<<7), chip->iobase + FORTE_AC97_CMD); + + if (forte_ac97_wait (chip)) { + printk (KERN_ERR PFX "ac97_read: Bus busy reading reg 0x%x\n", + reg); + goto out; + } + + /* Sanity checking */ + if (inw (chip->iobase + FORTE_AC97_CMD) & FORTE_AC97_DP_INVALID) { + printk (KERN_ERR PFX "ac97_read: Invalid data port"); + goto out; + } + + /* Fetch result */ + ret = inw (chip->iobase + FORTE_AC97_DATA); + + out: + spin_unlock (&chip->ac97_lock); + return ret; +} + + +/** + * forte_ac97_write: + * @codec: AC97 codec to send command to + * @reg: register to write + * @val: value to write + */ + +static void +forte_ac97_write (struct ac97_codec *codec, u8 reg, u16 val) +{ + struct forte_chip *chip = codec->private_data; + + spin_lock (&chip->ac97_lock); + + /* Knock, knock */ + if (forte_ac97_wait (chip)) { + printk (KERN_ERR PFX "ac97_write: Serial bus busy\n"); + goto out; + } + + outw (val, chip->iobase + FORTE_AC97_DATA); + outb (reg | FORTE_AC97_WRITE, chip->iobase + FORTE_AC97_CMD); + + /* Wait for completion */ + if (forte_ac97_wait (chip)) { + printk (KERN_ERR PFX "ac97_write: Bus busy after write\n"); + goto out; + } + + out: + spin_unlock (&chip->ac97_lock); +} + + +/* Mixer ------------------------------------------------------------------- */ + + +/** + * forte_mixer_open: + * @inode: + * @file: + */ + +static int +forte_mixer_open (struct inode *inode, struct file *file) +{ + struct forte_chip *chip = forte; + file->private_data = chip->ac97; + return 0; +} + + +/** + * forte_mixer_release: + * @inode: + * @file: + */ + +static int +forte_mixer_release (struct inode *inode, struct file *file) +{ + /* We will welease Wodewick */ + return 0; +} + + +/** + * forte_mixer_ioctl: + * @inode: + * @file: + */ + +static int +forte_mixer_ioctl (struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct ac97_codec *codec = (struct ac97_codec *) file->private_data; + + return codec->mixer_ioctl (codec, cmd, arg); +} + + +static struct file_operations forte_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = forte_mixer_ioctl, + .open = forte_mixer_open, + .release = forte_mixer_release, +}; + + +/* Channel ----------------------------------------------------------------- */ + +/** + * forte_channel_reset: + * @channel: Channel to reset + * + * Locking: Must be called with lock held. + */ + +static void +forte_channel_reset (struct forte_channel *channel) +{ + if (!channel || !channel->iobase) + return; + + DPRINTK ("%s: channel = %s\n", __FUNCTION__, channel->name); + + channel->ctrl &= ~FORTE_START; + outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL); + + /* We always play at least two fragments, hence these defaults */ + channel->hwptr = channel->frag_sz; + channel->next_buf = 1; + channel->swptr = 0; + channel->filled_frags = 0; + channel->active = 0; + channel->bytes = 0; + channel->nr_irqs = 0; + channel->mapped = 0; + channel->residue = 0; +} + + +/** + * forte_channel_start: + * @channel: Channel to start (record/playback) + * + * Locking: Must be called with lock held. + */ + +static void inline +forte_channel_start (struct forte_channel *channel) +{ + if (!channel || !channel->iobase || channel->active) + return; + + channel->ctrl &= ~(FORTE_PAUSE | FORTE_BUF1_LAST | FORTE_BUF2_LAST + | FORTE_IMMED_STOP); + channel->ctrl |= FORTE_START; + channel->active = 1; + outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL); +} + + +/** + * forte_channel_stop: + * @channel: Channel to stop + * + * Locking: Must be called with lock held. + */ + +static void inline +forte_channel_stop (struct forte_channel *channel) +{ + if (!channel || !channel->iobase) + return; + + channel->ctrl &= ~(FORTE_START | FORTE_PAUSE); + channel->ctrl |= FORTE_IMMED_STOP; + + channel->active = 0; + outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL); +} + + +/** + * forte_channel_pause: + * @channel: Channel to pause + * + * Locking: Must be called with lock held. + */ + +static void inline +forte_channel_pause (struct forte_channel *channel) +{ + if (!channel || !channel->iobase) + return; + + channel->ctrl |= FORTE_PAUSE; + + channel->active = 0; + outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL); +} + + +/** + * forte_channel_rate: + * @channel: Channel whose rate to set. Playback and record are + * independent. + * @rate: Channel rate in Hz + * + * Locking: Must be called with lock held. + */ + +static int +forte_channel_rate (struct forte_channel *channel, unsigned int rate) +{ + int new_rate; + + if (!channel || !channel->iobase) + return -EINVAL; + + /* The FM801 only supports a handful of fixed frequencies. + * We find the value closest to what userland requested. + */ + if (rate <= 6250) { rate = 5500; new_rate = 0; } + else if (rate <= 8800) { rate = 8000; new_rate = 1; } + else if (rate <= 10312) { rate = 9600; new_rate = 2; } + else if (rate <= 13512) { rate = 11025; new_rate = 3; } + else if (rate <= 17600) { rate = 16000; new_rate = 4; } + else if (rate <= 20625) { rate = 19200; new_rate = 5; } + else if (rate <= 27025) { rate = 22050; new_rate = 6; } + else if (rate <= 35200) { rate = 32000; new_rate = 7; } + else if (rate <= 41250) { rate = 38400; new_rate = 8; } + else if (rate <= 46050) { rate = 44100; new_rate = 9; } + else { rate = 48000; new_rate = 10; } + + channel->ctrl &= ~FORTE_RATE_MASK; + channel->ctrl |= new_rate << FORTE_RATE_SHIFT; + channel->rate = rate; + + DPRINTK ("%s: %s rate = %d\n", __FUNCTION__, channel->name, rate); + + return rate; +} + + +/** + * forte_channel_format: + * @channel: Channel whose audio format to set + * @format: OSS format ID + * + * Locking: Must be called with lock held. + */ + +static int +forte_channel_format (struct forte_channel *channel, int format) +{ + if (!channel || !channel->iobase) + return -EINVAL; + + switch (format) { + + case AFMT_QUERY: + break; + + case AFMT_U8: + channel->ctrl &= ~FORTE_16BIT; + channel->format = AFMT_U8; + break; + + case AFMT_S16_LE: + default: + channel->ctrl |= FORTE_16BIT; + channel->format = AFMT_S16_LE; + break; + } + + DPRINTK ("%s: %s want %d format, got %d\n", __FUNCTION__, channel->name, + format, channel->format); + + return channel->format; +} + + +/** + * forte_channel_stereo: + * @channel: Channel to toggle + * @stereo: 0 for Mono, 1 for Stereo + * + * Locking: Must be called with lock held. + */ + +static int +forte_channel_stereo (struct forte_channel *channel, unsigned int stereo) +{ + int ret; + + if (!channel || !channel->iobase) + return -EINVAL; + + DPRINTK ("%s: %s stereo = %d\n", __FUNCTION__, channel->name, stereo); + + switch (stereo) { + + case 0: + channel->ctrl &= ~(FORTE_STEREO | FORTE_CHANNELS_MASK); + channel-> stereo = stereo; + ret = stereo; + break; + + case 1: + channel->ctrl &= ~FORTE_CHANNELS_MASK; + channel->ctrl |= FORTE_STEREO; + channel-> stereo = stereo; + ret = stereo; + break; + + default: + DPRINTK ("Unsupported channel format"); + ret = -EINVAL; + break; + } + + return ret; +} + + +/** + * forte_channel_buffer: + * @channel: Channel whose buffer to set up + * + * Locking: Must be called with lock held. + */ + +static void +forte_channel_buffer (struct forte_channel *channel, int sz, int num) +{ + unsigned int msecs, shift; + + /* Go away, I'm busy */ + if (channel->filled_frags || channel->bytes) + return; + + /* Fragment size must be a power of 2 */ + shift = 0; sz++; + while (sz >>= 1) + shift++; + channel->frag_sz = 1 << shift; + + /* Round fragment size to something reasonable */ + if (channel->frag_sz < FORTE_MIN_FRAG_SIZE) + channel->frag_sz = FORTE_MIN_FRAG_SIZE; + + if (channel->frag_sz > FORTE_MAX_FRAG_SIZE) + channel->frag_sz = FORTE_MAX_FRAG_SIZE; + + /* Find fragment length in milliseconds */ + msecs = channel->frag_sz / + (channel->format == AFMT_S16_LE ? 2 : 1) / + (channel->stereo ? 2 : 1) / + (channel->rate / 1000); + + channel->frag_msecs = msecs; + + /* Pick a suitable number of fragments */ + if (msecs * num < FORTE_MIN_BUF_MSECS) + num = FORTE_MIN_BUF_MSECS / msecs; + + if (msecs * num > FORTE_MAX_BUF_MSECS) + num = FORTE_MAX_BUF_MSECS / msecs; + + /* Fragment number must be a power of 2 */ + shift = 0; + while (num >>= 1) + shift++; + channel->frag_num = 1 << (shift + 1); + + /* Round fragment number to something reasonable */ + if (channel->frag_num < FORTE_MIN_FRAGMENTS) + channel->frag_num = FORTE_MIN_FRAGMENTS; + + if (channel->frag_num > FORTE_MAX_FRAGMENTS) + channel->frag_num = FORTE_MAX_FRAGMENTS; + + channel->buf_sz = channel->frag_sz * channel->frag_num; + + DPRINTK ("%s: %s frag_sz = %d, frag_num = %d, buf_sz = %d\n", + __FUNCTION__, channel->name, channel->frag_sz, + channel->frag_num, channel->buf_sz); +} + + +/** + * forte_channel_prep: + * @channel: Channel whose buffer to prepare + * + * Locking: Lock held. + */ + +static void +forte_channel_prep (struct forte_channel *channel) +{ + struct page *page; + int i; + + if (channel->buf) + return; + + forte_channel_buffer (channel, channel->frag_sz, channel->frag_num); + channel->buf_pages = channel->buf_sz >> PAGE_SHIFT; + + if (channel->buf_sz % PAGE_SIZE) + channel->buf_pages++; + + DPRINTK ("%s: %s frag_sz = %d, frag_num = %d, buf_sz = %d, pg = %d\n", + __FUNCTION__, channel->name, channel->frag_sz, + channel->frag_num, channel->buf_sz, channel->buf_pages); + + /* DMA buffer */ + channel->buf = pci_alloc_consistent (forte->pci_dev, + channel->buf_pages * PAGE_SIZE, + &channel->buf_handle); + + if (!channel->buf || !channel->buf_handle) + BUG(); + + page = virt_to_page (channel->buf); + + /* FIXME: can this go away ? */ + for (i = 0 ; i < channel->buf_pages ; i++) + SetPageReserved(page++); + + /* Prep buffer registers */ + outw (channel->frag_sz - 1, channel->iobase + FORTE_PLY_COUNT); + outl (channel->buf_handle, channel->iobase + FORTE_PLY_BUF1); + outl (channel->buf_handle + channel->frag_sz, + channel->iobase + FORTE_PLY_BUF2); + + /* Reset hwptr */ + channel->hwptr = channel->frag_sz; + channel->next_buf = 1; + + DPRINTK ("%s: %s buffer @ %p (%p)\n", __FUNCTION__, channel->name, + channel->buf, channel->buf_handle); +} + + +/** + * forte_channel_drain: + * @chip: + * @channel: + * + * Locking: Don't hold the lock. + */ + +static inline int +forte_channel_drain (struct forte_channel *channel) +{ + DECLARE_WAITQUEUE (wait, current); + unsigned long flags; + + DPRINTK ("%s\n", __FUNCTION__); + + if (channel->mapped) { + spin_lock_irqsave (&forte->lock, flags); + forte_channel_stop (channel); + spin_unlock_irqrestore (&forte->lock, flags); + return 0; + } + + spin_lock_irqsave (&forte->lock, flags); + add_wait_queue (&channel->wait, &wait); + + for (;;) { + if (channel->active == 0 || channel->filled_frags == 1) + break; + + spin_unlock_irqrestore (&forte->lock, flags); + + __set_current_state (TASK_INTERRUPTIBLE); + schedule(); + + spin_lock_irqsave (&forte->lock, flags); + } + + forte_channel_stop (channel); + forte_channel_reset (channel); + set_current_state (TASK_RUNNING); + remove_wait_queue (&channel->wait, &wait); + spin_unlock_irqrestore (&forte->lock, flags); + + return 0; +} + + +/** + * forte_channel_init: + * @chip: Forte chip instance the channel hangs off + * @channel: Channel to initialize + * + * Description: + * Initializes a channel, sets defaults, and allocates + * buffers. + * + * Locking: No lock held. + */ + +static int +forte_channel_init (struct forte_chip *chip, struct forte_channel *channel) +{ + DPRINTK ("%s: chip iobase @ %p\n", __FUNCTION__, (void *)chip->iobase); + + spin_lock_irq (&chip->lock); + memset (channel, 0x0, sizeof (*channel)); + + if (channel == &chip->play) { + channel->name = "PCM_OUT"; + channel->iobase = chip->iobase; + DPRINTK ("%s: PCM-OUT iobase @ %p\n", __FUNCTION__, + (void *) channel->iobase); + } + else if (channel == &chip->rec) { + channel->name = "PCM_IN"; + channel->iobase = chip->iobase + FORTE_CAP_OFFSET; + channel->record = 1; + DPRINTK ("%s: PCM-IN iobase @ %p\n", __FUNCTION__, + (void *) channel->iobase); + } + else + BUG(); + + init_waitqueue_head (&channel->wait); + + /* Defaults: 48kHz, 16-bit, stereo */ + channel->ctrl = inw (channel->iobase + FORTE_PLY_CTRL); + forte_channel_reset (channel); + forte_channel_stereo (channel, 1); + forte_channel_format (channel, AFMT_S16_LE); + forte_channel_rate (channel, 48000); + channel->frag_sz = FORTE_DEF_FRAG_SIZE; + channel->frag_num = FORTE_DEF_FRAGMENTS; + + chip->trigger = 0; + spin_unlock_irq (&chip->lock); + + return 0; +} + + +/** + * forte_channel_free: + * @chip: Chip this channel hangs off + * @channel: Channel to nuke + * + * Description: + * Resets channel and frees buffers. + * + * Locking: Hold your horses. + */ + +static void +forte_channel_free (struct forte_chip *chip, struct forte_channel *channel) +{ + DPRINTK ("%s: %s\n", __FUNCTION__, channel->name); + + if (!channel->buf_handle) + return; + + pci_free_consistent (chip->pci_dev, channel->buf_pages * PAGE_SIZE, + channel->buf, channel->buf_handle); + + memset (channel, 0x0, sizeof (*channel)); +} + + +/* DSP --------------------------------------------------------------------- */ + + +/** + * forte_dsp_ioctl: + */ + +static int +forte_dsp_ioctl (struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ival=0, ret, rval=0, rd, wr, count; + struct forte_chip *chip; + struct audio_buf_info abi; + struct count_info cinfo; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + chip = file->private_data; + + if (file->f_mode & FMODE_WRITE) + wr = 1; + else + wr = 0; + + if (file->f_mode & FMODE_READ) + rd = 1; + else + rd = 0; + + switch (cmd) { + + case OSS_GETVERSION: + return put_user (SOUND_VERSION, p); + + case SNDCTL_DSP_GETCAPS: + DPRINTK ("%s: GETCAPS\n", __FUNCTION__); + + ival = FORTE_CAPS; /* DUPLEX */ + return put_user (ival, p); + + case SNDCTL_DSP_GETFMTS: + DPRINTK ("%s: GETFMTS\n", __FUNCTION__); + + ival = FORTE_FMTS; /* U8, 16LE */ + return put_user (ival, p); + + case SNDCTL_DSP_SETFMT: /* U8, 16LE */ + DPRINTK ("%s: SETFMT\n", __FUNCTION__); + + if (get_user (ival, p)) + return -EFAULT; + + spin_lock_irq (&chip->lock); + + if (rd) { + forte_channel_stop (&chip->rec); + rval = forte_channel_format (&chip->rec, ival); + } + + if (wr) { + forte_channel_stop (&chip->rec); + rval = forte_channel_format (&chip->play, ival); + } + + spin_unlock_irq (&chip->lock); + + return put_user (rval, p); + + case SNDCTL_DSP_STEREO: /* 0 - mono, 1 - stereo */ + DPRINTK ("%s: STEREO\n", __FUNCTION__); + + if (get_user (ival, p)) + return -EFAULT; + + spin_lock_irq (&chip->lock); + + if (rd) { + forte_channel_stop (&chip->rec); + rval = forte_channel_stereo (&chip->rec, ival); + } + + if (wr) { + forte_channel_stop (&chip->rec); + rval = forte_channel_stereo (&chip->play, ival); + } + + spin_unlock_irq (&chip->lock); + + return put_user (rval, p); + + case SNDCTL_DSP_CHANNELS: /* 1 - mono, 2 - stereo */ + DPRINTK ("%s: CHANNELS\n", __FUNCTION__); + + if (get_user (ival, p)) + return -EFAULT; + + spin_lock_irq (&chip->lock); + + if (rd) { + forte_channel_stop (&chip->rec); + rval = forte_channel_stereo (&chip->rec, ival-1) + 1; + } + + if (wr) { + forte_channel_stop (&chip->play); + rval = forte_channel_stereo (&chip->play, ival-1) + 1; + } + + spin_unlock_irq (&chip->lock); + + return put_user (rval, p); + + case SNDCTL_DSP_SPEED: + DPRINTK ("%s: SPEED\n", __FUNCTION__); + + if (get_user (ival, p)) + return -EFAULT; + + spin_lock_irq (&chip->lock); + + if (rd) { + forte_channel_stop (&chip->rec); + rval = forte_channel_rate (&chip->rec, ival); + } + + if (wr) { + forte_channel_stop (&chip->play); + rval = forte_channel_rate (&chip->play, ival); + } + + spin_unlock_irq (&chip->lock); + + return put_user(rval, p); + + case SNDCTL_DSP_GETBLKSIZE: + DPRINTK ("%s: GETBLKSIZE\n", __FUNCTION__); + + spin_lock_irq (&chip->lock); + + if (rd) + ival = chip->rec.frag_sz; + + if (wr) + ival = chip->play.frag_sz; + + spin_unlock_irq (&chip->lock); + + return put_user (ival, p); + + case SNDCTL_DSP_RESET: + DPRINTK ("%s: RESET\n", __FUNCTION__); + + spin_lock_irq (&chip->lock); + + if (rd) + forte_channel_reset (&chip->rec); + + if (wr) + forte_channel_reset (&chip->play); + + spin_unlock_irq (&chip->lock); + + return 0; + + case SNDCTL_DSP_SYNC: + DPRINTK ("%s: SYNC\n", __FUNCTION__); + + if (wr) + ret = forte_channel_drain (&chip->play); + + return 0; + + case SNDCTL_DSP_POST: + DPRINTK ("%s: POST\n", __FUNCTION__); + + if (wr) { + spin_lock_irq (&chip->lock); + + if (chip->play.filled_frags) + forte_channel_start (&chip->play); + + spin_unlock_irq (&chip->lock); + } + + return 0; + + case SNDCTL_DSP_SETFRAGMENT: + DPRINTK ("%s: SETFRAGMENT\n", __FUNCTION__); + + if (get_user (ival, p)) + return -EFAULT; + + spin_lock_irq (&chip->lock); + + if (rd) { + forte_channel_buffer (&chip->rec, ival & 0xffff, + (ival >> 16) & 0xffff); + ival = (chip->rec.frag_num << 16) + chip->rec.frag_sz; + } + + if (wr) { + forte_channel_buffer (&chip->play, ival & 0xffff, + (ival >> 16) & 0xffff); + ival = (chip->play.frag_num << 16) +chip->play.frag_sz; + } + + spin_unlock_irq (&chip->lock); + + return put_user (ival, p); + + case SNDCTL_DSP_GETISPACE: + DPRINTK ("%s: GETISPACE\n", __FUNCTION__); + + if (!rd) + return -EINVAL; + + spin_lock_irq (&chip->lock); + + abi.fragstotal = chip->rec.frag_num; + abi.fragsize = chip->rec.frag_sz; + + if (chip->rec.mapped) { + abi.fragments = chip->rec.frag_num - 2; + abi.bytes = abi.fragments * abi.fragsize; + } + else { + abi.fragments = chip->rec.filled_frags; + abi.bytes = abi.fragments * abi.fragsize; + } + + spin_unlock_irq (&chip->lock); + + return copy_to_user (argp, &abi, sizeof (abi)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETIPTR: + DPRINTK ("%s: GETIPTR\n", __FUNCTION__); + + if (!rd) + return -EINVAL; + + spin_lock_irq (&chip->lock); + + if (chip->rec.active) + cinfo.ptr = chip->rec.hwptr; + else + cinfo.ptr = 0; + + cinfo.bytes = chip->rec.bytes; + cinfo.blocks = chip->rec.nr_irqs; + chip->rec.nr_irqs = 0; + + spin_unlock_irq (&chip->lock); + + return copy_to_user (argp, &cinfo, sizeof (cinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETOSPACE: + if (!wr) + return -EINVAL; + + spin_lock_irq (&chip->lock); + + abi.fragstotal = chip->play.frag_num; + abi.fragsize = chip->play.frag_sz; + + if (chip->play.mapped) { + abi.fragments = chip->play.frag_num - 2; + abi.bytes = chip->play.buf_sz; + } + else { + abi.fragments = chip->play.frag_num - + chip->play.filled_frags; + + if (chip->play.residue) + abi.fragments--; + + abi.bytes = abi.fragments * abi.fragsize + + chip->play.residue; + } + + spin_unlock_irq (&chip->lock); + + return copy_to_user (argp, &abi, sizeof (abi)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETOPTR: + if (!wr) + return -EINVAL; + + spin_lock_irq (&chip->lock); + + if (chip->play.active) + cinfo.ptr = chip->play.hwptr; + else + cinfo.ptr = 0; + + cinfo.bytes = chip->play.bytes; + cinfo.blocks = chip->play.nr_irqs; + chip->play.nr_irqs = 0; + + spin_unlock_irq (&chip->lock); + + return copy_to_user (argp, &cinfo, sizeof (cinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETODELAY: + if (!wr) + return -EINVAL; + + spin_lock_irq (&chip->lock); + + if (!chip->play.active) { + ival = 0; + } + else if (chip->play.mapped) { + count = inw (chip->play.iobase + FORTE_PLY_COUNT) + 1; + ival = chip->play.frag_sz - count; + } + else { + ival = chip->play.filled_frags * chip->play.frag_sz; + + if (chip->play.residue) + ival += chip->play.frag_sz - chip->play.residue; + } + + spin_unlock_irq (&chip->lock); + + return put_user (ival, p); + + case SNDCTL_DSP_SETDUPLEX: + DPRINTK ("%s: SETDUPLEX\n", __FUNCTION__); + + return -EINVAL; + + case SNDCTL_DSP_GETTRIGGER: + DPRINTK ("%s: GETTRIGGER\n", __FUNCTION__); + + return put_user (chip->trigger, p); + + case SNDCTL_DSP_SETTRIGGER: + + if (get_user (ival, p)) + return -EFAULT; + + DPRINTK ("%s: SETTRIGGER %d\n", __FUNCTION__, ival); + + if (wr) { + spin_lock_irq (&chip->lock); + + if (ival & PCM_ENABLE_OUTPUT) + forte_channel_start (&chip->play); + else { + chip->trigger = 1; + forte_channel_prep (&chip->play); + forte_channel_stop (&chip->play); + } + + spin_unlock_irq (&chip->lock); + } + else if (rd) { + spin_lock_irq (&chip->lock); + + if (ival & PCM_ENABLE_INPUT) + forte_channel_start (&chip->rec); + else { + chip->trigger = 1; + forte_channel_prep (&chip->rec); + forte_channel_stop (&chip->rec); + } + + spin_unlock_irq (&chip->lock); + } + + return 0; + + case SOUND_PCM_READ_RATE: + DPRINTK ("%s: PCM_READ_RATE\n", __FUNCTION__); + return put_user (chip->play.rate, p); + + case SOUND_PCM_READ_CHANNELS: + DPRINTK ("%s: PCM_READ_CHANNELS\n", __FUNCTION__); + return put_user (chip->play.stereo, p); + + case SOUND_PCM_READ_BITS: + DPRINTK ("%s: PCM_READ_BITS\n", __FUNCTION__); + return put_user (chip->play.format, p); + + case SNDCTL_DSP_NONBLOCK: + DPRINTK ("%s: DSP_NONBLOCK\n", __FUNCTION__); + file->f_flags |= O_NONBLOCK; + return 0; + + default: + DPRINTK ("Unsupported ioctl: %x (%p)\n", cmd, argp); + break; + } + + return -EINVAL; +} + + +/** + * forte_dsp_open: + */ + +static int +forte_dsp_open (struct inode *inode, struct file *file) +{ + struct forte_chip *chip = forte; /* FIXME: HACK FROM HELL! */ + + if (file->f_flags & O_NONBLOCK) { + if (down_trylock (&chip->open_sem)) { + DPRINTK ("%s: returning -EAGAIN\n", __FUNCTION__); + return -EAGAIN; + } + } + else { + if (down_interruptible (&chip->open_sem)) { + DPRINTK ("%s: returning -ERESTARTSYS\n", __FUNCTION__); + return -ERESTARTSYS; + } + } + + file->private_data = forte; + + DPRINTK ("%s: dsp opened by %d\n", __FUNCTION__, current->pid); + + if (file->f_mode & FMODE_WRITE) + forte_channel_init (forte, &forte->play); + + if (file->f_mode & FMODE_READ) + forte_channel_init (forte, &forte->rec); + + return nonseekable_open(inode, file); +} + + +/** + * forte_dsp_release: + */ + +static int +forte_dsp_release (struct inode *inode, struct file *file) +{ + struct forte_chip *chip = file->private_data; + int ret = 0; + + DPRINTK ("%s: chip @ %p\n", __FUNCTION__, chip); + + if (file->f_mode & FMODE_WRITE) { + forte_channel_drain (&chip->play); + + spin_lock_irq (&chip->lock); + + forte_channel_free (chip, &chip->play); + + spin_unlock_irq (&chip->lock); + } + + if (file->f_mode & FMODE_READ) { + while (chip->rec.filled_frags > 0) + interruptible_sleep_on (&chip->rec.wait); + + spin_lock_irq (&chip->lock); + + forte_channel_stop (&chip->rec); + forte_channel_free (chip, &chip->rec); + + spin_unlock_irq (&chip->lock); + } + + up (&chip->open_sem); + + return ret; +} + + +/** + * forte_dsp_poll: + * + */ + +static unsigned int +forte_dsp_poll (struct file *file, struct poll_table_struct *wait) +{ + struct forte_chip *chip; + struct forte_channel *channel; + unsigned int mask = 0; + + chip = file->private_data; + + if (file->f_mode & FMODE_WRITE) { + channel = &chip->play; + + if (channel->active) + poll_wait (file, &channel->wait, wait); + + spin_lock_irq (&chip->lock); + + if (channel->frag_num - channel->filled_frags > 0) + mask |= POLLOUT | POLLWRNORM; + + spin_unlock_irq (&chip->lock); + } + + if (file->f_mode & FMODE_READ) { + channel = &chip->rec; + + if (channel->active) + poll_wait (file, &channel->wait, wait); + + spin_lock_irq (&chip->lock); + + if (channel->filled_frags > 0) + mask |= POLLIN | POLLRDNORM; + + spin_unlock_irq (&chip->lock); + } + + return mask; +} + + +/** + * forte_dsp_mmap: + */ + +static int +forte_dsp_mmap (struct file *file, struct vm_area_struct *vma) +{ + struct forte_chip *chip; + struct forte_channel *channel; + unsigned long size; + int ret; + + chip = file->private_data; + + DPRINTK ("%s: start %lXh, size %ld, pgoff %ld\n", __FUNCTION__, + vma->vm_start, vma->vm_end - vma->vm_start, vma->vm_pgoff); + + spin_lock_irq (&chip->lock); + + if (vma->vm_flags & VM_WRITE && chip->play.active) { + ret = -EBUSY; + goto out; + } + + if (vma->vm_flags & VM_READ && chip->rec.active) { + ret = -EBUSY; + goto out; + } + + if (file->f_mode & FMODE_WRITE) + channel = &chip->play; + else if (file->f_mode & FMODE_READ) + channel = &chip->rec; + else { + ret = -EINVAL; + goto out; + } + + forte_channel_prep (channel); + channel->mapped = 1; + + if (vma->vm_pgoff != 0) { + ret = -EINVAL; + goto out; + } + + size = vma->vm_end - vma->vm_start; + + if (size > channel->buf_pages * PAGE_SIZE) { + DPRINTK ("%s: size (%ld) > buf_sz (%d) \n", __FUNCTION__, + size, channel->buf_sz); + ret = -EINVAL; + goto out; + } + + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(channel->buf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) { + DPRINTK ("%s: remap el a no worko\n", __FUNCTION__); + ret = -EAGAIN; + goto out; + } + + ret = 0; + + out: + spin_unlock_irq (&chip->lock); + return ret; +} + + +/** + * forte_dsp_write: + */ + +static ssize_t +forte_dsp_write (struct file *file, const char __user *buffer, size_t bytes, + loff_t *ppos) +{ + struct forte_chip *chip; + struct forte_channel *channel; + unsigned int i = bytes, sz = 0; + unsigned long flags; + + if (!access_ok (VERIFY_READ, buffer, bytes)) + return -EFAULT; + + chip = (struct forte_chip *) file->private_data; + + if (!chip) + BUG(); + + channel = &chip->play; + + if (!channel) + BUG(); + + spin_lock_irqsave (&chip->lock, flags); + + /* Set up buffers with the right fragment size */ + forte_channel_prep (channel); + + while (i) { + /* All fragment buffers in use -> wait */ + if (channel->frag_num - channel->filled_frags == 0) { + DECLARE_WAITQUEUE (wait, current); + + /* For trigger or non-blocking operation, get out */ + if (chip->trigger || file->f_flags & O_NONBLOCK) { + spin_unlock_irqrestore (&chip->lock, flags); + return -EAGAIN; + } + + /* Otherwise wait for buffers */ + add_wait_queue (&channel->wait, &wait); + + for (;;) { + spin_unlock_irqrestore (&chip->lock, flags); + + set_current_state (TASK_INTERRUPTIBLE); + schedule(); + + spin_lock_irqsave (&chip->lock, flags); + + if (channel->frag_num - channel->filled_frags) + break; + } + + remove_wait_queue (&channel->wait, &wait); + set_current_state (TASK_RUNNING); + + if (signal_pending (current)) { + spin_unlock_irqrestore (&chip->lock, flags); + return -ERESTARTSYS; + } + } + + if (channel->residue) + sz = channel->residue; + else if (i > channel->frag_sz) + sz = channel->frag_sz; + else + sz = i; + + spin_unlock_irqrestore (&chip->lock, flags); + + if (copy_from_user ((void *) channel->buf + channel->swptr, buffer, sz)) + return -EFAULT; + + spin_lock_irqsave (&chip->lock, flags); + + /* Advance software pointer */ + buffer += sz; + channel->swptr += sz; + channel->swptr %= channel->buf_sz; + i -= sz; + + /* Only bump filled_frags if a full fragment has been written */ + if (channel->swptr % channel->frag_sz == 0) { + channel->filled_frags++; + channel->residue = 0; + } + else + channel->residue = channel->frag_sz - sz; + + /* If playback isn't active, start it */ + if (channel->active == 0 && chip->trigger == 0) + forte_channel_start (channel); + } + + spin_unlock_irqrestore (&chip->lock, flags); + + return bytes - i; +} + + +/** + * forte_dsp_read: + */ + +static ssize_t +forte_dsp_read (struct file *file, char __user *buffer, size_t bytes, + loff_t *ppos) +{ + struct forte_chip *chip; + struct forte_channel *channel; + unsigned int i = bytes, sz; + unsigned long flags; + + if (!access_ok (VERIFY_WRITE, buffer, bytes)) + return -EFAULT; + + chip = (struct forte_chip *) file->private_data; + + if (!chip) + BUG(); + + channel = &chip->rec; + + if (!channel) + BUG(); + + spin_lock_irqsave (&chip->lock, flags); + + /* Set up buffers with the right fragment size */ + forte_channel_prep (channel); + + /* Start recording */ + if (!chip->trigger) + forte_channel_start (channel); + + while (i) { + /* No fragment buffers in use -> wait */ + if (channel->filled_frags == 0) { + DECLARE_WAITQUEUE (wait, current); + + /* For trigger mode operation, get out */ + if (chip->trigger) { + spin_unlock_irqrestore (&chip->lock, flags); + return -EAGAIN; + } + + add_wait_queue (&channel->wait, &wait); + + for (;;) { + if (channel->active == 0) + break; + + if (channel->filled_frags) + break; + + spin_unlock_irqrestore (&chip->lock, flags); + + set_current_state (TASK_INTERRUPTIBLE); + schedule(); + + spin_lock_irqsave (&chip->lock, flags); + } + + set_current_state (TASK_RUNNING); + remove_wait_queue (&channel->wait, &wait); + } + + if (i > channel->frag_sz) + sz = channel->frag_sz; + else + sz = i; + + spin_unlock_irqrestore (&chip->lock, flags); + + if (copy_to_user (buffer, (void *)channel->buf+channel->swptr, sz)) { + DPRINTK ("%s: copy_to_user failed\n", __FUNCTION__); + return -EFAULT; + } + + spin_lock_irqsave (&chip->lock, flags); + + /* Advance software pointer */ + buffer += sz; + if (channel->filled_frags > 0) + channel->filled_frags--; + channel->swptr += channel->frag_sz; + channel->swptr %= channel->buf_sz; + i -= sz; + } + + spin_unlock_irqrestore (&chip->lock, flags); + + return bytes - i; +} + + +static struct file_operations forte_dsp_fops = { + .owner = THIS_MODULE, + .llseek = &no_llseek, + .read = &forte_dsp_read, + .write = &forte_dsp_write, + .poll = &forte_dsp_poll, + .ioctl = &forte_dsp_ioctl, + .open = &forte_dsp_open, + .release = &forte_dsp_release, + .mmap = &forte_dsp_mmap, +}; + + +/* Common ------------------------------------------------------------------ */ + + +/** + * forte_interrupt: + */ + +static irqreturn_t +forte_interrupt (int irq, void *dev_id, struct pt_regs *regs) +{ + struct forte_chip *chip = dev_id; + struct forte_channel *channel = NULL; + u16 status, count; + + status = inw (chip->iobase + FORTE_IRQ_STATUS); + + /* If this is not for us, get outta here ASAP */ + if ((status & (FORTE_IRQ_PLAYBACK | FORTE_IRQ_CAPTURE)) == 0) + return IRQ_NONE; + + if (status & FORTE_IRQ_PLAYBACK) { + channel = &chip->play; + + spin_lock (&chip->lock); + + if (channel->frag_sz == 0) + goto pack; + + /* Declare a fragment done */ + if (channel->filled_frags > 0) + channel->filled_frags--; + channel->bytes += channel->frag_sz; + channel->nr_irqs++; + + /* Flip-flop between buffer I and II */ + channel->next_buf ^= 1; + + /* Advance hardware pointer by fragment size and wrap around */ + channel->hwptr += channel->frag_sz; + channel->hwptr %= channel->buf_sz; + + /* Buffer I or buffer II BAR */ + outl (channel->buf_handle + channel->hwptr, + channel->next_buf == 0 ? + channel->iobase + FORTE_PLY_BUF1 : + channel->iobase + FORTE_PLY_BUF2); + + /* If the currently playing fragment is last, schedule pause */ + if (channel->filled_frags == 1) + forte_channel_pause (channel); + + pack: + /* Acknowledge interrupt */ + outw (FORTE_IRQ_PLAYBACK, chip->iobase + FORTE_IRQ_STATUS); + + if (waitqueue_active (&channel->wait)) + wake_up_all (&channel->wait); + + spin_unlock (&chip->lock); + } + + if (status & FORTE_IRQ_CAPTURE) { + channel = &chip->rec; + spin_lock (&chip->lock); + + /* One fragment filled */ + channel->filled_frags++; + + /* Get # of completed bytes */ + count = inw (channel->iobase + FORTE_PLY_COUNT) + 1; + + if (count == 0) { + DPRINTK ("%s: last, filled_frags = %d\n", __FUNCTION__, + channel->filled_frags); + channel->filled_frags = 0; + goto rack; + } + + /* Buffer I or buffer II BAR */ + outl (channel->buf_handle + channel->hwptr, + channel->next_buf == 0 ? + channel->iobase + FORTE_PLY_BUF1 : + channel->iobase + FORTE_PLY_BUF2); + + /* Flip-flop between buffer I and II */ + channel->next_buf ^= 1; + + /* Advance hardware pointer by fragment size and wrap around */ + channel->hwptr += channel->frag_sz; + channel->hwptr %= channel->buf_sz; + + /* Out of buffers */ + if (channel->filled_frags == channel->frag_num - 1) + forte_channel_stop (channel); + rack: + /* Acknowledge interrupt */ + outw (FORTE_IRQ_CAPTURE, chip->iobase + FORTE_IRQ_STATUS); + + spin_unlock (&chip->lock); + + if (waitqueue_active (&channel->wait)) + wake_up_all (&channel->wait); + } + + return IRQ_HANDLED; +} + + +/** + * forte_proc_read: + */ + +static int +forte_proc_read (char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + int i = 0, p_rate, p_chan, r_rate; + unsigned short p_reg, r_reg; + + i += sprintf (page, "ForteMedia FM801 OSS Lite driver\n%s\n \n", + DRIVER_VERSION); + + if (!forte->iobase) + return i; + + p_rate = p_chan = -1; + p_reg = inw (forte->iobase + FORTE_PLY_CTRL); + p_rate = (p_reg >> 8) & 15; + p_chan = (p_reg >> 12) & 3; + + if (p_rate >= 0 || p_rate <= 10) + p_rate = rates[p_rate]; + + if (p_chan >= 0 || p_chan <= 2) + p_chan = channels[p_chan]; + + r_rate = -1; + r_reg = inw (forte->iobase + FORTE_CAP_CTRL); + r_rate = (r_reg >> 8) & 15; + + if (r_rate >= 0 || r_rate <= 10) + r_rate = rates[r_rate]; + + i += sprintf (page + i, + " Playback Capture\n" + "FIFO empty : %-3s %-3s\n" + "Buf1 Last : %-3s %-3s\n" + "Buf2 Last : %-3s %-3s\n" + "Started : %-3s %-3s\n" + "Paused : %-3s %-3s\n" + "Immed Stop : %-3s %-3s\n" + "Rate : %-5d %-5d\n" + "Channels : %-5d -\n" + "16-bit : %-3s %-3s\n" + "Stereo : %-3s %-3s\n" + " \n" + "Buffer Sz : %-6d %-6d\n" + "Frag Sz : %-6d %-6d\n" + "Frag Num : %-6d %-6d\n" + "Frag msecs : %-6d %-6d\n" + "Used Frags : %-6d %-6d\n" + "Mapped : %-3s %-3s\n", + p_reg & 1<<0 ? "yes" : "no", + r_reg & 1<<0 ? "yes" : "no", + p_reg & 1<<1 ? "yes" : "no", + r_reg & 1<<1 ? "yes" : "no", + p_reg & 1<<2 ? "yes" : "no", + r_reg & 1<<2 ? "yes" : "no", + p_reg & 1<<5 ? "yes" : "no", + r_reg & 1<<5 ? "yes" : "no", + p_reg & 1<<6 ? "yes" : "no", + r_reg & 1<<6 ? "yes" : "no", + p_reg & 1<<7 ? "yes" : "no", + r_reg & 1<<7 ? "yes" : "no", + p_rate, r_rate, + p_chan, + p_reg & 1<<14 ? "yes" : "no", + r_reg & 1<<14 ? "yes" : "no", + p_reg & 1<<15 ? "yes" : "no", + r_reg & 1<<15 ? "yes" : "no", + forte->play.buf_sz, forte->rec.buf_sz, + forte->play.frag_sz, forte->rec.frag_sz, + forte->play.frag_num, forte->rec.frag_num, + forte->play.frag_msecs, forte->rec.frag_msecs, + forte->play.filled_frags, forte->rec.filled_frags, + forte->play.mapped ? "yes" : "no", + forte->rec.mapped ? "yes" : "no" + ); + + return i; +} + + +/** + * forte_proc_init: + * + * Creates driver info entries in /proc + */ + +static int __init +forte_proc_init (void) +{ + if (!proc_mkdir ("driver/forte", NULL)) + return -EIO; + + if (!create_proc_read_entry ("driver/forte/chip", 0, NULL, forte_proc_read, forte)) { + remove_proc_entry ("driver/forte", NULL); + return -EIO; + } + + if (!create_proc_read_entry("driver/forte/ac97", 0, NULL, ac97_read_proc, forte->ac97)) { + remove_proc_entry ("driver/forte/chip", NULL); + remove_proc_entry ("driver/forte", NULL); + return -EIO; + } + + return 0; +} + + +/** + * forte_proc_remove: + * + * Removes driver info entries in /proc + */ + +static void +forte_proc_remove (void) +{ + remove_proc_entry ("driver/forte/ac97", NULL); + remove_proc_entry ("driver/forte/chip", NULL); + remove_proc_entry ("driver/forte", NULL); +} + + +/** + * forte_chip_init: + * @chip: Chip instance to initialize + * + * Description: + * Resets chip, configures codec and registers the driver with + * the sound subsystem. + * + * Press and hold Start for 8 secs, then switch on Run + * and hold for 4 seconds. Let go of Start. Numbers + * assume a properly oiled TWG. + */ + +static int __devinit +forte_chip_init (struct forte_chip *chip) +{ + u8 revision; + u16 cmdw; + struct ac97_codec *codec; + + pci_read_config_byte (chip->pci_dev, PCI_REVISION_ID, &revision); + + if (revision >= 0xB1) { + chip->multichannel = 1; + printk (KERN_INFO PFX "Multi-channel device detected.\n"); + } + + /* Reset chip */ + outw (FORTE_CC_CODEC_RESET | FORTE_CC_AC97_RESET, + chip->iobase + FORTE_CODEC_CTRL); + udelay(100); + outw (0, chip->iobase + FORTE_CODEC_CTRL); + + /* Request read from AC97 */ + outw (FORTE_AC97_READ | (0 << FORTE_AC97_ADDR_SHIFT), + chip->iobase + FORTE_AC97_CMD); + mdelay(750); + + if ((inw (chip->iobase + FORTE_AC97_CMD) & (3<<8)) != (1<<8)) { + printk (KERN_INFO PFX "AC97 codec not responding"); + return -EIO; + } + + /* Init volume */ + outw (0x0808, chip->iobase + FORTE_PCM_VOL); + outw (0x9f1f, chip->iobase + FORTE_FM_VOL); + outw (0x8808, chip->iobase + FORTE_I2S_VOL); + + /* I2S control - I2S mode */ + outw (0x0003, chip->iobase + FORTE_I2S_MODE); + + /* Interrupt setup - unmask PLAYBACK & CAPTURE */ + cmdw = inw (chip->iobase + FORTE_IRQ_MASK); + cmdw &= ~0x0003; + outw (cmdw, chip->iobase + FORTE_IRQ_MASK); + + /* Interrupt clear */ + outw (FORTE_IRQ_PLAYBACK|FORTE_IRQ_CAPTURE, + chip->iobase + FORTE_IRQ_STATUS); + + /* Set up the AC97 codec */ + if ((codec = ac97_alloc_codec()) == NULL) + return -ENOMEM; + codec->private_data = chip; + codec->codec_read = forte_ac97_read; + codec->codec_write = forte_ac97_write; + codec->id = 0; + + if (ac97_probe_codec (codec) == 0) { + printk (KERN_ERR PFX "codec probe failed\n"); + ac97_release_codec(codec); + return -1; + } + + /* Register mixer */ + if ((codec->dev_mixer = + register_sound_mixer (&forte_mixer_fops, -1)) < 0) { + printk (KERN_ERR PFX "couldn't register mixer!\n"); + ac97_release_codec(codec); + return -1; + } + + chip->ac97 = codec; + + /* Register DSP */ + if ((chip->dsp = register_sound_dsp (&forte_dsp_fops, -1) ) < 0) { + printk (KERN_ERR PFX "couldn't register dsp!\n"); + return -1; + } + + /* Register with /proc */ + if (forte_proc_init()) { + printk (KERN_ERR PFX "couldn't add entries to /proc!\n"); + return -1; + } + + return 0; +} + + +/** + * forte_probe: + * @pci_dev: PCI struct for probed device + * @pci_id: + * + * Description: + * Allocates chip instance, I/O region, and IRQ + */ +static int __init +forte_probe (struct pci_dev *pci_dev, const struct pci_device_id *pci_id) +{ + struct forte_chip *chip; + int ret = 0; + + /* FIXME: Support more than one chip */ + if (found++) + return -EIO; + + /* Ignition */ + if (pci_enable_device (pci_dev)) + return -EIO; + + pci_set_master (pci_dev); + + /* Allocate chip instance and configure */ + forte = (struct forte_chip *) + kmalloc (sizeof (struct forte_chip), GFP_KERNEL); + chip = forte; + + if (chip == NULL) { + printk (KERN_WARNING PFX "Out of memory"); + return -ENOMEM; + } + + memset (chip, 0, sizeof (struct forte_chip)); + chip->pci_dev = pci_dev; + + init_MUTEX(&chip->open_sem); + spin_lock_init (&chip->lock); + spin_lock_init (&chip->ac97_lock); + + if (! request_region (pci_resource_start (pci_dev, 0), + pci_resource_len (pci_dev, 0), DRIVER_NAME)) { + printk (KERN_WARNING PFX "Unable to reserve I/O space"); + ret = -ENOMEM; + goto error; + } + + chip->iobase = pci_resource_start (pci_dev, 0); + chip->irq = pci_dev->irq; + + if (request_irq (chip->irq, forte_interrupt, SA_SHIRQ, DRIVER_NAME, + chip)) { + printk (KERN_WARNING PFX "Unable to reserve IRQ"); + ret = -EIO; + goto error; + } + + pci_set_drvdata (pci_dev, chip); + + printk (KERN_INFO PFX "FM801 chip found at 0x%04lX-0x%04lX IRQ %u\n", + chip->iobase, pci_resource_end (pci_dev, 0), chip->irq); + + /* Power it up */ + if ((ret = forte_chip_init (chip)) == 0) + return 0; + + error: + if (chip->irq) + free_irq (chip->irq, chip); + + if (chip->iobase) + release_region (pci_resource_start (pci_dev, 0), + pci_resource_len (pci_dev, 0)); + + kfree (chip); + + return ret; +} + + +/** + * forte_remove: + * @pci_dev: PCI device to unclaim + * + */ + +static void +forte_remove (struct pci_dev *pci_dev) +{ + struct forte_chip *chip = pci_get_drvdata (pci_dev); + + if (chip == NULL) + return; + + /* Turn volume down to avoid popping */ + outw (0x1f1f, chip->iobase + FORTE_PCM_VOL); + outw (0x1f1f, chip->iobase + FORTE_FM_VOL); + outw (0x1f1f, chip->iobase + FORTE_I2S_VOL); + + forte_proc_remove(); + free_irq (chip->irq, chip); + release_region (chip->iobase, pci_resource_len (pci_dev, 0)); + + unregister_sound_dsp (chip->dsp); + unregister_sound_mixer (chip->ac97->dev_mixer); + ac97_release_codec(chip->ac97); + kfree (chip); + + printk (KERN_INFO PFX "driver released\n"); +} + + +static struct pci_device_id forte_pci_ids[] = { + { 0x1319, 0x0801, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, }, + { 0, } +}; + + +static struct pci_driver forte_pci_driver = { + .name = DRIVER_NAME, + .id_table = forte_pci_ids, + .probe = forte_probe, + .remove = forte_remove, + +}; + + +/** + * forte_init_module: + * + */ + +static int __init +forte_init_module (void) +{ + printk (KERN_INFO PFX DRIVER_VERSION "\n"); + + return pci_register_driver (&forte_pci_driver); +} + + +/** + * forte_cleanup_module: + * + */ + +static void __exit +forte_cleanup_module (void) +{ + pci_unregister_driver (&forte_pci_driver); +} + + +module_init(forte_init_module); +module_exit(forte_cleanup_module); + +MODULE_AUTHOR("Martin K. Petersen "); +MODULE_DESCRIPTION("ForteMedia FM801 OSS Driver"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE (pci, forte_pci_ids); diff --git a/sound/oss/gus.h b/sound/oss/gus.h new file mode 100644 index 000000000000..3d5271baf042 --- /dev/null +++ b/sound/oss/gus.h @@ -0,0 +1,24 @@ + +#include "ad1848.h" + +/* From gus_card.c */ +int gus_set_midi_irq(int num); +irqreturn_t gusintr(int irq, void *dev_id, struct pt_regs * dummy); + +/* From gus_wave.c */ +int gus_wave_detect(int baseaddr); +void gus_wave_init(struct address_info *hw_config); +void gus_wave_unload (struct address_info *hw_config); +void gus_voice_irq(void); +void gus_write8(int reg, unsigned int data); +void guswave_dma_irq(void); +void gus_delay(void); +int gus_default_mixer_ioctl (int dev, unsigned int cmd, void __user *arg); +void gus_timer_command (unsigned int addr, unsigned int val); + +/* From gus_midi.c */ +void gus_midi_init(struct address_info *hw_config); +void gus_midi_interrupt(int dummy); + +/* From ics2101.c */ +int ics2101_mixer_init(void); diff --git a/sound/oss/gus_card.c b/sound/oss/gus_card.c new file mode 100644 index 000000000000..dbb29771e2bb --- /dev/null +++ b/sound/oss/gus_card.c @@ -0,0 +1,293 @@ +/* + * sound/gus_card.c + * + * Detection routine for the Gravis Ultrasound. + * + * Copyright (C) by Hannu Savolainen 1993-1997 + * + * + * Frank van de Pol : Fixed GUS MAX interrupt handling, enabled simultanious + * usage of CS4231A codec, GUS wave and MIDI for GUS MAX. + * Christoph Hellwig: Adapted to module_init/module_exit, simple cleanups. + * + * Status: + * Tested... + */ + + +#include +#include +#include +#include + +#include "sound_config.h" + +#include "gus.h" +#include "gus_hw.h" + +irqreturn_t gusintr(int irq, void *dev_id, struct pt_regs *dummy); + +int gus_base = 0, gus_irq = 0, gus_dma = 0; +int gus_no_wave_dma = 0; +extern int gus_wave_volume; +extern int gus_pcm_volume; +extern int have_gus_max; +int gus_pnp_flag = 0; +#ifdef CONFIG_SOUND_GUS16 +static int db16; /* Has a Gus16 AD1848 on it */ +#endif + +static void __init attach_gus(struct address_info *hw_config) +{ + gus_wave_init(hw_config); + + if (sound_alloc_dma(hw_config->dma, "GUS")) + printk(KERN_ERR "gus_card.c: Can't allocate DMA channel %d\n", hw_config->dma); + if (hw_config->dma2 != -1 && hw_config->dma2 != hw_config->dma) + if (sound_alloc_dma(hw_config->dma2, "GUS(2)")) + printk(KERN_ERR "gus_card.c: Can't allocate DMA channel %d\n", hw_config->dma2); + gus_midi_init(hw_config); + if(request_irq(hw_config->irq, gusintr, 0, "Gravis Ultrasound", hw_config)<0) + printk(KERN_ERR "gus_card.c: Unable to allocate IRQ %d\n", hw_config->irq); + + return; +} + +static int __init probe_gus(struct address_info *hw_config) +{ + int irq; + int io_addr; + + if (hw_config->card_subtype == 1) + gus_pnp_flag = 1; + + irq = hw_config->irq; + + if (hw_config->card_subtype == 0) /* GUS/MAX/ACE */ + if (irq != 3 && irq != 5 && irq != 7 && irq != 9 && + irq != 11 && irq != 12 && irq != 15) + { + printk(KERN_ERR "GUS: Unsupported IRQ %d\n", irq); + return 0; + } + if (gus_wave_detect(hw_config->io_base)) + return 1; + +#ifndef EXCLUDE_GUS_IODETECT + + /* + * Look at the possible base addresses (0x2X0, X=1, 2, 3, 4, 5, 6) + */ + + for (io_addr = 0x210; io_addr <= 0x260; io_addr += 0x10) { + if (io_addr == hw_config->io_base) /* Already tested */ + continue; + if (gus_wave_detect(io_addr)) { + hw_config->io_base = io_addr; + return 1; + } + } +#endif + + printk("NO GUS card found !\n"); + return 0; +} + +static void __exit unload_gus(struct address_info *hw_config) +{ + DDB(printk("unload_gus(%x)\n", hw_config->io_base)); + + gus_wave_unload(hw_config); + + release_region(hw_config->io_base, 16); + release_region(hw_config->io_base + 0x100, 12); /* 0x10c-> is MAX */ + free_irq(hw_config->irq, hw_config); + + sound_free_dma(hw_config->dma); + + if (hw_config->dma2 != -1 && hw_config->dma2 != hw_config->dma) + sound_free_dma(hw_config->dma2); +} + +irqreturn_t gusintr(int irq, void *dev_id, struct pt_regs *dummy) +{ + unsigned char src; + extern int gus_timer_enabled; + int handled = 0; + +#ifdef CONFIG_SOUND_GUSMAX + if (have_gus_max) { + struct address_info *hw_config = dev_id; + adintr(irq, (void *)hw_config->slots[1], NULL); + } +#endif +#ifdef CONFIG_SOUND_GUS16 + if (db16) { + struct address_info *hw_config = dev_id; + adintr(irq, (void *)hw_config->slots[3], NULL); + } +#endif + + while (1) + { + if (!(src = inb(u_IrqStatus))) + break; + handled = 1; + if (src & DMA_TC_IRQ) + { + guswave_dma_irq(); + } + if (src & (MIDI_TX_IRQ | MIDI_RX_IRQ)) + { + gus_midi_interrupt(0); + } + if (src & (GF1_TIMER1_IRQ | GF1_TIMER2_IRQ)) + { + if (gus_timer_enabled) + sound_timer_interrupt(); + gus_write8(0x45, 0); /* Ack IRQ */ + gus_timer_command(4, 0x80); /* Reset IRQ flags */ + } + if (src & (WAVETABLE_IRQ | ENVELOPE_IRQ)) + gus_voice_irq(); + } + return IRQ_RETVAL(handled); +} + +/* + * Some extra code for the 16 bit sampling option + */ + +#ifdef CONFIG_SOUND_GUS16 + +static int __init init_gus_db16(struct address_info *hw_config) +{ + struct resource *ports; + + ports = request_region(hw_config->io_base, 4, "ad1848"); + if (!ports) + return 0; + + if (!ad1848_detect(ports, NULL, hw_config->osp)) { + release_region(hw_config->io_base, 4); + return 0; + } + + gus_pcm_volume = 100; + gus_wave_volume = 90; + + hw_config->slots[3] = ad1848_init("GUS 16 bit sampling", ports, + hw_config->irq, + hw_config->dma, + hw_config->dma, 0, + hw_config->osp, + THIS_MODULE); + return 1; +} + +static void __exit unload_gus_db16(struct address_info *hw_config) +{ + + ad1848_unload(hw_config->io_base, + hw_config->irq, + hw_config->dma, + hw_config->dma, 0); + sound_unload_audiodev(hw_config->slots[3]); +} +#endif + +#ifdef CONFIG_SOUND_GUS16 +static int gus16; +#endif +#ifdef CONFIG_SOUND_GUSMAX +static int no_wave_dma; /* Set if no dma is to be used for the + wave table (GF1 chip) */ +#endif + + +/* + * Note DMA2 of -1 has the right meaning in the GUS driver as well + * as here. + */ + +static struct address_info cfg; + +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 type = 0; /* 1 for PnP */ + +module_param(io, int, 0); +module_param(irq, int, 0); +module_param(dma, int, 0); +module_param(dma16, int, 0); +module_param(type, int, 0); +#ifdef CONFIG_SOUND_GUSMAX +module_param(no_wave_dma, int, 0); +#endif +#ifdef CONFIG_SOUND_GUS16 +module_param(db16, int, 0); +module_param(gus16, int, 0); +#endif +MODULE_LICENSE("GPL"); + +static int __init init_gus(void) +{ + printk(KERN_INFO "Gravis Ultrasound audio driver Copyright (C) by Hannu Savolainen 1993-1996\n"); + + cfg.io_base = io; + cfg.irq = irq; + cfg.dma = dma; + cfg.dma2 = dma16; + cfg.card_subtype = type; +#ifdef CONFIG_SOUND_GUSMAX + gus_no_wave_dma = no_wave_dma; +#endif + + if (cfg.io_base == -1 || cfg.dma == -1 || cfg.irq == -1) { + printk(KERN_ERR "I/O, IRQ, and DMA are mandatory\n"); + return -EINVAL; + } + +#ifdef CONFIG_SOUND_GUS16 + if (gus16 && init_gus_db16(&cfg)) + db16 = 1; +#endif + if (!probe_gus(&cfg)) + return -ENODEV; + attach_gus(&cfg); + + return 0; +} + +static void __exit cleanup_gus(void) +{ +#ifdef CONFIG_SOUND_GUS16 + if (db16) + unload_gus_db16(&cfg); +#endif + unload_gus(&cfg); +} + +module_init(init_gus); +module_exit(cleanup_gus); + +#ifndef MODULE +static int __init setup_gus(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]; + dma16 = ints[4]; + + return 1; +} + +__setup("gus=", setup_gus); +#endif diff --git a/sound/oss/gus_hw.h b/sound/oss/gus_hw.h new file mode 100644 index 000000000000..f97a0b8670e3 --- /dev/null +++ b/sound/oss/gus_hw.h @@ -0,0 +1,50 @@ + +/* + * I/O addresses + */ + +#define u_Base (gus_base + 0x000) +#define u_Mixer u_Base +#define u_Status (gus_base + 0x006) +#define u_TimerControl (gus_base + 0x008) +#define u_TimerData (gus_base + 0x009) +#define u_IRQDMAControl (gus_base + 0x00b) +#define u_MidiControl (gus_base + 0x100) +#define MIDI_RESET 0x03 +#define MIDI_ENABLE_XMIT 0x20 +#define MIDI_ENABLE_RCV 0x80 +#define u_MidiStatus u_MidiControl +#define MIDI_RCV_FULL 0x01 +#define MIDI_XMIT_EMPTY 0x02 +#define MIDI_FRAME_ERR 0x10 +#define MIDI_OVERRUN 0x20 +#define MIDI_IRQ_PEND 0x80 +#define u_MidiData (gus_base + 0x101) +#define u_Voice (gus_base + 0x102) +#define u_Command (gus_base + 0x103) +#define u_DataLo (gus_base + 0x104) +#define u_DataHi (gus_base + 0x105) +#define u_MixData (gus_base + 0x106) /* Rev. 3.7+ mixing */ +#define u_MixSelect (gus_base + 0x506) /* registers. */ +#define u_IrqStatus u_Status +# define MIDI_TX_IRQ 0x01 /* pending MIDI xmit IRQ */ +# define MIDI_RX_IRQ 0x02 /* pending MIDI recv IRQ */ +# define GF1_TIMER1_IRQ 0x04 /* general purpose timer */ +# define GF1_TIMER2_IRQ 0x08 /* general purpose timer */ +# define WAVETABLE_IRQ 0x20 /* pending wavetable IRQ */ +# define ENVELOPE_IRQ 0x40 /* pending volume envelope IRQ */ +# define DMA_TC_IRQ 0x80 /* pending dma tc IRQ */ + +#define ICS2101 1 +# define ICS_MIXDEVS 6 +# define DEV_MIC 0 +# define DEV_LINE 1 +# define DEV_CD 2 +# define DEV_GF1 3 +# define DEV_UNUSED 4 +# define DEV_VOL 5 + +# define CHN_LEFT 0 +# define CHN_RIGHT 1 +#define CS4231 2 +#define u_DRAMIO (gus_base + 0x107) diff --git a/sound/oss/gus_linearvol.h b/sound/oss/gus_linearvol.h new file mode 100644 index 000000000000..7ad0c30d4fd9 --- /dev/null +++ b/sound/oss/gus_linearvol.h @@ -0,0 +1,18 @@ +static unsigned short gus_linearvol[128] = { + 0x0000, 0x08ff, 0x09ff, 0x0a80, 0x0aff, 0x0b40, 0x0b80, 0x0bc0, + 0x0bff, 0x0c20, 0x0c40, 0x0c60, 0x0c80, 0x0ca0, 0x0cc0, 0x0ce0, + 0x0cff, 0x0d10, 0x0d20, 0x0d30, 0x0d40, 0x0d50, 0x0d60, 0x0d70, + 0x0d80, 0x0d90, 0x0da0, 0x0db0, 0x0dc0, 0x0dd0, 0x0de0, 0x0df0, + 0x0dff, 0x0e08, 0x0e10, 0x0e18, 0x0e20, 0x0e28, 0x0e30, 0x0e38, + 0x0e40, 0x0e48, 0x0e50, 0x0e58, 0x0e60, 0x0e68, 0x0e70, 0x0e78, + 0x0e80, 0x0e88, 0x0e90, 0x0e98, 0x0ea0, 0x0ea8, 0x0eb0, 0x0eb8, + 0x0ec0, 0x0ec8, 0x0ed0, 0x0ed8, 0x0ee0, 0x0ee8, 0x0ef0, 0x0ef8, + 0x0eff, 0x0f04, 0x0f08, 0x0f0c, 0x0f10, 0x0f14, 0x0f18, 0x0f1c, + 0x0f20, 0x0f24, 0x0f28, 0x0f2c, 0x0f30, 0x0f34, 0x0f38, 0x0f3c, + 0x0f40, 0x0f44, 0x0f48, 0x0f4c, 0x0f50, 0x0f54, 0x0f58, 0x0f5c, + 0x0f60, 0x0f64, 0x0f68, 0x0f6c, 0x0f70, 0x0f74, 0x0f78, 0x0f7c, + 0x0f80, 0x0f84, 0x0f88, 0x0f8c, 0x0f90, 0x0f94, 0x0f98, 0x0f9c, + 0x0fa0, 0x0fa4, 0x0fa8, 0x0fac, 0x0fb0, 0x0fb4, 0x0fb8, 0x0fbc, + 0x0fc0, 0x0fc4, 0x0fc8, 0x0fcc, 0x0fd0, 0x0fd4, 0x0fd8, 0x0fdc, + 0x0fe0, 0x0fe4, 0x0fe8, 0x0fec, 0x0ff0, 0x0ff4, 0x0ff8, 0x0ffc +}; diff --git a/sound/oss/gus_midi.c b/sound/oss/gus_midi.c new file mode 100644 index 000000000000..b48f57c24e48 --- /dev/null +++ b/sound/oss/gus_midi.c @@ -0,0 +1,256 @@ +/* + * sound/gus2_midi.c + * + * The low level driver for the GUS 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. + * + * Changes: + * 11-10-2000 Bartlomiej Zolnierkiewicz + * Added __init to gus_midi_init() + */ + +#include +#include +#include "sound_config.h" + +#include "gus.h" +#include "gus_hw.h" + +static int midi_busy, input_opened; +static int my_dev; +static int output_used; +static volatile unsigned char gus_midi_control; +static void (*midi_input_intr) (int dev, unsigned char data); + +static unsigned char tmp_queue[256]; +extern int gus_pnp_flag; +static volatile int qlen; +static volatile unsigned char qhead, qtail; +extern int gus_base, gus_irq, gus_dma; +extern int *gus_osp; +extern spinlock_t gus_lock; + +static int GUS_MIDI_STATUS(void) +{ + return inb(u_MidiStatus); +} + +static int gus_midi_open(int dev, int mode, void (*input) (int dev, unsigned char data), void (*output) (int dev)) +{ + if (midi_busy) + { +/* printk("GUS: Midi busy\n");*/ + return -EBUSY; + } + outb((MIDI_RESET), u_MidiControl); + gus_delay(); + + gus_midi_control = 0; + input_opened = 0; + + if (mode == OPEN_READ || mode == OPEN_READWRITE) + if (!gus_pnp_flag) + { + gus_midi_control |= MIDI_ENABLE_RCV; + input_opened = 1; + } + outb((gus_midi_control), u_MidiControl); /* Enable */ + + midi_busy = 1; + qlen = qhead = qtail = output_used = 0; + midi_input_intr = input; + + return 0; +} + +static int dump_to_midi(unsigned char midi_byte) +{ + unsigned long flags; + int ok = 0; + + output_used = 1; + + spin_lock_irqsave(&gus_lock, flags); + + if (GUS_MIDI_STATUS() & MIDI_XMIT_EMPTY) + { + ok = 1; + outb((midi_byte), u_MidiData); + } + else + { + /* + * Enable Midi xmit interrupts (again) + */ + gus_midi_control |= MIDI_ENABLE_XMIT; + outb((gus_midi_control), u_MidiControl); + } + + spin_unlock_irqrestore(&gus_lock,flags); + return ok; +} + +static void gus_midi_close(int dev) +{ + /* + * Reset FIFO pointers, disable intrs + */ + + outb((MIDI_RESET), u_MidiControl); + midi_busy = 0; +} + +static int gus_midi_out(int dev, unsigned char midi_byte) +{ + unsigned long flags; + + /* + * Drain the local queue first + */ + spin_lock_irqsave(&gus_lock, flags); + + while (qlen && dump_to_midi(tmp_queue[qhead])) + { + qlen--; + qhead++; + } + spin_unlock_irqrestore(&gus_lock,flags); + + /* + * Output the byte if the local queue is empty. + */ + + if (!qlen) + if (dump_to_midi(midi_byte)) + return 1; /* + * OK + */ + + /* + * Put to the local queue + */ + + if (qlen >= 256) + return 0; /* + * Local queue full + */ + spin_lock_irqsave(&gus_lock, flags); + + tmp_queue[qtail] = midi_byte; + qlen++; + qtail++; + + spin_unlock_irqrestore(&gus_lock,flags); + return 1; +} + +static int gus_midi_start_read(int dev) +{ + return 0; +} + +static int gus_midi_end_read(int dev) +{ + return 0; +} + +static void gus_midi_kick(int dev) +{ +} + +static int gus_midi_buffer_status(int dev) +{ + unsigned long flags; + + if (!output_used) + return 0; + + spin_lock_irqsave(&gus_lock, flags); + + if (qlen && dump_to_midi(tmp_queue[qhead])) + { + qlen--; + qhead++; + } + spin_unlock_irqrestore(&gus_lock,flags); + return (qlen > 0) || !(GUS_MIDI_STATUS() & MIDI_XMIT_EMPTY); +} + +#define MIDI_SYNTH_NAME "Gravis Ultrasound Midi" +#define MIDI_SYNTH_CAPS SYNTH_CAP_INPUT +#include "midi_synth.h" + +static struct midi_operations gus_midi_operations = +{ + .owner = THIS_MODULE, + .info = {"Gravis UltraSound Midi", 0, 0, SNDCARD_GUS}, + .converter = &std_midi_synth, + .in_info = {0}, + .open = gus_midi_open, + .close = gus_midi_close, + .outputc = gus_midi_out, + .start_read = gus_midi_start_read, + .end_read = gus_midi_end_read, + .kick = gus_midi_kick, + .buffer_status = gus_midi_buffer_status, +}; + +void __init gus_midi_init(struct address_info *hw_config) +{ + int dev = sound_alloc_mididev(); + + if (dev == -1) + { + printk(KERN_INFO "gus_midi: Too many midi devices detected\n"); + return; + } + outb((MIDI_RESET), u_MidiControl); + + std_midi_synth.midi_dev = my_dev = dev; + hw_config->slots[2] = dev; + midi_devs[dev] = &gus_midi_operations; + sequencer_init(); + return; +} + +void gus_midi_interrupt(int dummy) +{ + volatile unsigned char stat, data; + int timeout = 10; + + spin_lock(&gus_lock); + + while (timeout-- > 0 && (stat = GUS_MIDI_STATUS()) & (MIDI_RCV_FULL | MIDI_XMIT_EMPTY)) + { + if (stat & MIDI_RCV_FULL) + { + data = inb(u_MidiData); + if (input_opened) + midi_input_intr(my_dev, data); + } + if (stat & MIDI_XMIT_EMPTY) + { + while (qlen && dump_to_midi(tmp_queue[qhead])) + { + qlen--; + qhead++; + } + if (!qlen) + { + /* + * Disable Midi output interrupts, since no data in the buffer + */ + gus_midi_control &= ~MIDI_ENABLE_XMIT; + outb((gus_midi_control), u_MidiControl); + outb((gus_midi_control), u_MidiControl); + } + } + } + spin_unlock(&gus_lock); +} diff --git a/sound/oss/gus_vol.c b/sound/oss/gus_vol.c new file mode 100644 index 000000000000..6ae6924e1647 --- /dev/null +++ b/sound/oss/gus_vol.c @@ -0,0 +1,153 @@ + +/* + * gus_vol.c - Compute volume for GUS. + * + * + * 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 "sound_config.h" + +#include "gus.h" +#include "gus_linearvol.h" + +#define GUS_VOLUME gus_wave_volume + + +extern int gus_wave_volume; + +/* + * Calculate gus volume from note velocity, main volume, expression, and + * intrinsic patch volume given in patch library. Expression is multiplied + * in, so it emphasizes differences in note velocity, while main volume is + * added in -- I don't know whether this is right, but it seems reasonable to + * me. (In the previous stage, main volume controller messages were changed + * to expression controller messages, if they were found to be used for + * dynamic volume adjustments, so here, main volume can be assumed to be + * constant throughout a song.) + * + * Intrinsic patch volume is added in, but if over 64 is also multiplied in, so + * we can give a big boost to very weak voices like nylon guitar and the + * basses. The normal value is 64. Strings are assigned lower values. + */ + +unsigned short gus_adagio_vol(int vel, int mainv, int xpn, int voicev) +{ + int i, m, n, x; + + + /* + * A voice volume of 64 is considered neutral, so adjust the main volume if + * something other than this neutral value was assigned in the patch + * library. + */ + x = 256 + 6 * (voicev - 64); + + /* + * Boost expression by voice volume above neutral. + */ + + if (voicev > 65) + xpn += voicev - 64; + xpn += (voicev - 64) / 2; + + /* + * Combine multiplicative and level components. + */ + x = vel * xpn * 6 + (voicev / 4) * x; + +#ifdef GUS_VOLUME + /* + * Further adjustment by installation-specific master volume control + * (default 60). + */ + x = (x * GUS_VOLUME * GUS_VOLUME) / 10000; +#endif + +#ifdef GUS_USE_CHN_MAIN_VOLUME + /* + * Experimental support for the channel main volume + */ + + mainv = (mainv / 2) + 64; /* Scale to 64 to 127 */ + x = (x * mainv * mainv) / 16384; +#endif + + if (x < 2) + return (0); + else if (x >= 65535) + return ((15 << 8) | 255); + + /* + * Convert to GUS's logarithmic form with 4 bit exponent i and 8 bit + * mantissa m. + */ + + n = x; + i = 7; + if (n < 128) + { + while (i > 0 && n < (1 << i)) + i--; + } + else + { + while (n > 255) + { + n >>= 1; + i++; + } + } + /* + * Mantissa is part of linear volume not expressed in exponent. (This is + * not quite like real logs -- I wonder if it's right.) + */ + m = x - (1 << i); + + /* + * Adjust mantissa to 8 bits. + */ + if (m > 0) + { + if (i > 8) + m >>= i - 8; + else if (i < 8) + m <<= 8 - i; + } + return ((i << 8) + m); +} + +/* + * Volume-values are interpreted as linear values. Volume is based on the + * value supplied with SEQ_START_NOTE(), channel main volume (if compiled in) + * and the volume set by the mixer-device (default 60%). + */ + +unsigned short gus_linear_vol(int vol, int mainvol) +{ + int mixer_mainvol; + + if (vol <= 0) + vol = 0; + else if (vol >= 127) + vol = 127; + +#ifdef GUS_VOLUME + mixer_mainvol = GUS_VOLUME; +#else + mixer_mainvol = 100; +#endif + +#ifdef GUS_USE_CHN_MAIN_VOLUME + if (mainvol <= 0) + mainvol = 0; + else if (mainvol >= 127) + mainvol = 127; +#else + mainvol = 127; +#endif + return gus_linearvol[(((vol * mainvol) / 127) * mixer_mainvol) / 100]; +} diff --git a/sound/oss/gus_wave.c b/sound/oss/gus_wave.c new file mode 100644 index 000000000000..942d5186580d --- /dev/null +++ b/sound/oss/gus_wave.c @@ -0,0 +1,3464 @@ +/* + * sound/gus_wave.c + * + * Driver for the Gravis UltraSound wave table synth. + * + * + * 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) + * Frank van de Pol : Fixed GUS MAX interrupt handling. Enabled simultanious + * usage of CS4231A codec, GUS wave and MIDI for GUS MAX. + * Bartlomiej Zolnierkiewicz : added some __init/__exit + */ + +#include +#include +#include + +#define GUSPNP_AUTODETECT + +#include "sound_config.h" +#include + +#include "gus.h" +#include "gus_hw.h" + +#define GUS_BANK_SIZE (((iw_mode) ? 256*1024*1024 : 256*1024)) + +#define MAX_SAMPLE 150 +#define MAX_PATCH 256 + +#define NOT_SAMPLE 0xffff + +struct voice_info +{ + unsigned long orig_freq; + unsigned long current_freq; + unsigned long mode; + int fixed_pitch; + int bender; + int bender_range; + int panning; + int midi_volume; + unsigned int initial_volume; + unsigned int current_volume; + int loop_irq_mode, loop_irq_parm; +#define LMODE_FINISH 1 +#define LMODE_PCM 2 +#define LMODE_PCM_STOP 3 + int volume_irq_mode, volume_irq_parm; +#define VMODE_HALT 1 +#define VMODE_ENVELOPE 2 +#define VMODE_START_NOTE 3 + + int env_phase; + unsigned char env_rate[6]; + unsigned char env_offset[6]; + + /* + * Volume computation parameters for gus_adagio_vol() + */ + int main_vol, expression_vol, patch_vol; + + /* Variables for "Ultraclick" removal */ + int dev_pending, note_pending, volume_pending, + sample_pending; + char kill_pending; + long offset_pending; + +}; + +static struct voice_alloc_info *voice_alloc; +static struct address_info *gus_hw_config; +extern int gus_base; +extern int gus_irq, gus_dma; +extern int gus_pnp_flag; +extern int gus_no_wave_dma; +static int gus_dma2 = -1; +static int dual_dma_mode; +static long gus_mem_size; +static long free_mem_ptr; +static int gus_busy; +static int gus_no_dma; +static int nr_voices; +static int gus_devnum; +static int volume_base, volume_scale, volume_method; +static int gus_recmask = SOUND_MASK_MIC; +static int recording_active; +static int only_read_access; +static int only_8_bits; + +static int iw_mode = 0; +int gus_wave_volume = 60; +int gus_pcm_volume = 80; +int have_gus_max = 0; +static int gus_line_vol = 100, gus_mic_vol; +static unsigned char mix_image = 0x00; + +int gus_timer_enabled = 0; + +/* + * Current version of this driver doesn't allow synth and PCM functions + * at the same time. The active_device specifies the active driver + */ + +static int active_device; + +#define GUS_DEV_WAVE 1 /* Wave table synth */ +#define GUS_DEV_PCM_DONE 2 /* PCM device, transfer done */ +#define GUS_DEV_PCM_CONTINUE 3 /* PCM device, transfer done ch. 1/2 */ + +static int gus_audio_speed; +static int gus_audio_channels; +static int gus_audio_bits; +static int gus_audio_bsize; +static char bounce_buf[8 * 1024]; /* Must match value set to max_fragment */ + +static DECLARE_WAIT_QUEUE_HEAD(dram_sleeper); + +/* + * Variables and buffers for PCM output + */ + +#define MAX_PCM_BUFFERS (128*MAX_REALTIME_FACTOR) /* Don't change */ + +static int pcm_bsize, pcm_nblk, pcm_banksize; +static int pcm_datasize[MAX_PCM_BUFFERS]; +static volatile int pcm_head, pcm_tail, pcm_qlen; +static volatile int pcm_active; +static volatile int dma_active; +static int pcm_opened; +static int pcm_current_dev; +static int pcm_current_block; +static unsigned long pcm_current_buf; +static int pcm_current_count; +static int pcm_current_intrflag; +DEFINE_SPINLOCK(gus_lock); + +extern int *gus_osp; + +static struct voice_info voices[32]; + +static int freq_div_table[] = +{ + 44100, /* 14 */ + 41160, /* 15 */ + 38587, /* 16 */ + 36317, /* 17 */ + 34300, /* 18 */ + 32494, /* 19 */ + 30870, /* 20 */ + 29400, /* 21 */ + 28063, /* 22 */ + 26843, /* 23 */ + 25725, /* 24 */ + 24696, /* 25 */ + 23746, /* 26 */ + 22866, /* 27 */ + 22050, /* 28 */ + 21289, /* 29 */ + 20580, /* 30 */ + 19916, /* 31 */ + 19293 /* 32 */ +}; + +static struct patch_info *samples; +static long sample_ptrs[MAX_SAMPLE + 1]; +static int sample_map[32]; +static int free_sample; +static int mixer_type; + + +static int patch_table[MAX_PATCH]; +static int patch_map[32]; + +static struct synth_info gus_info = { + "Gravis UltraSound", 0, SYNTH_TYPE_SAMPLE, SAMPLE_TYPE_GUS, + 0, 16, 0, MAX_PATCH +}; + +static void gus_poke(long addr, unsigned char data); +static void compute_and_set_volume(int voice, int volume, int ramp_time); +extern unsigned short gus_adagio_vol(int vel, int mainv, int xpn, int voicev); +extern unsigned short gus_linear_vol(int vol, int mainvol); +static void compute_volume(int voice, int volume); +static void do_volume_irq(int voice); +static void set_input_volumes(void); +static void gus_tmr_install(int io_base); + +#define INSTANT_RAMP -1 /* Instant change. No ramping */ +#define FAST_RAMP 0 /* Fastest possible ramp */ + +static void reset_sample_memory(void) +{ + int i; + + for (i = 0; i <= MAX_SAMPLE; i++) + sample_ptrs[i] = -1; + for (i = 0; i < 32; i++) + sample_map[i] = -1; + for (i = 0; i < 32; i++) + patch_map[i] = -1; + + gus_poke(0, 0); /* Put a silent sample to the beginning */ + gus_poke(1, 0); + free_mem_ptr = 2; + + free_sample = 0; + + for (i = 0; i < MAX_PATCH; i++) + patch_table[i] = NOT_SAMPLE; +} + +void gus_delay(void) +{ + int i; + + for (i = 0; i < 7; i++) + inb(u_DRAMIO); +} + +static void gus_poke(long addr, unsigned char data) +{ /* Writes a byte to the DRAM */ + outb((0x43), u_Command); + outb((addr & 0xff), u_DataLo); + outb(((addr >> 8) & 0xff), u_DataHi); + + outb((0x44), u_Command); + outb(((addr >> 16) & 0xff), u_DataHi); + outb((data), u_DRAMIO); +} + +static unsigned char gus_peek(long addr) +{ /* Reads a byte from the DRAM */ + unsigned char tmp; + + outb((0x43), u_Command); + outb((addr & 0xff), u_DataLo); + outb(((addr >> 8) & 0xff), u_DataHi); + + outb((0x44), u_Command); + outb(((addr >> 16) & 0xff), u_DataHi); + tmp = inb(u_DRAMIO); + + return tmp; +} + +void gus_write8(int reg, unsigned int data) +{ /* Writes to an indirect register (8 bit) */ + outb((reg), u_Command); + outb(((unsigned char) (data & 0xff)), u_DataHi); +} + +static unsigned char gus_read8(int reg) +{ + /* Reads from an indirect register (8 bit). Offset 0x80. */ + unsigned char val; + + outb((reg | 0x80), u_Command); + val = inb(u_DataHi); + + return val; +} + +static unsigned char gus_look8(int reg) +{ + /* Reads from an indirect register (8 bit). No additional offset. */ + unsigned char val; + + outb((reg), u_Command); + val = inb(u_DataHi); + + return val; +} + +static void gus_write16(int reg, unsigned int data) +{ + /* Writes to an indirect register (16 bit) */ + outb((reg), u_Command); + + outb(((unsigned char) (data & 0xff)), u_DataLo); + outb(((unsigned char) ((data >> 8) & 0xff)), u_DataHi); +} + +static unsigned short gus_read16(int reg) +{ + /* Reads from an indirect register (16 bit). Offset 0x80. */ + unsigned char hi, lo; + + outb((reg | 0x80), u_Command); + + lo = inb(u_DataLo); + hi = inb(u_DataHi); + + return ((hi << 8) & 0xff00) | lo; +} + +static unsigned short gus_look16(int reg) +{ + /* Reads from an indirect register (16 bit). No additional offset. */ + unsigned char hi, lo; + + outb((reg), u_Command); + + lo = inb(u_DataLo); + hi = inb(u_DataHi); + + return ((hi << 8) & 0xff00) | lo; +} + +static void gus_write_addr(int reg, unsigned long address, int frac, int is16bit) +{ + /* Writes an 24 bit memory address */ + unsigned long hold_address; + + if (is16bit) + { + if (iw_mode) + { + /* Interwave spesific address translations */ + address >>= 1; + } + else + { + /* + * Special processing required for 16 bit patches + */ + + hold_address = address; + address = address >> 1; + address &= 0x0001ffffL; + address |= (hold_address & 0x000c0000L); + } + } + gus_write16(reg, (unsigned short) ((address >> 7) & 0xffff)); + gus_write16(reg + 1, (unsigned short) ((address << 9) & 0xffff) + + (frac << 5)); + /* Could writing twice fix problems with GUS_VOICE_POS()? Let's try. */ + gus_delay(); + gus_write16(reg, (unsigned short) ((address >> 7) & 0xffff)); + gus_write16(reg + 1, (unsigned short) ((address << 9) & 0xffff) + + (frac << 5)); +} + +static void gus_select_voice(int voice) +{ + if (voice < 0 || voice > 31) + return; + outb((voice), u_Voice); +} + +static void gus_select_max_voices(int nvoices) +{ + if (iw_mode) + nvoices = 32; + if (nvoices < 14) + nvoices = 14; + if (nvoices > 32) + nvoices = 32; + + voice_alloc->max_voice = nr_voices = nvoices; + gus_write8(0x0e, (nvoices - 1) | 0xc0); +} + +static void gus_voice_on(unsigned int mode) +{ + gus_write8(0x00, (unsigned char) (mode & 0xfc)); + gus_delay(); + gus_write8(0x00, (unsigned char) (mode & 0xfc)); +} + +static void gus_voice_off(void) +{ + gus_write8(0x00, gus_read8(0x00) | 0x03); +} + +static void gus_voice_mode(unsigned int m) +{ + unsigned char mode = (unsigned char) (m & 0xff); + + gus_write8(0x00, (gus_read8(0x00) & 0x03) | + (mode & 0xfc)); /* Don't touch last two bits */ + gus_delay(); + gus_write8(0x00, (gus_read8(0x00) & 0x03) | (mode & 0xfc)); +} + +static void gus_voice_freq(unsigned long freq) +{ + unsigned long divisor = freq_div_table[nr_voices - 14]; + unsigned short fc; + + /* Interwave plays at 44100 Hz with any number of voices */ + if (iw_mode) + fc = (unsigned short) (((freq << 9) + (44100 >> 1)) / 44100); + else + fc = (unsigned short) (((freq << 9) + (divisor >> 1)) / divisor); + fc = fc << 1; + + gus_write16(0x01, fc); +} + +static void gus_voice_volume(unsigned int vol) +{ + gus_write8(0x0d, 0x03); /* Stop ramp before setting volume */ + gus_write16(0x09, (unsigned short) (vol << 4)); +} + +static void gus_voice_balance(unsigned int balance) +{ + gus_write8(0x0c, (unsigned char) (balance & 0xff)); +} + +static void gus_ramp_range(unsigned int low, unsigned int high) +{ + gus_write8(0x07, (unsigned char) ((low >> 4) & 0xff)); + gus_write8(0x08, (unsigned char) ((high >> 4) & 0xff)); +} + +static void gus_ramp_rate(unsigned int scale, unsigned int rate) +{ + gus_write8(0x06, (unsigned char) (((scale & 0x03) << 6) | (rate & 0x3f))); +} + +static void gus_rampon(unsigned int m) +{ + unsigned char mode = (unsigned char) (m & 0xff); + + gus_write8(0x0d, mode & 0xfc); + gus_delay(); + gus_write8(0x0d, mode & 0xfc); +} + +static void gus_ramp_mode(unsigned int m) +{ + unsigned char mode = (unsigned char) (m & 0xff); + + gus_write8(0x0d, (gus_read8(0x0d) & 0x03) | + (mode & 0xfc)); /* Leave the last 2 bits alone */ + gus_delay(); + gus_write8(0x0d, (gus_read8(0x0d) & 0x03) | (mode & 0xfc)); +} + +static void gus_rampoff(void) +{ + gus_write8(0x0d, 0x03); +} + +static void gus_set_voice_pos(int voice, long position) +{ + int sample_no; + + if ((sample_no = sample_map[voice]) != -1) { + if (position < samples[sample_no].len) { + if (voices[voice].volume_irq_mode == VMODE_START_NOTE) + voices[voice].offset_pending = position; + else + gus_write_addr(0x0a, sample_ptrs[sample_no] + position, 0, + samples[sample_no].mode & WAVE_16_BITS); + } + } +} + +static void gus_voice_init(int voice) +{ + unsigned long flags; + + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_voice_volume(0); + gus_voice_off(); + gus_write_addr(0x0a, 0, 0, 0); /* Set current position to 0 */ + gus_write8(0x00, 0x03); /* Voice off */ + gus_write8(0x0d, 0x03); /* Ramping off */ + voice_alloc->map[voice] = 0; + voice_alloc->alloc_times[voice] = 0; + spin_unlock_irqrestore(&gus_lock,flags); + +} + +static void gus_voice_init2(int voice) +{ + voices[voice].panning = 0; + voices[voice].mode = 0; + voices[voice].orig_freq = 20000; + voices[voice].current_freq = 20000; + voices[voice].bender = 0; + voices[voice].bender_range = 200; + voices[voice].initial_volume = 0; + voices[voice].current_volume = 0; + voices[voice].loop_irq_mode = 0; + voices[voice].loop_irq_parm = 0; + voices[voice].volume_irq_mode = 0; + voices[voice].volume_irq_parm = 0; + voices[voice].env_phase = 0; + voices[voice].main_vol = 127; + voices[voice].patch_vol = 127; + voices[voice].expression_vol = 127; + voices[voice].sample_pending = -1; + voices[voice].fixed_pitch = 0; +} + +static void step_envelope(int voice) +{ + unsigned vol, prev_vol, phase; + unsigned char rate; + unsigned long flags; + + if (voices[voice].mode & WAVE_SUSTAIN_ON && voices[voice].env_phase == 2) + { + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_rampoff(); + spin_unlock_irqrestore(&gus_lock,flags); + return; + /* + * Sustain phase begins. Continue envelope after receiving note off. + */ + } + if (voices[voice].env_phase >= 5) + { + /* Envelope finished. Shoot the voice down */ + gus_voice_init(voice); + return; + } + prev_vol = voices[voice].current_volume; + phase = ++voices[voice].env_phase; + compute_volume(voice, voices[voice].midi_volume); + vol = voices[voice].initial_volume * voices[voice].env_offset[phase] / 255; + rate = voices[voice].env_rate[phase]; + + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + + gus_voice_volume(prev_vol); + + + gus_write8(0x06, rate); /* Ramping rate */ + + voices[voice].volume_irq_mode = VMODE_ENVELOPE; + + if (((vol - prev_vol) / 64) == 0) /* No significant volume change */ + { + spin_unlock_irqrestore(&gus_lock,flags); + step_envelope(voice); /* Continue the envelope on the next step */ + return; + } + if (vol > prev_vol) + { + if (vol >= (4096 - 64)) + vol = 4096 - 65; + gus_ramp_range(0, vol); + gus_rampon(0x20); /* Increasing volume, with IRQ */ + } + else + { + if (vol <= 64) + vol = 65; + gus_ramp_range(vol, 4030); + gus_rampon(0x60); /* Decreasing volume, with IRQ */ + } + voices[voice].current_volume = vol; + spin_unlock_irqrestore(&gus_lock,flags); +} + +static void init_envelope(int voice) +{ + voices[voice].env_phase = -1; + voices[voice].current_volume = 64; + + step_envelope(voice); +} + +static void start_release(int voice) +{ + if (gus_read8(0x00) & 0x03) + return; /* Voice already stopped */ + + voices[voice].env_phase = 2; /* Will be incremented by step_envelope */ + + voices[voice].current_volume = voices[voice].initial_volume = + gus_read16(0x09) >> 4; /* Get current volume */ + + voices[voice].mode &= ~WAVE_SUSTAIN_ON; + gus_rampoff(); + step_envelope(voice); +} + +static void gus_voice_fade(int voice) +{ + int instr_no = sample_map[voice], is16bits; + unsigned long flags; + + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + + if (instr_no < 0 || instr_no > MAX_SAMPLE) + { + gus_write8(0x00, 0x03); /* Hard stop */ + voice_alloc->map[voice] = 0; + spin_unlock_irqrestore(&gus_lock,flags); + return; + } + is16bits = (samples[instr_no].mode & WAVE_16_BITS) ? 1 : 0; /* 8 or 16 bits */ + + if (voices[voice].mode & WAVE_ENVELOPES) + { + start_release(voice); + spin_unlock_irqrestore(&gus_lock,flags); + return; + } + /* + * Ramp the volume down but not too quickly. + */ + if ((int) (gus_read16(0x09) >> 4) < 100) /* Get current volume */ + { + gus_voice_off(); + gus_rampoff(); + gus_voice_init(voice); + spin_unlock_irqrestore(&gus_lock,flags); + return; + } + gus_ramp_range(65, 4030); + gus_ramp_rate(2, 4); + gus_rampon(0x40 | 0x20); /* Down, once, with IRQ */ + voices[voice].volume_irq_mode = VMODE_HALT; + spin_unlock_irqrestore(&gus_lock,flags); +} + +static void gus_reset(void) +{ + int i; + + gus_select_max_voices(24); + volume_base = 3071; + volume_scale = 4; + volume_method = VOL_METHOD_ADAGIO; + + for (i = 0; i < 32; i++) + { + gus_voice_init(i); /* Turn voice off */ + gus_voice_init2(i); + } +} + +static void gus_initialize(void) +{ + unsigned long flags; + unsigned char dma_image, irq_image, tmp; + + static unsigned char gus_irq_map[16] = { + 0, 0, 0, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7 + }; + + static unsigned char gus_dma_map[8] = { + 0, 1, 0, 2, 0, 3, 4, 5 + }; + + spin_lock_irqsave(&gus_lock,flags); + gus_write8(0x4c, 0); /* Reset GF1 */ + gus_delay(); + gus_delay(); + + gus_write8(0x4c, 1); /* Release Reset */ + gus_delay(); + gus_delay(); + + /* + * Clear all interrupts + */ + + gus_write8(0x41, 0); /* DMA control */ + gus_write8(0x45, 0); /* Timer control */ + gus_write8(0x49, 0); /* Sample control */ + + gus_select_max_voices(24); + + inb(u_Status); /* Touch the status register */ + + gus_look8(0x41); /* Clear any pending DMA IRQs */ + gus_look8(0x49); /* Clear any pending sample IRQs */ + gus_read8(0x0f); /* Clear pending IRQs */ + + gus_reset(); /* Resets all voices */ + + gus_look8(0x41); /* Clear any pending DMA IRQs */ + gus_look8(0x49); /* Clear any pending sample IRQs */ + gus_read8(0x0f); /* Clear pending IRQs */ + + gus_write8(0x4c, 7); /* Master reset | DAC enable | IRQ enable */ + + /* + * Set up for Digital ASIC + */ + + outb((0x05), gus_base + 0x0f); + + mix_image |= 0x02; /* Disable line out (for a moment) */ + outb((mix_image), u_Mixer); + + outb((0x00), u_IRQDMAControl); + + outb((0x00), gus_base + 0x0f); + + /* + * Now set up the DMA and IRQ interface + * + * The GUS supports two IRQs and two DMAs. + * + * Just one DMA channel is used. This prevents simultaneous ADC and DAC. + * Adding this support requires significant changes to the dmabuf.c, dsp.c + * and audio.c also. + */ + + irq_image = 0; + tmp = gus_irq_map[gus_irq]; + if (!gus_pnp_flag && !tmp) + printk(KERN_WARNING "Warning! GUS IRQ not selected\n"); + irq_image |= tmp; + irq_image |= 0x40; /* Combine IRQ1 (GF1) and IRQ2 (Midi) */ + + dual_dma_mode = 1; + if (gus_dma2 == gus_dma || gus_dma2 == -1) + { + dual_dma_mode = 0; + dma_image = 0x40; /* Combine DMA1 (DRAM) and IRQ2 (ADC) */ + + tmp = gus_dma_map[gus_dma]; + if (!tmp) + printk(KERN_WARNING "Warning! GUS DMA not selected\n"); + + dma_image |= tmp; + } + else + { + /* Setup dual DMA channel mode for GUS MAX */ + + dma_image = gus_dma_map[gus_dma]; + if (!dma_image) + printk(KERN_WARNING "Warning! GUS DMA not selected\n"); + + tmp = gus_dma_map[gus_dma2] << 3; + if (!tmp) + { + printk(KERN_WARNING "Warning! Invalid GUS MAX DMA\n"); + tmp = 0x40; /* Combine DMA channels */ + dual_dma_mode = 0; + } + dma_image |= tmp; + } + + /* + * For some reason the IRQ and DMA addresses must be written twice + */ + + /* + * Doing it first time + */ + + outb((mix_image), u_Mixer); /* Select DMA control */ + outb((dma_image | 0x80), u_IRQDMAControl); /* Set DMA address */ + + outb((mix_image | 0x40), u_Mixer); /* Select IRQ control */ + outb((irq_image), u_IRQDMAControl); /* Set IRQ address */ + + /* + * Doing it second time + */ + + outb((mix_image), u_Mixer); /* Select DMA control */ + outb((dma_image), u_IRQDMAControl); /* Set DMA address */ + + outb((mix_image | 0x40), u_Mixer); /* Select IRQ control */ + outb((irq_image), u_IRQDMAControl); /* Set IRQ address */ + + gus_select_voice(0); /* This disables writes to IRQ/DMA reg */ + + mix_image &= ~0x02; /* Enable line out */ + mix_image |= 0x08; /* Enable IRQ */ + outb((mix_image), u_Mixer); /* + * Turn mixer channels on + * Note! Mic in is left off. + */ + + gus_select_voice(0); /* This disables writes to IRQ/DMA reg */ + + gusintr(gus_irq, (void *)gus_hw_config, NULL); /* Serve pending interrupts */ + + inb(u_Status); /* Touch the status register */ + + gus_look8(0x41); /* Clear any pending DMA IRQs */ + gus_look8(0x49); /* Clear any pending sample IRQs */ + + gus_read8(0x0f); /* Clear pending IRQs */ + + if (iw_mode) + gus_write8(0x19, gus_read8(0x19) | 0x01); + spin_unlock_irqrestore(&gus_lock,flags); +} + + +static void __init pnp_mem_init(void) +{ +#include "iwmem.h" +#define CHUNK_SIZE (256*1024) +#define BANK_SIZE (4*1024*1024) +#define CHUNKS_PER_BANK (BANK_SIZE/CHUNK_SIZE) + + int bank, chunk, addr, total = 0; + int bank_sizes[4]; + int i, j, bits = -1, testbits = -1, nbanks = 0; + + /* + * This routine determines what kind of RAM is installed in each of the four + * SIMM banks and configures the DRAM address decode logic accordingly. + */ + + /* + * Place the chip into enhanced mode + */ + gus_write8(0x19, gus_read8(0x19) | 0x01); + gus_write8(0x53, gus_look8(0x53) & ~0x02); /* Select DRAM I/O access */ + + /* + * Set memory configuration to 4 DRAM banks of 4M in each (16M total). + */ + + gus_write16(0x52, (gus_look16(0x52) & 0xfff0) | 0x000c); + + /* + * Perform the DRAM size detection for each bank individually. + */ + for (bank = 0; bank < 4; bank++) + { + int size = 0; + + addr = bank * BANK_SIZE; + + /* Clean check points of each chunk */ + for (chunk = 0; chunk < CHUNKS_PER_BANK; chunk++) + { + gus_poke(addr + chunk * CHUNK_SIZE + 0L, 0x00); + gus_poke(addr + chunk * CHUNK_SIZE + 1L, 0x00); + } + + /* Write a value to each chunk point and verify the result */ + for (chunk = 0; chunk < CHUNKS_PER_BANK; chunk++) + { + gus_poke(addr + chunk * CHUNK_SIZE + 0L, 0x55); + gus_poke(addr + chunk * CHUNK_SIZE + 1L, 0xAA); + + if (gus_peek(addr + chunk * CHUNK_SIZE + 0L) == 0x55 && + gus_peek(addr + chunk * CHUNK_SIZE + 1L) == 0xAA) + { + /* OK. There is RAM. Now check for possible shadows */ + int ok = 1, chunk2; + + for (chunk2 = 0; ok && chunk2 < chunk; chunk2++) + if (gus_peek(addr + chunk2 * CHUNK_SIZE + 0L) || + gus_peek(addr + chunk2 * CHUNK_SIZE + 1L)) + ok = 0; /* Addressing wraps */ + + if (ok) + size = (chunk + 1) * CHUNK_SIZE; + } + gus_poke(addr + chunk * CHUNK_SIZE + 0L, 0x00); + gus_poke(addr + chunk * CHUNK_SIZE + 1L, 0x00); + } + bank_sizes[bank] = size; + if (size) + nbanks = bank + 1; + DDB(printk("Interwave: Bank %d, size=%dk\n", bank, size / 1024)); + } + + if (nbanks == 0) /* No RAM - Give up */ + { + printk(KERN_ERR "Sound: An Interwave audio chip detected but no DRAM\n"); + printk(KERN_ERR "Sound: Unable to work with this card.\n"); + gus_write8(0x19, gus_read8(0x19) & ~0x01); + gus_mem_size = 0; + return; + } + + /* + * Now we know how much DRAM there is in each bank. The next step is + * to find a DRAM size encoding (0 to 12) which is best for the combination + * we have. + * + * First try if any of the possible alternatives matches exactly the amount + * of memory we have. + */ + + for (i = 0; bits == -1 && i < 13; i++) + { + bits = i; + + for (j = 0; bits != -1 && j < 4; j++) + if (mem_decode[i][j] != bank_sizes[j]) + bits = -1; /* No hit */ + } + + /* + * If necessary, try to find a combination where other than the last + * bank matches our configuration and the last bank is left oversized. + * In this way we don't leave holes in the middle of memory. + */ + + if (bits == -1) /* No luck yet */ + { + for (i = 0; bits == -1 && i < 13; i++) + { + bits = i; + + for (j = 0; bits != -1 && j < nbanks - 1; j++) + if (mem_decode[i][j] != bank_sizes[j]) + bits = -1; /* No hit */ + if (mem_decode[i][nbanks - 1] < bank_sizes[nbanks - 1]) + bits = -1; /* The last bank is too small */ + } + } + /* + * The last resort is to search for a combination where the banks are + * smaller than the actual SIMMs. This leaves some memory in the banks + * unused but doesn't leave holes in the DRAM address space. + */ + if (bits == -1) /* No luck yet */ + { + for (i = 0; i < 13; i++) + { + testbits = i; + for (j = 0; testbits != -1 && j < nbanks - 1; j++) + if (mem_decode[i][j] > bank_sizes[j]) { + testbits = -1; + } + if(testbits > bits) bits = testbits; + } + if (bits != -1) + { + printk(KERN_INFO "Interwave: Can't use all installed RAM.\n"); + printk(KERN_INFO "Interwave: Try reordering SIMMS.\n"); + } + printk(KERN_INFO "Interwave: Can't find working DRAM encoding.\n"); + printk(KERN_INFO "Interwave: Defaulting to 256k. Try reordering SIMMS.\n"); + bits = 0; + } + DDB(printk("Interwave: Selecting DRAM addressing mode %d\n", bits)); + + for (bank = 0; bank < 4; bank++) + { + DDB(printk(" Bank %d, mem=%dk (limit %dk)\n", bank, bank_sizes[bank] / 1024, mem_decode[bits][bank] / 1024)); + + if (bank_sizes[bank] > mem_decode[bits][bank]) + total += mem_decode[bits][bank]; + else + total += bank_sizes[bank]; + } + + DDB(printk("Total %dk of DRAM (enhanced mode)\n", total / 1024)); + + /* + * Set the memory addressing mode. + */ + gus_write16(0x52, (gus_look16(0x52) & 0xfff0) | bits); + +/* Leave the chip into enhanced mode. Disable LFO */ + gus_mem_size = total; + iw_mode = 1; + gus_write8(0x19, (gus_read8(0x19) | 0x01) & ~0x02); +} + +int __init gus_wave_detect(int baseaddr) +{ + unsigned long i, max_mem = 1024L; + unsigned long loc; + unsigned char val; + + if (!request_region(baseaddr, 16, "GUS")) + return 0; + if (!request_region(baseaddr + 0x100, 12, "GUS")) { /* 0x10c-> is MAX */ + release_region(baseaddr, 16); + return 0; + } + + gus_base = baseaddr; + + gus_write8(0x4c, 0); /* Reset GF1 */ + gus_delay(); + gus_delay(); + + gus_write8(0x4c, 1); /* Release Reset */ + gus_delay(); + gus_delay(); + +#ifdef GUSPNP_AUTODETECT + val = gus_look8(0x5b); /* Version number register */ + gus_write8(0x5b, ~val); /* Invert all bits */ + + if ((gus_look8(0x5b) & 0xf0) == (val & 0xf0)) /* No change */ + { + if ((gus_look8(0x5b) & 0x0f) == ((~val) & 0x0f)) /* Change */ + { + DDB(printk("Interwave chip version %d detected\n", (val & 0xf0) >> 4)); + gus_pnp_flag = 1; + } + else + { + DDB(printk("Not an Interwave chip (%x)\n", gus_look8(0x5b))); + gus_pnp_flag = 0; + } + } + gus_write8(0x5b, val); /* Restore all bits */ +#endif + + if (gus_pnp_flag) + pnp_mem_init(); + if (iw_mode) + return 1; + + /* See if there is first block there.... */ + gus_poke(0L, 0xaa); + if (gus_peek(0L) != 0xaa) { + release_region(baseaddr + 0x100, 12); + release_region(baseaddr, 16); + return 0; + } + + /* Now zero it out so that I can check for mirroring .. */ + gus_poke(0L, 0x00); + for (i = 1L; i < max_mem; i++) + { + int n, failed; + + /* check for mirroring ... */ + if (gus_peek(0L) != 0) + break; + loc = i << 10; + + for (n = loc - 1, failed = 0; n <= loc; n++) + { + gus_poke(loc, 0xaa); + if (gus_peek(loc) != 0xaa) + failed = 1; + gus_poke(loc, 0x55); + if (gus_peek(loc) != 0x55) + failed = 1; + } + if (failed) + break; + } + gus_mem_size = i << 10; + return 1; +} + +static int guswave_ioctl(int dev, unsigned int cmd, void __user *arg) +{ + + switch (cmd) + { + case SNDCTL_SYNTH_INFO: + gus_info.nr_voices = nr_voices; + if (copy_to_user(arg, &gus_info, sizeof(gus_info))) + return -EFAULT; + return 0; + + case SNDCTL_SEQ_RESETSAMPLES: + reset_sample_memory(); + return 0; + + case SNDCTL_SEQ_PERCMODE: + return 0; + + case SNDCTL_SYNTH_MEMAVL: + return (gus_mem_size == 0) ? 0 : gus_mem_size - free_mem_ptr - 32; + + default: + return -EINVAL; + } +} + +static int guswave_set_instr(int dev, int voice, int instr_no) +{ + int sample_no; + + if (instr_no < 0 || instr_no > MAX_PATCH) + instr_no = 0; /* Default to acoustic piano */ + + if (voice < 0 || voice > 31) + return -EINVAL; + + if (voices[voice].volume_irq_mode == VMODE_START_NOTE) + { + voices[voice].sample_pending = instr_no; + return 0; + } + sample_no = patch_table[instr_no]; + patch_map[voice] = -1; + + if (sample_no == NOT_SAMPLE) + { +/* printk("GUS: Undefined patch %d for voice %d\n", instr_no, voice);*/ + return -EINVAL; /* Patch not defined */ + } + if (sample_ptrs[sample_no] == -1) /* Sample not loaded */ + { +/* printk("GUS: Sample #%d not loaded for patch %d (voice %d)\n", sample_no, instr_no, voice);*/ + return -EINVAL; + } + sample_map[voice] = sample_no; + patch_map[voice] = instr_no; + return 0; +} + +static int guswave_kill_note(int dev, int voice, int note, int velocity) +{ + unsigned long flags; + + spin_lock_irqsave(&gus_lock,flags); + /* voice_alloc->map[voice] = 0xffff; */ + if (voices[voice].volume_irq_mode == VMODE_START_NOTE) + { + voices[voice].kill_pending = 1; + spin_unlock_irqrestore(&gus_lock,flags); + } + else + { + spin_unlock_irqrestore(&gus_lock,flags); + gus_voice_fade(voice); + } + + return 0; +} + +static void guswave_aftertouch(int dev, int voice, int pressure) +{ +} + +static void guswave_panning(int dev, int voice, int value) +{ + if (voice >= 0 || voice < 32) + voices[voice].panning = value; +} + +static void guswave_volume_method(int dev, int mode) +{ + if (mode == VOL_METHOD_LINEAR || mode == VOL_METHOD_ADAGIO) + volume_method = mode; +} + +static void compute_volume(int voice, int volume) +{ + if (volume < 128) + voices[voice].midi_volume = volume; + + switch (volume_method) + { + case VOL_METHOD_ADAGIO: + voices[voice].initial_volume = + gus_adagio_vol(voices[voice].midi_volume, voices[voice].main_vol, + voices[voice].expression_vol, + voices[voice].patch_vol); + break; + + case VOL_METHOD_LINEAR: /* Totally ignores patch-volume and expression */ + voices[voice].initial_volume = gus_linear_vol(volume, voices[voice].main_vol); + break; + + default: + voices[voice].initial_volume = volume_base + + (voices[voice].midi_volume * volume_scale); + } + + if (voices[voice].initial_volume > 4030) + voices[voice].initial_volume = 4030; +} + +static void compute_and_set_volume(int voice, int volume, int ramp_time) +{ + int curr, target, rate; + unsigned long flags; + + compute_volume(voice, volume); + voices[voice].current_volume = voices[voice].initial_volume; + + spin_lock_irqsave(&gus_lock,flags); + /* + * CAUTION! Interrupts disabled. Enable them before returning + */ + + gus_select_voice(voice); + + curr = gus_read16(0x09) >> 4; + target = voices[voice].initial_volume; + + if (ramp_time == INSTANT_RAMP) + { + gus_rampoff(); + gus_voice_volume(target); + spin_unlock_irqrestore(&gus_lock,flags); + return; + } + if (ramp_time == FAST_RAMP) + rate = 63; + else + rate = 16; + gus_ramp_rate(0, rate); + + if ((target - curr) / 64 == 0) /* Close enough to target. */ + { + gus_rampoff(); + gus_voice_volume(target); + spin_unlock_irqrestore(&gus_lock,flags); + return; + } + if (target > curr) + { + if (target > (4095 - 65)) + target = 4095 - 65; + gus_ramp_range(curr, target); + gus_rampon(0x00); /* Ramp up, once, no IRQ */ + } + else + { + if (target < 65) + target = 65; + + gus_ramp_range(target, curr); + gus_rampon(0x40); /* Ramp down, once, no irq */ + } + spin_unlock_irqrestore(&gus_lock,flags); +} + +static void dynamic_volume_change(int voice) +{ + unsigned char status; + unsigned long flags; + + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + status = gus_read8(0x00); /* Get voice status */ + spin_unlock_irqrestore(&gus_lock,flags); + + if (status & 0x03) + return; /* Voice was not running */ + + if (!(voices[voice].mode & WAVE_ENVELOPES)) + { + compute_and_set_volume(voice, voices[voice].midi_volume, 1); + return; + } + + /* + * Voice is running and has envelopes. + */ + + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + status = gus_read8(0x0d); /* Ramping status */ + spin_unlock_irqrestore(&gus_lock,flags); + + if (status & 0x03) /* Sustain phase? */ + { + compute_and_set_volume(voice, voices[voice].midi_volume, 1); + return; + } + if (voices[voice].env_phase < 0) + return; + + compute_volume(voice, voices[voice].midi_volume); + +} + +static void guswave_controller(int dev, int voice, int ctrl_num, int value) +{ + unsigned long flags; + unsigned long freq; + + if (voice < 0 || voice > 31) + return; + + switch (ctrl_num) + { + case CTRL_PITCH_BENDER: + voices[voice].bender = value; + + if (voices[voice].volume_irq_mode != VMODE_START_NOTE) + { + freq = compute_finetune(voices[voice].orig_freq, value, voices[voice].bender_range, 0); + voices[voice].current_freq = freq; + + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_voice_freq(freq); + spin_unlock_irqrestore(&gus_lock,flags); + } + break; + + case CTRL_PITCH_BENDER_RANGE: + voices[voice].bender_range = value; + break; + case CTL_EXPRESSION: + value /= 128; + case CTRL_EXPRESSION: + if (volume_method == VOL_METHOD_ADAGIO) + { + voices[voice].expression_vol = value; + if (voices[voice].volume_irq_mode != VMODE_START_NOTE) + dynamic_volume_change(voice); + } + break; + + case CTL_PAN: + voices[voice].panning = (value * 2) - 128; + break; + + case CTL_MAIN_VOLUME: + value = (value * 100) / 16383; + + case CTRL_MAIN_VOLUME: + voices[voice].main_vol = value; + if (voices[voice].volume_irq_mode != VMODE_START_NOTE) + dynamic_volume_change(voice); + break; + + default: + break; + } +} + +static int guswave_start_note2(int dev, int voice, int note_num, int volume) +{ + int sample, best_sample, best_delta, delta_freq; + int is16bits, samplep, patch, pan; + unsigned long note_freq, base_note, freq, flags; + unsigned char mode = 0; + + if (voice < 0 || voice > 31) + { +/* printk("GUS: Invalid voice\n");*/ + return -EINVAL; + } + if (note_num == 255) + { + if (voices[voice].mode & WAVE_ENVELOPES) + { + voices[voice].midi_volume = volume; + dynamic_volume_change(voice); + return 0; + } + compute_and_set_volume(voice, volume, 1); + return 0; + } + if ((patch = patch_map[voice]) == -1) + return -EINVAL; + if ((samplep = patch_table[patch]) == NOT_SAMPLE) + { + return -EINVAL; + } + note_freq = note_to_freq(note_num); + + /* + * Find a sample within a patch so that the note_freq is between low_note + * and high_note. + */ + sample = -1; + + best_sample = samplep; + best_delta = 1000000; + while (samplep != 0 && samplep != NOT_SAMPLE && sample == -1) + { + delta_freq = note_freq - samples[samplep].base_note; + if (delta_freq < 0) + delta_freq = -delta_freq; + if (delta_freq < best_delta) + { + best_sample = samplep; + best_delta = delta_freq; + } + if (samples[samplep].low_note <= note_freq && + note_freq <= samples[samplep].high_note) + { + sample = samplep; + } + else + samplep = samples[samplep].key; /* Link to next sample */ + } + if (sample == -1) + sample = best_sample; + + if (sample == -1) + { +/* printk("GUS: Patch %d not defined for note %d\n", patch, note_num);*/ + return 0; /* Should play default patch ??? */ + } + is16bits = (samples[sample].mode & WAVE_16_BITS) ? 1 : 0; + voices[voice].mode = samples[sample].mode; + voices[voice].patch_vol = samples[sample].volume; + + if (iw_mode) + gus_write8(0x15, 0x00); /* RAM, Reset voice deactivate bit of SMSI */ + + if (voices[voice].mode & WAVE_ENVELOPES) + { + int i; + + for (i = 0; i < 6; i++) + { + voices[voice].env_rate[i] = samples[sample].env_rate[i]; + voices[voice].env_offset[i] = samples[sample].env_offset[i]; + } + } + sample_map[voice] = sample; + + if (voices[voice].fixed_pitch) /* Fixed pitch */ + { + freq = samples[sample].base_freq; + } + else + { + base_note = samples[sample].base_note / 100; + note_freq /= 100; + + freq = samples[sample].base_freq * note_freq / base_note; + } + + voices[voice].orig_freq = freq; + + /* + * Since the pitch bender may have been set before playing the note, we + * have to calculate the bending now. + */ + + freq = compute_finetune(voices[voice].orig_freq, voices[voice].bender, + voices[voice].bender_range, 0); + voices[voice].current_freq = freq; + + pan = (samples[sample].panning + voices[voice].panning) / 32; + pan += 7; + if (pan < 0) + pan = 0; + if (pan > 15) + pan = 15; + + if (samples[sample].mode & WAVE_16_BITS) + { + mode |= 0x04; /* 16 bits */ + if ((sample_ptrs[sample] / GUS_BANK_SIZE) != + ((sample_ptrs[sample] + samples[sample].len) / GUS_BANK_SIZE)) + printk(KERN_ERR "GUS: Sample address error\n"); + } + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_voice_off(); + gus_rampoff(); + + spin_unlock_irqrestore(&gus_lock,flags); + + if (voices[voice].mode & WAVE_ENVELOPES) + { + compute_volume(voice, volume); + init_envelope(voice); + } + else + { + compute_and_set_volume(voice, volume, 0); + } + + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + + if (samples[sample].mode & WAVE_LOOP_BACK) + gus_write_addr(0x0a, sample_ptrs[sample] + samples[sample].len - + voices[voice].offset_pending, 0, is16bits); /* start=end */ + else + gus_write_addr(0x0a, sample_ptrs[sample] + voices[voice].offset_pending, 0, is16bits); /* Sample start=begin */ + + if (samples[sample].mode & WAVE_LOOPING) + { + mode |= 0x08; + + if (samples[sample].mode & WAVE_BIDIR_LOOP) + mode |= 0x10; + + if (samples[sample].mode & WAVE_LOOP_BACK) + { + gus_write_addr(0x0a, sample_ptrs[sample] + samples[sample].loop_end - + voices[voice].offset_pending, + (samples[sample].fractions >> 4) & 0x0f, is16bits); + mode |= 0x40; + } + gus_write_addr(0x02, sample_ptrs[sample] + samples[sample].loop_start, + samples[sample].fractions & 0x0f, is16bits); /* Loop start location */ + gus_write_addr(0x04, sample_ptrs[sample] + samples[sample].loop_end, + (samples[sample].fractions >> 4) & 0x0f, is16bits); /* Loop end location */ + } + else + { + mode |= 0x20; /* Loop IRQ at the end */ + voices[voice].loop_irq_mode = LMODE_FINISH; /* Ramp down at the end */ + voices[voice].loop_irq_parm = 1; + gus_write_addr(0x02, sample_ptrs[sample], 0, is16bits); /* Loop start location */ + gus_write_addr(0x04, sample_ptrs[sample] + samples[sample].len - 1, + (samples[sample].fractions >> 4) & 0x0f, is16bits); /* Loop end location */ + } + gus_voice_freq(freq); + gus_voice_balance(pan); + gus_voice_on(mode); + spin_unlock_irqrestore(&gus_lock,flags); + + return 0; +} + +/* + * New guswave_start_note by Andrew J. Robinson attempts to minimize clicking + * when the note playing on the voice is changed. It uses volume + * ramping. + */ + +static int guswave_start_note(int dev, int voice, int note_num, int volume) +{ + unsigned long flags; + int mode; + int ret_val = 0; + + spin_lock_irqsave(&gus_lock,flags); + if (note_num == 255) + { + if (voices[voice].volume_irq_mode == VMODE_START_NOTE) + { + voices[voice].volume_pending = volume; + } + else + { + ret_val = guswave_start_note2(dev, voice, note_num, volume); + } + } + else + { + gus_select_voice(voice); + mode = gus_read8(0x00); + if (mode & 0x20) + gus_write8(0x00, mode & 0xdf); /* No interrupt! */ + + voices[voice].offset_pending = 0; + voices[voice].kill_pending = 0; + voices[voice].volume_irq_mode = 0; + voices[voice].loop_irq_mode = 0; + + if (voices[voice].sample_pending >= 0) + { + spin_unlock_irqrestore(&gus_lock,flags); /* Run temporarily with interrupts enabled */ + guswave_set_instr(voices[voice].dev_pending, voice, voices[voice].sample_pending); + voices[voice].sample_pending = -1; + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); /* Reselect the voice (just to be sure) */ + } + if ((mode & 0x01) || (int) ((gus_read16(0x09) >> 4) < (unsigned) 2065)) + { + ret_val = guswave_start_note2(dev, voice, note_num, volume); + } + else + { + voices[voice].dev_pending = dev; + voices[voice].note_pending = note_num; + voices[voice].volume_pending = volume; + voices[voice].volume_irq_mode = VMODE_START_NOTE; + + gus_rampoff(); + gus_ramp_range(2000, 4065); + gus_ramp_rate(0, 63); /* Fastest possible rate */ + gus_rampon(0x20 | 0x40); /* Ramp down, once, irq */ + } + } + spin_unlock_irqrestore(&gus_lock,flags); + return ret_val; +} + +static void guswave_reset(int dev) +{ + int i; + + for (i = 0; i < 32; i++) + { + gus_voice_init(i); + gus_voice_init2(i); + } +} + +static int guswave_open(int dev, int mode) +{ + int err; + + if (gus_busy) + return -EBUSY; + + voice_alloc->timestamp = 0; + + if (gus_no_wave_dma) { + gus_no_dma = 1; + } else { + if ((err = DMAbuf_open_dma(gus_devnum)) < 0) + { + /* printk( "GUS: Loading samples without DMA\n"); */ + gus_no_dma = 1; /* Upload samples using PIO */ + } + else + gus_no_dma = 0; + } + + init_waitqueue_head(&dram_sleeper); + gus_busy = 1; + active_device = GUS_DEV_WAVE; + + gusintr(gus_irq, (void *)gus_hw_config, NULL); /* Serve pending interrupts */ + gus_initialize(); + gus_reset(); + gusintr(gus_irq, (void *)gus_hw_config, NULL); /* Serve pending interrupts */ + + return 0; +} + +static void guswave_close(int dev) +{ + gus_busy = 0; + active_device = 0; + gus_reset(); + + if (!gus_no_dma) + DMAbuf_close_dma(gus_devnum); +} + +static int guswave_load_patch(int dev, int format, const char __user *addr, + int offs, int count, int pmgr_flag) +{ + struct patch_info patch; + int instr; + long sizeof_patch; + + unsigned long blk_sz, blk_end, left, src_offs, target; + + sizeof_patch = (long) &patch.data[0] - (long) &patch; /* Header size */ + + if (format != GUS_PATCH) + { +/* printk("GUS Error: Invalid patch format (key) 0x%x\n", format);*/ + return -EINVAL; + } + if (count < sizeof_patch) + { +/* printk("GUS Error: Patch header too short\n");*/ + return -EINVAL; + } + count -= sizeof_patch; + + if (free_sample >= MAX_SAMPLE) + { +/* printk("GUS: Sample table full\n");*/ + return -ENOSPC; + } + /* + * Copy the header from user space but ignore the first bytes which have + * been transferred already. + */ + + if (copy_from_user(&((char *) &patch)[offs], &(addr)[offs], + sizeof_patch - offs)) + return -EFAULT; + + if (patch.mode & WAVE_ROM) + return -EINVAL; + if (gus_mem_size == 0) + return -ENOSPC; + + instr = patch.instr_no; + + if (instr < 0 || instr > MAX_PATCH) + { +/* printk(KERN_ERR "GUS: Invalid patch number %d\n", instr);*/ + return -EINVAL; + } + if (count < patch.len) + { +/* printk(KERN_ERR "GUS Warning: Patch record too short (%d<%d)\n", count, (int) patch.len);*/ + patch.len = count; + } + if (patch.len <= 0 || patch.len > gus_mem_size) + { +/* printk(KERN_ERR "GUS: Invalid sample length %d\n", (int) patch.len);*/ + return -EINVAL; + } + if (patch.mode & WAVE_LOOPING) + { + if (patch.loop_start < 0 || patch.loop_start >= patch.len) + { +/* printk(KERN_ERR "GUS: Invalid loop start\n");*/ + return -EINVAL; + } + if (patch.loop_end < patch.loop_start || patch.loop_end > patch.len) + { +/* printk(KERN_ERR "GUS: Invalid loop end\n");*/ + return -EINVAL; + } + } + free_mem_ptr = (free_mem_ptr + 31) & ~31; /* 32 byte alignment */ + + if (patch.mode & WAVE_16_BITS) + { + /* + * 16 bit samples must fit one 256k bank. + */ + if (patch.len >= GUS_BANK_SIZE) + { +/* printk("GUS: Sample (16 bit) too long %d\n", (int) patch.len);*/ + return -ENOSPC; + } + if ((free_mem_ptr / GUS_BANK_SIZE) != + ((free_mem_ptr + patch.len) / GUS_BANK_SIZE)) + { + unsigned long tmp_mem = + /* Align to 256K */ + ((free_mem_ptr / GUS_BANK_SIZE) + 1) * GUS_BANK_SIZE; + + if ((tmp_mem + patch.len) > gus_mem_size) + return -ENOSPC; + + free_mem_ptr = tmp_mem; /* This leaves unusable memory */ + } + } + if ((free_mem_ptr + patch.len) > gus_mem_size) + return -ENOSPC; + + sample_ptrs[free_sample] = free_mem_ptr; + + /* + * Tremolo is not possible with envelopes + */ + + if (patch.mode & WAVE_ENVELOPES) + patch.mode &= ~WAVE_TREMOLO; + + if (!(patch.mode & WAVE_FRACTIONS)) + { + patch.fractions = 0; + } + memcpy((char *) &samples[free_sample], &patch, sizeof_patch); + + /* + * Link this_one sample to the list of samples for patch 'instr'. + */ + + samples[free_sample].key = patch_table[instr]; + patch_table[instr] = free_sample; + + /* + * Use DMA to transfer the wave data to the DRAM + */ + + left = patch.len; + src_offs = 0; + target = free_mem_ptr; + + while (left) /* Not completely transferred yet */ + { + blk_sz = audio_devs[gus_devnum]->dmap_out->bytes_in_use; + if (blk_sz > left) + blk_sz = left; + + /* + * DMA cannot cross bank (256k) boundaries. Check for that. + */ + + blk_end = target + blk_sz; + + if ((target / GUS_BANK_SIZE) != (blk_end / GUS_BANK_SIZE)) + { + /* Split the block */ + blk_end &= ~(GUS_BANK_SIZE - 1); + blk_sz = blk_end - target; + } + if (gus_no_dma) + { + /* + * For some reason the DMA is not possible. We have to use PIO. + */ + long i; + unsigned char data; + + for (i = 0; i < blk_sz; i++) + { + get_user(*(unsigned char *) &data, (unsigned char __user *) &((addr)[sizeof_patch + i])); + if (patch.mode & WAVE_UNSIGNED) + if (!(patch.mode & WAVE_16_BITS) || (i & 0x01)) + data ^= 0x80; /* Convert to signed */ + gus_poke(target + i, data); + } + } + else + { + unsigned long address, hold_address; + unsigned char dma_command; + unsigned long flags; + + if (audio_devs[gus_devnum]->dmap_out->raw_buf == NULL) + { + printk(KERN_ERR "GUS: DMA buffer == NULL\n"); + return -ENOSPC; + } + /* + * OK, move now. First in and then out. + */ + + if (copy_from_user(audio_devs[gus_devnum]->dmap_out->raw_buf, + &(addr)[sizeof_patch + src_offs], + blk_sz)) + return -EFAULT; + + spin_lock_irqsave(&gus_lock,flags); + gus_write8(0x41, 0); /* Disable GF1 DMA */ + DMAbuf_start_dma(gus_devnum, audio_devs[gus_devnum]->dmap_out->raw_buf_phys, + blk_sz, DMA_MODE_WRITE); + + /* + * Set the DRAM address for the wave data + */ + + if (iw_mode) + { + /* Different address translation in enhanced mode */ + + unsigned char hi; + + if (gus_dma > 4) + address = target >> 1; /* Convert to 16 bit word address */ + else + address = target; + + hi = (unsigned char) ((address >> 16) & 0xf0); + hi += (unsigned char) (address & 0x0f); + + gus_write16(0x42, (address >> 4) & 0xffff); /* DMA address (low) */ + gus_write8(0x50, hi); + } + else + { + address = target; + if (audio_devs[gus_devnum]->dmap_out->dma > 3) + { + hold_address = address; + address = address >> 1; + address &= 0x0001ffffL; + address |= (hold_address & 0x000c0000L); + } + gus_write16(0x42, (address >> 4) & 0xffff); /* DRAM DMA address */ + } + + /* + * Start the DMA transfer + */ + + dma_command = 0x21; /* IRQ enable, DMA start */ + if (patch.mode & WAVE_UNSIGNED) + dma_command |= 0x80; /* Invert MSB */ + if (patch.mode & WAVE_16_BITS) + dma_command |= 0x40; /* 16 bit _DATA_ */ + if (audio_devs[gus_devnum]->dmap_out->dma > 3) + dma_command |= 0x04; /* 16 bit DMA _channel_ */ + + /* + * Sleep here until the DRAM DMA done interrupt is served + */ + active_device = GUS_DEV_WAVE; + gus_write8(0x41, dma_command); /* Lets go luteet (=bugs) */ + + spin_unlock_irqrestore(&gus_lock,flags); /* opens a race */ + if (!interruptible_sleep_on_timeout(&dram_sleeper, HZ)) + printk("GUS: DMA Transfer timed out\n"); + } + + /* + * Now the next part + */ + + left -= blk_sz; + src_offs += blk_sz; + target += blk_sz; + + gus_write8(0x41, 0); /* Stop DMA */ + } + + free_mem_ptr += patch.len; + free_sample++; + return 0; +} + +static void guswave_hw_control(int dev, unsigned char *event_rec) +{ + int voice, cmd; + unsigned short p1, p2; + unsigned int plong; + unsigned long flags; + + cmd = event_rec[2]; + voice = event_rec[3]; + p1 = *(unsigned short *) &event_rec[4]; + p2 = *(unsigned short *) &event_rec[6]; + plong = *(unsigned int *) &event_rec[4]; + + if ((voices[voice].volume_irq_mode == VMODE_START_NOTE) && + (cmd != _GUS_VOICESAMPLE) && (cmd != _GUS_VOICE_POS)) + do_volume_irq(voice); + + switch (cmd) + { + case _GUS_NUMVOICES: + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_select_max_voices(p1); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_VOICESAMPLE: + guswave_set_instr(dev, voice, p1); + break; + + case _GUS_VOICEON: + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + p1 &= ~0x20; /* Don't allow interrupts */ + gus_voice_on(p1); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_VOICEOFF: + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_voice_off(); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_VOICEFADE: + gus_voice_fade(voice); + break; + + case _GUS_VOICEMODE: + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + p1 &= ~0x20; /* Don't allow interrupts */ + gus_voice_mode(p1); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_VOICEBALA: + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_voice_balance(p1); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_VOICEFREQ: + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_voice_freq(plong); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_VOICEVOL: + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_voice_volume(p1); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_VOICEVOL2: /* Just update the software voice level */ + voices[voice].initial_volume = voices[voice].current_volume = p1; + break; + + case _GUS_RAMPRANGE: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* NO-NO */ + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_ramp_range(p1, p2); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_RAMPRATE: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* NJET-NJET */ + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_ramp_rate(p1, p2); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_RAMPMODE: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* NO-NO */ + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + p1 &= ~0x20; /* Don't allow interrupts */ + gus_ramp_mode(p1); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_RAMPON: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* EI-EI */ + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + p1 &= ~0x20; /* Don't allow interrupts */ + gus_rampon(p1); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_RAMPOFF: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* NEJ-NEJ */ + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_rampoff(); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + case _GUS_VOLUME_SCALE: + volume_base = p1; + volume_scale = p2; + break; + + case _GUS_VOICE_POS: + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_set_voice_pos(voice, plong); + spin_unlock_irqrestore(&gus_lock,flags); + break; + + default: + break; + } +} + +static int gus_audio_set_speed(int speed) +{ + if (speed <= 0) + speed = gus_audio_speed; + + if (speed < 4000) + speed = 4000; + + if (speed > 44100) + speed = 44100; + + gus_audio_speed = speed; + + if (only_read_access) + { + /* Compute nearest valid recording speed and return it */ + + /* speed = (9878400 / (gus_audio_speed + 2)) / 16; */ + speed = (((9878400 + gus_audio_speed / 2) / (gus_audio_speed + 2)) + 8) / 16; + speed = (9878400 / (speed * 16)) - 2; + } + return speed; +} + +static int gus_audio_set_channels(int channels) +{ + if (!channels) + return gus_audio_channels; + if (channels > 2) + channels = 2; + if (channels < 1) + channels = 1; + gus_audio_channels = channels; + return channels; +} + +static int gus_audio_set_bits(int bits) +{ + if (!bits) + return gus_audio_bits; + + if (bits != 8 && bits != 16) + bits = 8; + + if (only_8_bits) + bits = 8; + + gus_audio_bits = bits; + return bits; +} + +static int gus_audio_ioctl(int dev, unsigned int cmd, void __user *arg) +{ + int val; + + switch (cmd) + { + case SOUND_PCM_WRITE_RATE: + if (get_user(val, (int __user*)arg)) + return -EFAULT; + val = gus_audio_set_speed(val); + break; + + case SOUND_PCM_READ_RATE: + val = gus_audio_speed; + break; + + case SNDCTL_DSP_STEREO: + if (get_user(val, (int __user *)arg)) + return -EFAULT; + val = gus_audio_set_channels(val + 1) - 1; + break; + + case SOUND_PCM_WRITE_CHANNELS: + if (get_user(val, (int __user *)arg)) + return -EFAULT; + val = gus_audio_set_channels(val); + break; + + case SOUND_PCM_READ_CHANNELS: + val = gus_audio_channels; + break; + + case SNDCTL_DSP_SETFMT: + if (get_user(val, (int __user *)arg)) + return -EFAULT; + val = gus_audio_set_bits(val); + break; + + case SOUND_PCM_READ_BITS: + val = gus_audio_bits; + break; + + case SOUND_PCM_WRITE_FILTER: /* NOT POSSIBLE */ + case SOUND_PCM_READ_FILTER: + val = -EINVAL; + break; + default: + return -EINVAL; + } + return put_user(val, (int __user *)arg); +} + +static void gus_audio_reset(int dev) +{ + if (recording_active) + { + gus_write8(0x49, 0x00); /* Halt recording */ + set_input_volumes(); + } +} + +static int saved_iw_mode; /* A hack hack hack */ + +static int gus_audio_open(int dev, int mode) +{ + if (gus_busy) + return -EBUSY; + + if (gus_pnp_flag && mode & OPEN_READ) + { +/* printk(KERN_ERR "GUS: Audio device #%d is playback only.\n", dev);*/ + return -EIO; + } + gus_initialize(); + + gus_busy = 1; + active_device = 0; + + saved_iw_mode = iw_mode; + if (iw_mode) + { + /* There are some problems with audio in enhanced mode so disable it */ + gus_write8(0x19, gus_read8(0x19) & ~0x01); /* Disable enhanced mode */ + iw_mode = 0; + } + + gus_reset(); + reset_sample_memory(); + gus_select_max_voices(14); + + pcm_active = 0; + dma_active = 0; + pcm_opened = 1; + if (mode & OPEN_READ) + { + recording_active = 1; + set_input_volumes(); + } + only_read_access = !(mode & OPEN_WRITE); + only_8_bits = mode & OPEN_READ; + if (only_8_bits) + audio_devs[dev]->format_mask = AFMT_U8; + else + audio_devs[dev]->format_mask = AFMT_U8 | AFMT_S16_LE; + + return 0; +} + +static void gus_audio_close(int dev) +{ + iw_mode = saved_iw_mode; + gus_reset(); + gus_busy = 0; + pcm_opened = 0; + active_device = 0; + + if (recording_active) + { + gus_write8(0x49, 0x00); /* Halt recording */ + set_input_volumes(); + } + recording_active = 0; +} + +static void gus_audio_update_volume(void) +{ + unsigned long flags; + int voice; + + if (pcm_active && pcm_opened) + for (voice = 0; voice < gus_audio_channels; voice++) + { + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_rampoff(); + gus_voice_volume(1530 + (25 * gus_pcm_volume)); + gus_ramp_range(65, 1530 + (25 * gus_pcm_volume)); + spin_unlock_irqrestore(&gus_lock,flags); + } +} + +static void play_next_pcm_block(void) +{ + unsigned long flags; + int speed = gus_audio_speed; + int this_one, is16bits, chn; + unsigned long dram_loc; + unsigned char mode[2], ramp_mode[2]; + + if (!pcm_qlen) + return; + + this_one = pcm_head; + + for (chn = 0; chn < gus_audio_channels; chn++) + { + mode[chn] = 0x00; + ramp_mode[chn] = 0x03; /* Ramping and rollover off */ + + if (chn == 0) + { + mode[chn] |= 0x20; /* Loop IRQ */ + voices[chn].loop_irq_mode = LMODE_PCM; + } + if (gus_audio_bits != 8) + { + is16bits = 1; + mode[chn] |= 0x04; /* 16 bit data */ + } + else + is16bits = 0; + + dram_loc = this_one * pcm_bsize; + dram_loc += chn * pcm_banksize; + + if (this_one == (pcm_nblk - 1)) /* Last fragment of the DRAM buffer */ + { + mode[chn] |= 0x08; /* Enable loop */ + ramp_mode[chn] = 0x03; /* Disable rollover bit */ + } + else + { + if (chn == 0) + ramp_mode[chn] = 0x04; /* Enable rollover bit */ + } + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(chn); + gus_voice_freq(speed); + + if (gus_audio_channels == 1) + gus_voice_balance(7); /* mono */ + else if (chn == 0) + gus_voice_balance(0); /* left */ + else + gus_voice_balance(15); /* right */ + + if (!pcm_active) /* Playback not already active */ + { + /* + * The playback was not started yet (or there has been a pause). + * Start the voice (again) and ask for a rollover irq at the end of + * this_one block. If this_one one is last of the buffers, use just + * the normal loop with irq. + */ + + gus_voice_off(); + gus_rampoff(); + gus_voice_volume(1530 + (25 * gus_pcm_volume)); + gus_ramp_range(65, 1530 + (25 * gus_pcm_volume)); + + gus_write_addr(0x0a, chn * pcm_banksize, 0, is16bits); /* Starting position */ + gus_write_addr(0x02, chn * pcm_banksize, 0, is16bits); /* Loop start */ + + if (chn != 0) + gus_write_addr(0x04, pcm_banksize + (pcm_bsize * pcm_nblk) - 1, + 0, is16bits); /* Loop end location */ + } + if (chn == 0) + gus_write_addr(0x04, dram_loc + pcm_bsize - 1, + 0, is16bits); /* Loop end location */ + else + mode[chn] |= 0x08; /* Enable looping */ + spin_unlock_irqrestore(&gus_lock,flags); + } + for (chn = 0; chn < gus_audio_channels; chn++) + { + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(chn); + gus_write8(0x0d, ramp_mode[chn]); + if (iw_mode) + gus_write8(0x15, 0x00); /* Reset voice deactivate bit of SMSI */ + gus_voice_on(mode[chn]); + spin_unlock_irqrestore(&gus_lock,flags); + } + pcm_active = 1; +} + +static void gus_transfer_output_block(int dev, unsigned long buf, + int total_count, int intrflag, int chn) +{ + /* + * This routine transfers one block of audio data to the DRAM. In mono mode + * it's called just once. When in stereo mode, this_one routine is called + * once for both channels. + * + * The left/mono channel data is transferred to the beginning of dram and the + * right data to the area pointed by gus_page_size. + */ + + int this_one, count; + unsigned long flags; + unsigned char dma_command; + unsigned long address, hold_address; + + spin_lock_irqsave(&gus_lock,flags); + + count = total_count / gus_audio_channels; + + if (chn == 0) + { + if (pcm_qlen >= pcm_nblk) + printk(KERN_WARNING "GUS Warning: PCM buffers out of sync\n"); + + this_one = pcm_current_block = pcm_tail; + pcm_qlen++; + pcm_tail = (pcm_tail + 1) % pcm_nblk; + pcm_datasize[this_one] = count; + } + else + this_one = pcm_current_block; + + gus_write8(0x41, 0); /* Disable GF1 DMA */ + DMAbuf_start_dma(dev, buf + (chn * count), count, DMA_MODE_WRITE); + + address = this_one * pcm_bsize; + address += chn * pcm_banksize; + + if (audio_devs[dev]->dmap_out->dma > 3) + { + hold_address = address; + address = address >> 1; + address &= 0x0001ffffL; + address |= (hold_address & 0x000c0000L); + } + gus_write16(0x42, (address >> 4) & 0xffff); /* DRAM DMA address */ + + dma_command = 0x21; /* IRQ enable, DMA start */ + + if (gus_audio_bits != 8) + dma_command |= 0x40; /* 16 bit _DATA_ */ + else + dma_command |= 0x80; /* Invert MSB */ + + if (audio_devs[dev]->dmap_out->dma > 3) + dma_command |= 0x04; /* 16 bit DMA channel */ + + gus_write8(0x41, dma_command); /* Kick start */ + + if (chn == (gus_audio_channels - 1)) /* Last channel */ + { + /* + * Last (right or mono) channel data + */ + dma_active = 1; /* DMA started. There is a unacknowledged buffer */ + active_device = GUS_DEV_PCM_DONE; + if (!pcm_active && (pcm_qlen > 1 || count < pcm_bsize)) + { + play_next_pcm_block(); + } + } + else + { + /* + * Left channel data. The right channel + * is transferred after DMA interrupt + */ + active_device = GUS_DEV_PCM_CONTINUE; + } + + spin_unlock_irqrestore(&gus_lock,flags); +} + +static void gus_uninterleave8(char *buf, int l) +{ +/* This routine uninterleaves 8 bit stereo output (LRLRLR->LLLRRR) */ + int i, p = 0, halfsize = l / 2; + char *buf2 = buf + halfsize, *src = bounce_buf; + + memcpy(bounce_buf, buf, l); + + for (i = 0; i < halfsize; i++) + { + buf[i] = src[p++]; /* Left channel */ + buf2[i] = src[p++]; /* Right channel */ + } +} + +static void gus_uninterleave16(short *buf, int l) +{ +/* This routine uninterleaves 16 bit stereo output (LRLRLR->LLLRRR) */ + int i, p = 0, halfsize = l / 2; + short *buf2 = buf + halfsize, *src = (short *) bounce_buf; + + memcpy(bounce_buf, (char *) buf, l * 2); + + for (i = 0; i < halfsize; i++) + { + buf[i] = src[p++]; /* Left channel */ + buf2[i] = src[p++]; /* Right channel */ + } +} + +static void gus_audio_output_block(int dev, unsigned long buf, int total_count, + int intrflag) +{ + struct dma_buffparms *dmap = audio_devs[dev]->dmap_out; + + dmap->flags |= DMA_NODMA | DMA_NOTIMEOUT; + + pcm_current_buf = buf; + pcm_current_count = total_count; + pcm_current_intrflag = intrflag; + pcm_current_dev = dev; + if (gus_audio_channels == 2) + { + char *b = dmap->raw_buf + (buf - dmap->raw_buf_phys); + + if (gus_audio_bits == 8) + gus_uninterleave8(b, total_count); + else + gus_uninterleave16((short *) b, total_count / 2); + } + gus_transfer_output_block(dev, buf, total_count, intrflag, 0); +} + +static void gus_audio_start_input(int dev, unsigned long buf, int count, + int intrflag) +{ + unsigned long flags; + unsigned char mode; + + spin_lock_irqsave(&gus_lock,flags); + + DMAbuf_start_dma(dev, buf, count, DMA_MODE_READ); + mode = 0xa0; /* DMA IRQ enabled, invert MSB */ + + if (audio_devs[dev]->dmap_in->dma > 3) + mode |= 0x04; /* 16 bit DMA channel */ + if (gus_audio_channels > 1) + mode |= 0x02; /* Stereo */ + mode |= 0x01; /* DMA enable */ + + gus_write8(0x49, mode); + spin_unlock_irqrestore(&gus_lock,flags); +} + +static int gus_audio_prepare_for_input(int dev, int bsize, int bcount) +{ + unsigned int rate; + + gus_audio_bsize = bsize; + audio_devs[dev]->dmap_in->flags |= DMA_NODMA; + rate = (((9878400 + gus_audio_speed / 2) / (gus_audio_speed + 2)) + 8) / 16; + + gus_write8(0x48, rate & 0xff); /* Set sampling rate */ + + if (gus_audio_bits != 8) + { +/* printk("GUS Error: 16 bit recording not supported\n");*/ + return -EINVAL; + } + return 0; +} + +static int gus_audio_prepare_for_output(int dev, int bsize, int bcount) +{ + int i; + + long mem_ptr, mem_size; + + audio_devs[dev]->dmap_out->flags |= DMA_NODMA | DMA_NOTIMEOUT; + mem_ptr = 0; + mem_size = gus_mem_size / gus_audio_channels; + + if (mem_size > (256 * 1024)) + mem_size = 256 * 1024; + + pcm_bsize = bsize / gus_audio_channels; + pcm_head = pcm_tail = pcm_qlen = 0; + + pcm_nblk = 2; /* MAX_PCM_BUFFERS; */ + if ((pcm_bsize * pcm_nblk) > mem_size) + pcm_nblk = mem_size / pcm_bsize; + + for (i = 0; i < pcm_nblk; i++) + pcm_datasize[i] = 0; + + pcm_banksize = pcm_nblk * pcm_bsize; + + if (gus_audio_bits != 8 && pcm_banksize == (256 * 1024)) + pcm_nblk--; + gus_write8(0x41, 0); /* Disable GF1 DMA */ + return 0; +} + +static int gus_local_qlen(int dev) +{ + return pcm_qlen; +} + + +static struct audio_driver gus_audio_driver = +{ + .owner = THIS_MODULE, + .open = gus_audio_open, + .close = gus_audio_close, + .output_block = gus_audio_output_block, + .start_input = gus_audio_start_input, + .ioctl = gus_audio_ioctl, + .prepare_for_input = gus_audio_prepare_for_input, + .prepare_for_output = gus_audio_prepare_for_output, + .halt_io = gus_audio_reset, + .local_qlen = gus_local_qlen, +}; + +static void guswave_setup_voice(int dev, int voice, int chn) +{ + struct channel_info *info = &synth_devs[dev]->chn_info[chn]; + + guswave_set_instr(dev, voice, info->pgm_num); + voices[voice].expression_vol = info->controllers[CTL_EXPRESSION]; /* Just MSB */ + voices[voice].main_vol = (info->controllers[CTL_MAIN_VOLUME] * 100) / (unsigned) 128; + voices[voice].panning = (info->controllers[CTL_PAN] * 2) - 128; + voices[voice].bender = 0; + voices[voice].bender_range = info->bender_range; + + if (chn == 9) + voices[voice].fixed_pitch = 1; +} + +static void guswave_bender(int dev, int voice, int value) +{ + int freq; + unsigned long flags; + + voices[voice].bender = value - 8192; + freq = compute_finetune(voices[voice].orig_freq, value - 8192, voices[voice].bender_range, 0); + voices[voice].current_freq = freq; + + spin_lock_irqsave(&gus_lock,flags); + gus_select_voice(voice); + gus_voice_freq(freq); + spin_unlock_irqrestore(&gus_lock,flags); +} + +static int guswave_alloc(int dev, int chn, int note, struct voice_alloc_info *alloc) +{ + int i, p, best = -1, best_time = 0x7fffffff; + + p = alloc->ptr; + /* + * First look for a completely stopped voice + */ + + for (i = 0; i < alloc->max_voice; i++) + { + if (alloc->map[p] == 0) + { + alloc->ptr = p; + return p; + } + if (alloc->alloc_times[p] < best_time) + { + best = p; + best_time = alloc->alloc_times[p]; + } + p = (p + 1) % alloc->max_voice; + } + + /* + * Then look for a releasing voice + */ + + for (i = 0; i < alloc->max_voice; i++) + { + if (alloc->map[p] == 0xffff) + { + alloc->ptr = p; + return p; + } + p = (p + 1) % alloc->max_voice; + } + if (best >= 0) + p = best; + + alloc->ptr = p; + return p; +} + +static struct synth_operations guswave_operations = +{ + .owner = THIS_MODULE, + .id = "GUS", + .info = &gus_info, + .midi_dev = 0, + .synth_type = SYNTH_TYPE_SAMPLE, + .synth_subtype = SAMPLE_TYPE_GUS, + .open = guswave_open, + .close = guswave_close, + .ioctl = guswave_ioctl, + .kill_note = guswave_kill_note, + .start_note = guswave_start_note, + .set_instr = guswave_set_instr, + .reset = guswave_reset, + .hw_control = guswave_hw_control, + .load_patch = guswave_load_patch, + .aftertouch = guswave_aftertouch, + .controller = guswave_controller, + .panning = guswave_panning, + .volume_method = guswave_volume_method, + .bender = guswave_bender, + .alloc_voice = guswave_alloc, + .setup_voice = guswave_setup_voice +}; + +static void set_input_volumes(void) +{ + unsigned long flags; + unsigned char mask = 0xff & ~0x06; /* Just line out enabled */ + + if (have_gus_max) /* Don't disturb GUS MAX */ + return; + + spin_lock_irqsave(&gus_lock,flags); + + /* + * Enable channels having vol > 10% + * Note! bit 0x01 means the line in DISABLED while 0x04 means + * the mic in ENABLED. + */ + if (gus_line_vol > 10) + mask &= ~0x01; + if (gus_mic_vol > 10) + mask |= 0x04; + + if (recording_active) + { + /* + * Disable channel, if not selected for recording + */ + if (!(gus_recmask & SOUND_MASK_LINE)) + mask |= 0x01; + if (!(gus_recmask & SOUND_MASK_MIC)) + mask &= ~0x04; + } + mix_image &= ~0x07; + mix_image |= mask & 0x07; + outb((mix_image), u_Mixer); + + spin_unlock_irqrestore(&gus_lock,flags); +} + +#define MIX_DEVS (SOUND_MASK_MIC|SOUND_MASK_LINE| \ + SOUND_MASK_SYNTH|SOUND_MASK_PCM) + +int gus_default_mixer_ioctl(int dev, unsigned int cmd, void __user *arg) +{ + int vol, val; + + if (((cmd >> 8) & 0xff) != 'M') + return -EINVAL; + + if (!access_ok(VERIFY_WRITE, arg, sizeof(int))) + return -EFAULT; + + if (_SIOC_DIR(cmd) & _SIOC_WRITE) + { + if (__get_user(val, (int __user *) arg)) + return -EFAULT; + + switch (cmd & 0xff) + { + case SOUND_MIXER_RECSRC: + gus_recmask = val & MIX_DEVS; + if (!(gus_recmask & (SOUND_MASK_MIC | SOUND_MASK_LINE))) + gus_recmask = SOUND_MASK_MIC; + /* Note! Input volumes are updated during next open for recording */ + val = gus_recmask; + break; + + case SOUND_MIXER_MIC: + vol = val & 0xff; + if (vol < 0) + vol = 0; + if (vol > 100) + vol = 100; + gus_mic_vol = vol; + set_input_volumes(); + val = vol | (vol << 8); + break; + + case SOUND_MIXER_LINE: + vol = val & 0xff; + if (vol < 0) + vol = 0; + if (vol > 100) + vol = 100; + gus_line_vol = vol; + set_input_volumes(); + val = vol | (vol << 8); + break; + + case SOUND_MIXER_PCM: + gus_pcm_volume = val & 0xff; + if (gus_pcm_volume < 0) + gus_pcm_volume = 0; + if (gus_pcm_volume > 100) + gus_pcm_volume = 100; + gus_audio_update_volume(); + val = gus_pcm_volume | (gus_pcm_volume << 8); + break; + + case SOUND_MIXER_SYNTH: + gus_wave_volume = val & 0xff; + if (gus_wave_volume < 0) + gus_wave_volume = 0; + if (gus_wave_volume > 100) + gus_wave_volume = 100; + if (active_device == GUS_DEV_WAVE) + { + int voice; + for (voice = 0; voice < nr_voices; voice++) + dynamic_volume_change(voice); /* Apply the new vol */ + } + val = gus_wave_volume | (gus_wave_volume << 8); + break; + + default: + return -EINVAL; + } + } + else + { + switch (cmd & 0xff) + { + /* + * Return parameters + */ + case SOUND_MIXER_RECSRC: + val = gus_recmask; + break; + + case SOUND_MIXER_DEVMASK: + val = MIX_DEVS; + break; + + case SOUND_MIXER_STEREODEVS: + val = 0; + break; + + case SOUND_MIXER_RECMASK: + val = SOUND_MASK_MIC | SOUND_MASK_LINE; + break; + + case SOUND_MIXER_CAPS: + val = 0; + break; + + case SOUND_MIXER_MIC: + val = gus_mic_vol | (gus_mic_vol << 8); + break; + + case SOUND_MIXER_LINE: + val = gus_line_vol | (gus_line_vol << 8); + break; + + case SOUND_MIXER_PCM: + val = gus_pcm_volume | (gus_pcm_volume << 8); + break; + + case SOUND_MIXER_SYNTH: + val = gus_wave_volume | (gus_wave_volume << 8); + break; + + default: + return -EINVAL; + } + } + return __put_user(val, (int __user *)arg); +} + +static struct mixer_operations gus_mixer_operations = +{ + .owner = THIS_MODULE, + .id = "GUS", + .name = "Gravis Ultrasound", + .ioctl = gus_default_mixer_ioctl +}; + +static int __init gus_default_mixer_init(void) +{ + int n; + + if ((n = sound_alloc_mixerdev()) != -1) + { + /* + * Don't install if there is another + * mixer + */ + mixer_devs[n] = &gus_mixer_operations; + } + if (have_gus_max) + { + /* + * Enable all mixer channels on the GF1 side. Otherwise recording will + * not be possible using GUS MAX. + */ + mix_image &= ~0x07; + mix_image |= 0x04; /* All channels enabled */ + outb((mix_image), u_Mixer); + } + return n; +} + +void __init gus_wave_init(struct address_info *hw_config) +{ + unsigned long flags; + unsigned char val; + char *model_num = "2.4"; + char tmp[64]; + int gus_type = 0x24; /* 2.4 */ + + int irq = hw_config->irq, dma = hw_config->dma, dma2 = hw_config->dma2; + int sdev; + + hw_config->slots[0] = -1; /* No wave */ + hw_config->slots[1] = -1; /* No ad1848 */ + hw_config->slots[4] = -1; /* No audio */ + hw_config->slots[5] = -1; /* No mixer */ + + if (!gus_pnp_flag) + { + if (irq < 0 || irq > 15) + { + printk(KERN_ERR "ERROR! Invalid IRQ#%d. GUS Disabled", irq); + return; + } + } + + if (dma < 0 || dma > 7 || dma == 4) + { + printk(KERN_ERR "ERROR! Invalid DMA#%d. GUS Disabled", dma); + return; + } + gus_irq = irq; + gus_dma = dma; + gus_dma2 = dma2; + gus_hw_config = hw_config; + + if (gus_dma2 == -1) + gus_dma2 = dma; + + /* + * Try to identify the GUS model. + * + * Versions < 3.6 don't have the digital ASIC. Try to probe it first. + */ + + spin_lock_irqsave(&gus_lock,flags); + outb((0x20), gus_base + 0x0f); + val = inb(gus_base + 0x0f); + spin_unlock_irqrestore(&gus_lock,flags); + + if (gus_pnp_flag || (val != 0xff && (val & 0x06))) /* Should be 0x02?? */ + { + int ad_flags = 0; + + if (gus_pnp_flag) + ad_flags = 0x12345678; /* Interwave "magic" */ + /* + * It has the digital ASIC so the card is at least v3.4. + * Next try to detect the true model. + */ + + if (gus_pnp_flag) /* Hack hack hack */ + val = 10; + else + val = inb(u_MixSelect); + + /* + * Value 255 means pre-3.7 which don't have mixer. + * Values 5 thru 9 mean v3.7 which has a ICS2101 mixer. + * 10 and above is GUS MAX which has the CS4231 codec/mixer. + * + */ + + if (val == 255 || val < 5) + { + model_num = "3.4"; + gus_type = 0x34; + } + else if (val < 10) + { + model_num = "3.7"; + gus_type = 0x37; + mixer_type = ICS2101; + request_region(u_MixSelect, 1, "GUS mixer"); + } + else + { + struct resource *ports; + ports = request_region(gus_base + 0x10c, 4, "ad1848"); + model_num = "MAX"; + gus_type = 0x40; + mixer_type = CS4231; +#ifdef CONFIG_SOUND_GUSMAX + { + unsigned char max_config = 0x40; /* Codec enable */ + + if (gus_dma2 == -1) + gus_dma2 = gus_dma; + + if (gus_dma > 3) + max_config |= 0x10; /* 16 bit capture DMA */ + + if (gus_dma2 > 3) + max_config |= 0x20; /* 16 bit playback DMA */ + + max_config |= (gus_base >> 4) & 0x0f; /* Extract the X from 2X0 */ + + outb((max_config), gus_base + 0x106); /* UltraMax control */ + } + + if (!ports) + goto no_cs4231; + + if (ad1848_detect(ports, &ad_flags, hw_config->osp)) + { + char *name = "GUS MAX"; + int old_num_mixers = num_mixers; + + if (gus_pnp_flag) + name = "GUS PnP"; + + gus_mic_vol = gus_line_vol = gus_pcm_volume = 100; + gus_wave_volume = 90; + have_gus_max = 1; + if (hw_config->name) + name = hw_config->name; + + hw_config->slots[1] = ad1848_init(name, ports, + -irq, gus_dma2, /* Playback DMA */ + gus_dma, /* Capture DMA */ + 1, /* Share DMA channels with GF1 */ + hw_config->osp, + THIS_MODULE); + + if (num_mixers > old_num_mixers) + { + /* GUS has it's own mixer map */ + AD1848_REROUTE(SOUND_MIXER_LINE1, SOUND_MIXER_SYNTH); + AD1848_REROUTE(SOUND_MIXER_LINE2, SOUND_MIXER_CD); + AD1848_REROUTE(SOUND_MIXER_LINE3, SOUND_MIXER_LINE); + } + } + else { + release_region(gus_base + 0x10c, 4); + no_cs4231: + printk(KERN_WARNING "GUS: No CS4231 ??"); + } +#else + printk(KERN_ERR "GUS MAX found, but not compiled in\n"); +#endif + } + } + else + { + /* + * ASIC not detected so the card must be 2.2 or 2.4. + * There could still be the 16-bit/mixer daughter card. + */ + } + + if (hw_config->name) + snprintf(tmp, sizeof(tmp), "%s (%dk)", hw_config->name, + (int) gus_mem_size / 1024); + else if (gus_pnp_flag) + snprintf(tmp, sizeof(tmp), "Gravis UltraSound PnP (%dk)", + (int) gus_mem_size / 1024); + else + snprintf(tmp, sizeof(tmp), "Gravis UltraSound %s (%dk)", model_num, + (int) gus_mem_size / 1024); + + + samples = (struct patch_info *)vmalloc((MAX_SAMPLE + 1) * sizeof(*samples)); + if (samples == NULL) + { + printk(KERN_WARNING "gus_init: Cant allocate memory for instrument tables\n"); + return; + } + conf_printf(tmp, hw_config); + strlcpy(gus_info.name, tmp, sizeof(gus_info.name)); + + if ((sdev = sound_alloc_synthdev()) == -1) + printk(KERN_WARNING "gus_init: Too many synthesizers\n"); + else + { + voice_alloc = &guswave_operations.alloc; + if (iw_mode) + guswave_operations.id = "IWAVE"; + hw_config->slots[0] = sdev; + synth_devs[sdev] = &guswave_operations; + sequencer_init(); + gus_tmr_install(gus_base + 8); + } + + reset_sample_memory(); + + gus_initialize(); + + if ((gus_mem_size > 0) && !gus_no_wave_dma) + { + hw_config->slots[4] = -1; + if ((gus_devnum = sound_install_audiodrv(AUDIO_DRIVER_VERSION, + "Ultrasound", + &gus_audio_driver, + sizeof(struct audio_driver), + NEEDS_RESTART | + ((!iw_mode && dma2 != dma && dma2 != -1) ? + DMA_DUPLEX : 0), + AFMT_U8 | AFMT_S16_LE, + NULL, dma, dma2)) < 0) + { + return; + } + + hw_config->slots[4] = gus_devnum; + audio_devs[gus_devnum]->min_fragment = 9; /* 512k */ + audio_devs[gus_devnum]->max_fragment = 11; /* 8k (must match size of bounce_buf */ + audio_devs[gus_devnum]->mixer_dev = -1; /* Next mixer# */ + audio_devs[gus_devnum]->flags |= DMA_HARDSTOP; + } + + /* + * Mixer dependent initialization. + */ + + switch (mixer_type) + { + case ICS2101: + gus_mic_vol = gus_line_vol = gus_pcm_volume = 100; + gus_wave_volume = 90; + request_region(u_MixSelect, 1, "GUS mixer"); + hw_config->slots[5] = ics2101_mixer_init(); + audio_devs[gus_devnum]->mixer_dev = hw_config->slots[5]; /* Next mixer# */ + return; + + case CS4231: + /* Initialized elsewhere (ad1848.c) */ + default: + hw_config->slots[5] = gus_default_mixer_init(); + audio_devs[gus_devnum]->mixer_dev = hw_config->slots[5]; /* Next mixer# */ + return; + } +} + +void __exit gus_wave_unload(struct address_info *hw_config) +{ +#ifdef CONFIG_SOUND_GUSMAX + if (have_gus_max) + { + ad1848_unload(gus_base + 0x10c, + -gus_irq, + gus_dma2, /* Playback DMA */ + gus_dma, /* Capture DMA */ + 1); /* Share DMA channels with GF1 */ + } +#endif + + if (mixer_type == ICS2101) + { + release_region(u_MixSelect, 1); + } + if (hw_config->slots[0] != -1) + sound_unload_synthdev(hw_config->slots[0]); + if (hw_config->slots[1] != -1) + sound_unload_audiodev(hw_config->slots[1]); + if (hw_config->slots[2] != -1) + sound_unload_mididev(hw_config->slots[2]); + if (hw_config->slots[4] != -1) + sound_unload_audiodev(hw_config->slots[4]); + if (hw_config->slots[5] != -1) + sound_unload_mixerdev(hw_config->slots[5]); + + vfree(samples); + samples=NULL; +} +/* called in interrupt context */ +static void do_loop_irq(int voice) +{ + unsigned char tmp; + int mode, parm; + + spin_lock(&gus_lock); + gus_select_voice(voice); + + tmp = gus_read8(0x00); + tmp &= ~0x20; /* + * Disable wave IRQ for this_one voice + */ + gus_write8(0x00, tmp); + + if (tmp & 0x03) /* Voice stopped */ + voice_alloc->map[voice] = 0; + + mode = voices[voice].loop_irq_mode; + voices[voice].loop_irq_mode = 0; + parm = voices[voice].loop_irq_parm; + + switch (mode) + { + case LMODE_FINISH: /* + * Final loop finished, shoot volume down + */ + + if ((int) (gus_read16(0x09) >> 4) < 100) /* + * Get current volume + */ + { + gus_voice_off(); + gus_rampoff(); + gus_voice_init(voice); + break; + } + gus_ramp_range(65, 4065); + gus_ramp_rate(0, 63); /* + * Fastest possible rate + */ + gus_rampon(0x20 | 0x40); /* + * Ramp down, once, irq + */ + voices[voice].volume_irq_mode = VMODE_HALT; + break; + + case LMODE_PCM_STOP: + pcm_active = 0; /* Signal to the play_next_pcm_block routine */ + case LMODE_PCM: + { + pcm_qlen--; + pcm_head = (pcm_head + 1) % pcm_nblk; + if (pcm_qlen && pcm_active) + { + play_next_pcm_block(); + } + else + { + /* Underrun. Just stop the voice */ + gus_select_voice(0); /* Left channel */ + gus_voice_off(); + gus_rampoff(); + gus_select_voice(1); /* Right channel */ + gus_voice_off(); + gus_rampoff(); + pcm_active = 0; + } + + /* + * If the queue was full before this interrupt, the DMA transfer was + * suspended. Let it continue now. + */ + + if (audio_devs[gus_devnum]->dmap_out->qlen > 0) + DMAbuf_outputintr(gus_devnum, 0); + } + break; + + default: + break; + } + spin_unlock(&gus_lock); +} + +static void do_volume_irq(int voice) +{ + unsigned char tmp; + int mode, parm; + unsigned long flags; + + spin_lock_irqsave(&gus_lock,flags); + + gus_select_voice(voice); + tmp = gus_read8(0x0d); + tmp &= ~0x20; /* + * Disable volume ramp IRQ + */ + gus_write8(0x0d, tmp); + + mode = voices[voice].volume_irq_mode; + voices[voice].volume_irq_mode = 0; + parm = voices[voice].volume_irq_parm; + + switch (mode) + { + case VMODE_HALT: /* Decay phase finished */ + if (iw_mode) + gus_write8(0x15, 0x02); /* Set voice deactivate bit of SMSI */ + spin_unlock_irqrestore(&gus_lock,flags); + gus_voice_init(voice); + break; + + case VMODE_ENVELOPE: + gus_rampoff(); + spin_unlock_irqrestore(&gus_lock,flags); + step_envelope(voice); + break; + + case VMODE_START_NOTE: + spin_unlock_irqrestore(&gus_lock,flags); + guswave_start_note2(voices[voice].dev_pending, voice, + voices[voice].note_pending, voices[voice].volume_pending); + if (voices[voice].kill_pending) + guswave_kill_note(voices[voice].dev_pending, voice, + voices[voice].note_pending, 0); + + if (voices[voice].sample_pending >= 0) + { + guswave_set_instr(voices[voice].dev_pending, voice, + voices[voice].sample_pending); + voices[voice].sample_pending = -1; + } + break; + + default: + spin_unlock_irqrestore(&gus_lock,flags); + } +} +/* called in irq context */ +void gus_voice_irq(void) +{ + unsigned long wave_ignore = 0, volume_ignore = 0; + unsigned long voice_bit; + + unsigned char src, voice; + + while (1) + { + src = gus_read8(0x0f); /* + * Get source info + */ + voice = src & 0x1f; + src &= 0xc0; + + if (src == (0x80 | 0x40)) + return; /* + * No interrupt + */ + + voice_bit = 1 << voice; + + if (!(src & 0x80)) /* + * Wave IRQ pending + */ + if (!(wave_ignore & voice_bit) && (int) voice < nr_voices) /* + * Not done + * yet + */ + { + wave_ignore |= voice_bit; + do_loop_irq(voice); + } + if (!(src & 0x40)) /* + * Volume IRQ pending + */ + if (!(volume_ignore & voice_bit) && (int) voice < nr_voices) /* + * Not done + * yet + */ + { + volume_ignore |= voice_bit; + do_volume_irq(voice); + } + } +} + +void guswave_dma_irq(void) +{ + unsigned char status; + + status = gus_look8(0x41); /* Get DMA IRQ Status */ + if (status & 0x40) /* DMA interrupt pending */ + switch (active_device) + { + case GUS_DEV_WAVE: + wake_up(&dram_sleeper); + break; + + case GUS_DEV_PCM_CONTINUE: /* Left channel data transferred */ + gus_write8(0x41, 0); /* Disable GF1 DMA */ + gus_transfer_output_block(pcm_current_dev, pcm_current_buf, + pcm_current_count, + pcm_current_intrflag, 1); + break; + + case GUS_DEV_PCM_DONE: /* Right or mono channel data transferred */ + gus_write8(0x41, 0); /* Disable GF1 DMA */ + if (pcm_qlen < pcm_nblk) + { + dma_active = 0; + if (gus_busy) + { + if (audio_devs[gus_devnum]->dmap_out->qlen > 0) + DMAbuf_outputintr(gus_devnum, 0); + } + } + break; + + default: + break; + } + status = gus_look8(0x49); /* + * Get Sampling IRQ Status + */ + if (status & 0x40) /* + * Sampling Irq pending + */ + { + DMAbuf_inputintr(gus_devnum); + } +} + +/* + * Timer stuff + */ + +static volatile int select_addr, data_addr; +static volatile int curr_timer; + +void gus_timer_command(unsigned int addr, unsigned int val) +{ + int i; + + outb(((unsigned char) (addr & 0xff)), select_addr); + + for (i = 0; i < 2; i++) + inb(select_addr); + + outb(((unsigned char) (val & 0xff)), data_addr); + + for (i = 0; i < 2; i++) + inb(select_addr); +} + +static void arm_timer(int timer, unsigned int interval) +{ + curr_timer = timer; + + if (timer == 1) + { + gus_write8(0x46, 256 - interval); /* Set counter for timer 1 */ + gus_write8(0x45, 0x04); /* Enable timer 1 IRQ */ + gus_timer_command(0x04, 0x01); /* Start timer 1 */ + } + else + { + gus_write8(0x47, 256 - interval); /* Set counter for timer 2 */ + gus_write8(0x45, 0x08); /* Enable timer 2 IRQ */ + gus_timer_command(0x04, 0x02); /* Start timer 2 */ + } + + gus_timer_enabled = 1; +} + +static unsigned int gus_tmr_start(int dev, unsigned int usecs_per_tick) +{ + int timer_no, resolution; + int divisor; + + if (usecs_per_tick > (256 * 80)) + { + timer_no = 2; + resolution = 320; /* usec */ + } + else + { + timer_no = 1; + resolution = 80; /* usec */ + } + divisor = (usecs_per_tick + (resolution / 2)) / resolution; + arm_timer(timer_no, divisor); + + return divisor * resolution; +} + +static void gus_tmr_disable(int dev) +{ + gus_write8(0x45, 0); /* Disable both timers */ + gus_timer_enabled = 0; +} + +static void gus_tmr_restart(int dev) +{ + if (curr_timer == 1) + gus_write8(0x45, 0x04); /* Start timer 1 again */ + else + gus_write8(0x45, 0x08); /* Start timer 2 again */ + gus_timer_enabled = 1; +} + +static struct sound_lowlev_timer gus_tmr = +{ + 0, + 1, + gus_tmr_start, + gus_tmr_disable, + gus_tmr_restart +}; + +static void gus_tmr_install(int io_base) +{ + struct sound_lowlev_timer *tmr; + + select_addr = io_base; + data_addr = io_base + 1; + + tmr = &gus_tmr; + +#ifdef THIS_GETS_FIXED + sound_timer_init(&gus_tmr, "GUS"); +#endif +} diff --git a/sound/oss/hal2.c b/sound/oss/hal2.c new file mode 100644 index 000000000000..afe97c4ce069 --- /dev/null +++ b/sound/oss/hal2.c @@ -0,0 +1,1557 @@ +/* + * Driver for A2 audio system used in SGI machines + * Copyright (c) 2001, 2002, 2003 Ladislav Michl + * + * Based on Ulf Carlsson's code. + * + * 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. + * + * 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. + * + * Supported devices: + * /dev/dsp standard dsp device, (mostly) OSS compatible + * /dev/mixer standard mixer device, (mostly) OSS compatible + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "hal2.h" + +#if 0 +#define DEBUG(args...) printk(args) +#else +#define DEBUG(args...) +#endif + +#if 0 +#define DEBUG_MIX(args...) printk(args) +#else +#define DEBUG_MIX(args...) +#endif + +/* + * Before touching these look how it works. It is a bit unusual I know, + * but it helps to keep things simple. This driver is considered complete + * and I won't add any new features although hardware has many cool + * capabilities. + * (Historical note: HAL2 driver was first written by Ulf Carlsson - ALSA + * 0.3 running with 2.2.x kernel. Then ALSA changed completely and it + * seemed easier to me to write OSS driver from scratch - this one. Now + * when ALSA is official part of 2.6 kernel it's time to write ALSA driver + * using (hopefully) final version of ALSA interface) + */ +#define H2_BLOCK_SIZE 1024 +#define H2_ADC_BUFSIZE 8192 +#define H2_DAC_BUFSIZE 16834 + +struct hal2_pbus { + struct hpc3_pbus_dmacregs *pbus; + int pbusnr; + unsigned int ctrl; /* Current state of pbus->pbdma_ctrl */ +}; + +struct hal2_desc { + struct hpc_dma_desc desc; + u32 cnt; /* don't touch, it is also padding */ +}; + +struct hal2_codec { + unsigned char *buffer; + struct hal2_desc *desc; + int desc_count; + int tail, head; /* tail index, head index */ + struct hal2_pbus pbus; + unsigned int format; /* Audio data format */ + int voices; /* mono/stereo */ + unsigned int sample_rate; + unsigned int master; /* Master frequency */ + unsigned short mod; /* MOD value */ + unsigned short inc; /* INC value */ + + wait_queue_head_t dma_wait; + spinlock_t lock; + struct semaphore sem; + + int usecount; /* recording and playback are + * independent */ +}; + +#define H2_MIX_OUTPUT_ATT 0 +#define H2_MIX_INPUT_GAIN 1 +#define H2_MIXERS 2 +struct hal2_mixer { + int modcnt; + unsigned int master; + unsigned int volume[H2_MIXERS]; +}; + +struct hal2_card { + int dev_dsp; /* audio device */ + int dev_mixer; /* mixer device */ + int dev_midi; /* midi device */ + + struct hal2_ctl_regs *ctl_regs; /* HAL2 ctl registers */ + struct hal2_aes_regs *aes_regs; /* HAL2 aes registers */ + struct hal2_vol_regs *vol_regs; /* HAL2 vol registers */ + struct hal2_syn_regs *syn_regs; /* HAL2 syn registers */ + + struct hal2_codec dac; + struct hal2_codec adc; + struct hal2_mixer mixer; +}; + +#define H2_INDIRECT_WAIT(regs) while (regs->isr & H2_ISR_TSTATUS); + +#define H2_READ_ADDR(addr) (addr | (1<<7)) +#define H2_WRITE_ADDR(addr) (addr) + +static char *hal2str = "HAL2"; + +/* + * I doubt anyone has a machine with two HAL2 cards. It's possible to + * have two HPC's, so it is probably possible to have two HAL2 cards. + * Try to deal with it, but note that it is not tested. + */ +#define MAXCARDS 2 +static struct hal2_card* hal2_card[MAXCARDS]; + +static const struct { + unsigned char idx:4, avail:1; +} mixtable[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_PCM] = { H2_MIX_OUTPUT_ATT, 1 }, /* voice */ + [SOUND_MIXER_MIC] = { H2_MIX_INPUT_GAIN, 1 }, /* mic */ +}; + +#define H2_SUPPORTED_FORMATS (AFMT_S16_LE | AFMT_S16_BE) + +static inline void hal2_isr_write(struct hal2_card *hal2, u16 val) +{ + hal2->ctl_regs->isr = val; +} + +static inline u16 hal2_isr_look(struct hal2_card *hal2) +{ + return hal2->ctl_regs->isr; +} + +static inline u16 hal2_rev_look(struct hal2_card *hal2) +{ + return hal2->ctl_regs->rev; +} + +#ifdef HAL2_DUMP_REGS +static u16 hal2_i_look16(struct hal2_card *hal2, u16 addr) +{ + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + regs->iar = H2_READ_ADDR(addr); + H2_INDIRECT_WAIT(regs); + return regs->idr0; +} +#endif + +static u32 hal2_i_look32(struct hal2_card *hal2, u16 addr) +{ + u32 ret; + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + regs->iar = H2_READ_ADDR(addr); + H2_INDIRECT_WAIT(regs); + ret = regs->idr0 & 0xffff; + regs->iar = H2_READ_ADDR(addr | 0x1); + H2_INDIRECT_WAIT(regs); + ret |= (regs->idr0 & 0xffff) << 16; + return ret; +} + +static void hal2_i_write16(struct hal2_card *hal2, u16 addr, u16 val) +{ + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + regs->idr0 = val; + regs->idr1 = 0; + regs->idr2 = 0; + regs->idr3 = 0; + regs->iar = H2_WRITE_ADDR(addr); + H2_INDIRECT_WAIT(regs); +} + +static void hal2_i_write32(struct hal2_card *hal2, u16 addr, u32 val) +{ + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + regs->idr0 = val & 0xffff; + regs->idr1 = val >> 16; + regs->idr2 = 0; + regs->idr3 = 0; + regs->iar = H2_WRITE_ADDR(addr); + H2_INDIRECT_WAIT(regs); +} + +static void hal2_i_setbit16(struct hal2_card *hal2, u16 addr, u16 bit) +{ + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + regs->iar = H2_READ_ADDR(addr); + H2_INDIRECT_WAIT(regs); + regs->idr0 = (regs->idr0 & 0xffff) | bit; + regs->idr1 = 0; + regs->idr2 = 0; + regs->idr3 = 0; + regs->iar = H2_WRITE_ADDR(addr); + H2_INDIRECT_WAIT(regs); +} + +static void hal2_i_setbit32(struct hal2_card *hal2, u16 addr, u32 bit) +{ + u32 tmp; + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + regs->iar = H2_READ_ADDR(addr); + H2_INDIRECT_WAIT(regs); + tmp = (regs->idr0 & 0xffff) | (regs->idr1 << 16) | bit; + regs->idr0 = tmp & 0xffff; + regs->idr1 = tmp >> 16; + regs->idr2 = 0; + regs->idr3 = 0; + regs->iar = H2_WRITE_ADDR(addr); + H2_INDIRECT_WAIT(regs); +} + +static void hal2_i_clearbit16(struct hal2_card *hal2, u16 addr, u16 bit) +{ + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + regs->iar = H2_READ_ADDR(addr); + H2_INDIRECT_WAIT(regs); + regs->idr0 = (regs->idr0 & 0xffff) & ~bit; + regs->idr1 = 0; + regs->idr2 = 0; + regs->idr3 = 0; + regs->iar = H2_WRITE_ADDR(addr); + H2_INDIRECT_WAIT(regs); +} + +#if 0 +static void hal2_i_clearbit32(struct hal2_card *hal2, u16 addr, u32 bit) +{ + u32 tmp; + hal2_ctl_regs_t *regs = hal2->ctl_regs; + + regs->iar = H2_READ_ADDR(addr); + H2_INDIRECT_WAIT(regs); + tmp = ((regs->idr0 & 0xffff) | (regs->idr1 << 16)) & ~bit; + regs->idr0 = tmp & 0xffff; + regs->idr1 = tmp >> 16; + regs->idr2 = 0; + regs->idr3 = 0; + regs->iar = H2_WRITE_ADDR(addr); + H2_INDIRECT_WAIT(regs); +} +#endif + +#ifdef HAL2_DUMP_REGS +static void hal2_dump_regs(struct hal2_card *hal2) +{ + DEBUG("isr: %08hx ", hal2_isr_look(hal2)); + DEBUG("rev: %08hx\n", hal2_rev_look(hal2)); + DEBUG("relay: %04hx\n", hal2_i_look16(hal2, H2I_RELAY_C)); + DEBUG("port en: %04hx ", hal2_i_look16(hal2, H2I_DMA_PORT_EN)); + DEBUG("dma end: %04hx ", hal2_i_look16(hal2, H2I_DMA_END)); + DEBUG("dma drv: %04hx\n", hal2_i_look16(hal2, H2I_DMA_DRV)); + DEBUG("syn ctl: %04hx ", hal2_i_look16(hal2, H2I_SYNTH_C)); + DEBUG("aesrx ctl: %04hx ", hal2_i_look16(hal2, H2I_AESRX_C)); + DEBUG("aestx ctl: %04hx ", hal2_i_look16(hal2, H2I_AESTX_C)); + DEBUG("dac ctl1: %04hx ", hal2_i_look16(hal2, H2I_ADC_C1)); + DEBUG("dac ctl2: %08x ", hal2_i_look32(hal2, H2I_ADC_C2)); + DEBUG("adc ctl1: %04hx ", hal2_i_look16(hal2, H2I_DAC_C1)); + DEBUG("adc ctl2: %08x ", hal2_i_look32(hal2, H2I_DAC_C2)); + DEBUG("syn map: %04hx\n", hal2_i_look16(hal2, H2I_SYNTH_MAP_C)); + DEBUG("bres1 ctl1: %04hx ", hal2_i_look16(hal2, H2I_BRES1_C1)); + DEBUG("bres1 ctl2: %04x ", hal2_i_look32(hal2, H2I_BRES1_C2)); + DEBUG("bres2 ctl1: %04hx ", hal2_i_look16(hal2, H2I_BRES2_C1)); + DEBUG("bres2 ctl2: %04x ", hal2_i_look32(hal2, H2I_BRES2_C2)); + DEBUG("bres3 ctl1: %04hx ", hal2_i_look16(hal2, H2I_BRES3_C1)); + DEBUG("bres3 ctl2: %04x\n", hal2_i_look32(hal2, H2I_BRES3_C2)); +} +#endif + +static struct hal2_card* hal2_dsp_find_card(int minor) +{ + int i; + + for (i = 0; i < MAXCARDS; i++) + if (hal2_card[i] != NULL && hal2_card[i]->dev_dsp == minor) + return hal2_card[i]; + return NULL; +} + +static struct hal2_card* hal2_mixer_find_card(int minor) +{ + int i; + + for (i = 0; i < MAXCARDS; i++) + if (hal2_card[i] != NULL && hal2_card[i]->dev_mixer == minor) + return hal2_card[i]; + return NULL; +} + +static void hal2_inc_head(struct hal2_codec *codec) +{ + codec->head++; + if (codec->head == codec->desc_count) + codec->head = 0; +} + +static void hal2_inc_tail(struct hal2_codec *codec) +{ + codec->tail++; + if (codec->tail == codec->desc_count) + codec->tail = 0; +} + +static void hal2_dac_interrupt(struct hal2_codec *dac) +{ + int running; + + spin_lock(&dac->lock); + /* if tail buffer contains zero samples DMA stream was already + * stopped */ + running = dac->desc[dac->tail].cnt; + dac->desc[dac->tail].cnt = 0; + dac->desc[dac->tail].desc.cntinfo = HPCDMA_XIE | HPCDMA_EOX; + /* we just proccessed empty buffer, don't update tail pointer */ + if (running) + hal2_inc_tail(dac); + spin_unlock(&dac->lock); + + wake_up(&dac->dma_wait); +} + +static void hal2_adc_interrupt(struct hal2_codec *adc) +{ + int running; + + spin_lock(&adc->lock); + /* if head buffer contains nonzero samples DMA stream was already + * stopped */ + running = !adc->desc[adc->head].cnt; + adc->desc[adc->head].cnt = H2_BLOCK_SIZE; + adc->desc[adc->head].desc.cntinfo = HPCDMA_XIE | HPCDMA_EOR; + /* we just proccessed empty buffer, don't update head pointer */ + if (running) + hal2_inc_head(adc); + spin_unlock(&adc->lock); + + wake_up(&adc->dma_wait); +} + +static irqreturn_t hal2_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct hal2_card *hal2 = (struct hal2_card*)dev_id; + irqreturn_t ret = IRQ_NONE; + + /* decide what caused this interrupt */ + if (hal2->dac.pbus.pbus->pbdma_ctrl & HPC3_PDMACTRL_INT) { + hal2_dac_interrupt(&hal2->dac); + ret = IRQ_HANDLED; + } + if (hal2->adc.pbus.pbus->pbdma_ctrl & HPC3_PDMACTRL_INT) { + hal2_adc_interrupt(&hal2->adc); + ret = IRQ_HANDLED; + } + return ret; +} + +static int hal2_compute_rate(struct hal2_codec *codec, unsigned int rate) +{ + unsigned short mod; + + DEBUG("rate: %d\n", rate); + + if (rate < 4000) rate = 4000; + else if (rate > 48000) rate = 48000; + + if (44100 % rate < 48000 % rate) { + mod = 4 * 44100 / rate; + codec->master = 44100; + } else { + mod = 4 * 48000 / rate; + codec->master = 48000; + } + + codec->inc = 4; + codec->mod = mod; + rate = 4 * codec->master / mod; + + DEBUG("real_rate: %d\n", rate); + + return rate; +} + +static void hal2_set_dac_rate(struct hal2_card *hal2) +{ + unsigned int master = hal2->dac.master; + int inc = hal2->dac.inc; + int mod = hal2->dac.mod; + + DEBUG("master: %d inc: %d mod: %d\n", master, inc, mod); + + hal2_i_write16(hal2, H2I_BRES1_C1, (master == 44100) ? 1 : 0); + hal2_i_write32(hal2, H2I_BRES1_C2, ((0xffff & (inc - mod - 1)) << 16) | inc); +} + +static void hal2_set_adc_rate(struct hal2_card *hal2) +{ + unsigned int master = hal2->adc.master; + int inc = hal2->adc.inc; + int mod = hal2->adc.mod; + + DEBUG("master: %d inc: %d mod: %d\n", master, inc, mod); + + hal2_i_write16(hal2, H2I_BRES2_C1, (master == 44100) ? 1 : 0); + hal2_i_write32(hal2, H2I_BRES2_C2, ((0xffff & (inc - mod - 1)) << 16) | inc); +} + +static void hal2_setup_dac(struct hal2_card *hal2) +{ + unsigned int fifobeg, fifoend, highwater, sample_size; + struct hal2_pbus *pbus = &hal2->dac.pbus; + + DEBUG("hal2_setup_dac\n"); + + /* Now we set up some PBUS information. The PBUS needs information about + * what portion of the fifo it will use. If it's receiving or + * transmitting, and finally whether the stream is little endian or big + * endian. The information is written later, on the start call. + */ + sample_size = 2 * hal2->dac.voices; + /* Fifo should be set to hold exactly four samples. Highwater mark + * should be set to two samples. */ + highwater = (sample_size * 2) >> 1; /* halfwords */ + fifobeg = 0; /* playback is first */ + fifoend = (sample_size * 4) >> 3; /* doublewords */ + pbus->ctrl = HPC3_PDMACTRL_RT | HPC3_PDMACTRL_LD | + (highwater << 8) | (fifobeg << 16) | (fifoend << 24) | + (hal2->dac.format & AFMT_S16_LE ? HPC3_PDMACTRL_SEL : 0); + /* We disable everything before we do anything at all */ + pbus->pbus->pbdma_ctrl = HPC3_PDMACTRL_LD; + hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX); + /* Setup the HAL2 for playback */ + hal2_set_dac_rate(hal2); + /* Set endianess */ + if (hal2->dac.format & AFMT_S16_LE) + hal2_i_setbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECTX); + else + hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECTX); + /* Set DMA bus */ + hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); + /* We are using 1st Bresenham clock generator for playback */ + hal2_i_write16(hal2, H2I_DAC_C1, (pbus->pbusnr << H2I_C1_DMA_SHIFT) + | (1 << H2I_C1_CLKID_SHIFT) + | (hal2->dac.voices << H2I_C1_DATAT_SHIFT)); +} + +static void hal2_setup_adc(struct hal2_card *hal2) +{ + unsigned int fifobeg, fifoend, highwater, sample_size; + struct hal2_pbus *pbus = &hal2->adc.pbus; + + DEBUG("hal2_setup_adc\n"); + + sample_size = 2 * hal2->adc.voices; + highwater = (sample_size * 2) >> 1; /* halfwords */ + fifobeg = (4 * 4) >> 3; /* record is second */ + fifoend = (4 * 4 + sample_size * 4) >> 3; /* doublewords */ + pbus->ctrl = HPC3_PDMACTRL_RT | HPC3_PDMACTRL_RCV | HPC3_PDMACTRL_LD | + (highwater << 8) | (fifobeg << 16) | (fifoend << 24) | + (hal2->adc.format & AFMT_S16_LE ? HPC3_PDMACTRL_SEL : 0); + pbus->pbus->pbdma_ctrl = HPC3_PDMACTRL_LD; + hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR); + /* Setup the HAL2 for record */ + hal2_set_adc_rate(hal2); + /* Set endianess */ + if (hal2->adc.format & AFMT_S16_LE) + hal2_i_setbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECR); + else + hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECR); + /* Set DMA bus */ + hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); + /* We are using 2nd Bresenham clock generator for record */ + hal2_i_write16(hal2, H2I_ADC_C1, (pbus->pbusnr << H2I_C1_DMA_SHIFT) + | (2 << H2I_C1_CLKID_SHIFT) + | (hal2->adc.voices << H2I_C1_DATAT_SHIFT)); +} + +static dma_addr_t hal2_desc_addr(struct hal2_codec *codec, int i) +{ + if (--i < 0) + i = codec->desc_count - 1; + return codec->desc[i].desc.pnext; +} + +static void hal2_start_dac(struct hal2_card *hal2) +{ + struct hal2_codec *dac = &hal2->dac; + struct hal2_pbus *pbus = &dac->pbus; + + pbus->pbus->pbdma_dptr = hal2_desc_addr(dac, dac->tail); + pbus->pbus->pbdma_ctrl = pbus->ctrl | HPC3_PDMACTRL_ACT; + /* enable DAC */ + hal2_i_setbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX); +} + +static void hal2_start_adc(struct hal2_card *hal2) +{ + struct hal2_codec *adc = &hal2->adc; + struct hal2_pbus *pbus = &adc->pbus; + + pbus->pbus->pbdma_dptr = hal2_desc_addr(adc, adc->head); + pbus->pbus->pbdma_ctrl = pbus->ctrl | HPC3_PDMACTRL_ACT; + /* enable ADC */ + hal2_i_setbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR); +} + +static inline void hal2_stop_dac(struct hal2_card *hal2) +{ + hal2->dac.pbus.pbus->pbdma_ctrl = HPC3_PDMACTRL_LD; + /* The HAL2 itself may remain enabled safely */ +} + +static inline void hal2_stop_adc(struct hal2_card *hal2) +{ + hal2->adc.pbus.pbus->pbdma_ctrl = HPC3_PDMACTRL_LD; +} + +static int hal2_alloc_dmabuf(struct hal2_codec *codec, int size, + int count, int cntinfo, int dir) +{ + struct hal2_desc *desc, *dma_addr; + int i; + + DEBUG("allocating %dk DMA buffer.\n", size / 1024); + + codec->buffer = (unsigned char *)__get_free_pages(GFP_KERNEL | GFP_DMA, + get_order(size)); + if (!codec->buffer) + return -ENOMEM; + desc = dma_alloc_coherent(NULL, count * sizeof(struct hal2_desc), + (dma_addr_t *)&dma_addr, GFP_KERNEL); + if (!desc) { + free_pages((unsigned long)codec->buffer, get_order(size)); + return -ENOMEM; + } + codec->desc = desc; + for (i = 0; i < count; i++) { + desc->desc.pbuf = dma_map_single(NULL, + (void *)(codec->buffer + i * H2_BLOCK_SIZE), + H2_BLOCK_SIZE, dir); + desc->desc.cntinfo = cntinfo; + desc->desc.pnext = (i == count - 1) ? + (u32)dma_addr : (u32)(dma_addr + i + 1); + desc->cnt = 0; + desc++; + } + codec->desc_count = count; + codec->head = codec->tail = 0; + return 0; +} + +static int hal2_alloc_dac_dmabuf(struct hal2_codec *codec) +{ + return hal2_alloc_dmabuf(codec, H2_DAC_BUFSIZE, + H2_DAC_BUFSIZE / H2_BLOCK_SIZE, + HPCDMA_XIE | HPCDMA_EOX, + DMA_TO_DEVICE); +} + +static int hal2_alloc_adc_dmabuf(struct hal2_codec *codec) +{ + return hal2_alloc_dmabuf(codec, H2_ADC_BUFSIZE, + H2_ADC_BUFSIZE / H2_BLOCK_SIZE, + HPCDMA_XIE | H2_BLOCK_SIZE, + DMA_TO_DEVICE); +} + +static void hal2_free_dmabuf(struct hal2_codec *codec, int size, int dir) +{ + dma_addr_t dma_addr; + int i; + + dma_addr = codec->desc[codec->desc_count - 1].desc.pnext; + for (i = 0; i < codec->desc_count; i++) + dma_unmap_single(NULL, codec->desc[i].desc.pbuf, + H2_BLOCK_SIZE, dir); + dma_free_coherent(NULL, codec->desc_count * sizeof(struct hal2_desc), + (void *)codec->desc, dma_addr); + free_pages((unsigned long)codec->buffer, get_order(size)); +} + +static void hal2_free_dac_dmabuf(struct hal2_codec *codec) +{ + return hal2_free_dmabuf(codec, H2_DAC_BUFSIZE, DMA_TO_DEVICE); +} + +static void hal2_free_adc_dmabuf(struct hal2_codec *codec) +{ + return hal2_free_dmabuf(codec, H2_ADC_BUFSIZE, DMA_FROM_DEVICE); +} + +/* + * Add 'count' bytes to 'buffer' from DMA ring buffers. Return number of + * bytes added or -EFAULT if copy_from_user failed. + */ +static int hal2_get_buffer(struct hal2_card *hal2, char *buffer, int count) +{ + unsigned long flags; + int size, ret = 0; + unsigned char *buf; + struct hal2_desc *tail; + struct hal2_codec *adc = &hal2->adc; + + DEBUG("getting %d bytes ", count); + + spin_lock_irqsave(&adc->lock, flags); + tail = &adc->desc[adc->tail]; + /* enable DMA stream if there are no data */ + if (!tail->cnt && !(adc->pbus.pbus->pbdma_ctrl & HPC3_PDMACTRL_ISACT)) + hal2_start_adc(hal2); + while (tail->cnt > 0 && count > 0) { + size = min((int)tail->cnt, count); + buf = &adc->buffer[(adc->tail + 1) * H2_BLOCK_SIZE - tail->cnt]; + spin_unlock_irqrestore(&adc->lock, flags); + dma_sync_single(NULL, tail->desc.pbuf, size, DMA_FROM_DEVICE); + if (copy_to_user(buffer, buf, size)) { + ret = -EFAULT; + goto out; + } + spin_lock_irqsave(&adc->lock, flags); + tail->cnt -= size; + /* buffer is empty, update tail pointer */ + if (tail->cnt == 0) { + tail->desc.cntinfo = HPCDMA_XIE | H2_BLOCK_SIZE; + hal2_inc_tail(adc); + tail = &adc->desc[adc->tail]; + /* enable DMA stream again if needed */ + if (!(adc->pbus.pbus->pbdma_ctrl & HPC3_PDMACTRL_ISACT)) + hal2_start_adc(hal2); + } + buffer += size; + ret += size; + count -= size; + + DEBUG("(%d) ", size); + } + spin_unlock_irqrestore(&adc->lock, flags); +out: + DEBUG("\n"); + + return ret; +} + +/* + * Add 'count' bytes from 'buffer' to DMA ring buffers. Return number of + * bytes added or -EFAULT if copy_from_user failed. + */ +static int hal2_add_buffer(struct hal2_card *hal2, char *buffer, int count) +{ + unsigned long flags; + unsigned char *buf; + int size, ret = 0; + struct hal2_desc *head; + struct hal2_codec *dac = &hal2->dac; + + DEBUG("adding %d bytes ", count); + + spin_lock_irqsave(&dac->lock, flags); + head = &dac->desc[dac->head]; + while (head->cnt == 0 && count > 0) { + size = min((int)H2_BLOCK_SIZE, count); + buf = &dac->buffer[dac->head * H2_BLOCK_SIZE]; + spin_unlock_irqrestore(&dac->lock, flags); + if (copy_from_user(buf, buffer, size)) { + ret = -EFAULT; + goto out; + } + dma_sync_single(NULL, head->desc.pbuf, size, DMA_TO_DEVICE); + spin_lock_irqsave(&dac->lock, flags); + head->desc.cntinfo = size | HPCDMA_XIE; + head->cnt = size; + buffer += size; + ret += size; + count -= size; + hal2_inc_head(dac); + head = &dac->desc[dac->head]; + + DEBUG("(%d) ", size); + } + if (!(dac->pbus.pbus->pbdma_ctrl & HPC3_PDMACTRL_ISACT) && ret > 0) + hal2_start_dac(hal2); + spin_unlock_irqrestore(&dac->lock, flags); +out: + DEBUG("\n"); + + return ret; +} + +#define hal2_reset_dac_pointer(hal2) hal2_reset_pointer(hal2, 1) +#define hal2_reset_adc_pointer(hal2) hal2_reset_pointer(hal2, 0) +static void hal2_reset_pointer(struct hal2_card *hal2, int is_dac) +{ + int i; + struct hal2_codec *codec = (is_dac) ? &hal2->dac : &hal2->adc; + + DEBUG("hal2_reset_pointer\n"); + + for (i = 0; i < codec->desc_count; i++) { + codec->desc[i].cnt = 0; + codec->desc[i].desc.cntinfo = HPCDMA_XIE | (is_dac) ? + HPCDMA_EOX : H2_BLOCK_SIZE; + } + codec->head = codec->tail = 0; +} + +static int hal2_sync_dac(struct hal2_card *hal2) +{ + DECLARE_WAITQUEUE(wait, current); + struct hal2_codec *dac = &hal2->dac; + int ret = 0; + unsigned long flags; + signed long timeout = 1000 * H2_BLOCK_SIZE * 2 * dac->voices * + HZ / dac->sample_rate / 900; + + while (dac->pbus.pbus->pbdma_ctrl & HPC3_PDMACTRL_ISACT) { + add_wait_queue(&dac->dma_wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(timeout); + spin_lock_irqsave(&dac->lock, flags); + if (dac->desc[dac->tail].cnt) + ret = -ETIME; + spin_unlock_irqrestore(&dac->lock, flags); + if (signal_pending(current)) + ret = -ERESTARTSYS; + if (ret) { + hal2_stop_dac(hal2); + hal2_reset_dac_pointer(hal2); + } + remove_wait_queue(&dac->dma_wait, &wait); + } + + return ret; +} + +static int hal2_write_mixer(struct hal2_card *hal2, int index, int vol) +{ + unsigned int l, r, tmp; + + DEBUG_MIX("mixer %d write\n", index); + + if (index >= SOUND_MIXER_NRDEVICES || !mixtable[index].avail) + return -EINVAL; + + r = (vol >> 8) & 0xff; + if (r > 100) + r = 100; + l = vol & 0xff; + if (l > 100) + l = 100; + + hal2->mixer.volume[mixtable[index].idx] = l | (r << 8); + + switch (mixtable[index].idx) { + case H2_MIX_OUTPUT_ATT: + + DEBUG_MIX("output attenuator %d,%d\n", l, r); + + if (r | l) { + tmp = hal2_i_look32(hal2, H2I_DAC_C2); + tmp &= ~(H2I_C2_L_ATT_M | H2I_C2_R_ATT_M | H2I_C2_MUTE); + + /* Attenuator has five bits */ + l = 31 * (100 - l) / 99; + r = 31 * (100 - r) / 99; + + DEBUG_MIX("left: %d, right %d\n", l, r); + + tmp |= (l << H2I_C2_L_ATT_SHIFT) & H2I_C2_L_ATT_M; + tmp |= (r << H2I_C2_R_ATT_SHIFT) & H2I_C2_R_ATT_M; + hal2_i_write32(hal2, H2I_DAC_C2, tmp); + } else + hal2_i_setbit32(hal2, H2I_DAC_C2, H2I_C2_MUTE); + break; + case H2_MIX_INPUT_GAIN: + + DEBUG_MIX("input gain %d,%d\n", l, r); + + tmp = hal2_i_look32(hal2, H2I_ADC_C2); + tmp &= ~(H2I_C2_L_GAIN_M | H2I_C2_R_GAIN_M); + + /* Gain control has four bits */ + l = 16 * l / 100; + r = 16 * r / 100; + + DEBUG_MIX("left: %d, right %d\n", l, r); + + tmp |= (l << H2I_C2_L_GAIN_SHIFT) & H2I_C2_L_GAIN_M; + tmp |= (r << H2I_C2_R_GAIN_SHIFT) & H2I_C2_R_GAIN_M; + hal2_i_write32(hal2, H2I_ADC_C2, tmp); + + break; + } + + return 0; +} + +static void hal2_init_mixer(struct hal2_card *hal2) +{ + int i; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].avail) + hal2->mixer.volume[mixtable[i].idx] = 100 | (100 << 8); + + /* disable attenuator */ + hal2_i_write32(hal2, H2I_DAC_C2, 0); + /* set max input gain */ + hal2_i_write32(hal2, H2I_ADC_C2, H2I_C2_MUTE | + (H2I_C2_L_GAIN_M << H2I_C2_L_GAIN_SHIFT) | + (H2I_C2_R_GAIN_M << H2I_C2_R_GAIN_SHIFT)); + /* set max volume */ + hal2->mixer.master = 0xff; + hal2->vol_regs->left = 0xff; + hal2->vol_regs->right = 0xff; +} + +/* + * XXX: later i'll implement mixer for main volume which will be disabled + * by default. enabling it users will be allowed to have master volume level + * control on panel in their favourite X desktop + */ +static void hal2_volume_control(int direction) +{ + unsigned int master = hal2_card[0]->mixer.master; + struct hal2_vol_regs *vol = hal2_card[0]->vol_regs; + + /* volume up */ + if (direction > 0 && master < 0xff) + master++; + /* volume down */ + else if (direction < 0 && master > 0) + master--; + /* TODO: mute/unmute */ + vol->left = master; + vol->right = master; + hal2_card[0]->mixer.master = master; +} + +static int hal2_mixer_ioctl(struct hal2_card *hal2, unsigned int cmd, + unsigned long arg) +{ + int val; + + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + + memset(&info, 0, sizeof(info)); + strlcpy(info.id, hal2str, sizeof(info.id)); + strlcpy(info.name, hal2str, sizeof(info.name)); + info.modify_counter = hal2->mixer.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, hal2str, sizeof(info.id)); + strlcpy(info.name, hal2str, 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' || _IOC_SIZE(cmd) != sizeof(int)) + return -EINVAL; + + if (_IOC_DIR(cmd) == _IOC_READ) { + switch (_IOC_NR(cmd)) { + /* Give the current record source */ + case SOUND_MIXER_RECSRC: + val = 0; /* FIXME */ + break; + /* Give the supported mixers, all of them support stereo */ + case SOUND_MIXER_DEVMASK: + case SOUND_MIXER_STEREODEVS: { + int i; + + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].avail) + val |= 1 << i; + break; + } + /* Arg contains a bit for each supported recording source */ + case SOUND_MIXER_RECMASK: + val = 0; + break; + case SOUND_MIXER_CAPS: + val = 0; + break; + /* Read a specific mixer */ + default: { + int i = _IOC_NR(cmd); + + if (i >= SOUND_MIXER_NRDEVICES || !mixtable[i].avail) + return -EINVAL; + val = hal2->mixer.volume[mixtable[i].idx]; + break; + } + } + return put_user(val, (int *)arg); + } + + if (_IOC_DIR(cmd) != (_IOC_WRITE|_IOC_READ)) + return -EINVAL; + + hal2->mixer.modcnt++; + + if (get_user(val, (int *)arg)) + return -EFAULT; + + switch (_IOC_NR(cmd)) { + /* Arg contains a bit for each recording source */ + case SOUND_MIXER_RECSRC: + return 0; /* FIXME */ + default: + return hal2_write_mixer(hal2, _IOC_NR(cmd), val); + } + + return 0; +} + +static int hal2_open_mixdev(struct inode *inode, struct file *file) +{ + struct hal2_card *hal2 = hal2_mixer_find_card(iminor(inode)); + + if (hal2) { + file->private_data = hal2; + return nonseekable_open(inode, file); + } + return -ENODEV; +} + +static int hal2_release_mixdev(struct inode *inode, struct file *file) +{ + return 0; +} + +static int hal2_ioctl_mixdev(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hal2_mixer_ioctl((struct hal2_card *)file->private_data, cmd, arg); +} + +static int hal2_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int val; + struct hal2_card *hal2 = (struct hal2_card *) file->private_data; + + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, (int *)arg); + + case SNDCTL_DSP_SYNC: + if (file->f_mode & FMODE_WRITE) + return hal2_sync_dac(hal2); + return 0; + + case SNDCTL_DSP_SETDUPLEX: + return 0; + + case SNDCTL_DSP_GETCAPS: + return put_user(DSP_CAP_DUPLEX | DSP_CAP_MULTI, (int *)arg); + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_READ) { + hal2_stop_adc(hal2); + hal2_reset_adc_pointer(hal2); + } + if (file->f_mode & FMODE_WRITE) { + hal2_stop_dac(hal2); + hal2_reset_dac_pointer(hal2); + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, (int *)arg)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + hal2_stop_adc(hal2); + val = hal2_compute_rate(&hal2->adc, val); + hal2->adc.sample_rate = val; + hal2_set_adc_rate(hal2); + } + if (file->f_mode & FMODE_WRITE) { + hal2_stop_dac(hal2); + val = hal2_compute_rate(&hal2->dac, val); + hal2->dac.sample_rate = val; + hal2_set_dac_rate(hal2); + } + return put_user(val, (int *)arg); + + case SNDCTL_DSP_STEREO: + if (get_user(val, (int *)arg)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + hal2_stop_adc(hal2); + hal2->adc.voices = (val) ? 2 : 1; + hal2_setup_adc(hal2); + } + if (file->f_mode & FMODE_WRITE) { + hal2_stop_dac(hal2); + hal2->dac.voices = (val) ? 2 : 1; + hal2_setup_dac(hal2); + } + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, (int *)arg)) + return -EFAULT; + if (val != 0) { + if (file->f_mode & FMODE_READ) { + hal2_stop_adc(hal2); + hal2->adc.voices = (val == 1) ? 1 : 2; + hal2_setup_adc(hal2); + } + if (file->f_mode & FMODE_WRITE) { + hal2_stop_dac(hal2); + hal2->dac.voices = (val == 1) ? 1 : 2; + hal2_setup_dac(hal2); + } + } + val = -EINVAL; + if (file->f_mode & FMODE_READ) + val = hal2->adc.voices; + if (file->f_mode & FMODE_WRITE) + val = hal2->dac.voices; + return put_user(val, (int *)arg); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(H2_SUPPORTED_FORMATS, (int *)arg); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, (int *)arg)) + return -EFAULT; + if (val != AFMT_QUERY) { + if (!(val & H2_SUPPORTED_FORMATS)) + return -EINVAL; + if (file->f_mode & FMODE_READ) { + hal2_stop_adc(hal2); + hal2->adc.format = val; + hal2_setup_adc(hal2); + } + if (file->f_mode & FMODE_WRITE) { + hal2_stop_dac(hal2); + hal2->dac.format = val; + hal2_setup_dac(hal2); + } + } else { + val = -EINVAL; + if (file->f_mode & FMODE_READ) + val = hal2->adc.format; + if (file->f_mode & FMODE_WRITE) + val = hal2->dac.format; + } + return put_user(val, (int *)arg); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETOSPACE: { + audio_buf_info info; + int i; + unsigned long flags; + struct hal2_codec *dac = &hal2->dac; + + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + info.fragments = 0; + spin_lock_irqsave(&dac->lock, flags); + for (i = 0; i < dac->desc_count; i++) + if (dac->desc[i].cnt == 0) + info.fragments++; + spin_unlock_irqrestore(&dac->lock, flags); + info.fragstotal = dac->desc_count; + info.fragsize = H2_BLOCK_SIZE; + info.bytes = info.fragsize * info.fragments; + + return copy_to_user((void *)arg, &info, sizeof(info)) ? -EFAULT : 0; + } + + case SNDCTL_DSP_GETISPACE: { + audio_buf_info info; + int i; + unsigned long flags; + struct hal2_codec *adc = &hal2->adc; + + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + info.fragments = 0; + info.bytes = 0; + spin_lock_irqsave(&adc->lock, flags); + for (i = 0; i < adc->desc_count; i++) + if (adc->desc[i].cnt > 0) { + info.fragments++; + info.bytes += adc->desc[i].cnt; + } + spin_unlock_irqrestore(&adc->lock, flags); + info.fragstotal = adc->desc_count; + info.fragsize = H2_BLOCK_SIZE; + + return copy_to_user((void *)arg, &info, sizeof(info)) ? -EFAULT : 0; + } + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + return put_user(H2_BLOCK_SIZE, (int *)arg); + + case SNDCTL_DSP_SETFRAGMENT: + return 0; + + case SOUND_PCM_READ_RATE: + val = -EINVAL; + if (file->f_mode & FMODE_READ) + val = hal2->adc.sample_rate; + if (file->f_mode & FMODE_WRITE) + val = hal2->dac.sample_rate; + return put_user(val, (int *)arg); + + case SOUND_PCM_READ_CHANNELS: + val = -EINVAL; + if (file->f_mode & FMODE_READ) + val = hal2->adc.voices; + if (file->f_mode & FMODE_WRITE) + val = hal2->dac.voices; + return put_user(val, (int *)arg); + + case SOUND_PCM_READ_BITS: + return put_user(16, (int *)arg); + } + + return hal2_mixer_ioctl(hal2, cmd, arg); +} + +static ssize_t hal2_read(struct file *file, char *buffer, + size_t count, loff_t *ppos) +{ + ssize_t err; + struct hal2_card *hal2 = (struct hal2_card *) file->private_data; + struct hal2_codec *adc = &hal2->adc; + + if (!count) + return 0; + if (down_interruptible(&adc->sem)) + return -EINTR; + if (file->f_flags & O_NONBLOCK) { + err = hal2_get_buffer(hal2, buffer, count); + err = err == 0 ? -EAGAIN : err; + } else { + do { + /* ~10% longer */ + signed long timeout = 1000 * H2_BLOCK_SIZE * + 2 * adc->voices * HZ / adc->sample_rate / 900; + unsigned long flags; + DECLARE_WAITQUEUE(wait, current); + ssize_t cnt = 0; + + err = hal2_get_buffer(hal2, buffer, count); + if (err > 0) { + count -= err; + cnt += err; + buffer += err; + err = cnt; + } + if (count > 0 && err >= 0) { + add_wait_queue(&adc->dma_wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(timeout); + spin_lock_irqsave(&adc->lock, flags); + if (!adc->desc[adc->tail].cnt) + err = -EAGAIN; + spin_unlock_irqrestore(&adc->lock, flags); + if (signal_pending(current)) + err = -ERESTARTSYS; + remove_wait_queue(&adc->dma_wait, &wait); + if (err < 0) { + hal2_stop_adc(hal2); + hal2_reset_adc_pointer(hal2); + } + } + } while (count > 0 && err >= 0); + } + up(&adc->sem); + + return err; +} + +static ssize_t hal2_write(struct file *file, const char *buffer, + size_t count, loff_t *ppos) +{ + ssize_t err; + char *buf = (char*) buffer; + struct hal2_card *hal2 = (struct hal2_card *) file->private_data; + struct hal2_codec *dac = &hal2->dac; + + if (!count) + return 0; + if (down_interruptible(&dac->sem)) + return -EINTR; + if (file->f_flags & O_NONBLOCK) { + err = hal2_add_buffer(hal2, buf, count); + err = err == 0 ? -EAGAIN : err; + } else { + do { + /* ~10% longer */ + signed long timeout = 1000 * H2_BLOCK_SIZE * + 2 * dac->voices * HZ / dac->sample_rate / 900; + unsigned long flags; + DECLARE_WAITQUEUE(wait, current); + ssize_t cnt = 0; + + err = hal2_add_buffer(hal2, buf, count); + if (err > 0) { + count -= err; + cnt += err; + buf += err; + err = cnt; + } + if (count > 0 && err >= 0) { + add_wait_queue(&dac->dma_wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(timeout); + spin_lock_irqsave(&dac->lock, flags); + if (dac->desc[dac->head].cnt) + err = -EAGAIN; + spin_unlock_irqrestore(&dac->lock, flags); + if (signal_pending(current)) + err = -ERESTARTSYS; + remove_wait_queue(&dac->dma_wait, &wait); + if (err < 0) { + hal2_stop_dac(hal2); + hal2_reset_dac_pointer(hal2); + } + } + } while (count > 0 && err >= 0); + } + up(&dac->sem); + + return err; +} + +static unsigned int hal2_poll(struct file *file, struct poll_table_struct *wait) +{ + unsigned long flags; + unsigned int mask = 0; + struct hal2_card *hal2 = (struct hal2_card *) file->private_data; + + if (file->f_mode & FMODE_READ) { + struct hal2_codec *adc = &hal2->adc; + + poll_wait(file, &adc->dma_wait, wait); + spin_lock_irqsave(&adc->lock, flags); + if (adc->desc[adc->tail].cnt > 0) + mask |= POLLIN; + spin_unlock_irqrestore(&adc->lock, flags); + } + + if (file->f_mode & FMODE_WRITE) { + struct hal2_codec *dac = &hal2->dac; + + poll_wait(file, &dac->dma_wait, wait); + spin_lock_irqsave(&dac->lock, flags); + if (dac->desc[dac->head].cnt == 0) + mask |= POLLOUT; + spin_unlock_irqrestore(&dac->lock, flags); + } + + return mask; +} + +static int hal2_open(struct inode *inode, struct file *file) +{ + int err; + struct hal2_card *hal2 = hal2_dsp_find_card(iminor(inode)); + + if (!hal2) + return -ENODEV; + file->private_data = hal2; + if (file->f_mode & FMODE_READ) { + struct hal2_codec *adc = &hal2->adc; + + if (adc->usecount) + return -EBUSY; + /* OSS spec wanted us to use 8 bit, 8 kHz mono by default, + * but HAL2 can't do 8bit audio */ + adc->format = AFMT_S16_BE; + adc->voices = 1; + adc->sample_rate = hal2_compute_rate(adc, 8000); + hal2_set_adc_rate(hal2); + err = hal2_alloc_adc_dmabuf(adc); + if (err) + return err; + hal2_setup_adc(hal2); + adc->usecount++; + } + if (file->f_mode & FMODE_WRITE) { + struct hal2_codec *dac = &hal2->dac; + + if (dac->usecount) + return -EBUSY; + dac->format = AFMT_S16_BE; + dac->voices = 1; + dac->sample_rate = hal2_compute_rate(dac, 8000); + hal2_set_dac_rate(hal2); + err = hal2_alloc_dac_dmabuf(dac); + if (err) + return err; + hal2_setup_dac(hal2); + dac->usecount++; + } + + return nonseekable_open(inode, file); +} + +static int hal2_release(struct inode *inode, struct file *file) +{ + struct hal2_card *hal2 = (struct hal2_card *) file->private_data; + + if (file->f_mode & FMODE_READ) { + struct hal2_codec *adc = &hal2->adc; + + down(&adc->sem); + hal2_stop_adc(hal2); + hal2_free_adc_dmabuf(adc); + adc->usecount--; + up(&adc->sem); + } + if (file->f_mode & FMODE_WRITE) { + struct hal2_codec *dac = &hal2->dac; + + down(&dac->sem); + hal2_sync_dac(hal2); + hal2_free_dac_dmabuf(dac); + dac->usecount--; + up(&dac->sem); + } + + return 0; +} + +static struct file_operations hal2_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = hal2_read, + .write = hal2_write, + .poll = hal2_poll, + .ioctl = hal2_ioctl, + .open = hal2_open, + .release = hal2_release, +}; + +static struct file_operations hal2_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = hal2_ioctl_mixdev, + .open = hal2_open_mixdev, + .release = hal2_release_mixdev, +}; + +static void hal2_init_codec(struct hal2_codec *codec, struct hpc3_regs *hpc3, + int index) +{ + codec->pbus.pbusnr = index; + codec->pbus.pbus = &hpc3->pbdma[index]; + init_waitqueue_head(&codec->dma_wait); + init_MUTEX(&codec->sem); + spin_lock_init(&codec->lock); +} + +static int hal2_detect(struct hal2_card *hal2) +{ + unsigned short board, major, minor; + unsigned short rev; + + /* reset HAL2 */ + hal2_isr_write(hal2, 0); + /* release reset */ + hal2_isr_write(hal2, H2_ISR_GLOBAL_RESET_N | H2_ISR_CODEC_RESET_N); + + hal2_i_write16(hal2, H2I_RELAY_C, H2I_RELAY_C_STATE); + if ((rev = hal2_rev_look(hal2)) & H2_REV_AUDIO_PRESENT) + return -ENODEV; + + board = (rev & H2_REV_BOARD_M) >> 12; + major = (rev & H2_REV_MAJOR_CHIP_M) >> 4; + minor = (rev & H2_REV_MINOR_CHIP_M); + + printk(KERN_INFO "SGI HAL2 revision %i.%i.%i\n", + board, major, minor); + + return 0; +} + +static int hal2_init_card(struct hal2_card **phal2, struct hpc3_regs *hpc3) +{ + int ret = 0; + struct hal2_card *hal2; + + hal2 = (struct hal2_card *) kmalloc(sizeof(struct hal2_card), GFP_KERNEL); + if (!hal2) + return -ENOMEM; + memset(hal2, 0, sizeof(struct hal2_card)); + + hal2->ctl_regs = (struct hal2_ctl_regs *)hpc3->pbus_extregs[0]; + hal2->aes_regs = (struct hal2_aes_regs *)hpc3->pbus_extregs[1]; + hal2->vol_regs = (struct hal2_vol_regs *)hpc3->pbus_extregs[2]; + hal2->syn_regs = (struct hal2_syn_regs *)hpc3->pbus_extregs[3]; + + if (hal2_detect(hal2) < 0) { + ret = -ENODEV; + goto free_card; + } + + hal2_init_codec(&hal2->dac, hpc3, 0); + hal2_init_codec(&hal2->adc, hpc3, 1); + + /* + * All DMA channel interfaces in HAL2 are designed to operate with + * PBUS programmed for 2 cycles in D3, 2 cycles in D4 and 2 cycles + * in D5. HAL2 is a 16-bit device which can accept both big and little + * endian format. It assumes that even address bytes are on high + * portion of PBUS (15:8) and assumes that HPC3 is programmed to + * accept a live (unsynchronized) version of P_DREQ_N from HAL2. + */ +#define HAL2_PBUS_DMACFG ((0 << HPC3_DMACFG_D3R_SHIFT) | \ + (2 << HPC3_DMACFG_D4R_SHIFT) | \ + (2 << HPC3_DMACFG_D5R_SHIFT) | \ + (0 << HPC3_DMACFG_D3W_SHIFT) | \ + (2 << HPC3_DMACFG_D4W_SHIFT) | \ + (2 << HPC3_DMACFG_D5W_SHIFT) | \ + HPC3_DMACFG_DS16 | \ + HPC3_DMACFG_EVENHI | \ + HPC3_DMACFG_RTIME | \ + (8 << HPC3_DMACFG_BURST_SHIFT) | \ + HPC3_DMACFG_DRQLIVE) + /* + * Ignore what's mentioned in the specification and write value which + * works in The Real World (TM) + */ + hpc3->pbus_dmacfg[hal2->dac.pbus.pbusnr][0] = 0x8208844; + hpc3->pbus_dmacfg[hal2->adc.pbus.pbusnr][0] = 0x8208844; + + if (request_irq(SGI_HPCDMA_IRQ, hal2_interrupt, SA_SHIRQ, + hal2str, hal2)) { + printk(KERN_ERR "HAL2: Can't get irq %d\n", SGI_HPCDMA_IRQ); + ret = -EAGAIN; + goto free_card; + } + + hal2->dev_dsp = register_sound_dsp(&hal2_audio_fops, -1); + if (hal2->dev_dsp < 0) { + ret = hal2->dev_dsp; + goto free_irq; + } + + hal2->dev_mixer = register_sound_mixer(&hal2_mixer_fops, -1); + if (hal2->dev_mixer < 0) { + ret = hal2->dev_mixer; + goto unregister_dsp; + } + + hal2_init_mixer(hal2); + + *phal2 = hal2; + return 0; +unregister_dsp: + unregister_sound_dsp(hal2->dev_dsp); +free_irq: + free_irq(SGI_HPCDMA_IRQ, hal2); +free_card: + kfree(hal2); + + return ret; +} + +extern void (*indy_volume_button)(int); + +/* + * Assuming only one HAL2 card. Mail me if you ever meet machine with + * more than one. + */ +static int __init init_hal2(void) +{ + int i, error; + + for (i = 0; i < MAXCARDS; i++) + hal2_card[i] = NULL; + + error = hal2_init_card(&hal2_card[0], hpc3c0); + + /* let Indy's volume buttons work */ + if (!error && !ip22_is_fullhouse()) + indy_volume_button = hal2_volume_control; + + return error; + +} + +static void __exit exit_hal2(void) +{ + int i; + + /* unregister volume butons callback function */ + indy_volume_button = NULL; + + for (i = 0; i < MAXCARDS; i++) + if (hal2_card[i]) { + free_irq(SGI_HPCDMA_IRQ, hal2_card[i]); + unregister_sound_dsp(hal2_card[i]->dev_dsp); + unregister_sound_mixer(hal2_card[i]->dev_mixer); + kfree(hal2_card[i]); + } +} + +module_init(init_hal2); +module_exit(exit_hal2); + +MODULE_DESCRIPTION("OSS compatible driver for SGI HAL2 audio"); +MODULE_AUTHOR("Ladislav Michl"); +MODULE_LICENSE("GPL"); diff --git a/sound/oss/hal2.h b/sound/oss/hal2.h new file mode 100644 index 000000000000..2bd3b52d8a37 --- /dev/null +++ b/sound/oss/hal2.h @@ -0,0 +1,248 @@ +#ifndef __HAL2_H +#define __HAL2_H + +/* + * Driver for HAL2 sound processors + * Copyright (c) 1999 Ulf Carlsson + * Copyright (c) 2001, 2002, 2003 Ladislav Michl + * + * 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. + * + * 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 +#include +#include +#include + +/* Indirect status register */ + +#define H2_ISR_TSTATUS 0x01 /* RO: transaction status 1=busy */ +#define H2_ISR_USTATUS 0x02 /* RO: utime status bit 1=armed */ +#define H2_ISR_QUAD_MODE 0x04 /* codec mode 0=indigo 1=quad */ +#define H2_ISR_GLOBAL_RESET_N 0x08 /* chip global reset 0=reset */ +#define H2_ISR_CODEC_RESET_N 0x10 /* codec/synth reset 0=reset */ + +/* Revision register */ + +#define H2_REV_AUDIO_PRESENT 0x8000 /* RO: audio present 0=present */ +#define H2_REV_BOARD_M 0x7000 /* RO: bits 14:12, board revision */ +#define H2_REV_MAJOR_CHIP_M 0x00F0 /* RO: bits 7:4, major chip revision */ +#define H2_REV_MINOR_CHIP_M 0x000F /* RO: bits 3:0, minor chip revision */ + +/* Indirect address register */ + +/* + * Address of indirect internal register to be accessed. A write to this + * register initiates read or write access to the indirect registers in the + * HAL2. Note that there af four indirect data registers for write access to + * registers larger than 16 byte. + */ + +#define H2_IAR_TYPE_M 0xF000 /* bits 15:12, type of functional */ + /* block the register resides in */ + /* 1=DMA Port */ + /* 9=Global DMA Control */ + /* 2=Bresenham */ + /* 3=Unix Timer */ +#define H2_IAR_NUM_M 0x0F00 /* bits 11:8 instance of the */ + /* blockin which the indirect */ + /* register resides */ + /* If IAR_TYPE_M=DMA Port: */ + /* 1=Synth In */ + /* 2=AES In */ + /* 3=AES Out */ + /* 4=DAC Out */ + /* 5=ADC Out */ + /* 6=Synth Control */ + /* If IAR_TYPE_M=Global DMA Control: */ + /* 1=Control */ + /* If IAR_TYPE_M=Bresenham: */ + /* 1=Bresenham Clock Gen 1 */ + /* 2=Bresenham Clock Gen 2 */ + /* 3=Bresenham Clock Gen 3 */ + /* If IAR_TYPE_M=Unix Timer: */ + /* 1=Unix Timer */ +#define H2_IAR_ACCESS_SELECT 0x0080 /* 1=read 0=write */ +#define H2_IAR_PARAM 0x000C /* Parameter Select */ +#define H2_IAR_RB_INDEX_M 0x0003 /* Read Back Index */ + /* 00:word0 */ + /* 01:word1 */ + /* 10:word2 */ + /* 11:word3 */ +/* + * HAL2 internal addressing + * + * The HAL2 has "indirect registers" (idr) which are accessed by writing to the + * Indirect Data registers. Write the address to the Indirect Address register + * to transfer the data. + * + * We define the H2IR_* to the read address and H2IW_* to the write address and + * H2I_* to be fields in whatever register is referred to. + * + * When we write to indirect registers which are larger than one word (16 bit) + * we have to fill more than one indirect register before writing. When we read + * back however we have to read several times, each time with different Read + * Back Indexes (there are defs for doing this easily). + */ + +/* + * Relay Control + */ +#define H2I_RELAY_C 0x9100 +#define H2I_RELAY_C_STATE 0x01 /* state of RELAY pin signal */ + +/* DMA port enable */ + +#define H2I_DMA_PORT_EN 0x9104 +#define H2I_DMA_PORT_EN_SY_IN 0x01 /* Synth_in DMA port */ +#define H2I_DMA_PORT_EN_AESRX 0x02 /* AES receiver DMA port */ +#define H2I_DMA_PORT_EN_AESTX 0x04 /* AES transmitter DMA port */ +#define H2I_DMA_PORT_EN_CODECTX 0x08 /* CODEC transmit DMA port */ +#define H2I_DMA_PORT_EN_CODECR 0x10 /* CODEC receive DMA port */ + +#define H2I_DMA_END 0x9108 /* global dma endian select */ +#define H2I_DMA_END_SY_IN 0x01 /* Synth_in DMA port */ +#define H2I_DMA_END_AESRX 0x02 /* AES receiver DMA port */ +#define H2I_DMA_END_AESTX 0x04 /* AES transmitter DMA port */ +#define H2I_DMA_END_CODECTX 0x08 /* CODEC transmit DMA port */ +#define H2I_DMA_END_CODECR 0x10 /* CODEC receive DMA port */ + /* 0=b_end 1=l_end */ + +#define H2I_DMA_DRV 0x910C /* global PBUS DMA enable */ + +#define H2I_SYNTH_C 0x1104 /* Synth DMA control */ + +#define H2I_AESRX_C 0x1204 /* AES RX dma control */ + +#define H2I_C_TS_EN 0x20 /* Timestamp enable */ +#define H2I_C_TS_FRMT 0x40 /* Timestamp format */ +#define H2I_C_NAUDIO 0x80 /* Sign extend */ + +/* AESRX CTL, 16 bit */ + +#define H2I_AESTX_C 0x1304 /* AES TX DMA control */ +#define H2I_AESTX_C_CLKID_SHIFT 3 /* Bresenham Clock Gen 1-3 */ +#define H2I_AESTX_C_CLKID_M 0x18 +#define H2I_AESTX_C_DATAT_SHIFT 8 /* 1=mono 2=stereo (3=quad) */ +#define H2I_AESTX_C_DATAT_M 0x300 + +/* CODEC registers */ + +#define H2I_DAC_C1 0x1404 /* DAC DMA control, 16 bit */ +#define H2I_DAC_C2 0x1408 /* DAC DMA control, 32 bit */ +#define H2I_ADC_C1 0x1504 /* ADC DMA control, 16 bit */ +#define H2I_ADC_C2 0x1508 /* ADC DMA control, 32 bit */ + +/* Bits in CTL1 register */ + +#define H2I_C1_DMA_SHIFT 0 /* DMA channel */ +#define H2I_C1_DMA_M 0x7 +#define H2I_C1_CLKID_SHIFT 3 /* Bresenham Clock Gen 1-3 */ +#define H2I_C1_CLKID_M 0x18 +#define H2I_C1_DATAT_SHIFT 8 /* 1=mono 2=stereo (3=quad) */ +#define H2I_C1_DATAT_M 0x300 + +/* Bits in CTL2 register */ + +#define H2I_C2_R_GAIN_SHIFT 0 /* right a/d input gain */ +#define H2I_C2_R_GAIN_M 0xf +#define H2I_C2_L_GAIN_SHIFT 4 /* left a/d input gain */ +#define H2I_C2_L_GAIN_M 0xf0 +#define H2I_C2_R_SEL 0x100 /* right input select */ +#define H2I_C2_L_SEL 0x200 /* left input select */ +#define H2I_C2_MUTE 0x400 /* mute */ +#define H2I_C2_DO1 0x00010000 /* digital output port bit 0 */ +#define H2I_C2_DO2 0x00020000 /* digital output port bit 1 */ +#define H2I_C2_R_ATT_SHIFT 18 /* right d/a output - */ +#define H2I_C2_R_ATT_M 0x007c0000 /* attenuation */ +#define H2I_C2_L_ATT_SHIFT 23 /* left d/a output - */ +#define H2I_C2_L_ATT_M 0x0f800000 /* attenuation */ + +#define H2I_SYNTH_MAP_C 0x1104 /* synth dma handshake ctrl */ + +/* Clock generator CTL 1, 16 bit */ + +#define H2I_BRES1_C1 0x2104 +#define H2I_BRES2_C1 0x2204 +#define H2I_BRES3_C1 0x2304 + +#define H2I_BRES_C1_SHIFT 0 /* 0=48.0 1=44.1 2=aes_rx */ +#define H2I_BRES_C1_M 0x03 + +/* Clock generator CTL 2, 32 bit */ + +#define H2I_BRES1_C2 0x2108 +#define H2I_BRES2_C2 0x2208 +#define H2I_BRES3_C2 0x2308 + +#define H2I_BRES_C2_INC_SHIFT 0 /* increment value */ +#define H2I_BRES_C2_INC_M 0xffff +#define H2I_BRES_C2_MOD_SHIFT 16 /* modcontrol value */ +#define H2I_BRES_C2_MOD_M 0xffff0000 /* modctrl=0xffff&(modinc-1) */ + +/* Unix timer, 64 bit */ + +#define H2I_UTIME 0x3104 +#define H2I_UTIME_0_LD 0xffff /* microseconds, LSB's */ +#define H2I_UTIME_1_LD0 0x0f /* microseconds, MSB's */ +#define H2I_UTIME_1_LD1 0xf0 /* tenths of microseconds */ +#define H2I_UTIME_2_LD 0xffff /* seconds, LSB's */ +#define H2I_UTIME_3_LD 0xffff /* seconds, MSB's */ + +struct hal2_ctl_regs { + u32 _unused0[4]; + volatile u32 isr; /* 0x10 Status Register */ + u32 _unused1[3]; + volatile u32 rev; /* 0x20 Revision Register */ + u32 _unused2[3]; + volatile u32 iar; /* 0x30 Indirect Address Register */ + u32 _unused3[3]; + volatile u32 idr0; /* 0x40 Indirect Data Register 0 */ + u32 _unused4[3]; + volatile u32 idr1; /* 0x50 Indirect Data Register 1 */ + u32 _unused5[3]; + volatile u32 idr2; /* 0x60 Indirect Data Register 2 */ + u32 _unused6[3]; + volatile u32 idr3; /* 0x70 Indirect Data Register 3 */ +}; + +struct hal2_aes_regs { + volatile u32 rx_stat[2]; /* Status registers */ + volatile u32 rx_cr[2]; /* Control registers */ + volatile u32 rx_ud[4]; /* User data window */ + volatile u32 rx_st[24]; /* Channel status data */ + + volatile u32 tx_stat[1]; /* Status register */ + volatile u32 tx_cr[3]; /* Control registers */ + volatile u32 tx_ud[4]; /* User data window */ + volatile u32 tx_st[24]; /* Channel status data */ +}; + +struct hal2_vol_regs { + volatile u32 right; /* Right volume */ + volatile u32 left; /* Left volume */ +}; + +struct hal2_syn_regs { + u32 _unused0[2]; + volatile u32 page; /* DOC Page register */ + volatile u32 regsel; /* DOC Register selection */ + volatile u32 dlow; /* DOC Data low */ + volatile u32 dhigh; /* DOC Data high */ + volatile u32 irq; /* IRQ Status */ + volatile u32 dram; /* DRAM Access */ +}; + +#endif /* __HAL2_H */ diff --git a/sound/oss/harmony.c b/sound/oss/harmony.c new file mode 100644 index 000000000000..bee9d344cd26 --- /dev/null +++ b/sound/oss/harmony.c @@ -0,0 +1,1330 @@ +/* + drivers/sound/harmony.c + + This is a sound driver for ASP's and Lasi's Harmony sound chip + and is unlikely to be used for anything other than on a HP PA-RISC. + + Harmony is found in HP 712s, 715/new and many other GSC based machines. + On older 715 machines you'll find the technically identical chip + called 'Vivace'. Both Harmony and Vicace are supported by this driver. + + Copyright 2000 (c) Linuxcare Canada, Alex deVries + Copyright 2000-2003 (c) Helge Deller + Copyright 2001 (c) Matthieu Delahaye + Copyright 2001 (c) Jean-Christophe Vaugeois + Copyright 2004 (c) Stuart Brady + + +TODO: + - fix SNDCTL_DSP_GETOSPACE and SNDCTL_DSP_GETISPACE ioctls to + return the real values + - add private ioctl for selecting line- or microphone input + (only one of them is available at the same time) + - add module parameters + - implement mmap functionality + - implement gain meter ? + - ... +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sound_config.h" + + +#define PFX "harmony: " +#define HARMONY_VERSION "V0.9a" + +#undef DEBUG +#ifdef DEBUG +# define DPRINTK printk +#else +# define DPRINTK(x,...) +#endif + + +#define MAX_BUFS 10 /* maximum number of rotating buffers */ +#define HARMONY_BUF_SIZE 4096 /* needs to be a multiple of PAGE_SIZE (4096)! */ + +#define CNTL_C 0x80000000 +#define CNTL_ST 0x00000020 +#define CNTL_44100 0x00000015 /* HARMONY_SR_44KHZ */ +#define CNTL_8000 0x00000008 /* HARMONY_SR_8KHZ */ + +#define GAINCTL_HE 0x08000000 +#define GAINCTL_LE 0x04000000 +#define GAINCTL_SE 0x02000000 + +#define DSTATUS_PN 0x00000200 +#define DSTATUS_RN 0x00000002 + +#define DSTATUS_IE 0x80000000 + +#define HARMONY_DF_16BIT_LINEAR 0 +#define HARMONY_DF_8BIT_ULAW 1 +#define HARMONY_DF_8BIT_ALAW 2 + +#define HARMONY_SS_MONO 0 +#define HARMONY_SS_STEREO 1 + +#define HARMONY_SR_8KHZ 0x08 +#define HARMONY_SR_16KHZ 0x09 +#define HARMONY_SR_27KHZ 0x0A +#define HARMONY_SR_32KHZ 0x0B +#define HARMONY_SR_48KHZ 0x0E +#define HARMONY_SR_9KHZ 0x0F +#define HARMONY_SR_5KHZ 0x10 +#define HARMONY_SR_11KHZ 0x11 +#define HARMONY_SR_18KHZ 0x12 +#define HARMONY_SR_22KHZ 0x13 +#define HARMONY_SR_37KHZ 0x14 +#define HARMONY_SR_44KHZ 0x15 +#define HARMONY_SR_33KHZ 0x16 +#define HARMONY_SR_6KHZ 0x17 + +/* + * Some magics numbers used to auto-detect file formats + */ + +#define HARMONY_MAGIC_8B_ULAW 1 +#define HARMONY_MAGIC_8B_ALAW 27 +#define HARMONY_MAGIC_16B_LINEAR 3 +#define HARMONY_MAGIC_MONO 1 +#define HARMONY_MAGIC_STEREO 2 + +/* + * Channels Positions in mixer register + */ + +#define GAIN_HE_SHIFT 27 +#define GAIN_HE_MASK ( 1 << GAIN_HE_SHIFT) +#define GAIN_LE_SHIFT 26 +#define GAIN_LE_MASK ( 1 << GAIN_LE_SHIFT) +#define GAIN_SE_SHIFT 25 +#define GAIN_SE_MASK ( 1 << GAIN_SE_SHIFT) +#define GAIN_IS_SHIFT 24 +#define GAIN_IS_MASK ( 1 << GAIN_IS_SHIFT) +#define GAIN_MA_SHIFT 20 +#define GAIN_MA_MASK ( 0x0f << GAIN_MA_SHIFT) +#define GAIN_LI_SHIFT 16 +#define GAIN_LI_MASK ( 0x0f << GAIN_LI_SHIFT) +#define GAIN_RI_SHIFT 12 +#define GAIN_RI_MASK ( 0x0f << GAIN_RI_SHIFT) +#define GAIN_LO_SHIFT 6 +#define GAIN_LO_MASK ( 0x3f << GAIN_LO_SHIFT) +#define GAIN_RO_SHIFT 0 +#define GAIN_RO_MASK ( 0x3f << GAIN_RO_SHIFT) + + +#define MAX_OUTPUT_LEVEL (GAIN_RO_MASK >> GAIN_RO_SHIFT) +#define MAX_INPUT_LEVEL (GAIN_RI_MASK >> GAIN_RI_SHIFT) +#define MAX_MONITOR_LEVEL (GAIN_MA_MASK >> GAIN_MA_SHIFT) + +#define MIXER_INTERNAL SOUND_MIXER_LINE1 +#define MIXER_LINEOUT SOUND_MIXER_LINE2 +#define MIXER_HEADPHONES SOUND_MIXER_LINE3 + +#define MASK_INTERNAL SOUND_MASK_LINE1 +#define MASK_LINEOUT SOUND_MASK_LINE2 +#define MASK_HEADPHONES SOUND_MASK_LINE3 + +/* + * Channels Mask in mixer register + */ + +#define GAIN_TOTAL_SILENCE 0x00F00FFF +#define GAIN_DEFAULT 0x0FF00000 + + +struct harmony_hpa { + u8 unused000; + u8 id; + u8 teleshare_id; + u8 unused003; + u32 reset; + u32 cntl; + u32 gainctl; + u32 pnxtadd; + u32 pcuradd; + u32 rnxtadd; + u32 rcuradd; + u32 dstatus; + u32 ov; + u32 pio; + u32 unused02c; + u32 unused030[3]; + u32 diag; +}; + +struct harmony_dev { + struct harmony_hpa *hpa; + struct parisc_device *dev; + u32 current_gain; + u32 dac_rate; /* 8000 ... 48000 (Hz) */ + u8 data_format; /* HARMONY_DF_xx_BIT_xxx */ + u8 sample_rate; /* HARMONY_SR_xx_KHZ */ + u8 stereo_select; /* HARMONY_SS_MONO or HARMONY_SS_STEREO */ + int format_initialized :1; + int suspended_playing :1; + int suspended_recording :1; + + int blocked_playing :1; + int blocked_recording :1; + int audio_open :1; + int mixer_open :1; + + wait_queue_head_t wq_play, wq_record; + int first_filled_play; /* first buffer containing data (next to play) */ + int nb_filled_play; + int play_offset; + int first_filled_record; + int nb_filled_record; + + int dsp_unit, mixer_unit; +}; + + +static struct harmony_dev harmony; + + +/* + * Dynamic sound buffer allocation and DMA memory + */ + +struct harmony_buffer { + unsigned char *addr; + dma_addr_t dma_handle; + int dma_coherent; /* Zero if dma_alloc_coherent() fails */ + unsigned int len; +}; + +/* + * Harmony memory buffers + */ + +static struct harmony_buffer played_buf, recorded_buf, silent, graveyard; + + +#define CHECK_WBACK_INV_OFFSET(b,offset,len) \ + do { if (!b.dma_coherent) \ + dma_cache_wback_inv((unsigned long)b.addr+offset,len); \ + } while (0) + + +static int __init harmony_alloc_buffer(struct harmony_buffer *b, + unsigned int buffer_count) +{ + b->len = buffer_count * HARMONY_BUF_SIZE; + b->addr = dma_alloc_coherent(&harmony.dev->dev, + b->len, &b->dma_handle, GFP_KERNEL|GFP_DMA); + if (b->addr && b->dma_handle) { + b->dma_coherent = 1; + DPRINTK(KERN_INFO PFX "coherent memory: 0x%lx, played_buf: 0x%lx\n", + (unsigned long)b->dma_handle, (unsigned long)b->addr); + } else { + b->dma_coherent = 0; + /* kmalloc()ed memory will HPMC on ccio machines ! */ + b->addr = kmalloc(b->len, GFP_KERNEL); + if (!b->addr) { + printk(KERN_ERR PFX "couldn't allocate memory\n"); + return -EBUSY; + } + b->dma_handle = __pa(b->addr); + } + return 0; +} + +static void __exit harmony_free_buffer(struct harmony_buffer *b) +{ + if (!b->addr) + return; + + if (b->dma_coherent) + dma_free_coherent(&harmony.dev->dev, + b->len, b->addr, b->dma_handle); + else + kfree(b->addr); + + memset(b, 0, sizeof(*b)); +} + + + +/* + * Low-Level sound-chip programming + */ + +static void __inline__ harmony_wait_CNTL(void) +{ + /* Wait until we're out of control mode */ + while (gsc_readl(&harmony.hpa->cntl) & CNTL_C) + /* wait */ ; +} + + +static void harmony_update_control(void) +{ + u32 default_cntl; + + /* Set CNTL */ + default_cntl = (CNTL_C | /* The C bit */ + (harmony.data_format << 6) | /* Set the data format */ + (harmony.stereo_select << 5) | /* Stereo select */ + (harmony.sample_rate)); /* Set sample rate */ + harmony.format_initialized = 1; + + /* initialize CNTL */ + gsc_writel(default_cntl, &harmony.hpa->cntl); +} + +static void harmony_set_control(u8 data_format, u8 sample_rate, u8 stereo_select) +{ + harmony.sample_rate = sample_rate; + harmony.data_format = data_format; + harmony.stereo_select = stereo_select; + harmony_update_control(); +} + +static void harmony_set_rate(u8 data_rate) +{ + harmony.sample_rate = data_rate; + harmony_update_control(); +} + +static int harmony_detect_rate(int *freq) +{ + int newrate; + switch (*freq) { + case 8000: newrate = HARMONY_SR_8KHZ; break; + case 16000: newrate = HARMONY_SR_16KHZ; break; + case 27428: newrate = HARMONY_SR_27KHZ; break; + case 32000: newrate = HARMONY_SR_32KHZ; break; + case 48000: newrate = HARMONY_SR_48KHZ; break; + case 9600: newrate = HARMONY_SR_9KHZ; break; + case 5512: newrate = HARMONY_SR_5KHZ; break; + case 11025: newrate = HARMONY_SR_11KHZ; break; + case 18900: newrate = HARMONY_SR_18KHZ; break; + case 22050: newrate = HARMONY_SR_22KHZ; break; + case 37800: newrate = HARMONY_SR_37KHZ; break; + case 44100: newrate = HARMONY_SR_44KHZ; break; + case 33075: newrate = HARMONY_SR_33KHZ; break; + case 6615: newrate = HARMONY_SR_6KHZ; break; + default: newrate = HARMONY_SR_8KHZ; + *freq = 8000; break; + } + return newrate; +} + +static void harmony_set_format(u8 data_format) +{ + harmony.data_format = data_format; + harmony_update_control(); +} + +static void harmony_set_stereo(u8 stereo_select) +{ + harmony.stereo_select = stereo_select; + harmony_update_control(); +} + +static void harmony_disable_interrupts(void) +{ + harmony_wait_CNTL(); + gsc_writel(0, &harmony.hpa->dstatus); +} + +static void harmony_enable_interrupts(void) +{ + harmony_wait_CNTL(); + gsc_writel(DSTATUS_IE, &harmony.hpa->dstatus); +} + +/* + * harmony_silence() + * + * This subroutine fills in a buffer starting at location start and + * silences for length bytes. This references the current + * configuration of the audio format. + * + */ + +static void harmony_silence(struct harmony_buffer *buffer, int start, int length) +{ + u8 silence_char; + + /* Despite what you hear, silence is different in + different audio formats. */ + switch (harmony.data_format) { + case HARMONY_DF_8BIT_ULAW: silence_char = 0x55; break; + case HARMONY_DF_8BIT_ALAW: silence_char = 0xff; break; + case HARMONY_DF_16BIT_LINEAR: /* fall through */ + default: silence_char = 0; + } + + memset(buffer->addr+start, silence_char, length); +} + + +static int harmony_audio_open(struct inode *inode, struct file *file) +{ + if (harmony.audio_open) + return -EBUSY; + + harmony.audio_open = 1; + harmony.suspended_playing = harmony.suspended_recording = 1; + harmony.blocked_playing = harmony.blocked_recording = 0; + harmony.first_filled_play = harmony.first_filled_record = 0; + harmony.nb_filled_play = harmony.nb_filled_record = 0; + harmony.play_offset = 0; + init_waitqueue_head(&harmony.wq_play); + init_waitqueue_head(&harmony.wq_record); + + /* Start off in a balanced mode. */ + harmony_set_control(HARMONY_DF_8BIT_ULAW, HARMONY_SR_8KHZ, HARMONY_SS_MONO); + harmony_update_control(); + harmony.format_initialized = 0; + + /* Clear out all the buffers and flush to cache */ + harmony_silence(&played_buf, 0, HARMONY_BUF_SIZE*MAX_BUFS); + CHECK_WBACK_INV_OFFSET(played_buf, 0, HARMONY_BUF_SIZE*MAX_BUFS); + + return 0; +} + +/* + * Release (close) the audio device. + */ + +static int harmony_audio_release(struct inode *inode, struct file *file) +{ + if (!harmony.audio_open) + return -EBUSY; + + harmony.audio_open = 0; + + return 0; +} + +/* + * Read recorded data off the audio device. + */ + +static ssize_t harmony_audio_read(struct file *file, + char *buffer, + size_t size_count, + loff_t *ppos) +{ + int total_count = (int) size_count; + int count = 0; + int buf_to_read; + + while (count24) { + if (copy_from_user(file_header, buffer, sizeof(file_header))) + ret = -EFAULT; + + start_string = four_bytes_to_u32(0); + + if ((file_header[4]==0) && (start_string==0x2E736E64)) { + u32 format; + u32 nb_voices; + u32 speed; + + format = four_bytes_to_u32(12); + nb_voices = four_bytes_to_u32(20); + speed = four_bytes_to_u32(16); + + switch (format) { + case HARMONY_MAGIC_8B_ULAW: + harmony.data_format = HARMONY_DF_8BIT_ULAW; + break; + case HARMONY_MAGIC_8B_ALAW: + harmony.data_format = HARMONY_DF_8BIT_ALAW; + break; + case HARMONY_MAGIC_16B_LINEAR: + harmony.data_format = HARMONY_DF_16BIT_LINEAR; + break; + default: + harmony_set_control(HARMONY_DF_16BIT_LINEAR, + HARMONY_SR_44KHZ, HARMONY_SS_STEREO); + goto out; + } + switch (nb_voices) { + case HARMONY_MAGIC_MONO: + harmony.stereo_select = HARMONY_SS_MONO; + break; + case HARMONY_MAGIC_STEREO: + harmony.stereo_select = HARMONY_SS_STEREO; + break; + default: + harmony.stereo_select = HARMONY_SS_MONO; + break; + } + harmony_set_rate(harmony_detect_rate(&speed)); + harmony.dac_rate = speed; + goto out; + } + } + harmony_set_control(HARMONY_DF_8BIT_ULAW, HARMONY_SR_8KHZ, HARMONY_SS_MONO); +out: + return ret; +} +#undef four_bytes_to_u32 + + +static ssize_t harmony_audio_write(struct file *file, + const char *buffer, + size_t size_count, + loff_t *ppos) +{ + int total_count = (int) size_count; + int count = 0; + int frame_size; + int buf_to_fill; + int fresh_buffer; + + if (!harmony.format_initialized) { + if (harmony_format_auto_detect(buffer, total_count)) + return -EFAULT; + } + + while (count= MAX_BUFS && !harmony.play_offset) { + harmony.blocked_playing = 1; + interruptible_sleep_on(&harmony.wq_play); + harmony.blocked_playing = 0; + } + if (harmony.nb_filled_play+2 >= MAX_BUFS && !harmony.play_offset) + return -EBUSY; + + + buf_to_fill = (harmony.first_filled_play+harmony.nb_filled_play); + if (harmony.play_offset) { + buf_to_fill--; + buf_to_fill += MAX_BUFS; + } + buf_to_fill %= MAX_BUFS; + + fresh_buffer = (harmony.play_offset == 0); + + /* Figure out the size of the frame */ + if ((total_count-count) >= HARMONY_BUF_SIZE - harmony.play_offset) { + frame_size = HARMONY_BUF_SIZE - harmony.play_offset; + } else { + frame_size = total_count - count; + /* Clear out the buffer, since there we'll only be + overlaying part of the old buffer with the new one */ + harmony_silence(&played_buf, + HARMONY_BUF_SIZE*buf_to_fill+frame_size+harmony.play_offset, + HARMONY_BUF_SIZE-frame_size-harmony.play_offset); + } + + /* Copy the page to an aligned buffer */ + if (copy_from_user(played_buf.addr +(HARMONY_BUF_SIZE*buf_to_fill) + harmony.play_offset, + buffer+count, frame_size)) + return -EFAULT; + CHECK_WBACK_INV_OFFSET(played_buf, (HARMONY_BUF_SIZE*buf_to_fill + harmony.play_offset), + frame_size); + + if (fresh_buffer) + harmony.nb_filled_play++; + + count += frame_size; + harmony.play_offset += frame_size; + harmony.play_offset %= HARMONY_BUF_SIZE; + if (harmony.suspended_playing && (harmony.nb_filled_play>=4)) + harmony_enable_interrupts(); + } + + return count; +} + +static unsigned int harmony_audio_poll(struct file *file, + struct poll_table_struct *wait) +{ + unsigned int mask = 0; + + if (file->f_mode & FMODE_READ) { + if (!harmony.suspended_recording) + poll_wait(file, &harmony.wq_record, wait); + if (harmony.nb_filled_record) + mask |= POLLIN | POLLRDNORM; + } + + if (file->f_mode & FMODE_WRITE) { + if (!harmony.suspended_playing) + poll_wait(file, &harmony.wq_play, wait); + if (harmony.nb_filled_play) + mask |= POLLOUT | POLLWRNORM; + } + + return mask; +} + +static int harmony_audio_ioctl(struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long arg) +{ + int ival, new_format; + int frag_size, frag_buf; + struct audio_buf_info info; + + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, (int *) arg); + + case SNDCTL_DSP_GETCAPS: + ival = DSP_CAP_DUPLEX; + return put_user(ival, (int *) arg); + + case SNDCTL_DSP_GETFMTS: + ival = (AFMT_S16_BE | AFMT_MU_LAW | AFMT_A_LAW ); + return put_user(ival, (int *) arg); + + case SNDCTL_DSP_SETFMT: + if (get_user(ival, (int *) arg)) + return -EFAULT; + if (ival != AFMT_QUERY) { + switch (ival) { + case AFMT_MU_LAW: new_format = HARMONY_DF_8BIT_ULAW; break; + case AFMT_A_LAW: new_format = HARMONY_DF_8BIT_ALAW; break; + case AFMT_S16_BE: new_format = HARMONY_DF_16BIT_LINEAR; break; + default: { + DPRINTK(KERN_WARNING PFX + "unsupported sound format 0x%04x requested.\n", + ival); + ival = AFMT_S16_BE; + return put_user(ival, (int *) arg); + } + } + harmony_set_format(new_format); + return 0; + } else { + switch (harmony.data_format) { + case HARMONY_DF_8BIT_ULAW: ival = AFMT_MU_LAW; break; + case HARMONY_DF_8BIT_ALAW: ival = AFMT_A_LAW; break; + case HARMONY_DF_16BIT_LINEAR: ival = AFMT_U16_BE; break; + default: ival = 0; + } + return put_user(ival, (int *) arg); + } + + case SOUND_PCM_READ_RATE: + ival = harmony.dac_rate; + return put_user(ival, (int *) arg); + + case SNDCTL_DSP_SPEED: + if (get_user(ival, (int *) arg)) + return -EFAULT; + harmony_set_rate(harmony_detect_rate(&ival)); + harmony.dac_rate = ival; + return put_user(ival, (int*) arg); + + case SNDCTL_DSP_STEREO: + if (get_user(ival, (int *) arg)) + return -EFAULT; + if (ival != 0 && ival != 1) + return -EINVAL; + harmony_set_stereo(ival); + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(ival, (int *) arg)) + return -EFAULT; + if (ival != 1 && ival != 2) { + ival = harmony.stereo_select == HARMONY_SS_MONO ? 1 : 2; + return put_user(ival, (int *) arg); + } + harmony_set_stereo(ival-1); + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + ival = HARMONY_BUF_SIZE; + return put_user(ival, (int *) arg); + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_RESET: + if (!harmony.suspended_recording) { + /* TODO: stop_recording() */ + } + return 0; + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(ival, (int *)arg)) + return -EFAULT; + frag_size = ival & 0xffff; + frag_buf = (ival>>16) & 0xffff; + /* TODO: We use hardcoded fragment sizes and numbers for now */ + frag_size = 12; /* 4096 == 2^12 */ + frag_buf = MAX_BUFS; + ival = (frag_buf << 16) + frag_size; + return put_user(ival, (int *) arg); + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + info.fragstotal = MAX_BUFS; + info.fragments = MAX_BUFS - harmony.nb_filled_play; + info.fragsize = HARMONY_BUF_SIZE; + info.bytes = info.fragments * info.fragsize; + return copy_to_user((void *)arg, &info, sizeof(info)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + info.fragstotal = MAX_BUFS; + info.fragments = /*MAX_BUFS-*/ harmony.nb_filled_record; + info.fragsize = HARMONY_BUF_SIZE; + info.bytes = info.fragments * info.fragsize; + return copy_to_user((void *)arg, &info, sizeof(info)) ? -EFAULT : 0; + + case SNDCTL_DSP_SYNC: + return 0; + } + + return -EINVAL; +} + + +/* + * harmony_interrupt() + * + * harmony interruption service routine + * + */ + +static irqreturn_t harmony_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + u32 dstatus; + struct harmony_hpa *hpa; + + /* Setup the hpa */ + hpa = ((struct harmony_dev *)dev)->hpa; + harmony_wait_CNTL(); + + /* Read dstatus and pcuradd (the current address) */ + dstatus = gsc_readl(&hpa->dstatus); + + /* Turn off interrupts */ + harmony_disable_interrupts(); + + /* Check if this is a request to get the next play buffer */ + if (dstatus & DSTATUS_PN) { + if (!harmony.nb_filled_play) { + harmony.suspended_playing = 1; + gsc_writel((unsigned long)silent.dma_handle, &hpa->pnxtadd); + + if (!harmony.suspended_recording) + harmony_enable_interrupts(); + } else { + harmony.suspended_playing = 0; + gsc_writel((unsigned long)played_buf.dma_handle + + (HARMONY_BUF_SIZE*harmony.first_filled_play), + &hpa->pnxtadd); + harmony.first_filled_play++; + harmony.first_filled_play %= MAX_BUFS; + harmony.nb_filled_play--; + + harmony_enable_interrupts(); + } + + if (harmony.blocked_playing) + wake_up_interruptible(&harmony.wq_play); + } + + /* Check if we're being asked to fill in a recording buffer */ + if (dstatus & DSTATUS_RN) { + if((harmony.nb_filled_record+2>=MAX_BUFS) || harmony.suspended_recording) + { + harmony.nb_filled_record = 0; + harmony.first_filled_record = 0; + harmony.suspended_recording = 1; + gsc_writel((unsigned long)graveyard.dma_handle, &hpa->rnxtadd); + if (!harmony.suspended_playing) + harmony_enable_interrupts(); + } else { + int buf_to_fill; + buf_to_fill = (harmony.first_filled_record+harmony.nb_filled_record) % MAX_BUFS; + CHECK_WBACK_INV_OFFSET(recorded_buf, HARMONY_BUF_SIZE*buf_to_fill, HARMONY_BUF_SIZE); + gsc_writel((unsigned long)recorded_buf.dma_handle + + HARMONY_BUF_SIZE*buf_to_fill, + &hpa->rnxtadd); + harmony.nb_filled_record++; + harmony_enable_interrupts(); + } + + if (harmony.blocked_recording && harmony.nb_filled_record>3) + wake_up_interruptible(&harmony.wq_record); + } + return IRQ_HANDLED; +} + +/* + * Sound playing functions + */ + +static struct file_operations harmony_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = harmony_audio_read, + .write = harmony_audio_write, + .poll = harmony_audio_poll, + .ioctl = harmony_audio_ioctl, + .open = harmony_audio_open, + .release = harmony_audio_release, +}; + +static int harmony_audio_init(void) +{ + /* Request that IRQ */ + if (request_irq(harmony.dev->irq, harmony_interrupt, 0 ,"harmony", &harmony)) { + printk(KERN_ERR PFX "Error requesting irq %d.\n", harmony.dev->irq); + return -EFAULT; + } + + harmony.dsp_unit = register_sound_dsp(&harmony_audio_fops, -1); + if (harmony.dsp_unit < 0) { + printk(KERN_ERR PFX "Error registering dsp\n"); + free_irq(harmony.dev->irq, &harmony); + return -EFAULT; + } + + /* Clear the buffers so you don't end up with crap in the buffers. */ + harmony_silence(&played_buf, 0, HARMONY_BUF_SIZE*MAX_BUFS); + + /* Make sure this makes it to cache */ + CHECK_WBACK_INV_OFFSET(played_buf, 0, HARMONY_BUF_SIZE*MAX_BUFS); + + /* Clear out the silent buffer and flush to cache */ + harmony_silence(&silent, 0, HARMONY_BUF_SIZE); + CHECK_WBACK_INV_OFFSET(silent, 0, HARMONY_BUF_SIZE); + + harmony.audio_open = 0; + + return 0; +} + + +/* + * mixer functions + */ + +static void harmony_mixer_set_gain(void) +{ + harmony_wait_CNTL(); + gsc_writel(harmony.current_gain, &harmony.hpa->gainctl); +} + +/* + * Read gain of selected channel. + * The OSS rate is from 0 (silent) to 100 -> need some conversions + * + * The harmony gain are attenuation for output and monitor gain. + * is amplifaction for input gain + */ +#define to_harmony_level(level,max) ((level)*max/100) +#define to_oss_level(level,max) ((level)*100/max) + +static int harmony_mixer_get_level(int channel) +{ + int left_level; + int right_level; + + switch (channel) { + case SOUND_MIXER_VOLUME: + left_level = (harmony.current_gain & GAIN_LO_MASK) >> GAIN_LO_SHIFT; + right_level = (harmony.current_gain & GAIN_RO_MASK) >> GAIN_RO_SHIFT; + left_level = to_oss_level(MAX_OUTPUT_LEVEL - left_level, MAX_OUTPUT_LEVEL); + right_level = to_oss_level(MAX_OUTPUT_LEVEL - right_level, MAX_OUTPUT_LEVEL); + return (right_level << 8)+left_level; + + case SOUND_MIXER_IGAIN: + left_level = (harmony.current_gain & GAIN_LI_MASK) >> GAIN_LI_SHIFT; + right_level= (harmony.current_gain & GAIN_RI_MASK) >> GAIN_RI_SHIFT; + left_level = to_oss_level(left_level, MAX_INPUT_LEVEL); + right_level= to_oss_level(right_level, MAX_INPUT_LEVEL); + return (right_level << 8)+left_level; + + case SOUND_MIXER_MONITOR: + left_level = (harmony.current_gain & GAIN_MA_MASK) >> GAIN_MA_SHIFT; + left_level = to_oss_level(MAX_MONITOR_LEVEL-left_level, MAX_MONITOR_LEVEL); + return (left_level << 8)+left_level; + } + return -EINVAL; +} + + + +/* + * Some conversions for the same reasons. + * We give back the new real value(s) due to + * the rescale. + */ + +static int harmony_mixer_set_level(int channel, int value) +{ + int left_level; + int right_level; + int new_left_level; + int new_right_level; + + right_level = (value & 0x0000ff00) >> 8; + left_level = value & 0x000000ff; + if (right_level > 100) right_level = 100; + if (left_level > 100) left_level = 100; + + switch (channel) { + case SOUND_MIXER_VOLUME: + right_level = to_harmony_level(100-right_level, MAX_OUTPUT_LEVEL); + left_level = to_harmony_level(100-left_level, MAX_OUTPUT_LEVEL); + new_right_level = to_oss_level(MAX_OUTPUT_LEVEL - right_level, MAX_OUTPUT_LEVEL); + new_left_level = to_oss_level(MAX_OUTPUT_LEVEL - left_level, MAX_OUTPUT_LEVEL); + harmony.current_gain = (harmony.current_gain & ~(GAIN_LO_MASK | GAIN_RO_MASK)) + | (left_level << GAIN_LO_SHIFT) | (right_level << GAIN_RO_SHIFT); + harmony_mixer_set_gain(); + return (new_right_level << 8) + new_left_level; + + case SOUND_MIXER_IGAIN: + right_level = to_harmony_level(right_level, MAX_INPUT_LEVEL); + left_level = to_harmony_level(left_level, MAX_INPUT_LEVEL); + new_right_level = to_oss_level(right_level, MAX_INPUT_LEVEL); + new_left_level = to_oss_level(left_level, MAX_INPUT_LEVEL); + harmony.current_gain = (harmony.current_gain & ~(GAIN_LI_MASK | GAIN_RI_MASK)) + | (left_level << GAIN_LI_SHIFT) | (right_level << GAIN_RI_SHIFT); + harmony_mixer_set_gain(); + return (new_right_level << 8) + new_left_level; + + case SOUND_MIXER_MONITOR: + left_level = to_harmony_level(100-left_level, MAX_MONITOR_LEVEL); + new_left_level = to_oss_level(MAX_MONITOR_LEVEL-left_level, MAX_MONITOR_LEVEL); + harmony.current_gain = (harmony.current_gain & ~GAIN_MA_MASK) | (left_level << GAIN_MA_SHIFT); + harmony_mixer_set_gain(); + return (new_left_level << 8) + new_left_level; + } + + return -EINVAL; +} + +#undef to_harmony_level +#undef to_oss_level + +/* + * Return the selected input device (mic or line) + */ + +static int harmony_mixer_get_recmask(void) +{ + int current_input_line; + + current_input_line = (harmony.current_gain & GAIN_IS_MASK) + >> GAIN_IS_SHIFT; + if (current_input_line) + return SOUND_MASK_MIC; + + return SOUND_MASK_LINE; +} + +/* + * Set the input (only one at time, arbitrary priority to line in) + */ + +static int harmony_mixer_set_recmask(int recmask) +{ + int new_input_line; + int new_input_mask; + int current_input_line; + + current_input_line = (harmony.current_gain & GAIN_IS_MASK) + >> GAIN_IS_SHIFT; + if ((current_input_line && ((recmask & SOUND_MASK_LINE) || !(recmask & SOUND_MASK_MIC))) || + (!current_input_line && ((recmask & SOUND_MASK_LINE) && !(recmask & SOUND_MASK_MIC)))) { + new_input_line = 0; + new_input_mask = SOUND_MASK_LINE; + } else { + new_input_line = 1; + new_input_mask = SOUND_MASK_MIC; + } + harmony.current_gain = ((harmony.current_gain & ~GAIN_IS_MASK) | + (new_input_line << GAIN_IS_SHIFT )); + harmony_mixer_set_gain(); + return new_input_mask; +} + + +/* + * give the active outlines + */ + +static int harmony_mixer_get_outmask(void) +{ + int outmask = 0; + + if (harmony.current_gain & GAIN_SE_MASK) outmask |= MASK_INTERNAL; + if (harmony.current_gain & GAIN_LE_MASK) outmask |= MASK_LINEOUT; + if (harmony.current_gain & GAIN_HE_MASK) outmask |= MASK_HEADPHONES; + + return outmask; +} + + +static int harmony_mixer_set_outmask(int outmask) +{ + if (outmask & MASK_INTERNAL) + harmony.current_gain |= GAIN_SE_MASK; + else + harmony.current_gain &= ~GAIN_SE_MASK; + + if (outmask & MASK_LINEOUT) + harmony.current_gain |= GAIN_LE_MASK; + else + harmony.current_gain &= ~GAIN_LE_MASK; + + if (outmask & MASK_HEADPHONES) + harmony.current_gain |= GAIN_HE_MASK; + else + harmony.current_gain &= ~GAIN_HE_MASK; + + harmony_mixer_set_gain(); + + return (outmask & (MASK_INTERNAL | MASK_LINEOUT | MASK_HEADPHONES)); +} + +/* + * This code is inspired from sb_mixer.c + */ + +static int harmony_mixer_ioctl(struct inode * inode, struct file * file, + unsigned int cmd, unsigned long arg) +{ + int val; + int ret; + + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + memset(&info, 0, sizeof(info)); + strncpy(info.id, "harmony", sizeof(info.id)-1); + strncpy(info.name, "Harmony audio", sizeof(info.name)-1); + info.modify_counter = 1; /* ? */ + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, (int *)arg); + + /* read */ + val = 0; + if (_SIOC_DIR(cmd) & _SIOC_WRITE) + if (get_user(val, (int *)arg)) + return -EFAULT; + + switch (cmd) { + case MIXER_READ(SOUND_MIXER_CAPS): + ret = SOUND_CAP_EXCL_INPUT; + break; + case MIXER_READ(SOUND_MIXER_STEREODEVS): + ret = SOUND_MASK_VOLUME | SOUND_MASK_IGAIN; + break; + + case MIXER_READ(SOUND_MIXER_RECMASK): + ret = SOUND_MASK_MIC | SOUND_MASK_LINE; + break; + case MIXER_READ(SOUND_MIXER_DEVMASK): + ret = SOUND_MASK_VOLUME | SOUND_MASK_IGAIN | + SOUND_MASK_MONITOR; + break; + case MIXER_READ(SOUND_MIXER_OUTMASK): + ret = MASK_INTERNAL | MASK_LINEOUT | + MASK_HEADPHONES; + break; + + case MIXER_WRITE(SOUND_MIXER_RECSRC): + ret = harmony_mixer_set_recmask(val); + break; + case MIXER_READ(SOUND_MIXER_RECSRC): + ret = harmony_mixer_get_recmask(); + break; + + case MIXER_WRITE(SOUND_MIXER_OUTSRC): + ret = harmony_mixer_set_outmask(val); + break; + case MIXER_READ(SOUND_MIXER_OUTSRC): + ret = harmony_mixer_get_outmask(); + break; + + case MIXER_WRITE(SOUND_MIXER_VOLUME): + case MIXER_WRITE(SOUND_MIXER_IGAIN): + case MIXER_WRITE(SOUND_MIXER_MONITOR): + ret = harmony_mixer_set_level(cmd & 0xff, val); + break; + + case MIXER_READ(SOUND_MIXER_VOLUME): + case MIXER_READ(SOUND_MIXER_IGAIN): + case MIXER_READ(SOUND_MIXER_MONITOR): + ret = harmony_mixer_get_level(cmd & 0xff); + break; + + default: + return -EINVAL; + } + + if (put_user(ret, (int *)arg)) + return -EFAULT; + return 0; +} + + +static int harmony_mixer_open(struct inode *inode, struct file *file) +{ + if (harmony.mixer_open) + return -EBUSY; + harmony.mixer_open = 1; + return 0; +} + +static int harmony_mixer_release(struct inode *inode, struct file *file) +{ + if (!harmony.mixer_open) + return -EBUSY; + harmony.mixer_open = 0; + return 0; +} + +static struct file_operations harmony_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = harmony_mixer_open, + .release = harmony_mixer_release, + .ioctl = harmony_mixer_ioctl, +}; + + +/* + * Mute all the output and reset Harmony. + */ + +static void __init harmony_mixer_reset(void) +{ + harmony.current_gain = GAIN_TOTAL_SILENCE; + harmony_mixer_set_gain(); + harmony_wait_CNTL(); + gsc_writel(1, &harmony.hpa->reset); + mdelay(50); /* wait 50 ms */ + gsc_writel(0, &harmony.hpa->reset); + harmony.current_gain = GAIN_DEFAULT; + harmony_mixer_set_gain(); +} + +static int __init harmony_mixer_init(void) +{ + /* Register the device file operations */ + harmony.mixer_unit = register_sound_mixer(&harmony_mixer_fops, -1); + if (harmony.mixer_unit < 0) { + printk(KERN_WARNING PFX "Error Registering Mixer Driver\n"); + return -EFAULT; + } + + harmony_mixer_reset(); + harmony.mixer_open = 0; + + return 0; +} + + + +/* + * This is the callback that's called by the inventory hardware code + * if it finds a match to the registered driver. + */ +static int __devinit +harmony_driver_probe(struct parisc_device *dev) +{ + u8 id; + u8 rev; + u32 cntl; + int ret; + + if (harmony.hpa) { + /* We only support one Harmony at this time */ + printk(KERN_ERR PFX "driver already registered\n"); + return -EBUSY; + } + + if (!dev->irq) { + printk(KERN_ERR PFX "no irq found\n"); + return -ENODEV; + } + + /* Set the HPA of harmony */ + harmony.hpa = (struct harmony_hpa *)dev->hpa; + harmony.dev = dev; + + /* Grab the ID and revision from the device */ + id = gsc_readb(&harmony.hpa->id); + if ((id | 1) != 0x15) { + printk(KERN_WARNING PFX "wrong harmony id 0x%02x\n", id); + return -EBUSY; + } + cntl = gsc_readl(&harmony.hpa->cntl); + rev = (cntl>>20) & 0xff; + + printk(KERN_INFO "Lasi Harmony Audio driver " HARMONY_VERSION ", " + "h/w id %i, rev. %i at 0x%lx, IRQ %i\n", + id, rev, dev->hpa, harmony.dev->irq); + + /* Make sure the control bit isn't set, although I don't think it + ever is. */ + if (cntl & CNTL_C) { + printk(KERN_WARNING PFX "CNTL busy\n"); + harmony.hpa = 0; + return -EBUSY; + } + + /* Initialize the memory buffers */ + if (harmony_alloc_buffer(&played_buf, MAX_BUFS) || + harmony_alloc_buffer(&recorded_buf, MAX_BUFS) || + harmony_alloc_buffer(&graveyard, 1) || + harmony_alloc_buffer(&silent, 1)) { + ret = -EBUSY; + goto out_err; + } + + /* Initialize /dev/mixer and /dev/audio */ + if ((ret=harmony_mixer_init())) + goto out_err; + if ((ret=harmony_audio_init())) + goto out_err; + + return 0; + +out_err: + harmony.hpa = 0; + harmony_free_buffer(&played_buf); + harmony_free_buffer(&recorded_buf); + harmony_free_buffer(&graveyard); + harmony_free_buffer(&silent); + return ret; +} + + +static struct parisc_device_id harmony_tbl[] = { + /* { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007A }, Bushmaster/Flounder */ + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007B }, /* 712/715 Audio */ + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007E }, /* Pace Audio */ + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007F }, /* Outfield / Coral II */ + { 0, } +}; + +MODULE_DEVICE_TABLE(parisc, harmony_tbl); + +static struct parisc_driver harmony_driver = { + .name = "Lasi Harmony", + .id_table = harmony_tbl, + .probe = harmony_driver_probe, +}; + +static int __init init_harmony(void) +{ + return register_parisc_driver(&harmony_driver); +} + +static void __exit cleanup_harmony(void) +{ + free_irq(harmony.dev->irq, &harmony); + unregister_sound_mixer(harmony.mixer_unit); + unregister_sound_dsp(harmony.dsp_unit); + harmony_free_buffer(&played_buf); + harmony_free_buffer(&recorded_buf); + harmony_free_buffer(&graveyard); + harmony_free_buffer(&silent); + unregister_parisc_driver(&harmony_driver); +} + + +MODULE_AUTHOR("Alex DeVries "); +MODULE_DESCRIPTION("Harmony sound driver"); +MODULE_LICENSE("GPL"); + +module_init(init_harmony); +module_exit(cleanup_harmony); + diff --git a/sound/oss/hex2hex.c b/sound/oss/hex2hex.c new file mode 100644 index 000000000000..5460faae98c9 --- /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 +#include +#include + +#define ABANDON(why) { fprintf(stderr, "%s\n", why); exit(1); } +#define MAX_SIZE (256*1024) +unsigned char buf[MAX_SIZE]; + +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= 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 + * + * Built from: + * Low level code: Zach Brown (original nonworking i810 OSS driver) + * Jaroslav Kysela (working ALSA driver) + * + * Framework: Thomas Sailer + * Extended by: Zach Brown + * and others.. + * + * Hardware Provided By: + * Analog Devices (A major AC97 codec maker) + * Intel Corp (you've probably heard of them already) + * + * AC97 clues and assistance provided by + * Analog Devices + * Zach 'Fufu' Brown + * Jeff Garzik + * + * 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. + * + * + * Intel 810 theory of operation + * + * The chipset provides three DMA channels that talk to an AC97 + * CODEC (AC97 is a digital/analog mixer standard). At its simplest + * you get 48Khz audio with basic volume and mixer controls. At the + * best you get rate adaption in the codec. We set the card up so + * that we never take completion interrupts but instead keep the card + * chasing its tail around a ring buffer. This is needed for mmap + * mode audio and happens to work rather well for non-mmap modes too. + * + * The board has one output channel for PCM audio (supported) and + * a stereo line in and mono microphone input. Again these are normally + * locked to 48Khz only. Right now recording is not finished. + * + * There is no midi support, no synth support. Use timidity. To get + * esd working you need to use esd -r 48000 as it won't probe 48KHz + * by default. mpg123 can't handle 48Khz only audio so use xmms. + * + * Fix The Sound On Dell + * + * Not everyone uses 48KHz. We know of no way to detect this reliably + * and certainly not to get the right data. If your i810 audio sounds + * stupid you may need to investigate other speeds. According to Analog + * they tend to use a 14.318MHz clock which gives you a base rate of + * 41194Hz. + * + * This is available via the 'ftsodell=1' option. + * + * If you need to force a specific rate set the clocking= option + * + * This driver is cursed. (Ben LaHaise) + * + * ICH 3 caveats + * Intel errata #7 for ICH3 IO. We need to disable SMI stuff + * when codec probing. [Not Yet Done] + * + * ICH 4 caveats + * + * The ICH4 has the feature, that the codec ID doesn't have to be + * congruent with the IO connection. + * + * Therefore, from driver version 0.23 on, there is a "codec ID" <-> + * "IO register base offset" mapping (card->ac97_id_map) field. + * + * Juergen "George" Sawinski (jsaw) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_VERSION "1.01" + +#define MODULOP2(a, b) ((a) & ((b) - 1)) +#define MASKP2(a, b) ((a) & ~((b) - 1)) + +static int ftsodell; +static int strict_clocking; +static unsigned int clocking; +static int spdif_locked; +static int ac97_quirk = AC97_TUNE_DEFAULT; + +//#define DEBUG +//#define DEBUG2 +//#define DEBUG_INTERRUPTS +//#define DEBUG_MMAP +//#define DEBUG_MMIO + +#define ADC_RUNNING 1 +#define DAC_RUNNING 2 + +#define I810_FMT_16BIT 1 +#define I810_FMT_STEREO 2 +#define I810_FMT_MASK 3 + +#define SPDIF_ON 0x0004 +#define SURR_ON 0x0010 +#define CENTER_LFE_ON 0x0020 +#define VOL_MUTED 0x8000 + +/* the 810's array of pointers to data buffers */ + +struct sg_item { +#define BUSADDR_MASK 0xFFFFFFFE + u32 busaddr; +#define CON_IOC 0x80000000 /* interrupt on completion */ +#define CON_BUFPAD 0x40000000 /* pad underrun with last sample, else 0 */ +#define CON_BUFLEN_MASK 0x0000ffff /* buffer length in samples */ + u32 control; +}; + +/* an instance of the i810 channel */ +#define SG_LEN 32 +struct i810_channel +{ + /* these sg guys should probably be allocated + separately as nocache. Must be 8 byte aligned */ + struct sg_item sg[SG_LEN]; /* 32*8 */ + u32 offset; /* 4 */ + u32 port; /* 4 */ + u32 used; + u32 num; +}; + +/* + * we have 3 separate dma engines. pcm in, pcm out, and mic. + * each dma engine has controlling registers. These goofy + * names are from the datasheet, but make it easy to write + * code while leafing through it. + * + * ICH4 has 6 dma engines, pcm in, pcm out, mic, pcm in 2, + * mic in 2, s/pdif. Of special interest is the fact that + * the upper 3 DMA engines on the ICH4 *must* be accessed + * via mmio access instead of pio access. + */ + +#define ENUM_ENGINE(PRE,DIG) \ +enum { \ + PRE##_BASE = 0x##DIG##0, /* Base Address */ \ + PRE##_BDBAR = 0x##DIG##0, /* Buffer Descriptor list Base Address */ \ + PRE##_CIV = 0x##DIG##4, /* Current Index Value */ \ + PRE##_LVI = 0x##DIG##5, /* Last Valid Index */ \ + PRE##_SR = 0x##DIG##6, /* Status Register */ \ + PRE##_PICB = 0x##DIG##8, /* Position In Current Buffer */ \ + PRE##_PIV = 0x##DIG##a, /* Prefetched Index Value */ \ + PRE##_CR = 0x##DIG##b /* Control Register */ \ +} + +ENUM_ENGINE(OFF,0); /* Offsets */ +ENUM_ENGINE(PI,0); /* PCM In */ +ENUM_ENGINE(PO,1); /* PCM Out */ +ENUM_ENGINE(MC,2); /* Mic In */ + +enum { + GLOB_CNT = 0x2c, /* Global Control */ + GLOB_STA = 0x30, /* Global Status */ + CAS = 0x34 /* Codec Write Semaphore Register */ +}; + +ENUM_ENGINE(MC2,4); /* Mic In 2 */ +ENUM_ENGINE(PI2,5); /* PCM In 2 */ +ENUM_ENGINE(SP,6); /* S/PDIF */ + +enum { + SDM = 0x80 /* SDATA_IN Map Register */ +}; + +/* interrupts for a dma engine */ +#define DMA_INT_FIFO (1<<4) /* fifo under/over flow */ +#define DMA_INT_COMPLETE (1<<3) /* buffer read/write complete and ioc set */ +#define DMA_INT_LVI (1<<2) /* last valid done */ +#define DMA_INT_CELV (1<<1) /* last valid is current */ +#define DMA_INT_DCH (1) /* DMA Controller Halted (happens on LVI interrupts) */ +#define DMA_INT_MASK (DMA_INT_FIFO|DMA_INT_COMPLETE|DMA_INT_LVI) + +/* interrupts for the whole chip */ +#define INT_SEC (1<<11) +#define INT_PRI (1<<10) +#define INT_MC (1<<7) +#define INT_PO (1<<6) +#define INT_PI (1<<5) +#define INT_MO (1<<2) +#define INT_NI (1<<1) +#define INT_GPI (1<<0) +#define INT_MASK (INT_SEC|INT_PRI|INT_MC|INT_PO|INT_PI|INT_MO|INT_NI|INT_GPI) + +/* magic numbers to protect our data structures */ +#define I810_CARD_MAGIC 0x5072696E /* "Prin" */ +#define I810_STATE_MAGIC 0x63657373 /* "cess" */ +#define I810_DMA_MASK 0xffffffff /* DMA buffer mask for pci_alloc_consist */ +#define NR_HW_CH 3 + +/* maxinum number of AC97 codecs connected, AC97 2.0 defined 4 */ +#define NR_AC97 4 + +/* Please note that an 8bit mono stream is not valid on this card, you must have a 16bit */ +/* stream at a minimum for this card to be happy */ +static const unsigned sample_size[] = { 1, 2, 2, 4 }; +/* Samples are 16bit values, so we are shifting to a word, not to a byte, hence shift */ +/* values are one less than might be expected */ +static const unsigned sample_shift[] = { -1, 0, 0, 1 }; + +enum { + ICH82801AA = 0, + ICH82901AB, + INTEL440MX, + INTELICH2, + INTELICH3, + INTELICH4, + INTELICH5, + SI7012, + NVIDIA_NFORCE, + AMD768, + AMD8111 +}; + +static char * card_names[] = { + "Intel ICH 82801AA", + "Intel ICH 82901AB", + "Intel 440MX", + "Intel ICH2", + "Intel ICH3", + "Intel ICH4", + "Intel ICH5", + "SiS 7012", + "NVIDIA nForce Audio", + "AMD 768", + "AMD-8111 IOHub" +}; + +/* These are capabilities (and bugs) the chipsets _can_ have */ +static struct { + int16_t nr_ac97; +#define CAP_MMIO 0x0001 +#define CAP_20BIT_AUDIO_SUPPORT 0x0002 + u_int16_t flags; +} card_cap[] = { + { 1, 0x0000 }, /* ICH82801AA */ + { 1, 0x0000 }, /* ICH82901AB */ + { 1, 0x0000 }, /* INTEL440MX */ + { 1, 0x0000 }, /* INTELICH2 */ + { 2, 0x0000 }, /* INTELICH3 */ + { 3, 0x0003 }, /* INTELICH4 */ + { 3, 0x0003 }, /* INTELICH5 */ + /*@FIXME to be verified*/ { 2, 0x0000 }, /* SI7012 */ + /*@FIXME to be verified*/ { 2, 0x0000 }, /* NVIDIA_NFORCE */ + /*@FIXME to be verified*/ { 2, 0x0000 }, /* AMD768 */ + /*@FIXME to be verified*/ { 3, 0x0001 }, /* AMD8111 */ +}; + +static struct pci_device_id i810_pci_tbl [] = { + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_5, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, ICH82801AA}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_5, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, ICH82901AB}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_440MX, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, INTEL440MX}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_4, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, INTELICH2}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_5, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, INTELICH3}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_5, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, INTELICH4}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_5, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, INTELICH5}, + {PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_7012, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, SI7012}, + {PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_MCP1_AUDIO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, NVIDIA_NFORCE}, + {PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_MCP2_AUDIO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, NVIDIA_NFORCE}, + {PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_MCP3_AUDIO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, NVIDIA_NFORCE}, + {PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_OPUS_7445, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, AMD768}, + {PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_AUDIO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, AMD8111}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_5, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, INTELICH4}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_18, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, INTELICH4}, + + {0,} +}; + +MODULE_DEVICE_TABLE (pci, i810_pci_tbl); + +#ifdef CONFIG_PM +#define PM_SUSPENDED(card) (card->pm_suspended) +#else +#define PM_SUSPENDED(card) (0) +#endif + +/* "software" or virtual channel, an instance of opened /dev/dsp */ +struct i810_state { + unsigned int magic; + struct i810_card *card; /* Card info */ + + /* single open lock mechanism, only used for recording */ + struct semaphore open_sem; + wait_queue_head_t open_wait; + + /* file mode */ + mode_t open_mode; + + /* virtual channel number */ + int virt; + +#ifdef CONFIG_PM + unsigned int pm_saved_dac_rate,pm_saved_adc_rate; +#endif + struct dmabuf { + /* wave sample stuff */ + unsigned int rate; + unsigned char fmt, enable, trigger; + + /* hardware channel */ + struct i810_channel *read_channel; + struct i810_channel *write_channel; + + /* OSS buffer management stuff */ + void *rawbuf; + dma_addr_t dma_handle; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + + /* our buffer acts like a circular ring */ + unsigned hwptr; /* where dma last started, updated by update_ptr */ + unsigned swptr; /* where driver last clear/filled, updated by read/write */ + int count; /* bytes to be consumed or been generated by dma machine */ + unsigned total_bytes; /* total bytes dmaed by hardware */ + + unsigned error; /* number of over/underruns */ + wait_queue_head_t wait; /* put process on wait queue when no more space in buffer */ + + /* redundant, but makes calculations easier */ + /* what the hardware uses */ + unsigned dmasize; + unsigned fragsize; + unsigned fragsamples; + + /* what we tell the user to expect */ + unsigned userfrags; + unsigned userfragsize; + + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned update_flag; + unsigned ossfragsize; + unsigned ossmaxfrags; + unsigned subdivision; + } dmabuf; +}; + + +struct i810_card { + unsigned int magic; + + /* We keep i810 cards in a linked list */ + struct i810_card *next; + + /* The i810 has a certain amount of cross channel interaction + so we use a single per card lock */ + spinlock_t lock; + + /* Control AC97 access serialization */ + spinlock_t ac97_lock; + + /* PCI device stuff */ + struct pci_dev * pci_dev; + u16 pci_id; + u16 pci_id_internal; /* used to access card_cap[] */ +#ifdef CONFIG_PM + u16 pm_suspended; + int pm_saved_mixer_settings[SOUND_MIXER_NRDEVICES][NR_AC97]; +#endif + /* soundcore stuff */ + int dev_audio; + + /* structures for abstraction of hardware facilities, codecs, banks and channels*/ + u16 ac97_id_map[NR_AC97]; + struct ac97_codec *ac97_codec[NR_AC97]; + struct i810_state *states[NR_HW_CH]; + struct i810_channel *channel; /* 1:1 to states[] but diff. lifetime */ + dma_addr_t chandma; + + u16 ac97_features; + u16 ac97_status; + u16 channels; + + /* hardware resources */ + unsigned long ac97base; + unsigned long iobase; + u32 irq; + + unsigned long ac97base_mmio_phys; + unsigned long iobase_mmio_phys; + u_int8_t __iomem *ac97base_mmio; + u_int8_t __iomem *iobase_mmio; + + int use_mmio; + + /* Function support */ + struct i810_channel *(*alloc_pcm_channel)(struct i810_card *); + struct i810_channel *(*alloc_rec_pcm_channel)(struct i810_card *); + struct i810_channel *(*alloc_rec_mic_channel)(struct i810_card *); + void (*free_pcm_channel)(struct i810_card *, int chan); + + /* We have a *very* long init time possibly, so use this to block */ + /* attempts to open our devices before we are ready (stops oops'es) */ + int initializing; +}; + +/* extract register offset from codec struct */ +#define IO_REG_OFF(codec) (((struct i810_card *) codec->private_data)->ac97_id_map[codec->id]) + +#define I810_IOREAD(size, type, card, off) \ +({ \ + type val; \ + if (card->use_mmio) \ + val=read##size(card->iobase_mmio+off); \ + else \ + val=in##size(card->iobase+off); \ + val; \ +}) + +#define I810_IOREADL(card, off) I810_IOREAD(l, u32, card, off) +#define I810_IOREADW(card, off) I810_IOREAD(w, u16, card, off) +#define I810_IOREADB(card, off) I810_IOREAD(b, u8, card, off) + +#define I810_IOWRITE(size, val, card, off) \ +({ \ + if (card->use_mmio) \ + write##size(val, card->iobase_mmio+off); \ + else \ + out##size(val, card->iobase+off); \ +}) + +#define I810_IOWRITEL(val, card, off) I810_IOWRITE(l, val, card, off) +#define I810_IOWRITEW(val, card, off) I810_IOWRITE(w, val, card, off) +#define I810_IOWRITEB(val, card, off) I810_IOWRITE(b, val, card, off) + +#define GET_CIV(card, port) MODULOP2(I810_IOREADB((card), (port) + OFF_CIV), SG_LEN) +#define GET_LVI(card, port) MODULOP2(I810_IOREADB((card), (port) + OFF_LVI), SG_LEN) + +/* set LVI from CIV */ +#define CIV_TO_LVI(card, port, off) \ + I810_IOWRITEB(MODULOP2(GET_CIV((card), (port)) + (off), SG_LEN), (card), (port) + OFF_LVI) + +static struct ac97_quirk ac97_quirks[] __devinitdata = { + { + .vendor = 0x0e11, + .device = 0x00b8, + .name = "Compaq Evo D510C", + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x1028, + .device = 0x00d8, + .name = "Dell Precision 530", /* AD1885 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x1028, + .device = 0x0126, + .name = "Dell Optiplex GX260", /* AD1981A */ + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x1028, + .device = 0x012d, + .name = "Dell Precision 450", /* AD1981B*/ + .type = AC97_TUNE_HP_ONLY + }, + { /* FIXME: which codec? */ + .vendor = 0x103c, + .device = 0x00c3, + .name = "Hewlett-Packard onboard", + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x103c, + .device = 0x12f1, + .name = "HP xw8200", /* AD1981B*/ + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x103c, + .device = 0x3008, + .name = "HP xw4200", /* AD1981B*/ + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x10f1, + .device = 0x2665, + .name = "Fujitsu-Siemens Celsius", /* AD1981? */ + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x10f1, + .device = 0x2885, + .name = "AMD64 Mobo", /* ALC650 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x110a, + .device = 0x0056, + .name = "Fujitsu-Siemens Scenic", /* AD1981? */ + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x11d4, + .device = 0x5375, + .name = "ADI AD1985 (discrete)", + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x1462, + .device = 0x5470, + .name = "MSI P4 ATX 645 Ultra", + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x1734, + .device = 0x0088, + .name = "Fujitsu-Siemens D1522", /* AD1981 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x8086, + .device = 0x4856, + .name = "Intel D845WN (82801BA)", + .type = AC97_TUNE_SWAP_HP + }, + { + .vendor = 0x8086, + .device = 0x4d44, + .name = "Intel D850EMV2", /* AD1885 */ + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x8086, + .device = 0x4d56, + .name = "Intel ICH/AD1885", + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x1028, + .device = 0x012d, + .name = "Dell Precision 450", /* AD1981B*/ + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x103c, + .device = 0x3008, + .name = "HP xw4200", /* AD1981B*/ + .type = AC97_TUNE_HP_ONLY + }, + { + .vendor = 0x103c, + .device = 0x12f1, + .name = "HP xw8200", /* AD1981B*/ + .type = AC97_TUNE_HP_ONLY + }, + { } /* terminator */ +}; + +static struct i810_card *devs = NULL; + +static int i810_open_mixdev(struct inode *inode, struct file *file); +static int i810_ioctl_mixdev(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); +static u16 i810_ac97_get(struct ac97_codec *dev, u8 reg); +static void i810_ac97_set(struct ac97_codec *dev, u8 reg, u16 data); +static u16 i810_ac97_get_mmio(struct ac97_codec *dev, u8 reg); +static void i810_ac97_set_mmio(struct ac97_codec *dev, u8 reg, u16 data); +static u16 i810_ac97_get_io(struct ac97_codec *dev, u8 reg); +static void i810_ac97_set_io(struct ac97_codec *dev, u8 reg, u16 data); + +static struct i810_channel *i810_alloc_pcm_channel(struct i810_card *card) +{ + if(card->channel[1].used==1) + return NULL; + card->channel[1].used=1; + return &card->channel[1]; +} + +static struct i810_channel *i810_alloc_rec_pcm_channel(struct i810_card *card) +{ + if(card->channel[0].used==1) + return NULL; + card->channel[0].used=1; + return &card->channel[0]; +} + +static struct i810_channel *i810_alloc_rec_mic_channel(struct i810_card *card) +{ + if(card->channel[2].used==1) + return NULL; + card->channel[2].used=1; + return &card->channel[2]; +} + +static void i810_free_pcm_channel(struct i810_card *card, int channel) +{ + card->channel[channel].used=0; +} + +static int i810_valid_spdif_rate ( struct ac97_codec *codec, int rate ) +{ + unsigned long id = 0L; + + id = (i810_ac97_get(codec, AC97_VENDOR_ID1) << 16); + id |= i810_ac97_get(codec, AC97_VENDOR_ID2) & 0xffff; +#ifdef DEBUG + printk ( "i810_audio: codec = %s, codec_id = 0x%08lx\n", codec->name, id); +#endif + switch ( id ) { + case 0x41445361: /* AD1886 */ + if (rate == 48000) { + return 1; + } + break; + default: /* all other codecs, until we know otherwiae */ + if (rate == 48000 || rate == 44100 || rate == 32000) { + return 1; + } + break; + } + return (0); +} + +/* i810_set_spdif_output + * + * Configure the S/PDIF output transmitter. When we turn on + * S/PDIF, we turn off the analog output. This may not be + * the right thing to do. + * + * Assumptions: + * The DSP sample rate must already be set to a supported + * S/PDIF rate (32kHz, 44.1kHz, or 48kHz) or we abort. + */ +static int i810_set_spdif_output(struct i810_state *state, int slots, int rate) +{ + int vol; + int aud_reg; + int r = 0; + struct ac97_codec *codec = state->card->ac97_codec[0]; + + if(!codec->codec_ops->digital) { + state->card->ac97_status &= ~SPDIF_ON; + } else { + if ( slots == -1 ) { /* Turn off S/PDIF */ + codec->codec_ops->digital(codec, 0, 0, 0); + /* If the volume wasn't muted before we turned on S/PDIF, unmute it */ + if ( !(state->card->ac97_status & VOL_MUTED) ) { + aud_reg = i810_ac97_get(codec, AC97_MASTER_VOL_STEREO); + i810_ac97_set(codec, AC97_MASTER_VOL_STEREO, (aud_reg & ~VOL_MUTED)); + } + state->card->ac97_status &= ~(VOL_MUTED | SPDIF_ON); + return 0; + } + + vol = i810_ac97_get(codec, AC97_MASTER_VOL_STEREO); + state->card->ac97_status = vol & VOL_MUTED; + + r = codec->codec_ops->digital(codec, slots, rate, 0); + + if(r) + state->card->ac97_status |= SPDIF_ON; + else + state->card->ac97_status &= ~SPDIF_ON; + + /* Mute the analog output */ + /* Should this only mute the PCM volume??? */ + i810_ac97_set(codec, AC97_MASTER_VOL_STEREO, (vol | VOL_MUTED)); + } + return r; +} + +/* i810_set_dac_channels + * + * Configure the codec's multi-channel DACs + * + * The logic is backwards. Setting the bit to 1 turns off the DAC. + * + * What about the ICH? We currently configure it using the + * SNDCTL_DSP_CHANNELS ioctl. If we're turnning on the DAC, + * does that imply that we want the ICH set to support + * these channels? + * + * TODO: + * vailidate that the codec really supports these DACs + * before turning them on. + */ +static void i810_set_dac_channels(struct i810_state *state, int channel) +{ + int aud_reg; + struct ac97_codec *codec = state->card->ac97_codec[0]; + + /* No codec, no setup */ + + if(codec == NULL) + return; + + aud_reg = i810_ac97_get(codec, AC97_EXTENDED_STATUS); + aud_reg |= AC97_EA_PRI | AC97_EA_PRJ | AC97_EA_PRK; + state->card->ac97_status &= ~(SURR_ON | CENTER_LFE_ON); + + switch ( channel ) { + case 2: /* always enabled */ + break; + case 4: + aud_reg &= ~AC97_EA_PRJ; + state->card->ac97_status |= SURR_ON; + break; + case 6: + aud_reg &= ~(AC97_EA_PRJ | AC97_EA_PRI | AC97_EA_PRK); + state->card->ac97_status |= SURR_ON | CENTER_LFE_ON; + break; + default: + break; + } + i810_ac97_set(codec, AC97_EXTENDED_STATUS, aud_reg); + +} + + +/* set playback sample rate */ +static unsigned int i810_set_dac_rate(struct i810_state * state, unsigned int rate) +{ + struct dmabuf *dmabuf = &state->dmabuf; + u32 new_rate; + struct ac97_codec *codec=state->card->ac97_codec[0]; + + if(!(state->card->ac97_features&0x0001)) + { + dmabuf->rate = clocking; +#ifdef DEBUG + printk("Asked for %d Hz, but ac97_features says we only do %dHz. Sorry!\n", + rate,clocking); +#endif + return clocking; + } + + if (rate > 48000) + rate = 48000; + if (rate < 8000) + rate = 8000; + dmabuf->rate = rate; + + /* + * Adjust for misclocked crap + */ + rate = ( rate * clocking)/48000; + if(strict_clocking && rate < 8000) { + rate = 8000; + dmabuf->rate = (rate * 48000)/clocking; + } + + new_rate=ac97_set_dac_rate(codec, rate); + if(new_rate != rate) { + dmabuf->rate = (new_rate * 48000)/clocking; + } +#ifdef DEBUG + printk("i810_audio: called i810_set_dac_rate : asked for %d, got %d\n", rate, dmabuf->rate); +#endif + rate = new_rate; + return dmabuf->rate; +} + +/* set recording sample rate */ +static unsigned int i810_set_adc_rate(struct i810_state * state, unsigned int rate) +{ + struct dmabuf *dmabuf = &state->dmabuf; + u32 new_rate; + struct ac97_codec *codec=state->card->ac97_codec[0]; + + if(!(state->card->ac97_features&0x0001)) + { + dmabuf->rate = clocking; + return clocking; + } + + if (rate > 48000) + rate = 48000; + if (rate < 8000) + rate = 8000; + dmabuf->rate = rate; + + /* + * Adjust for misclocked crap + */ + + rate = ( rate * clocking)/48000; + if(strict_clocking && rate < 8000) { + rate = 8000; + dmabuf->rate = (rate * 48000)/clocking; + } + + new_rate = ac97_set_adc_rate(codec, rate); + + if(new_rate != rate) { + dmabuf->rate = (new_rate * 48000)/clocking; + rate = new_rate; + } +#ifdef DEBUG + printk("i810_audio: called i810_set_adc_rate : rate = %d/%d\n", dmabuf->rate, rate); +#endif + return dmabuf->rate; +} + +/* get current playback/recording dma buffer pointer (byte offset from LBA), + called with spinlock held! */ + +static inline unsigned i810_get_dma_addr(struct i810_state *state, int rec) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned int civ, offset, port, port_picb, bytes = 2; + + if (!dmabuf->enable) + return 0; + + if (rec) + port = dmabuf->read_channel->port; + else + port = dmabuf->write_channel->port; + + if(state->card->pci_id == PCI_DEVICE_ID_SI_7012) { + port_picb = port + OFF_SR; + bytes = 1; + } else + port_picb = port + OFF_PICB; + + do { + civ = GET_CIV(state->card, port); + offset = I810_IOREADW(state->card, port_picb); + /* Must have a delay here! */ + if(offset == 0) + udelay(1); + /* Reread both registers and make sure that that total + * offset from the first reading to the second is 0. + * There is an issue with SiS hardware where it will count + * picb down to 0, then update civ to the next value, + * then set the new picb to fragsize bytes. We can catch + * it between the civ update and the picb update, making + * it look as though we are 1 fragsize ahead of where we + * are. The next to we get the address though, it will + * be back in the right place, and we will suddenly think + * we just went forward dmasize - fragsize bytes, causing + * totally stupid *huge* dma overrun messages. We are + * assuming that the 1us delay is more than long enough + * that we won't have to worry about the chip still being + * out of sync with reality ;-) + */ + } while (civ != GET_CIV(state->card, port) || offset != I810_IOREADW(state->card, port_picb)); + + return (((civ + 1) * dmabuf->fragsize - (bytes * offset)) + % dmabuf->dmasize); +} + +/* Stop recording (lock held) */ +static inline void __stop_adc(struct i810_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct i810_card *card = state->card; + + dmabuf->enable &= ~ADC_RUNNING; + I810_IOWRITEB(0, card, PI_CR); + // wait for the card to acknowledge shutdown + while( I810_IOREADB(card, PI_CR) != 0 ) ; + // now clear any latent interrupt bits (like the halt bit) + if(card->pci_id == PCI_DEVICE_ID_SI_7012) + I810_IOWRITEB( I810_IOREADB(card, PI_PICB), card, PI_PICB ); + else + I810_IOWRITEB( I810_IOREADB(card, PI_SR), card, PI_SR ); + I810_IOWRITEL( I810_IOREADL(card, GLOB_STA) & INT_PI, card, GLOB_STA); +} + +static void stop_adc(struct i810_state *state) +{ + struct i810_card *card = state->card; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + __stop_adc(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +static inline void __start_adc(struct i810_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + + if (dmabuf->count < dmabuf->dmasize && dmabuf->ready && !dmabuf->enable && + (dmabuf->trigger & PCM_ENABLE_INPUT)) { + dmabuf->enable |= ADC_RUNNING; + // Interrupt enable, LVI enable, DMA enable + I810_IOWRITEB(0x10 | 0x04 | 0x01, state->card, PI_CR); + } +} + +static void start_adc(struct i810_state *state) +{ + struct i810_card *card = state->card; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + __start_adc(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +/* stop playback (lock held) */ +static inline void __stop_dac(struct i810_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct i810_card *card = state->card; + + dmabuf->enable &= ~DAC_RUNNING; + I810_IOWRITEB(0, card, PO_CR); + // wait for the card to acknowledge shutdown + while( I810_IOREADB(card, PO_CR) != 0 ) ; + // now clear any latent interrupt bits (like the halt bit) + if(card->pci_id == PCI_DEVICE_ID_SI_7012) + I810_IOWRITEB( I810_IOREADB(card, PO_PICB), card, PO_PICB ); + else + I810_IOWRITEB( I810_IOREADB(card, PO_SR), card, PO_SR ); + I810_IOWRITEL( I810_IOREADL(card, GLOB_STA) & INT_PO, card, GLOB_STA); +} + +static void stop_dac(struct i810_state *state) +{ + struct i810_card *card = state->card; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + __stop_dac(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +static inline void __start_dac(struct i810_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + + if (dmabuf->count > 0 && dmabuf->ready && !dmabuf->enable && + (dmabuf->trigger & PCM_ENABLE_OUTPUT)) { + dmabuf->enable |= DAC_RUNNING; + // Interrupt enable, LVI enable, DMA enable + I810_IOWRITEB(0x10 | 0x04 | 0x01, state->card, PO_CR); + } +} +static void start_dac(struct i810_state *state) +{ + struct i810_card *card = state->card; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + __start_dac(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +#define DMABUF_DEFAULTORDER (16-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +/* allocate DMA buffer, playback and recording buffer should be allocated separately */ +static int alloc_dmabuf(struct i810_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + void *rawbuf= NULL; + int order, size; + struct page *page, *pend; + + /* If we don't have any oss frag params, then use our default ones */ + if(dmabuf->ossmaxfrags == 0) + dmabuf->ossmaxfrags = 4; + if(dmabuf->ossfragsize == 0) + dmabuf->ossfragsize = (PAGE_SIZE<ossmaxfrags; + size = dmabuf->ossfragsize * dmabuf->ossmaxfrags; + + if(dmabuf->rawbuf && (PAGE_SIZE << dmabuf->buforder) == size) + return 0; + /* alloc enough to satisfy the oss params */ + for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--) { + if ( (PAGE_SIZE< size ) + continue; + if ((rawbuf = pci_alloc_consistent(state->card->pci_dev, + PAGE_SIZE << order, + &dmabuf->dma_handle))) + break; + } + if (!rawbuf) + return -ENOMEM; + + +#ifdef DEBUG + printk("i810_audio: allocated %ld (order = %d) bytes at %p\n", + PAGE_SIZE << order, order, rawbuf); +#endif + + dmabuf->ready = dmabuf->mapped = 0; + dmabuf->rawbuf = rawbuf; + dmabuf->buforder = order; + + /* now mark the pages as reserved; otherwise remap_pfn_range doesn't do what we want */ + pend = virt_to_page(rawbuf + (PAGE_SIZE << order) - 1); + for (page = virt_to_page(rawbuf); page <= pend; page++) + SetPageReserved(page); + + return 0; +} + +/* free DMA buffer */ +static void dealloc_dmabuf(struct i810_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct page *page, *pend; + + if (dmabuf->rawbuf) { + /* undo marking the pages as reserved */ + pend = virt_to_page(dmabuf->rawbuf + (PAGE_SIZE << dmabuf->buforder) - 1); + for (page = virt_to_page(dmabuf->rawbuf); page <= pend; page++) + ClearPageReserved(page); + pci_free_consistent(state->card->pci_dev, PAGE_SIZE << dmabuf->buforder, + dmabuf->rawbuf, dmabuf->dma_handle); + } + dmabuf->rawbuf = NULL; + dmabuf->mapped = dmabuf->ready = 0; +} + +static int prog_dmabuf(struct i810_state *state, unsigned rec) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct i810_channel *c; + struct sg_item *sg; + unsigned long flags; + int ret; + unsigned fragint; + int i; + + spin_lock_irqsave(&state->card->lock, flags); + if(dmabuf->enable & DAC_RUNNING) + __stop_dac(state); + if(dmabuf->enable & ADC_RUNNING) + __stop_adc(state); + dmabuf->total_bytes = 0; + dmabuf->count = dmabuf->error = 0; + dmabuf->swptr = dmabuf->hwptr = 0; + spin_unlock_irqrestore(&state->card->lock, flags); + + /* allocate DMA buffer, let alloc_dmabuf determine if we are already + * allocated well enough or if we should replace the current buffer + * (assuming one is already allocated, if it isn't, then allocate it). + */ + if ((ret = alloc_dmabuf(state))) + return ret; + + /* FIXME: figure out all this OSS fragment stuff */ + /* I did, it now does what it should according to the OSS API. DL */ + /* We may not have realloced our dmabuf, but the fragment size to + * fragment number ratio may have changed, so go ahead and reprogram + * things + */ + dmabuf->dmasize = PAGE_SIZE << dmabuf->buforder; + dmabuf->numfrag = SG_LEN; + dmabuf->fragsize = dmabuf->dmasize/dmabuf->numfrag; + dmabuf->fragsamples = dmabuf->fragsize >> 1; + dmabuf->fragshift = ffs(dmabuf->fragsize) - 1; + dmabuf->userfragsize = dmabuf->ossfragsize; + dmabuf->userfrags = dmabuf->dmasize/dmabuf->ossfragsize; + + memset(dmabuf->rawbuf, 0, dmabuf->dmasize); + + if(dmabuf->ossmaxfrags == 4) { + fragint = 8; + } else if (dmabuf->ossmaxfrags == 8) { + fragint = 4; + } else if (dmabuf->ossmaxfrags == 16) { + fragint = 2; + } else { + fragint = 1; + } + /* + * Now set up the ring + */ + if(dmabuf->read_channel) + c = dmabuf->read_channel; + else + c = dmabuf->write_channel; + while(c != NULL) { + sg=&c->sg[0]; + /* + * Load up 32 sg entries and take an interrupt at half + * way (we might want more interrupts later..) + */ + + for(i=0;inumfrag;i++) + { + sg->busaddr=(u32)dmabuf->dma_handle+dmabuf->fragsize*i; + // the card will always be doing 16bit stereo + sg->control=dmabuf->fragsamples; + if(state->card->pci_id == PCI_DEVICE_ID_SI_7012) + sg->control <<= 1; + sg->control|=CON_BUFPAD; + // set us up to get IOC interrupts as often as needed to + // satisfy numfrag requirements, no more + if( ((i+1) % fragint) == 0) { + sg->control|=CON_IOC; + } + sg++; + } + spin_lock_irqsave(&state->card->lock, flags); + I810_IOWRITEB(2, state->card, c->port+OFF_CR); /* reset DMA machine */ + while( I810_IOREADB(state->card, c->port+OFF_CR) & 0x02 ) ; + I810_IOWRITEL((u32)state->card->chandma + + c->num*sizeof(struct i810_channel), + state->card, c->port+OFF_BDBAR); + CIV_TO_LVI(state->card, c->port, 0); + + spin_unlock_irqrestore(&state->card->lock, flags); + + if(c != dmabuf->write_channel) + c = dmabuf->write_channel; + else + c = NULL; + } + + /* set the ready flag for the dma buffer */ + dmabuf->ready = 1; + +#ifdef DEBUG + printk("i810_audio: prog_dmabuf, sample rate = %d, format = %d,\n\tnumfrag = %d, " + "fragsize = %d dmasize = %d\n", + dmabuf->rate, dmabuf->fmt, dmabuf->numfrag, + dmabuf->fragsize, dmabuf->dmasize); +#endif + + return 0; +} + +static void __i810_update_lvi(struct i810_state *state, int rec) +{ + struct dmabuf *dmabuf = &state->dmabuf; + int x, port; + int trigger; + int count, fragsize; + void (*start)(struct i810_state *); + + count = dmabuf->count; + if (rec) { + port = dmabuf->read_channel->port; + trigger = PCM_ENABLE_INPUT; + start = __start_adc; + count = dmabuf->dmasize - count; + } else { + port = dmabuf->write_channel->port; + trigger = PCM_ENABLE_OUTPUT; + start = __start_dac; + } + + /* Do not process partial fragments. */ + fragsize = dmabuf->fragsize; + if (count < fragsize) + return; + + /* if we are currently stopped, then our CIV is actually set to our + * *last* sg segment and we are ready to wrap to the next. However, + * if we set our LVI to the last sg segment, then it won't wrap to + * the next sg segment, it won't even get a start. So, instead, when + * we are stopped, we set both the LVI value and also we increment + * the CIV value to the next sg segment to be played so that when + * we call start, things will operate properly. Since the CIV can't + * be written to directly for this purpose, we set the LVI to CIV + 1 + * temporarily. Once the engine has started we set the LVI to its + * final value. + */ + if (!dmabuf->enable && dmabuf->ready) { + if (!(dmabuf->trigger & trigger)) + return; + + CIV_TO_LVI(state->card, port, 1); + + start(state); + while (!(I810_IOREADB(state->card, port + OFF_CR) & ((1<<4) | (1<<2)))) + ; + } + + /* MASKP2(swptr, fragsize) - 1 is the tail of our transfer */ + x = MODULOP2(MASKP2(dmabuf->swptr, fragsize) - 1, dmabuf->dmasize); + x >>= dmabuf->fragshift; + I810_IOWRITEB(x, state->card, port + OFF_LVI); +} + +static void i810_update_lvi(struct i810_state *state, int rec) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + + if(!dmabuf->ready) + return; + spin_lock_irqsave(&state->card->lock, flags); + __i810_update_lvi(state, rec); + spin_unlock_irqrestore(&state->card->lock, flags); +} + +/* update buffer manangement pointers, especially, dmabuf->count and dmabuf->hwptr */ +static void i810_update_ptr(struct i810_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned hwptr; + unsigned fragmask, dmamask; + int diff; + + fragmask = MASKP2(~0, dmabuf->fragsize); + dmamask = MODULOP2(~0, dmabuf->dmasize); + + /* error handling and process wake up for ADC */ + if (dmabuf->enable == ADC_RUNNING) { + /* update hardware pointer */ + hwptr = i810_get_dma_addr(state, 1) & fragmask; + diff = (hwptr - dmabuf->hwptr) & dmamask; +#if defined(DEBUG_INTERRUPTS) || defined(DEBUG_MMAP) + printk("ADC HWP %d,%d,%d\n", hwptr, dmabuf->hwptr, diff); +#endif + dmabuf->hwptr = hwptr; + dmabuf->total_bytes += diff; + dmabuf->count += diff; + if (dmabuf->count > dmabuf->dmasize) { + /* buffer underrun or buffer overrun */ + /* this is normal for the end of a read */ + /* only give an error if we went past the */ + /* last valid sg entry */ + if (GET_CIV(state->card, PI_BASE) != + GET_LVI(state->card, PI_BASE)) { + printk(KERN_WARNING "i810_audio: DMA overrun on read\n"); + dmabuf->error++; + } + } + if (diff) + wake_up(&dmabuf->wait); + } + /* error handling and process wake up for DAC */ + if (dmabuf->enable == DAC_RUNNING) { + /* update hardware pointer */ + hwptr = i810_get_dma_addr(state, 0) & fragmask; + diff = (hwptr - dmabuf->hwptr) & dmamask; +#if defined(DEBUG_INTERRUPTS) || defined(DEBUG_MMAP) + printk("DAC HWP %d,%d,%d\n", hwptr, dmabuf->hwptr, diff); +#endif + dmabuf->hwptr = hwptr; + dmabuf->total_bytes += diff; + dmabuf->count -= diff; + if (dmabuf->count < 0) { + /* buffer underrun or buffer overrun */ + /* this is normal for the end of a write */ + /* only give an error if we went past the */ + /* last valid sg entry */ + if (GET_CIV(state->card, PO_BASE) != + GET_LVI(state->card, PO_BASE)) { + printk(KERN_WARNING "i810_audio: DMA overrun on write\n"); + printk("i810_audio: CIV %d, LVI %d, hwptr %x, " + "count %d\n", + GET_CIV(state->card, PO_BASE), + GET_LVI(state->card, PO_BASE), + dmabuf->hwptr, dmabuf->count); + dmabuf->error++; + } + } + if (diff) + wake_up(&dmabuf->wait); + } +} + +static inline int i810_get_free_write_space(struct i810_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + int free; + + i810_update_ptr(state); + // catch underruns during playback + if (dmabuf->count < 0) { + dmabuf->count = 0; + dmabuf->swptr = dmabuf->hwptr; + } + free = dmabuf->dmasize - dmabuf->count; + if(free < 0) + return(0); + return(free); +} + +static inline int i810_get_available_read_data(struct i810_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + int avail; + + i810_update_ptr(state); + // catch overruns during record + if (dmabuf->count > dmabuf->dmasize) { + dmabuf->count = dmabuf->dmasize; + dmabuf->swptr = dmabuf->hwptr; + } + avail = dmabuf->count; + if(avail < 0) + return(0); + return(avail); +} + +static inline void fill_partial_frag(struct dmabuf *dmabuf) +{ + unsigned fragsize; + unsigned swptr, len; + + fragsize = dmabuf->fragsize; + swptr = dmabuf->swptr; + len = fragsize - MODULOP2(dmabuf->swptr, fragsize); + if (len == fragsize) + return; + + memset(dmabuf->rawbuf + swptr, '\0', len); + dmabuf->swptr = MODULOP2(swptr + len, dmabuf->dmasize); + dmabuf->count += len; +} + +static int drain_dac(struct i810_state *state, int signals_allowed) +{ + DECLARE_WAITQUEUE(wait, current); + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + unsigned long tmo; + int count; + + if (!dmabuf->ready) + return 0; + if(dmabuf->mapped) { + stop_dac(state); + return 0; + } + + spin_lock_irqsave(&state->card->lock, flags); + + fill_partial_frag(dmabuf); + + /* + * This will make sure that our LVI is correct, that our + * pointer is updated, and that the DAC is running. We + * have to force the setting of dmabuf->trigger to avoid + * any possible deadlocks. + */ + dmabuf->trigger = PCM_ENABLE_OUTPUT; + __i810_update_lvi(state, 0); + + spin_unlock_irqrestore(&state->card->lock, flags); + + add_wait_queue(&dmabuf->wait, &wait); + for (;;) { + + spin_lock_irqsave(&state->card->lock, flags); + i810_update_ptr(state); + count = dmabuf->count; + + /* It seems that we have to set the current state to + * TASK_INTERRUPTIBLE every time to make the process + * really go to sleep. This also has to be *after* the + * update_ptr() call because update_ptr is likely to + * do a wake_up() which will unset this before we ever + * try to sleep, resuling in a tight loop in this code + * instead of actually sleeping and waiting for an + * interrupt to wake us up! + */ + __set_current_state(signals_allowed ? + TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + spin_unlock_irqrestore(&state->card->lock, flags); + + if (count <= 0) + break; + + if (signal_pending(current) && signals_allowed) { + break; + } + + /* + * set the timeout to significantly longer than it *should* + * take for the DAC to drain the DMA buffer + */ + tmo = (count * HZ) / (dmabuf->rate); + if (!schedule_timeout(tmo >= 2 ? tmo : 2)){ + printk(KERN_ERR "i810_audio: drain_dac, dma timeout?\n"); + count = 0; + break; + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&dmabuf->wait, &wait); + if(count > 0 && signal_pending(current) && signals_allowed) + return -ERESTARTSYS; + stop_dac(state); + return 0; +} + +static void i810_channel_interrupt(struct i810_card *card) +{ + int i, count; + +#ifdef DEBUG_INTERRUPTS + printk("CHANNEL "); +#endif + for(i=0;istates[i]; + struct i810_channel *c; + struct dmabuf *dmabuf; + unsigned long port; + u16 status; + + if(!state) + continue; + if(!state->dmabuf.ready) + continue; + dmabuf = &state->dmabuf; + if(dmabuf->enable & DAC_RUNNING) { + c=dmabuf->write_channel; + } else if(dmabuf->enable & ADC_RUNNING) { + c=dmabuf->read_channel; + } else /* This can occur going from R/W to close */ + continue; + + port = c->port; + + if(card->pci_id == PCI_DEVICE_ID_SI_7012) + status = I810_IOREADW(card, port + OFF_PICB); + else + status = I810_IOREADW(card, port + OFF_SR); + +#ifdef DEBUG_INTERRUPTS + printk("NUM %d PORT %X IRQ ( ST%d ", c->num, c->port, status); +#endif + if(status & DMA_INT_COMPLETE) + { + /* only wake_up() waiters if this interrupt signals + * us being beyond a userfragsize of data open or + * available, and i810_update_ptr() does that for + * us + */ + i810_update_ptr(state); +#ifdef DEBUG_INTERRUPTS + printk("COMP %d ", dmabuf->hwptr / + dmabuf->fragsize); +#endif + } + if(status & (DMA_INT_LVI | DMA_INT_DCH)) + { + /* wake_up() unconditionally on LVI and DCH */ + i810_update_ptr(state); + wake_up(&dmabuf->wait); +#ifdef DEBUG_INTERRUPTS + if(status & DMA_INT_LVI) + printk("LVI "); + if(status & DMA_INT_DCH) + printk("DCH -"); +#endif + count = dmabuf->count; + if(dmabuf->enable & ADC_RUNNING) + count = dmabuf->dmasize - count; + if (count >= (int)dmabuf->fragsize) { + I810_IOWRITEB(I810_IOREADB(card, port+OFF_CR) | 1, card, port+OFF_CR); +#ifdef DEBUG_INTERRUPTS + printk(" CONTINUE "); +#endif + } else { + if (dmabuf->enable & DAC_RUNNING) + __stop_dac(state); + if (dmabuf->enable & ADC_RUNNING) + __stop_adc(state); + dmabuf->enable = 0; +#ifdef DEBUG_INTERRUPTS + printk(" STOP "); +#endif + } + } + if(card->pci_id == PCI_DEVICE_ID_SI_7012) + I810_IOWRITEW(status & DMA_INT_MASK, card, port + OFF_PICB); + else + I810_IOWRITEW(status & DMA_INT_MASK, card, port + OFF_SR); + } +#ifdef DEBUG_INTERRUPTS + printk(")\n"); +#endif +} + +static irqreturn_t i810_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct i810_card *card = (struct i810_card *)dev_id; + u32 status; + + spin_lock(&card->lock); + + status = I810_IOREADL(card, GLOB_STA); + + if(!(status & INT_MASK)) + { + spin_unlock(&card->lock); + return IRQ_NONE; /* not for us */ + } + + if(status & (INT_PO|INT_PI|INT_MC)) + i810_channel_interrupt(card); + + /* clear 'em */ + I810_IOWRITEL(status & INT_MASK, card, GLOB_STA); + spin_unlock(&card->lock); + return IRQ_HANDLED; +} + +/* in this loop, dmabuf.count signifies the amount of data that is + waiting to be copied to the user's buffer. It is filled by the dma + machine and drained by this loop. */ + +static ssize_t i810_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct i810_state *state = (struct i810_state *)file->private_data; + struct i810_card *card=state ? state->card : NULL; + struct dmabuf *dmabuf = &state->dmabuf; + ssize_t ret; + unsigned long flags; + unsigned int swptr; + int cnt; + int pending; + DECLARE_WAITQUEUE(waita, current); + +#ifdef DEBUG2 + printk("i810_audio: i810_read called, count = %d\n", count); +#endif + + if (dmabuf->mapped) + return -ENXIO; + if (dmabuf->enable & DAC_RUNNING) + return -ENODEV; + if (!dmabuf->read_channel) { + dmabuf->ready = 0; + dmabuf->read_channel = card->alloc_rec_pcm_channel(card); + if (!dmabuf->read_channel) { + return -EBUSY; + } + } + if (!dmabuf->ready && (ret = prog_dmabuf(state, 1))) + return ret; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; + + pending = 0; + + add_wait_queue(&dmabuf->wait, &waita); + while (count > 0) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&card->lock, flags); + if (PM_SUSPENDED(card)) { + spin_unlock_irqrestore(&card->lock, flags); + schedule(); + if (signal_pending(current)) { + if (!ret) ret = -EAGAIN; + break; + } + continue; + } + cnt = i810_get_available_read_data(state); + swptr = dmabuf->swptr; + // this is to make the copy_to_user simpler below + if(cnt > (dmabuf->dmasize - swptr)) + cnt = dmabuf->dmasize - swptr; + spin_unlock_irqrestore(&card->lock, flags); + + if (cnt > count) + cnt = count; + if (cnt <= 0) { + unsigned long tmo; + /* + * Don't let us deadlock. The ADC won't start if + * dmabuf->trigger isn't set. A call to SETTRIGGER + * could have turned it off after we set it to on + * previously. + */ + dmabuf->trigger = PCM_ENABLE_INPUT; + /* + * This does three things. Updates LVI to be correct, + * makes sure the ADC is running, and updates the + * hwptr. + */ + i810_update_lvi(state,1); + if (file->f_flags & O_NONBLOCK) { + if (!ret) ret = -EAGAIN; + goto done; + } + /* Set the timeout to how long it would take to fill + * two of our buffers. If we haven't been woke up + * by then, then we know something is wrong. + */ + tmo = (dmabuf->dmasize * HZ * 2) / (dmabuf->rate * 4); + /* There are two situations when sleep_on_timeout returns, one is when + the interrupt is serviced correctly and the process is waked up by + ISR ON TIME. Another is when timeout is expired, which means that + either interrupt is NOT serviced correctly (pending interrupt) or it + is TOO LATE for the process to be scheduled to run (scheduler latency) + which results in a (potential) buffer overrun. And worse, there is + NOTHING we can do to prevent it. */ + if (!schedule_timeout(tmo >= 2 ? tmo : 2)) { +#ifdef DEBUG + printk(KERN_ERR "i810_audio: recording schedule timeout, " + "dmasz %u fragsz %u count %i hwptr %u swptr %u\n", + dmabuf->dmasize, dmabuf->fragsize, dmabuf->count, + dmabuf->hwptr, dmabuf->swptr); +#endif + /* a buffer overrun, we delay the recovery until next time the + while loop begin and we REALLY have space to record */ + } + if (signal_pending(current)) { + ret = ret ? ret : -ERESTARTSYS; + goto done; + } + continue; + } + + if (copy_to_user(buffer, dmabuf->rawbuf + swptr, cnt)) { + if (!ret) ret = -EFAULT; + goto done; + } + + swptr = MODULOP2(swptr + cnt, dmabuf->dmasize); + + spin_lock_irqsave(&card->lock, flags); + + if (PM_SUSPENDED(card)) { + spin_unlock_irqrestore(&card->lock, flags); + continue; + } + dmabuf->swptr = swptr; + pending = dmabuf->count -= cnt; + spin_unlock_irqrestore(&card->lock, flags); + + count -= cnt; + buffer += cnt; + ret += cnt; + } + done: + pending = dmabuf->dmasize - pending; + if (dmabuf->enable || pending >= dmabuf->userfragsize) + i810_update_lvi(state, 1); + set_current_state(TASK_RUNNING); + remove_wait_queue(&dmabuf->wait, &waita); + + return ret; +} + +/* in this loop, dmabuf.count signifies the amount of data that is waiting to be dma to + the soundcard. it is drained by the dma machine and filled by this loop. */ +static ssize_t i810_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct i810_state *state = (struct i810_state *)file->private_data; + struct i810_card *card=state ? state->card : NULL; + struct dmabuf *dmabuf = &state->dmabuf; + ssize_t ret; + unsigned long flags; + unsigned int swptr = 0; + int pending; + int cnt; + DECLARE_WAITQUEUE(waita, current); + +#ifdef DEBUG2 + printk("i810_audio: i810_write called, count = %d\n", count); +#endif + + if (dmabuf->mapped) + return -ENXIO; + if (dmabuf->enable & ADC_RUNNING) + return -ENODEV; + if (!dmabuf->write_channel) { + dmabuf->ready = 0; + dmabuf->write_channel = card->alloc_pcm_channel(card); + if(!dmabuf->write_channel) + return -EBUSY; + } + if (!dmabuf->ready && (ret = prog_dmabuf(state, 0))) + return ret; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; + + pending = 0; + + add_wait_queue(&dmabuf->wait, &waita); + while (count > 0) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&state->card->lock, flags); + if (PM_SUSPENDED(card)) { + spin_unlock_irqrestore(&card->lock, flags); + schedule(); + if (signal_pending(current)) { + if (!ret) ret = -EAGAIN; + break; + } + continue; + } + + cnt = i810_get_free_write_space(state); + swptr = dmabuf->swptr; + /* Bound the maximum size to how much we can copy to the + * dma buffer before we hit the end. If we have more to + * copy then it will get done in a second pass of this + * loop starting from the beginning of the buffer. + */ + if(cnt > (dmabuf->dmasize - swptr)) + cnt = dmabuf->dmasize - swptr; + spin_unlock_irqrestore(&state->card->lock, flags); + +#ifdef DEBUG2 + printk(KERN_INFO "i810_audio: i810_write: %d bytes available space\n", cnt); +#endif + if (cnt > count) + cnt = count; + if (cnt <= 0) { + unsigned long tmo; + // There is data waiting to be played + /* + * Force the trigger setting since we would + * deadlock with it set any other way + */ + dmabuf->trigger = PCM_ENABLE_OUTPUT; + i810_update_lvi(state,0); + if (file->f_flags & O_NONBLOCK) { + if (!ret) ret = -EAGAIN; + goto ret; + } + /* Not strictly correct but works */ + tmo = (dmabuf->dmasize * HZ * 2) / (dmabuf->rate * 4); + /* There are two situations when sleep_on_timeout returns, one is when + the interrupt is serviced correctly and the process is waked up by + ISR ON TIME. Another is when timeout is expired, which means that + either interrupt is NOT serviced correctly (pending interrupt) or it + is TOO LATE for the process to be scheduled to run (scheduler latency) + which results in a (potential) buffer underrun. And worse, there is + NOTHING we can do to prevent it. */ + if (!schedule_timeout(tmo >= 2 ? tmo : 2)) { +#ifdef DEBUG + printk(KERN_ERR "i810_audio: playback schedule timeout, " + "dmasz %u fragsz %u count %i hwptr %u swptr %u\n", + dmabuf->dmasize, dmabuf->fragsize, dmabuf->count, + dmabuf->hwptr, dmabuf->swptr); +#endif + /* a buffer underrun, we delay the recovery until next time the + while loop begin and we REALLY have data to play */ + //return ret; + } + if (signal_pending(current)) { + if (!ret) ret = -ERESTARTSYS; + goto ret; + } + continue; + } + if (copy_from_user(dmabuf->rawbuf+swptr,buffer,cnt)) { + if (!ret) ret = -EFAULT; + goto ret; + } + + swptr = MODULOP2(swptr + cnt, dmabuf->dmasize); + + spin_lock_irqsave(&state->card->lock, flags); + if (PM_SUSPENDED(card)) { + spin_unlock_irqrestore(&card->lock, flags); + continue; + } + + dmabuf->swptr = swptr; + pending = dmabuf->count += cnt; + + count -= cnt; + buffer += cnt; + ret += cnt; + spin_unlock_irqrestore(&state->card->lock, flags); + } +ret: + if (dmabuf->enable || pending >= dmabuf->userfragsize) + i810_update_lvi(state, 0); + set_current_state(TASK_RUNNING); + remove_wait_queue(&dmabuf->wait, &waita); + + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int i810_poll(struct file *file, struct poll_table_struct *wait) +{ + struct i810_state *state = (struct i810_state *)file->private_data; + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + unsigned int mask = 0; + + if(!dmabuf->ready) + return 0; + poll_wait(file, &dmabuf->wait, wait); + spin_lock_irqsave(&state->card->lock, flags); + if (dmabuf->enable & ADC_RUNNING || + dmabuf->trigger & PCM_ENABLE_INPUT) { + if (i810_get_available_read_data(state) >= + (signed)dmabuf->userfragsize) + mask |= POLLIN | POLLRDNORM; + } + if (dmabuf->enable & DAC_RUNNING || + dmabuf->trigger & PCM_ENABLE_OUTPUT) { + if (i810_get_free_write_space(state) >= + (signed)dmabuf->userfragsize) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&state->card->lock, flags); + return mask; +} + +static int i810_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct i810_state *state = (struct i810_state *)file->private_data; + struct dmabuf *dmabuf = &state->dmabuf; + int ret = -EINVAL; + unsigned long size; + + lock_kernel(); + if (vma->vm_flags & VM_WRITE) { + if (!dmabuf->write_channel && + (dmabuf->write_channel = + state->card->alloc_pcm_channel(state->card)) == NULL) { + ret = -EBUSY; + goto out; + } + } + if (vma->vm_flags & VM_READ) { + if (!dmabuf->read_channel && + (dmabuf->read_channel = + state->card->alloc_rec_pcm_channel(state->card)) == NULL) { + ret = -EBUSY; + goto out; + } + } + if ((ret = prog_dmabuf(state, 0)) != 0) + goto out; + + ret = -EINVAL; + if (vma->vm_pgoff != 0) + goto out; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << dmabuf->buforder)) + goto out; + ret = -EAGAIN; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(dmabuf->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + goto out; + dmabuf->mapped = 1; + dmabuf->trigger = 0; + ret = 0; +#ifdef DEBUG_MMAP + printk("i810_audio: mmap'ed %ld bytes of data space\n", size); +#endif +out: + unlock_kernel(); + return ret; +} + +static int i810_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct i810_state *state = (struct i810_state *)file->private_data; + struct i810_channel *c = NULL; + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + unsigned int i_glob_cnt; + int val = 0, ret; + struct ac97_codec *codec = state->card->ac97_codec[0]; + void __user *argp = (void __user *)arg; + int __user *p = argp; + +#ifdef DEBUG + printk("i810_audio: i810_ioctl, arg=0x%x, cmd=", arg ? *p : 0); +#endif + + switch (cmd) + { + case OSS_GETVERSION: +#ifdef DEBUG + printk("OSS_GETVERSION\n"); +#endif + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_RESET: +#ifdef DEBUG + printk("SNDCTL_DSP_RESET\n"); +#endif + spin_lock_irqsave(&state->card->lock, flags); + if (dmabuf->enable == DAC_RUNNING) { + c = dmabuf->write_channel; + __stop_dac(state); + } + if (dmabuf->enable == ADC_RUNNING) { + c = dmabuf->read_channel; + __stop_adc(state); + } + if (c != NULL) { + I810_IOWRITEB(2, state->card, c->port+OFF_CR); /* reset DMA machine */ + while ( I810_IOREADB(state->card, c->port+OFF_CR) & 2 ) + cpu_relax(); + I810_IOWRITEL((u32)state->card->chandma + + c->num*sizeof(struct i810_channel), + state->card, c->port+OFF_BDBAR); + CIV_TO_LVI(state->card, c->port, 0); + } + + spin_unlock_irqrestore(&state->card->lock, flags); + synchronize_irq(state->card->pci_dev->irq); + dmabuf->ready = 0; + dmabuf->swptr = dmabuf->hwptr = 0; + dmabuf->count = dmabuf->total_bytes = 0; + return 0; + + case SNDCTL_DSP_SYNC: +#ifdef DEBUG + printk("SNDCTL_DSP_SYNC\n"); +#endif + if (dmabuf->enable != DAC_RUNNING || file->f_flags & O_NONBLOCK) + return 0; + if((val = drain_dac(state, 1))) + return val; + dmabuf->total_bytes = 0; + return 0; + + case SNDCTL_DSP_SPEED: /* set smaple rate */ +#ifdef DEBUG + printk("SNDCTL_DSP_SPEED\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { + if (file->f_mode & FMODE_WRITE) { + if ( (state->card->ac97_status & SPDIF_ON) ) { /* S/PDIF Enabled */ + /* AD1886 only supports 48000, need to check that */ + if ( i810_valid_spdif_rate ( codec, val ) ) { + /* Set DAC rate */ + i810_set_spdif_output ( state, -1, 0 ); + stop_dac(state); + dmabuf->ready = 0; + spin_lock_irqsave(&state->card->lock, flags); + i810_set_dac_rate(state, val); + spin_unlock_irqrestore(&state->card->lock, flags); + /* Set S/PDIF transmitter rate. */ + i810_set_spdif_output ( state, AC97_EA_SPSA_3_4, val ); + if ( ! (state->card->ac97_status & SPDIF_ON) ) { + val = dmabuf->rate; + } + } else { /* Not a valid rate for S/PDIF, ignore it */ + val = dmabuf->rate; + } + } else { + stop_dac(state); + dmabuf->ready = 0; + spin_lock_irqsave(&state->card->lock, flags); + i810_set_dac_rate(state, val); + spin_unlock_irqrestore(&state->card->lock, flags); + } + } + if (file->f_mode & FMODE_READ) { + stop_adc(state); + dmabuf->ready = 0; + spin_lock_irqsave(&state->card->lock, flags); + i810_set_adc_rate(state, val); + spin_unlock_irqrestore(&state->card->lock, flags); + } + } + return put_user(dmabuf->rate, p); + + case SNDCTL_DSP_STEREO: /* set stereo or mono channel */ +#ifdef DEBUG + printk("SNDCTL_DSP_STEREO\n"); +#endif + if (dmabuf->enable & DAC_RUNNING) { + stop_dac(state); + } + if (dmabuf->enable & ADC_RUNNING) { + stop_adc(state); + } + return put_user(1, p); + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + if (!dmabuf->ready && (val = prog_dmabuf(state, 0))) + return val; + } + if (file->f_mode & FMODE_READ) { + if (!dmabuf->ready && (val = prog_dmabuf(state, 1))) + return val; + } +#ifdef DEBUG + printk("SNDCTL_DSP_GETBLKSIZE %d\n", dmabuf->userfragsize); +#endif + return put_user(dmabuf->userfragsize, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask of supported sample format*/ +#ifdef DEBUG + printk("SNDCTL_DSP_GETFMTS\n"); +#endif + return put_user(AFMT_S16_LE, p); + + case SNDCTL_DSP_SETFMT: /* Select sample format */ +#ifdef DEBUG + printk("SNDCTL_DSP_SETFMT\n"); +#endif + return put_user(AFMT_S16_LE, p); + + case SNDCTL_DSP_CHANNELS: +#ifdef DEBUG + printk("SNDCTL_DSP_CHANNELS\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + + if (val > 0) { + if (dmabuf->enable & DAC_RUNNING) { + stop_dac(state); + } + if (dmabuf->enable & ADC_RUNNING) { + stop_adc(state); + } + } else { + return put_user(state->card->channels, p); + } + + /* ICH and ICH0 only support 2 channels */ + if ( state->card->pci_id == PCI_DEVICE_ID_INTEL_82801AA_5 + || state->card->pci_id == PCI_DEVICE_ID_INTEL_82801AB_5) + return put_user(2, p); + + /* Multi-channel support was added with ICH2. Bits in */ + /* Global Status and Global Control register are now */ + /* used to indicate this. */ + + i_glob_cnt = I810_IOREADL(state->card, GLOB_CNT); + + /* Current # of channels enabled */ + if ( i_glob_cnt & 0x0100000 ) + ret = 4; + else if ( i_glob_cnt & 0x0200000 ) + ret = 6; + else + ret = 2; + + switch ( val ) { + case 2: /* 2 channels is always supported */ + I810_IOWRITEL(i_glob_cnt & 0xffcfffff, + state->card, GLOB_CNT); + /* Do we need to change mixer settings???? */ + break; + case 4: /* Supported on some chipsets, better check first */ + if ( state->card->channels >= 4 ) { + I810_IOWRITEL((i_glob_cnt & 0xffcfffff) | 0x100000, + state->card, GLOB_CNT); + /* Do we need to change mixer settings??? */ + } else { + val = ret; + } + break; + case 6: /* Supported on some chipsets, better check first */ + if ( state->card->channels >= 6 ) { + I810_IOWRITEL((i_glob_cnt & 0xffcfffff) | 0x200000, + state->card, GLOB_CNT); + /* Do we need to change mixer settings??? */ + } else { + val = ret; + } + break; + default: /* nothing else is ever supported by the chipset */ + val = ret; + break; + } + + return put_user(val, p); + + case SNDCTL_DSP_POST: /* the user has sent all data and is notifying us */ + /* we update the swptr to the end of the last sg segment then return */ +#ifdef DEBUG + printk("SNDCTL_DSP_POST\n"); +#endif + if(!dmabuf->ready || (dmabuf->enable != DAC_RUNNING)) + return 0; + if((dmabuf->swptr % dmabuf->fragsize) != 0) { + val = dmabuf->fragsize - (dmabuf->swptr % dmabuf->fragsize); + dmabuf->swptr += val; + dmabuf->count += val; + } + return 0; + + case SNDCTL_DSP_SUBDIVIDE: + if (dmabuf->subdivision) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; +#ifdef DEBUG + printk("SNDCTL_DSP_SUBDIVIDE %d\n", val); +#endif + dmabuf->subdivision = val; + dmabuf->ready = 0; + return 0; + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + + dmabuf->ossfragsize = 1<<(val & 0xffff); + dmabuf->ossmaxfrags = (val >> 16) & 0xffff; + if (!dmabuf->ossfragsize || !dmabuf->ossmaxfrags) + return -EINVAL; + /* + * Bound the frag size into our allowed range of 256 - 4096 + */ + if (dmabuf->ossfragsize < 256) + dmabuf->ossfragsize = 256; + else if (dmabuf->ossfragsize > 4096) + dmabuf->ossfragsize = 4096; + /* + * The numfrags could be something reasonable, or it could + * be 0xffff meaning "Give me as much as possible". So, + * we check the numfrags * fragsize doesn't exceed our + * 64k buffer limit, nor is it less than our 8k minimum. + * If it fails either one of these checks, then adjust the + * number of fragments, not the size of them. It's OK if + * our number of fragments doesn't equal 32 or anything + * like our hardware based number now since we are using + * a different frag count for the hardware. Before we get + * into this though, bound the maxfrags to avoid overflow + * issues. A reasonable bound would be 64k / 256 since our + * maximum buffer size is 64k and our minimum frag size is + * 256. On the other end, our minimum buffer size is 8k and + * our maximum frag size is 4k, so the lower bound should + * be 2. + */ + + if(dmabuf->ossmaxfrags > 256) + dmabuf->ossmaxfrags = 256; + else if (dmabuf->ossmaxfrags < 2) + dmabuf->ossmaxfrags = 2; + + val = dmabuf->ossfragsize * dmabuf->ossmaxfrags; + while (val < 8192) { + val <<= 1; + dmabuf->ossmaxfrags <<= 1; + } + while (val > 65536) { + val >>= 1; + dmabuf->ossmaxfrags >>= 1; + } + dmabuf->ready = 0; +#ifdef DEBUG + printk("SNDCTL_DSP_SETFRAGMENT 0x%x, %d, %d\n", val, + dmabuf->ossfragsize, dmabuf->ossmaxfrags); +#endif + + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!dmabuf->ready && (val = prog_dmabuf(state, 0)) != 0) + return val; + spin_lock_irqsave(&state->card->lock, flags); + i810_update_ptr(state); + abinfo.fragsize = dmabuf->userfragsize; + abinfo.fragstotal = dmabuf->userfrags; + if (dmabuf->mapped) + abinfo.bytes = dmabuf->dmasize; + else + abinfo.bytes = i810_get_free_write_space(state); + abinfo.fragments = abinfo.bytes / dmabuf->userfragsize; + spin_unlock_irqrestore(&state->card->lock, flags); +#if defined(DEBUG) || defined(DEBUG_MMAP) + printk("SNDCTL_DSP_GETOSPACE %d, %d, %d, %d\n", abinfo.bytes, + abinfo.fragsize, abinfo.fragments, abinfo.fragstotal); +#endif + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!dmabuf->ready && (val = prog_dmabuf(state, 0)) != 0) + return val; + spin_lock_irqsave(&state->card->lock, flags); + val = i810_get_free_write_space(state); + cinfo.bytes = dmabuf->total_bytes; + cinfo.ptr = dmabuf->hwptr; + cinfo.blocks = val/dmabuf->userfragsize; + if (dmabuf->mapped && (dmabuf->trigger & PCM_ENABLE_OUTPUT)) { + dmabuf->count += val; + dmabuf->swptr = (dmabuf->swptr + val) % dmabuf->dmasize; + __i810_update_lvi(state, 0); + } + spin_unlock_irqrestore(&state->card->lock, flags); +#if defined(DEBUG) || defined(DEBUG_MMAP) + printk("SNDCTL_DSP_GETOPTR %d, %d, %d, %d\n", cinfo.bytes, + cinfo.blocks, cinfo.ptr, dmabuf->count); +#endif + return copy_to_user(argp, &cinfo, sizeof(cinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!dmabuf->ready && (val = prog_dmabuf(state, 1)) != 0) + return val; + spin_lock_irqsave(&state->card->lock, flags); + abinfo.bytes = i810_get_available_read_data(state); + abinfo.fragsize = dmabuf->userfragsize; + abinfo.fragstotal = dmabuf->userfrags; + abinfo.fragments = abinfo.bytes / dmabuf->userfragsize; + spin_unlock_irqrestore(&state->card->lock, flags); +#if defined(DEBUG) || defined(DEBUG_MMAP) + printk("SNDCTL_DSP_GETISPACE %d, %d, %d, %d\n", abinfo.bytes, + abinfo.fragsize, abinfo.fragments, abinfo.fragstotal); +#endif + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!dmabuf->ready && (val = prog_dmabuf(state, 0)) != 0) + return val; + spin_lock_irqsave(&state->card->lock, flags); + val = i810_get_available_read_data(state); + cinfo.bytes = dmabuf->total_bytes; + cinfo.blocks = val/dmabuf->userfragsize; + cinfo.ptr = dmabuf->hwptr; + if (dmabuf->mapped && (dmabuf->trigger & PCM_ENABLE_INPUT)) { + dmabuf->count -= val; + dmabuf->swptr = (dmabuf->swptr + val) % dmabuf->dmasize; + __i810_update_lvi(state, 1); + } + spin_unlock_irqrestore(&state->card->lock, flags); +#if defined(DEBUG) || defined(DEBUG_MMAP) + printk("SNDCTL_DSP_GETIPTR %d, %d, %d, %d\n", cinfo.bytes, + cinfo.blocks, cinfo.ptr, dmabuf->count); +#endif + return copy_to_user(argp, &cinfo, sizeof(cinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: +#ifdef DEBUG + printk("SNDCTL_DSP_NONBLOCK\n"); +#endif + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETCAPS: +#ifdef DEBUG + printk("SNDCTL_DSP_GETCAPS\n"); +#endif + return put_user(DSP_CAP_REALTIME|DSP_CAP_TRIGGER|DSP_CAP_MMAP|DSP_CAP_BIND, + p); + + case SNDCTL_DSP_GETTRIGGER: + val = 0; +#ifdef DEBUG + printk("SNDCTL_DSP_GETTRIGGER 0x%x\n", dmabuf->trigger); +#endif + return put_user(dmabuf->trigger, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + return -EFAULT; +#if defined(DEBUG) || defined(DEBUG_MMAP) + printk("SNDCTL_DSP_SETTRIGGER 0x%x\n", val); +#endif + /* silently ignore invalid PCM_ENABLE_xxx bits, + * like the other drivers do + */ + if (!(file->f_mode & FMODE_READ )) + val &= ~PCM_ENABLE_INPUT; + if (!(file->f_mode & FMODE_WRITE )) + val &= ~PCM_ENABLE_OUTPUT; + if((file->f_mode & FMODE_READ) && !(val & PCM_ENABLE_INPUT) && dmabuf->enable == ADC_RUNNING) { + stop_adc(state); + } + if((file->f_mode & FMODE_WRITE) && !(val & PCM_ENABLE_OUTPUT) && dmabuf->enable == DAC_RUNNING) { + stop_dac(state); + } + dmabuf->trigger = val; + if((val & PCM_ENABLE_OUTPUT) && !(dmabuf->enable & DAC_RUNNING)) { + if (!dmabuf->write_channel) { + dmabuf->ready = 0; + dmabuf->write_channel = state->card->alloc_pcm_channel(state->card); + if (!dmabuf->write_channel) + return -EBUSY; + } + if (!dmabuf->ready && (ret = prog_dmabuf(state, 0))) + return ret; + if (dmabuf->mapped) { + spin_lock_irqsave(&state->card->lock, flags); + i810_update_ptr(state); + dmabuf->count = 0; + dmabuf->swptr = dmabuf->hwptr; + dmabuf->count = i810_get_free_write_space(state); + dmabuf->swptr = (dmabuf->swptr + dmabuf->count) % dmabuf->dmasize; + spin_unlock_irqrestore(&state->card->lock, flags); + } + i810_update_lvi(state, 0); + start_dac(state); + } + if((val & PCM_ENABLE_INPUT) && !(dmabuf->enable & ADC_RUNNING)) { + if (!dmabuf->read_channel) { + dmabuf->ready = 0; + dmabuf->read_channel = state->card->alloc_rec_pcm_channel(state->card); + if (!dmabuf->read_channel) + return -EBUSY; + } + if (!dmabuf->ready && (ret = prog_dmabuf(state, 1))) + return ret; + if (dmabuf->mapped) { + spin_lock_irqsave(&state->card->lock, flags); + i810_update_ptr(state); + dmabuf->swptr = dmabuf->hwptr; + dmabuf->count = 0; + spin_unlock_irqrestore(&state->card->lock, flags); + } + i810_update_lvi(state, 1); + start_adc(state); + } + return 0; + + case SNDCTL_DSP_SETDUPLEX: +#ifdef DEBUG + printk("SNDCTL_DSP_SETDUPLEX\n"); +#endif + return -EINVAL; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&state->card->lock, flags); + i810_update_ptr(state); + val = dmabuf->count; + spin_unlock_irqrestore(&state->card->lock, flags); +#ifdef DEBUG + printk("SNDCTL_DSP_GETODELAY %d\n", dmabuf->count); +#endif + return put_user(val, p); + + case SOUND_PCM_READ_RATE: +#ifdef DEBUG + printk("SOUND_PCM_READ_RATE %d\n", dmabuf->rate); +#endif + return put_user(dmabuf->rate, p); + + case SOUND_PCM_READ_CHANNELS: +#ifdef DEBUG + printk("SOUND_PCM_READ_CHANNELS\n"); +#endif + return put_user(2, p); + + case SOUND_PCM_READ_BITS: +#ifdef DEBUG + printk("SOUND_PCM_READ_BITS\n"); +#endif + return put_user(AFMT_S16_LE, p); + + case SNDCTL_DSP_SETSPDIF: /* Set S/PDIF Control register */ +#ifdef DEBUG + printk("SNDCTL_DSP_SETSPDIF\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + + /* Check to make sure the codec supports S/PDIF transmitter */ + + if((state->card->ac97_features & 4)) { + /* mask out the transmitter speed bits so the user can't set them */ + val &= ~0x3000; + + /* Add the current transmitter speed bits to the passed value */ + ret = i810_ac97_get(codec, AC97_SPDIF_CONTROL); + val |= (ret & 0x3000); + + i810_ac97_set(codec, AC97_SPDIF_CONTROL, val); + if(i810_ac97_get(codec, AC97_SPDIF_CONTROL) != val ) { + printk(KERN_ERR "i810_audio: Unable to set S/PDIF configuration to 0x%04x.\n", val); + return -EFAULT; + } + } +#ifdef DEBUG + else + printk(KERN_WARNING "i810_audio: S/PDIF transmitter not avalible.\n"); +#endif + return put_user(val, p); + + case SNDCTL_DSP_GETSPDIF: /* Get S/PDIF Control register */ +#ifdef DEBUG + printk("SNDCTL_DSP_GETSPDIF\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + + /* Check to make sure the codec supports S/PDIF transmitter */ + + if(!(state->card->ac97_features & 4)) { +#ifdef DEBUG + printk(KERN_WARNING "i810_audio: S/PDIF transmitter not avalible.\n"); +#endif + val = 0; + } else { + val = i810_ac97_get(codec, AC97_SPDIF_CONTROL); + } + //return put_user((val & 0xcfff), p); + return put_user(val, p); + + case SNDCTL_DSP_GETCHANNELMASK: +#ifdef DEBUG + printk("SNDCTL_DSP_GETCHANNELMASK\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + + /* Based on AC'97 DAC support, not ICH hardware */ + val = DSP_BIND_FRONT; + if ( state->card->ac97_features & 0x0004 ) + val |= DSP_BIND_SPDIF; + + if ( state->card->ac97_features & 0x0080 ) + val |= DSP_BIND_SURR; + if ( state->card->ac97_features & 0x0140 ) + val |= DSP_BIND_CENTER_LFE; + + return put_user(val, p); + + case SNDCTL_DSP_BIND_CHANNEL: +#ifdef DEBUG + printk("SNDCTL_DSP_BIND_CHANNEL\n"); +#endif + if (get_user(val, p)) + return -EFAULT; + if ( val == DSP_BIND_QUERY ) { + val = DSP_BIND_FRONT; /* Always report this as being enabled */ + if ( state->card->ac97_status & SPDIF_ON ) + val |= DSP_BIND_SPDIF; + else { + if ( state->card->ac97_status & SURR_ON ) + val |= DSP_BIND_SURR; + if ( state->card->ac97_status & CENTER_LFE_ON ) + val |= DSP_BIND_CENTER_LFE; + } + } else { /* Not a query, set it */ + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if ( dmabuf->enable == DAC_RUNNING ) { + stop_dac(state); + } + if ( val & DSP_BIND_SPDIF ) { /* Turn on SPDIF */ + /* Ok, this should probably define what slots + * to use. For now, we'll only set it to the + * defaults: + * + * non multichannel codec maps to slots 3&4 + * 2 channel codec maps to slots 7&8 + * 4 channel codec maps to slots 6&9 + * 6 channel codec maps to slots 10&11 + * + * there should be some way for the app to + * select the slot assignment. + */ + + i810_set_spdif_output ( state, AC97_EA_SPSA_3_4, dmabuf->rate ); + if ( !(state->card->ac97_status & SPDIF_ON) ) + val &= ~DSP_BIND_SPDIF; + } else { + int mask; + int channels; + + /* Turn off S/PDIF if it was on */ + if ( state->card->ac97_status & SPDIF_ON ) + i810_set_spdif_output ( state, -1, 0 ); + + mask = val & (DSP_BIND_FRONT | DSP_BIND_SURR | DSP_BIND_CENTER_LFE); + switch (mask) { + case DSP_BIND_FRONT: + channels = 2; + break; + case DSP_BIND_FRONT|DSP_BIND_SURR: + channels = 4; + break; + case DSP_BIND_FRONT|DSP_BIND_SURR|DSP_BIND_CENTER_LFE: + channels = 6; + break; + default: + val = DSP_BIND_FRONT; + channels = 2; + break; + } + i810_set_dac_channels ( state, channels ); + + /* check that they really got turned on */ + if (!(state->card->ac97_status & SURR_ON)) + val &= ~DSP_BIND_SURR; + if (!(state->card->ac97_status & CENTER_LFE_ON)) + val &= ~DSP_BIND_CENTER_LFE; + } + } + return put_user(val, p); + + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: +#ifdef DEBUG + printk("SNDCTL_* -EINVAL\n"); +#endif + return -EINVAL; + } + return -EINVAL; +} + +static int i810_open(struct inode *inode, struct file *file) +{ + int i = 0; + struct i810_card *card = devs; + struct i810_state *state = NULL; + struct dmabuf *dmabuf = NULL; + + /* find an avaiable virtual channel (instance of /dev/dsp) */ + while (card != NULL) { + /* + * If we are initializing and then fail, card could go + * away unuexpectedly while we are in the for() loop. + * So, check for card on each iteration before we check + * for card->initializing to avoid a possible oops. + * This usually only matters for times when the driver is + * autoloaded by kmod. + */ + for (i = 0; i < 50 && card && card->initializing; i++) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/20); + } + for (i = 0; i < NR_HW_CH && card && !card->initializing; i++) { + if (card->states[i] == NULL) { + state = card->states[i] = (struct i810_state *) + kmalloc(sizeof(struct i810_state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + memset(state, 0, sizeof(struct i810_state)); + dmabuf = &state->dmabuf; + goto found_virt; + } + } + card = card->next; + } + /* no more virtual channel avaiable */ + if (!state) + return -ENODEV; + +found_virt: + /* initialize the virtual channel */ + state->virt = i; + state->card = card; + state->magic = I810_STATE_MAGIC; + init_waitqueue_head(&dmabuf->wait); + init_MUTEX(&state->open_sem); + file->private_data = state; + dmabuf->trigger = 0; + + /* allocate hardware channels */ + if(file->f_mode & FMODE_READ) { + if((dmabuf->read_channel = card->alloc_rec_pcm_channel(card)) == NULL) { + kfree (card->states[i]); + card->states[i] = NULL; + return -EBUSY; + } + dmabuf->trigger |= PCM_ENABLE_INPUT; + i810_set_adc_rate(state, 8000); + } + if(file->f_mode & FMODE_WRITE) { + if((dmabuf->write_channel = card->alloc_pcm_channel(card)) == NULL) { + /* make sure we free the record channel allocated above */ + if(file->f_mode & FMODE_READ) + card->free_pcm_channel(card,dmabuf->read_channel->num); + kfree (card->states[i]); + card->states[i] = NULL; + return -EBUSY; + } + /* Initialize to 8kHz? What if we don't support 8kHz? */ + /* Let's change this to check for S/PDIF stuff */ + + dmabuf->trigger |= PCM_ENABLE_OUTPUT; + if ( spdif_locked ) { + i810_set_dac_rate(state, spdif_locked); + i810_set_spdif_output(state, AC97_EA_SPSA_3_4, spdif_locked); + } else { + i810_set_dac_rate(state, 8000); + /* Put the ACLink in 2 channel mode by default */ + i = I810_IOREADL(card, GLOB_CNT); + I810_IOWRITEL(i & 0xffcfffff, card, GLOB_CNT); + } + } + + /* set default sample format. According to OSS Programmer's Guide /dev/dsp + should be default to unsigned 8-bits, mono, with sample rate 8kHz and + /dev/dspW will accept 16-bits sample, but we don't support those so we + set it immediately to stereo and 16bit, which is all we do support */ + dmabuf->fmt |= I810_FMT_16BIT | I810_FMT_STEREO; + dmabuf->ossfragsize = 0; + dmabuf->ossmaxfrags = 0; + dmabuf->subdivision = 0; + + state->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + + return nonseekable_open(inode, file); +} + +static int i810_release(struct inode *inode, struct file *file) +{ + struct i810_state *state = (struct i810_state *)file->private_data; + struct i810_card *card = state->card; + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + + lock_kernel(); + + /* stop DMA state machine and free DMA buffers/channels */ + if(dmabuf->trigger & PCM_ENABLE_OUTPUT) { + drain_dac(state, 0); + } + if(dmabuf->trigger & PCM_ENABLE_INPUT) { + stop_adc(state); + } + spin_lock_irqsave(&card->lock, flags); + dealloc_dmabuf(state); + if (file->f_mode & FMODE_WRITE) { + state->card->free_pcm_channel(state->card, dmabuf->write_channel->num); + } + if (file->f_mode & FMODE_READ) { + state->card->free_pcm_channel(state->card, dmabuf->read_channel->num); + } + + state->card->states[state->virt] = NULL; + kfree(state); + spin_unlock_irqrestore(&card->lock, flags); + unlock_kernel(); + + return 0; +} + +static /*const*/ struct file_operations i810_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = i810_read, + .write = i810_write, + .poll = i810_poll, + .ioctl = i810_ioctl, + .mmap = i810_mmap, + .open = i810_open, + .release = i810_release, +}; + +/* Write AC97 codec registers */ + +static u16 i810_ac97_get_mmio(struct ac97_codec *dev, u8 reg) +{ + struct i810_card *card = dev->private_data; + int count = 100; + u16 reg_set = IO_REG_OFF(dev) | (reg&0x7f); + + while(count-- && (readb(card->iobase_mmio + CAS) & 1)) + udelay(1); + +#ifdef DEBUG_MMIO + { + u16 ans = readw(card->ac97base_mmio + reg_set); + printk(KERN_DEBUG "i810_audio: ac97_get_mmio(%d) -> 0x%04X\n", ((int) reg_set) & 0xffff, (u32) ans); + return ans; + } +#else + return readw(card->ac97base_mmio + reg_set); +#endif +} + +static u16 i810_ac97_get_io(struct ac97_codec *dev, u8 reg) +{ + struct i810_card *card = dev->private_data; + int count = 100; + u16 reg_set = IO_REG_OFF(dev) | (reg&0x7f); + + while(count-- && (I810_IOREADB(card, CAS) & 1)) + udelay(1); + + return inw(card->ac97base + reg_set); +} + +static void i810_ac97_set_mmio(struct ac97_codec *dev, u8 reg, u16 data) +{ + struct i810_card *card = dev->private_data; + int count = 100; + u16 reg_set = IO_REG_OFF(dev) | (reg&0x7f); + + while(count-- && (readb(card->iobase_mmio + CAS) & 1)) + udelay(1); + + writew(data, card->ac97base_mmio + reg_set); + +#ifdef DEBUG_MMIO + printk(KERN_DEBUG "i810_audio: ac97_set_mmio(0x%04X, %d)\n", (u32) data, ((int) reg_set) & 0xffff); +#endif +} + +static void i810_ac97_set_io(struct ac97_codec *dev, u8 reg, u16 data) +{ + struct i810_card *card = dev->private_data; + int count = 100; + u16 reg_set = IO_REG_OFF(dev) | (reg&0x7f); + + while(count-- && (I810_IOREADB(card, CAS) & 1)) + udelay(1); + + outw(data, card->ac97base + reg_set); +} + +static u16 i810_ac97_get(struct ac97_codec *dev, u8 reg) +{ + struct i810_card *card = dev->private_data; + u16 ret; + + spin_lock(&card->ac97_lock); + if (card->use_mmio) { + ret = i810_ac97_get_mmio(dev, reg); + } + else { + ret = i810_ac97_get_io(dev, reg); + } + spin_unlock(&card->ac97_lock); + + return ret; +} + +static void i810_ac97_set(struct ac97_codec *dev, u8 reg, u16 data) +{ + struct i810_card *card = dev->private_data; + + spin_lock(&card->ac97_lock); + if (card->use_mmio) { + i810_ac97_set_mmio(dev, reg, data); + } + else { + i810_ac97_set_io(dev, reg, data); + } + spin_unlock(&card->ac97_lock); +} + + +/* OSS /dev/mixer file operation methods */ + +static int i810_open_mixdev(struct inode *inode, struct file *file) +{ + int i; + int minor = iminor(inode); + struct i810_card *card = devs; + + for (card = devs; card != NULL; card = card->next) { + /* + * If we are initializing and then fail, card could go + * away unuexpectedly while we are in the for() loop. + * So, check for card on each iteration before we check + * for card->initializing to avoid a possible oops. + * This usually only matters for times when the driver is + * autoloaded by kmod. + */ + for (i = 0; i < 50 && card && card->initializing; i++) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/20); + } + for (i = 0; i < NR_AC97 && card && !card->initializing; i++) + if (card->ac97_codec[i] != NULL && + card->ac97_codec[i]->dev_mixer == minor) { + file->private_data = card->ac97_codec[i]; + return nonseekable_open(inode, file); + } + } + return -ENODEV; +} + +static int i810_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct ac97_codec *codec = (struct ac97_codec *)file->private_data; + + return codec->mixer_ioctl(codec, cmd, arg); +} + +static /*const*/ struct file_operations i810_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = i810_ioctl_mixdev, + .open = i810_open_mixdev, +}; + +/* AC97 codec initialisation. These small functions exist so we don't + duplicate code between module init and apm resume */ + +static inline int i810_ac97_exists(struct i810_card *card, int ac97_number) +{ + u32 reg = I810_IOREADL(card, GLOB_STA); + switch (ac97_number) { + case 0: + return reg & (1<<8); + case 1: + return reg & (1<<9); + case 2: + return reg & (1<<28); + } + return 0; +} + +static inline int i810_ac97_enable_variable_rate(struct ac97_codec *codec) +{ + i810_ac97_set(codec, AC97_EXTENDED_STATUS, 9); + i810_ac97_set(codec,AC97_EXTENDED_STATUS, + i810_ac97_get(codec, AC97_EXTENDED_STATUS)|0xE800); + + return (i810_ac97_get(codec, AC97_EXTENDED_STATUS)&1); +} + + +static int i810_ac97_probe_and_powerup(struct i810_card *card,struct ac97_codec *codec) +{ + /* Returns 0 on failure */ + int i; + + if (ac97_probe_codec(codec) == 0) return 0; + + /* power it all up */ + i810_ac97_set(codec, AC97_POWER_CONTROL, + i810_ac97_get(codec, AC97_POWER_CONTROL) & ~0x7f00); + + /* wait for analog ready */ + for (i=100; i && ((i810_ac97_get(codec, AC97_POWER_CONTROL) & 0xf) != 0xf); i--) + { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/20); + } + return i; +} + +static int is_new_ich(u16 pci_id) +{ + switch (pci_id) { + case PCI_DEVICE_ID_INTEL_82801DB_5: + case PCI_DEVICE_ID_INTEL_82801EB_5: + case PCI_DEVICE_ID_INTEL_ESB_5: + case PCI_DEVICE_ID_INTEL_ICH6_18: + return 1; + default: + break; + } + + return 0; +} + +static inline int ich_use_mmio(struct i810_card *card) +{ + return is_new_ich(card->pci_id) && card->use_mmio; +} + +/** + * i810_ac97_power_up_bus - bring up AC97 link + * @card : ICH audio device to power up + * + * Bring up the ACLink AC97 codec bus + */ + +static int i810_ac97_power_up_bus(struct i810_card *card) +{ + u32 reg = I810_IOREADL(card, GLOB_CNT); + int i; + int primary_codec_id = 0; + + if((reg&2)==0) /* Cold required */ + reg|=2; + else + reg|=4; /* Warm */ + + reg&=~8; /* ACLink on */ + + /* At this point we deassert AC_RESET # */ + I810_IOWRITEL(reg , card, GLOB_CNT); + + /* We must now allow time for the Codec initialisation. + 600mS is the specified time */ + + for(i=0;i<10;i++) + { + if((I810_IOREADL(card, GLOB_CNT)&4)==0) + break; + + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/20); + } + if(i==10) + { + printk(KERN_ERR "i810_audio: AC'97 reset failed.\n"); + return 0; + } + + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/2); + + /* + * See if the primary codec comes ready. This must happen + * before we start doing DMA stuff + */ + /* see i810_ac97_init for the next 10 lines (jsaw) */ + if (card->use_mmio) + readw(card->ac97base_mmio); + else + inw(card->ac97base); + if (ich_use_mmio(card)) { + primary_codec_id = (int) readl(card->iobase_mmio + SDM) & 0x3; + printk(KERN_INFO "i810_audio: Primary codec has ID %d\n", + primary_codec_id); + } + + if(! i810_ac97_exists(card, primary_codec_id)) + { + printk(KERN_INFO "i810_audio: Codec not ready.. wait.. "); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ); /* actually 600mS by the spec */ + + if(i810_ac97_exists(card, primary_codec_id)) + printk("OK\n"); + else + printk("no response.\n"); + } + if (card->use_mmio) + readw(card->ac97base_mmio); + else + inw(card->ac97base); + return 1; +} + +static int __devinit i810_ac97_init(struct i810_card *card) +{ + int num_ac97 = 0; + int ac97_id; + int total_channels = 0; + int nr_ac97_max = card_cap[card->pci_id_internal].nr_ac97; + struct ac97_codec *codec; + u16 eid; + u32 reg; + + if(!i810_ac97_power_up_bus(card)) return 0; + + /* Number of channels supported */ + /* What about the codec? Just because the ICH supports */ + /* multiple channels doesn't mean the codec does. */ + /* we'll have to modify this in the codec section below */ + /* to reflect what the codec has. */ + /* ICH and ICH0 only support 2 channels so don't bother */ + /* to check.... */ + + card->channels = 2; + reg = I810_IOREADL(card, GLOB_STA); + if ( reg & 0x0200000 ) + card->channels = 6; + else if ( reg & 0x0100000 ) + card->channels = 4; + printk(KERN_INFO "i810_audio: Audio Controller supports %d channels.\n", card->channels); + printk(KERN_INFO "i810_audio: Defaulting to base 2 channel mode.\n"); + reg = I810_IOREADL(card, GLOB_CNT); + I810_IOWRITEL(reg & 0xffcfffff, card, GLOB_CNT); + + for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) + card->ac97_codec[num_ac97] = NULL; + + /*@FIXME I don't know, if I'm playing to safe here... (jsaw) */ + if ((nr_ac97_max > 2) && !card->use_mmio) nr_ac97_max = 2; + + for (num_ac97 = 0; num_ac97 < nr_ac97_max; num_ac97++) { + /* codec reset */ + printk(KERN_INFO "i810_audio: Resetting connection %d\n", num_ac97); + if (card->use_mmio) + readw(card->ac97base_mmio + 0x80*num_ac97); + else + inw(card->ac97base + 0x80*num_ac97); + + /* If we have the SDATA_IN Map Register, as on ICH4, we + do not loop thru all possible codec IDs but thru all + possible IO channels. Bit 0:1 of SDM then holds the + last codec ID spoken to. + */ + if (ich_use_mmio(card)) { + ac97_id = (int) readl(card->iobase_mmio + SDM) & 0x3; + printk(KERN_INFO "i810_audio: Connection %d with codec id %d\n", + num_ac97, ac97_id); + } + else { + ac97_id = num_ac97; + } + + /* The ICH programmer's reference says you should */ + /* check the ready status before probing. So we chk */ + /* What do we do if it's not ready? Wait and try */ + /* again, or abort? */ + if (!i810_ac97_exists(card, ac97_id)) { + if(num_ac97 == 0) + printk(KERN_ERR "i810_audio: Primary codec not ready.\n"); + } + + if ((codec = ac97_alloc_codec()) == NULL) + return -ENOMEM; + + /* initialize some basic codec information, other fields will be filled + in ac97_probe_codec */ + codec->private_data = card; + codec->id = ac97_id; + card->ac97_id_map[ac97_id] = num_ac97 * 0x80; + + if (card->use_mmio) { + codec->codec_read = i810_ac97_get_mmio; + codec->codec_write = i810_ac97_set_mmio; + } + else { + codec->codec_read = i810_ac97_get_io; + codec->codec_write = i810_ac97_set_io; + } + + if(!i810_ac97_probe_and_powerup(card,codec)) { + printk(KERN_ERR "i810_audio: timed out waiting for codec %d analog ready.\n", ac97_id); + ac97_release_codec(codec); + break; /* it didn't work */ + } + /* Store state information about S/PDIF transmitter */ + card->ac97_status = 0; + + /* Don't attempt to get eid until powerup is complete */ + eid = i810_ac97_get(codec, AC97_EXTENDED_ID); + + if(eid==0xFFFF) + { + printk(KERN_WARNING "i810_audio: no codec attached ?\n"); + ac97_release_codec(codec); + break; + } + + /* Check for an AC97 1.0 soft modem (ID1) */ + + if(codec->modem) + { + printk(KERN_WARNING "i810_audio: codec %d is a softmodem - skipping.\n", ac97_id); + ac97_release_codec(codec); + continue; + } + + card->ac97_features = eid; + + /* Now check the codec for useful features to make up for + the dumbness of the 810 hardware engine */ + + if(!(eid&0x0001)) + printk(KERN_WARNING "i810_audio: only 48Khz playback available.\n"); + else + { + if(!i810_ac97_enable_variable_rate(codec)) { + printk(KERN_WARNING "i810_audio: Codec refused to allow VRA, using 48Khz only.\n"); + card->ac97_features&=~1; + } + } + + /* Turn on the amplifier */ + + codec->codec_write(codec, AC97_POWER_CONTROL, + codec->codec_read(codec, AC97_POWER_CONTROL) & ~0x8000); + + /* Determine how many channels the codec(s) support */ + /* - The primary codec always supports 2 */ + /* - If the codec supports AMAP, surround DACs will */ + /* automaticlly get assigned to slots. */ + /* * Check for surround DACs and increment if */ + /* found. */ + /* - Else check if the codec is revision 2.2 */ + /* * If surround DACs exist, assign them to slots */ + /* and increment channel count. */ + + /* All of this only applies to ICH2 and above. ICH */ + /* and ICH0 only support 2 channels. ICH2 will only */ + /* support multiple codecs in a "split audio" config. */ + /* as described above. */ + + /* TODO: Remove all the debugging messages! */ + + if((eid & 0xc000) == 0) /* primary codec */ + total_channels += 2; + + if(eid & 0x200) { /* GOOD, AMAP support */ + if (eid & 0x0080) /* L/R Surround channels */ + total_channels += 2; + if (eid & 0x0140) /* LFE and Center channels */ + total_channels += 2; + printk("i810_audio: AC'97 codec %d supports AMAP, total channels = %d\n", ac97_id, total_channels); + } else if (eid & 0x0400) { /* this only works on 2.2 compliant codecs */ + eid &= 0xffcf; + if((eid & 0xc000) != 0) { + switch ( total_channels ) { + case 2: + /* Set dsa1, dsa0 to 01 */ + eid |= 0x0010; + break; + case 4: + /* Set dsa1, dsa0 to 10 */ + eid |= 0x0020; + break; + case 6: + /* Set dsa1, dsa0 to 11 */ + eid |= 0x0030; + break; + } + total_channels += 2; + } + i810_ac97_set(codec, AC97_EXTENDED_ID, eid); + eid = i810_ac97_get(codec, AC97_EXTENDED_ID); + printk("i810_audio: AC'97 codec %d, new EID value = 0x%04x\n", ac97_id, eid); + if (eid & 0x0080) /* L/R Surround channels */ + total_channels += 2; + if (eid & 0x0140) /* LFE and Center channels */ + total_channels += 2; + printk("i810_audio: AC'97 codec %d, DAC map configured, total channels = %d\n", ac97_id, total_channels); + } else { + printk("i810_audio: AC'97 codec %d Unable to map surround DAC's (or DAC's not present), total channels = %d\n", ac97_id, total_channels); + } + + if ((codec->dev_mixer = register_sound_mixer(&i810_mixer_fops, -1)) < 0) { + printk(KERN_ERR "i810_audio: couldn't register mixer!\n"); + ac97_release_codec(codec); + break; + } + + card->ac97_codec[num_ac97] = codec; + } + + /* tune up the primary codec */ + ac97_tune_hardware(card->pci_dev, ac97_quirks, ac97_quirk); + + /* pick the minimum of channels supported by ICHx or codec(s) */ + card->channels = (card->channels > total_channels)?total_channels:card->channels; + + return num_ac97; +} + +static void __devinit i810_configure_clocking (void) +{ + struct i810_card *card; + struct i810_state *state; + struct dmabuf *dmabuf; + unsigned int i, offset, new_offset; + unsigned long flags; + + card = devs; + /* We could try to set the clocking for multiple cards, but can you even have + * more than one i810 in a machine? Besides, clocking is global, so unless + * someone actually thinks more than one i810 in a machine is possible and + * decides to rewrite that little bit, setting the rate for more than one card + * is a waste of time. + */ + if(card != NULL) { + state = card->states[0] = (struct i810_state *) + kmalloc(sizeof(struct i810_state), GFP_KERNEL); + if (state == NULL) + return; + memset(state, 0, sizeof(struct i810_state)); + dmabuf = &state->dmabuf; + + dmabuf->write_channel = card->alloc_pcm_channel(card); + state->virt = 0; + state->card = card; + state->magic = I810_STATE_MAGIC; + init_waitqueue_head(&dmabuf->wait); + init_MUTEX(&state->open_sem); + dmabuf->fmt = I810_FMT_STEREO | I810_FMT_16BIT; + dmabuf->trigger = PCM_ENABLE_OUTPUT; + i810_set_spdif_output(state, -1, 0); + i810_set_dac_channels(state, 2); + i810_set_dac_rate(state, 48000); + if(prog_dmabuf(state, 0) != 0) { + goto config_out_nodmabuf; + } + if(dmabuf->dmasize < 16384) { + goto config_out; + } + dmabuf->count = dmabuf->dmasize; + CIV_TO_LVI(card, dmabuf->write_channel->port, -1); + local_irq_save(flags); + start_dac(state); + offset = i810_get_dma_addr(state, 0); + mdelay(50); + new_offset = i810_get_dma_addr(state, 0); + stop_dac(state); + local_irq_restore(flags); + i = new_offset - offset; +#ifdef DEBUG_INTERRUPTS + printk("i810_audio: %d bytes in 50 milliseconds\n", i); +#endif + if(i == 0) + goto config_out; + i = i / 4 * 20; + if (i > 48500 || i < 47500) { + clocking = clocking * clocking / i; + printk("i810_audio: setting clocking to %d\n", clocking); + } +config_out: + dealloc_dmabuf(state); +config_out_nodmabuf: + state->card->free_pcm_channel(state->card,state->dmabuf.write_channel->num); + kfree(state); + card->states[0] = NULL; + } +} + +/* install the driver, we do not allocate hardware channel nor DMA buffer now, they are defered + until "ACCESS" time (in prog_dmabuf called by open/read/write/ioctl/mmap) */ + +static int __devinit i810_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id) +{ + struct i810_card *card; + + if (pci_enable_device(pci_dev)) + return -EIO; + + if (pci_set_dma_mask(pci_dev, I810_DMA_MASK)) { + printk(KERN_ERR "i810_audio: architecture does not support" + " 32bit PCI busmaster DMA\n"); + return -ENODEV; + } + + if ((card = kmalloc(sizeof(struct i810_card), GFP_KERNEL)) == NULL) { + printk(KERN_ERR "i810_audio: out of memory\n"); + return -ENOMEM; + } + memset(card, 0, sizeof(*card)); + + card->initializing = 1; + card->pci_dev = pci_dev; + card->pci_id = pci_id->device; + card->ac97base = pci_resource_start (pci_dev, 0); + card->iobase = pci_resource_start (pci_dev, 1); + + if (!(card->ac97base) || !(card->iobase)) { + card->ac97base = 0; + card->iobase = 0; + } + + /* if chipset could have mmio capability, check it */ + if (card_cap[pci_id->driver_data].flags & CAP_MMIO) { + card->ac97base_mmio_phys = pci_resource_start (pci_dev, 2); + card->iobase_mmio_phys = pci_resource_start (pci_dev, 3); + + if ((card->ac97base_mmio_phys) && (card->iobase_mmio_phys)) { + card->use_mmio = 1; + } + else { + card->ac97base_mmio_phys = 0; + card->iobase_mmio_phys = 0; + } + } + + if (!(card->use_mmio) && (!(card->iobase) || !(card->ac97base))) { + printk(KERN_ERR "i810_audio: No I/O resources available.\n"); + goto out_mem; + } + + card->irq = pci_dev->irq; + card->next = devs; + card->magic = I810_CARD_MAGIC; +#ifdef CONFIG_PM + card->pm_suspended=0; +#endif + spin_lock_init(&card->lock); + spin_lock_init(&card->ac97_lock); + devs = card; + + pci_set_master(pci_dev); + + printk(KERN_INFO "i810: %s found at IO 0x%04lx and 0x%04lx, " + "MEM 0x%04lx and 0x%04lx, IRQ %d\n", + card_names[pci_id->driver_data], + card->iobase, card->ac97base, + card->ac97base_mmio_phys, card->iobase_mmio_phys, + card->irq); + + card->alloc_pcm_channel = i810_alloc_pcm_channel; + card->alloc_rec_pcm_channel = i810_alloc_rec_pcm_channel; + card->alloc_rec_mic_channel = i810_alloc_rec_mic_channel; + card->free_pcm_channel = i810_free_pcm_channel; + + if ((card->channel = pci_alloc_consistent(pci_dev, + sizeof(struct i810_channel)*NR_HW_CH, &card->chandma)) == NULL) { + printk(KERN_ERR "i810: cannot allocate channel DMA memory\n"); + goto out_mem; + } + + { /* We may dispose of this altogether some time soon, so... */ + struct i810_channel *cp = card->channel; + + cp[0].offset = 0; + cp[0].port = 0x00; + cp[0].num = 0; + cp[1].offset = 0; + cp[1].port = 0x10; + cp[1].num = 1; + cp[2].offset = 0; + cp[2].port = 0x20; + cp[2].num = 2; + } + + /* claim our iospace and irq */ + if (!request_region(card->iobase, 64, card_names[pci_id->driver_data])) { + printk(KERN_ERR "i810_audio: unable to allocate region %lx\n", card->iobase); + goto out_region1; + } + if (!request_region(card->ac97base, 256, card_names[pci_id->driver_data])) { + printk(KERN_ERR "i810_audio: unable to allocate region %lx\n", card->ac97base); + goto out_region2; + } + + if (request_irq(card->irq, &i810_interrupt, SA_SHIRQ, + card_names[pci_id->driver_data], card)) { + printk(KERN_ERR "i810_audio: unable to allocate irq %d\n", card->irq); + goto out_pio; + } + + if (card->use_mmio) { + if (request_mem_region(card->ac97base_mmio_phys, 512, "ich_audio MMBAR")) { + if ((card->ac97base_mmio = ioremap(card->ac97base_mmio_phys, 512))) { /*@FIXME can ioremap fail? don't know (jsaw) */ + if (request_mem_region(card->iobase_mmio_phys, 256, "ich_audio MBBAR")) { + if ((card->iobase_mmio = ioremap(card->iobase_mmio_phys, 256))) { + printk(KERN_INFO "i810: %s mmio at 0x%04lx and 0x%04lx\n", + card_names[pci_id->driver_data], + (unsigned long) card->ac97base_mmio, + (unsigned long) card->iobase_mmio); + } + else { + iounmap(card->ac97base_mmio); + release_mem_region(card->ac97base_mmio_phys, 512); + release_mem_region(card->iobase_mmio_phys, 512); + card->use_mmio = 0; + } + } + else { + iounmap(card->ac97base_mmio); + release_mem_region(card->ac97base_mmio_phys, 512); + card->use_mmio = 0; + } + } + } + else { + card->use_mmio = 0; + } + } + + /* initialize AC97 codec and register /dev/mixer */ + if (i810_ac97_init(card) <= 0) { + free_irq(card->irq, card); + goto out_iospace; + } + pci_set_drvdata(pci_dev, card); + + if(clocking == 0) { + clocking = 48000; + i810_configure_clocking(); + } + + /* register /dev/dsp */ + if ((card->dev_audio = register_sound_dsp(&i810_audio_fops, -1)) < 0) { + int i; + printk(KERN_ERR "i810_audio: couldn't register DSP device!\n"); + free_irq(card->irq, card); + for (i = 0; i < NR_AC97; i++) + if (card->ac97_codec[i] != NULL) { + unregister_sound_mixer(card->ac97_codec[i]->dev_mixer); + ac97_release_codec(card->ac97_codec[i]); + } + goto out_iospace; + } + + card->initializing = 0; + return 0; + +out_iospace: + if (card->use_mmio) { + iounmap(card->ac97base_mmio); + iounmap(card->iobase_mmio); + release_mem_region(card->ac97base_mmio_phys, 512); + release_mem_region(card->iobase_mmio_phys, 256); + } +out_pio: + release_region(card->iobase, 64); +out_region2: + release_region(card->ac97base, 256); +out_region1: + pci_free_consistent(pci_dev, sizeof(struct i810_channel)*NR_HW_CH, + card->channel, card->chandma); +out_mem: + kfree(card); + return -ENODEV; +} + +static void __devexit i810_remove(struct pci_dev *pci_dev) +{ + int i; + struct i810_card *card = pci_get_drvdata(pci_dev); + /* free hardware resources */ + free_irq(card->irq, devs); + release_region(card->iobase, 64); + release_region(card->ac97base, 256); + pci_free_consistent(pci_dev, sizeof(struct i810_channel)*NR_HW_CH, + card->channel, card->chandma); + if (card->use_mmio) { + iounmap(card->ac97base_mmio); + iounmap(card->iobase_mmio); + release_mem_region(card->ac97base_mmio_phys, 512); + release_mem_region(card->iobase_mmio_phys, 256); + } + + /* unregister audio devices */ + for (i = 0; i < NR_AC97; i++) + if (card->ac97_codec[i] != NULL) { + unregister_sound_mixer(card->ac97_codec[i]->dev_mixer); + ac97_release_codec(card->ac97_codec[i]); + card->ac97_codec[i] = NULL; + } + unregister_sound_dsp(card->dev_audio); + kfree(card); +} + +#ifdef CONFIG_PM +static int i810_pm_suspend(struct pci_dev *dev, pm_message_t pm_state) +{ + struct i810_card *card = pci_get_drvdata(dev); + struct i810_state *state; + unsigned long flags; + struct dmabuf *dmabuf; + int i,num_ac97; +#ifdef DEBUG + printk("i810_audio: i810_pm_suspend called\n"); +#endif + if(!card) return 0; + spin_lock_irqsave(&card->lock, flags); + card->pm_suspended=1; + for(i=0;istates[i]; + if(!state) continue; + /* this happens only if there are open files */ + dmabuf = &state->dmabuf; + if(dmabuf->enable & DAC_RUNNING || + (dmabuf->count && (dmabuf->trigger & PCM_ENABLE_OUTPUT))) { + state->pm_saved_dac_rate=dmabuf->rate; + stop_dac(state); + } else { + state->pm_saved_dac_rate=0; + } + if(dmabuf->enable & ADC_RUNNING) { + state->pm_saved_adc_rate=dmabuf->rate; + stop_adc(state); + } else { + state->pm_saved_adc_rate=0; + } + dmabuf->ready = 0; + dmabuf->swptr = dmabuf->hwptr = 0; + dmabuf->count = dmabuf->total_bytes = 0; + } + + spin_unlock_irqrestore(&card->lock, flags); + + /* save mixer settings */ + for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) { + struct ac97_codec *codec = card->ac97_codec[num_ac97]; + if(!codec) continue; + for(i=0;i< SOUND_MIXER_NRDEVICES ;i++) { + if((supported_mixer(codec,i)) && + (codec->read_mixer)) { + card->pm_saved_mixer_settings[i][num_ac97]= + codec->read_mixer(codec,i); + } + } + } + pci_save_state(dev); /* XXX do we need this? */ + pci_disable_device(dev); /* disable busmastering */ + pci_set_power_state(dev,3); /* Zzz. */ + + return 0; +} + + +static int i810_pm_resume(struct pci_dev *dev) +{ + int num_ac97,i=0; + struct i810_card *card=pci_get_drvdata(dev); + pci_enable_device(dev); + pci_restore_state (dev); + + /* observation of a toshiba portege 3440ct suggests that the + hardware has to be more or less completely reinitialized from + scratch after an apm suspend. Works For Me. -dan */ + + i810_ac97_power_up_bus(card); + + for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) { + struct ac97_codec *codec = card->ac97_codec[num_ac97]; + /* check they haven't stolen the hardware while we were + away */ + if(!codec || !i810_ac97_exists(card,num_ac97)) { + if(num_ac97) continue; + else BUG(); + } + if(!i810_ac97_probe_and_powerup(card,codec)) BUG(); + + if((card->ac97_features&0x0001)) { + /* at probe time we found we could do variable + rates, but APM suspend has made it forget + its magical powers */ + if(!i810_ac97_enable_variable_rate(codec)) BUG(); + } + /* we lost our mixer settings, so restore them */ + for(i=0;i< SOUND_MIXER_NRDEVICES ;i++) { + if(supported_mixer(codec,i)){ + int val=card-> + pm_saved_mixer_settings[i][num_ac97]; + codec->mixer_state[i]=val; + codec->write_mixer(codec,i, + (val & 0xff) , + ((val >> 8) & 0xff) ); + } + } + } + + /* we need to restore the sample rate from whatever it was */ + for(i=0;istates[i]; + if(state) { + if(state->pm_saved_adc_rate) + i810_set_adc_rate(state,state->pm_saved_adc_rate); + if(state->pm_saved_dac_rate) + i810_set_dac_rate(state,state->pm_saved_dac_rate); + } + } + + + card->pm_suspended = 0; + + /* any processes that were reading/writing during the suspend + probably ended up here */ + for(i=0;istates[i]; + if(state) wake_up(&state->dmabuf.wait); + } + + return 0; +} +#endif /* CONFIG_PM */ + +MODULE_AUTHOR("The Linux kernel team"); +MODULE_DESCRIPTION("Intel 810 audio support"); +MODULE_LICENSE("GPL"); +module_param(ftsodell, int, 0444); +module_param(clocking, uint, 0444); +module_param(strict_clocking, int, 0444); +module_param(spdif_locked, int, 0444); + +#define I810_MODULE_NAME "i810_audio" + +static struct pci_driver i810_pci_driver = { + .name = I810_MODULE_NAME, + .id_table = i810_pci_tbl, + .probe = i810_probe, + .remove = __devexit_p(i810_remove), +#ifdef CONFIG_PM + .suspend = i810_pm_suspend, + .resume = i810_pm_resume, +#endif /* CONFIG_PM */ +}; + + +static int __init i810_init_module (void) +{ + int retval; + + printk(KERN_INFO "Intel 810 + AC97 Audio, version " + DRIVER_VERSION ", " __TIME__ " " __DATE__ "\n"); + + retval = pci_register_driver(&i810_pci_driver); + if (retval) + return retval; + + if(ftsodell != 0) { + printk("i810_audio: ftsodell is now a deprecated option.\n"); + } + if(spdif_locked > 0 ) { + if(spdif_locked == 32000 || spdif_locked == 44100 || spdif_locked == 48000) { + printk("i810_audio: Enabling S/PDIF at sample rate %dHz.\n", spdif_locked); + } else { + printk("i810_audio: S/PDIF can only be locked to 32000, 44100, or 48000Hz.\n"); + spdif_locked = 0; + } + } + + return 0; +} + +static void __exit i810_cleanup_module (void) +{ + pci_unregister_driver(&i810_pci_driver); +} + +module_init(i810_init_module); +module_exit(i810_cleanup_module); + +/* +Local Variables: +c-basic-offset: 8 +End: +*/ diff --git a/sound/oss/ics2101.c b/sound/oss/ics2101.c new file mode 100644 index 000000000000..d5f3be8550f3 --- /dev/null +++ b/sound/oss/ics2101.c @@ -0,0 +1,247 @@ +/* + * sound/ics2101.c + * + * Driver for the ICS2101 mixer of GUS v3.7. + * + * + * 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 ics2101_mixer_init() + */ +#include +#include +#include "sound_config.h" + +#include + +#include "gus.h" +#include "gus_hw.h" + +#define MIX_DEVS (SOUND_MASK_MIC|SOUND_MASK_LINE| \ + SOUND_MASK_SYNTH| \ + SOUND_MASK_CD | SOUND_MASK_VOLUME) + +extern int *gus_osp; +extern int gus_base; +extern spinlock_t gus_lock; +static int volumes[ICS_MIXDEVS]; +static int left_fix[ICS_MIXDEVS] = +{1, 1, 1, 2, 1, 2}; +static int right_fix[ICS_MIXDEVS] = +{2, 2, 2, 1, 2, 1}; + +static int scale_vol(int vol) +{ + /* + * Experimental volume scaling by Risto Kankkunen. + * This should give smoother volume response than just + * a plain multiplication. + */ + + int e; + + if (vol < 0) + vol = 0; + if (vol > 100) + vol = 100; + vol = (31 * vol + 50) / 100; + e = 0; + if (vol) + { + while (vol < 16) + { + vol <<= 1; + e--; + } + vol -= 16; + e += 7; + } + return ((e << 4) + vol); +} + +static void write_mix(int dev, int chn, int vol) +{ + int *selector; + unsigned long flags; + int ctrl_addr = dev << 3; + int attn_addr = dev << 3; + + vol = scale_vol(vol); + + if (chn == CHN_LEFT) + { + selector = left_fix; + ctrl_addr |= 0x00; + attn_addr |= 0x02; + } + else + { + selector = right_fix; + ctrl_addr |= 0x01; + attn_addr |= 0x03; + } + + spin_lock_irqsave(&gus_lock, flags); + outb((ctrl_addr), u_MixSelect); + outb((selector[dev]), u_MixData); + outb((attn_addr), u_MixSelect); + outb(((unsigned char) vol), u_MixData); + spin_unlock_irqrestore(&gus_lock,flags); +} + +static int set_volumes(int dev, int vol) +{ + int left = vol & 0x00ff; + int right = (vol >> 8) & 0x00ff; + + if (left < 0) + left = 0; + if (left > 100) + left = 100; + if (right < 0) + right = 0; + if (right > 100) + right = 100; + + write_mix(dev, CHN_LEFT, left); + write_mix(dev, CHN_RIGHT, right); + + vol = left + (right << 8); + volumes[dev] = vol; + return vol; +} + +static int ics2101_mixer_ioctl(int dev, unsigned int cmd, void __user *arg) +{ + int val; + + if (((cmd >> 8) & 0xff) == 'M') { + if (_SIOC_DIR(cmd) & _SIOC_WRITE) { + + if (get_user(val, (int __user *)arg)) + return -EFAULT; + switch (cmd & 0xff) { + case SOUND_MIXER_RECSRC: + return gus_default_mixer_ioctl(dev, cmd, arg); + + case SOUND_MIXER_MIC: + val = set_volumes(DEV_MIC, val); + break; + + case SOUND_MIXER_CD: + val = set_volumes(DEV_CD, val); + break; + + case SOUND_MIXER_LINE: + val = set_volumes(DEV_LINE, val); + break; + + case SOUND_MIXER_SYNTH: + val = set_volumes(DEV_GF1, val); + break; + + case SOUND_MIXER_VOLUME: + val = set_volumes(DEV_VOL, val); + break; + + default: + return -EINVAL; + } + return put_user(val, (int __user *)arg); + } else { + switch (cmd & 0xff) { + /* + * Return parameters + */ + case SOUND_MIXER_RECSRC: + return gus_default_mixer_ioctl(dev, cmd, arg); + + case SOUND_MIXER_DEVMASK: + val = MIX_DEVS; + break; + + case SOUND_MIXER_STEREODEVS: + val = SOUND_MASK_LINE | SOUND_MASK_CD | SOUND_MASK_SYNTH | SOUND_MASK_VOLUME | SOUND_MASK_MIC; + break; + + case SOUND_MIXER_RECMASK: + val = SOUND_MASK_MIC | SOUND_MASK_LINE; + break; + + case SOUND_MIXER_CAPS: + val = 0; + break; + + case SOUND_MIXER_MIC: + val = volumes[DEV_MIC]; + break; + + case SOUND_MIXER_LINE: + val = volumes[DEV_LINE]; + break; + + case SOUND_MIXER_CD: + val = volumes[DEV_CD]; + break; + + case SOUND_MIXER_VOLUME: + val = volumes[DEV_VOL]; + break; + + case SOUND_MIXER_SYNTH: + val = volumes[DEV_GF1]; + break; + + default: + return -EINVAL; + } + return put_user(val, (int __user *)arg); + } + } + return -EINVAL; +} + +static struct mixer_operations ics2101_mixer_operations = +{ + .owner = THIS_MODULE, + .id = "ICS2101", + .name = "ICS2101 Multimedia Mixer", + .ioctl = ics2101_mixer_ioctl +}; + +int __init ics2101_mixer_init(void) +{ + int i; + int n; + + if ((n = sound_alloc_mixerdev()) != -1) + { + mixer_devs[n] = &ics2101_mixer_operations; + + /* + * Some GUS v3.7 cards had some channels flipped. Disable + * the flipping feature if the model id is other than 5. + */ + + if (inb(u_MixSelect) != 5) + { + for (i = 0; i < ICS_MIXDEVS; i++) + left_fix[i] = 1; + for (i = 0; i < ICS_MIXDEVS; i++) + right_fix[i] = 2; + } + set_volumes(DEV_GF1, 0x5a5a); + set_volumes(DEV_CD, 0x5a5a); + set_volumes(DEV_MIC, 0x0000); + set_volumes(DEV_LINE, 0x5a5a); + set_volumes(DEV_VOL, 0x5a5a); + set_volumes(DEV_UNUSED, 0x0000); + } + return n; +} diff --git a/sound/oss/ite8172.c b/sound/oss/ite8172.c new file mode 100644 index 000000000000..58f879fda975 --- /dev/null +++ b/sound/oss/ite8172.c @@ -0,0 +1,2259 @@ +/* + * ite8172.c -- ITE IT8172G Sound Driver. + * + * Copyright 2001 MontaVista Software Inc. + * Author: MontaVista Software, Inc. + * stevel@mvista.com or source@mvista.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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: + * + * Supported devices: + * /dev/dsp standard OSS /dev/dsp device + * /dev/mixer standard OSS /dev/mixer device + * + * Notes: + * + * 1. Much of the OSS buffer allocation, ioctl's, and mmap'ing are + * taken, slightly modified or not at all, from the ES1371 driver, + * so refer to the credits in es1371.c for those. The rest of the + * code (probe, open, read, write, the ISR, etc.) is new. + * 2. The following support is untested: + * * Memory mapping the audio buffers, and the ioctl controls that go + * with it. + * * S/PDIF output. + * * I2S support. + * 3. The following is not supported: + * * legacy audio mode. + * 4. Support for volume button interrupts is implemented but doesn't + * work yet. + * + * Revision history + * 02.08.2001 Initial release + * 06.22.2001 Added I2S support + * 07.30.2003 Removed initialisation to zero for static variables + * (spdif[NR_DEVICE], i2s_fmt[NR_DEVICE], and devindex) + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* --------------------------------------------------------------------- */ + +#undef OSS_DOCUMENTED_MIXER_SEMANTICS +#define IT8172_DEBUG +#undef IT8172_VERBOSE_DEBUG +#define DBG(x) {} + +#define IT8172_MODULE_NAME "IT8172 audio" +#define PFX IT8172_MODULE_NAME + +#ifdef IT8172_DEBUG +#define dbg(format, arg...) printk(KERN_DEBUG PFX ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif +#define err(format, arg...) printk(KERN_ERR PFX ": " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO PFX ": " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING PFX ": " format "\n" , ## arg) + + +#define IT8172_MODULE_NAME "IT8172 audio" +#define PFX IT8172_MODULE_NAME + +#ifdef IT8172_DEBUG +#define dbg(format, arg...) printk(KERN_DEBUG PFX ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif +#define err(format, arg...) printk(KERN_ERR PFX ": " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO PFX ": " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING PFX ": " format "\n" , ## arg) + + +static const unsigned sample_shift[] = { 0, 1, 1, 2 }; + + +/* + * Audio Controller register bit definitions follow. See + * include/asm/it8172/it8172.h for register offsets. + */ + +/* PCM Out Volume Reg */ +#define PCMOV_PCMOM (1<<15) /* PCM Out Mute default 1: mute */ +#define PCMOV_PCMRCG_BIT 8 /* PCM Right channel Gain */ +#define PCMOV_PCMRCG_MASK (0x1f<= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +/* --------------------------------------------------------------------- */ + +static void it8172_delay(int msec) +{ + unsigned long tmo; + signed long tmo2; + + if (in_interrupt()) + return; + + tmo = jiffies + (msec*HZ)/1000; + for (;;) { + tmo2 = tmo - jiffies; + if (tmo2 <= 0) + break; + schedule_timeout(tmo2); + } +} + + +static unsigned short +get_compat_rate(unsigned* rate) +{ + unsigned rate_out = *rate; + unsigned short sr; + + if (rate_out >= 46050) { + sr = CC_SR_48000; rate_out = 48000; + } else if (rate_out >= 41250) { + sr = CC_SR_44100; rate_out = 44100; + } else if (rate_out >= 35200) { + sr = CC_SR_38400; rate_out = 38400; + } else if (rate_out >= 27025) { + sr = CC_SR_32000; rate_out = 32000; + } else if (rate_out >= 20625) { + sr = CC_SR_22050; rate_out = 22050; + } else if (rate_out >= 17600) { + sr = CC_SR_19200; rate_out = 19200; + } else if (rate_out >= 13513) { + sr = CC_SR_16000; rate_out = 16000; + } else if (rate_out >= 10313) { + sr = CC_SR_11025; rate_out = 11025; + } else if (rate_out >= 8800) { + sr = CC_SR_9600; rate_out = 9600; + } else if (rate_out >= 6750) { + sr = CC_SR_8000; rate_out = 8000; + } else { + sr = CC_SR_5500; rate_out = 5500; + } + + *rate = rate_out; + return sr; +} + +static void set_adc_rate(struct it8172_state *s, unsigned rate) +{ + unsigned long flags; + unsigned short sr; + + sr = get_compat_rate(&rate); + + spin_lock_irqsave(&s->lock, flags); + s->capcc &= ~CC_SR_MASK; + s->capcc |= sr; + outw(s->capcc, s->io+IT_AC_CAPCC); + spin_unlock_irqrestore(&s->lock, flags); + + s->adcrate = rate; +} + + +static void set_dac_rate(struct it8172_state *s, unsigned rate) +{ + unsigned long flags; + unsigned short sr; + + sr = get_compat_rate(&rate); + + spin_lock_irqsave(&s->lock, flags); + s->pcc &= ~CC_SR_MASK; + s->pcc |= sr; + outw(s->pcc, s->io+IT_AC_PCC); + spin_unlock_irqrestore(&s->lock, flags); + + s->dacrate = rate; +} + + +/* --------------------------------------------------------------------- */ + +static u16 rdcodec(struct ac97_codec *codec, u8 addr) +{ + struct it8172_state *s = (struct it8172_state *)codec->private_data; + unsigned long flags; + unsigned short circp, data; + int i; + + spin_lock_irqsave(&s->lock, flags); + + for (i = 0; i < POLL_COUNT; i++) + if (!(inw(s->io+IT_AC_CIRCP) & CIRCP_CPS)) + break; + if (i == POLL_COUNT) + err("rdcodec: codec ready poll expired!"); + + circp = addr & CIRCP_CIA_MASK; + circp |= (codec->id << CIRCP_CID_BIT); + circp |= CIRCP_RWC; // read command + outw(circp, s->io+IT_AC_CIRCP); + + /* now wait for the data */ + for (i = 0; i < POLL_COUNT; i++) + if (inw(s->io+IT_AC_CIRCP) & CIRCP_DPVF) + break; + if (i == POLL_COUNT) + err("rdcodec: read poll expired!"); + + data = inw(s->io+IT_AC_CIRDP); + spin_unlock_irqrestore(&s->lock, flags); + + return data; +} + + +static void wrcodec(struct ac97_codec *codec, u8 addr, u16 data) +{ + struct it8172_state *s = (struct it8172_state *)codec->private_data; + unsigned long flags; + unsigned short circp; + int i; + + spin_lock_irqsave(&s->lock, flags); + + for (i = 0; i < POLL_COUNT; i++) + if (!(inw(s->io+IT_AC_CIRCP) & CIRCP_CPS)) + break; + if (i == POLL_COUNT) + err("wrcodec: codec ready poll expired!"); + + circp = addr & CIRCP_CIA_MASK; + circp |= (codec->id << CIRCP_CID_BIT); + circp &= ~CIRCP_RWC; // write command + + outw(data, s->io+IT_AC_CIRDP); // send data first + outw(circp, s->io+IT_AC_CIRCP); + + spin_unlock_irqrestore(&s->lock, flags); +} + + +static void waitcodec(struct ac97_codec *codec) +{ + unsigned short temp; + + /* codec_wait is used to wait for a ready state after + an AC97_RESET. */ + it8172_delay(10); + + temp = rdcodec(codec, 0x26); + + // If power down, power up + if (temp & 0x3f00) { + // Power on + wrcodec(codec, 0x26, 0); + it8172_delay(100); + // Reread + temp = rdcodec(codec, 0x26); + } + + // Check if Codec REF,ANL,DAC,ADC ready***/ + if ((temp & 0x3f0f) != 0x000f) { + err("codec reg 26 status (0x%x) not ready!!", temp); + return; + } +} + + +/* --------------------------------------------------------------------- */ + +static inline void stop_adc(struct it8172_state *s) +{ + struct dmabuf* db = &s->dma_adc; + unsigned long flags; + unsigned char imc; + + if (db->stopped) + return; + + spin_lock_irqsave(&s->lock, flags); + + s->capcc &= ~(CC_CA | CC_CP | CC_CB2L | CC_CB1L); + s->capcc |= CC_CSP; + outw(s->capcc, s->io+IT_AC_CAPCC); + + // disable capture interrupt + imc = inb(s->io+IT_AC_IMC); + outb(imc | IMC_CCIM, s->io+IT_AC_IMC); + + db->stopped = 1; + + spin_unlock_irqrestore(&s->lock, flags); +} + +static inline void stop_dac(struct it8172_state *s) +{ + struct dmabuf* db = &s->dma_dac; + unsigned long flags; + unsigned char imc; + + if (db->stopped) + return; + + spin_lock_irqsave(&s->lock, flags); + + s->pcc &= ~(CC_CA | CC_CP | CC_CB2L | CC_CB1L); + s->pcc |= CC_CSP; + outw(s->pcc, s->io+IT_AC_PCC); + + // disable playback interrupt + imc = inb(s->io+IT_AC_IMC); + outb(imc | IMC_PCIM, s->io+IT_AC_IMC); + + db->stopped = 1; + + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_dac(struct it8172_state *s) +{ + struct dmabuf* db = &s->dma_dac; + unsigned long flags; + unsigned char imc; + unsigned long buf1, buf2; + + if (!db->stopped) + return; + + spin_lock_irqsave(&s->lock, flags); + + // reset Buffer 1 and 2 pointers to nextOut and nextOut+fragsize + buf1 = virt_to_bus(db->nextOut); + buf2 = buf1 + db->fragsize; + if (buf2 >= db->dmaaddr + db->dmasize) + buf2 -= db->dmasize; + + outl(buf1, s->io+IT_AC_PCB1STA); + outl(buf2, s->io+IT_AC_PCB2STA); + db->curBufPtr = IT_AC_PCB1STA; + + // enable playback interrupt + imc = inb(s->io+IT_AC_IMC); + outb(imc & ~IMC_PCIM, s->io+IT_AC_IMC); + + s->pcc &= ~(CC_CSP | CC_CP | CC_CB2L | CC_CB1L); + s->pcc |= CC_CA; + outw(s->pcc, s->io+IT_AC_PCC); + + db->stopped = 0; + + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_adc(struct it8172_state *s) +{ + struct dmabuf* db = &s->dma_adc; + unsigned long flags; + unsigned char imc; + unsigned long buf1, buf2; + + if (!db->stopped) + return; + + spin_lock_irqsave(&s->lock, flags); + + // reset Buffer 1 and 2 pointers to nextIn and nextIn+fragsize + buf1 = virt_to_bus(db->nextIn); + buf2 = buf1 + db->fragsize; + if (buf2 >= db->dmaaddr + db->dmasize) + buf2 -= db->dmasize; + + outl(buf1, s->io+IT_AC_CAPB1STA); + outl(buf2, s->io+IT_AC_CAPB2STA); + db->curBufPtr = IT_AC_CAPB1STA; + + // enable capture interrupt + imc = inb(s->io+IT_AC_IMC); + outb(imc & ~IMC_CCIM, s->io+IT_AC_IMC); + + s->capcc &= ~(CC_CSP | CC_CP | CC_CB2L | CC_CB1L); + s->capcc |= CC_CA; + outw(s->capcc, s->io+IT_AC_CAPCC); + + db->stopped = 0; + + spin_unlock_irqrestore(&s->lock, flags); +} + +/* --------------------------------------------------------------------- */ + +#define DMABUF_DEFAULTORDER (17-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +static inline void dealloc_dmabuf(struct it8172_state *s, struct dmabuf *db) +{ + struct page *page, *pend; + + if (db->rawbuf) { + /* undo marking the pages as reserved */ + pend = virt_to_page(db->rawbuf + + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + ClearPageReserved(page); + pci_free_consistent(s->dev, PAGE_SIZE << db->buforder, + db->rawbuf, db->dmaaddr); + } + db->rawbuf = db->nextIn = db->nextOut = NULL; + db->mapped = db->ready = 0; +} + +static int prog_dmabuf(struct it8172_state *s, struct dmabuf *db, + unsigned rate, unsigned fmt, unsigned reg) +{ + int order; + unsigned bytepersec; + unsigned bufs; + struct page *page, *pend; + + if (!db->rawbuf) { + db->ready = db->mapped = 0; + for (order = DMABUF_DEFAULTORDER; + order >= DMABUF_MINORDER; order--) + if ((db->rawbuf = + pci_alloc_consistent(s->dev, + PAGE_SIZE << order, + &db->dmaaddr))) + break; + if (!db->rawbuf) + return -ENOMEM; + db->buforder = order; + /* now mark the pages as reserved; + otherwise remap_pfn_range doesn't do what we want */ + pend = virt_to_page(db->rawbuf + + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + SetPageReserved(page); + } + + db->count = 0; + db->nextIn = db->nextOut = db->rawbuf; + + bytepersec = rate << sample_shift[fmt]; + bufs = PAGE_SIZE << db->buforder; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < bytepersec) + db->fragshift = ld2(bytepersec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(bytepersec/100/(db->subdivision ? + db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + db->numfrag = bufs >> db->fragshift; + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->numfrag = bufs >> db->fragshift; + } + db->fragsize = 1 << db->fragshift; + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; + db->fragsamples = db->fragsize >> sample_shift[fmt]; + db->dmasize = db->numfrag << db->fragshift; + memset(db->rawbuf, (fmt & (CC_DF>>CC_FMT_BIT)) ? 0 : 0x80, bufs); + +#ifdef IT8172_VERBOSE_DEBUG + dbg("rate=%d, fragsize=%d, numfrag=%d, dmasize=%d", + rate, db->fragsize, db->numfrag, db->dmasize); +#endif + + // set data length register + outw(db->fragsize, s->io+reg+2); + db->ready = 1; + + return 0; +} + +static inline int prog_dmabuf_adc(struct it8172_state *s) +{ + stop_adc(s); + return prog_dmabuf(s, &s->dma_adc, s->adcrate, + (s->capcc & CC_FMT_MASK) >> CC_FMT_BIT, + IT_AC_CAPCC); +} + +static inline int prog_dmabuf_dac(struct it8172_state *s) +{ + stop_dac(s); + return prog_dmabuf(s, &s->dma_dac, s->dacrate, + (s->pcc & CC_FMT_MASK) >> CC_FMT_BIT, + IT_AC_PCC); +} + + +/* hold spinlock for the following! */ + +static irqreturn_t it8172_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct it8172_state *s = (struct it8172_state *)dev_id; + struct dmabuf* dac = &s->dma_dac; + struct dmabuf* adc = &s->dma_adc; + unsigned char isc, vs; + unsigned short vol, mute; + unsigned long newptr; + + spin_lock(&s->lock); + + isc = inb(s->io+IT_AC_ISC); + + /* fastpath out, to ease interrupt sharing */ + if (!(isc & (ISC_VCI | ISC_CCI | ISC_PCI))) { + spin_unlock(&s->lock); + return IRQ_NONE; + } + + /* clear audio interrupts first */ + outb(isc | ISC_VCI | ISC_CCI | ISC_PCI, s->io+IT_AC_ISC); + + /* handle volume button events (ignore if S/PDIF enabled) */ + if ((isc & ISC_VCI) && s->spdif_volume == -1) { + vs = inb(s->io+IT_AC_VS); + outb(0, s->io+IT_AC_VS); + vol = inw(s->io+IT_AC_PCMOV); + mute = vol & PCMOV_PCMOM; + vol &= PCMOV_PCMLCG_MASK; + if ((vs & VS_VUP) && vol > 0) + vol--; + if ((vs & VS_VDP) && vol < 0x1f) + vol++; + vol |= (vol << PCMOV_PCMRCG_BIT); + if (vs & VS_VMP) + vol |= (mute ^ PCMOV_PCMOM); + outw(vol, s->io+IT_AC_PCMOV); + } + + /* update capture pointers */ + if (isc & ISC_CCI) { + if (adc->count > adc->dmasize - adc->fragsize) { + // Overrun. Stop ADC and log the error + stop_adc(s); + adc->error++; + dbg("adc overrun"); + } else { + newptr = virt_to_bus(adc->nextIn) + 2*adc->fragsize; + if (newptr >= adc->dmaaddr + adc->dmasize) + newptr -= adc->dmasize; + + outl(newptr, s->io+adc->curBufPtr); + adc->curBufPtr = (adc->curBufPtr == IT_AC_CAPB1STA) ? + IT_AC_CAPB2STA : IT_AC_CAPB1STA; + + adc->nextIn += adc->fragsize; + if (adc->nextIn >= adc->rawbuf + adc->dmasize) + adc->nextIn -= adc->dmasize; + + adc->count += adc->fragsize; + adc->total_bytes += adc->fragsize; + + /* wake up anybody listening */ + if (waitqueue_active(&adc->wait)) + wake_up_interruptible(&adc->wait); + } + } + + /* update playback pointers */ + if (isc & ISC_PCI) { + newptr = virt_to_bus(dac->nextOut) + 2*dac->fragsize; + if (newptr >= dac->dmaaddr + dac->dmasize) + newptr -= dac->dmasize; + + outl(newptr, s->io+dac->curBufPtr); + dac->curBufPtr = (dac->curBufPtr == IT_AC_PCB1STA) ? + IT_AC_PCB2STA : IT_AC_PCB1STA; + + dac->nextOut += dac->fragsize; + if (dac->nextOut >= dac->rawbuf + dac->dmasize) + dac->nextOut -= dac->dmasize; + + dac->count -= dac->fragsize; + dac->total_bytes += dac->fragsize; + + /* wake up anybody listening */ + if (waitqueue_active(&dac->wait)) + wake_up_interruptible(&dac->wait); + + if (dac->count <= 0) + stop_dac(s); + } + + spin_unlock(&s->lock); + return IRQ_HANDLED; +} + +/* --------------------------------------------------------------------- */ + +static int it8172_open_mixdev(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct list_head *list; + struct it8172_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct it8172_state, devs); + if (s->codec.dev_mixer == minor) + break; + } + file->private_data = s; + return nonseekable_open(inode, file); +} + +static int it8172_release_mixdev(struct inode *inode, struct file *file) +{ + return 0; +} + + +static u16 +cvt_ossvol(unsigned int gain) +{ + u16 ret; + + if (gain == 0) + return 0; + + if (gain > 100) + gain = 100; + + ret = (100 - gain + 32) / 4; + ret = ret > 31 ? 31 : ret; + return ret; +} + + +static int mixdev_ioctl(struct ac97_codec *codec, unsigned int cmd, + unsigned long arg) +{ + struct it8172_state *s = (struct it8172_state *)codec->private_data; + unsigned int left, right; + unsigned long flags; + int val; + u16 vol; + + /* + * When we are in S/PDIF mode, we want to disable any analog output so + * we filter the master/PCM channel volume ioctls. + * + * Also filter I2S channel, which AC'97 knows nothing about. + */ + + switch (cmd) { + case SOUND_MIXER_WRITE_VOLUME: + // if not in S/PDIF mode, pass to AC'97 + if (s->spdif_volume == -1) + break; + return 0; + case SOUND_MIXER_WRITE_PCM: + // if not in S/PDIF mode, pass to AC'97 + if (s->spdif_volume == -1) + break; + if (get_user(val, (int *)arg)) + return -EFAULT; + right = ((val >> 8) & 0xff); + left = (val & 0xff); + if (right > 100) + right = 100; + if (left > 100) + left = 100; + s->spdif_volume = (right << 8) | left; + vol = cvt_ossvol(left); + vol |= (cvt_ossvol(right) << PCMOV_PCMRCG_BIT); + if (vol == 0) + vol = PCMOV_PCMOM; // mute + spin_lock_irqsave(&s->lock, flags); + outw(vol, s->io+IT_AC_PCMOV); + spin_unlock_irqrestore(&s->lock, flags); + return put_user(s->spdif_volume, (int *)arg); + case SOUND_MIXER_READ_PCM: + // if not in S/PDIF mode, pass to AC'97 + if (s->spdif_volume == -1) + break; + return put_user(s->spdif_volume, (int *)arg); + case SOUND_MIXER_WRITE_I2S: + if (get_user(val, (int *)arg)) + return -EFAULT; + right = ((val >> 8) & 0xff); + left = (val & 0xff); + if (right > 100) + right = 100; + if (left > 100) + left = 100; + s->i2s_volume = (right << 8) | left; + vol = cvt_ossvol(left); + vol |= (cvt_ossvol(right) << I2SV_I2SRCG_BIT); + if (vol == 0) + vol = I2SV_I2SOM; // mute + outw(vol, s->io+IT_AC_I2SV); + return put_user(s->i2s_volume, (int *)arg); + case SOUND_MIXER_READ_I2S: + return put_user(s->i2s_volume, (int *)arg); + case SOUND_MIXER_WRITE_RECSRC: + if (get_user(val, (int *)arg)) + return -EFAULT; + if (val & SOUND_MASK_I2S) { + s->i2s_recording = 1; + outb(DRSS_I2S, s->io+IT_AC_DRSS); + return 0; + } else { + s->i2s_recording = 0; + outb(DRSS_AC97_PRIM, s->io+IT_AC_DRSS); + // now let AC'97 select record source + break; + } + case SOUND_MIXER_READ_RECSRC: + if (s->i2s_recording) + return put_user(SOUND_MASK_I2S, (int *)arg); + else + // let AC'97 report recording source + break; + } + + return codec->mixer_ioctl(codec, cmd, arg); +} + +static int it8172_ioctl_mixdev(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct it8172_state *s = (struct it8172_state *)file->private_data; + struct ac97_codec *codec = &s->codec; + + return mixdev_ioctl(codec, cmd, arg); +} + +static /*const*/ struct file_operations it8172_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = it8172_ioctl_mixdev, + .open = it8172_open_mixdev, + .release = it8172_release_mixdev, +}; + +/* --------------------------------------------------------------------- */ + +static int drain_dac(struct it8172_state *s, int nonblock) +{ + unsigned long flags; + int count, tmo; + + if (s->dma_dac.mapped || !s->dma_dac.ready || s->dma_dac.stopped) + return 0; + + for (;;) { + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + //if (nonblock) + //return -EBUSY; + tmo = 1000 * count / s->dacrate; + tmo >>= sample_shift[(s->pcc & CC_FMT_MASK) >> CC_FMT_BIT]; + it8172_delay(tmo); + } + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +/* --------------------------------------------------------------------- */ + + +/* + * Copy audio data to/from user buffer from/to dma buffer, taking care + * that we wrap when reading/writing the dma buffer. Returns actual byte + * count written to or read from the dma buffer. + */ +static int copy_dmabuf_user(struct dmabuf *db, char* userbuf, + int count, int to_user) +{ + char* bufptr = to_user ? db->nextOut : db->nextIn; + char* bufend = db->rawbuf + db->dmasize; + + if (bufptr + count > bufend) { + int partial = (int)(bufend - bufptr); + if (to_user) { + if (copy_to_user(userbuf, bufptr, partial)) + return -EFAULT; + if (copy_to_user(userbuf + partial, db->rawbuf, + count - partial)) + return -EFAULT; + } else { + if (copy_from_user(bufptr, userbuf, partial)) + return -EFAULT; + if (copy_from_user(db->rawbuf, + userbuf + partial, + count - partial)) + return -EFAULT; + } + } else { + if (to_user) { + if (copy_to_user(userbuf, bufptr, count)) + return -EFAULT; + } else { + if (copy_from_user(bufptr, userbuf, count)) + return -EFAULT; + } + } + + return count; +} + + +static ssize_t it8172_read(struct file *file, char *buffer, + size_t count, loff_t *ppos) +{ + struct it8172_state *s = (struct it8172_state *)file->private_data; + struct dmabuf *db = &s->dma_adc; + ssize_t ret; + unsigned long flags; + int cnt, remainder, avail; + + if (db->mapped) + return -ENXIO; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; + + while (count > 0) { + // wait for samples in capture buffer + do { + spin_lock_irqsave(&s->lock, flags); + if (db->stopped) + start_adc(s); + avail = db->count; + spin_unlock_irqrestore(&s->lock, flags); + if (avail <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + return ret; + } + interruptible_sleep_on(&db->wait); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + return ret; + } + } + } while (avail <= 0); + + // copy from nextOut to user + if ((cnt = copy_dmabuf_user(db, buffer, count > avail ? + avail : count, 1)) < 0) { + if (!ret) + ret = -EFAULT; + return ret; + } + + spin_lock_irqsave(&s->lock, flags); + db->count -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + + db->nextOut += cnt; + if (db->nextOut >= db->rawbuf + db->dmasize) + db->nextOut -= db->dmasize; + + count -= cnt; + buffer += cnt; + ret += cnt; + } // while (count > 0) + + /* + * See if the dma buffer count after this read call is + * aligned on a fragsize boundary. If not, read from + * buffer until we reach a boundary, and let's hope this + * is just the last remainder of an audio record. If not + * it means the user is not reading in fragsize chunks, in + * which case it's his/her fault that there are audio gaps + * in their record. + */ + spin_lock_irqsave(&s->lock, flags); + remainder = db->count % db->fragsize; + if (remainder) { + db->nextOut += remainder; + if (db->nextOut >= db->rawbuf + db->dmasize) + db->nextOut -= db->dmasize; + db->count -= remainder; + } + spin_unlock_irqrestore(&s->lock, flags); + + return ret; +} + +static ssize_t it8172_write(struct file *file, const char *buffer, + size_t count, loff_t *ppos) +{ + struct it8172_state *s = (struct it8172_state *)file->private_data; + struct dmabuf *db = &s->dma_dac; + ssize_t ret; + unsigned long flags; + int cnt, remainder, avail; + + if (db->mapped) + return -ENXIO; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; + + while (count > 0) { + // wait for space in playback buffer + do { + spin_lock_irqsave(&s->lock, flags); + avail = db->dmasize - db->count; + spin_unlock_irqrestore(&s->lock, flags); + if (avail <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + return ret; + } + interruptible_sleep_on(&db->wait); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + return ret; + } + } + } while (avail <= 0); + + // copy to nextIn + if ((cnt = copy_dmabuf_user(db, (char*)buffer, + count > avail ? + avail : count, 0)) < 0) { + if (!ret) + ret = -EFAULT; + return ret; + } + + spin_lock_irqsave(&s->lock, flags); + db->count += cnt; + if (db->stopped) + start_dac(s); + spin_unlock_irqrestore(&s->lock, flags); + + db->nextIn += cnt; + if (db->nextIn >= db->rawbuf + db->dmasize) + db->nextIn -= db->dmasize; + + count -= cnt; + buffer += cnt; + ret += cnt; + } // while (count > 0) + + /* + * See if the dma buffer count after this write call is + * aligned on a fragsize boundary. If not, fill buffer + * with silence to the next boundary, and let's hope this + * is just the last remainder of an audio playback. If not + * it means the user is not sending us fragsize chunks, in + * which case it's his/her fault that there are audio gaps + * in their playback. + */ + spin_lock_irqsave(&s->lock, flags); + remainder = db->count % db->fragsize; + if (remainder) { + int fill_cnt = db->fragsize - remainder; + memset(db->nextIn, 0, fill_cnt); + db->nextIn += fill_cnt; + if (db->nextIn >= db->rawbuf + db->dmasize) + db->nextIn -= db->dmasize; + db->count += fill_cnt; + } + spin_unlock_irqrestore(&s->lock, flags); + + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int it8172_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct it8172_state *s = (struct it8172_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + if (file->f_mode & FMODE_WRITE) { + if (!s->dma_dac.ready) + return 0; + poll_wait(file, &s->dma_dac.wait, wait); + } + if (file->f_mode & FMODE_READ) { + if (!s->dma_adc.ready) + return 0; + poll_wait(file, &s->dma_adc.wait, wait); + } + + spin_lock_irqsave(&s->lock, flags); + if (file->f_mode & FMODE_READ) { + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->dma_dac.mapped) { + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)s->dma_dac.dmasize >= + s->dma_dac.count + (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int it8172_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct it8172_state *s = (struct it8172_state *)file->private_data; + struct dmabuf *db; + unsigned long size; + + lock_kernel(); + if (vma->vm_flags & VM_WRITE) + db = &s->dma_dac; + else if (vma->vm_flags & VM_READ) + db = &s->dma_adc; + else { + unlock_kernel(); + return -EINVAL; + } + if (vma->vm_pgoff != 0) { + unlock_kernel(); + return -EINVAL; + } + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << db->buforder)) { + unlock_kernel(); + return -EINVAL; + } + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(db->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) { + unlock_kernel(); + return -EAGAIN; + } + db->mapped = 1; + unlock_kernel(); + return 0; +} + + +#ifdef IT8172_VERBOSE_DEBUG +static struct ioctl_str_t { + unsigned int cmd; + const char* str; +} ioctl_str[] = { + {SNDCTL_DSP_RESET, "SNDCTL_DSP_RESET"}, + {SNDCTL_DSP_SYNC, "SNDCTL_DSP_SYNC"}, + {SNDCTL_DSP_SPEED, "SNDCTL_DSP_SPEED"}, + {SNDCTL_DSP_STEREO, "SNDCTL_DSP_STEREO"}, + {SNDCTL_DSP_GETBLKSIZE, "SNDCTL_DSP_GETBLKSIZE"}, + {SNDCTL_DSP_SAMPLESIZE, "SNDCTL_DSP_SAMPLESIZE"}, + {SNDCTL_DSP_CHANNELS, "SNDCTL_DSP_CHANNELS"}, + {SOUND_PCM_WRITE_CHANNELS, "SOUND_PCM_WRITE_CHANNELS"}, + {SOUND_PCM_WRITE_FILTER, "SOUND_PCM_WRITE_FILTER"}, + {SNDCTL_DSP_POST, "SNDCTL_DSP_POST"}, + {SNDCTL_DSP_SUBDIVIDE, "SNDCTL_DSP_SUBDIVIDE"}, + {SNDCTL_DSP_SETFRAGMENT, "SNDCTL_DSP_SETFRAGMENT"}, + {SNDCTL_DSP_GETFMTS, "SNDCTL_DSP_GETFMTS"}, + {SNDCTL_DSP_SETFMT, "SNDCTL_DSP_SETFMT"}, + {SNDCTL_DSP_GETOSPACE, "SNDCTL_DSP_GETOSPACE"}, + {SNDCTL_DSP_GETISPACE, "SNDCTL_DSP_GETISPACE"}, + {SNDCTL_DSP_NONBLOCK, "SNDCTL_DSP_NONBLOCK"}, + {SNDCTL_DSP_GETCAPS, "SNDCTL_DSP_GETCAPS"}, + {SNDCTL_DSP_GETTRIGGER, "SNDCTL_DSP_GETTRIGGER"}, + {SNDCTL_DSP_SETTRIGGER, "SNDCTL_DSP_SETTRIGGER"}, + {SNDCTL_DSP_GETIPTR, "SNDCTL_DSP_GETIPTR"}, + {SNDCTL_DSP_GETOPTR, "SNDCTL_DSP_GETOPTR"}, + {SNDCTL_DSP_MAPINBUF, "SNDCTL_DSP_MAPINBUF"}, + {SNDCTL_DSP_MAPOUTBUF, "SNDCTL_DSP_MAPOUTBUF"}, + {SNDCTL_DSP_SETSYNCRO, "SNDCTL_DSP_SETSYNCRO"}, + {SNDCTL_DSP_SETDUPLEX, "SNDCTL_DSP_SETDUPLEX"}, + {SNDCTL_DSP_GETODELAY, "SNDCTL_DSP_GETODELAY"}, + {SNDCTL_DSP_GETCHANNELMASK, "SNDCTL_DSP_GETCHANNELMASK"}, + {SNDCTL_DSP_BIND_CHANNEL, "SNDCTL_DSP_BIND_CHANNEL"}, + {OSS_GETVERSION, "OSS_GETVERSION"}, + {SOUND_PCM_READ_RATE, "SOUND_PCM_READ_RATE"}, + {SOUND_PCM_READ_CHANNELS, "SOUND_PCM_READ_CHANNELS"}, + {SOUND_PCM_READ_BITS, "SOUND_PCM_READ_BITS"}, + {SOUND_PCM_READ_FILTER, "SOUND_PCM_READ_FILTER"} +}; +#endif + +static int it8172_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct it8172_state *s = (struct it8172_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int count; + int val, mapped, ret, diff; + + mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac.mapped) || + ((file->f_mode & FMODE_READ) && s->dma_adc.mapped); + +#ifdef IT8172_VERBOSE_DEBUG + for (count=0; countf_mode & FMODE_WRITE) + return drain_dac(s, 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: + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + synchronize_irq(s->irq); + s->dma_dac.count = s->dma_dac.total_bytes = 0; + s->dma_dac.nextIn = s->dma_dac.nextOut = + s->dma_dac.rawbuf; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(s->irq); + s->dma_adc.count = s->dma_adc.total_bytes = 0; + s->dma_adc.nextIn = s->dma_adc.nextOut = + s->dma_adc.rawbuf; + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, (int *)arg)) + return -EFAULT; + if (val >= 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + set_adc_rate(s, val); + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + set_dac_rate(s, val); + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + } + return put_user((file->f_mode & FMODE_READ) ? + s->adcrate : s->dacrate, (int *)arg); + + case SNDCTL_DSP_STEREO: + if (get_user(val, (int *)arg)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + if (val) + s->capcc |= CC_SM; + else + s->capcc &= ~CC_SM; + outw(s->capcc, s->io+IT_AC_CAPCC); + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + if (val) + s->pcc |= CC_SM; + else + s->pcc &= ~CC_SM; + outw(s->pcc, s->io+IT_AC_PCC); + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, (int *)arg)) + return -EFAULT; + if (val != 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + if (val >= 2) { + val = 2; + s->capcc |= CC_SM; + } + else + s->capcc &= ~CC_SM; + outw(s->capcc, s->io+IT_AC_CAPCC); + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + switch (val) { + case 1: + s->pcc &= ~CC_SM; + break; + case 2: + s->pcc |= CC_SM; + break; + default: + // FIX! support multichannel??? + val = 2; + s->pcc |= CC_SM; + break; + } + outw(s->pcc, s->io+IT_AC_PCC); + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + } + return put_user(val, (int *)arg); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_LE|AFMT_U8, (int *)arg); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, (int *)arg)) + return -EFAULT; + if (val != AFMT_QUERY) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + if (val == AFMT_S16_LE) + s->capcc |= CC_DF; + else { + val = AFMT_U8; + s->capcc &= ~CC_DF; + } + outw(s->capcc, s->io+IT_AC_CAPCC); + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + if (val == AFMT_S16_LE) + s->pcc |= CC_DF; + else { + val = AFMT_U8; + s->pcc &= ~CC_DF; + } + outw(s->pcc, s->io+IT_AC_PCC); + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + } else { + if (file->f_mode & FMODE_READ) + val = (s->capcc & CC_DF) ? + AFMT_S16_LE : AFMT_U8; + else + val = (s->pcc & CC_DF) ? + AFMT_S16_LE : AFMT_U8; + } + return put_user(val, (int *)arg); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + spin_lock_irqsave(&s->lock, flags); + if (file->f_mode & FMODE_READ && !s->dma_adc.stopped) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && !s->dma_dac.stopped) + val |= PCM_ENABLE_OUTPUT; + spin_unlock_irqrestore(&s->lock, flags); + 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) + start_adc(s); + else + stop_adc(s); + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) + start_dac(s); + else + stop_dac(s); + } + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + abinfo.fragsize = s->dma_dac.fragsize; + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + if (!s->dma_dac.stopped) + count -= (s->dma_dac.fragsize - + inw(s->io+IT_AC_PCDL)); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + abinfo.bytes = s->dma_dac.dmasize - count; + abinfo.fragstotal = s->dma_dac.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift; + return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? + -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + abinfo.fragsize = s->dma_adc.fragsize; + spin_lock_irqsave(&s->lock, flags); + count = s->dma_adc.count; + if (!s->dma_adc.stopped) + count += (s->dma_adc.fragsize - + inw(s->io+IT_AC_CAPCDL)); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + abinfo.bytes = count; + abinfo.fragstotal = s->dma_adc.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_adc.fragshift; + return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? + -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + if (!s->dma_dac.stopped) + count -= (s->dma_dac.fragsize - + inw(s->io+IT_AC_PCDL)); + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + return put_user(count, (int *)arg); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cinfo.bytes = s->dma_adc.total_bytes; + count = s->dma_adc.count; + if (!s->dma_adc.stopped) { + diff = s->dma_adc.fragsize - inw(s->io+IT_AC_CAPCDL); + count += diff; + cinfo.bytes += diff; + cinfo.ptr = inl(s->io+s->dma_adc.curBufPtr) - + s->dma_adc.dmaaddr; + } else + cinfo.ptr = virt_to_bus(s->dma_adc.nextIn) - + s->dma_adc.dmaaddr; + if (s->dma_adc.mapped) + s->dma_adc.count &= s->dma_adc.fragsize-1; + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_adc.fragshift; + if (copy_to_user((void *)arg, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cinfo.bytes = s->dma_dac.total_bytes; + count = s->dma_dac.count; + if (!s->dma_dac.stopped) { + diff = s->dma_dac.fragsize - inw(s->io+IT_AC_CAPCDL); + count -= diff; + cinfo.bytes += diff; + cinfo.ptr = inl(s->io+s->dma_dac.curBufPtr) - + s->dma_dac.dmaaddr; + } else + cinfo.ptr = virt_to_bus(s->dma_dac.nextOut) - + s->dma_dac.dmaaddr; + if (s->dma_dac.mapped) + s->dma_dac.count &= s->dma_dac.fragsize-1; + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_dac.fragshift; + if (copy_to_user((void *)arg, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) + return put_user(s->dma_dac.fragsize, (int *)arg); + else + return put_user(s->dma_adc.fragsize, (int *)arg); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, (int *)arg)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ossfragshift = val & 0xffff; + s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_adc.ossfragshift < 4) + s->dma_adc.ossfragshift = 4; + if (s->dma_adc.ossfragshift > 15) + s->dma_adc.ossfragshift = 15; + if (s->dma_adc.ossmaxfrags < 4) + s->dma_adc.ossmaxfrags = 4; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ossfragshift = val & 0xffff; + s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac.ossfragshift < 4) + s->dma_dac.ossfragshift = 4; + if (s->dma_dac.ossfragshift > 15) + s->dma_dac.ossfragshift = 15; + if (s->dma_dac.ossmaxfrags < 4) + s->dma_dac.ossmaxfrags = 4; + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return 0; + + 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) { + stop_adc(s); + s->dma_adc.subdivision = val; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.subdivision = val; + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return 0; + + case SOUND_PCM_READ_RATE: + return put_user((file->f_mode & FMODE_READ) ? + s->adcrate : s->dacrate, (int *)arg); + + case SOUND_PCM_READ_CHANNELS: + if (file->f_mode & FMODE_READ) + return put_user((s->capcc & CC_SM) ? 2 : 1, + (int *)arg); + else + return put_user((s->pcc & CC_SM) ? 2 : 1, + (int *)arg); + + case SOUND_PCM_READ_BITS: + if (file->f_mode & FMODE_READ) + return put_user((s->capcc & CC_DF) ? 16 : 8, + (int *)arg); + else + return put_user((s->pcc & CC_DF) ? 16 : 8, + (int *)arg); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + } + + return mixdev_ioctl(&s->codec, cmd, arg); +} + + +static int it8172_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + struct list_head *list; + struct it8172_state *s; + int ret; + +#ifdef IT8172_VERBOSE_DEBUG + if (file->f_flags & O_NONBLOCK) + dbg("%s: non-blocking", __FUNCTION__); + else + dbg("%s: blocking", __FUNCTION__); +#endif + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct it8172_state, devs); + if (!((s->dev_audio ^ minor) & ~0xf)) + break; + } + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & file->f_mode) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + + spin_lock_irqsave(&s->lock, flags); + + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = + s->dma_adc.subdivision = s->dma_adc.total_bytes = 0; + s->capcc &= ~(CC_SM | CC_DF); + set_adc_rate(s, 8000); + if ((minor & 0xf) == SND_DEV_DSP16) + s->capcc |= CC_DF; + outw(s->capcc, s->io+IT_AC_CAPCC); + if ((ret = prog_dmabuf_adc(s))) { + spin_unlock_irqrestore(&s->lock, flags); + return ret; + } + } + if (file->f_mode & FMODE_WRITE) { + s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = + s->dma_dac.subdivision = s->dma_dac.total_bytes = 0; + s->pcc &= ~(CC_SM | CC_DF); + set_dac_rate(s, 8000); + if ((minor & 0xf) == SND_DEV_DSP16) + s->pcc |= CC_DF; + outw(s->pcc, s->io+IT_AC_PCC); + if ((ret = prog_dmabuf_dac(s))) { + spin_unlock_irqrestore(&s->lock, flags); + return ret; + } + } + + spin_unlock_irqrestore(&s->lock, flags); + + s->open_mode |= (file->f_mode & (FMODE_READ | FMODE_WRITE)); + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int it8172_release(struct inode *inode, struct file *file) +{ + struct it8172_state *s = (struct it8172_state *)file->private_data; + +#ifdef IT8172_VERBOSE_DEBUG + dbg(__FUNCTION__); +#endif + lock_kernel(); + if (file->f_mode & FMODE_WRITE) + drain_dac(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem); + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + dealloc_dmabuf(s, &s->dma_dac); + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + dealloc_dmabuf(s, &s->dma_adc); + } + s->open_mode &= ((~file->f_mode) & (FMODE_READ|FMODE_WRITE)); + up(&s->open_sem); + wake_up(&s->open_wait); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations it8172_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = it8172_read, + .write = it8172_write, + .poll = it8172_poll, + .ioctl = it8172_ioctl, + .mmap = it8172_mmap, + .open = it8172_open, + .release = it8172_release, +}; + + +/* --------------------------------------------------------------------- */ + + +/* --------------------------------------------------------------------- */ + +/* + * for debugging purposes, we'll create a proc device that dumps the + * CODEC chipstate + */ + +#ifdef IT8172_DEBUG +static int proc_it8172_dump (char *buf, char **start, off_t fpos, + int length, int *eof, void *data) +{ + struct it8172_state *s; + int cnt, len = 0; + + if (list_empty(&devs)) + return 0; + s = list_entry(devs.next, struct it8172_state, devs); + + /* print out header */ + len += sprintf(buf + len, "\n\t\tIT8172 Audio Debug\n\n"); + + // print out digital controller state + len += sprintf (buf + len, "IT8172 Audio Controller registers\n"); + len += sprintf (buf + len, "---------------------------------\n"); + cnt=0; + while (cnt < 0x72) { + if (cnt == IT_AC_PCB1STA || cnt == IT_AC_PCB2STA || + cnt == IT_AC_CAPB1STA || cnt == IT_AC_CAPB2STA || + cnt == IT_AC_PFDP) { + len+= sprintf (buf + len, "reg %02x = %08x\n", + cnt, inl(s->io+cnt)); + cnt += 4; + } else { + len+= sprintf (buf + len, "reg %02x = %04x\n", + cnt, inw(s->io+cnt)); + cnt += 2; + } + } + + /* print out CODEC state */ + len += sprintf (buf + len, "\nAC97 CODEC registers\n"); + len += sprintf (buf + len, "----------------------\n"); + for (cnt=0; cnt <= 0x7e; cnt = cnt +2) + len+= sprintf (buf + len, "reg %02x = %04x\n", + cnt, rdcodec(&s->codec, cnt)); + + if (fpos >=len){ + *start = buf; + *eof =1; + return 0; + } + *start = buf + fpos; + if ((len -= fpos) > length) + return length; + *eof =1; + return len; + +} +#endif /* IT8172_DEBUG */ + +/* --------------------------------------------------------------------- */ + +/* maximum number of devices; only used for command line params */ +#define NR_DEVICE 5 + +static int spdif[NR_DEVICE]; +static int i2s_fmt[NR_DEVICE]; + +static unsigned int devindex; + +MODULE_PARM(spdif, "1-" __MODULE_STRING(NR_DEVICE) "i"); +MODULE_PARM_DESC(spdif, "if 1 the S/PDIF digital output is enabled"); +MODULE_PARM(i2s_fmt, "1-" __MODULE_STRING(NR_DEVICE) "i"); +MODULE_PARM_DESC(i2s_fmt, "the format of I2S"); + +MODULE_AUTHOR("Monta Vista Software, stevel@mvista.com"); +MODULE_DESCRIPTION("IT8172 Audio Driver"); + +/* --------------------------------------------------------------------- */ + +static int __devinit it8172_probe(struct pci_dev *pcidev, + const struct pci_device_id *pciid) +{ + struct it8172_state *s; + int i, val; + unsigned short pcisr, vol; + unsigned char legacy, imc; + char proc_str[80]; + + if (pcidev->irq == 0) + return -1; + + if (!(s = kmalloc(sizeof(struct it8172_state), GFP_KERNEL))) { + err("alloc of device struct failed"); + return -1; + } + + memset(s, 0, sizeof(struct it8172_state)); + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac.wait); + init_waitqueue_head(&s->open_wait); + init_MUTEX(&s->open_sem); + spin_lock_init(&s->lock); + s->dev = pcidev; + s->io = pci_resource_start(pcidev, 0); + s->irq = pcidev->irq; + s->vendor = pcidev->vendor; + s->device = pcidev->device; + pci_read_config_byte(pcidev, PCI_REVISION_ID, &s->rev); + s->codec.private_data = s; + s->codec.id = 0; + s->codec.codec_read = rdcodec; + s->codec.codec_write = wrcodec; + s->codec.codec_wait = waitcodec; + + if (!request_region(s->io, pci_resource_len(pcidev,0), + IT8172_MODULE_NAME)) { + err("io ports %#lx->%#lx in use", + s->io, s->io + pci_resource_len(pcidev,0)-1); + goto err_region; + } + if (request_irq(s->irq, it8172_interrupt, SA_INTERRUPT, + IT8172_MODULE_NAME, s)) { + err("irq %u in use", s->irq); + goto err_irq; + } + + info("IO at %#lx, IRQ %d", s->io, s->irq); + + /* register devices */ + if ((s->dev_audio = register_sound_dsp(&it8172_audio_fops, -1)) < 0) + goto err_dev1; + if ((s->codec.dev_mixer = + register_sound_mixer(&it8172_mixer_fops, -1)) < 0) + goto err_dev2; + +#ifdef IT8172_DEBUG + /* initialize the debug proc device */ + s->ps = create_proc_read_entry(IT8172_MODULE_NAME, 0, NULL, + proc_it8172_dump, NULL); +#endif /* IT8172_DEBUG */ + + /* + * Reset the Audio device using the IT8172 PCI Reset register. This + * creates an audible double click on a speaker connected to Line-out. + */ + IT_IO_READ16(IT_PM_PCISR, pcisr); + pcisr |= IT_PM_PCISR_ACSR; + IT_IO_WRITE16(IT_PM_PCISR, pcisr); + /* wait up to 100msec for reset to complete */ + for (i=0; pcisr & IT_PM_PCISR_ACSR; i++) { + it8172_delay(10); + if (i == 10) + break; + IT_IO_READ16(IT_PM_PCISR, pcisr); + } + if (i == 10) { + err("chip reset timeout!"); + goto err_dev3; + } + + /* enable pci io and bus mastering */ + if (pci_enable_device(pcidev)) + goto err_dev3; + pci_set_master(pcidev); + + /* get out of legacy mode */ + pci_read_config_byte (pcidev, 0x40, &legacy); + pci_write_config_byte (pcidev, 0x40, legacy & ~1); + + s->spdif_volume = -1; + /* check to see if s/pdif mode is being requested */ + if (spdif[devindex]) { + info("enabling S/PDIF output"); + s->spdif_volume = 0; + outb(GC_SOE, s->io+IT_AC_GC); + } else { + info("disabling S/PDIF output"); + outb(0, s->io+IT_AC_GC); + } + + /* check to see if I2S format requested */ + if (i2s_fmt[devindex]) { + info("setting I2S format to 0x%02x", i2s_fmt[devindex]); + outb(i2s_fmt[devindex], s->io+IT_AC_I2SMC); + } else { + outb(I2SMC_I2SF_I2S, s->io+IT_AC_I2SMC); + } + + /* cold reset the AC97 */ + outw(CODECC_CR, s->io+IT_AC_CODECC); + udelay(1000); + outw(0, s->io+IT_AC_CODECC); + /* need to delay around 500msec(bleech) to give + some CODECs enough time to wakeup */ + it8172_delay(500); + + /* AC97 warm reset to start the bitclk */ + outw(CODECC_WR, s->io+IT_AC_CODECC); + udelay(1000); + outw(0, s->io+IT_AC_CODECC); + + /* codec init */ + if (!ac97_probe_codec(&s->codec)) + goto err_dev3; + + /* add I2S as allowable recording source */ + s->codec.record_sources |= SOUND_MASK_I2S; + + /* Enable Volume button interrupts */ + imc = inb(s->io+IT_AC_IMC); + outb(imc & ~IMC_VCIM, s->io+IT_AC_IMC); + + /* Un-mute PCM and FM out on the controller */ + vol = inw(s->io+IT_AC_PCMOV); + outw(vol & ~PCMOV_PCMOM, s->io+IT_AC_PCMOV); + vol = inw(s->io+IT_AC_FMOV); + outw(vol & ~FMOV_FMOM, s->io+IT_AC_FMOV); + + /* set channel defaults to 8-bit, mono, 8 Khz */ + s->pcc = 0; + s->capcc = 0; + set_dac_rate(s, 8000); + set_adc_rate(s, 8000); + + /* set mic to be the recording source */ + val = SOUND_MASK_MIC; + mixdev_ioctl(&s->codec, SOUND_MIXER_WRITE_RECSRC, + (unsigned long)&val); + + /* mute AC'97 master and PCM when in S/PDIF mode */ + if (s->spdif_volume != -1) { + val = 0x0000; + s->codec.mixer_ioctl(&s->codec, SOUND_MIXER_WRITE_VOLUME, + (unsigned long)&val); + s->codec.mixer_ioctl(&s->codec, SOUND_MIXER_WRITE_PCM, + (unsigned long)&val); + } + +#ifdef IT8172_DEBUG + sprintf(proc_str, "driver/%s/%d/ac97", IT8172_MODULE_NAME, + s->codec.id); + s->ac97_ps = create_proc_read_entry (proc_str, 0, NULL, + ac97_read_proc, &s->codec); +#endif + + /* store it in the driver field */ + pci_set_drvdata(pcidev, s); + pcidev->dma_mask = 0xffffffff; + /* put it into driver list */ + list_add_tail(&s->devs, &devs); + /* increment devindex */ + if (devindex < NR_DEVICE-1) + devindex++; + return 0; + + err_dev3: + unregister_sound_mixer(s->codec.dev_mixer); + err_dev2: + unregister_sound_dsp(s->dev_audio); + err_dev1: + err("cannot register misc device"); + free_irq(s->irq, s); + err_irq: + release_region(s->io, pci_resource_len(pcidev,0)); + err_region: + kfree(s); + return -1; +} + +static void __devexit it8172_remove(struct pci_dev *dev) +{ + struct it8172_state *s = pci_get_drvdata(dev); + + if (!s) + return; + list_del(&s->devs); +#ifdef IT8172_DEBUG + if (s->ps) + remove_proc_entry(IT8172_MODULE_NAME, NULL); +#endif /* IT8172_DEBUG */ + synchronize_irq(s->irq); + free_irq(s->irq, s); + release_region(s->io, pci_resource_len(dev,0)); + unregister_sound_dsp(s->dev_audio); + unregister_sound_mixer(s->codec.dev_mixer); + kfree(s); + pci_set_drvdata(dev, NULL); +} + + + +static struct pci_device_id id_table[] = { + { PCI_VENDOR_ID_ITE, PCI_DEVICE_ID_ITE_IT8172G_AUDIO, PCI_ANY_ID, + PCI_ANY_ID, 0, 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, id_table); + +static struct pci_driver it8172_driver = { + .name = IT8172_MODULE_NAME, + .id_table = id_table, + .probe = it8172_probe, + .remove = __devexit_p(it8172_remove) +}; + +static int __init init_it8172(void) +{ + info("version v0.5 time " __TIME__ " " __DATE__); + return pci_module_init(&it8172_driver); +} + +static void __exit cleanup_it8172(void) +{ + info("unloading"); + pci_unregister_driver(&it8172_driver); +} + +module_init(init_it8172); +module_exit(cleanup_it8172); + +/* --------------------------------------------------------------------- */ + +#ifndef MODULE + +/* format is: it8172=[spdif],[i2s:] */ + +static int __init it8172_setup(char *options) +{ + char* this_opt; + static unsigned __initdata nr_dev = 0; + + if (nr_dev >= NR_DEVICE) + return 0; + + if (!options || !*options) + return 0; + + while (this_opt = strsep(&options, ",")) { + if (!*this_opt) + continue; + if (!strncmp(this_opt, "spdif", 5)) { + spdif[nr_dev] = 1; + } else if (!strncmp(this_opt, "i2s:", 4)) { + if (!strncmp(this_opt+4, "dac", 3)) + i2s_fmt[nr_dev] = I2SMC_I2SF_DAC; + else if (!strncmp(this_opt+4, "adc", 3)) + i2s_fmt[nr_dev] = I2SMC_I2SF_ADC; + else if (!strncmp(this_opt+4, "i2s", 3)) + i2s_fmt[nr_dev] = I2SMC_I2SF_I2S; + } + } + + nr_dev++; + return 1; +} + +__setup("it8172=", it8172_setup); + +#endif /* MODULE */ diff --git a/sound/oss/iwmem.h b/sound/oss/iwmem.h new file mode 100644 index 000000000000..84745fbcabcb --- /dev/null +++ b/sound/oss/iwmem.h @@ -0,0 +1,36 @@ +/* + * sound/iwmem.h + * + * DRAM size encoding table for AMD Interwave 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: + * Bartlomiej Zolnierkiewicz : added __initdata to mem_decode + */ + + +#define K 1024 +#define M (1024*K) +static int mem_decode[][4] __initdata = +{ +/* Bank0 Bank1 Bank2 Bank3 Encoding bits */ + {256*K, 0, 0, 0}, /* 0 */ + {256*K, 256*K, 0, 0}, /* 1 */ + {256*K, 256*K, 256*K, 256*K}, /* 2 */ + {256*K, 1*M, 0, 0}, /* 3 */ + {256*K, 1*M, 1*M, 1*M}, /* 4 */ + {256*K, 256*K, 1*M, 0}, /* 5 */ + {256*K, 256*K, 1*M, 1*M}, /* 6 */ + {1*M, 0, 0, 0}, /* 7 */ + {1*M, 1*M, 0, 0}, /* 8 */ + {1*M, 1*M, 1*M, 1*M}, /* 9 */ + {4*M, 0, 0, 0}, /* 10 */ + {4*M, 4*M, 0, 0}, /* 11 */ + {4*M, 4*M, 4*M, 4*M} /* 12 */ +}; diff --git a/sound/oss/kahlua.c b/sound/oss/kahlua.c new file mode 100644 index 000000000000..808c5ef969be --- /dev/null +++ b/sound/oss/kahlua.c @@ -0,0 +1,232 @@ +/* + * Initialisation code for Cyrix/NatSemi VSA1 softaudio + * + * (C) Copyright 2003 Red Hat Inc + * + * XpressAudio(tm) is used on the Cyrix MediaGX (now NatSemi Geode) systems. + * The older version (VSA1) provides fairly good soundblaster emulation + * although there are a couple of bugs: large DMA buffers break record, + * and the MPU event handling seems suspect. VSA2 allows the native driver + * to control the AC97 audio engine directly and requires a different driver. + * + * Thanks to National Semiconductor for providing the needed information + * on the XpressAudio(tm) internals. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * TO DO: + * Investigate whether we can portably support Cognac (5520) in the + * same manner. + */ + +#include +#include +#include +#include +#include + +#include "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 == 0UL) + 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 = kmalloc(sizeof(struct address_info), GFP_KERNEL); + if(hw_config == NULL) + { + printk(KERN_ERR "kahlua: out of memory.\n"); + return 1; + } + memset(hw_config, 0, sizeof(*hw_config)); + + 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 struct pci_device_id id_tbl[] = { + { PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 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_module_init(&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/mad16.c b/sound/oss/mad16.c new file mode 100644 index 000000000000..a7067f169919 --- /dev/null +++ b/sound/oss/mad16.c @@ -0,0 +1,1097 @@ +/* + * Copyright (C) by Hannu Savolainen 1993-1997 + * + * mad16.c + * + * Initialization code for OPTi MAD16 compatible audio chips. Including + * + * OPTi 82C928 MAD16 (replaced by C929) + * OAK OTI-601D Mozart + * OAK OTI-605 Mozart (later version with MPU401 Midi) + * OPTi 82C929 MAD16 Pro + * OPTi 82C930 + * OPTi 82C924 + * + * These audio interface chips don't produce sound themselves. They just + * connect some other components (OPL-[234] and a WSS compatible codec) + * to the PC bus and perform I/O, DMA and IRQ address decoding. There is + * also a UART for the MPU-401 mode (not 82C928/Mozart). + * The Mozart chip appears to be compatible with the 82C928, although later + * issues of the card, using the OTI-605 chip, have an MPU-401 compatible Midi + * port. This port is configured differently to that of the OPTi audio chips. + * + * Changes + * + * Alan Cox Clean up, added module selections. + * + * A. Wik Added support for Opti924 PnP. + * Improved debugging support. 16-May-1998 + * Fixed bug. 16-Jun-1998 + * + * Torsten Duwe Made Opti924 PnP support non-destructive + * 23-Dec-1998 + * + * Paul Grayson Added support for Midi on later Mozart cards. + * 25-Nov-1999 + * Christoph Hellwig Adapted to module_init/module_exit. + * Arnaldo C. de Melo got rid of attach_uart401 21-Sep-2000 + * + * Pavel Rabel Clean up Nov-2000 + */ + +#include +#include +#include +#include +#include +#include "sound_config.h" + +#include "ad1848.h" +#include "sb.h" +#include "mpu401.h" + +static int mad16_conf; +static int mad16_cdsel; +static struct gameport *gameport; +static DEFINE_SPINLOCK(lock); + +#define C928 1 +#define MOZART 2 +#define C929 3 +#define C930 4 +#define C924 5 + +/* + * Registers + * + * The MAD16 occupies I/O ports 0xf8d to 0xf93 (fixed locations). + * All ports are inactive by default. They can be activated by + * writing 0xE2 or 0xE3 to the password register. The password is valid + * only until the next I/O read or write. + * + * 82C930 uses 0xE4 as the password and indirect addressing to access + * the config registers. + */ + +#define MC0_PORT 0xf8c /* Dummy port */ +#define MC1_PORT 0xf8d /* SB address, CD-ROM interface type, joystick */ +#define MC2_PORT 0xf8e /* CD-ROM address, IRQ, DMA, plus OPL4 bit */ +#define MC3_PORT 0xf8f +#define PASSWD_REG 0xf8f +#define MC4_PORT 0xf90 +#define MC5_PORT 0xf91 +#define MC6_PORT 0xf92 +#define MC7_PORT 0xf93 +#define MC8_PORT 0xf94 +#define MC9_PORT 0xf95 +#define MC10_PORT 0xf96 +#define MC11_PORT 0xf97 +#define MC12_PORT 0xf98 + +static int board_type = C928; + +static int *mad16_osp; +static int c931_detected; /* minor differences from C930 */ +static char c924pnp; /* " " " C924 */ +static int debug; /* debugging output */ + +#ifdef DDB +#undef DDB +#endif +#define DDB(x) do {if (debug) x;} while (0) + +static unsigned char mad_read(int port) +{ + unsigned long flags; + unsigned char tmp; + + spin_lock_irqsave(&lock,flags); + + switch (board_type) /* Output password */ + { + case C928: + case MOZART: + outb((0xE2), PASSWD_REG); + break; + + case C929: + outb((0xE3), PASSWD_REG); + break; + + case C930: + /* outb(( 0xE4), PASSWD_REG); */ + break; + + case C924: + /* the c924 has its ports relocated by -128 if + PnP is enabled -aw */ + if (!c924pnp) + outb((0xE5), PASSWD_REG); else + outb((0xE5), PASSWD_REG - 0x80); + break; + } + + if (board_type == C930) + { + outb((port - MC0_PORT), 0xe0e); /* Write to index reg */ + tmp = inb(0xe0f); /* Read from data reg */ + } + else + if (!c924pnp) + tmp = inb(port); else + tmp = inb(port-0x80); + spin_unlock_irqrestore(&lock,flags); + + return tmp; +} + +static void mad_write(int port, int value) +{ + unsigned long flags; + + spin_lock_irqsave(&lock,flags); + + switch (board_type) /* Output password */ + { + case C928: + case MOZART: + outb((0xE2), PASSWD_REG); + break; + + case C929: + outb((0xE3), PASSWD_REG); + break; + + case C930: + /* outb(( 0xE4), PASSWD_REG); */ + break; + + case C924: + if (!c924pnp) + outb((0xE5), PASSWD_REG); else + outb((0xE5), PASSWD_REG - 0x80); + break; + } + + if (board_type == C930) + { + outb((port - MC0_PORT), 0xe0e); /* Write to index reg */ + outb(((unsigned char) (value & 0xff)), 0xe0f); + } + else + if (!c924pnp) + outb(((unsigned char) (value & 0xff)), port); else + outb(((unsigned char) (value & 0xff)), port-0x80); + spin_unlock_irqrestore(&lock,flags); +} + +static int __init detect_c930(void) +{ + unsigned char tmp = mad_read(MC1_PORT); + + if ((tmp & 0x06) != 0x06) + { + DDB(printk("Wrong C930 signature (%x)\n", tmp)); + /* return 0; */ + } + mad_write(MC1_PORT, 0); + + if (mad_read(MC1_PORT) != 0x06) + { + DDB(printk("Wrong C930 signature2 (%x)\n", tmp)); + /* return 0; */ + } + mad_write(MC1_PORT, tmp); /* Restore bits */ + + mad_write(MC7_PORT, 0); + if ((tmp = mad_read(MC7_PORT)) != 0) + { + DDB(printk("MC7 not writable (%x)\n", tmp)); + return 0; + } + mad_write(MC7_PORT, 0xcb); + if ((tmp = mad_read(MC7_PORT)) != 0xcb) + { + DDB(printk("MC7 not writable2 (%x)\n", tmp)); + return 0; + } + + tmp = mad_read(MC0_PORT+18); + if (tmp == 0xff || tmp == 0x00) + return 1; + /* We probably have a C931 */ + DDB(printk("Detected C931 config=0x%02x\n", tmp)); + c931_detected = 1; + + /* + * We cannot configure the chip if it is in PnP mode. + * If we have a CSN assigned (bit 8 in MC13) we first try + * a software reset, then a software power off, finally + * Clearing PnP mode. The last option is not + * Bit 8 in MC13 + */ + if ((mad_read(MC0_PORT+13) & 0x80) == 0) + return 1; + + /* Software reset */ + mad_write(MC9_PORT, 0x02); + mad_write(MC9_PORT, 0x00); + + if ((mad_read(MC0_PORT+13) & 0x80) == 0) + return 1; + + /* Power off, and on again */ + mad_write(MC9_PORT, 0xc2); + mad_write(MC9_PORT, 0xc0); + + if ((mad_read(MC0_PORT+13) & 0x80) == 0) + return 1; + +#if 0 + /* Force off PnP mode. This is not recommended because + * the PnP bios will not recognize the chip on the next + * warm boot and may assignd different resources to other + * PnP/PCI cards. + */ + mad_write(MC0_PORT+17, 0x04); +#endif + return 1; +} + +static int __init detect_mad16(void) +{ + unsigned char tmp, tmp2, bit; + int i, port; + + /* + * Check that reading a register doesn't return bus float (0xff) + * when the card is accessed using password. This may fail in case + * the card is in low power mode. Normally at least the power saving + * mode bit should be 0. + */ + + if ((tmp = mad_read(MC1_PORT)) == 0xff) + { + DDB(printk("MC1_PORT returned 0xff\n")); + return 0; + } + for (i = 0xf8d; i <= 0xf98; i++) + if (!c924pnp) + DDB(printk("Port %0x (init value) = %0x\n", i, mad_read(i))); + else + DDB(printk("Port %0x (init value) = %0x\n", i-0x80, mad_read(i))); + + if (board_type == C930) + return detect_c930(); + + /* + * Now check that the gate is closed on first I/O after writing + * the password. (This is how a MAD16 compatible card works). + */ + + if ((tmp2 = inb(MC1_PORT)) == tmp) /* It didn't close */ + { + DDB(printk("MC1_PORT didn't close after read (0x%02x)\n", tmp2)); + return 0; + } + + bit = (c924pnp) ? 0x20 : 0x80; + port = (c924pnp) ? MC2_PORT : MC1_PORT; + + tmp = mad_read(port); + mad_write(port, tmp ^ bit); /* Toggle a bit */ + if ((tmp2 = mad_read(port)) != (tmp ^ bit)) /* Compare the bit */ + { + mad_write(port, tmp); /* Restore */ + DDB(printk("Bit revert test failed (0x%02x, 0x%02x)\n", tmp, tmp2)); + return 0; + } + mad_write(port, tmp); /* Restore */ + return 1; /* Bingo */ +} + +static int __init wss_init(struct address_info *hw_config) +{ + /* + * Check if the IO port returns valid signature. The original MS Sound + * system returns 0x04 while some cards (AudioTrix Pro for example) + * return 0x00. + */ + + if ((inb(hw_config->io_base + 3) & 0x3f) != 0x04 && + (inb(hw_config->io_base + 3) & 0x3f) != 0x00) + { + DDB(printk("No MSS signature detected on port 0x%x (0x%x)\n", hw_config->io_base, inb(hw_config->io_base + 3))); + 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("MSS: Can't use DMA0 with a 8 bit card/slot\n"); + return 0; + } + if (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 1; +} + +static void __init init_c930(struct address_info *hw_config, int base) +{ + unsigned char cfg = 0; + + cfg |= (0x0f & mad16_conf); + + if(c931_detected) + { + /* Bit 0 has reversd meaning. Bits 1 and 2 sese + reversed on write. + Support only IDE cdrom. IDE port programmed + somewhere else. */ + cfg = (cfg & 0x09) ^ 0x07; + } + cfg |= base << 4; + mad_write(MC1_PORT, cfg); + + /* MC2 is CD configuration. Don't touch it. */ + + mad_write(MC3_PORT, 0); /* Disable SB mode IRQ and DMA */ + + /* bit 2 of MC4 reverses it's meaning between the C930 + and the C931. */ + cfg = c931_detected ? 0x04 : 0x00; + + if(mad16_cdsel & 0x20) + mad_write(MC4_PORT, 0x62|cfg); /* opl4 */ + else + mad_write(MC4_PORT, 0x52|cfg); /* opl3 */ + + mad_write(MC5_PORT, 0x3C); /* Init it into mode2 */ + mad_write(MC6_PORT, 0x02); /* Enable WSS, Disable MPU and SB */ + mad_write(MC7_PORT, 0xCB); + mad_write(MC10_PORT, 0x11); +} + +static int __init chip_detect(void) +{ + int i; + + /* + * Then try to detect with the old password + */ + board_type = C924; + + DDB(printk("Detect using password = 0xE5\n")); + + if (detect_mad16()) { + return 1; + } + + board_type = C928; + + DDB(printk("Detect using password = 0xE2\n")); + + if (detect_mad16()) + { + unsigned char model; + + if (((model = mad_read(MC3_PORT)) & 0x03) == 0x03) { + DDB(printk("mad16.c: Mozart detected\n")); + board_type = MOZART; + } else { + DDB(printk("mad16.c: 82C928 detected???\n")); + board_type = C928; + } + return 1; + } + + board_type = C929; + + DDB(printk("Detect using password = 0xE3\n")); + + if (detect_mad16()) + { + DDB(printk("mad16.c: 82C929 detected\n")); + return 1; + } + + if (inb(PASSWD_REG) != 0xff) + return 0; + + /* + * First relocate MC# registers to 0xe0e/0xe0f, disable password + */ + + outb((0xE4), PASSWD_REG); + outb((0x80), PASSWD_REG); + + board_type = C930; + + DDB(printk("Detect using password = 0xE4\n")); + + for (i = 0xf8d; i <= 0xf93; i++) + DDB(printk("port %03x = %02x\n", i, mad_read(i))); + + if(detect_mad16()) { + DDB(printk("mad16.c: 82C930 detected\n")); + return 1; + } + + /* The C931 has the password reg at F8D */ + outb((0xE4), 0xF8D); + outb((0x80), 0xF8D); + DDB(printk("Detect using password = 0xE4 for C931\n")); + + if (detect_mad16()) { + return 1; + } + + board_type = C924; + c924pnp++; + DDB(printk("Detect using password = 0xE5 (again), port offset -0x80\n")); + if (detect_mad16()) { + DDB(printk("mad16.c: 82C924 PnP detected\n")); + return 1; + } + + c924pnp=0; + + return 0; +} + +static int __init probe_mad16(struct address_info *hw_config) +{ + int i; + unsigned char tmp; + unsigned char cs4231_mode = 0; + + int ad_flags = 0; + + signed char bits; + + static char dma_bits[4] = { + 1, 2, 0, 3 + }; + + int config_port = hw_config->io_base + 0, version_port = hw_config->io_base + 3; + int dma = hw_config->dma, dma2 = hw_config->dma2; + unsigned char dma2_bit = 0; + int base; + struct resource *ports; + + mad16_osp = hw_config->osp; + + switch (hw_config->io_base) { + case 0x530: + base = 0; + break; + case 0xe80: + base = 1; + break; + case 0xf40: + base = 2; + break; + case 0x604: + base = 3; + break; + default: + printk(KERN_ERR "MAD16/Mozart: Bad WSS base address 0x%x\n", hw_config->io_base); + return 0; + } + + if (dma != 0 && dma != 1 && dma != 3) { + printk(KERN_ERR "MSS: Bad DMA %d\n", dma); + return 0; + } + + /* + * Check that all ports return 0xff (bus float) when no password + * is written to the password register. + */ + + DDB(printk("--- Detecting MAD16 / Mozart ---\n")); + if (!chip_detect()) + return 0; + + switch (hw_config->irq) { + case 7: + bits = 8; + break; + case 9: + bits = 0x10; + break; + case 10: + bits = 0x18; + break; + case 12: + bits = 0x20; + break; + case 5: /* Also IRQ5 is possible on C930 */ + if (board_type == C930 || c924pnp) { + bits = 0x28; + break; + } + default: + printk(KERN_ERR "MAD16/Mozart: Bad IRQ %d\n", hw_config->irq); + return 0; + } + + ports = request_region(hw_config->io_base + 4, 4, "ad1848"); + if (!ports) { + printk(KERN_ERR "MSS: I/O port conflict\n"); + return 0; + } + if (!request_region(hw_config->io_base, 4, "mad16 WSS config")) { + release_region(hw_config->io_base + 4, 4); + printk(KERN_ERR "MSS: I/O port conflict\n"); + return 0; + } + + if (board_type == C930) { + init_c930(hw_config, base); + goto got_it; + } + + for (i = 0xf8d; i <= 0xf93; i++) { + if (!c924pnp) + DDB(printk("port %03x = %02x\n", i, mad_read(i))); + else + DDB(printk("port %03x = %02x\n", i-0x80, mad_read(i))); + } + +/* + * Set the WSS address + */ + + tmp = (mad_read(MC1_PORT) & 0x0f) | 0x80; /* Enable WSS, Disable SB */ + tmp |= base << 4; /* WSS port select bits */ + + /* + * Set optional CD-ROM and joystick settings. + */ + + tmp &= ~0x0f; + tmp |= (mad16_conf & 0x0f); /* CD-ROM and joystick bits */ + mad_write(MC1_PORT, tmp); + + tmp = mad16_cdsel; + mad_write(MC2_PORT, tmp); + mad_write(MC3_PORT, 0xf0); /* Disable SB */ + + if (board_type == C924) /* Specific C924 init values */ + { + mad_write(MC4_PORT, 0xA0); + mad_write(MC5_PORT, 0x05); + mad_write(MC6_PORT, 0x03); + } + if (!ad1848_detect(ports, &ad_flags, mad16_osp)) + goto fail; + + if (ad_flags & (AD_F_CS4231 | AD_F_CS4248)) + cs4231_mode = 0x02; /* CS4248/CS4231 sync delay switch */ + + if (board_type == C929) + { + mad_write(MC4_PORT, 0xa2); + mad_write(MC5_PORT, 0xA5 | cs4231_mode); + mad_write(MC6_PORT, 0x03); /* Disable MPU401 */ + } + else + { + mad_write(MC4_PORT, 0x02); + mad_write(MC5_PORT, 0x30 | cs4231_mode); + } + + for (i = 0xf8d; i <= 0xf93; i++) { + if (!c924pnp) + DDB(printk("port %03x after init = %02x\n", i, mad_read(i))); + else + DDB(printk("port %03x after init = %02x\n", i-0x80, mad_read(i))); + } + +got_it: + ad_flags = 0; + if (!ad1848_detect(ports, &ad_flags, mad16_osp)) + goto fail; + + if (!wss_init(hw_config)) + goto fail; + + /* + * Set the IRQ and DMA addresses. + */ + + outb((bits | 0x40), config_port); + if ((inb(version_port) & 0x40) == 0) + printk(KERN_ERR "[IRQ Conflict?]\n"); + + /* + * Handle the capture DMA channel + */ + + if (ad_flags & AD_F_CS4231 && 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("MAD16: Invalid capture DMA\n"); + dma2 = dma; + } + } + else dma2 = dma; + + outb((bits | dma_bits[dma] | dma2_bit), config_port); /* Write IRQ+DMA setup */ + + hw_config->slots[0] = ad1848_init("mad16 WSS", ports, + hw_config->irq, + dma, + dma2, 0, + hw_config->osp, + THIS_MODULE); + return 1; + +fail: + release_region(hw_config->io_base + 4, 4); + release_region(hw_config->io_base, 4); + return 0; +} + +static int __init probe_mad16_mpu(struct address_info *hw_config) +{ + unsigned char tmp; + + if (board_type < C929) /* Early chip. No MPU support. Just SB MIDI */ + { + +#ifdef CONFIG_MAD16_OLDCARD + + tmp = mad_read(MC3_PORT); + + /* + * MAD16 SB base is defined by the WSS base. It cannot be changed + * alone. + * Ignore configured I/O base. Use the active setting. + */ + + if (mad_read(MC1_PORT) & 0x20) + hw_config->io_base = 0x240; + else + hw_config->io_base = 0x220; + + switch (hw_config->irq) + { + case 5: + tmp = (tmp & 0x3f) | 0x80; + break; + case 7: + tmp = (tmp & 0x3f); + break; + case 11: + tmp = (tmp & 0x3f) | 0x40; + break; + default: + printk(KERN_ERR "mad16/Mozart: Invalid MIDI IRQ\n"); + return 0; + } + + mad_write(MC3_PORT, tmp | 0x04); + hw_config->driver_use_1 = SB_MIDI_ONLY; + if (!request_region(hw_config->io_base, 16, "soundblaster")) + return 0; + if (!sb_dsp_detect(hw_config, 0, 0, NULL)) { + release_region(hw_config->io_base, 16); + return 0; + } + + if (mad_read(MC1_PORT) & 0x20) + hw_config->io_base = 0x240; + else + hw_config->io_base = 0x220; + + hw_config->name = "Mad16/Mozart"; + sb_dsp_init(hw_config, THIS_MODULE); + return 1; +#else + /* assuming all later Mozart cards are identified as + * either 82C928 or Mozart. If so, following code attempts + * to set MPU register. TODO - add probing + */ + + tmp = mad_read(MC8_PORT); + + switch (hw_config->irq) + { + case 5: + tmp |= 0x08; + break; + case 7: + tmp |= 0x10; + break; + case 9: + tmp |= 0x18; + break; + case 10: + tmp |= 0x20; + break; + case 11: + tmp |= 0x28; + break; + default: + printk(KERN_ERR "mad16/MOZART: invalid mpu_irq\n"); + return 0; + } + + switch (hw_config->io_base) + { + case 0x300: + tmp |= 0x01; + break; + case 0x310: + tmp |= 0x03; + break; + case 0x320: + tmp |= 0x05; + break; + case 0x330: + tmp |= 0x07; + break; + default: + printk(KERN_ERR "mad16/MOZART: invalid mpu_io\n"); + return 0; + } + + mad_write(MC8_PORT, tmp); /* write MPU port parameters */ + goto probe_401; +#endif + } + tmp = mad_read(MC6_PORT) & 0x83; + tmp |= 0x80; /* MPU-401 enable */ + + /* Set the MPU base bits */ + + switch (hw_config->io_base) + { + case 0x300: + tmp |= 0x60; + break; + case 0x310: + tmp |= 0x40; + break; + case 0x320: + tmp |= 0x20; + break; + case 0x330: + tmp |= 0x00; + break; + default: + printk(KERN_ERR "MAD16: Invalid MIDI port 0x%x\n", hw_config->io_base); + return 0; + } + + /* Set the MPU IRQ bits */ + + switch (hw_config->irq) + { + case 5: + tmp |= 0x10; + break; + case 7: + tmp |= 0x18; + break; + case 9: + tmp |= 0x00; + break; + case 10: + tmp |= 0x08; + break; + default: + printk(KERN_ERR "MAD16: Invalid MIDI IRQ %d\n", hw_config->irq); + break; + } + + mad_write(MC6_PORT, tmp); /* Write MPU401 config */ + +#ifndef CONFIG_MAD16_OLDCARD +probe_401: +#endif + hw_config->driver_use_1 = SB_MIDI_ONLY; + hw_config->name = "Mad16/Mozart"; + return probe_uart401(hw_config, THIS_MODULE); +} + +static void __exit unload_mad16(struct address_info *hw_config) +{ + ad1848_unload(hw_config->io_base + 4, + hw_config->irq, + hw_config->dma, + hw_config->dma2, 0); + release_region(hw_config->io_base, 4); + sound_unload_audiodev(hw_config->slots[0]); +} + +static void __exit unload_mad16_mpu(struct address_info *hw_config) +{ +#ifdef CONFIG_MAD16_OLDCARD + if (board_type < C929) /* Early chip. No MPU support. Just SB MIDI */ + { + sb_dsp_unload(hw_config, 0); + return; + } +#endif + + unload_uart401(hw_config); +} + +static struct address_info cfg; +static struct address_info cfg_mpu; + +static int found_mpu; + +static int __initdata mpu_io = 0; +static int __initdata mpu_irq = 0; +static int __initdata io = -1; +static int __initdata dma = -1; +static int __initdata dma16 = -1; /* Set this for modules that need it */ +static int __initdata irq = -1; +static int __initdata cdtype = 0; +static int __initdata cdirq = 0; +static int __initdata cdport = 0x340; +static int __initdata cddma = -1; +static int __initdata opl4 = 0; +static int __initdata joystick = 0; + +module_param(mpu_io, int, 0); +module_param(mpu_irq, int, 0); +module_param(io, int, 0); +module_param(dma, int, 0); +module_param(dma16, int, 0); +module_param(irq, int, 0); +module_param(cdtype, int, 0); +module_param(cdirq, int, 0); +module_param(cdport, int, 0); +module_param(cddma, int, 0); +module_param(opl4, int, 0); +module_param(joystick, bool, 0); +module_param(debug, bool, 0644); + +static int __initdata dma_map[2][8] = +{ + {0x03, -1, -1, -1, -1, 0x00, 0x01, 0x02}, + {0x03, -1, 0x01, 0x00, -1, -1, -1, -1} +}; + +static int __initdata irq_map[16] = +{ + 0x00, -1, -1, 0x0A, + -1, 0x04, -1, 0x08, + -1, 0x10, 0x14, 0x18, + -1, -1, -1, -1 +}; + +static int __devinit mad16_register_gameport(int io_port) +{ + if (!request_region(io_port, 1, "mad16 gameport")) { + printk(KERN_ERR "mad16: gameport address 0x%#x already in use\n", io_port); + return -EBUSY; + } + + gameport = gameport_allocate_port(); + if (!gameport) { + printk(KERN_ERR "mad16: can not allocate memory for gameport\n"); + release_region(io_port, 1); + return -ENOMEM; + } + + gameport_set_name(gameport, "MAD16 Gameport"); + gameport_set_phys(gameport, "isa%04x/gameport0", io_port); + gameport->io = io_port; + + gameport_register_port(gameport); + + return 0; +} + +static int __devinit init_mad16(void) +{ + int dmatype = 0; + + printk(KERN_INFO "MAD16 audio driver Copyright (C) by Hannu Savolainen 1993-1996\n"); + + printk(KERN_INFO "CDROM "); + switch (cdtype) + { + case 0x00: + printk("Disabled"); + cdirq = 0; + break; + case 0x02: + printk("Sony CDU31A"); + dmatype = 1; + if(cddma == -1) cddma = 3; + break; + case 0x04: + printk("Mitsumi"); + dmatype = 0; + if(cddma == -1) cddma = 5; + break; + case 0x06: + printk("Panasonic Lasermate"); + dmatype = 1; + if(cddma == -1) cddma = 3; + break; + case 0x08: + printk("Secondary IDE"); + dmatype = 0; + if(cddma == -1) cddma = 5; + break; + case 0x0A: + printk("Primary IDE"); + dmatype = 0; + if(cddma == -1) cddma = 5; + break; + default: + printk("\n"); + printk(KERN_ERR "Invalid CDROM type\n"); + return -EINVAL; + } + + /* + * Build the config words + */ + + mad16_conf = (joystick ^ 1) | cdtype; + mad16_cdsel = 0; + if (opl4) + mad16_cdsel |= 0x20; + + if(cdtype){ + if (cddma > 7 || cddma < 0 || dma_map[dmatype][cddma] == -1) + { + printk("\n"); + printk(KERN_ERR "Invalid CDROM DMA\n"); + return -EINVAL; + } + if (cddma) + printk(", DMA %d", cddma); + else + printk(", no DMA"); + + if (!cdirq) + printk(", no IRQ"); + else if (cdirq < 0 || cdirq > 15 || irq_map[cdirq] == -1) + { + printk(", invalid IRQ (disabling)"); + cdirq = 0; + } + else printk(", IRQ %d", cdirq); + + mad16_cdsel |= dma_map[dmatype][cddma]; + + if (cdtype < 0x08) + { + switch (cdport) + { + case 0x340: + mad16_cdsel |= 0x00; + break; + case 0x330: + mad16_cdsel |= 0x40; + break; + case 0x360: + mad16_cdsel |= 0x80; + break; + case 0x320: + mad16_cdsel |= 0xC0; + break; + default: + printk(KERN_ERR "Unknown CDROM I/O base %d\n", cdport); + return -EINVAL; + } + } + mad16_cdsel |= irq_map[cdirq]; + } + + printk(".\n"); + + cfg.io_base = io; + cfg.irq = irq; + cfg.dma = dma; + cfg.dma2 = dma16; + + if (cfg.io_base == -1 || cfg.dma == -1 || cfg.irq == -1) { + printk(KERN_ERR "I/O, DMA and irq are mandatory\n"); + return -EINVAL; + } + + if (!request_region(MC0_PORT, 12, "mad16")) + return -EBUSY; + + if (!probe_mad16(&cfg)) { + release_region(MC0_PORT, 12); + return -ENODEV; + } + + cfg_mpu.io_base = mpu_io; + cfg_mpu.irq = mpu_irq; + + found_mpu = probe_mad16_mpu(&cfg_mpu); + + if (joystick) + mad16_register_gameport(0x201); + + return 0; +} + +static void __exit cleanup_mad16(void) +{ + if (found_mpu) + unload_mad16_mpu(&cfg_mpu); + if (gameport) { + /* the gameport was initialized so we must free it up */ + gameport_unregister_port(gameport); + gameport = NULL; + release_region(0x201, 1); + } + unload_mad16(&cfg); + release_region(MC0_PORT, 12); +} + +module_init(init_mad16); +module_exit(cleanup_mad16); + +#ifndef MODULE +static int __init setup_mad16(char *str) +{ + /* io, irq */ + int ints[8]; + + str = get_options(str, ARRAY_SIZE(ints), ints); + + io = ints[1]; + irq = ints[2]; + dma = ints[3]; + dma16 = ints[4]; + mpu_io = ints[5]; + mpu_irq = ints[6]; + joystick = ints[7]; + + return 1; +} + +__setup("mad16=", setup_mad16); +#endif +MODULE_LICENSE("GPL"); diff --git a/sound/oss/maestro.c b/sound/oss/maestro.c new file mode 100644 index 000000000000..52d2db4bc312 --- /dev/null +++ b/sound/oss/maestro.c @@ -0,0 +1,3832 @@ +/***************************************************************************** + * + * ESS Maestro/Maestro-2/Maestro-2E driver for Linux 2.[23].x + * + * 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. + * + * (c) Copyright 1999 Alan Cox + * + * Based heavily on SonicVibes.c: + * Copyright (C) 1998-1999 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * Heavily modified by Zach Brown based on lunch + * with ESS engineers. Many thanks to Howard Kim for providing + * contacts and hardware. Honorable mention goes to Eric + * Brombaugh for all sorts of things. Best regards to the + * proprietors of Hack Central for fine lodging. + * + * Supported devices: + * /dev/dsp0-3 standard /dev/dsp device, (mostly) OSS compatible + * /dev/mixer standard /dev/mixer device, (mostly) OSS compatible + * + * Hardware Description + * + * A working Maestro setup contains the Maestro chip wired to a + * codec or 2. In the Maestro we have the APUs, the ASSP, and the + * Wavecache. The APUs can be though of as virtual audio routing + * channels. They can take data from a number of sources and perform + * basic encodings of the data. The wavecache is a storehouse for + * PCM data. Typically it deals with PCI and interracts with the + * APUs. The ASSP is a wacky DSP like device that ESS is loth + * to release docs on. Thankfully it isn't required on the Maestro + * until you start doing insane things like FM emulation and surround + * encoding. The codecs are almost always AC-97 compliant codecs, + * but it appears that early Maestros may have had PT101 (an ESS + * part?) wired to them. The only real difference in the Maestro + * families is external goop like docking capability, memory for + * the ASSP, and initialization differences. + * + * Driver Operation + * + * We only drive the APU/Wavecache as typical DACs and drive the + * mixers in the codecs. There are 64 APUs. We assign 6 to each + * /dev/dsp? device. 2 channels for output, and 4 channels for + * input. + * + * Each APU can do a number of things, but we only really use + * 3 basic functions. For playback we use them to convert PCM + * data fetched over PCI by the wavecahche into analog data that + * is handed to the codec. One APU for mono, and a pair for stereo. + * When in stereo, the combination of smarts in the APU and Wavecache + * decide which wavecache gets the left or right channel. + * + * For record we still use the old overly mono system. For each in + * coming channel the data comes in from the codec, through a 'input' + * APU, through another rate converter APU, and then into memory via + * the wavecache and PCI. If its stereo, we mash it back into LRLR in + * software. The pass between the 2 APUs is supposedly what requires us + * to have a 512 byte buffer sitting around in wavecache/memory. + * + * The wavecache makes our life even more fun. First off, it can + * only address the first 28 bits of PCI address space, making it + * useless on quite a few architectures. Secondly, its insane. + * It claims to fetch from 4 regions of PCI space, each 4 meg in length. + * But that doesn't really work. You can only use 1 region. So all our + * allocations have to be in 4meg of each other. Booo. Hiss. + * So we have a module parameter, dsps_order, that is the order of + * the number of dsps to provide. All their buffer space is allocated + * on open time. The sonicvibes OSS routines we inherited really want + * power of 2 buffers, so we have all those next to each other, then + * 512 byte regions for the recording wavecaches. This ends up + * wasting quite a bit of memory. The only fixes I can see would be + * getting a kernel allocator that could work in zones, or figuring out + * just how to coerce the WP into doing what we want. + * + * The indirection of the various registers means we have to spinlock + * nearly all register accesses. We have the main register indirection + * like the wave cache, maestro registers, etc. Then we have beasts + * like the APU interface that is indirect registers gotten at through + * the main maestro indirection. Ouch. We spinlock around the actual + * ports on a per card basis. This means spinlock activity at each IO + * operation, but the only IO operation clusters are in non critical + * paths and it makes the code far easier to follow. Interrupts are + * blocked while holding the locks because the int handler has to + * get at some of them :(. The mixer interface doesn't, however. + * We also have an OSS state lock that is thrown around in a few + * places. + * + * This driver has brute force APM suspend support. We catch suspend + * notifications and stop all work being done on the chip. Any people + * that try between this shutdown and the real suspend operation will + * be put to sleep. When we resume we restore our software state on + * the chip and wake up the people that were using it. The code thats + * being used now is quite dirty and assumes we're on a uni-processor + * machine. Much of it will need to be cleaned up for SMP ACPI or + * similar. + * + * We also pay attention to PCI power management now. The driver + * will power down units of the chip that it knows aren't needed. + * The WaveProcessor and company are only powered on when people + * have /dev/dsp*s open. On removal the driver will + * power down the maestro entirely. There could still be + * trouble with BIOSen that magically change power states + * themselves, but we'll see. + * + * History + * v0.15 - May 21 2001 - Marcus Meissner + * Ported to Linux 2.4 PCI API. Some clean ups, global devs list + * removed (now using pci device driver data). + * PM needs to be polished still. Bumped version. + * (still kind of v0.14) May 13 2001 - Ben Pfaff + * Add support for 978 docking and basic hardware volume control + * (still kind of v0.14) Nov 23 - Alan Cox + * Add clocking= for people with seriously warped hardware + * (still v0.14) Nov 10 2000 - Bartlomiej Zolnierkiewicz + * add __init to maestro_ac97_init() and maestro_install() + * (still based on v0.14) Mar 29 2000 - Zach Brown + * move to 2.3 power management interface, which + * required hacking some suspend/resume/check paths + * make static compilation work + * v0.14 - Jan 28 2000 - Zach Brown + * add PCI power management through ACPI regs. + * we now shut down on machine reboot/halt + * leave scary PCI config items alone (isa stuff, mostly) + * enable 1921s, it seems only mine was broke. + * fix swapped left/right pcm dac. har har. + * up bob freq, increase buffers, fix pointers at underflow + * silly compilation problems + * v0.13 - Nov 18 1999 - Zach Brown + * fix nec Versas? man would that be cool. + * v0.12 - Nov 12 1999 - Zach Brown + * brown bag volume max fix.. + * v0.11 - Nov 11 1999 - Zach Brown + * use proper stereo apu decoding, mmap/write should work. + * make volume sliders more useful, tweak rate calculation. + * fix lame 8bit format reporting bug. duh. apm apu saving buglet also + * fix maestro 1 clock freq "bug", remove pt101 support + * v0.10 - Oct 28 1999 - Zach Brown + * aha, so, sometimes the WP writes a status word to offset 0 + * from one of the PCMBARs. rearrange allocation accordingly.. + * cheers again to Eric for being a good hacker in investigating this. + * Jeroen Hoogervorst submits 7500 fix out of nowhere. yay. :) + * v0.09 - Oct 23 1999 - Zach Brown + * added APM support. + * re-order something such that some 2Es now work. Magic! + * new codec reset routine. made some codecs come to life. + * fix clear_advance, sync some control with ESS. + * now write to all base regs to be paranoid. + * v0.08 - Oct 20 1999 - Zach Brown + * Fix initial buflen bug. I am so smart. also smp compiling.. + * I owe Eric yet another beer: fixed recmask, igain, + * muting, and adc sync consistency. Go Team. + * v0.07 - Oct 4 1999 - Zach Brown + * tweak adc/dac, formating, and stuff to allow full duplex + * allocate dsps memory at open() so we can fit in the wavecache window + * fix wavecache braindamage. again. no more scribbling? + * fix ess 1921 codec bug on some laptops. + * fix dumb pci scanning bug + * started 2.3 cleanup, redid spinlocks, little cleanups + * v0.06 - Sep 20 1999 - Zach Brown + * fix wavecache thinkos. limit to 1 /dev/dsp. + * eric is wearing his thinking toque this week. + * spotted apu mode bugs and gain ramping problem + * don't touch weird mixer regs, make recmask optional + * fixed igain inversion, defaults for mixers, clean up rec_start + * make mono recording work. + * report subsystem stuff, please send reports. + * littles: parallel out, amp now + * v0.05 - Sep 17 1999 - Zach Brown + * merged and fixed up Eric's initial recording code + * munged format handling to catch misuse, needs rewrite. + * revert ring bus init, fixup shared int, add pci busmaster setting + * fix mixer oss interface, fix mic mute and recmask + * mask off unsupported mixers, reset with all 1s, modularize defaults + * make sure bob is running while we need it + * got rid of device limit, initial minimal apm hooks + * pull out dead code/includes, only allow multimedia/audio maestros + * v0.04 - Sep 01 1999 - Zach Brown + * copied memory leak fix from sonicvibes driver + * different ac97 reset, play with 2.0 ac97, simplify ring bus setup + * bob freq code, region sanity, jitter sync fix; all from Eric + * + * TODO + * fix bob frequency + * endianness + * do smart things with ac97 2.0 bits. + * dual codecs + * leave 54->61 open + * + * it also would be fun to have a mode that would not use pci dma at all + * but would copy into the wavecache on board memory and use that + * on architectures that don't like the maestro's pci dma ickiness. + */ + +/*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +static int maestro_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *d); + +#include "maestro.h" + +static struct pci_driver maestro_pci_driver; + +/* --------------------------------------------------------------------- */ + +#define M_DEBUG 1 + +#ifdef M_DEBUG +static int debug; +#define M_printk(args...) {if (debug) printk(args);} +#else +#define M_printk(x) +#endif + +/* we try to setup 2^(dsps_order) /dev/dsp devices */ +static int dsps_order; +/* whether or not we mess around with power management */ +static int use_pm=2; /* set to 1 for force */ +/* clocking for broken hardware - a few laptops seem to use a 50Khz clock + ie insmod with clocking=50000 or so */ + +static int clocking=48000; + +MODULE_AUTHOR("Zach Brown , Alan Cox "); +MODULE_DESCRIPTION("ESS Maestro Driver"); +MODULE_LICENSE("GPL"); + +#ifdef M_DEBUG +module_param(debug, bool, 0644); +#endif +module_param(dsps_order, int, 0); +module_param(use_pm, int, 0); +module_param(clocking, int, 0); + +/* --------------------------------------------------------------------- */ +#define DRIVER_VERSION "0.15" + +#ifndef PCI_VENDOR_ESS +#define PCI_VENDOR_ESS 0x125D +#define PCI_DEVICE_ID_ESS_ESS1968 0x1968 /* Maestro 2 */ +#define PCI_DEVICE_ID_ESS_ESS1978 0x1978 /* Maestro 2E */ + +#define PCI_VENDOR_ESS_OLD 0x1285 /* Platform Tech, + the people the maestro + was bought from */ +#define PCI_DEVICE_ID_ESS_ESS0100 0x0100 /* maestro 1 */ +#endif /* PCI_VENDOR_ESS */ + +#define ESS_CHAN_HARD 0x100 + +/* NEC Versas ? */ +#define NEC_VERSA_SUBID1 0x80581033 +#define NEC_VERSA_SUBID2 0x803c1033 + + +/* changed so that I could actually find all the + references and fix them up. it's a little more readable now. */ +#define ESS_FMT_STEREO 0x01 +#define ESS_FMT_16BIT 0x02 +#define ESS_FMT_MASK 0x03 +#define ESS_DAC_SHIFT 0 +#define ESS_ADC_SHIFT 4 + +#define ESS_STATE_MAGIC 0x125D1968 +#define ESS_CARD_MAGIC 0x19283746 + +#define DAC_RUNNING 1 +#define ADC_RUNNING 2 + +#define MAX_DSP_ORDER 2 +#define MAX_DSPS (1<src buffer page */ + void *mixbuf; + +}; + +struct ess_card { + unsigned int magic; + + /* We keep maestro cards in a linked list */ + struct ess_card *next; + + int dev_mixer; + + int card_type; + + /* as most of this is static, + perhaps it should be a pointer to a global struct */ + struct mixer_goo { + int modcnt; + int supported_mixers; + int stereo_mixers; + int record_sources; + /* the caller must guarantee arg sanity before calling these */ +/* int (*read_mixer)(struct ess_card *card, int index);*/ + void (*write_mixer)(struct ess_card *card,int mixer, unsigned int left,unsigned int right); + int (*recmask_io)(struct ess_card *card,int rw,int mask); + unsigned int mixer_state[SOUND_MIXER_NRDEVICES]; + } mix; + + int power_regs; + + int in_suspend; + wait_queue_head_t suspend_queue; + + struct ess_state channels[MAX_DSPS]; + u16 maestro_map[NR_IDRS]; /* Register map */ + /* we have to store this junk so that we can come back from a + suspend */ + u16 apu_map[NR_APUS][NR_APU_REGS]; /* contents of apu regs */ + + /* this locks around the physical registers on the card */ + spinlock_t lock; + + /* memory for this card.. wavecache limited :(*/ + void *dmapages; + int dmaorder; + + /* hardware resources */ + struct pci_dev *pcidev; + u32 iobase; + u32 irq; + + int bob_freq; + char dsps_open; + + int dock_mute_vol; +}; + +static void set_mixer(struct ess_card *card,unsigned int mixer, unsigned int val ); + +static unsigned +ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + + +/* --------------------------------------------------------------------- */ + +static void check_suspend(struct ess_card *card); + +/* --------------------------------------------------------------------- */ + + +/* + * ESS Maestro AC97 codec programming interface. + */ + +static void maestro_ac97_set(struct ess_card *card, u8 cmd, u16 val) +{ + int io = card->iobase; + int i; + /* + * Wait for the codec bus to be free + */ + + check_suspend(card); + + for(i=0;i<10000;i++) + { + if(!(inb(io+ESS_AC97_INDEX)&1)) + break; + } + /* + * Write the bus + */ + outw(val, io+ESS_AC97_DATA); + mdelay(1); + outb(cmd, io+ESS_AC97_INDEX); + mdelay(1); +} + +static u16 maestro_ac97_get(struct ess_card *card, u8 cmd) +{ + int io = card->iobase; + int sanity=10000; + u16 data; + int i; + + check_suspend(card); + /* + * Wait for the codec bus to be free + */ + + for(i=0;i<10000;i++) + { + if(!(inb(io+ESS_AC97_INDEX)&1)) + break; + } + + outb(cmd|0x80, io+ESS_AC97_INDEX); + mdelay(1); + + while(inb(io+ESS_AC97_INDEX)&1) + { + sanity--; + if(!sanity) + { + printk(KERN_ERR "maestro: ac97 codec timeout reading 0x%x.\n",cmd); + return 0; + } + } + data=inw(io+ESS_AC97_DATA); + mdelay(1); + return data; +} + +/* OSS interface to the ac97s.. */ + +#define AC97_STEREO_MASK (SOUND_MASK_VOLUME|\ + SOUND_MASK_PCM|SOUND_MASK_LINE|SOUND_MASK_CD|\ + SOUND_MASK_VIDEO|SOUND_MASK_LINE1|SOUND_MASK_IGAIN) + +#define AC97_SUPPORTED_MASK (AC97_STEREO_MASK | \ + SOUND_MASK_BASS|SOUND_MASK_TREBLE|SOUND_MASK_MIC|\ + SOUND_MASK_SPEAKER) + +#define AC97_RECORD_MASK (SOUND_MASK_MIC|\ + SOUND_MASK_CD| SOUND_MASK_VIDEO| SOUND_MASK_LINE1| SOUND_MASK_LINE|\ + SOUND_MASK_PHONEIN) + +#define supported_mixer(CARD,FOO) ( CARD->mix.supported_mixers & (1<offset); + + if(AC97_STEREO_MASK & (1<> 8) & 0x7f; + right = val & 0x7f; + + if (mixer == SOUND_MIXER_IGAIN) { + right = (right * 100) / mh->scale; + left = (left * 100) / mh->scale; + } else { + right = 100 - ((right * 100) / mh->scale); + left = 100 - ((left * 100) / mh->scale); + } + + ret = left | (right << 8); + } else if (mixer == SOUND_MIXER_SPEAKER) { + ret = 100 - ((((val & 0x1e)>>1) * 100) / mh->scale); + } else if (mixer == SOUND_MIXER_MIC) { + ret = 100 - (((val & 0x1f) * 100) / mh->scale); + /* the low bit is optional in the tone sliders and masking + it lets is avoid the 0xf 'bypass'.. */ + } else if (mixer == SOUND_MIXER_BASS) { + ret = 100 - ((((val >> 8) & 0xe) * 100) / mh->scale); + } else if (mixer == SOUND_MIXER_TREBLE) { + ret = 100 - (((val & 0xe) * 100) / mh->scale); + } + + M_printk("read mixer %d (0x%x) %x -> %x\n",mixer,mh->offset,val,ret); + + return ret; +} +#endif + +/* write the OSS encoded volume to the given OSS encoded mixer, + again caller's job to make sure all is well in arg land, + call with spinlock held */ + +/* linear scale -> log */ +static unsigned char lin2log[101] = +{ +0, 0 , 15 , 23 , 30 , 34 , 38 , 42 , 45 , 47 , +50 , 52 , 53 , 55 , 57 , 58 , 60 , 61 , 62 , +63 , 65 , 66 , 67 , 68 , 69 , 69 , 70 , 71 , +72 , 73 , 73 , 74 , 75 , 75 , 76 , 77 , 77 , +78 , 78 , 79 , 80 , 80 , 81 , 81 , 82 , 82 , +83 , 83 , 84 , 84 , 84 , 85 , 85 , 86 , 86 , +87 , 87 , 87 , 88 , 88 , 88 , 89 , 89 , 89 , +90 , 90 , 90 , 91 , 91 , 91 , 92 , 92 , 92 , +93 , 93 , 93 , 94 , 94 , 94 , 94 , 95 , 95 , +95 , 95 , 96 , 96 , 96 , 96 , 97 , 97 , 97 , +97 , 98 , 98 , 98 , 98 , 99 , 99 , 99 , 99 , 99 +}; + +static void ac97_write_mixer(struct ess_card *card,int mixer, unsigned int left, unsigned int right) +{ + u16 val=0; + struct ac97_mixer_hw *mh = &ac97_hw[mixer]; + + M_printk("wrote mixer %d (0x%x) %d,%d",mixer,mh->offset,left,right); + + if(AC97_STEREO_MASK & (1<scale) / 100; + left = (left * mh->scale) / 100; + if ((left == 0) && (right == 0)) + val |= 0x8000; + } else if (mixer == SOUND_MIXER_PCM || mixer == SOUND_MIXER_CD) { + /* log conversion seems bad for them */ + if ((left == 0) && (right == 0)) + val = 0x8000; + right = ((100 - right) * mh->scale) / 100; + left = ((100 - left) * mh->scale) / 100; + } else { + /* log conversion for the stereo controls */ + if((left == 0) && (right == 0)) + val = 0x8000; + right = ((100 - lin2log[right]) * mh->scale) / 100; + left = ((100 - lin2log[left]) * mh->scale) / 100; + } + + val |= (left << 8) | right; + + } else if (mixer == SOUND_MIXER_SPEAKER) { + val = (((100 - left) * mh->scale) / 100) << 1; + } else if (mixer == SOUND_MIXER_MIC) { + val = maestro_ac97_get(card, mh->offset) & ~0x801f; + val |= (((100 - left) * mh->scale) / 100); + /* the low bit is optional in the tone sliders and masking + it lets is avoid the 0xf 'bypass'.. */ + } else if (mixer == SOUND_MIXER_BASS) { + val = maestro_ac97_get(card , mh->offset) & ~0x0f00; + val |= ((((100 - left) * mh->scale) / 100) << 8) & 0x0e00; + } else if (mixer == SOUND_MIXER_TREBLE) { + val = maestro_ac97_get(card , mh->offset) & ~0x000f; + val |= (((100 - left) * mh->scale) / 100) & 0x000e; + } + + maestro_ac97_set(card , mh->offset, val); + + M_printk(" -> %x\n",val); +} + +/* the following tables allow us to go from + OSS <-> ac97 quickly. */ + +enum ac97_recsettings { + AC97_REC_MIC=0, + AC97_REC_CD, + AC97_REC_VIDEO, + AC97_REC_AUX, + AC97_REC_LINE, + AC97_REC_STEREO, /* combination of all enabled outputs.. */ + AC97_REC_MONO, /*.. or the mono equivalent */ + AC97_REC_PHONE +}; + +static unsigned int ac97_oss_mask[] = { + [AC97_REC_MIC] = SOUND_MASK_MIC, + [AC97_REC_CD] = SOUND_MASK_CD, + [AC97_REC_VIDEO] = SOUND_MASK_VIDEO, + [AC97_REC_AUX] = SOUND_MASK_LINE1, + [AC97_REC_LINE] = SOUND_MASK_LINE, + [AC97_REC_PHONE] = SOUND_MASK_PHONEIN +}; + +/* indexed by bit position */ +static unsigned int ac97_oss_rm[] = { + [SOUND_MIXER_MIC] = AC97_REC_MIC, + [SOUND_MIXER_CD] = AC97_REC_CD, + [SOUND_MIXER_VIDEO] = AC97_REC_VIDEO, + [SOUND_MIXER_LINE1] = AC97_REC_AUX, + [SOUND_MIXER_LINE] = AC97_REC_LINE, + [SOUND_MIXER_PHONEIN] = AC97_REC_PHONE +}; + +/* read or write the recmask + the ac97 can really have left and right recording + inputs independently set, but OSS doesn't seem to + want us to express that to the user. + the caller guarantees that we have a supported bit set, + and they must be holding the card's spinlock */ +static int +ac97_recmask_io(struct ess_card *card, int read, int mask) +{ + unsigned int val = ac97_oss_mask[ maestro_ac97_get(card, 0x1a) & 0x7 ]; + + if (read) return val; + + /* oss can have many inputs, maestro can't. try + to pick the 'new' one */ + + if (mask != val) mask &= ~val; + + val = ffs(mask) - 1; + val = ac97_oss_rm[val]; + val |= val << 8; /* set both channels */ + + M_printk("maestro: setting ac97 recmask to 0x%x\n",val); + + maestro_ac97_set(card,0x1a,val); + + return 0; +}; + +/* + * The Maestro can be wired to a standard AC97 compliant codec + * (see www.intel.com for the pdf's on this), or to a PT101 codec + * which appears to be the ES1918 (data sheet on the esstech.com.tw site) + * + * The PT101 setup is untested. + */ + +static u16 __init maestro_ac97_init(struct ess_card *card) +{ + u16 vend1, vend2, caps; + + card->mix.supported_mixers = AC97_SUPPORTED_MASK; + card->mix.stereo_mixers = AC97_STEREO_MASK; + card->mix.record_sources = AC97_RECORD_MASK; +/* card->mix.read_mixer = ac97_read_mixer;*/ + card->mix.write_mixer = ac97_write_mixer; + card->mix.recmask_io = ac97_recmask_io; + + vend1 = maestro_ac97_get(card, 0x7c); + vend2 = maestro_ac97_get(card, 0x7e); + + caps = maestro_ac97_get(card, 0x00); + + printk(KERN_INFO "maestro: AC97 Codec detected: v: 0x%2x%2x caps: 0x%x pwr: 0x%x\n", + vend1,vend2,caps,maestro_ac97_get(card,0x26) & 0xf); + + if (! (caps & 0x4) ) { + /* no bass/treble nobs */ + card->mix.supported_mixers &= ~(SOUND_MASK_BASS|SOUND_MASK_TREBLE); + } + + /* XXX endianness, dork head. */ + /* vendor specifc bits.. */ + switch ((long)(vend1 << 16) | vend2) { + case 0x545200ff: /* TriTech */ + /* no idea what this does */ + maestro_ac97_set(card,0x2a,0x0001); + maestro_ac97_set(card,0x2c,0x0000); + maestro_ac97_set(card,0x2c,0xffff); + break; +#if 0 /* i thought the problems I was seeing were with + the 1921, but apparently they were with the pci board + it was on, so this code is commented out. + lets see if this holds true. */ + case 0x83847609: /* ESS 1921 */ + /* writing to 0xe (mic) or 0x1a (recmask) seems + to hang this codec */ + card->mix.supported_mixers &= ~(SOUND_MASK_MIC); + card->mix.record_sources = 0; + card->mix.recmask_io = NULL; +#if 0 /* don't ask. I have yet to see what these actually do. */ + maestro_ac97_set(card,0x76,0xABBA); /* o/~ Take a chance on me o/~ */ + udelay(20); + maestro_ac97_set(card,0x78,0x3002); + udelay(20); + maestro_ac97_set(card,0x78,0x3802); + udelay(20); +#endif + break; +#endif + default: break; + } + + maestro_ac97_set(card, 0x1E, 0x0404); + /* null misc stuff */ + maestro_ac97_set(card, 0x20, 0x0000); + + return 0; +} + +#if 0 /* there has been 1 person on the planet with a pt101 that we + know of. If they care, they can put this back in :) */ +static u16 maestro_pt101_init(struct ess_card *card,int iobase) +{ + printk(KERN_INFO "maestro: PT101 Codec detected, initializing but _not_ installing mixer device.\n"); + /* who knows.. */ + maestro_ac97_set(iobase, 0x2A, 0x0001); + maestro_ac97_set(iobase, 0x2C, 0x0000); + maestro_ac97_set(iobase, 0x2C, 0xFFFF); + maestro_ac97_set(iobase, 0x10, 0x9F1F); + maestro_ac97_set(iobase, 0x12, 0x0808); + maestro_ac97_set(iobase, 0x14, 0x9F1F); + maestro_ac97_set(iobase, 0x16, 0x9F1F); + maestro_ac97_set(iobase, 0x18, 0x0404); + maestro_ac97_set(iobase, 0x1A, 0x0000); + maestro_ac97_set(iobase, 0x1C, 0x0000); + maestro_ac97_set(iobase, 0x02, 0x0404); + maestro_ac97_set(iobase, 0x04, 0x0808); + maestro_ac97_set(iobase, 0x0C, 0x801F); + maestro_ac97_set(iobase, 0x0E, 0x801F); + return 0; +} +#endif + +/* this is very magic, and very slow.. */ +static void +maestro_ac97_reset(int ioaddr, struct pci_dev *pcidev) +{ + u16 save_68; + u16 w; + u32 vend; + + outw( inw(ioaddr + 0x38) & 0xfffc, ioaddr + 0x38); + outw( inw(ioaddr + 0x3a) & 0xfffc, ioaddr + 0x3a); + outw( inw(ioaddr + 0x3c) & 0xfffc, ioaddr + 0x3c); + + /* reset the first codec */ + outw(0x0000, ioaddr+0x36); + save_68 = inw(ioaddr+0x68); + pci_read_config_word(pcidev, 0x58, &w); /* something magical with gpio and bus arb. */ + pci_read_config_dword(pcidev, PCI_SUBSYSTEM_VENDOR_ID, &vend); + if( w & 0x1) + save_68 |= 0x10; + outw(0xfffe, ioaddr + 0x64); /* tickly gpio 0.. */ + outw(0x0001, ioaddr + 0x68); + outw(0x0000, ioaddr + 0x60); + udelay(20); + outw(0x0001, ioaddr + 0x60); + mdelay(20); + + outw(save_68 | 0x1, ioaddr + 0x68); /* now restore .. */ + outw( (inw(ioaddr + 0x38) & 0xfffc)|0x1, ioaddr + 0x38); + outw( (inw(ioaddr + 0x3a) & 0xfffc)|0x1, ioaddr + 0x3a); + outw( (inw(ioaddr + 0x3c) & 0xfffc)|0x1, ioaddr + 0x3c); + + /* now the second codec */ + outw(0x0000, ioaddr+0x36); + outw(0xfff7, ioaddr + 0x64); + save_68 = inw(ioaddr+0x68); + outw(0x0009, ioaddr + 0x68); + outw(0x0001, ioaddr + 0x60); + udelay(20); + outw(0x0009, ioaddr + 0x60); + mdelay(500); /* .. ouch.. */ + outw( inw(ioaddr + 0x38) & 0xfffc, ioaddr + 0x38); + outw( inw(ioaddr + 0x3a) & 0xfffc, ioaddr + 0x3a); + outw( inw(ioaddr + 0x3c) & 0xfffc, ioaddr + 0x3c); + +#if 0 /* the loop here needs to be much better if we want it.. */ + M_printk("trying software reset\n"); + /* try and do a software reset */ + outb(0x80|0x7c, ioaddr + 0x30); + for (w=0; ; w++) { + if ((inw(ioaddr+ 0x30) & 1) == 0) { + if(inb(ioaddr + 0x32) !=0) break; + + outb(0x80|0x7d, ioaddr + 0x30); + if (((inw(ioaddr+ 0x30) & 1) == 0) && (inb(ioaddr + 0x32) !=0)) break; + outb(0x80|0x7f, ioaddr + 0x30); + if (((inw(ioaddr+ 0x30) & 1) == 0) && (inb(ioaddr + 0x32) !=0)) break; + } + + if( w > 10000) { + outb( inb(ioaddr + 0x37) | 0x08, ioaddr + 0x37); /* do a software reset */ + mdelay(500); /* oh my.. */ + outb( inb(ioaddr + 0x37) & ~0x08, ioaddr + 0x37); + udelay(1); + outw( 0x80, ioaddr+0x30); + for(w = 0 ; w < 10000; w++) { + if((inw(ioaddr + 0x30) & 1) ==0) break; + } + } + } +#endif + if ( vend == NEC_VERSA_SUBID1 || vend == NEC_VERSA_SUBID2) { + /* turn on external amp? */ + outw(0xf9ff, ioaddr + 0x64); + outw(inw(ioaddr+0x68) | 0x600, ioaddr + 0x68); + outw(0x0209, ioaddr + 0x60); + } + + /* Turn on the 978 docking chip. + First frob the "master output enable" bit, + then set most of the playback volume control registers to max. */ + outb(inb(ioaddr+0xc0)|(1<<5), ioaddr+0xc0); + outb(0xff, ioaddr+0xc3); + outb(0xff, ioaddr+0xc4); + outb(0xff, ioaddr+0xc6); + outb(0xff, ioaddr+0xc8); + outb(0x3f, ioaddr+0xcf); + outb(0x3f, ioaddr+0xd0); +} +/* + * Indirect register access. Not all registers are readable so we + * need to keep register state ourselves + */ + +#define WRITEABLE_MAP 0xEFFFFF +#define READABLE_MAP 0x64003F + +/* + * The Maestro engineers were a little indirection happy. These indirected + * registers themselves include indirect registers at another layer + */ + +static void __maestro_write(struct ess_card *card, u16 reg, u16 data) +{ + long ioaddr = card->iobase; + + outw(reg, ioaddr+0x02); + outw(data, ioaddr+0x00); + if( reg >= NR_IDRS) printk("maestro: IDR %d out of bounds!\n",reg); + else card->maestro_map[reg]=data; + +} + +static void maestro_write(struct ess_state *s, u16 reg, u16 data) +{ + unsigned long flags; + + check_suspend(s->card); + spin_lock_irqsave(&s->card->lock,flags); + + __maestro_write(s->card,reg,data); + + spin_unlock_irqrestore(&s->card->lock,flags); +} + +static u16 __maestro_read(struct ess_card *card, u16 reg) +{ + long ioaddr = card->iobase; + + outw(reg, ioaddr+0x02); + return card->maestro_map[reg]=inw(ioaddr+0x00); +} + +static u16 maestro_read(struct ess_state *s, u16 reg) +{ + if(READABLE_MAP & (1<card); + spin_lock_irqsave(&s->card->lock,flags); + + __maestro_read(s->card,reg); + + spin_unlock_irqrestore(&s->card->lock,flags); + } + return s->card->maestro_map[reg]; +} + +/* + * These routines handle accessing the second level indirections to the + * wave ram. + */ + +/* + * The register names are the ones ESS uses (see 104T31.ZIP) + */ + +#define IDR0_DATA_PORT 0x00 +#define IDR1_CRAM_POINTER 0x01 +#define IDR2_CRAM_DATA 0x02 +#define IDR3_WAVE_DATA 0x03 +#define IDR4_WAVE_PTR_LOW 0x04 +#define IDR5_WAVE_PTR_HI 0x05 +#define IDR6_TIMER_CTRL 0x06 +#define IDR7_WAVE_ROMRAM 0x07 + +static void apu_index_set(struct ess_card *card, u16 index) +{ + int i; + __maestro_write(card, IDR1_CRAM_POINTER, index); + for(i=0;i<1000;i++) + if(__maestro_read(card, IDR1_CRAM_POINTER)==index) + return; + printk(KERN_WARNING "maestro: APU register select failed.\n"); +} + +static void apu_data_set(struct ess_card *card, u16 data) +{ + int i; + for(i=0;i<1000;i++) + { + if(__maestro_read(card, IDR0_DATA_PORT)==data) + return; + __maestro_write(card, IDR0_DATA_PORT, data); + } +} + +/* + * This is the public interface for APU manipulation. It handles the + * interlock to avoid two APU writes in parallel etc. Don't diddle + * directly with the stuff above. + */ + +static void apu_set_register(struct ess_state *s, u16 channel, u8 reg, u16 data) +{ + unsigned long flags; + + check_suspend(s->card); + + if(channel&ESS_CHAN_HARD) + channel&=~ESS_CHAN_HARD; + else + { + if(channel>5) + printk("BAD CHANNEL %d.\n",channel); + else + channel = s->apu[channel]; + /* store based on real hardware apu/reg */ + s->card->apu_map[channel][reg]=data; + } + reg|=(channel<<4); + + /* hooray for double indirection!! */ + spin_lock_irqsave(&s->card->lock,flags); + + apu_index_set(s->card, reg); + apu_data_set(s->card, data); + + spin_unlock_irqrestore(&s->card->lock,flags); +} + +static u16 apu_get_register(struct ess_state *s, u16 channel, u8 reg) +{ + unsigned long flags; + u16 v; + + check_suspend(s->card); + + if(channel&ESS_CHAN_HARD) + channel&=~ESS_CHAN_HARD; + else + channel = s->apu[channel]; + + reg|=(channel<<4); + + spin_lock_irqsave(&s->card->lock,flags); + + apu_index_set(s->card, reg); + v=__maestro_read(s->card, IDR0_DATA_PORT); + + spin_unlock_irqrestore(&s->card->lock,flags); + return v; +} + + +/* + * The wavecache buffers between the APUs and + * pci bus mastering + */ + +static void wave_set_register(struct ess_state *s, u16 reg, u16 value) +{ + long ioaddr = s->card->iobase; + unsigned long flags; + check_suspend(s->card); + + spin_lock_irqsave(&s->card->lock,flags); + + outw(reg, ioaddr+0x10); + outw(value, ioaddr+0x12); + + spin_unlock_irqrestore(&s->card->lock,flags); +} + +static u16 wave_get_register(struct ess_state *s, u16 reg) +{ + long ioaddr = s->card->iobase; + unsigned long flags; + u16 value; + check_suspend(s->card); + + spin_lock_irqsave(&s->card->lock,flags); + outw(reg, ioaddr+0x10); + value=inw(ioaddr+0x12); + spin_unlock_irqrestore(&s->card->lock,flags); + + return value; +} + +static void sound_reset(int ioaddr) +{ + outw(0x2000, 0x18+ioaddr); + udelay(1); + outw(0x0000, 0x18+ioaddr); + udelay(1); +} + +/* sets the play formats of these apus, should be passed the already shifted format */ +static void set_apu_fmt(struct ess_state *s, int apu, int mode) +{ + int apu_fmt = 0x10; + + if(!(mode&ESS_FMT_16BIT)) apu_fmt+=0x20; + if((mode&ESS_FMT_STEREO)) apu_fmt+=0x10; + s->apu_mode[apu] = apu_fmt; + s->apu_mode[apu+1] = apu_fmt; +} + +/* this only fixes the output apu mode to be later set by start_dac and + company. output apu modes are set in ess_rec_setup */ +static void set_fmt(struct ess_state *s, unsigned char mask, unsigned char data) +{ + s->fmt = (s->fmt & mask) | data; + set_apu_fmt(s, 0, (s->fmt >> ESS_DAC_SHIFT) & ESS_FMT_MASK); +} + +/* this is off by a little bit.. */ +static u32 compute_rate(struct ess_state *s, u32 freq) +{ + u32 clock = clock_freq[s->card->card_type]; + + freq = (freq * clocking)/48000; + + if (freq == 48000) + return 0x10000; + + return ((freq / clock) <<16 )+ + (((freq % clock) << 16) / clock); +} + +static void set_dac_rate(struct ess_state *s, unsigned int rate) +{ + u32 freq; + int fmt = (s->fmt >> ESS_DAC_SHIFT) & ESS_FMT_MASK; + + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; + + s->ratedac = rate; + + if(! (fmt & ESS_FMT_16BIT) && !(fmt & ESS_FMT_STEREO)) + rate >>= 1; + +/* M_printk("computing dac rate %d with mode %d\n",rate,s->fmt);*/ + + freq = compute_rate(s, rate); + + /* Load the frequency, turn on 6dB */ + apu_set_register(s, 0, 2,(apu_get_register(s, 0, 2)&0x00FF)| + ( ((freq&0xFF)<<8)|0x10 )); + apu_set_register(s, 0, 3, freq>>8); + apu_set_register(s, 1, 2,(apu_get_register(s, 1, 2)&0x00FF)| + ( ((freq&0xFF)<<8)|0x10 )); + apu_set_register(s, 1, 3, freq>>8); +} + +static void set_adc_rate(struct ess_state *s, unsigned rate) +{ + u32 freq; + + /* Sample Rate conversion APUs don't like 0x10000 for their rate */ + if (rate > 47999) + rate = 47999; + if (rate < 4000) + rate = 4000; + + s->rateadc = rate; + + freq = compute_rate(s, rate); + + /* Load the frequency, turn on 6dB */ + apu_set_register(s, 2, 2,(apu_get_register(s, 2, 2)&0x00FF)| + ( ((freq&0xFF)<<8)|0x10 )); + apu_set_register(s, 2, 3, freq>>8); + apu_set_register(s, 3, 2,(apu_get_register(s, 3, 2)&0x00FF)| + ( ((freq&0xFF)<<8)|0x10 )); + apu_set_register(s, 3, 3, freq>>8); + + /* fix mixer rate at 48khz. and its _must_ be 0x10000. */ + freq = 0x10000; + + apu_set_register(s, 4, 2,(apu_get_register(s, 4, 2)&0x00FF)| + ( ((freq&0xFF)<<8)|0x10 )); + apu_set_register(s, 4, 3, freq>>8); + apu_set_register(s, 5, 2,(apu_get_register(s, 5, 2)&0x00FF)| + ( ((freq&0xFF)<<8)|0x10 )); + apu_set_register(s, 5, 3, freq>>8); +} + +/* Stop our host of recording apus */ +static inline void stop_adc(struct ess_state *s) +{ + /* XXX lets hope we don't have to lock around this */ + if (! (s->enable & ADC_RUNNING)) return; + + s->enable &= ~ADC_RUNNING; + apu_set_register(s, 2, 0, apu_get_register(s, 2, 0)&0xFF0F); + apu_set_register(s, 3, 0, apu_get_register(s, 3, 0)&0xFF0F); + apu_set_register(s, 4, 0, apu_get_register(s, 2, 0)&0xFF0F); + apu_set_register(s, 5, 0, apu_get_register(s, 3, 0)&0xFF0F); +} + +/* stop output apus */ +static void stop_dac(struct ess_state *s) +{ + /* XXX have to lock around this? */ + if (! (s->enable & DAC_RUNNING)) return; + + s->enable &= ~DAC_RUNNING; + apu_set_register(s, 0, 0, apu_get_register(s, 0, 0)&0xFF0F); + apu_set_register(s, 1, 0, apu_get_register(s, 1, 0)&0xFF0F); +} + +static void start_dac(struct ess_state *s) +{ + /* XXX locks? */ + if ( (s->dma_dac.mapped || s->dma_dac.count > 0) && + s->dma_dac.ready && + (! (s->enable & DAC_RUNNING)) ) { + + s->enable |= DAC_RUNNING; + + apu_set_register(s, 0, 0, + (apu_get_register(s, 0, 0)&0xFF0F)|s->apu_mode[0]); + + if((s->fmt >> ESS_DAC_SHIFT) & ESS_FMT_STEREO) + apu_set_register(s, 1, 0, + (apu_get_register(s, 1, 0)&0xFF0F)|s->apu_mode[1]); + } +} + +static void start_adc(struct ess_state *s) +{ + /* XXX locks? */ + if ((s->dma_adc.mapped || s->dma_adc.count < (signed)(s->dma_adc.dmasize - 2*s->dma_adc.fragsize)) + && s->dma_adc.ready && (! (s->enable & ADC_RUNNING)) ) { + + s->enable |= ADC_RUNNING; + apu_set_register(s, 2, 0, + (apu_get_register(s, 2, 0)&0xFF0F)|s->apu_mode[2]); + apu_set_register(s, 4, 0, + (apu_get_register(s, 4, 0)&0xFF0F)|s->apu_mode[4]); + + if( s->fmt & (ESS_FMT_STEREO << ESS_ADC_SHIFT)) { + apu_set_register(s, 3, 0, + (apu_get_register(s, 3, 0)&0xFF0F)|s->apu_mode[3]); + apu_set_register(s, 5, 0, + (apu_get_register(s, 5, 0)&0xFF0F)|s->apu_mode[5]); + } + + } +} + + +/* + * Native play back driver + */ + +/* the mode passed should be already shifted and masked */ +static void +ess_play_setup(struct ess_state *ess, int mode, u32 rate, void *buffer, int size) +{ + u32 pa; + u32 tmpval; + int high_apu = 0; + int channel; + + M_printk("mode=%d rate=%d buf=%p len=%d.\n", + mode, rate, buffer, size); + + /* all maestro sizes are in 16bit words */ + size >>=1; + + if(mode&ESS_FMT_STEREO) { + high_apu++; + /* only 16/stereo gets size divided */ + if(mode&ESS_FMT_16BIT) + size>>=1; + } + + for(channel=0; channel <= high_apu; channel++) + { + pa = virt_to_bus(buffer); + + /* set the wavecache control reg */ + tmpval = (pa - 0x10) & 0xFFF8; + if(!(mode & ESS_FMT_16BIT)) tmpval |= 4; + if(mode & ESS_FMT_STEREO) tmpval |= 2; + ess->apu_base[channel]=tmpval; + wave_set_register(ess, ess->apu[channel]<<3, tmpval); + + pa -= virt_to_bus(ess->card->dmapages); + pa>>=1; /* words */ + + /* base offset of dma calcs when reading the pointer + on the left one */ + if(!channel) ess->dma_dac.base = pa&0xFFFF; + + pa|=0x00400000; /* System RAM */ + + /* XXX the 16bit here might not be needed.. */ + if((mode & ESS_FMT_STEREO) && (mode & ESS_FMT_16BIT)) { + if(channel) + pa|=0x00800000; /* Stereo */ + pa>>=1; + } + +/* XXX think about endianess when writing these registers */ + M_printk("maestro: ess_play_setup: APU[%d] pa = 0x%x\n", ess->apu[channel], pa); + /* start of sample */ + apu_set_register(ess, channel, 4, ((pa>>16)&0xFF)<<8); + apu_set_register(ess, channel, 5, pa&0xFFFF); + /* sample end */ + apu_set_register(ess, channel, 6, (pa+size)&0xFFFF); + /* setting loop len == sample len */ + apu_set_register(ess, channel, 7, size); + + /* clear effects/env.. */ + apu_set_register(ess, channel, 8, 0x0000); + /* set amp now to 0xd0 (?), low byte is 'amplitude dest'? */ + apu_set_register(ess, channel, 9, 0xD000); + + /* clear routing stuff */ + apu_set_register(ess, channel, 11, 0x0000); + /* dma on, no envelopes, filter to all 1s) */ + apu_set_register(ess, channel, 0, 0x400F); + + if(mode&ESS_FMT_16BIT) + ess->apu_mode[channel]=0x10; + else + ess->apu_mode[channel]=0x30; + + if(mode&ESS_FMT_STEREO) { + /* set panning: left or right */ + apu_set_register(ess, channel, 10, 0x8F00 | (channel ? 0 : 0x10)); + ess->apu_mode[channel] += 0x10; + } else + apu_set_register(ess, channel, 10, 0x8F08); + } + + /* clear WP interrupts */ + outw(1, ess->card->iobase+0x04); + /* enable WP ints */ + outw(inw(ess->card->iobase+0x18)|4, ess->card->iobase+0x18); + + /* go team! */ + set_dac_rate(ess,rate); + start_dac(ess); +} + +/* + * Native record driver + */ + +/* again, passed mode is alrady shifted/masked */ +static void +ess_rec_setup(struct ess_state *ess, int mode, u32 rate, void *buffer, int size) +{ + int apu_step = 2; + int channel; + + M_printk("maestro: ess_rec_setup: mode=%d rate=%d buf=0x%p len=%d.\n", + mode, rate, buffer, size); + + /* all maestro sizes are in 16bit words */ + size >>=1; + + /* we're given the full size of the buffer, but + in stereo each channel will only use its half */ + if(mode&ESS_FMT_STEREO) { + size >>=1; + apu_step = 1; + } + + /* APU assignments: 2 = mono/left SRC + 3 = right SRC + 4 = mono/left Input Mixer + 5 = right Input Mixer */ + for(channel=2;channel<6;channel+=apu_step) + { + int i; + int bsize, route; + u32 pa; + u32 tmpval; + + /* data seems to flow from the codec, through an apu into + the 'mixbuf' bit of page, then through the SRC apu + and out to the real 'buffer'. ok. sure. */ + + if(channel & 0x04) { + /* ok, we're an input mixer going from adc + through the mixbuf to the other apus */ + + if(!(channel & 0x01)) { + pa = virt_to_bus(ess->mixbuf); + } else { + pa = virt_to_bus(ess->mixbuf + (PAGE_SIZE >> 4)); + } + + /* we source from a 'magic' apu */ + bsize = PAGE_SIZE >> 5; /* half of this channels alloc, in words */ + route = 0x14 + (channel - 4); /* parallel in crap, see maestro reg 0xC [8-11] */ + ess->apu_mode[channel] = 0x90; /* Input Mixer */ + + } else { + /* we're a rate converter taking + input from the input apus and outputing it to + system memory */ + if(!(channel & 0x01)) { + pa = virt_to_bus(buffer); + } else { + /* right channel records its split half. + *2 accommodates for rampant shifting earlier */ + pa = virt_to_bus(buffer + size*2); + } + + ess->apu_mode[channel] = 0xB0; /* Sample Rate Converter */ + + bsize = size; + /* get input from inputing apu */ + route = channel + 2; + } + + M_printk("maestro: ess_rec_setup: getting pa 0x%x from %d\n",pa,channel); + + /* set the wavecache control reg */ + tmpval = (pa - 0x10) & 0xFFF8; + ess->apu_base[channel]=tmpval; + wave_set_register(ess, ess->apu[channel]<<3, tmpval); + + pa -= virt_to_bus(ess->card->dmapages); + pa>>=1; /* words */ + + /* base offset of dma calcs when reading the pointer + on this left one */ + if(channel==2) ess->dma_adc.base = pa&0xFFFF; + + pa|=0x00400000; /* bit 22 -> System RAM */ + + M_printk("maestro: ess_rec_setup: APU[%d] pa = 0x%x size = 0x%x route = 0x%x\n", + ess->apu[channel], pa, bsize, route); + + /* Begin loading the APU */ + for(i=0;i<15;i++) /* clear all PBRs */ + apu_set_register(ess, channel, i, 0x0000); + + apu_set_register(ess, channel, 0, 0x400F); + + /* need to enable subgroups.. and we should probably + have different groups for different /dev/dsps.. */ + apu_set_register(ess, channel, 2, 0x8); + + /* Load the buffer into the wave engine */ + apu_set_register(ess, channel, 4, ((pa>>16)&0xFF)<<8); + /* XXX reg is little endian.. */ + apu_set_register(ess, channel, 5, pa&0xFFFF); + apu_set_register(ess, channel, 6, (pa+bsize)&0xFFFF); + apu_set_register(ess, channel, 7, bsize); + + /* clear effects/env.. */ + apu_set_register(ess, channel, 8, 0x00F0); + + /* amplitude now? sure. why not. */ + apu_set_register(ess, channel, 9, 0x0000); + + /* set filter tune, radius, polar pan */ + apu_set_register(ess, channel, 10, 0x8F08); + + /* route input */ + apu_set_register(ess, channel, 11, route); + } + + /* clear WP interrupts */ + outw(1, ess->card->iobase+0x04); + /* enable WP ints */ + outw(inw(ess->card->iobase+0x18)|4, ess->card->iobase+0x18); + + /* let 'er rip */ + set_adc_rate(ess,rate); + start_adc(ess); +} +/* --------------------------------------------------------------------- */ + +static void set_dmaa(struct ess_state *s, unsigned int addr, unsigned int count) +{ + M_printk("set_dmaa??\n"); +} + +static void set_dmac(struct ess_state *s, unsigned int addr, unsigned int count) +{ + M_printk("set_dmac??\n"); +} + +/* Playback pointer */ +static inline unsigned get_dmaa(struct ess_state *s) +{ + int offset; + + offset = apu_get_register(s,0,5); + +/* M_printk("dmaa: offset: %d, base: %d\n",offset,s->dma_dac.base); */ + + offset-=s->dma_dac.base; + + return (offset&0xFFFE)<<1; /* hardware is in words */ +} + +/* Record pointer */ +static inline unsigned get_dmac(struct ess_state *s) +{ + int offset; + + offset = apu_get_register(s,2,5); + +/* M_printk("dmac: offset: %d, base: %d\n",offset,s->dma_adc.base); */ + + /* The offset is an address not a position relative to base */ + offset-=s->dma_adc.base; + + return (offset&0xFFFE)<<1; /* hardware is in words */ +} + +/* + * Meet Bob, the timer... + */ + +static irqreturn_t ess_interrupt(int irq, void *dev_id, struct pt_regs *regs); + +static void stop_bob(struct ess_state *s) +{ + /* Mask IDR 11,17 */ + maestro_write(s, 0x11, maestro_read(s, 0x11)&~1); + maestro_write(s, 0x17, maestro_read(s, 0x17)&~1); +} + +/* eventually we could be clever and limit bob ints + to the frequency at which our smallest duration + chunks may expire */ +#define ESS_SYSCLK 50000000 +static void start_bob(struct ess_state *s) +{ + int prescale; + int divide; + + /* XXX make freq selector much smarter, see calc_bob_rate */ + int freq = 200; + + /* compute ideal interrupt frequency for buffer size & play rate */ + /* first, find best prescaler value to match freq */ + for(prescale=5;prescale<12;prescale++) + if(freq > (ESS_SYSCLK>>(prescale+9))) + break; + + /* next, back off prescaler whilst getting divider into optimum range */ + divide=1; + while((prescale > 5) && (divide<32)) + { + prescale--; + divide <<=1; + } + divide>>=1; + + /* now fine-tune the divider for best match */ + for(;divide<31;divide++) + if(freq >= ((ESS_SYSCLK>>(prescale+9))/(divide+1))) + break; + + /* divide = 0 is illegal, but don't let prescale = 4! */ + if(divide == 0) + { + divide++; + if(prescale>5) + prescale--; + } + + maestro_write(s, 6, 0x9000 | (prescale<<5) | divide); /* set reg */ + + /* Now set IDR 11/17 */ + maestro_write(s, 0x11, maestro_read(s, 0x11)|1); + maestro_write(s, 0x17, maestro_read(s, 0x17)|1); +} +/* --------------------------------------------------------------------- */ + +/* this quickly calculates the frequency needed for bob + and sets it if its different than what bob is + currently running at. its called often so + needs to be fairly quick. */ +#define BOB_MIN 50 +#define BOB_MAX 400 +static void calc_bob_rate(struct ess_state *s) { +#if 0 /* this thing tries to set the frequency of bob such that + there are 2 interrupts / buffer walked by the dac/adc. That + is probably very wrong for people who actually care about + mid buffer positioning. it should be calculated as bytes/interrupt + and that needs to be decided :) so for now just use the static 150 + in start_bob.*/ + + unsigned int dac_rate=2,adc_rate=1,newrate; + static int israte=-1; + + if (s->dma_dac.fragsize == 0) dac_rate = BOB_MIN; + else { + dac_rate = (2 * s->ratedac * sample_size[(s->fmt >> ESS_DAC_SHIFT) & ESS_FMT_MASK]) / + (s->dma_dac.fragsize) ; + } + + if (s->dma_adc.fragsize == 0) adc_rate = BOB_MIN; + else { + adc_rate = (2 * s->rateadc * sample_size[(s->fmt >> ESS_DAC_SHIFT) & ESS_FMT_MASK]) / + (s->dma_adc.fragsize) ; + } + + if(dac_rate > adc_rate) newrate = adc_rate; + else newrate=dac_rate; + + if(newrate > BOB_MAX) newrate = BOB_MAX; + else { + if(newrate < BOB_MIN) + newrate = BOB_MIN; + } + + if( israte != newrate) { + printk("dac: %d adc: %d rate: %d\n",dac_rate,adc_rate,israte); + israte=newrate; + } +#endif + +} + +static int +prog_dmabuf(struct ess_state *s, unsigned rec) +{ + struct dmabuf *db = rec ? &s->dma_adc : &s->dma_dac; + unsigned rate = rec ? s->rateadc : s->ratedac; + unsigned bytepersec; + unsigned bufs; + unsigned char fmt; + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + fmt = s->fmt; + if (rec) { + stop_adc(s); + fmt >>= ESS_ADC_SHIFT; + } else { + stop_dac(s); + fmt >>= ESS_DAC_SHIFT; + } + spin_unlock_irqrestore(&s->lock, flags); + fmt &= ESS_FMT_MASK; + + db->hwptr = db->swptr = db->total_bytes = db->count = db->error = db->endcleared = 0; + + /* this algorithm is a little nuts.. where did /1000 come from? */ + bytepersec = rate << sample_shift[fmt]; + bufs = PAGE_SIZE << db->buforder; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < bytepersec) + db->fragshift = ld2(bytepersec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(bytepersec/100/(db->subdivision ? db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + db->numfrag = bufs >> db->fragshift; + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->numfrag = bufs >> db->fragshift; + } + db->fragsize = 1 << db->fragshift; + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; + db->fragsamples = db->fragsize >> sample_shift[fmt]; + db->dmasize = db->numfrag << db->fragshift; + + M_printk("maestro: setup oss: numfrag: %d fragsize: %d dmasize: %d\n",db->numfrag,db->fragsize,db->dmasize); + + memset(db->rawbuf, (fmt & ESS_FMT_16BIT) ? 0 : 0x80, db->dmasize); + + spin_lock_irqsave(&s->lock, flags); + if (rec) + ess_rec_setup(s, fmt, s->rateadc, db->rawbuf, db->dmasize); + else + ess_play_setup(s, fmt, s->ratedac, db->rawbuf, db->dmasize); + + spin_unlock_irqrestore(&s->lock, flags); + db->ready = 1; + + return 0; +} + +static __inline__ void +clear_advance(struct ess_state *s) +{ + unsigned char c = ((s->fmt >> ESS_DAC_SHIFT) & ESS_FMT_16BIT) ? 0 : 0x80; + + unsigned char *buf = s->dma_dac.rawbuf; + unsigned bsize = s->dma_dac.dmasize; + unsigned bptr = s->dma_dac.swptr; + unsigned len = s->dma_dac.fragsize; + + if (bptr + len > bsize) { + unsigned x = bsize - bptr; + memset(buf + bptr, c, x); + /* account for wrapping? */ + bptr = 0; + len -= x; + } + memset(buf + bptr, c, len); +} + +/* call with spinlock held! */ +static void +ess_update_ptr(struct ess_state *s) +{ + unsigned hwptr; + int diff; + + /* update ADC pointer */ + if (s->dma_adc.ready) { + /* oh boy should this all be re-written. everything in the current code paths think + that the various counters/pointers are expressed in bytes to the user but we have + two apus doing stereo stuff so we fix it up here.. it propagates to all the various + counters from here. */ + if ( s->fmt & (ESS_FMT_STEREO << ESS_ADC_SHIFT)) { + hwptr = (get_dmac(s)*2) % s->dma_adc.dmasize; + } else { + hwptr = get_dmac(s) % s->dma_adc.dmasize; + } + diff = (s->dma_adc.dmasize + hwptr - s->dma_adc.hwptr) % s->dma_adc.dmasize; + s->dma_adc.hwptr = hwptr; + s->dma_adc.total_bytes += diff; + s->dma_adc.count += diff; + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + wake_up(&s->dma_adc.wait); + if (!s->dma_adc.mapped) { + if (s->dma_adc.count > (signed)(s->dma_adc.dmasize - ((3 * s->dma_adc.fragsize) >> 1))) { + /* FILL ME + wrindir(s, SV_CIENABLE, s->enable); */ + stop_adc(s); + /* brute force everyone back in sync, sigh */ + s->dma_adc.count = 0; + s->dma_adc.swptr = 0; + s->dma_adc.hwptr = 0; + s->dma_adc.error++; + } + } + } + /* update DAC pointer */ + if (s->dma_dac.ready) { + hwptr = get_dmaa(s) % s->dma_dac.dmasize; + /* the apu only reports the length it has seen, not the + length of the memory that has been used (the WP + knows that) */ + if ( ((s->fmt >> ESS_DAC_SHIFT) & ESS_FMT_MASK) == (ESS_FMT_STEREO|ESS_FMT_16BIT)) + hwptr<<=1; + + diff = (s->dma_dac.dmasize + hwptr - s->dma_dac.hwptr) % s->dma_dac.dmasize; +/* M_printk("updating dac: hwptr: %d diff: %d\n",hwptr,diff);*/ + s->dma_dac.hwptr = hwptr; + s->dma_dac.total_bytes += diff; + if (s->dma_dac.mapped) { + s->dma_dac.count += diff; + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) { + wake_up(&s->dma_dac.wait); + } + } else { + s->dma_dac.count -= diff; +/* M_printk("maestro: ess_update_ptr: diff: %d, count: %d\n", diff, s->dma_dac.count); */ + if (s->dma_dac.count <= 0) { + M_printk("underflow! diff: %d count: %d hw: %d sw: %d\n", diff, s->dma_dac.count, + hwptr, s->dma_dac.swptr); + /* FILL ME + wrindir(s, SV_CIENABLE, s->enable); */ + /* XXX how on earth can calling this with the lock held work.. */ + stop_dac(s); + /* brute force everyone back in sync, sigh */ + s->dma_dac.count = 0; + s->dma_dac.swptr = hwptr; + s->dma_dac.error++; + } else if (s->dma_dac.count <= (signed)s->dma_dac.fragsize && !s->dma_dac.endcleared) { + clear_advance(s); + s->dma_dac.endcleared = 1; + } + if (s->dma_dac.count + (signed)s->dma_dac.fragsize <= (signed)s->dma_dac.dmasize) { + wake_up(&s->dma_dac.wait); +/* printk("waking up DAC count: %d sw: %d hw: %d\n",s->dma_dac.count, s->dma_dac.swptr, + hwptr);*/ + } + } + } +} + +static irqreturn_t +ess_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct ess_state *s; + struct ess_card *c = (struct ess_card *)dev_id; + int i; + u32 event; + + if ( ! (event = inb(c->iobase+0x1A)) ) + return IRQ_NONE; + + outw(inw(c->iobase+4)&1, c->iobase+4); + +/* M_printk("maestro int: %x\n",event);*/ + if(event&(1<<6)) + { + int x; + enum {UP_EVT, DOWN_EVT, MUTE_EVT} vol_evt; + int volume; + + /* Figure out which volume control button was pushed, + based on differences from the default register + values. */ + x = inb(c->iobase+0x1c); + if (x&1) vol_evt = MUTE_EVT; + else if (((x>>1)&7) > 4) vol_evt = UP_EVT; + else vol_evt = DOWN_EVT; + + /* Reset the volume control registers. */ + outb(0x88, c->iobase+0x1c); + outb(0x88, c->iobase+0x1d); + outb(0x88, c->iobase+0x1e); + outb(0x88, c->iobase+0x1f); + + /* Deal with the button press in a hammer-handed + manner by adjusting the master mixer volume. */ + volume = c->mix.mixer_state[0] & 0xff; + if (vol_evt == UP_EVT) { + volume += 5; + if (volume > 100) + volume = 100; + } + else if (vol_evt == DOWN_EVT) { + volume -= 5; + if (volume < 0) + volume = 0; + } else { + /* vol_evt == MUTE_EVT */ + if (volume == 0) + volume = c->dock_mute_vol; + else { + c->dock_mute_vol = volume; + volume = 0; + } + } + set_mixer (c, 0, (volume << 8) | volume); + } + + /* Ack all the interrupts. */ + outb(0xFF, c->iobase+0x1A); + + /* + * Update the pointers for all APU's we are running. + */ + for(i=0;ichannels[i]; + if(s->dev_audio == -1) + break; + spin_lock(&s->lock); + ess_update_ptr(s); + spin_unlock(&s->lock); + } + return IRQ_HANDLED; +} + + +/* --------------------------------------------------------------------- */ + +static const char invalid_magic[] = KERN_CRIT "maestro: invalid magic value in %s\n"; + +#define VALIDATE_MAGIC(FOO,MAG) \ +({ \ + if (!(FOO) || (FOO)->magic != MAG) { \ + printk(invalid_magic,__FUNCTION__); \ + return -ENXIO; \ + } \ +}) + +#define VALIDATE_STATE(a) VALIDATE_MAGIC(a,ESS_STATE_MAGIC) +#define VALIDATE_CARD(a) VALIDATE_MAGIC(a,ESS_CARD_MAGIC) + +static void set_mixer(struct ess_card *card,unsigned int mixer, unsigned int val ) +{ + unsigned int left,right; + /* cleanse input a little */ + right = ((val >> 8) & 0xff) ; + left = (val & 0xff) ; + + if(right > 100) right = 100; + if(left > 100) left = 100; + + card->mix.mixer_state[mixer]=(right << 8) | left; + card->mix.write_mixer(card,mixer,left,right); +} + +static void +mixer_push_state(struct ess_card *card) +{ + int i; + for(i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++) { + if( ! supported_mixer(card,i)) continue; + + set_mixer(card,i,card->mix.mixer_state[i]); + } +} + +static int mixer_ioctl(struct ess_card *card, unsigned int cmd, unsigned long arg) +{ + int i, val=0; + unsigned long flags; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + VALIDATE_CARD(card); + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + memset(&info, 0, sizeof(info)); + strlcpy(info.id, card_names[card->card_type], sizeof(info.id)); + strlcpy(info.name, card_names[card->card_type], sizeof(info.name)); + info.modify_counter = card->mix.modcnt; + if (copy_to_user(argp, &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, card_names[card->card_type], sizeof(info.id)); + strlcpy(info.name, card_names[card->card_type], sizeof(info.name)); + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, p); + + if (_IOC_TYPE(cmd) != 'M' || _IOC_SIZE(cmd) != sizeof(int)) + return -EINVAL; + + if (_IOC_DIR(cmd) == _IOC_READ) { + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* give them the current record source */ + + if(!card->mix.recmask_io) { + val = 0; + } else { + spin_lock_irqsave(&card->lock, flags); + val = card->mix.recmask_io(card,1,0); + spin_unlock_irqrestore(&card->lock, flags); + } + break; + + case SOUND_MIXER_DEVMASK: /* give them the supported mixers */ + val = card->mix.supported_mixers; + break; + + case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */ + val = card->mix.record_sources; + break; + + case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */ + val = card->mix.stereo_mixers; + break; + + case SOUND_MIXER_CAPS: + val = SOUND_CAP_EXCL_INPUT; + break; + + default: /* read a specific mixer */ + i = _IOC_NR(cmd); + + if ( ! supported_mixer(card,i)) + return -EINVAL; + + /* do we ever want to touch the hardware? */ +/* spin_lock_irqsave(&card->lock, flags); + val = card->mix.read_mixer(card,i); + spin_unlock_irqrestore(&card->lock, flags);*/ + + val = card->mix.mixer_state[i]; +/* M_printk("returned 0x%x for mixer %d\n",val,i);*/ + + break; + } + return put_user(val, p); + } + + if (_IOC_DIR(cmd) != (_IOC_WRITE|_IOC_READ)) + return -EINVAL; + + card->mix.modcnt++; + + if (get_user(val, p)) + return -EFAULT; + + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + + if (!card->mix.recmask_io) return -EINVAL; + if(!val) return 0; + if(! (val &= card->mix.record_sources)) return -EINVAL; + + spin_lock_irqsave(&card->lock, flags); + card->mix.recmask_io(card,0,val); + spin_unlock_irqrestore(&card->lock, flags); + return 0; + + default: + i = _IOC_NR(cmd); + + if ( ! supported_mixer(card,i)) + return -EINVAL; + + spin_lock_irqsave(&card->lock, flags); + set_mixer(card,i,val); + spin_unlock_irqrestore(&card->lock, flags); + + return 0; + } +} + +/* --------------------------------------------------------------------- */ +static int ess_open_mixdev(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct ess_card *card = NULL; + struct pci_dev *pdev = NULL; + struct pci_driver *drvr; + + while ((pdev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL) { + drvr = pci_dev_driver (pdev); + if (drvr == &maestro_pci_driver) { + card = (struct ess_card*)pci_get_drvdata (pdev); + if (!card) + continue; + if (card->dev_mixer == minor) + break; + } + } + if (!card) + return -ENODEV; + file->private_data = card; + return nonseekable_open(inode, file); +} + +static int ess_release_mixdev(struct inode *inode, struct file *file) +{ + struct ess_card *card = (struct ess_card *)file->private_data; + + VALIDATE_CARD(card); + + return 0; +} + +static int ess_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct ess_card *card = (struct ess_card *)file->private_data; + + VALIDATE_CARD(card); + + return mixer_ioctl(card, cmd, arg); +} + +static /*const*/ struct file_operations ess_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = ess_ioctl_mixdev, + .open = ess_open_mixdev, + .release = ess_release_mixdev, +}; + +/* --------------------------------------------------------------------- */ + +static int drain_dac(struct ess_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait,current); + unsigned long flags; + int count; + signed long tmo; + + if (s->dma_dac.mapped || !s->dma_dac.ready) + return 0; + current->state = TASK_INTERRUPTIBLE; + add_wait_queue(&s->dma_dac.wait, &wait); + for (;;) { + /* XXX uhm.. questionable locking*/ + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_dac.wait, &wait); + current->state = TASK_RUNNING; + return -EBUSY; + } + tmo = (count * HZ) / s->ratedac; + tmo >>= sample_shift[(s->fmt >> ESS_DAC_SHIFT) & ESS_FMT_MASK]; + /* XXX this is just broken. someone is waking us up alot, or schedule_timeout is broken. + or something. who cares. - zach */ + if (!schedule_timeout(tmo ? tmo : 1) && tmo) + M_printk(KERN_DEBUG "maestro: dma timed out?? %ld\n",jiffies); + } + remove_wait_queue(&s->dma_dac.wait, &wait); + current->state = TASK_RUNNING; + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +/* --------------------------------------------------------------------- */ +/* Zach sez: "god this is gross.." */ +static int +comb_stereo(unsigned char *real_buffer,unsigned char *tmp_buffer, int offset, + int count, int bufsize) +{ + /* No such thing as stereo recording, so we + use dual input mixers. which means we have to + combine mono to stereo buffer. yuck. + + but we don't have to be able to work a byte at a time..*/ + + unsigned char *so,*left,*right; + int i; + + so = tmp_buffer; + left = real_buffer + offset; + right = real_buffer + bufsize/2 + offset; + +/* M_printk("comb_stereo writing %d to %p from %p and %p, offset: %d size: %d\n",count/2, tmp_buffer,left,right,offset,bufsize);*/ + + for(i=count/4; i ; i--) { + (*(so+2)) = *(right++); + (*(so+3)) = *(right++); + (*so) = *(left++); + (*(so+1)) = *(left++); + so+=4; + } + + return 0; +} + +/* in this loop, dma_adc.count signifies the amount of data thats waiting + to be copied to the user's buffer. it is filled by the interrupt + handler and drained by this loop. */ +static ssize_t +ess_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct ess_state *s = (struct ess_state *)file->private_data; + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + unsigned char *combbuf = NULL; + + VALIDATE_STATE(s); + if (s->dma_adc.mapped) + return -ENXIO; + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + if(!(combbuf = kmalloc(count,GFP_KERNEL))) + return -ENOMEM; + ret = 0; + + calc_bob_rate(s); + + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + /* remember, all these things are expressed in bytes to be + sent to the user.. hence the evil / 2 down below */ + swptr = s->dma_adc.swptr; + cnt = s->dma_adc.dmasize-swptr; + if (s->dma_adc.count < cnt) + cnt = s->dma_adc.count; + spin_unlock_irqrestore(&s->lock, flags); + + if (cnt > count) + cnt = count; + + if ( cnt > 0 ) cnt &= ~3; + + if (cnt <= 0) { + start_adc(s); + if (file->f_flags & O_NONBLOCK) + { + ret = ret ? ret : -EAGAIN; + goto rec_return_free; + } + if (!interruptible_sleep_on_timeout(&s->dma_adc.wait, HZ)) { + if(! s->card->in_suspend) printk(KERN_DEBUG "maestro: read: chip lockup? dmasz %u fragsz %u count %i hwptr %u swptr %u\n", + s->dma_adc.dmasize, s->dma_adc.fragsize, s->dma_adc.count, + s->dma_adc.hwptr, s->dma_adc.swptr); + stop_adc(s); + spin_lock_irqsave(&s->lock, flags); + set_dmac(s, virt_to_bus(s->dma_adc.rawbuf), s->dma_adc.numfrag << s->dma_adc.fragshift); + /* program enhanced mode registers */ + /* FILL ME */ +/* wrindir(s, SV_CIDMACBASECOUNT1, (s->dma_adc.fragsamples-1) >> 8); + wrindir(s, SV_CIDMACBASECOUNT0, s->dma_adc.fragsamples-1); */ + s->dma_adc.count = s->dma_adc.hwptr = s->dma_adc.swptr = 0; + spin_unlock_irqrestore(&s->lock, flags); + } + if (signal_pending(current)) + { + ret = ret ? ret : -ERESTARTSYS; + goto rec_return_free; + } + continue; + } + + if(s->fmt & (ESS_FMT_STEREO << ESS_ADC_SHIFT)) { + /* swptr/2 so that we know the real offset in each apu's buffer */ + comb_stereo(s->dma_adc.rawbuf,combbuf,swptr/2,cnt,s->dma_adc.dmasize); + if (copy_to_user(buffer, combbuf, cnt)) { + ret = ret ? ret : -EFAULT; + goto rec_return_free; + } + } else { + if (copy_to_user(buffer, s->dma_adc.rawbuf + swptr, cnt)) { + ret = ret ? ret : -EFAULT; + goto rec_return_free; + } + } + + swptr = (swptr + cnt) % s->dma_adc.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_adc.swptr = swptr; + s->dma_adc.count -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + start_adc(s); + } + +rec_return_free: + if(combbuf) kfree(combbuf); + return ret; +} + +static ssize_t +ess_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct ess_state *s = (struct ess_state *)file->private_data; + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_dac.mapped) + return -ENXIO; + if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0))) + return ret; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; + + calc_bob_rate(s); + + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + + if (s->dma_dac.count < 0) { + s->dma_dac.count = 0; + s->dma_dac.swptr = s->dma_dac.hwptr; + } + swptr = s->dma_dac.swptr; + + cnt = s->dma_dac.dmasize-swptr; + + if (s->dma_dac.count + cnt > s->dma_dac.dmasize) + cnt = s->dma_dac.dmasize - s->dma_dac.count; + + spin_unlock_irqrestore(&s->lock, flags); + + if (cnt > count) + cnt = count; + + if (cnt <= 0) { + start_dac(s); + if (file->f_flags & O_NONBLOCK) { + if(!ret) ret = -EAGAIN; + goto return_free; + } + if (!interruptible_sleep_on_timeout(&s->dma_dac.wait, HZ)) { + if(! s->card->in_suspend) printk(KERN_DEBUG "maestro: write: chip lockup? dmasz %u fragsz %u count %i hwptr %u swptr %u\n", + s->dma_dac.dmasize, s->dma_dac.fragsize, s->dma_dac.count, + s->dma_dac.hwptr, s->dma_dac.swptr); + stop_dac(s); + spin_lock_irqsave(&s->lock, flags); + set_dmaa(s, virt_to_bus(s->dma_dac.rawbuf), s->dma_dac.numfrag << s->dma_dac.fragshift); + /* program enhanced mode registers */ +/* wrindir(s, SV_CIDMAABASECOUNT1, (s->dma_dac.fragsamples-1) >> 8); + wrindir(s, SV_CIDMAABASECOUNT0, s->dma_dac.fragsamples-1); */ + /* FILL ME */ + s->dma_dac.count = s->dma_dac.hwptr = s->dma_dac.swptr = 0; + spin_unlock_irqrestore(&s->lock, flags); + } + if (signal_pending(current)) { + if (!ret) ret = -ERESTARTSYS; + goto return_free; + } + continue; + } + if (copy_from_user(s->dma_dac.rawbuf + swptr, buffer, cnt)) { + if (!ret) ret = -EFAULT; + goto return_free; + } +/* printk("wrote %d bytes at sw: %d cnt: %d while hw: %d\n",cnt, swptr, s->dma_dac.count, s->dma_dac.hwptr);*/ + + swptr = (swptr + cnt) % s->dma_dac.dmasize; + + spin_lock_irqsave(&s->lock, flags); + s->dma_dac.swptr = swptr; + s->dma_dac.count += cnt; + s->dma_dac.endcleared = 0; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + start_dac(s); + } +return_free: + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int ess_poll(struct file *file, struct poll_table_struct *wait) +{ + struct ess_state *s = (struct ess_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + +/* In 0.14 prog_dmabuf always returns success anyway ... */ + if (file->f_mode & FMODE_WRITE) { + if (!s->dma_dac.ready && prog_dmabuf(s, 0)) + return 0; + } + if (file->f_mode & FMODE_READ) { + if (!s->dma_adc.ready && prog_dmabuf(s, 1)) + return 0; + } + + if (file->f_mode & FMODE_WRITE) + poll_wait(file, &s->dma_dac.wait, wait); + if (file->f_mode & FMODE_READ) + poll_wait(file, &s->dma_adc.wait, wait); + spin_lock_irqsave(&s->lock, flags); + ess_update_ptr(s); + if (file->f_mode & FMODE_READ) { + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->dma_dac.mapped) { + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)s->dma_dac.dmasize >= s->dma_dac.count + (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int ess_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct ess_state *s = (struct ess_state *)file->private_data; + struct dmabuf *db; + int ret = -EINVAL; + unsigned long size; + + VALIDATE_STATE(s); + lock_kernel(); + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf(s, 1)) != 0) + goto out; + db = &s->dma_dac; + } else +#if 0 + /* if we can have the wp/wc do the combining + we can turn this back on. */ + if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf(s, 0)) != 0) + goto out; + db = &s->dma_adc; + } else +#endif + goto out; + ret = -EINVAL; + if (vma->vm_pgoff != 0) + goto out; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << db->buforder)) + goto out; + ret = -EAGAIN; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(db->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + goto out; + db->mapped = 1; + ret = 0; +out: + unlock_kernel(); + return ret; +} + +static int ess_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct ess_state *s = (struct ess_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int val, mapped, ret; + unsigned char fmtm, fmtd; + void __user *argp = (void __user *)arg; + int __user *p = argp; + +/* printk("maestro: ess_ioctl: cmd %d\n", cmd);*/ + + 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: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_SYNC: + if (file->f_mode & FMODE_WRITE) + return drain_dac(s, file->f_flags & O_NONBLOCK); + return 0; + + case SNDCTL_DSP_SETDUPLEX: + /* XXX fix */ + return 0; + + case SNDCTL_DSP_GETCAPS: + return put_user(DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER | DSP_CAP_MMAP, p); + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + synchronize_irq(s->card->pcidev->irq); + s->dma_dac.swptr = s->dma_dac.hwptr = s->dma_dac.count = s->dma_dac.total_bytes = 0; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(s->card->pcidev->irq); + s->dma_adc.swptr = s->dma_adc.hwptr = s->dma_adc.count = s->dma_adc.total_bytes = 0; + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + set_adc_rate(s, val); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + set_dac_rate(s, val); + } + } + return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, p); + + case SNDCTL_DSP_STEREO: + if (get_user(val, p)) + return -EFAULT; + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val) + fmtd |= ESS_FMT_STEREO << ESS_ADC_SHIFT; + else + fmtm &= ~(ESS_FMT_STEREO << ESS_ADC_SHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val) + fmtd |= ESS_FMT_STEREO << ESS_DAC_SHIFT; + else + fmtm &= ~(ESS_FMT_STEREO << ESS_DAC_SHIFT); + } + set_fmt(s, fmtm, fmtd); + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + if (val != 0) { + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val >= 2) + fmtd |= ESS_FMT_STEREO << ESS_ADC_SHIFT; + else + fmtm &= ~(ESS_FMT_STEREO << ESS_ADC_SHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val >= 2) + fmtd |= ESS_FMT_STEREO << ESS_DAC_SHIFT; + else + fmtm &= ~(ESS_FMT_STEREO << ESS_DAC_SHIFT); + } + set_fmt(s, fmtm, fmtd); + } + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (ESS_FMT_STEREO << ESS_ADC_SHIFT) + : (ESS_FMT_STEREO << ESS_DAC_SHIFT))) ? 2 : 1, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_U8|AFMT_S16_LE, p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, p)) + return -EFAULT; + if (val != AFMT_QUERY) { + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + /* fixed at 16bit for now */ + fmtd |= ESS_FMT_16BIT << ESS_ADC_SHIFT; +#if 0 + if (val == AFMT_S16_LE) + fmtd |= ESS_FMT_16BIT << ESS_ADC_SHIFT; + else + fmtm &= ~(ESS_FMT_16BIT << ESS_ADC_SHIFT); +#endif + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val == AFMT_S16_LE) + fmtd |= ESS_FMT_16BIT << ESS_DAC_SHIFT; + else + fmtm &= ~(ESS_FMT_16BIT << ESS_DAC_SHIFT); + } + set_fmt(s, fmtm, fmtd); + } + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? + (ESS_FMT_16BIT << ESS_ADC_SHIFT) + : (ESS_FMT_16BIT << ESS_DAC_SHIFT))) ? + AFMT_S16_LE : + AFMT_U8, + p); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if ((file->f_mode & FMODE_READ) && (s->enable & ADC_RUNNING)) + val |= PCM_ENABLE_INPUT; + if ((file->f_mode & FMODE_WRITE) && (s->enable & DAC_RUNNING)) + val |= PCM_ENABLE_OUTPUT; + return put_user(val, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + if (val & PCM_ENABLE_INPUT) { + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + 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(s, 0))) + 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 && (ret = prog_dmabuf(s, 0))) + return ret; + spin_lock_irqsave(&s->lock, flags); + ess_update_ptr(s); + abinfo.fragsize = s->dma_dac.fragsize; + abinfo.bytes = s->dma_dac.dmasize - s->dma_dac.count; + abinfo.fragstotal = s->dma_dac.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + spin_lock_irqsave(&s->lock, flags); + ess_update_ptr(s); + 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(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0))) + return ret; + spin_lock_irqsave(&s->lock, flags); + ess_update_ptr(s); + val = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + return put_user(val, p); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + spin_lock_irqsave(&s->lock, flags); + ess_update_ptr(s); + cinfo.bytes = s->dma_adc.total_bytes; + cinfo.blocks = s->dma_adc.count >> s->dma_adc.fragshift; + 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); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0))) + return ret; + spin_lock_irqsave(&s->lock, flags); + ess_update_ptr(s); + cinfo.bytes = s->dma_dac.total_bytes; + 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); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + if ((val = prog_dmabuf(s, 0))) + return val; + return put_user(s->dma_dac.fragsize, p); + } + if ((val = prog_dmabuf(s, 1))) + return val; + return put_user(s->dma_adc.fragsize, p); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + M_printk("maestro: SETFRAGMENT: %0x\n",val); + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = val & 0xffff; + s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_adc.ossfragshift < 4) + s->dma_adc.ossfragshift = 4; + if (s->dma_adc.ossfragshift > 15) + s->dma_adc.ossfragshift = 15; + if (s->dma_adc.ossmaxfrags < 4) + s->dma_adc.ossmaxfrags = 4; + } + if (file->f_mode & FMODE_WRITE) { + s->dma_dac.ossfragshift = val & 0xffff; + s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac.ossfragshift < 4) + s->dma_dac.ossfragshift = 4; + if (s->dma_dac.ossfragshift > 15) + s->dma_dac.ossfragshift = 15; + if (s->dma_dac.ossmaxfrags < 4) + s->dma_dac.ossmaxfrags = 4; + } + return 0; + + 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, p)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + if (file->f_mode & FMODE_READ) + s->dma_adc.subdivision = val; + if (file->f_mode & FMODE_WRITE) + s->dma_dac.subdivision = val; + return 0; + + case SOUND_PCM_READ_RATE: + return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, p); + + case SOUND_PCM_READ_CHANNELS: + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (ESS_FMT_STEREO << ESS_ADC_SHIFT) + : (ESS_FMT_STEREO << ESS_DAC_SHIFT))) ? 2 : 1, p); + + case SOUND_PCM_READ_BITS: + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (ESS_FMT_16BIT << ESS_ADC_SHIFT) + : (ESS_FMT_16BIT << ESS_DAC_SHIFT))) ? 16 : 8, p); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + + } + return -EINVAL; +} + +static void +set_base_registers(struct ess_state *s,void *vaddr) +{ + unsigned long packed_phys = virt_to_bus(vaddr)>>12; + wave_set_register(s, 0x01FC , packed_phys); + wave_set_register(s, 0x01FD , packed_phys); + wave_set_register(s, 0x01FE , packed_phys); + wave_set_register(s, 0x01FF , packed_phys); +} + +/* + * this guy makes sure we're in the right power + * state for what we want to be doing + */ +static void maestro_power(struct ess_card *card, int tostate) +{ + u16 active_mask = acpi_state_mask[tostate]; + u8 state; + + if(!use_pm) return; + + pci_read_config_byte(card->pcidev, card->power_regs+0x4, &state); + state&=3; + + /* make sure we're in the right state */ + if(state != tostate) { + M_printk(KERN_WARNING "maestro: dev %02x:%02x.%x switching from D%d to D%d\n", + card->pcidev->bus->number, + PCI_SLOT(card->pcidev->devfn), + PCI_FUNC(card->pcidev->devfn), + state,tostate); + pci_write_config_byte(card->pcidev, card->power_regs+0x4, tostate); + } + + /* and make sure the units we care about are on + XXX we might want to do this before state flipping? */ + pci_write_config_word(card->pcidev, 0x54, ~ active_mask); + pci_write_config_word(card->pcidev, 0x56, ~ active_mask); +} + +/* we allocate a large power of two for all our memory. + this is cut up into (not to scale :): + |silly fifo word | 512byte mixbuf per adc | dac/adc * channels | +*/ +static int +allocate_buffers(struct ess_state *s) +{ + void *rawbuf=NULL; + int order,i; + struct page *page, *pend; + + /* alloc as big a chunk as we can */ + for (order = (dsps_order + (16-PAGE_SHIFT) + 1); order >= (dsps_order + 2 + 1); order--) + if((rawbuf = (void *)__get_free_pages(GFP_KERNEL|GFP_DMA, order))) + break; + + if (!rawbuf) + return 1; + + M_printk("maestro: allocated %ld (%d) bytes at %p\n",PAGE_SIZE<card->dmapages = rawbuf; + s->card->dmaorder = order; + + for(i=0;icard->channels[i]; + + if(ess->dev_audio == -1) + continue; + + ess->dma_dac.ready = s->dma_dac.mapped = 0; + ess->dma_adc.ready = s->dma_adc.mapped = 0; + ess->dma_adc.buforder = ess->dma_dac.buforder = order - 1 - dsps_order - 1; + + /* offset dac and adc buffers starting half way through and then at each [da][ad]c's + order's intervals.. */ + ess->dma_dac.rawbuf = rawbuf + (PAGE_SIZE<<(order-1)) + (i * ( PAGE_SIZE << (ess->dma_dac.buforder + 1 ))); + ess->dma_adc.rawbuf = ess->dma_dac.rawbuf + ( PAGE_SIZE << ess->dma_dac.buforder); + /* offset mixbuf by a mixbuf so that the lame status fifo can + happily scribble away.. */ + ess->mixbuf = rawbuf + (512 * (i+1)); + + M_printk("maestro: setup apu %d: dac: %p adc: %p mix: %p\n",i,ess->dma_dac.rawbuf, + ess->dma_adc.rawbuf, ess->mixbuf); + + } + + /* now mark the pages as reserved; otherwise remap_pfn_range doesn't do what we want */ + pend = virt_to_page(rawbuf + (PAGE_SIZE << order) - 1); + for (page = virt_to_page(rawbuf); page <= pend; page++) + SetPageReserved(page); + + return 0; +} +static void +free_buffers(struct ess_state *s) +{ + struct page *page, *pend; + + s->dma_dac.rawbuf = s->dma_adc.rawbuf = NULL; + s->dma_dac.mapped = s->dma_adc.mapped = 0; + s->dma_dac.ready = s->dma_adc.ready = 0; + + M_printk("maestro: freeing %p\n",s->card->dmapages); + /* undo marking the pages as reserved */ + + pend = virt_to_page(s->card->dmapages + (PAGE_SIZE << s->card->dmaorder) - 1); + for (page = virt_to_page(s->card->dmapages); page <= pend; page++) + ClearPageReserved(page); + + free_pages((unsigned long)s->card->dmapages,s->card->dmaorder); + s->card->dmapages = NULL; +} + +static int +ess_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct ess_state *s = NULL; + unsigned char fmtm = ~0, fmts = 0; + struct pci_dev *pdev = NULL; + /* + * Scan the cards and find the channel. We only + * do this at open time so it is ok + */ + + while ((pdev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL) { + struct ess_card *c; + struct pci_driver *drvr; + + drvr = pci_dev_driver (pdev); + if (drvr == &maestro_pci_driver) { + int i; + struct ess_state *sp; + + c = (struct ess_card*)pci_get_drvdata (pdev); + if (!c) + continue; + for(i=0;ichannels[i]; + if(sp->dev_audio < 0) + continue; + if((sp->dev_audio ^ minor) & ~0xf) + continue; + s=sp; + } + } + } + if (!s) + return -ENODEV; + + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & file->f_mode) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EWOULDBLOCK; + } + up(&s->open_sem); + interruptible_sleep_on(&s->open_wait); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + + /* under semaphore.. */ + if ((s->card->dmapages==NULL) && allocate_buffers(s)) { + up(&s->open_sem); + return -ENOMEM; + } + + /* we're covered by the open_sem */ + if( ! s->card->dsps_open ) { + maestro_power(s->card,ACPI_D0); + start_bob(s); + } + s->card->dsps_open++; + M_printk("maestro: open, %d bobs now\n",s->card->dsps_open); + + /* ok, lets write WC base regs now that we've + powered up the chip */ + M_printk("maestro: writing 0x%lx (bus 0x%lx) to the wp\n",virt_to_bus(s->card->dmapages), + ((virt_to_bus(s->card->dmapages))&0xFFE00000)>>12); + set_base_registers(s,s->card->dmapages); + + if (file->f_mode & FMODE_READ) { +/* + fmtm &= ~((ESS_FMT_STEREO | ESS_FMT_16BIT) << ESS_ADC_SHIFT); + if ((minor & 0xf) == SND_DEV_DSP16) + fmts |= ESS_FMT_16BIT << ESS_ADC_SHIFT; */ + + fmtm &= ~((ESS_FMT_STEREO|ESS_FMT_16BIT) << ESS_ADC_SHIFT); + fmts = (ESS_FMT_STEREO|ESS_FMT_16BIT) << ESS_ADC_SHIFT; + + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = s->dma_adc.subdivision = 0; + set_adc_rate(s, 8000); + } + if (file->f_mode & FMODE_WRITE) { + fmtm &= ~((ESS_FMT_STEREO | ESS_FMT_16BIT) << ESS_DAC_SHIFT); + if ((minor & 0xf) == SND_DEV_DSP16) + fmts |= ESS_FMT_16BIT << ESS_DAC_SHIFT; + + s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = s->dma_dac.subdivision = 0; + set_dac_rate(s, 8000); + } + set_fmt(s, fmtm, fmts); + s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int +ess_release(struct inode *inode, struct file *file) +{ + struct ess_state *s = (struct ess_state *)file->private_data; + + VALIDATE_STATE(s); + lock_kernel(); + if (file->f_mode & FMODE_WRITE) + drain_dac(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem); + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + } + + s->open_mode &= (~file->f_mode) & (FMODE_READ|FMODE_WRITE); + /* we're covered by the open_sem */ + M_printk("maestro: %d dsps now alive\n",s->card->dsps_open-1); + if( --s->card->dsps_open <= 0) { + s->card->dsps_open = 0; + stop_bob(s); + free_buffers(s); + maestro_power(s->card,ACPI_D2); + } + up(&s->open_sem); + wake_up(&s->open_wait); + unlock_kernel(); + return 0; +} + +static struct file_operations ess_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = ess_read, + .write = ess_write, + .poll = ess_poll, + .ioctl = ess_ioctl, + .mmap = ess_mmap, + .open = ess_open, + .release = ess_release, +}; + +static int +maestro_config(struct ess_card *card) +{ + struct pci_dev *pcidev = card->pcidev; + struct ess_state *ess = &card->channels[0]; + int apu,iobase = card->iobase; + u16 w; + u32 n; + + /* We used to muck around with pci config space that + * we had no business messing with. We don't know enough + * about the machine to know which DMA mode is appropriate, + * etc. We were guessing wrong on some machines and making + * them unhappy. We now trust in the BIOS to do things right, + * which almost certainly means a new host of problems will + * arise with broken BIOS implementations. screw 'em. + * We're already intolerant of machines that don't assign + * IRQs. + */ + + /* do config work at full power */ + maestro_power(card,ACPI_D0); + + pci_read_config_word(pcidev, 0x50, &w); + + w&=~(1<<5); /* Don't swap left/right (undoc)*/ + + pci_write_config_word(pcidev, 0x50, w); + + pci_read_config_word(pcidev, 0x52, &w); + w&=~(1<<15); /* Turn off internal clock multiplier */ + /* XXX how do we know which to use? */ + w&=~(1<<14); /* External clock */ + + w|= (1<<7); /* Hardware volume control on */ + w|= (1<<6); /* Debounce off: easier to push the HWV buttons. */ + w&=~(1<<5); /* GPIO 4:5 */ + w|= (1<<4); /* Disconnect from the CHI. Enabling this made a dell 7500 work. */ + w&=~(1<<2); /* MIDI fix off (undoc) */ + w&=~(1<<1); /* reserved, always write 0 */ + pci_write_config_word(pcidev, 0x52, w); + + /* + * Legacy mode + */ + + pci_read_config_word(pcidev, 0x40, &w); + w|=(1<<15); /* legacy decode off */ + w&=~(1<<14); /* Disable SIRQ */ + w&=~(0x1f); /* disable mpu irq/io, game port, fm, SB */ + + pci_write_config_word(pcidev, 0x40, w); + + /* Set up 978 docking control chip. */ + pci_read_config_word(pcidev, 0x58, &w); + w|=1<<2; /* Enable 978. */ + w|=1<<3; /* Turn on 978 hardware volume control. */ + w&=~(1<<11); /* Turn on 978 mixer volume control. */ + pci_write_config_word(pcidev, 0x58, w); + + sound_reset(iobase); + + /* + * Ring Bus Setup + */ + + /* setup usual 0x34 stuff.. 0x36 may be chip specific */ + outw(0xC090, iobase+0x34); /* direct sound, stereo */ + udelay(20); + outw(0x3000, iobase+0x36); /* direct sound, stereo */ + udelay(20); + + + /* + * Reset the CODEC + */ + + maestro_ac97_reset(iobase,pcidev); + + /* + * Ring Bus Setup + */ + + n=inl(iobase+0x34); + n&=~0xF000; + n|=12<<12; /* Direct Sound, Stereo */ + outl(n, iobase+0x34); + + n=inl(iobase+0x34); + n&=~0x0F00; /* Modem off */ + outl(n, iobase+0x34); + + n=inl(iobase+0x34); + n&=~0x00F0; + n|=9<<4; /* DAC, Stereo */ + outl(n, iobase+0x34); + + n=inl(iobase+0x34); + n&=~0x000F; /* ASSP off */ + outl(n, iobase+0x34); + + n=inl(iobase+0x34); + n|=(1<<29); /* Enable ring bus */ + outl(n, iobase+0x34); + + n=inl(iobase+0x34); + n|=(1<<28); /* Enable serial bus */ + outl(n, iobase+0x34); + + n=inl(iobase+0x34); + n&=~0x00F00000; /* MIC off */ + outl(n, iobase+0x34); + + n=inl(iobase+0x34); + n&=~0x000F0000; /* I2S off */ + outl(n, iobase+0x34); + + + w=inw(iobase+0x18); + w&=~(1<<7); /* ClkRun off */ + outw(w, iobase+0x18); + + w=inw(iobase+0x18); + w&=~(1<<6); /* Hardware volume control interrupt off... for now. */ + outw(w, iobase+0x18); + + w=inw(iobase+0x18); + w&=~(1<<4); /* ASSP irq off */ + outw(w, iobase+0x18); + + w=inw(iobase+0x18); + w&=~(1<<3); /* ISDN irq off */ + outw(w, iobase+0x18); + + w=inw(iobase+0x18); + w|=(1<<2); /* Direct Sound IRQ on */ + outw(w, iobase+0x18); + + w=inw(iobase+0x18); + w&=~(1<<1); /* MPU401 IRQ off */ + outw(w, iobase+0x18); + + w=inw(iobase+0x18); + w|=(1<<0); /* SB IRQ on */ + outw(w, iobase+0x18); + + /* Set hardware volume control registers to midpoints. + We can tell which button was pushed based on how they change. */ + outb(0x88, iobase+0x1c); + outb(0x88, iobase+0x1d); + outb(0x88, iobase+0x1e); + outb(0x88, iobase+0x1f); + + /* it appears some maestros (dell 7500) only work if these are set, + regardless of whether we use the assp or not. */ + + outb(0, iobase+0xA4); + outb(3, iobase+0xA2); + outb(0, iobase+0xA6); + + for(apu=0;apu<16;apu++) + { + /* Write 0 into the buffer area 0x1E0->1EF */ + outw(0x01E0+apu, 0x10+iobase); + outw(0x0000, 0x12+iobase); + + /* + * The 1.10 test program seem to write 0 into the buffer area + * 0x1D0-0x1DF too. + */ + outw(0x01D0+apu, 0x10+iobase); + outw(0x0000, 0x12+iobase); + } + +#if 1 + wave_set_register(ess, IDR7_WAVE_ROMRAM, + (wave_get_register(ess, IDR7_WAVE_ROMRAM)&0xFF00)); + wave_set_register(ess, IDR7_WAVE_ROMRAM, + wave_get_register(ess, IDR7_WAVE_ROMRAM)|0x100); + wave_set_register(ess, IDR7_WAVE_ROMRAM, + wave_get_register(ess, IDR7_WAVE_ROMRAM)&~0x200); + wave_set_register(ess, IDR7_WAVE_ROMRAM, + wave_get_register(ess, IDR7_WAVE_ROMRAM)|~0x400); +#else + maestro_write(ess, IDR7_WAVE_ROMRAM, + (maestro_read(ess, IDR7_WAVE_ROMRAM)&0xFF00)); + maestro_write(ess, IDR7_WAVE_ROMRAM, + maestro_read(ess, IDR7_WAVE_ROMRAM)|0x100); + maestro_write(ess, IDR7_WAVE_ROMRAM, + maestro_read(ess, IDR7_WAVE_ROMRAM)&~0x200); + maestro_write(ess, IDR7_WAVE_ROMRAM, + maestro_read(ess, IDR7_WAVE_ROMRAM)|0x400); +#endif + + maestro_write(ess, IDR2_CRAM_DATA, 0x0000); + maestro_write(ess, 0x08, 0xB004); + /* Now back to the DirectSound stuff */ + maestro_write(ess, 0x09, 0x001B); + maestro_write(ess, 0x0A, 0x8000); + maestro_write(ess, 0x0B, 0x3F37); + maestro_write(ess, 0x0C, 0x0098); + + /* parallel out ?? */ + maestro_write(ess, 0x0C, + (maestro_read(ess, 0x0C)&~0xF000)|0x8000); + /* parallel in, has something to do with recording :) */ + maestro_write(ess, 0x0C, + (maestro_read(ess, 0x0C)&~0x0F00)|0x0500); + + maestro_write(ess, 0x0D, 0x7632); + + /* Wave cache control on - test off, sg off, + enable, enable extra chans 1Mb */ + + outw(inw(0x14+iobase)|(1<<8),0x14+iobase); + outw(inw(0x14+iobase)&0xFE03,0x14+iobase); + outw((inw(0x14+iobase)&0xFFFC), 0x14+iobase); + outw(inw(0x14+iobase)|(1<<7),0x14+iobase); + + outw(0xA1A0, 0x14+iobase); /* 0300 ? */ + + /* Now clear the APU control ram */ + for(apu=0;apupower_regs = 0; + + /* check to see if we have a capabilities list in + the config register */ + pci_read_config_word(pcidev, PCI_STATUS, &w); + if(!(w & PCI_STATUS_CAP_LIST)) return 0; + + /* walk the list, starting at the head. */ + pci_read_config_byte(pcidev,PCI_CAPABILITY_LIST,&next); + + while(next && max--) { + pci_read_config_dword(pcidev, next & ~3, &n); + if((n & 0xff) == PCI_CAP_ID_PM) { + card->power_regs = next; + break; + } + next = ((n>>8) & 0xff); + } + + return card->power_regs ? 1 : 0; +} + +static int __init +maestro_probe(struct pci_dev *pcidev,const struct pci_device_id *pdid) +{ + int card_type = pdid->driver_data; + u32 n; + int iobase; + int i, ret; + struct ess_card *card; + struct ess_state *ess; + struct pm_dev *pmdev; + int num = 0; + +/* when built into the kernel, we only print version if device is found */ +#ifndef MODULE + static int printed_version; + if (!printed_version++) + printk(version); +#endif + + /* don't pick up weird modem maestros */ + if(((pcidev->class >> 8) & 0xffff) != PCI_CLASS_MULTIMEDIA_AUDIO) + return -ENODEV; + + + if ((ret=pci_enable_device(pcidev))) + return ret; + + iobase = pci_resource_start(pcidev,0); + if (!iobase || !(pci_resource_flags(pcidev, 0 ) & IORESOURCE_IO)) + return -ENODEV; + + if(pcidev->irq == 0) + return -ENODEV; + + /* stake our claim on the iospace */ + if( request_region(iobase, 256, card_names[card_type]) == NULL ) + { + printk(KERN_WARNING "maestro: can't allocate 256 bytes I/O at 0x%4.4x\n", iobase); + return -EBUSY; + } + + /* just to be sure */ + pci_set_master(pcidev); + + card = kmalloc(sizeof(struct ess_card), GFP_KERNEL); + if(card == NULL) + { + printk(KERN_WARNING "maestro: out of memory\n"); + release_region(iobase, 256); + return -ENOMEM; + } + + memset(card, 0, sizeof(*card)); + card->pcidev = pcidev; + + pmdev = pm_register(PM_PCI_DEV, PM_PCI_ID(pcidev), + maestro_pm_callback); + if (pmdev) + pmdev->data = card; + + card->iobase = iobase; + card->card_type = card_type; + card->irq = pcidev->irq; + card->magic = ESS_CARD_MAGIC; + spin_lock_init(&card->lock); + init_waitqueue_head(&card->suspend_queue); + + card->dock_mute_vol = 50; + + /* init our groups of 6 apus */ + for(i=0;ichannels[i]; + + s->index = i; + + s->card = card; + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac.wait); + init_waitqueue_head(&s->open_wait); + spin_lock_init(&s->lock); + init_MUTEX(&s->open_sem); + s->magic = ESS_STATE_MAGIC; + + s->apu[0] = 6*i; + s->apu[1] = (6*i)+1; + s->apu[2] = (6*i)+2; + s->apu[3] = (6*i)+3; + s->apu[4] = (6*i)+4; + s->apu[5] = (6*i)+5; + + if(s->dma_adc.ready || s->dma_dac.ready || s->dma_adc.rawbuf) + printk("maestro: BOTCH!\n"); + /* register devices */ + if ((s->dev_audio = register_sound_dsp(&ess_audio_fops, -1)) < 0) + break; + } + + num = i; + + /* clear the rest if we ran out of slots to register */ + for(;ichannels[i]; + s->dev_audio = -1; + } + + ess = &card->channels[0]; + + /* + * Ok card ready. Begin setup proper + */ + + printk(KERN_INFO "maestro: Configuring %s found at IO 0x%04X IRQ %d\n", + card_names[card_type],iobase,card->irq); + pci_read_config_dword(pcidev, PCI_SUBSYSTEM_VENDOR_ID, &n); + printk(KERN_INFO "maestro: subvendor id: 0x%08x\n",n); + + /* turn off power management unless: + * - the user explicitly asks for it + * or + * - we're not a 2e, lesser chipps seem to have problems. + * - we're not on our _very_ small whitelist. some implemenetations + * really don't like the pm code, others require it. + * feel free to expand this as required. + */ +#define SUBSYSTEM_VENDOR(x) (x&0xffff) + if( (use_pm != 1) && + ((card_type != TYPE_MAESTRO2E) || (SUBSYSTEM_VENDOR(n) != 0x1028))) + use_pm = 0; + + if(!use_pm) + printk(KERN_INFO "maestro: not attempting power management.\n"); + else { + if(!parse_power(card,pcidev)) + printk(KERN_INFO "maestro: no PCI power management interface found.\n"); + else { + pci_read_config_dword(pcidev, card->power_regs, &n); + printk(KERN_INFO "maestro: PCI power management capability: 0x%x\n",n>>16); + } + } + + maestro_config(card); + + if(maestro_ac97_get(card, 0x00)==0x0080) { + printk(KERN_ERR "maestro: my goodness! you seem to have a pt101 codec, which is quite rare.\n" + "\tyou should tell someone about this.\n"); + } else { + maestro_ac97_init(card); + } + + if ((card->dev_mixer = register_sound_mixer(&ess_mixer_fops, -1)) < 0) { + printk("maestro: couldn't register mixer!\n"); + } else { + memcpy(card->mix.mixer_state,mixer_defaults,sizeof(card->mix.mixer_state)); + mixer_push_state(card); + } + + if((ret=request_irq(card->irq, ess_interrupt, SA_SHIRQ, card_names[card_type], card))) + { + printk(KERN_ERR "maestro: unable to allocate irq %d,\n", card->irq); + unregister_sound_mixer(card->dev_mixer); + for(i=0;ichannels[i]; + if(s->dev_audio != -1) + unregister_sound_dsp(s->dev_audio); + } + release_region(card->iobase, 256); + unregister_reboot_notifier(&maestro_nb); + kfree(card); + return ret; + } + + /* Turn on hardware volume control interrupt. + This has to come after we grab the IRQ above, + or a crash will result on installation if a button has been pressed, + because in that case we'll get an immediate interrupt. */ + n = inw(iobase+0x18); + n|=(1<<6); + outw(n, iobase+0x18); + + pci_set_drvdata(pcidev,card); + /* now go to sleep 'till something interesting happens */ + maestro_power(card,ACPI_D2); + + printk(KERN_INFO "maestro: %d channels configured.\n", num); + return 0; +} + +static void maestro_remove(struct pci_dev *pcidev) { + struct ess_card *card = pci_get_drvdata(pcidev); + int i; + u32 n; + + /* XXX maybe should force stop bob, but should be all + stopped by _release by now */ + + /* Turn off hardware volume control interrupt. + This has to come before we leave the IRQ below, + or a crash results if a button is pressed ! */ + n = inw(card->iobase+0x18); + n&=~(1<<6); + outw(n, card->iobase+0x18); + + free_irq(card->irq, card); + unregister_sound_mixer(card->dev_mixer); + for(i=0;ichannels[i]; + if(ess->dev_audio != -1) + unregister_sound_dsp(ess->dev_audio); + } + /* Goodbye, Mr. Bond. */ + maestro_power(card,ACPI_D3); + release_region(card->iobase, 256); + kfree(card); + pci_set_drvdata(pcidev,NULL); +} + +static struct pci_device_id maestro_pci_tbl[] = { + {PCI_VENDOR_ESS, PCI_DEVICE_ID_ESS_ESS1968, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TYPE_MAESTRO2}, + {PCI_VENDOR_ESS, PCI_DEVICE_ID_ESS_ESS1978, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TYPE_MAESTRO2E}, + {PCI_VENDOR_ESS_OLD, PCI_DEVICE_ID_ESS_ESS0100, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TYPE_MAESTRO}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, maestro_pci_tbl); + +static struct pci_driver maestro_pci_driver = { + .name = "maestro", + .id_table = maestro_pci_tbl, + .probe = maestro_probe, + .remove = maestro_remove, +}; + +static int __init init_maestro(void) +{ + int rc; + + rc = pci_module_init(&maestro_pci_driver); + if (rc < 0) + return rc; + + if (register_reboot_notifier(&maestro_nb)) + printk(KERN_WARNING "maestro: reboot notifier registration failed; may not reboot properly.\n"); +#ifdef MODULE + printk(version); +#endif + if (dsps_order < 0) { + dsps_order = 1; + printk(KERN_WARNING "maestro: clipping dsps_order to %d\n",dsps_order); + } + else if (dsps_order > MAX_DSP_ORDER) { + dsps_order = MAX_DSP_ORDER; + printk(KERN_WARNING "maestro: clipping dsps_order to %d\n",dsps_order); + } + return 0; +} + +static int maestro_notifier(struct notifier_block *nb, unsigned long event, void *buf) +{ + /* this notifier is called when the kernel is really shut down. */ + M_printk("maestro: shutting down\n"); + /* this will remove all card instances too */ + pci_unregister_driver(&maestro_pci_driver); + /* XXX dunno about power management */ + return NOTIFY_OK; +} + +/* --------------------------------------------------------------------- */ + + +static void cleanup_maestro(void) { + M_printk("maestro: unloading\n"); + pci_unregister_driver(&maestro_pci_driver); + pm_unregister_all(maestro_pm_callback); + unregister_reboot_notifier(&maestro_nb); +} + +/* --------------------------------------------------------------------- */ + +void +check_suspend(struct ess_card *card) +{ + DECLARE_WAITQUEUE(wait, current); + + if(!card->in_suspend) return; + + card->in_suspend++; + add_wait_queue(&(card->suspend_queue), &wait); + current->state = TASK_UNINTERRUPTIBLE; + schedule(); + remove_wait_queue(&(card->suspend_queue), &wait); + current->state = TASK_RUNNING; +} + +static int +maestro_suspend(struct ess_card *card) +{ + unsigned long flags; + int i,j; + + spin_lock_irqsave(&card->lock,flags); /* over-kill */ + + M_printk("maestro: apm in dev %p\n",card); + + /* we have to read from the apu regs, need + to power it up */ + maestro_power(card,ACPI_D0); + + for(i=0;ichannels[i]; + + if(s->dev_audio == -1) + continue; + + M_printk("maestro: stopping apus for device %d\n",i); + stop_dac(s); + stop_adc(s); + for(j=0;j<6;j++) + card->apu_map[s->apu[j]][5]=apu_get_register(s,j,5); + + } + + /* get rid of interrupts? */ + if( card->dsps_open > 0) + stop_bob(&card->channels[0]); + + card->in_suspend++; + + spin_unlock_irqrestore(&card->lock,flags); + + /* we trust in the bios to power down the chip on suspend. + * XXX I'm also not sure that in_suspend will protect + * against all reg accesses from here on out. + */ + return 0; +} +static int +maestro_resume(struct ess_card *card) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&card->lock,flags); /* over-kill */ + + card->in_suspend = 0; + + M_printk("maestro: resuming card at %p\n",card); + + /* restore all our config */ + maestro_config(card); + /* need to restore the base pointers.. */ + if(card->dmapages) + set_base_registers(&card->channels[0],card->dmapages); + + mixer_push_state(card); + + /* set each channels' apu control registers before + * restoring audio + */ + for(i=0;ichannels[i]; + int chan,reg; + + if(s->dev_audio == -1) + continue; + + for(chan = 0 ; chan < 6 ; chan++) { + wave_set_register(s,s->apu[chan]<<3,s->apu_base[chan]); + for(reg = 1 ; reg < NR_APU_REGS ; reg++) + apu_set_register(s,chan,reg,s->card->apu_map[s->apu[chan]][reg]); + } + for(chan = 0 ; chan < 6 ; chan++) + apu_set_register(s,chan,0,s->card->apu_map[s->apu[chan]][0] & 0xFF0F); + } + + /* now we flip on the music */ + + if( card->dsps_open <= 0) { + /* this card's idle */ + maestro_power(card,ACPI_D2); + } else { + /* ok, we're actually playing things on + this card */ + maestro_power(card,ACPI_D0); + start_bob(&card->channels[0]); + for(i=0;ichannels[i]; + + /* these use the apu_mode, and can handle + spurious calls */ + start_dac(s); + start_adc(s); + } + } + + spin_unlock_irqrestore(&card->lock,flags); + + /* all right, we think things are ready, + wake up people who were using the device + when we suspended */ + wake_up(&(card->suspend_queue)); + + return 0; +} + +int +maestro_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data) +{ + struct ess_card *card = (struct ess_card*) dev->data; + + if ( ! card ) goto out; + + M_printk("maestro: pm event 0x%x received for card %p\n", rqst, card); + + switch (rqst) { + case PM_SUSPEND: + maestro_suspend(card); + break; + case PM_RESUME: + maestro_resume(card); + break; + /* + * we'd also like to find out about + * power level changes because some biosen + * do mean things to the maestro when they + * change their power state. + */ + } +out: + return 0; +} + +module_init(init_maestro); +module_exit(cleanup_maestro); diff --git a/sound/oss/maestro.h b/sound/oss/maestro.h new file mode 100644 index 000000000000..023ec7f968f9 --- /dev/null +++ b/sound/oss/maestro.h @@ -0,0 +1,60 @@ +/* + * Registers for the ESS PCI cards + */ + +/* + * Memory access + */ + +#define ESS_MEM_DATA 0x00 +#define ESS_MEM_INDEX 0x02 + +/* + * AC-97 Codec port. Delay 1uS after each write. This is used to + * talk AC-97 (see intel.com). Write data then register. + */ + +#define ESS_AC97_INDEX 0x30 /* byte wide */ +#define ESS_AC97_DATA 0x32 + +/* + * Reading is a bit different. You write register|0x80 to ubdex + * delay 1uS poll the low bit of index, when it clears read the + * data value. + */ + +/* + * Control port. Not yet fully understood + * The value 0xC090 gets loaded to it then 0x0000 and 0x2800 + * to the data port. Then after 4uS the value 0x300 is written + */ + +#define RING_BUS_CTRL_L 0x34 +#define RING_BUS_CTRL_H 0x36 + +/* + * This is also used during setup. The value 0x17 is written to it + */ + +#define ESS_SETUP_18 0x18 + +/* + * And this one gets 0x000b + */ + +#define ESS_SETUP_A2 0xA2 + +/* + * And this 0x0000 + */ + +#define ESS_SETUP_A4 0xA4 +#define ESS_SETUP_A6 0xA6 + +/* + * Stuff to do with Harpo - the wave stuff + */ + +#define ESS_WAVETABLE_SIZE 0x14 +#define ESS_WAVETABLE_2M 0xA180 + diff --git a/sound/oss/maestro3.c b/sound/oss/maestro3.c new file mode 100644 index 000000000000..f3dec70fcb9b --- /dev/null +++ b/sound/oss/maestro3.c @@ -0,0 +1,2973 @@ +/***************************************************************************** + * + * ESS Maestro3/Allegro driver for Linux 2.4.x + * + * 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. + * + * (c) Copyright 2000 Zach Brown + * + * I need to thank many people for helping make this driver happen. + * As always, Eric Brombaugh was a hacking machine and killed many bugs + * that I was too dumb to notice. Howard Kim at ESS provided reference boards + * and as much docs as he could. Todd and Mick at Dell tested snapshots on + * an army of laptops. msw and deviant at Red Hat also humoured me by hanging + * their laptops every few hours in the name of science. + * + * Shouts go out to Mike "DJ XPCom" Ang. + * + * History + * v1.23 - Jun 5 2002 - Michael Olson + * added a module option to allow selection of GPIO pin number + * for external amp + * v1.22 - Feb 28 2001 - Zach Brown + * allocate mem at insmod/setup, rather than open + * limit pci dma addresses to 28bit, thanks guys. + * v1.21 - Feb 04 2001 - Zach Brown + * fix up really dumb notifier -> suspend oops + * v1.20 - Jan 30 2001 - Zach Brown + * get rid of pm callback and use pci_dev suspend/resume instead + * m3_probe cleanups, including pm oops think-o + * v1.10 - Jan 6 2001 - Zach Brown + * revert to lame remap_page_range mmap() just to make it work + * record mmap fixed. + * fix up incredibly broken open/release resource management + * duh. fix record format setting. + * add SMP locking and cleanup formatting here and there + * v1.00 - Dec 16 2000 - Zach Brown + * port to sexy 2.4 interfaces + * properly align instance allocations so recording works + * clean up function namespace a little :/ + * update PCI IDs based on mail from ESS + * arbitrarily bump version number to show its 2.4 now, + * 2.2 will stay 0., oss_audio port gets 2. + * v0.03 - Nov 05 2000 - Zach Brown + * disable recording but allow dsp to be opened read + * pull out most silly compat defines + * v0.02 - Nov 04 2000 - Zach Brown + * changed clocking setup for m3, slowdown fixed. + * codec reset is hopefully reliable now + * rudimentary apm/power management makes suspend/resume work + * v0.01 - Oct 31 2000 - Zach Brown + * first release + * v0.00 - Sep 09 2000 - Zach Brown + * first pass derivation from maestro.c + * + * TODO + * in/out allocated contiguously so fullduplex mmap will work? + * no beep on init (mute) + * resetup msrc data memory if freq changes? + * + * -- + * + * Allow me to ramble a bit about the m3 architecture. The core of the + * chip is the 'assp', the custom ESS dsp that runs the show. It has + * a small amount of code and data ram. ESS drops binary dsp code images + * on our heads, but we don't get to see specs on the dsp. + * + * The constant piece of code on the dsp is the 'kernel'. It also has a + * chunk of the dsp memory that is statically set aside for its control + * info. This is the KDATA defines in maestro3.h. Part of its core + * data is a list of code addresses that point to the pieces of DSP code + * that it should walk through in its loop. These other pieces of code + * do the real work. The kernel presumably jumps into each of them in turn. + * These code images tend to have their own data area, and one can have + * multiple data areas representing different states for each of the 'client + * instance' code portions. There is generally a list in the kernel data + * that points to the data instances for a given piece of code. + * + * We've only been given the binary image for the 'minisrc', mini sample + * rate converter. This is rather annoying because it limits the work + * we can do on the dsp, but it also greatly simplifies the job of managing + * dsp data memory for the code and data for our playing streams :). We + * statically allocate the minisrc code into a region we 'know' to be free + * based on the map of the binary kernel image we're loading. We also + * statically allocate the data areas for the maximum number of pcm streams + * we can be dealing with. This max is set by the length of the static list + * in the kernel data that records the number of minisrc data regions we + * can have. Thats right, all software dsp mixing with static code list + * limits. Rock. + * + * How sound goes in and out is still a relative mystery. It appears + * that the dsp has the ability to get input and output through various + * 'connections'. To do IO from or to a connection, you put the address + * of the minisrc client area in the static kernel data lists for that + * input or output. so for pcm -> dsp -> mixer, we put the minisrc data + * instance in the DMA list and also in the list for the mixer. I guess + * it Just Knows which is in/out, and we give some dma control info that + * helps. There are all sorts of cool inputs/outputs that it seems we can't + * use without dsp code images that know how to use them. + * + * So at init time we preload all the memory allocation stuff and set some + * system wide parameters. When we really get a sound to play we build + * up its minisrc header (stream parameters, buffer addresses, input/output + * settings). Then we throw its header on the various lists. We also + * tickle some KDATA settings that ask the assp to raise clock interrupts + * and do some amount of software mixing before handing data to the ac97. + * + * Sorry for the vague details. Feel free to ask Eric or myself if you + * happen to be trying to use this driver elsewhere. Please accept my + * apologies for the quality of the OSS support code, its passed through + * too many hands now and desperately wants to be rethought. + */ + +/*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "maestro3.h" + +#define M_DEBUG 1 + +#define DRIVER_VERSION "1.23" +#define M3_MODULE_NAME "maestro3" +#define PFX M3_MODULE_NAME ": " + +#define M3_STATE_MAGIC 0x734d724d +#define M3_CARD_MAGIC 0x646e6f50 + +#define ESS_FMT_STEREO 0x01 +#define ESS_FMT_16BIT 0x02 +#define ESS_FMT_MASK 0x03 +#define ESS_DAC_SHIFT 0 +#define ESS_ADC_SHIFT 4 + +#define DAC_RUNNING 1 +#define ADC_RUNNING 2 + +#define SND_DEV_DSP16 5 + +#ifdef M_DEBUG +static int debug; +#define DPMOD 1 /* per module load */ +#define DPSTR 2 /* per 'stream' */ +#define DPSYS 3 /* per syscall */ +#define DPCRAP 4 /* stuff the user shouldn't see unless they're really debuggin */ +#define DPINT 5 /* per interrupt, LOTS */ +#define DPRINTK(DP, args...) {if (debug >= (DP)) printk(KERN_DEBUG PFX args);} +#else +#define DPRINTK(x) +#endif + +struct m3_list { + int curlen; + u16 mem_addr; + int max; +}; + +static int external_amp = 1; +static int gpio_pin = -1; + +struct m3_state { + unsigned int magic; + struct m3_card *card; + unsigned char fmt, enable; + + int index; + + /* this locks around the oss state in the driver */ + /* no, this lock is removed - only use card->lock */ + /* otherwise: against what are you protecting on SMP + when irqhandler uses s->lock + and m3_assp_read uses card->lock ? + */ + struct semaphore open_sem; + wait_queue_head_t open_wait; + mode_t open_mode; + + int dev_audio; + + struct assp_instance { + u16 code, data; + } dac_inst, adc_inst; + + /* should be in dmabuf */ + unsigned int rateadc, ratedac; + + struct dmabuf { + void *rawbuf; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + unsigned hwptr, swptr; + unsigned total_bytes; + int count; + unsigned error; /* over/underrun */ + wait_queue_head_t wait; + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dmasize; + unsigned fragsamples; + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned endcleared:1; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + /* new in m3 */ + int mixer_index, dma_index, msrc_index, adc1_index; + int in_lists; + /* 2.4.. */ + dma_addr_t handle; + + } dma_dac, dma_adc; +}; + +struct m3_card { + unsigned int magic; + + struct m3_card *next; + + struct ac97_codec *ac97; + spinlock_t ac97_lock; + + int card_type; + +#define NR_DSPS 1 +#define MAX_DSPS NR_DSPS + struct m3_state channels[MAX_DSPS]; + + /* this locks around the physical registers on the card */ + spinlock_t lock; + + /* hardware resources */ + struct pci_dev *pcidev; + u32 iobase; + u32 irq; + + int dacs_active; + + int timer_users; + + struct m3_list msrc_list, + mixer_list, + adc1_list, + dma_list; + + /* for storing reset state..*/ + u8 reset_state; + + u16 *suspend_mem; + int in_suspend; + wait_queue_head_t suspend_queue; +}; + +/* + * an arbitrary volume we set the internal + * volume settings to so that the ac97 volume + * range is a little less insane. 0x7fff is + * max. + */ +#define ARB_VOLUME ( 0x6800 ) + +static const unsigned sample_shift[] = { 0, 1, 1, 2 }; + +enum { + ESS_ALLEGRO, + ESS_MAESTRO3, + /* + * a maestro3 with 'hardware strapping', only + * found inside ESS? + */ + ESS_MAESTRO3HW, +}; + +static char *card_names[] = { + [ESS_ALLEGRO] = "Allegro", + [ESS_MAESTRO3] = "Maestro3(i)", + [ESS_MAESTRO3HW] = "Maestro3(i)hw" +}; + +#ifndef PCI_VENDOR_ESS +#define PCI_VENDOR_ESS 0x125D +#endif + +#define M3_DEVICE(DEV, TYPE) \ +{ \ +.vendor = PCI_VENDOR_ESS, \ +.device = DEV, \ +.subvendor = PCI_ANY_ID, \ +.subdevice = PCI_ANY_ID, \ +.class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, \ +.class_mask = 0xffff << 8, \ +.driver_data = TYPE, \ +} + +static struct pci_device_id m3_id_table[] = { + M3_DEVICE(0x1988, ESS_ALLEGRO), + M3_DEVICE(0x1998, ESS_MAESTRO3), + M3_DEVICE(0x199a, ESS_MAESTRO3HW), + {0,} +}; + +MODULE_DEVICE_TABLE (pci, m3_id_table); + +/* + * reports seem to indicate that the m3 is limited + * to 28bit bus addresses. aaaargggh... + */ +#define M3_PCI_DMA_MASK 0x0fffffff + +static unsigned +ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +static struct m3_card *devs; + +/* + * I'm not very good at laying out functions in a file :) + */ +static int m3_notifier(struct notifier_block *nb, unsigned long event, void *buf); +static int m3_suspend(struct pci_dev *pci_dev, pm_message_t state); +static void check_suspend(struct m3_card *card); + +static struct notifier_block m3_reboot_nb = { + .notifier_call = m3_notifier, +}; + +static void m3_outw(struct m3_card *card, + u16 value, unsigned long reg) +{ + check_suspend(card); + outw(value, card->iobase + reg); +} + +static u16 m3_inw(struct m3_card *card, unsigned long reg) +{ + check_suspend(card); + return inw(card->iobase + reg); +} +static void m3_outb(struct m3_card *card, + u8 value, unsigned long reg) +{ + check_suspend(card); + outb(value, card->iobase + reg); +} +static u8 m3_inb(struct m3_card *card, unsigned long reg) +{ + check_suspend(card); + return inb(card->iobase + reg); +} + +/* + * access 16bit words to the code or data regions of the dsp's memory. + * index addresses 16bit words. + */ +static u16 __m3_assp_read(struct m3_card *card, u16 region, u16 index) +{ + m3_outw(card, region & MEMTYPE_MASK, DSP_PORT_MEMORY_TYPE); + m3_outw(card, index, DSP_PORT_MEMORY_INDEX); + return m3_inw(card, DSP_PORT_MEMORY_DATA); +} +static u16 m3_assp_read(struct m3_card *card, u16 region, u16 index) +{ + unsigned long flags; + u16 ret; + + spin_lock_irqsave(&(card->lock), flags); + ret = __m3_assp_read(card, region, index); + spin_unlock_irqrestore(&(card->lock), flags); + + return ret; +} + +static void __m3_assp_write(struct m3_card *card, + u16 region, u16 index, u16 data) +{ + m3_outw(card, region & MEMTYPE_MASK, DSP_PORT_MEMORY_TYPE); + m3_outw(card, index, DSP_PORT_MEMORY_INDEX); + m3_outw(card, data, DSP_PORT_MEMORY_DATA); +} +static void m3_assp_write(struct m3_card *card, + u16 region, u16 index, u16 data) +{ + unsigned long flags; + + spin_lock_irqsave(&(card->lock), flags); + __m3_assp_write(card, region, index, data); + spin_unlock_irqrestore(&(card->lock), flags); +} + +static void m3_assp_halt(struct m3_card *card) +{ + card->reset_state = m3_inb(card, DSP_PORT_CONTROL_REG_B) & ~REGB_STOP_CLOCK; + mdelay(10); + m3_outb(card, card->reset_state & ~REGB_ENABLE_RESET, DSP_PORT_CONTROL_REG_B); +} + +static void m3_assp_continue(struct m3_card *card) +{ + m3_outb(card, card->reset_state | REGB_ENABLE_RESET, DSP_PORT_CONTROL_REG_B); +} + +/* + * This makes me sad. the maestro3 has lists + * internally that must be packed.. 0 terminates, + * apparently, or maybe all unused entries have + * to be 0, the lists have static lengths set + * by the binary code images. + */ + +static int m3_add_list(struct m3_card *card, + struct m3_list *list, u16 val) +{ + DPRINTK(DPSTR, "adding val 0x%x to list 0x%p at pos %d\n", + val, list, list->curlen); + + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + list->mem_addr + list->curlen, + val); + + return list->curlen++; + +} + +static void m3_remove_list(struct m3_card *card, + struct m3_list *list, int index) +{ + u16 val; + int lastindex = list->curlen - 1; + + DPRINTK(DPSTR, "removing ind %d from list 0x%p\n", + index, list); + + if(index != lastindex) { + val = m3_assp_read(card, MEMTYPE_INTERNAL_DATA, + list->mem_addr + lastindex); + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + list->mem_addr + index, + val); + } + + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + list->mem_addr + lastindex, + 0); + + list->curlen--; +} + +static void set_fmt(struct m3_state *s, unsigned char mask, unsigned char data) +{ + int tmp; + + s->fmt = (s->fmt & mask) | data; + + tmp = (s->fmt >> ESS_DAC_SHIFT) & ESS_FMT_MASK; + + /* write to 'mono' word */ + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + SRC3_DIRECTION_OFFSET + 1, + (tmp & ESS_FMT_STEREO) ? 0 : 1); + /* write to '8bit' word */ + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + SRC3_DIRECTION_OFFSET + 2, + (tmp & ESS_FMT_16BIT) ? 0 : 1); + + tmp = (s->fmt >> ESS_ADC_SHIFT) & ESS_FMT_MASK; + + /* write to 'mono' word */ + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + SRC3_DIRECTION_OFFSET + 1, + (tmp & ESS_FMT_STEREO) ? 0 : 1); + /* write to '8bit' word */ + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + SRC3_DIRECTION_OFFSET + 2, + (tmp & ESS_FMT_16BIT) ? 0 : 1); +} + +static void set_dac_rate(struct m3_state *s, unsigned int rate) +{ + u32 freq; + + if (rate > 48000) + rate = 48000; + if (rate < 8000) + rate = 8000; + + s->ratedac = rate; + + freq = ((rate << 15) + 24000 ) / 48000; + if(freq) + freq--; + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_FREQUENCY, + freq); +} + +static void set_adc_rate(struct m3_state *s, unsigned int rate) +{ + u32 freq; + + if (rate > 48000) + rate = 48000; + if (rate < 8000) + rate = 8000; + + s->rateadc = rate; + + freq = ((rate << 15) + 24000 ) / 48000; + if(freq) + freq--; + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_FREQUENCY, + freq); +} + +static void inc_timer_users(struct m3_card *card) +{ + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + + card->timer_users++; + DPRINTK(DPSYS, "inc timer users now %d\n", + card->timer_users); + if(card->timer_users != 1) + goto out; + + __m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_TIMER_COUNT_RELOAD, + 240 ) ; + + __m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_TIMER_COUNT_CURRENT, + 240 ) ; + + m3_outw(card, + m3_inw(card, HOST_INT_CTRL) | CLKRUN_GEN_ENABLE, + HOST_INT_CTRL); +out: + spin_unlock_irqrestore(&card->lock, flags); +} + +static void dec_timer_users(struct m3_card *card) +{ + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + + card->timer_users--; + DPRINTK(DPSYS, "dec timer users now %d\n", + card->timer_users); + if(card->timer_users > 0 ) + goto out; + + __m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_TIMER_COUNT_RELOAD, + 0 ) ; + + __m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_TIMER_COUNT_CURRENT, + 0 ) ; + + m3_outw(card, m3_inw(card, HOST_INT_CTRL) & ~CLKRUN_GEN_ENABLE, + HOST_INT_CTRL); +out: + spin_unlock_irqrestore(&card->lock, flags); +} + +/* + * {start,stop}_{adc,dac} should be called + * while holding the 'state' lock and they + * will try to grab the 'card' lock.. + */ +static void stop_adc(struct m3_state *s) +{ + if (! (s->enable & ADC_RUNNING)) + return; + + s->enable &= ~ADC_RUNNING; + dec_timer_users(s->card); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_INSTANCE_READY, 0); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + KDATA_ADC1_REQUEST, 0); +} + +static void stop_dac(struct m3_state *s) +{ + if (! (s->enable & DAC_RUNNING)) + return; + + DPRINTK(DPSYS, "stop_dac()\n"); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_INSTANCE_READY, 0); + + s->enable &= ~DAC_RUNNING; + s->card->dacs_active--; + dec_timer_users(s->card); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + KDATA_MIXER_TASK_NUMBER, + s->card->dacs_active ) ; +} + +static void start_dac(struct m3_state *s) +{ + if( (!s->dma_dac.mapped && s->dma_dac.count < 1) || + !s->dma_dac.ready || + (s->enable & DAC_RUNNING)) + return; + + DPRINTK(DPSYS, "start_dac()\n"); + + s->enable |= DAC_RUNNING; + s->card->dacs_active++; + inc_timer_users(s->card); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_INSTANCE_READY, 1); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + KDATA_MIXER_TASK_NUMBER, + s->card->dacs_active ) ; +} + +static void start_adc(struct m3_state *s) +{ + if ((! s->dma_adc.mapped && + s->dma_adc.count >= (signed)(s->dma_adc.dmasize - 2*s->dma_adc.fragsize)) + || !s->dma_adc.ready + || (s->enable & ADC_RUNNING) ) + return; + + DPRINTK(DPSYS, "start_adc()\n"); + + s->enable |= ADC_RUNNING; + inc_timer_users(s->card); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + KDATA_ADC1_REQUEST, 1); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_INSTANCE_READY, 1); +} + +static struct play_vals { + u16 addr, val; +} pv[] = { + {CDATA_LEFT_VOLUME, ARB_VOLUME}, + {CDATA_RIGHT_VOLUME, ARB_VOLUME}, + {SRC3_DIRECTION_OFFSET, 0} , + /* +1, +2 are stereo/16 bit */ + {SRC3_DIRECTION_OFFSET + 3, 0x0000}, /* fraction? */ + {SRC3_DIRECTION_OFFSET + 4, 0}, /* first l */ + {SRC3_DIRECTION_OFFSET + 5, 0}, /* first r */ + {SRC3_DIRECTION_OFFSET + 6, 0}, /* second l */ + {SRC3_DIRECTION_OFFSET + 7, 0}, /* second r */ + {SRC3_DIRECTION_OFFSET + 8, 0}, /* delta l */ + {SRC3_DIRECTION_OFFSET + 9, 0}, /* delta r */ + {SRC3_DIRECTION_OFFSET + 10, 0x8000}, /* round */ + {SRC3_DIRECTION_OFFSET + 11, 0xFF00}, /* higher bute mark */ + {SRC3_DIRECTION_OFFSET + 13, 0}, /* temp0 */ + {SRC3_DIRECTION_OFFSET + 14, 0}, /* c fraction */ + {SRC3_DIRECTION_OFFSET + 15, 0}, /* counter */ + {SRC3_DIRECTION_OFFSET + 16, 8}, /* numin */ + {SRC3_DIRECTION_OFFSET + 17, 50*2}, /* numout */ + {SRC3_DIRECTION_OFFSET + 18, MINISRC_BIQUAD_STAGE - 1}, /* numstage */ + {SRC3_DIRECTION_OFFSET + 20, 0}, /* filtertap */ + {SRC3_DIRECTION_OFFSET + 21, 0} /* booster */ +}; + + +/* the mode passed should be already shifted and masked */ +static void m3_play_setup(struct m3_state *s, int mode, u32 rate, void *buffer, int size) +{ + int dsp_in_size = MINISRC_IN_BUFFER_SIZE - (0x20 * 2); + int dsp_out_size = MINISRC_OUT_BUFFER_SIZE - (0x20 * 2); + int dsp_in_buffer = s->dac_inst.data + (MINISRC_TMP_BUFFER_SIZE / 2); + int dsp_out_buffer = dsp_in_buffer + (dsp_in_size / 2) + 1; + struct dmabuf *db = &s->dma_dac; + int i; + + DPRINTK(DPSTR, "mode=%d rate=%d buf=%p len=%d.\n", + mode, rate, buffer, size); + +#define LO(x) ((x) & 0xffff) +#define HI(x) LO((x) >> 16) + + /* host dma buffer pointers */ + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_HOST_SRC_ADDRL, + LO(virt_to_bus(buffer))); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_HOST_SRC_ADDRH, + HI(virt_to_bus(buffer))); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_HOST_SRC_END_PLUS_1L, + LO(virt_to_bus(buffer) + size)); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_HOST_SRC_END_PLUS_1H, + HI(virt_to_bus(buffer) + size)); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_HOST_SRC_CURRENTL, + LO(virt_to_bus(buffer))); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_HOST_SRC_CURRENTH, + HI(virt_to_bus(buffer))); +#undef LO +#undef HI + + /* dsp buffers */ + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_IN_BUF_BEGIN, + dsp_in_buffer); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_IN_BUF_END_PLUS_1, + dsp_in_buffer + (dsp_in_size / 2)); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_IN_BUF_HEAD, + dsp_in_buffer); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_IN_BUF_TAIL, + dsp_in_buffer); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_OUT_BUF_BEGIN, + dsp_out_buffer); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_OUT_BUF_END_PLUS_1, + dsp_out_buffer + (dsp_out_size / 2)); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_OUT_BUF_HEAD, + dsp_out_buffer); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_OUT_BUF_TAIL, + dsp_out_buffer); + + /* + * some per client initializers + */ + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + SRC3_DIRECTION_OFFSET + 12, + s->dac_inst.data + 40 + 8); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + SRC3_DIRECTION_OFFSET + 19, + s->dac_inst.code + MINISRC_COEF_LOC); + + /* enable or disable low pass filter? */ + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + SRC3_DIRECTION_OFFSET + 22, + s->ratedac > 45000 ? 0xff : 0 ); + + /* tell it which way dma is going? */ + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + CDATA_DMA_CONTROL, + DMACONTROL_AUTOREPEAT + DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR); + + /* + * set an armload of static initializers + */ + for(i = 0 ; i < (sizeof(pv) / sizeof(pv[0])) ; i++) + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->dac_inst.data + pv[i].addr, pv[i].val); + + /* + * put us in the lists if we're not already there + */ + + if(db->in_lists == 0) { + + db->msrc_index = m3_add_list(s->card, &s->card->msrc_list, + s->dac_inst.data >> DP_SHIFT_COUNT); + + db->dma_index = m3_add_list(s->card, &s->card->dma_list, + s->dac_inst.data >> DP_SHIFT_COUNT); + + db->mixer_index = m3_add_list(s->card, &s->card->mixer_list, + s->dac_inst.data >> DP_SHIFT_COUNT); + + db->in_lists = 1; + } + + set_dac_rate(s,rate); + start_dac(s); +} + +/* + * Native record driver + */ +static struct rec_vals { + u16 addr, val; +} rv[] = { + {CDATA_LEFT_VOLUME, ARB_VOLUME}, + {CDATA_RIGHT_VOLUME, ARB_VOLUME}, + {SRC3_DIRECTION_OFFSET, 1} , + /* +1, +2 are stereo/16 bit */ + {SRC3_DIRECTION_OFFSET + 3, 0x0000}, /* fraction? */ + {SRC3_DIRECTION_OFFSET + 4, 0}, /* first l */ + {SRC3_DIRECTION_OFFSET + 5, 0}, /* first r */ + {SRC3_DIRECTION_OFFSET + 6, 0}, /* second l */ + {SRC3_DIRECTION_OFFSET + 7, 0}, /* second r */ + {SRC3_DIRECTION_OFFSET + 8, 0}, /* delta l */ + {SRC3_DIRECTION_OFFSET + 9, 0}, /* delta r */ + {SRC3_DIRECTION_OFFSET + 10, 0x8000}, /* round */ + {SRC3_DIRECTION_OFFSET + 11, 0xFF00}, /* higher bute mark */ + {SRC3_DIRECTION_OFFSET + 13, 0}, /* temp0 */ + {SRC3_DIRECTION_OFFSET + 14, 0}, /* c fraction */ + {SRC3_DIRECTION_OFFSET + 15, 0}, /* counter */ + {SRC3_DIRECTION_OFFSET + 16, 50},/* numin */ + {SRC3_DIRECTION_OFFSET + 17, 8}, /* numout */ + {SRC3_DIRECTION_OFFSET + 18, 0}, /* numstage */ + {SRC3_DIRECTION_OFFSET + 19, 0}, /* coef */ + {SRC3_DIRECTION_OFFSET + 20, 0}, /* filtertap */ + {SRC3_DIRECTION_OFFSET + 21, 0}, /* booster */ + {SRC3_DIRECTION_OFFSET + 22, 0xff} /* skip lpf */ +}; + +/* again, passed mode is alrady shifted/masked */ +static void m3_rec_setup(struct m3_state *s, int mode, u32 rate, void *buffer, int size) +{ + int dsp_in_size = MINISRC_IN_BUFFER_SIZE + (0x10 * 2); + int dsp_out_size = MINISRC_OUT_BUFFER_SIZE - (0x10 * 2); + int dsp_in_buffer = s->adc_inst.data + (MINISRC_TMP_BUFFER_SIZE / 2); + int dsp_out_buffer = dsp_in_buffer + (dsp_in_size / 2) + 1; + struct dmabuf *db = &s->dma_adc; + int i; + + DPRINTK(DPSTR, "rec_setup mode=%d rate=%d buf=%p len=%d.\n", + mode, rate, buffer, size); + +#define LO(x) ((x) & 0xffff) +#define HI(x) LO((x) >> 16) + + /* host dma buffer pointers */ + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_HOST_SRC_ADDRL, + LO(virt_to_bus(buffer))); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_HOST_SRC_ADDRH, + HI(virt_to_bus(buffer))); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_HOST_SRC_END_PLUS_1L, + LO(virt_to_bus(buffer) + size)); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_HOST_SRC_END_PLUS_1H, + HI(virt_to_bus(buffer) + size)); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_HOST_SRC_CURRENTL, + LO(virt_to_bus(buffer))); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_HOST_SRC_CURRENTH, + HI(virt_to_bus(buffer))); +#undef LO +#undef HI + + /* dsp buffers */ + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_IN_BUF_BEGIN, + dsp_in_buffer); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_IN_BUF_END_PLUS_1, + dsp_in_buffer + (dsp_in_size / 2)); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_IN_BUF_HEAD, + dsp_in_buffer); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_IN_BUF_TAIL, + dsp_in_buffer); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_OUT_BUF_BEGIN, + dsp_out_buffer); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_OUT_BUF_END_PLUS_1, + dsp_out_buffer + (dsp_out_size / 2)); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_OUT_BUF_HEAD, + dsp_out_buffer); + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_OUT_BUF_TAIL, + dsp_out_buffer); + + /* + * some per client initializers + */ + + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + SRC3_DIRECTION_OFFSET + 12, + s->adc_inst.data + 40 + 8); + + /* tell it which way dma is going? */ + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + CDATA_DMA_CONTROL, + DMACONTROL_DIRECTION + DMACONTROL_AUTOREPEAT + + DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR); + + /* + * set an armload of static initializers + */ + for(i = 0 ; i < (sizeof(rv) / sizeof(rv[0])) ; i++) + m3_assp_write(s->card, MEMTYPE_INTERNAL_DATA, + s->adc_inst.data + rv[i].addr, rv[i].val); + + /* + * put us in the lists if we're not already there + */ + + if(db->in_lists == 0) { + + db->adc1_index = m3_add_list(s->card, &s->card->adc1_list, + s->adc_inst.data >> DP_SHIFT_COUNT); + + db->dma_index = m3_add_list(s->card, &s->card->dma_list, + s->adc_inst.data >> DP_SHIFT_COUNT); + + db->msrc_index = m3_add_list(s->card, &s->card->msrc_list, + s->adc_inst.data >> DP_SHIFT_COUNT); + + db->in_lists = 1; + } + + set_adc_rate(s,rate); + start_adc(s); +} +/* --------------------------------------------------------------------- */ + +static void set_dmaa(struct m3_state *s, unsigned int addr, unsigned int count) +{ + DPRINTK(DPINT,"set_dmaa??\n"); +} + +static void set_dmac(struct m3_state *s, unsigned int addr, unsigned int count) +{ + DPRINTK(DPINT,"set_dmac??\n"); +} + +static u32 get_dma_pos(struct m3_card *card, + int instance_addr) +{ + u16 hi = 0, lo = 0; + int retry = 10; + + /* + * try and get a valid answer + */ + while(retry--) { + hi = m3_assp_read(card, MEMTYPE_INTERNAL_DATA, + instance_addr + CDATA_HOST_SRC_CURRENTH); + + lo = m3_assp_read(card, MEMTYPE_INTERNAL_DATA, + instance_addr + CDATA_HOST_SRC_CURRENTL); + + if(hi == m3_assp_read(card, MEMTYPE_INTERNAL_DATA, + instance_addr + CDATA_HOST_SRC_CURRENTH)) + break; + } + return lo | (hi<<16); +} + +static u32 get_dmaa(struct m3_state *s) +{ + u32 offset; + + offset = get_dma_pos(s->card, s->dac_inst.data) - + virt_to_bus(s->dma_dac.rawbuf); + + DPRINTK(DPINT,"get_dmaa: 0x%08x\n",offset); + + return offset; +} + +static u32 get_dmac(struct m3_state *s) +{ + u32 offset; + + offset = get_dma_pos(s->card, s->adc_inst.data) - + virt_to_bus(s->dma_adc.rawbuf); + + DPRINTK(DPINT,"get_dmac: 0x%08x\n",offset); + + return offset; + +} + +static int +prog_dmabuf(struct m3_state *s, unsigned rec) +{ + struct dmabuf *db = rec ? &s->dma_adc : &s->dma_dac; + unsigned rate = rec ? s->rateadc : s->ratedac; + unsigned bytepersec; + unsigned bufs; + unsigned char fmt; + unsigned long flags; + + spin_lock_irqsave(&s->card->lock, flags); + + fmt = s->fmt; + if (rec) { + stop_adc(s); + fmt >>= ESS_ADC_SHIFT; + } else { + stop_dac(s); + fmt >>= ESS_DAC_SHIFT; + } + fmt &= ESS_FMT_MASK; + + db->hwptr = db->swptr = db->total_bytes = db->count = db->error = db->endcleared = 0; + + bytepersec = rate << sample_shift[fmt]; + bufs = PAGE_SIZE << db->buforder; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < bytepersec) + db->fragshift = ld2(bytepersec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(bytepersec/100/(db->subdivision ? db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + db->numfrag = bufs >> db->fragshift; + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->numfrag = bufs >> db->fragshift; + } + db->fragsize = 1 << db->fragshift; + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; + db->fragsamples = db->fragsize >> sample_shift[fmt]; + db->dmasize = db->numfrag << db->fragshift; + + DPRINTK(DPSTR,"prog_dmabuf: numfrag: %d fragsize: %d dmasize: %d\n",db->numfrag,db->fragsize,db->dmasize); + + memset(db->rawbuf, (fmt & ESS_FMT_16BIT) ? 0 : 0x80, db->dmasize); + + if (rec) + m3_rec_setup(s, fmt, s->rateadc, db->rawbuf, db->dmasize); + else + m3_play_setup(s, fmt, s->ratedac, db->rawbuf, db->dmasize); + + db->ready = 1; + + spin_unlock_irqrestore(&s->card->lock, flags); + + return 0; +} + +static void clear_advance(struct m3_state *s) +{ + unsigned char c = ((s->fmt >> ESS_DAC_SHIFT) & ESS_FMT_16BIT) ? 0 : 0x80; + + unsigned char *buf = s->dma_dac.rawbuf; + unsigned bsize = s->dma_dac.dmasize; + unsigned bptr = s->dma_dac.swptr; + unsigned len = s->dma_dac.fragsize; + + if (bptr + len > bsize) { + unsigned x = bsize - bptr; + memset(buf + bptr, c, x); + /* account for wrapping? */ + bptr = 0; + len -= x; + } + memset(buf + bptr, c, len); +} + +/* call with spinlock held! */ +static void m3_update_ptr(struct m3_state *s) +{ + unsigned hwptr; + int diff; + + /* update ADC pointer */ + if (s->dma_adc.ready) { + hwptr = get_dmac(s) % s->dma_adc.dmasize; + diff = (s->dma_adc.dmasize + hwptr - s->dma_adc.hwptr) % s->dma_adc.dmasize; + s->dma_adc.hwptr = hwptr; + s->dma_adc.total_bytes += diff; + s->dma_adc.count += diff; + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + wake_up(&s->dma_adc.wait); + if (!s->dma_adc.mapped) { + if (s->dma_adc.count > (signed)(s->dma_adc.dmasize - ((3 * s->dma_adc.fragsize) >> 1))) { + stop_adc(s); + /* brute force everyone back in sync, sigh */ + s->dma_adc.count = 0; + s->dma_adc.swptr = 0; + s->dma_adc.hwptr = 0; + s->dma_adc.error++; + } + } + } + /* update DAC pointer */ + if (s->dma_dac.ready) { + hwptr = get_dmaa(s) % s->dma_dac.dmasize; + diff = (s->dma_dac.dmasize + hwptr - s->dma_dac.hwptr) % s->dma_dac.dmasize; + + DPRINTK(DPINT,"updating dac: hwptr: %6d diff: %6d count: %6d\n", + hwptr,diff,s->dma_dac.count); + + s->dma_dac.hwptr = hwptr; + s->dma_dac.total_bytes += diff; + + if (s->dma_dac.mapped) { + + s->dma_dac.count += diff; + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) { + wake_up(&s->dma_dac.wait); + } + } else { + + s->dma_dac.count -= diff; + + if (s->dma_dac.count <= 0) { + DPRINTK(DPCRAP,"underflow! diff: %d (0x%x) count: %d (0x%x) hw: %d (0x%x) sw: %d (0x%x)\n", + diff, diff, + s->dma_dac.count, + s->dma_dac.count, + hwptr, hwptr, + s->dma_dac.swptr, + s->dma_dac.swptr); + stop_dac(s); + /* brute force everyone back in sync, sigh */ + s->dma_dac.count = 0; + s->dma_dac.swptr = hwptr; + s->dma_dac.error++; + } else if (s->dma_dac.count <= (signed)s->dma_dac.fragsize && !s->dma_dac.endcleared) { + clear_advance(s); + s->dma_dac.endcleared = 1; + } + if (s->dma_dac.count + (signed)s->dma_dac.fragsize <= (signed)s->dma_dac.dmasize) { + wake_up(&s->dma_dac.wait); + DPRINTK(DPINT,"waking up DAC count: %d sw: %d hw: %d\n", + s->dma_dac.count, s->dma_dac.swptr, hwptr); + } + } + } +} + +static irqreturn_t m3_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct m3_card *c = (struct m3_card *)dev_id; + struct m3_state *s = &c->channels[0]; + u8 status; + + status = inb(c->iobase+0x1A); + + if(status == 0xff) + return IRQ_NONE; + + /* presumably acking the ints? */ + outw(status, c->iobase+0x1A); + + if(c->in_suspend) + return IRQ_HANDLED; + + /* + * ack an assp int if its running + * and has an int pending + */ + if( status & ASSP_INT_PENDING) { + u8 ctl = inb(c->iobase + ASSP_CONTROL_B); + if( !(ctl & STOP_ASSP_CLOCK)) { + ctl = inb(c->iobase + ASSP_HOST_INT_STATUS ); + if(ctl & DSP2HOST_REQ_TIMER) { + outb( DSP2HOST_REQ_TIMER, c->iobase + ASSP_HOST_INT_STATUS); + /* update adc/dac info if it was a timer int */ + spin_lock(&c->lock); + m3_update_ptr(s); + spin_unlock(&c->lock); + } + } + } + + /* XXX is this needed? */ + if(status & 0x40) + outb(0x40, c->iobase+0x1A); + return IRQ_HANDLED; +} + + +/* --------------------------------------------------------------------- */ + +static const char invalid_magic[] = KERN_CRIT PFX "invalid magic value in %s\n"; + +#define VALIDATE_MAGIC(FOO,MAG) \ +({ \ + if (!(FOO) || (FOO)->magic != MAG) { \ + printk(invalid_magic,__FUNCTION__); \ + return -ENXIO; \ + } \ +}) + +#define VALIDATE_STATE(a) VALIDATE_MAGIC(a,M3_STATE_MAGIC) +#define VALIDATE_CARD(a) VALIDATE_MAGIC(a,M3_CARD_MAGIC) + +/* --------------------------------------------------------------------- */ + +static int drain_dac(struct m3_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait,current); + unsigned long flags; + int count; + signed long tmo; + + if (s->dma_dac.mapped || !s->dma_dac.ready) + return 0; + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&s->dma_dac.wait, &wait); + for (;;) { + spin_lock_irqsave(&s->card->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->card->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_dac.wait, &wait); + set_current_state(TASK_RUNNING); + return -EBUSY; + } + tmo = (count * HZ) / s->ratedac; + tmo >>= sample_shift[(s->fmt >> ESS_DAC_SHIFT) & ESS_FMT_MASK]; + /* XXX this is just broken. someone is waking us up alot, or schedule_timeout is broken. + or something. who cares. - zach */ + if (!schedule_timeout(tmo ? tmo : 1) && tmo) + DPRINTK(DPCRAP,"dma timed out?? %ld\n",jiffies); + } + remove_wait_queue(&s->dma_dac.wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +static ssize_t m3_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct m3_state *s = (struct m3_state *)file->private_data; + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_adc.mapped) + return -ENXIO; + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; + + spin_lock_irqsave(&s->card->lock, flags); + + while (count > 0) { + int timed_out; + + swptr = s->dma_adc.swptr; + cnt = s->dma_adc.dmasize-swptr; + if (s->dma_adc.count < cnt) + cnt = s->dma_adc.count; + + if (cnt > count) + cnt = count; + + if (cnt <= 0) { + start_adc(s); + if (file->f_flags & O_NONBLOCK) + { + ret = ret ? ret : -EAGAIN; + goto out; + } + + spin_unlock_irqrestore(&s->card->lock, flags); + timed_out = interruptible_sleep_on_timeout(&s->dma_adc.wait, HZ) == 0; + spin_lock_irqsave(&s->card->lock, flags); + + if(timed_out) { + printk("read: chip lockup? dmasz %u fragsz %u count %u hwptr %u swptr %u\n", + s->dma_adc.dmasize, s->dma_adc.fragsize, s->dma_adc.count, + s->dma_adc.hwptr, s->dma_adc.swptr); + stop_adc(s); + set_dmac(s, virt_to_bus(s->dma_adc.rawbuf), s->dma_adc.numfrag << s->dma_adc.fragshift); + s->dma_adc.count = s->dma_adc.hwptr = s->dma_adc.swptr = 0; + } + if (signal_pending(current)) + { + ret = ret ? ret : -ERESTARTSYS; + goto out; + } + continue; + } + + spin_unlock_irqrestore(&s->card->lock, flags); + if (copy_to_user(buffer, s->dma_adc.rawbuf + swptr, cnt)) { + ret = ret ? ret : -EFAULT; + return ret; + } + spin_lock_irqsave(&s->card->lock, flags); + + swptr = (swptr + cnt) % s->dma_adc.dmasize; + s->dma_adc.swptr = swptr; + s->dma_adc.count -= cnt; + count -= cnt; + buffer += cnt; + ret += cnt; + start_adc(s); + } + +out: + spin_unlock_irqrestore(&s->card->lock, flags); + return ret; +} + +static ssize_t m3_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct m3_state *s = (struct m3_state *)file->private_data; + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_dac.mapped) + return -ENXIO; + if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0))) + return ret; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; + + spin_lock_irqsave(&s->card->lock, flags); + + while (count > 0) { + int timed_out; + + if (s->dma_dac.count < 0) { + s->dma_dac.count = 0; + s->dma_dac.swptr = s->dma_dac.hwptr; + } + swptr = s->dma_dac.swptr; + + cnt = s->dma_dac.dmasize-swptr; + + if (s->dma_dac.count + cnt > s->dma_dac.dmasize) + cnt = s->dma_dac.dmasize - s->dma_dac.count; + + + if (cnt > count) + cnt = count; + + if (cnt <= 0) { + start_dac(s); + if (file->f_flags & O_NONBLOCK) { + if(!ret) ret = -EAGAIN; + goto out; + } + spin_unlock_irqrestore(&s->card->lock, flags); + timed_out = interruptible_sleep_on_timeout(&s->dma_dac.wait, HZ) == 0; + spin_lock_irqsave(&s->card->lock, flags); + if(timed_out) { + DPRINTK(DPCRAP,"write: chip lockup? dmasz %u fragsz %u count %u hwptr %u swptr %u\n", + s->dma_dac.dmasize, s->dma_dac.fragsize, s->dma_dac.count, + s->dma_dac.hwptr, s->dma_dac.swptr); + stop_dac(s); + set_dmaa(s, virt_to_bus(s->dma_dac.rawbuf), s->dma_dac.numfrag << s->dma_dac.fragshift); + s->dma_dac.count = s->dma_dac.hwptr = s->dma_dac.swptr = 0; + } + if (signal_pending(current)) { + if (!ret) ret = -ERESTARTSYS; + goto out; + } + continue; + } + spin_unlock_irqrestore(&s->card->lock, flags); + if (copy_from_user(s->dma_dac.rawbuf + swptr, buffer, cnt)) { + if (!ret) ret = -EFAULT; + return ret; + } + spin_lock_irqsave(&s->card->lock, flags); + + DPRINTK(DPSYS,"wrote %6d bytes at sw: %6d cnt: %6d while hw: %6d\n", + cnt, swptr, s->dma_dac.count, s->dma_dac.hwptr); + + swptr = (swptr + cnt) % s->dma_dac.dmasize; + + s->dma_dac.swptr = swptr; + s->dma_dac.count += cnt; + s->dma_dac.endcleared = 0; + count -= cnt; + buffer += cnt; + ret += cnt; + start_dac(s); + } +out: + spin_unlock_irqrestore(&s->card->lock, flags); + return ret; +} + +static unsigned int m3_poll(struct file *file, struct poll_table_struct *wait) +{ + struct m3_state *s = (struct m3_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) + poll_wait(file, &s->dma_dac.wait, wait); + if (file->f_mode & FMODE_READ) + poll_wait(file, &s->dma_adc.wait, wait); + + spin_lock_irqsave(&s->card->lock, flags); + m3_update_ptr(s); + + if (file->f_mode & FMODE_READ) { + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->dma_dac.mapped) { + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)s->dma_dac.dmasize >= s->dma_dac.count + (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + + spin_unlock_irqrestore(&s->card->lock, flags); + return mask; +} + +static int m3_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct m3_state *s = (struct m3_state *)file->private_data; + unsigned long max_size, size, start, offset; + struct dmabuf *db; + int ret = -EINVAL; + + VALIDATE_STATE(s); + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf(s, 0)) != 0) + return ret; + db = &s->dma_dac; + } else + if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf(s, 1)) != 0) + return ret; + db = &s->dma_adc; + } else + return -EINVAL; + + max_size = db->dmasize; + + start = vma->vm_start; + offset = (vma->vm_pgoff << PAGE_SHIFT); + size = vma->vm_end - vma->vm_start; + + if(size > max_size) + goto out; + if(offset > max_size - size) + goto out; + + /* + * this will be ->nopage() once I can + * ask Jeff what the hell I'm doing wrong. + */ + ret = -EAGAIN; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(db->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + goto out; + + db->mapped = 1; + ret = 0; + +out: + return ret; +} + +/* + * this function is a disaster.. + */ +#define get_user_ret(x, ptr, ret) ({ if(get_user(x, ptr)) return ret; }) +static int m3_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct m3_state *s = (struct m3_state *)file->private_data; + struct m3_card *card=s->card; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int val, mapped, ret; + unsigned char fmtm, fmtd; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + VALIDATE_STATE(s); + + mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac.mapped) || + ((file->f_mode & FMODE_READ) && s->dma_adc.mapped); + + DPRINTK(DPSYS,"m3_ioctl: cmd %d\n", cmd); + + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_SYNC: + if (file->f_mode & FMODE_WRITE) + return drain_dac(s, file->f_flags & O_NONBLOCK); + return 0; + + case SNDCTL_DSP_SETDUPLEX: + /* XXX fix */ + return 0; + + case SNDCTL_DSP_GETCAPS: + return put_user(DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER | DSP_CAP_MMAP, p); + + case SNDCTL_DSP_RESET: + spin_lock_irqsave(&card->lock, flags); + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + synchronize_irq(s->card->pcidev->irq); + s->dma_dac.swptr = s->dma_dac.hwptr = s->dma_dac.count = s->dma_dac.total_bytes = 0; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(s->card->pcidev->irq); + s->dma_adc.swptr = s->dma_adc.hwptr = s->dma_adc.count = s->dma_adc.total_bytes = 0; + } + spin_unlock_irqrestore(&card->lock, flags); + return 0; + + case SNDCTL_DSP_SPEED: + get_user_ret(val, p, -EFAULT); + spin_lock_irqsave(&card->lock, flags); + if (val >= 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + set_adc_rate(s, val); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + set_dac_rate(s, val); + } + } + spin_unlock_irqrestore(&card->lock, flags); + return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, p); + + case SNDCTL_DSP_STEREO: + get_user_ret(val, p, -EFAULT); + spin_lock_irqsave(&card->lock, flags); + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val) + fmtd |= ESS_FMT_STEREO << ESS_ADC_SHIFT; + else + fmtm &= ~(ESS_FMT_STEREO << ESS_ADC_SHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val) + fmtd |= ESS_FMT_STEREO << ESS_DAC_SHIFT; + else + fmtm &= ~(ESS_FMT_STEREO << ESS_DAC_SHIFT); + } + set_fmt(s, fmtm, fmtd); + spin_unlock_irqrestore(&card->lock, flags); + return 0; + + case SNDCTL_DSP_CHANNELS: + get_user_ret(val, p, -EFAULT); + spin_lock_irqsave(&card->lock, flags); + if (val != 0) { + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val >= 2) + fmtd |= ESS_FMT_STEREO << ESS_ADC_SHIFT; + else + fmtm &= ~(ESS_FMT_STEREO << ESS_ADC_SHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val >= 2) + fmtd |= ESS_FMT_STEREO << ESS_DAC_SHIFT; + else + fmtm &= ~(ESS_FMT_STEREO << ESS_DAC_SHIFT); + } + set_fmt(s, fmtm, fmtd); + } + spin_unlock_irqrestore(&card->lock, flags); + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (ESS_FMT_STEREO << ESS_ADC_SHIFT) + : (ESS_FMT_STEREO << ESS_DAC_SHIFT))) ? 2 : 1, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_U8|AFMT_S16_LE, p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + get_user_ret(val, p, -EFAULT); + spin_lock_irqsave(&card->lock, flags); + if (val != AFMT_QUERY) { + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val == AFMT_S16_LE) + fmtd |= ESS_FMT_16BIT << ESS_ADC_SHIFT; + else + fmtm &= ~(ESS_FMT_16BIT << ESS_ADC_SHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val == AFMT_S16_LE) + fmtd |= ESS_FMT_16BIT << ESS_DAC_SHIFT; + else + fmtm &= ~(ESS_FMT_16BIT << ESS_DAC_SHIFT); + } + set_fmt(s, fmtm, fmtd); + } + spin_unlock_irqrestore(&card->lock, flags); + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? + (ESS_FMT_16BIT << ESS_ADC_SHIFT) + : (ESS_FMT_16BIT << ESS_DAC_SHIFT))) ? + AFMT_S16_LE : + AFMT_U8, + p); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if ((file->f_mode & FMODE_READ) && (s->enable & ADC_RUNNING)) + val |= PCM_ENABLE_INPUT; + if ((file->f_mode & FMODE_WRITE) && (s->enable & DAC_RUNNING)) + val |= PCM_ENABLE_OUTPUT; + return put_user(val, p); + + case SNDCTL_DSP_SETTRIGGER: + get_user_ret(val, p, -EFAULT); + if (file->f_mode & FMODE_READ) { + if (val & PCM_ENABLE_INPUT) { + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + 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(s, 0))) + 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->enable & DAC_RUNNING) && (val = prog_dmabuf(s, 0)) != 0) + return val; + spin_lock_irqsave(&card->lock, flags); + m3_update_ptr(s); + abinfo.fragsize = s->dma_dac.fragsize; + abinfo.bytes = s->dma_dac.dmasize - s->dma_dac.count; + abinfo.fragstotal = s->dma_dac.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift; + spin_unlock_irqrestore(&card->lock, flags); + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!(s->enable & ADC_RUNNING) && (val = prog_dmabuf(s, 1)) != 0) + return val; + spin_lock_irqsave(&card->lock, flags); + m3_update_ptr(s); + 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(&card->lock, flags); + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&card->lock, flags); + m3_update_ptr(s); + val = s->dma_dac.count; + spin_unlock_irqrestore(&card->lock, flags); + return put_user(val, p); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + spin_lock_irqsave(&card->lock, flags); + m3_update_ptr(s); + cinfo.bytes = s->dma_adc.total_bytes; + cinfo.blocks = s->dma_adc.count >> s->dma_adc.fragshift; + cinfo.ptr = s->dma_adc.hwptr; + if (s->dma_adc.mapped) + s->dma_adc.count &= s->dma_adc.fragsize-1; + spin_unlock_irqrestore(&card->lock, flags); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&card->lock, flags); + m3_update_ptr(s); + cinfo.bytes = s->dma_dac.total_bytes; + 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(&card->lock, flags); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + if ((val = prog_dmabuf(s, 0))) + return val; + return put_user(s->dma_dac.fragsize, p); + } + if ((val = prog_dmabuf(s, 1))) + return val; + return put_user(s->dma_adc.fragsize, p); + + case SNDCTL_DSP_SETFRAGMENT: + get_user_ret(val, p, -EFAULT); + spin_lock_irqsave(&card->lock, flags); + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = val & 0xffff; + s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_adc.ossfragshift < 4) + s->dma_adc.ossfragshift = 4; + if (s->dma_adc.ossfragshift > 15) + s->dma_adc.ossfragshift = 15; + if (s->dma_adc.ossmaxfrags < 4) + s->dma_adc.ossmaxfrags = 4; + } + if (file->f_mode & FMODE_WRITE) { + s->dma_dac.ossfragshift = val & 0xffff; + s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac.ossfragshift < 4) + s->dma_dac.ossfragshift = 4; + if (s->dma_dac.ossfragshift > 15) + s->dma_dac.ossfragshift = 15; + if (s->dma_dac.ossmaxfrags < 4) + s->dma_dac.ossmaxfrags = 4; + } + spin_unlock_irqrestore(&card->lock, flags); + return 0; + + 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; + get_user_ret(val, p, -EFAULT); + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + if (file->f_mode & FMODE_READ) + s->dma_adc.subdivision = val; + if (file->f_mode & FMODE_WRITE) + s->dma_dac.subdivision = val; + return 0; + + case SOUND_PCM_READ_RATE: + return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, p); + + case SOUND_PCM_READ_CHANNELS: + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (ESS_FMT_STEREO << ESS_ADC_SHIFT) + : (ESS_FMT_STEREO << ESS_DAC_SHIFT))) ? 2 : 1, p); + + case SOUND_PCM_READ_BITS: + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (ESS_FMT_16BIT << ESS_ADC_SHIFT) + : (ESS_FMT_16BIT << ESS_DAC_SHIFT))) ? 16 : 8, p); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + + } + return -EINVAL; +} + +static int +allocate_dmabuf(struct pci_dev *pci_dev, struct dmabuf *db) +{ + int order; + + DPRINTK(DPSTR,"allocating for dmabuf %p\n", db); + + /* + * alloc as big a chunk as we can, start with + * 64k 'cause we're insane. based on order cause + * the amazingly complicated prog_dmabuf wants it. + * + * pci_alloc_sonsistent guarantees that it won't cross a natural + * boundary; the m3 hardware can't have dma cross a 64k bus + * address boundary. + */ + for (order = 16-PAGE_SHIFT; order >= 1; order--) { + db->rawbuf = pci_alloc_consistent(pci_dev, PAGE_SIZE << order, + &(db->handle)); + if(db->rawbuf) + break; + } + + if (!db->rawbuf) + return 1; + + DPRINTK(DPSTR,"allocated %ld (%d) bytes at %p\n", + PAGE_SIZE<rawbuf); + + { + struct page *page, *pend; + + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << order) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + SetPageReserved(page); + } + + + db->buforder = order; + db->ready = 0; + db->mapped = 0; + + return 0; +} + +static void +nuke_lists(struct m3_card *card, struct dmabuf *db) +{ + m3_remove_list(card, &(card->dma_list), db->dma_index); + m3_remove_list(card, &(card->msrc_list), db->msrc_index); + db->in_lists = 0; +} + +static void +free_dmabuf(struct pci_dev *pci_dev, struct dmabuf *db) +{ + if(db->rawbuf == NULL) + return; + + DPRINTK(DPSTR,"freeing %p from dmabuf %p\n",db->rawbuf, db); + + { + struct page *page, *pend; + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + ClearPageReserved(page); + } + + + pci_free_consistent(pci_dev, PAGE_SIZE << db->buforder, + db->rawbuf, db->handle); + + db->rawbuf = NULL; + db->buforder = 0; + db->mapped = 0; + db->ready = 0; +} + +static int m3_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct m3_card *c; + struct m3_state *s = NULL; + int i; + unsigned char fmtm = ~0, fmts = 0; + unsigned long flags; + + /* + * Scan the cards and find the channel. We only + * do this at open time so it is ok + */ + for(c = devs ; c != NULL ; c = c->next) { + + for(i=0;ichannels[i].dev_audio < 0) + continue; + if((c->channels[i].dev_audio ^ minor) & ~0xf) + continue; + + s = &c->channels[i]; + break; + } + } + + if (!s) + return -ENODEV; + + VALIDATE_STATE(s); + + file->private_data = s; + + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & file->f_mode) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EWOULDBLOCK; + } + up(&s->open_sem); + interruptible_sleep_on(&s->open_wait); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + + spin_lock_irqsave(&c->lock, flags); + + if (file->f_mode & FMODE_READ) { + fmtm &= ~((ESS_FMT_STEREO | ESS_FMT_16BIT) << ESS_ADC_SHIFT); + if ((minor & 0xf) == SND_DEV_DSP16) + fmts |= ESS_FMT_16BIT << ESS_ADC_SHIFT; + + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = s->dma_adc.subdivision = 0; + set_adc_rate(s, 8000); + } + if (file->f_mode & FMODE_WRITE) { + fmtm &= ~((ESS_FMT_STEREO | ESS_FMT_16BIT) << ESS_DAC_SHIFT); + if ((minor & 0xf) == SND_DEV_DSP16) + fmts |= ESS_FMT_16BIT << ESS_DAC_SHIFT; + + s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = s->dma_dac.subdivision = 0; + set_dac_rate(s, 8000); + } + set_fmt(s, fmtm, fmts); + s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + + up(&s->open_sem); + spin_unlock_irqrestore(&c->lock, flags); + return nonseekable_open(inode, file); +} + +static int m3_release(struct inode *inode, struct file *file) +{ + struct m3_state *s = (struct m3_state *)file->private_data; + struct m3_card *card=s->card; + unsigned long flags; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) + drain_dac(s, file->f_flags & O_NONBLOCK); + + down(&s->open_sem); + spin_lock_irqsave(&card->lock, flags); + + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + if(s->dma_dac.in_lists) { + m3_remove_list(s->card, &(s->card->mixer_list), s->dma_dac.mixer_index); + nuke_lists(s->card, &(s->dma_dac)); + } + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + if(s->dma_adc.in_lists) { + m3_remove_list(s->card, &(s->card->adc1_list), s->dma_adc.adc1_index); + nuke_lists(s->card, &(s->dma_adc)); + } + } + + s->open_mode &= (~file->f_mode) & (FMODE_READ|FMODE_WRITE); + + spin_unlock_irqrestore(&card->lock, flags); + up(&s->open_sem); + wake_up(&s->open_wait); + + return 0; +} + +/* + * Wait for the ac97 serial bus to be free. + * return nonzero if the bus is still busy. + */ +static int m3_ac97_wait(struct m3_card *card) +{ + int i = 10000; + + while( (m3_inb(card, 0x30) & 1) && i--) ; + + return i == 0; +} + +static u16 m3_ac97_read(struct ac97_codec *codec, u8 reg) +{ + u16 ret = 0; + struct m3_card *card = codec->private_data; + + spin_lock(&card->ac97_lock); + + if(m3_ac97_wait(card)) { + printk(KERN_ERR PFX "serial bus busy reading reg 0x%x\n",reg); + goto out; + } + + m3_outb(card, 0x80 | (reg & 0x7f), 0x30); + + if(m3_ac97_wait(card)) { + printk(KERN_ERR PFX "serial bus busy finishing read reg 0x%x\n",reg); + goto out; + } + + ret = m3_inw(card, 0x32); + DPRINTK(DPCRAP,"reading 0x%04x from 0x%02x\n",ret, reg); + +out: + spin_unlock(&card->ac97_lock); + return ret; +} + +static void m3_ac97_write(struct ac97_codec *codec, u8 reg, u16 val) +{ + struct m3_card *card = codec->private_data; + + spin_lock(&card->ac97_lock); + + if(m3_ac97_wait(card)) { + printk(KERN_ERR PFX "serial bus busy writing 0x%x to 0x%x\n",val, reg); + goto out; + } + DPRINTK(DPCRAP,"writing 0x%04x to 0x%02x\n", val, reg); + + m3_outw(card, val, 0x32); + m3_outb(card, reg & 0x7f, 0x30); +out: + spin_unlock(&card->ac97_lock); +} +/* OSS /dev/mixer file operation methods */ +static int m3_open_mixdev(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct m3_card *card = devs; + + for (card = devs; card != NULL; card = card->next) { + if((card->ac97 != NULL) && (card->ac97->dev_mixer == minor)) + break; + } + + if (!card) { + return -ENODEV; + } + + file->private_data = card->ac97; + + return nonseekable_open(inode, file); +} + +static int m3_release_mixdev(struct inode *inode, struct file *file) +{ + return 0; +} + +static int m3_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct ac97_codec *codec = (struct ac97_codec *)file->private_data; + + return codec->mixer_ioctl(codec, cmd, arg); +} + +static struct file_operations m3_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = m3_ioctl_mixdev, + .open = m3_open_mixdev, + .release = m3_release_mixdev, +}; + +static void remote_codec_config(int io, int isremote) +{ + isremote = isremote ? 1 : 0; + + outw( (inw(io + RING_BUS_CTRL_B) & ~SECOND_CODEC_ID_MASK) | isremote, + io + RING_BUS_CTRL_B); + outw( (inw(io + SDO_OUT_DEST_CTRL) & ~COMMAND_ADDR_OUT) | isremote, + io + SDO_OUT_DEST_CTRL); + outw( (inw(io + SDO_IN_DEST_CTRL) & ~STATUS_ADDR_IN) | isremote, + io + SDO_IN_DEST_CTRL); +} + +/* + * hack, returns non zero on err + */ +static int try_read_vendor(struct m3_card *card) +{ + u16 ret; + + if(m3_ac97_wait(card)) + return 1; + + m3_outb(card, 0x80 | (AC97_VENDOR_ID1 & 0x7f), 0x30); + + if(m3_ac97_wait(card)) + return 1; + + ret = m3_inw(card, 0x32); + + return (ret == 0) || (ret == 0xffff); +} + +static void m3_codec_reset(struct m3_card *card, int busywait) +{ + u16 dir; + int delay1 = 0, delay2 = 0, i; + int io = card->iobase; + + switch (card->card_type) { + /* + * the onboard codec on the allegro seems + * to want to wait a very long time before + * coming back to life + */ + case ESS_ALLEGRO: + delay1 = 50; + delay2 = 800; + break; + case ESS_MAESTRO3: + case ESS_MAESTRO3HW: + delay1 = 20; + delay2 = 500; + break; + } + + for(i = 0; i < 5; i ++) { + dir = inw(io + GPIO_DIRECTION); + dir |= 0x10; /* assuming pci bus master? */ + + remote_codec_config(io, 0); + + outw(IO_SRAM_ENABLE, io + RING_BUS_CTRL_A); + udelay(20); + + outw(dir & ~GPO_PRIMARY_AC97 , io + GPIO_DIRECTION); + outw(~GPO_PRIMARY_AC97 , io + GPIO_MASK); + outw(0, io + GPIO_DATA); + outw(dir | GPO_PRIMARY_AC97, io + GPIO_DIRECTION); + + if(busywait) { + mdelay(delay1); + } else { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout((delay1 * HZ) / 1000); + } + + outw(GPO_PRIMARY_AC97, io + GPIO_DATA); + udelay(5); + /* ok, bring back the ac-link */ + outw(IO_SRAM_ENABLE | SERIAL_AC_LINK_ENABLE, io + RING_BUS_CTRL_A); + outw(~0, io + GPIO_MASK); + + if(busywait) { + mdelay(delay2); + } else { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout((delay2 * HZ) / 1000); + } + if(! try_read_vendor(card)) + break; + + delay1 += 10; + delay2 += 100; + + DPRINTK(DPMOD, "retrying codec reset with delays of %d and %d ms\n", + delay1, delay2); + } + +#if 0 + /* more gung-ho reset that doesn't + * seem to work anywhere :) + */ + tmp = inw(io + RING_BUS_CTRL_A); + outw(RAC_SDFS_ENABLE|LAC_SDFS_ENABLE, io + RING_BUS_CTRL_A); + mdelay(20); + outw(tmp, io + RING_BUS_CTRL_A); + mdelay(50); +#endif +} + +static int __devinit m3_codec_install(struct m3_card *card) +{ + struct ac97_codec *codec; + + if ((codec = ac97_alloc_codec()) == NULL) + return -ENOMEM; + + codec->private_data = card; + codec->codec_read = m3_ac97_read; + codec->codec_write = m3_ac97_write; + /* someday we should support secondary codecs.. */ + codec->id = 0; + + if (ac97_probe_codec(codec) == 0) { + printk(KERN_ERR PFX "codec probe failed\n"); + ac97_release_codec(codec); + return -1; + } + + if ((codec->dev_mixer = register_sound_mixer(&m3_mixer_fops, -1)) < 0) { + printk(KERN_ERR PFX "couldn't register mixer!\n"); + ac97_release_codec(codec); + return -1; + } + + card->ac97 = codec; + + return 0; +} + + +#define MINISRC_LPF_LEN 10 +static u16 minisrc_lpf[MINISRC_LPF_LEN] = { + 0X0743, 0X1104, 0X0A4C, 0XF88D, 0X242C, + 0X1023, 0X1AA9, 0X0B60, 0XEFDD, 0X186F +}; +static void m3_assp_init(struct m3_card *card) +{ + int i; + + /* zero kernel data */ + for(i = 0 ; i < (REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA) / 2; i++) + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_BASE_ADDR + i, 0); + + /* zero mixer data? */ + for(i = 0 ; i < (REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA) / 2; i++) + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_BASE_ADDR2 + i, 0); + + /* init dma pointer */ + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_CURRENT_DMA, + KDATA_DMA_XFER0); + + /* write kernel into code memory.. */ + for(i = 0 ; i < sizeof(assp_kernel_image) / 2; i++) { + m3_assp_write(card, MEMTYPE_INTERNAL_CODE, + REV_B_CODE_MEMORY_BEGIN + i, + assp_kernel_image[i]); + } + + /* + * We only have this one client and we know that 0x400 + * is free in our kernel's mem map, so lets just + * drop it there. It seems that the minisrc doesn't + * need vectors, so we won't bother with them.. + */ + for(i = 0 ; i < sizeof(assp_minisrc_image) / 2; i++) { + m3_assp_write(card, MEMTYPE_INTERNAL_CODE, + 0x400 + i, + assp_minisrc_image[i]); + } + + /* + * write the coefficients for the low pass filter? + */ + for(i = 0; i < MINISRC_LPF_LEN ; i++) { + m3_assp_write(card, MEMTYPE_INTERNAL_CODE, + 0x400 + MINISRC_COEF_LOC + i, + minisrc_lpf[i]); + } + + m3_assp_write(card, MEMTYPE_INTERNAL_CODE, + 0x400 + MINISRC_COEF_LOC + MINISRC_LPF_LEN, + 0x8000); + + /* + * the minisrc is the only thing on + * our task list.. + */ + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_TASK0, + 0x400); + + /* + * init the mixer number.. + */ + + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_MIXER_TASK_NUMBER,0); + + /* + * EXTREME KERNEL MASTER VOLUME + */ + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_DAC_LEFT_VOLUME, ARB_VOLUME); + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_DAC_RIGHT_VOLUME, ARB_VOLUME); + + card->mixer_list.mem_addr = KDATA_MIXER_XFER0; + card->mixer_list.max = MAX_VIRTUAL_MIXER_CHANNELS; + card->adc1_list.mem_addr = KDATA_ADC1_XFER0; + card->adc1_list.max = MAX_VIRTUAL_ADC1_CHANNELS; + card->dma_list.mem_addr = KDATA_DMA_XFER0; + card->dma_list.max = MAX_VIRTUAL_DMA_CHANNELS; + card->msrc_list.mem_addr = KDATA_INSTANCE0_MINISRC; + card->msrc_list.max = MAX_INSTANCE_MINISRC; +} + +static int setup_msrc(struct m3_card *card, + struct assp_instance *inst, int index) +{ + int data_bytes = 2 * ( MINISRC_TMP_BUFFER_SIZE / 2 + + MINISRC_IN_BUFFER_SIZE / 2 + + 1 + MINISRC_OUT_BUFFER_SIZE / 2 + 1 ); + int address, i; + + /* + * the revb memory map has 0x1100 through 0x1c00 + * free. + */ + + /* + * align instance address to 256 bytes so that it's + * shifted list address is aligned. + * list address = (mem address >> 1) >> 7; + */ + data_bytes = (data_bytes + 255) & ~255; + address = 0x1100 + ((data_bytes/2) * index); + + if((address + (data_bytes/2)) >= 0x1c00) { + printk(KERN_ERR PFX "no memory for %d bytes at ind %d (addr 0x%x)\n", + data_bytes, index, address); + return -1; + } + + for(i = 0; i < data_bytes/2 ; i++) + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + address + i, 0); + + inst->code = 0x400; + inst->data = address; + + return 0; +} + +static int m3_assp_client_init(struct m3_state *s) +{ + setup_msrc(s->card, &(s->dac_inst), s->index * 2); + setup_msrc(s->card, &(s->adc_inst), (s->index * 2) + 1); + + return 0; +} + +static void m3_amp_enable(struct m3_card *card, int enable) +{ + /* + * this works for the reference board, have to find + * out about others + * + * this needs more magic for 4 speaker, but.. + */ + int io = card->iobase; + u16 gpo, polarity_port, polarity; + + if(!external_amp) + return; + + if (gpio_pin >= 0 && gpio_pin <= 15) { + polarity_port = 0x1000 + (0x100 * gpio_pin); + } else { + switch (card->card_type) { + case ESS_ALLEGRO: + polarity_port = 0x1800; + break; + default: + polarity_port = 0x1100; + /* Panasonic toughbook CF72 has to be different... */ + if(card->pcidev->subsystem_vendor == 0x10F7 && card->pcidev->subsystem_device == 0x833D) + polarity_port = 0x1D00; + break; + } + } + + gpo = (polarity_port >> 8) & 0x0F; + polarity = polarity_port >> 12; + if ( enable ) + polarity = !polarity; + polarity = polarity << gpo; + gpo = 1 << gpo; + + outw(~gpo , io + GPIO_MASK); + + outw( inw(io + GPIO_DIRECTION) | gpo , + io + GPIO_DIRECTION); + + outw( (GPO_SECONDARY_AC97 | GPO_PRIMARY_AC97 | polarity) , + io + GPIO_DATA); + + outw(0xffff , io + GPIO_MASK); +} + +static int +maestro_config(struct m3_card *card) +{ + struct pci_dev *pcidev = card->pcidev; + u32 n; + u8 t; /* makes as much sense as 'n', no? */ + + pci_read_config_dword(pcidev, PCI_ALLEGRO_CONFIG, &n); + n &= REDUCED_DEBOUNCE; + n |= PM_CTRL_ENABLE | CLK_DIV_BY_49 | USE_PCI_TIMING; + pci_write_config_dword(pcidev, PCI_ALLEGRO_CONFIG, n); + + outb(RESET_ASSP, card->iobase + ASSP_CONTROL_B); + pci_read_config_dword(pcidev, PCI_ALLEGRO_CONFIG, &n); + n &= ~INT_CLK_SELECT; + if(card->card_type >= ESS_MAESTRO3) { + n &= ~INT_CLK_MULT_ENABLE; + n |= INT_CLK_SRC_NOT_PCI; + } + n &= ~( CLK_MULT_MODE_SELECT | CLK_MULT_MODE_SELECT_2 ); + pci_write_config_dword(pcidev, PCI_ALLEGRO_CONFIG, n); + + if(card->card_type <= ESS_ALLEGRO) { + pci_read_config_dword(pcidev, PCI_USER_CONFIG, &n); + n |= IN_CLK_12MHZ_SELECT; + pci_write_config_dword(pcidev, PCI_USER_CONFIG, n); + } + + t = inb(card->iobase + ASSP_CONTROL_A); + t &= ~( DSP_CLK_36MHZ_SELECT | ASSP_CLK_49MHZ_SELECT); + t |= ASSP_CLK_49MHZ_SELECT; + t |= ASSP_0_WS_ENABLE; + outb(t, card->iobase + ASSP_CONTROL_A); + + outb(RUN_ASSP, card->iobase + ASSP_CONTROL_B); + + return 0; +} + +static void m3_enable_ints(struct m3_card *card) +{ + unsigned long io = card->iobase; + + outw(ASSP_INT_ENABLE, io + HOST_INT_CTRL); + outb(inb(io + ASSP_CONTROL_C) | ASSP_HOST_INT_ENABLE, + io + ASSP_CONTROL_C); +} + +static struct file_operations m3_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = m3_read, + .write = m3_write, + .poll = m3_poll, + .ioctl = m3_ioctl, + .mmap = m3_mmap, + .open = m3_open, + .release = m3_release, +}; + +#ifdef CONFIG_PM +static int alloc_dsp_suspendmem(struct m3_card *card) +{ + int len = sizeof(u16) * (REV_B_CODE_MEMORY_LENGTH + REV_B_DATA_MEMORY_LENGTH); + + if( (card->suspend_mem = vmalloc(len)) == NULL) + return 1; + + return 0; +} +static void free_dsp_suspendmem(struct m3_card *card) +{ + if(card->suspend_mem) + vfree(card->suspend_mem); +} + +#else +#define alloc_dsp_suspendmem(args...) 0 +#define free_dsp_suspendmem(args...) +#endif + +/* + * great day! this function is ugly as hell. + */ +static int __devinit m3_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id) +{ + u32 n; + int i; + struct m3_card *card = NULL; + int ret = 0; + int card_type = pci_id->driver_data; + + DPRINTK(DPMOD, "in maestro_install\n"); + + if (pci_enable_device(pci_dev)) + return -EIO; + + if (pci_set_dma_mask(pci_dev, M3_PCI_DMA_MASK)) { + printk(KERN_ERR PFX "architecture does not support limiting to 28bit PCI bus addresses\n"); + return -ENODEV; + } + + pci_set_master(pci_dev); + + if( (card = kmalloc(sizeof(struct m3_card), GFP_KERNEL)) == NULL) { + printk(KERN_WARNING PFX "out of memory\n"); + return -ENOMEM; + } + memset(card, 0, sizeof(struct m3_card)); + card->pcidev = pci_dev; + init_waitqueue_head(&card->suspend_queue); + + if ( ! request_region(pci_resource_start(pci_dev, 0), + pci_resource_len (pci_dev, 0), M3_MODULE_NAME)) { + + printk(KERN_WARNING PFX "unable to reserve I/O space.\n"); + ret = -EBUSY; + goto out; + } + + card->iobase = pci_resource_start(pci_dev, 0); + + if(alloc_dsp_suspendmem(card)) { + printk(KERN_WARNING PFX "couldn't alloc %d bytes for saving dsp state on suspend\n", + REV_B_CODE_MEMORY_LENGTH + REV_B_DATA_MEMORY_LENGTH); + ret = -ENOMEM; + goto out; + } + + card->card_type = card_type; + card->irq = pci_dev->irq; + card->next = devs; + card->magic = M3_CARD_MAGIC; + spin_lock_init(&card->lock); + spin_lock_init(&card->ac97_lock); + devs = card; + for(i = 0; ichannels[i]); + s->dev_audio = -1; + } + + printk(KERN_INFO PFX "Configuring ESS %s found at IO 0x%04X IRQ %d\n", + card_names[card->card_type], card->iobase, card->irq); + + pci_read_config_dword(pci_dev, PCI_SUBSYSTEM_VENDOR_ID, &n); + printk(KERN_INFO PFX " subvendor id: 0x%08x\n",n); + + maestro_config(card); + m3_assp_halt(card); + + m3_codec_reset(card, 0); + + if(m3_codec_install(card)) { + ret = -EIO; + goto out; + } + + m3_assp_init(card); + m3_amp_enable(card, 1); + + for(i=0;ichannels[i]; + + s->index = i; + + s->card = card; + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac.wait); + init_waitqueue_head(&s->open_wait); + init_MUTEX(&(s->open_sem)); + s->magic = M3_STATE_MAGIC; + + m3_assp_client_init(s); + + if(s->dma_adc.ready || s->dma_dac.ready || s->dma_adc.rawbuf) + printk(KERN_WARNING PFX "initing a dsp device that is already in use?\n"); + /* register devices */ + if ((s->dev_audio = register_sound_dsp(&m3_audio_fops, -1)) < 0) { + break; + } + + if( allocate_dmabuf(card->pcidev, &(s->dma_adc)) || + allocate_dmabuf(card->pcidev, &(s->dma_dac))) { + ret = -ENOMEM; + goto out; + } + } + + if(request_irq(card->irq, m3_interrupt, SA_SHIRQ, card_names[card->card_type], card)) { + + printk(KERN_ERR PFX "unable to allocate irq %d,\n", card->irq); + + ret = -EIO; + goto out; + } + + pci_set_drvdata(pci_dev, card); + + m3_enable_ints(card); + m3_assp_continue(card); + +out: + if(ret) { + if(card->iobase) + release_region(pci_resource_start(pci_dev, 0), pci_resource_len(pci_dev, 0)); + free_dsp_suspendmem(card); + if(card->ac97) { + unregister_sound_mixer(card->ac97->dev_mixer); + kfree(card->ac97); + } + for(i=0;ichannels[i]; + if(s->dev_audio != -1) + unregister_sound_dsp(s->dev_audio); + } + kfree(card); + } + + return ret; +} + +static void m3_remove(struct pci_dev *pci_dev) +{ + struct m3_card *card; + + unregister_reboot_notifier(&m3_reboot_nb); + + while ((card = devs)) { + int i; + devs = devs->next; + + free_irq(card->irq, card); + unregister_sound_mixer(card->ac97->dev_mixer); + kfree(card->ac97); + + for(i=0;ichannels[i]; + if(s->dev_audio < 0) + continue; + + unregister_sound_dsp(s->dev_audio); + free_dmabuf(card->pcidev, &s->dma_adc); + free_dmabuf(card->pcidev, &s->dma_dac); + } + + release_region(card->iobase, 256); + free_dsp_suspendmem(card); + kfree(card); + } + devs = NULL; +} + +/* + * some bioses like the sound chip to be powered down + * at shutdown. We're just calling _suspend to + * achieve that.. + */ +static int m3_notifier(struct notifier_block *nb, unsigned long event, void *buf) +{ + struct m3_card *card; + + DPRINTK(DPMOD, "notifier suspending all cards\n"); + + for(card = devs; card != NULL; card = card->next) { + if(!card->in_suspend) + m3_suspend(card->pcidev, PMSG_SUSPEND); /* XXX legal? */ + } + return 0; +} + +static int m3_suspend(struct pci_dev *pci_dev, pm_message_t state) +{ + unsigned long flags; + int i; + struct m3_card *card = pci_get_drvdata(pci_dev); + + /* must be a better way.. */ + spin_lock_irqsave(&card->lock, flags); + + DPRINTK(DPMOD, "pm in dev %p\n",card); + + for(i=0;ichannels[i]; + + if(s->dev_audio == -1) + continue; + + DPRINTK(DPMOD, "stop_adc/dac() device %d\n",i); + stop_dac(s); + stop_adc(s); + } + + mdelay(10); /* give the assp a chance to idle.. */ + + m3_assp_halt(card); + + if(card->suspend_mem) { + int index = 0; + + DPRINTK(DPMOD, "saving code\n"); + for(i = REV_B_CODE_MEMORY_BEGIN ; i <= REV_B_CODE_MEMORY_END; i++) + card->suspend_mem[index++] = + m3_assp_read(card, MEMTYPE_INTERNAL_CODE, i); + DPRINTK(DPMOD, "saving data\n"); + for(i = REV_B_DATA_MEMORY_BEGIN ; i <= REV_B_DATA_MEMORY_END; i++) + card->suspend_mem[index++] = + m3_assp_read(card, MEMTYPE_INTERNAL_DATA, i); + } + + DPRINTK(DPMOD, "powering down apci regs\n"); + m3_outw(card, 0xffff, 0x54); + m3_outw(card, 0xffff, 0x56); + + card->in_suspend = 1; + + spin_unlock_irqrestore(&card->lock, flags); + + return 0; +} + +static int m3_resume(struct pci_dev *pci_dev) +{ + unsigned long flags; + int index; + int i; + struct m3_card *card = pci_get_drvdata(pci_dev); + + spin_lock_irqsave(&card->lock, flags); + card->in_suspend = 0; + + DPRINTK(DPMOD, "resuming\n"); + + /* first lets just bring everything back. .*/ + + DPRINTK(DPMOD, "bringing power back on card 0x%p\n",card); + m3_outw(card, 0, 0x54); + m3_outw(card, 0, 0x56); + + DPRINTK(DPMOD, "restoring pci configs and reseting codec\n"); + maestro_config(card); + m3_assp_halt(card); + m3_codec_reset(card, 1); + + DPRINTK(DPMOD, "restoring dsp code card\n"); + index = 0; + for(i = REV_B_CODE_MEMORY_BEGIN ; i <= REV_B_CODE_MEMORY_END; i++) + m3_assp_write(card, MEMTYPE_INTERNAL_CODE, i, + card->suspend_mem[index++]); + for(i = REV_B_DATA_MEMORY_BEGIN ; i <= REV_B_DATA_MEMORY_END; i++) + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, i, + card->suspend_mem[index++]); + + /* tell the dma engine to restart itself */ + m3_assp_write(card, MEMTYPE_INTERNAL_DATA, + KDATA_DMA_ACTIVE, 0); + + DPRINTK(DPMOD, "resuming dsp\n"); + m3_assp_continue(card); + + DPRINTK(DPMOD, "enabling ints\n"); + m3_enable_ints(card); + + /* bring back the old school flavor */ + for(i = 0; i < SOUND_MIXER_NRDEVICES ; i++) { + int state = card->ac97->mixer_state[i]; + if (!supported_mixer(card->ac97, i)) + continue; + + card->ac97->write_mixer(card->ac97, i, + state & 0xff, (state >> 8) & 0xff); + } + + m3_amp_enable(card, 1); + + /* + * now we flip on the music + */ + for(i=0;ichannels[i]; + if(s->dev_audio == -1) + continue; + /* + * db->ready makes it so these guys can be + * called unconditionally.. + */ + DPRINTK(DPMOD, "turning on dacs ind %d\n",i); + start_dac(s); + start_adc(s); + } + + spin_unlock_irqrestore(&card->lock, flags); + + /* + * all right, we think things are ready, + * wake up people who were using the device + * when we suspended + */ + wake_up(&card->suspend_queue); + + return 0; +} + +MODULE_AUTHOR("Zach Brown "); +MODULE_DESCRIPTION("ESS Maestro3/Allegro Driver"); +MODULE_LICENSE("GPL"); + +#ifdef M_DEBUG +module_param(debug, int, 0); +#endif +module_param(external_amp, int, 0); +module_param(gpio_pin, int, 0); + +static struct pci_driver m3_pci_driver = { + .name = "ess_m3_audio", + .id_table = m3_id_table, + .probe = m3_probe, + .remove = m3_remove, + .suspend = m3_suspend, + .resume = m3_resume, +}; + +static int __init m3_init_module(void) +{ + printk(KERN_INFO PFX "version " DRIVER_VERSION " built at " __TIME__ " " __DATE__ "\n"); + + if (register_reboot_notifier(&m3_reboot_nb)) { + printk(KERN_WARNING PFX "reboot notifier registration failed\n"); + return -ENODEV; /* ? */ + } + + if (pci_register_driver(&m3_pci_driver)) { + unregister_reboot_notifier(&m3_reboot_nb); + return -ENODEV; + } + return 0; +} + +static void __exit m3_cleanup_module(void) +{ + pci_unregister_driver(&m3_pci_driver); +} + +module_init(m3_init_module); +module_exit(m3_cleanup_module); + +void check_suspend(struct m3_card *card) +{ + DECLARE_WAITQUEUE(wait, current); + + if(!card->in_suspend) + return; + + card->in_suspend++; + add_wait_queue(&card->suspend_queue, &wait); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule(); + remove_wait_queue(&card->suspend_queue, &wait); + set_current_state(TASK_RUNNING); +} diff --git a/sound/oss/maestro3.h b/sound/oss/maestro3.h new file mode 100644 index 000000000000..dde29862c572 --- /dev/null +++ b/sound/oss/maestro3.h @@ -0,0 +1,821 @@ +/* + * ESS Technology allegro audio driver. + * + * Copyright (C) 1992-2000 Don Kim (don.kim@esstech.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Hacked for the maestro3 driver by zab + */ + +// Allegro PCI configuration registers +#define PCI_LEGACY_AUDIO_CTRL 0x40 +#define SOUND_BLASTER_ENABLE 0x00000001 +#define FM_SYNTHESIS_ENABLE 0x00000002 +#define GAME_PORT_ENABLE 0x00000004 +#define MPU401_IO_ENABLE 0x00000008 +#define MPU401_IRQ_ENABLE 0x00000010 +#define ALIAS_10BIT_IO 0x00000020 +#define SB_DMA_MASK 0x000000C0 +#define SB_DMA_0 0x00000040 +#define SB_DMA_1 0x00000040 +#define SB_DMA_R 0x00000080 +#define SB_DMA_3 0x000000C0 +#define SB_IRQ_MASK 0x00000700 +#define SB_IRQ_5 0x00000000 +#define SB_IRQ_7 0x00000100 +#define SB_IRQ_9 0x00000200 +#define SB_IRQ_10 0x00000300 +#define MIDI_IRQ_MASK 0x00003800 +#define SERIAL_IRQ_ENABLE 0x00004000 +#define DISABLE_LEGACY 0x00008000 + +#define PCI_ALLEGRO_CONFIG 0x50 +#define SB_ADDR_240 0x00000004 +#define MPU_ADDR_MASK 0x00000018 +#define MPU_ADDR_330 0x00000000 +#define MPU_ADDR_300 0x00000008 +#define MPU_ADDR_320 0x00000010 +#define MPU_ADDR_340 0x00000018 +#define USE_PCI_TIMING 0x00000040 +#define POSTED_WRITE_ENABLE 0x00000080 +#define DMA_POLICY_MASK 0x00000700 +#define DMA_DDMA 0x00000000 +#define DMA_TDMA 0x00000100 +#define DMA_PCPCI 0x00000200 +#define DMA_WBDMA16 0x00000400 +#define DMA_WBDMA4 0x00000500 +#define DMA_WBDMA2 0x00000600 +#define DMA_WBDMA1 0x00000700 +#define DMA_SAFE_GUARD 0x00000800 +#define HI_PERF_GP_ENABLE 0x00001000 +#define PIC_SNOOP_MODE_0 0x00002000 +#define PIC_SNOOP_MODE_1 0x00004000 +#define SOUNDBLASTER_IRQ_MASK 0x00008000 +#define RING_IN_ENABLE 0x00010000 +#define SPDIF_TEST_MODE 0x00020000 +#define CLK_MULT_MODE_SELECT_2 0x00040000 +#define EEPROM_WRITE_ENABLE 0x00080000 +#define CODEC_DIR_IN 0x00100000 +#define HV_BUTTON_FROM_GD 0x00200000 +#define REDUCED_DEBOUNCE 0x00400000 +#define HV_CTRL_ENABLE 0x00800000 +#define SPDIF_ENABLE 0x01000000 +#define CLK_DIV_SELECT 0x06000000 +#define CLK_DIV_BY_48 0x00000000 +#define CLK_DIV_BY_49 0x02000000 +#define CLK_DIV_BY_50 0x04000000 +#define CLK_DIV_RESERVED 0x06000000 +#define PM_CTRL_ENABLE 0x08000000 +#define CLK_MULT_MODE_SELECT 0x30000000 +#define CLK_MULT_MODE_SHIFT 28 +#define CLK_MULT_MODE_0 0x00000000 +#define CLK_MULT_MODE_1 0x10000000 +#define CLK_MULT_MODE_2 0x20000000 +#define CLK_MULT_MODE_3 0x30000000 +#define INT_CLK_SELECT 0x40000000 +#define INT_CLK_MULT_RESET 0x80000000 + +// M3 +#define INT_CLK_SRC_NOT_PCI 0x00100000 +#define INT_CLK_MULT_ENABLE 0x80000000 + +#define PCI_ACPI_CONTROL 0x54 +#define PCI_ACPI_D0 0x00000000 +#define PCI_ACPI_D1 0xB4F70000 +#define PCI_ACPI_D2 0xB4F7B4F7 + +#define PCI_USER_CONFIG 0x58 +#define EXT_PCI_MASTER_ENABLE 0x00000001 +#define SPDIF_OUT_SELECT 0x00000002 +#define TEST_PIN_DIR_CTRL 0x00000004 +#define AC97_CODEC_TEST 0x00000020 +#define TRI_STATE_BUFFER 0x00000080 +#define IN_CLK_12MHZ_SELECT 0x00000100 +#define MULTI_FUNC_DISABLE 0x00000200 +#define EXT_MASTER_PAIR_SEL 0x00000400 +#define PCI_MASTER_SUPPORT 0x00000800 +#define STOP_CLOCK_ENABLE 0x00001000 +#define EAPD_DRIVE_ENABLE 0x00002000 +#define REQ_TRI_STATE_ENABLE 0x00004000 +#define REQ_LOW_ENABLE 0x00008000 +#define MIDI_1_ENABLE 0x00010000 +#define MIDI_2_ENABLE 0x00020000 +#define SB_AUDIO_SYNC 0x00040000 +#define HV_CTRL_TEST 0x00100000 +#define SOUNDBLASTER_TEST 0x00400000 + +#define PCI_USER_CONFIG_C 0x5C + +#define PCI_DDMA_CTRL 0x60 +#define DDMA_ENABLE 0x00000001 + + +// Allegro registers +#define HOST_INT_CTRL 0x18 +#define SB_INT_ENABLE 0x0001 +#define MPU401_INT_ENABLE 0x0002 +#define ASSP_INT_ENABLE 0x0010 +#define RING_INT_ENABLE 0x0020 +#define HV_INT_ENABLE 0x0040 +#define CLKRUN_GEN_ENABLE 0x0100 +#define HV_CTRL_TO_PME 0x0400 +#define SOFTWARE_RESET_ENABLE 0x8000 + +/* + * should be using the above defines, probably. + */ +#define REGB_ENABLE_RESET 0x01 +#define REGB_STOP_CLOCK 0x10 + +#define HOST_INT_STATUS 0x1A +#define SB_INT_PENDING 0x01 +#define MPU401_INT_PENDING 0x02 +#define ASSP_INT_PENDING 0x10 +#define RING_INT_PENDING 0x20 +#define HV_INT_PENDING 0x40 + +#define HARDWARE_VOL_CTRL 0x1B +#define SHADOW_MIX_REG_VOICE 0x1C +#define HW_VOL_COUNTER_VOICE 0x1D +#define SHADOW_MIX_REG_MASTER 0x1E +#define HW_VOL_COUNTER_MASTER 0x1F + +#define CODEC_COMMAND 0x30 +#define CODEC_READ_B 0x80 + +#define CODEC_STATUS 0x30 +#define CODEC_BUSY_B 0x01 + +#define CODEC_DATA 0x32 + +#define RING_BUS_CTRL_A 0x36 +#define RAC_PME_ENABLE 0x0100 +#define RAC_SDFS_ENABLE 0x0200 +#define LAC_PME_ENABLE 0x0400 +#define LAC_SDFS_ENABLE 0x0800 +#define SERIAL_AC_LINK_ENABLE 0x1000 +#define IO_SRAM_ENABLE 0x2000 +#define IIS_INPUT_ENABLE 0x8000 + +#define RING_BUS_CTRL_B 0x38 +#define SECOND_CODEC_ID_MASK 0x0003 +#define SPDIF_FUNC_ENABLE 0x0010 +#define SECOND_AC_ENABLE 0x0020 +#define SB_MODULE_INTF_ENABLE 0x0040 +#define SSPE_ENABLE 0x0040 +#define M3I_DOCK_ENABLE 0x0080 + +#define SDO_OUT_DEST_CTRL 0x3A +#define COMMAND_ADDR_OUT 0x0003 +#define PCM_LR_OUT_LOCAL 0x0000 +#define PCM_LR_OUT_REMOTE 0x0004 +#define PCM_LR_OUT_MUTE 0x0008 +#define PCM_LR_OUT_BOTH 0x000C +#define LINE1_DAC_OUT_LOCAL 0x0000 +#define LINE1_DAC_OUT_REMOTE 0x0010 +#define LINE1_DAC_OUT_MUTE 0x0020 +#define LINE1_DAC_OUT_BOTH 0x0030 +#define PCM_CLS_OUT_LOCAL 0x0000 +#define PCM_CLS_OUT_REMOTE 0x0040 +#define PCM_CLS_OUT_MUTE 0x0080 +#define PCM_CLS_OUT_BOTH 0x00C0 +#define PCM_RLF_OUT_LOCAL 0x0000 +#define PCM_RLF_OUT_REMOTE 0x0100 +#define PCM_RLF_OUT_MUTE 0x0200 +#define PCM_RLF_OUT_BOTH 0x0300 +#define LINE2_DAC_OUT_LOCAL 0x0000 +#define LINE2_DAC_OUT_REMOTE 0x0400 +#define LINE2_DAC_OUT_MUTE 0x0800 +#define LINE2_DAC_OUT_BOTH 0x0C00 +#define HANDSET_OUT_LOCAL 0x0000 +#define HANDSET_OUT_REMOTE 0x1000 +#define HANDSET_OUT_MUTE 0x2000 +#define HANDSET_OUT_BOTH 0x3000 +#define IO_CTRL_OUT_LOCAL 0x0000 +#define IO_CTRL_OUT_REMOTE 0x4000 +#define IO_CTRL_OUT_MUTE 0x8000 +#define IO_CTRL_OUT_BOTH 0xC000 + +#define SDO_IN_DEST_CTRL 0x3C +#define STATUS_ADDR_IN 0x0003 +#define PCM_LR_IN_LOCAL 0x0000 +#define PCM_LR_IN_REMOTE 0x0004 +#define PCM_LR_RESERVED 0x0008 +#define PCM_LR_IN_BOTH 0x000C +#define LINE1_ADC_IN_LOCAL 0x0000 +#define LINE1_ADC_IN_REMOTE 0x0010 +#define LINE1_ADC_IN_MUTE 0x0020 +#define MIC_ADC_IN_LOCAL 0x0000 +#define MIC_ADC_IN_REMOTE 0x0040 +#define MIC_ADC_IN_MUTE 0x0080 +#define LINE2_DAC_IN_LOCAL 0x0000 +#define LINE2_DAC_IN_REMOTE 0x0400 +#define LINE2_DAC_IN_MUTE 0x0800 +#define HANDSET_IN_LOCAL 0x0000 +#define HANDSET_IN_REMOTE 0x1000 +#define HANDSET_IN_MUTE 0x2000 +#define IO_STATUS_IN_LOCAL 0x0000 +#define IO_STATUS_IN_REMOTE 0x4000 + +#define SPDIF_IN_CTRL 0x3E +#define SPDIF_IN_ENABLE 0x0001 + +#define GPIO_DATA 0x60 +#define GPIO_DATA_MASK 0x0FFF +#define GPIO_HV_STATUS 0x3000 +#define GPIO_PME_STATUS 0x4000 + +#define GPIO_MASK 0x64 +#define GPIO_DIRECTION 0x68 +#define GPO_PRIMARY_AC97 0x0001 +#define GPI_LINEOUT_SENSE 0x0004 +#define GPO_SECONDARY_AC97 0x0008 +#define GPI_VOL_DOWN 0x0010 +#define GPI_VOL_UP 0x0020 +#define GPI_IIS_CLK 0x0040 +#define GPI_IIS_LRCLK 0x0080 +#define GPI_IIS_DATA 0x0100 +#define GPI_DOCKING_STATUS 0x0100 +#define GPI_HEADPHONE_SENSE 0x0200 +#define GPO_EXT_AMP_SHUTDOWN 0x1000 + +// M3 +#define GPO_M3_EXT_AMP_SHUTDN 0x0002 + +#define ASSP_INDEX_PORT 0x80 +#define ASSP_MEMORY_PORT 0x82 +#define ASSP_DATA_PORT 0x84 + +#define MPU401_DATA_PORT 0x98 +#define MPU401_STATUS_PORT 0x99 + +#define CLK_MULT_DATA_PORT 0x9C + +#define ASSP_CONTROL_A 0xA2 +#define ASSP_0_WS_ENABLE 0x01 +#define ASSP_CTRL_A_RESERVED1 0x02 +#define ASSP_CTRL_A_RESERVED2 0x04 +#define ASSP_CLK_49MHZ_SELECT 0x08 +#define FAST_PLU_ENABLE 0x10 +#define ASSP_CTRL_A_RESERVED3 0x20 +#define DSP_CLK_36MHZ_SELECT 0x40 + +#define ASSP_CONTROL_B 0xA4 +#define RESET_ASSP 0x00 +#define RUN_ASSP 0x01 +#define ENABLE_ASSP_CLOCK 0x00 +#define STOP_ASSP_CLOCK 0x10 +#define RESET_TOGGLE 0x40 + +#define ASSP_CONTROL_C 0xA6 +#define ASSP_HOST_INT_ENABLE 0x01 +#define FM_ADDR_REMAP_DISABLE 0x02 +#define HOST_WRITE_PORT_ENABLE 0x08 + +#define ASSP_HOST_INT_STATUS 0xAC +#define DSP2HOST_REQ_PIORECORD 0x01 +#define DSP2HOST_REQ_I2SRATE 0x02 +#define DSP2HOST_REQ_TIMER 0x04 + +// AC97 registers +// XXX fix this crap up +/*#define AC97_RESET 0x00*/ + +#define AC97_VOL_MUTE_B 0x8000 +#define AC97_VOL_M 0x1F +#define AC97_LEFT_VOL_S 8 + +#define AC97_MASTER_VOL 0x02 +#define AC97_LINE_LEVEL_VOL 0x04 +#define AC97_MASTER_MONO_VOL 0x06 +#define AC97_PC_BEEP_VOL 0x0A +#define AC97_PC_BEEP_VOL_M 0x0F +#define AC97_SROUND_MASTER_VOL 0x38 +#define AC97_PC_BEEP_VOL_S 1 + +/*#define AC97_PHONE_VOL 0x0C +#define AC97_MIC_VOL 0x0E*/ +#define AC97_MIC_20DB_ENABLE 0x40 + +/*#define AC97_LINEIN_VOL 0x10 +#define AC97_CD_VOL 0x12 +#define AC97_VIDEO_VOL 0x14 +#define AC97_AUX_VOL 0x16*/ +#define AC97_PCM_OUT_VOL 0x18 +/*#define AC97_RECORD_SELECT 0x1A*/ +#define AC97_RECORD_MIC 0x00 +#define AC97_RECORD_CD 0x01 +#define AC97_RECORD_VIDEO 0x02 +#define AC97_RECORD_AUX 0x03 +#define AC97_RECORD_MONO_MUX 0x02 +#define AC97_RECORD_DIGITAL 0x03 +#define AC97_RECORD_LINE 0x04 +#define AC97_RECORD_STEREO 0x05 +#define AC97_RECORD_MONO 0x06 +#define AC97_RECORD_PHONE 0x07 + +/*#define AC97_RECORD_GAIN 0x1C*/ +#define AC97_RECORD_VOL_M 0x0F + +/*#define AC97_GENERAL_PURPOSE 0x20*/ +#define AC97_POWER_DOWN_CTRL 0x26 +#define AC97_ADC_READY 0x0001 +#define AC97_DAC_READY 0x0002 +#define AC97_ANALOG_READY 0x0004 +#define AC97_VREF_ON 0x0008 +#define AC97_PR0 0x0100 +#define AC97_PR1 0x0200 +#define AC97_PR2 0x0400 +#define AC97_PR3 0x0800 +#define AC97_PR4 0x1000 + +#define AC97_RESERVED1 0x28 + +#define AC97_VENDOR_TEST 0x5A + +#define AC97_CLOCK_DELAY 0x5C +#define AC97_LINEOUT_MUX_SEL 0x0001 +#define AC97_MONO_MUX_SEL 0x0002 +#define AC97_CLOCK_DELAY_SEL 0x1F +#define AC97_DAC_CDS_SHIFT 6 +#define AC97_ADC_CDS_SHIFT 11 + +#define AC97_MULTI_CHANNEL_SEL 0x74 + +/*#define AC97_VENDOR_ID1 0x7C +#define AC97_VENDOR_ID2 0x7E*/ + +/* + * ASSP control regs + */ +#define DSP_PORT_TIMER_COUNT 0x06 + +#define DSP_PORT_MEMORY_INDEX 0x80 + +#define DSP_PORT_MEMORY_TYPE 0x82 +#define MEMTYPE_INTERNAL_CODE 0x0002 +#define MEMTYPE_INTERNAL_DATA 0x0003 +#define MEMTYPE_MASK 0x0003 + +#define DSP_PORT_MEMORY_DATA 0x84 + +#define DSP_PORT_CONTROL_REG_A 0xA2 +#define DSP_PORT_CONTROL_REG_B 0xA4 +#define DSP_PORT_CONTROL_REG_C 0xA6 + +#define REV_A_CODE_MEMORY_BEGIN 0x0000 +#define REV_A_CODE_MEMORY_END 0x0FFF +#define REV_A_CODE_MEMORY_UNIT_LENGTH 0x0040 +#define REV_A_CODE_MEMORY_LENGTH (REV_A_CODE_MEMORY_END - REV_A_CODE_MEMORY_BEGIN + 1) + +#define REV_B_CODE_MEMORY_BEGIN 0x0000 +#define REV_B_CODE_MEMORY_END 0x0BFF +#define REV_B_CODE_MEMORY_UNIT_LENGTH 0x0040 +#define REV_B_CODE_MEMORY_LENGTH (REV_B_CODE_MEMORY_END - REV_B_CODE_MEMORY_BEGIN + 1) + +#define REV_A_DATA_MEMORY_BEGIN 0x1000 +#define REV_A_DATA_MEMORY_END 0x2FFF +#define REV_A_DATA_MEMORY_UNIT_LENGTH 0x0080 +#define REV_A_DATA_MEMORY_LENGTH (REV_A_DATA_MEMORY_END - REV_A_DATA_MEMORY_BEGIN + 1) + +#define REV_B_DATA_MEMORY_BEGIN 0x1000 +#define REV_B_DATA_MEMORY_END 0x2BFF +#define REV_B_DATA_MEMORY_UNIT_LENGTH 0x0080 +#define REV_B_DATA_MEMORY_LENGTH (REV_B_DATA_MEMORY_END - REV_B_DATA_MEMORY_BEGIN + 1) + + +#define NUM_UNITS_KERNEL_CODE 16 +#define NUM_UNITS_KERNEL_DATA 2 + +#define NUM_UNITS_KERNEL_CODE_WITH_HSP 16 +#define NUM_UNITS_KERNEL_DATA_WITH_HSP 5 + +/* + * Kernel data layout + */ + +#define DP_SHIFT_COUNT 7 + +#define KDATA_BASE_ADDR 0x1000 +#define KDATA_BASE_ADDR2 0x1080 + +#define KDATA_TASK0 (KDATA_BASE_ADDR + 0x0000) +#define KDATA_TASK1 (KDATA_BASE_ADDR + 0x0001) +#define KDATA_TASK2 (KDATA_BASE_ADDR + 0x0002) +#define KDATA_TASK3 (KDATA_BASE_ADDR + 0x0003) +#define KDATA_TASK4 (KDATA_BASE_ADDR + 0x0004) +#define KDATA_TASK5 (KDATA_BASE_ADDR + 0x0005) +#define KDATA_TASK6 (KDATA_BASE_ADDR + 0x0006) +#define KDATA_TASK7 (KDATA_BASE_ADDR + 0x0007) +#define KDATA_TASK_ENDMARK (KDATA_BASE_ADDR + 0x0008) + +#define KDATA_CURRENT_TASK (KDATA_BASE_ADDR + 0x0009) +#define KDATA_TASK_SWITCH (KDATA_BASE_ADDR + 0x000A) + +#define KDATA_INSTANCE0_POS3D (KDATA_BASE_ADDR + 0x000B) +#define KDATA_INSTANCE1_POS3D (KDATA_BASE_ADDR + 0x000C) +#define KDATA_INSTANCE2_POS3D (KDATA_BASE_ADDR + 0x000D) +#define KDATA_INSTANCE3_POS3D (KDATA_BASE_ADDR + 0x000E) +#define KDATA_INSTANCE4_POS3D (KDATA_BASE_ADDR + 0x000F) +#define KDATA_INSTANCE5_POS3D (KDATA_BASE_ADDR + 0x0010) +#define KDATA_INSTANCE6_POS3D (KDATA_BASE_ADDR + 0x0011) +#define KDATA_INSTANCE7_POS3D (KDATA_BASE_ADDR + 0x0012) +#define KDATA_INSTANCE8_POS3D (KDATA_BASE_ADDR + 0x0013) +#define KDATA_INSTANCE_POS3D_ENDMARK (KDATA_BASE_ADDR + 0x0014) + +#define KDATA_INSTANCE0_SPKVIRT (KDATA_BASE_ADDR + 0x0015) +#define KDATA_INSTANCE_SPKVIRT_ENDMARK (KDATA_BASE_ADDR + 0x0016) + +#define KDATA_INSTANCE0_SPDIF (KDATA_BASE_ADDR + 0x0017) +#define KDATA_INSTANCE_SPDIF_ENDMARK (KDATA_BASE_ADDR + 0x0018) + +#define KDATA_INSTANCE0_MODEM (KDATA_BASE_ADDR + 0x0019) +#define KDATA_INSTANCE_MODEM_ENDMARK (KDATA_BASE_ADDR + 0x001A) + +#define KDATA_INSTANCE0_SRC (KDATA_BASE_ADDR + 0x001B) +#define KDATA_INSTANCE1_SRC (KDATA_BASE_ADDR + 0x001C) +#define KDATA_INSTANCE_SRC_ENDMARK (KDATA_BASE_ADDR + 0x001D) + +#define KDATA_INSTANCE0_MINISRC (KDATA_BASE_ADDR + 0x001E) +#define KDATA_INSTANCE1_MINISRC (KDATA_BASE_ADDR + 0x001F) +#define KDATA_INSTANCE2_MINISRC (KDATA_BASE_ADDR + 0x0020) +#define KDATA_INSTANCE3_MINISRC (KDATA_BASE_ADDR + 0x0021) +#define KDATA_INSTANCE_MINISRC_ENDMARK (KDATA_BASE_ADDR + 0x0022) + +#define KDATA_INSTANCE0_CPYTHRU (KDATA_BASE_ADDR + 0x0023) +#define KDATA_INSTANCE1_CPYTHRU (KDATA_BASE_ADDR + 0x0024) +#define KDATA_INSTANCE_CPYTHRU_ENDMARK (KDATA_BASE_ADDR + 0x0025) + +#define KDATA_CURRENT_DMA (KDATA_BASE_ADDR + 0x0026) +#define KDATA_DMA_SWITCH (KDATA_BASE_ADDR + 0x0027) +#define KDATA_DMA_ACTIVE (KDATA_BASE_ADDR + 0x0028) + +#define KDATA_DMA_XFER0 (KDATA_BASE_ADDR + 0x0029) +#define KDATA_DMA_XFER1 (KDATA_BASE_ADDR + 0x002A) +#define KDATA_DMA_XFER2 (KDATA_BASE_ADDR + 0x002B) +#define KDATA_DMA_XFER3 (KDATA_BASE_ADDR + 0x002C) +#define KDATA_DMA_XFER4 (KDATA_BASE_ADDR + 0x002D) +#define KDATA_DMA_XFER5 (KDATA_BASE_ADDR + 0x002E) +#define KDATA_DMA_XFER6 (KDATA_BASE_ADDR + 0x002F) +#define KDATA_DMA_XFER7 (KDATA_BASE_ADDR + 0x0030) +#define KDATA_DMA_XFER8 (KDATA_BASE_ADDR + 0x0031) +#define KDATA_DMA_XFER_ENDMARK (KDATA_BASE_ADDR + 0x0032) + +#define KDATA_I2S_SAMPLE_COUNT (KDATA_BASE_ADDR + 0x0033) +#define KDATA_I2S_INT_METER (KDATA_BASE_ADDR + 0x0034) +#define KDATA_I2S_ACTIVE (KDATA_BASE_ADDR + 0x0035) + +#define KDATA_TIMER_COUNT_RELOAD (KDATA_BASE_ADDR + 0x0036) +#define KDATA_TIMER_COUNT_CURRENT (KDATA_BASE_ADDR + 0x0037) + +#define KDATA_HALT_SYNCH_CLIENT (KDATA_BASE_ADDR + 0x0038) +#define KDATA_HALT_SYNCH_DMA (KDATA_BASE_ADDR + 0x0039) +#define KDATA_HALT_ACKNOWLEDGE (KDATA_BASE_ADDR + 0x003A) + +#define KDATA_ADC1_XFER0 (KDATA_BASE_ADDR + 0x003B) +#define KDATA_ADC1_XFER_ENDMARK (KDATA_BASE_ADDR + 0x003C) +#define KDATA_ADC1_LEFT_VOLUME (KDATA_BASE_ADDR + 0x003D) +#define KDATA_ADC1_RIGHT_VOLUME (KDATA_BASE_ADDR + 0x003E) +#define KDATA_ADC1_LEFT_SUR_VOL (KDATA_BASE_ADDR + 0x003F) +#define KDATA_ADC1_RIGHT_SUR_VOL (KDATA_BASE_ADDR + 0x0040) + +#define KDATA_ADC2_XFER0 (KDATA_BASE_ADDR + 0x0041) +#define KDATA_ADC2_XFER_ENDMARK (KDATA_BASE_ADDR + 0x0042) +#define KDATA_ADC2_LEFT_VOLUME (KDATA_BASE_ADDR + 0x0043) +#define KDATA_ADC2_RIGHT_VOLUME (KDATA_BASE_ADDR + 0x0044) +#define KDATA_ADC2_LEFT_SUR_VOL (KDATA_BASE_ADDR + 0x0045) +#define KDATA_ADC2_RIGHT_SUR_VOL (KDATA_BASE_ADDR + 0x0046) + +#define KDATA_CD_XFER0 (KDATA_BASE_ADDR + 0x0047) +#define KDATA_CD_XFER_ENDMARK (KDATA_BASE_ADDR + 0x0048) +#define KDATA_CD_LEFT_VOLUME (KDATA_BASE_ADDR + 0x0049) +#define KDATA_CD_RIGHT_VOLUME (KDATA_BASE_ADDR + 0x004A) +#define KDATA_CD_LEFT_SUR_VOL (KDATA_BASE_ADDR + 0x004B) +#define KDATA_CD_RIGHT_SUR_VOL (KDATA_BASE_ADDR + 0x004C) + +#define KDATA_MIC_XFER0 (KDATA_BASE_ADDR + 0x004D) +#define KDATA_MIC_XFER_ENDMARK (KDATA_BASE_ADDR + 0x004E) +#define KDATA_MIC_VOLUME (KDATA_BASE_ADDR + 0x004F) +#define KDATA_MIC_SUR_VOL (KDATA_BASE_ADDR + 0x0050) + +#define KDATA_I2S_XFER0 (KDATA_BASE_ADDR + 0x0051) +#define KDATA_I2S_XFER_ENDMARK (KDATA_BASE_ADDR + 0x0052) + +#define KDATA_CHI_XFER0 (KDATA_BASE_ADDR + 0x0053) +#define KDATA_CHI_XFER_ENDMARK (KDATA_BASE_ADDR + 0x0054) + +#define KDATA_SPDIF_XFER (KDATA_BASE_ADDR + 0x0055) +#define KDATA_SPDIF_CURRENT_FRAME (KDATA_BASE_ADDR + 0x0056) +#define KDATA_SPDIF_FRAME0 (KDATA_BASE_ADDR + 0x0057) +#define KDATA_SPDIF_FRAME1 (KDATA_BASE_ADDR + 0x0058) +#define KDATA_SPDIF_FRAME2 (KDATA_BASE_ADDR + 0x0059) + +#define KDATA_SPDIF_REQUEST (KDATA_BASE_ADDR + 0x005A) +#define KDATA_SPDIF_TEMP (KDATA_BASE_ADDR + 0x005B) + +#define KDATA_SPDIFIN_XFER0 (KDATA_BASE_ADDR + 0x005C) +#define KDATA_SPDIFIN_XFER_ENDMARK (KDATA_BASE_ADDR + 0x005D) +#define KDATA_SPDIFIN_INT_METER (KDATA_BASE_ADDR + 0x005E) + +#define KDATA_DSP_RESET_COUNT (KDATA_BASE_ADDR + 0x005F) +#define KDATA_DEBUG_OUTPUT (KDATA_BASE_ADDR + 0x0060) + +#define KDATA_KERNEL_ISR_LIST (KDATA_BASE_ADDR + 0x0061) + +#define KDATA_KERNEL_ISR_CBSR1 (KDATA_BASE_ADDR + 0x0062) +#define KDATA_KERNEL_ISR_CBER1 (KDATA_BASE_ADDR + 0x0063) +#define KDATA_KERNEL_ISR_CBCR (KDATA_BASE_ADDR + 0x0064) +#define KDATA_KERNEL_ISR_AR0 (KDATA_BASE_ADDR + 0x0065) +#define KDATA_KERNEL_ISR_AR1 (KDATA_BASE_ADDR + 0x0066) +#define KDATA_KERNEL_ISR_AR2 (KDATA_BASE_ADDR + 0x0067) +#define KDATA_KERNEL_ISR_AR3 (KDATA_BASE_ADDR + 0x0068) +#define KDATA_KERNEL_ISR_AR4 (KDATA_BASE_ADDR + 0x0069) +#define KDATA_KERNEL_ISR_AR5 (KDATA_BASE_ADDR + 0x006A) +#define KDATA_KERNEL_ISR_BRCR (KDATA_BASE_ADDR + 0x006B) +#define KDATA_KERNEL_ISR_PASR (KDATA_BASE_ADDR + 0x006C) +#define KDATA_KERNEL_ISR_PAER (KDATA_BASE_ADDR + 0x006D) + +#define KDATA_CLIENT_SCRATCH0 (KDATA_BASE_ADDR + 0x006E) +#define KDATA_CLIENT_SCRATCH1 (KDATA_BASE_ADDR + 0x006F) +#define KDATA_KERNEL_SCRATCH (KDATA_BASE_ADDR + 0x0070) +#define KDATA_KERNEL_ISR_SCRATCH (KDATA_BASE_ADDR + 0x0071) + +#define KDATA_OUEUE_LEFT (KDATA_BASE_ADDR + 0x0072) +#define KDATA_QUEUE_RIGHT (KDATA_BASE_ADDR + 0x0073) + +#define KDATA_ADC1_REQUEST (KDATA_BASE_ADDR + 0x0074) +#define KDATA_ADC2_REQUEST (KDATA_BASE_ADDR + 0x0075) +#define KDATA_CD_REQUEST (KDATA_BASE_ADDR + 0x0076) +#define KDATA_MIC_REQUEST (KDATA_BASE_ADDR + 0x0077) + +#define KDATA_ADC1_MIXER_REQUEST (KDATA_BASE_ADDR + 0x0078) +#define KDATA_ADC2_MIXER_REQUEST (KDATA_BASE_ADDR + 0x0079) +#define KDATA_CD_MIXER_REQUEST (KDATA_BASE_ADDR + 0x007A) +#define KDATA_MIC_MIXER_REQUEST (KDATA_BASE_ADDR + 0x007B) +#define KDATA_MIC_SYNC_COUNTER (KDATA_BASE_ADDR + 0x007C) + +/* + * second 'segment' (?) reserved for mixer + * buffers.. + */ + +#define KDATA_MIXER_WORD0 (KDATA_BASE_ADDR2 + 0x0000) +#define KDATA_MIXER_WORD1 (KDATA_BASE_ADDR2 + 0x0001) +#define KDATA_MIXER_WORD2 (KDATA_BASE_ADDR2 + 0x0002) +#define KDATA_MIXER_WORD3 (KDATA_BASE_ADDR2 + 0x0003) +#define KDATA_MIXER_WORD4 (KDATA_BASE_ADDR2 + 0x0004) +#define KDATA_MIXER_WORD5 (KDATA_BASE_ADDR2 + 0x0005) +#define KDATA_MIXER_WORD6 (KDATA_BASE_ADDR2 + 0x0006) +#define KDATA_MIXER_WORD7 (KDATA_BASE_ADDR2 + 0x0007) +#define KDATA_MIXER_WORD8 (KDATA_BASE_ADDR2 + 0x0008) +#define KDATA_MIXER_WORD9 (KDATA_BASE_ADDR2 + 0x0009) +#define KDATA_MIXER_WORDA (KDATA_BASE_ADDR2 + 0x000A) +#define KDATA_MIXER_WORDB (KDATA_BASE_ADDR2 + 0x000B) +#define KDATA_MIXER_WORDC (KDATA_BASE_ADDR2 + 0x000C) +#define KDATA_MIXER_WORDD (KDATA_BASE_ADDR2 + 0x000D) +#define KDATA_MIXER_WORDE (KDATA_BASE_ADDR2 + 0x000E) +#define KDATA_MIXER_WORDF (KDATA_BASE_ADDR2 + 0x000F) + +#define KDATA_MIXER_XFER0 (KDATA_BASE_ADDR2 + 0x0010) +#define KDATA_MIXER_XFER1 (KDATA_BASE_ADDR2 + 0x0011) +#define KDATA_MIXER_XFER2 (KDATA_BASE_ADDR2 + 0x0012) +#define KDATA_MIXER_XFER3 (KDATA_BASE_ADDR2 + 0x0013) +#define KDATA_MIXER_XFER4 (KDATA_BASE_ADDR2 + 0x0014) +#define KDATA_MIXER_XFER5 (KDATA_BASE_ADDR2 + 0x0015) +#define KDATA_MIXER_XFER6 (KDATA_BASE_ADDR2 + 0x0016) +#define KDATA_MIXER_XFER7 (KDATA_BASE_ADDR2 + 0x0017) +#define KDATA_MIXER_XFER8 (KDATA_BASE_ADDR2 + 0x0018) +#define KDATA_MIXER_XFER9 (KDATA_BASE_ADDR2 + 0x0019) +#define KDATA_MIXER_XFER_ENDMARK (KDATA_BASE_ADDR2 + 0x001A) + +#define KDATA_MIXER_TASK_NUMBER (KDATA_BASE_ADDR2 + 0x001B) +#define KDATA_CURRENT_MIXER (KDATA_BASE_ADDR2 + 0x001C) +#define KDATA_MIXER_ACTIVE (KDATA_BASE_ADDR2 + 0x001D) +#define KDATA_MIXER_BANK_STATUS (KDATA_BASE_ADDR2 + 0x001E) +#define KDATA_DAC_LEFT_VOLUME (KDATA_BASE_ADDR2 + 0x001F) +#define KDATA_DAC_RIGHT_VOLUME (KDATA_BASE_ADDR2 + 0x0020) + +#define MAX_INSTANCE_MINISRC (KDATA_INSTANCE_MINISRC_ENDMARK - KDATA_INSTANCE0_MINISRC) +#define MAX_VIRTUAL_DMA_CHANNELS (KDATA_DMA_XFER_ENDMARK - KDATA_DMA_XFER0) +#define MAX_VIRTUAL_MIXER_CHANNELS (KDATA_MIXER_XFER_ENDMARK - KDATA_MIXER_XFER0) +#define MAX_VIRTUAL_ADC1_CHANNELS (KDATA_ADC1_XFER_ENDMARK - KDATA_ADC1_XFER0) + +/* + * client data area offsets + */ +#define CDATA_INSTANCE_READY 0x00 + +#define CDATA_HOST_SRC_ADDRL 0x01 +#define CDATA_HOST_SRC_ADDRH 0x02 +#define CDATA_HOST_SRC_END_PLUS_1L 0x03 +#define CDATA_HOST_SRC_END_PLUS_1H 0x04 +#define CDATA_HOST_SRC_CURRENTL 0x05 +#define CDATA_HOST_SRC_CURRENTH 0x06 + +#define CDATA_IN_BUF_CONNECT 0x07 +#define CDATA_OUT_BUF_CONNECT 0x08 + +#define CDATA_IN_BUF_BEGIN 0x09 +#define CDATA_IN_BUF_END_PLUS_1 0x0A +#define CDATA_IN_BUF_HEAD 0x0B +#define CDATA_IN_BUF_TAIL 0x0C +#define CDATA_OUT_BUF_BEGIN 0x0D +#define CDATA_OUT_BUF_END_PLUS_1 0x0E +#define CDATA_OUT_BUF_HEAD 0x0F +#define CDATA_OUT_BUF_TAIL 0x10 + +#define CDATA_DMA_CONTROL 0x11 +#define CDATA_RESERVED 0x12 + +#define CDATA_FREQUENCY 0x13 +#define CDATA_LEFT_VOLUME 0x14 +#define CDATA_RIGHT_VOLUME 0x15 +#define CDATA_LEFT_SUR_VOL 0x16 +#define CDATA_RIGHT_SUR_VOL 0x17 + +#define CDATA_HEADER_LEN 0x18 + +#define SRC3_DIRECTION_OFFSET CDATA_HEADER_LEN +#define SRC3_MODE_OFFSET (CDATA_HEADER_LEN + 1) +#define SRC3_WORD_LENGTH_OFFSET (CDATA_HEADER_LEN + 2) +#define SRC3_PARAMETER_OFFSET (CDATA_HEADER_LEN + 3) +#define SRC3_COEFF_ADDR_OFFSET (CDATA_HEADER_LEN + 8) +#define SRC3_FILTAP_ADDR_OFFSET (CDATA_HEADER_LEN + 10) +#define SRC3_TEMP_INBUF_ADDR_OFFSET (CDATA_HEADER_LEN + 16) +#define SRC3_TEMP_OUTBUF_ADDR_OFFSET (CDATA_HEADER_LEN + 17) + +#define MINISRC_IN_BUFFER_SIZE ( 0x50 * 2 ) +#define MINISRC_OUT_BUFFER_SIZE ( 0x50 * 2 * 2) +#define MINISRC_OUT_BUFFER_SIZE ( 0x50 * 2 * 2) +#define MINISRC_TMP_BUFFER_SIZE ( 112 + ( MINISRC_BIQUAD_STAGE * 3 + 4 ) * 2 * 2 ) +#define MINISRC_BIQUAD_STAGE 2 +#define MINISRC_COEF_LOC 0X175 + +#define DMACONTROL_BLOCK_MASK 0x000F +#define DMAC_BLOCK0_SELECTOR 0x0000 +#define DMAC_BLOCK1_SELECTOR 0x0001 +#define DMAC_BLOCK2_SELECTOR 0x0002 +#define DMAC_BLOCK3_SELECTOR 0x0003 +#define DMAC_BLOCK4_SELECTOR 0x0004 +#define DMAC_BLOCK5_SELECTOR 0x0005 +#define DMAC_BLOCK6_SELECTOR 0x0006 +#define DMAC_BLOCK7_SELECTOR 0x0007 +#define DMAC_BLOCK8_SELECTOR 0x0008 +#define DMAC_BLOCK9_SELECTOR 0x0009 +#define DMAC_BLOCKA_SELECTOR 0x000A +#define DMAC_BLOCKB_SELECTOR 0x000B +#define DMAC_BLOCKC_SELECTOR 0x000C +#define DMAC_BLOCKD_SELECTOR 0x000D +#define DMAC_BLOCKE_SELECTOR 0x000E +#define DMAC_BLOCKF_SELECTOR 0x000F +#define DMACONTROL_PAGE_MASK 0x00F0 +#define DMAC_PAGE0_SELECTOR 0x0030 +#define DMAC_PAGE1_SELECTOR 0x0020 +#define DMAC_PAGE2_SELECTOR 0x0010 +#define DMAC_PAGE3_SELECTOR 0x0000 +#define DMACONTROL_AUTOREPEAT 0x1000 +#define DMACONTROL_STOPPED 0x2000 +#define DMACONTROL_DIRECTION 0x0100 + + +/* + * DSP Code images + */ + +static u16 assp_kernel_image[] = { + 0x7980, 0x0030, 0x7980, 0x03B4, 0x7980, 0x03B4, 0x7980, 0x00FB, 0x7980, 0x00DD, 0x7980, 0x03B4, + 0x7980, 0x0332, 0x7980, 0x0287, 0x7980, 0x03B4, 0x7980, 0x03B4, 0x7980, 0x03B4, 0x7980, 0x03B4, + 0x7980, 0x031A, 0x7980, 0x03B4, 0x7980, 0x022F, 0x7980, 0x03B4, 0x7980, 0x03B4, 0x7980, 0x03B4, + 0x7980, 0x03B4, 0x7980, 0x03B4, 0x7980, 0x0063, 0x7980, 0x006B, 0x7980, 0x03B4, 0x7980, 0x03B4, + 0xBF80, 0x2C7C, 0x8806, 0x8804, 0xBE40, 0xBC20, 0xAE09, 0x1000, 0xAE0A, 0x0001, 0x6938, 0xEB08, + 0x0053, 0x695A, 0xEB08, 0x00D6, 0x0009, 0x8B88, 0x6980, 0xE388, 0x0036, 0xBE30, 0xBC20, 0x6909, + 0xB801, 0x9009, 0xBE41, 0xBE41, 0x6928, 0xEB88, 0x0078, 0xBE41, 0xBE40, 0x7980, 0x0038, 0xBE41, + 0xBE41, 0x903A, 0x6938, 0xE308, 0x0056, 0x903A, 0xBE41, 0xBE40, 0xEF00, 0x903A, 0x6939, 0xE308, + 0x005E, 0x903A, 0xEF00, 0x690B, 0x660C, 0xEF8C, 0x690A, 0x660C, 0x620B, 0x6609, 0xEF00, 0x6910, + 0x660F, 0xEF04, 0xE388, 0x0075, 0x690E, 0x660F, 0x6210, 0x660D, 0xEF00, 0x690E, 0x660D, 0xEF00, + 0xAE70, 0x0001, 0xBC20, 0xAE27, 0x0001, 0x6939, 0xEB08, 0x005D, 0x6926, 0xB801, 0x9026, 0x0026, + 0x8B88, 0x6980, 0xE388, 0x00CB, 0x9028, 0x0D28, 0x4211, 0xE100, 0x007A, 0x4711, 0xE100, 0x00A0, + 0x7A80, 0x0063, 0xB811, 0x660A, 0x6209, 0xE304, 0x007A, 0x0C0B, 0x4005, 0x100A, 0xBA01, 0x9012, + 0x0C12, 0x4002, 0x7980, 0x00AF, 0x7A80, 0x006B, 0xBE02, 0x620E, 0x660D, 0xBA10, 0xE344, 0x007A, + 0x0C10, 0x4005, 0x100E, 0xBA01, 0x9012, 0x0C12, 0x4002, 0x1003, 0xBA02, 0x9012, 0x0C12, 0x4000, + 0x1003, 0xE388, 0x00BA, 0x1004, 0x7980, 0x00BC, 0x1004, 0xBA01, 0x9012, 0x0C12, 0x4001, 0x0C05, + 0x4003, 0x0C06, 0x4004, 0x1011, 0xBFB0, 0x01FF, 0x9012, 0x0C12, 0x4006, 0xBC20, 0xEF00, 0xAE26, + 0x1028, 0x6970, 0xBFD0, 0x0001, 0x9070, 0xE388, 0x007A, 0xAE28, 0x0000, 0xEF00, 0xAE70, 0x0300, + 0x0C70, 0xB00C, 0xAE5A, 0x0000, 0xEF00, 0x7A80, 0x038A, 0x697F, 0xB801, 0x907F, 0x0056, 0x8B88, + 0x0CA0, 0xB008, 0xAF71, 0xB000, 0x4E71, 0xE200, 0x00F3, 0xAE56, 0x1057, 0x0056, 0x0CA0, 0xB008, + 0x8056, 0x7980, 0x03A1, 0x0810, 0xBFA0, 0x1059, 0xE304, 0x03A1, 0x8056, 0x7980, 0x03A1, 0x7A80, + 0x038A, 0xBF01, 0xBE43, 0xBE59, 0x907C, 0x6937, 0xE388, 0x010D, 0xBA01, 0xE308, 0x010C, 0xAE71, + 0x0004, 0x0C71, 0x5000, 0x6936, 0x9037, 0xBF0A, 0x109E, 0x8B8A, 0xAF80, 0x8014, 0x4C80, 0xBF0A, + 0x0560, 0xF500, 0xBF0A, 0x0520, 0xB900, 0xBB17, 0x90A0, 0x6917, 0xE388, 0x0148, 0x0D17, 0xE100, + 0x0127, 0xBF0C, 0x0578, 0xBF0D, 0x057C, 0x7980, 0x012B, 0xBF0C, 0x0538, 0xBF0D, 0x053C, 0x6900, + 0xE308, 0x0135, 0x8B8C, 0xBE59, 0xBB07, 0x90A0, 0xBC20, 0x7980, 0x0157, 0x030C, 0x8B8B, 0xB903, + 0x8809, 0xBEC6, 0x013E, 0x69AC, 0x90AB, 0x69AD, 0x90AB, 0x0813, 0x660A, 0xE344, 0x0144, 0x0309, + 0x830C, 0xBC20, 0x7980, 0x0157, 0x6955, 0xE388, 0x0157, 0x7C38, 0xBF0B, 0x0578, 0xF500, 0xBF0B, + 0x0538, 0xB907, 0x8809, 0xBEC6, 0x0156, 0x10AB, 0x90AA, 0x6974, 0xE388, 0x0163, 0xAE72, 0x0540, + 0xF500, 0xAE72, 0x0500, 0xAE61, 0x103B, 0x7A80, 0x02F6, 0x6978, 0xE388, 0x0182, 0x8B8C, 0xBF0C, + 0x0560, 0xE500, 0x7C40, 0x0814, 0xBA20, 0x8812, 0x733D, 0x7A80, 0x0380, 0x733E, 0x7A80, 0x0380, + 0x8B8C, 0xBF0C, 0x056C, 0xE500, 0x7C40, 0x0814, 0xBA2C, 0x8812, 0x733F, 0x7A80, 0x0380, 0x7340, + 0x7A80, 0x0380, 0x6975, 0xE388, 0x018E, 0xAE72, 0x0548, 0xF500, 0xAE72, 0x0508, 0xAE61, 0x1041, + 0x7A80, 0x02F6, 0x6979, 0xE388, 0x01AD, 0x8B8C, 0xBF0C, 0x0560, 0xE500, 0x7C40, 0x0814, 0xBA18, + 0x8812, 0x7343, 0x7A80, 0x0380, 0x7344, 0x7A80, 0x0380, 0x8B8C, 0xBF0C, 0x056C, 0xE500, 0x7C40, + 0x0814, 0xBA24, 0x8812, 0x7345, 0x7A80, 0x0380, 0x7346, 0x7A80, 0x0380, 0x6976, 0xE388, 0x01B9, + 0xAE72, 0x0558, 0xF500, 0xAE72, 0x0518, 0xAE61, 0x1047, 0x7A80, 0x02F6, 0x697A, 0xE388, 0x01D8, + 0x8B8C, 0xBF0C, 0x0560, 0xE500, 0x7C40, 0x0814, 0xBA08, 0x8812, 0x7349, 0x7A80, 0x0380, 0x734A, + 0x7A80, 0x0380, 0x8B8C, 0xBF0C, 0x056C, 0xE500, 0x7C40, 0x0814, 0xBA14, 0x8812, 0x734B, 0x7A80, + 0x0380, 0x734C, 0x7A80, 0x0380, 0xBC21, 0xAE1C, 0x1090, 0x8B8A, 0xBF0A, 0x0560, 0xE500, 0x7C40, + 0x0812, 0xB804, 0x8813, 0x8B8D, 0xBF0D, 0x056C, 0xE500, 0x7C40, 0x0815, 0xB804, 0x8811, 0x7A80, + 0x034A, 0x8B8A, 0xBF0A, 0x0560, 0xE500, 0x7C40, 0x731F, 0xB903, 0x8809, 0xBEC6, 0x01F9, 0x548A, + 0xBE03, 0x98A0, 0x7320, 0xB903, 0x8809, 0xBEC6, 0x0201, 0x548A, 0xBE03, 0x98A0, 0x1F20, 0x2F1F, + 0x9826, 0xBC20, 0x6935, 0xE388, 0x03A1, 0x6933, 0xB801, 0x9033, 0xBFA0, 0x02EE, 0xE308, 0x03A1, + 0x9033, 0xBF00, 0x6951, 0xE388, 0x021F, 0x7334, 0xBE80, 0x5760, 0xBE03, 0x9F7E, 0xBE59, 0x9034, + 0x697E, 0x0D51, 0x9013, 0xBC20, 0x695C, 0xE388, 0x03A1, 0x735E, 0xBE80, 0x5760, 0xBE03, 0x9F7E, + 0xBE59, 0x905E, 0x697E, 0x0D5C, 0x9013, 0x7980, 0x03A1, 0x7A80, 0x038A, 0xBF01, 0xBE43, 0x6977, + 0xE388, 0x024E, 0xAE61, 0x104D, 0x0061, 0x8B88, 0x6980, 0xE388, 0x024E, 0x9071, 0x0D71, 0x000B, + 0xAFA0, 0x8010, 0xAFA0, 0x8010, 0x0810, 0x660A, 0xE308, 0x0249, 0x0009, 0x0810, 0x660C, 0xE388, + 0x024E, 0x800B, 0xBC20, 0x697B, 0xE388, 0x03A1, 0xBF0A, 0x109E, 0x8B8A, 0xAF80, 0x8014, 0x4C80, + 0xE100, 0x0266, 0x697C, 0xBF90, 0x0560, 0x9072, 0x0372, 0x697C, 0xBF90, 0x0564, 0x9073, 0x0473, + 0x7980, 0x0270, 0x697C, 0xBF90, 0x0520, 0x9072, 0x0372, 0x697C, 0xBF90, 0x0524, 0x9073, 0x0473, + 0x697C, 0xB801, 0x907C, 0xBF0A, 0x10FD, 0x8B8A, 0xAF80, 0x8010, 0x734F, 0x548A, 0xBE03, 0x9880, + 0xBC21, 0x7326, 0x548B, 0xBE03, 0x618B, 0x988C, 0xBE03, 0x6180, 0x9880, 0x7980, 0x03A1, 0x7A80, + 0x038A, 0x0D28, 0x4711, 0xE100, 0x02BE, 0xAF12, 0x4006, 0x6912, 0xBFB0, 0x0C00, 0xE388, 0x02B6, + 0xBFA0, 0x0800, 0xE388, 0x02B2, 0x6912, 0xBFB0, 0x0C00, 0xBFA0, 0x0400, 0xE388, 0x02A3, 0x6909, + 0x900B, 0x7980, 0x02A5, 0xAF0B, 0x4005, 0x6901, 0x9005, 0x6902, 0x9006, 0x4311, 0xE100, 0x02ED, + 0x6911, 0xBFC0, 0x2000, 0x9011, 0x7980, 0x02ED, 0x6909, 0x900B, 0x7980, 0x02B8, 0xAF0B, 0x4005, + 0xAF05, 0x4003, 0xAF06, 0x4004, 0x7980, 0x02ED, 0xAF12, 0x4006, 0x6912, 0xBFB0, 0x0C00, 0xE388, + 0x02E7, 0xBFA0, 0x0800, 0xE388, 0x02E3, 0x6912, 0xBFB0, 0x0C00, 0xBFA0, 0x0400, 0xE388, 0x02D4, + 0x690D, 0x9010, 0x7980, 0x02D6, 0xAF10, 0x4005, 0x6901, 0x9005, 0x6902, 0x9006, 0x4311, 0xE100, + 0x02ED, 0x6911, 0xBFC0, 0x2000, 0x9011, 0x7980, 0x02ED, 0x690D, 0x9010, 0x7980, 0x02E9, 0xAF10, + 0x4005, 0xAF05, 0x4003, 0xAF06, 0x4004, 0xBC20, 0x6970, 0x9071, 0x7A80, 0x0078, 0x6971, 0x9070, + 0x7980, 0x03A1, 0xBC20, 0x0361, 0x8B8B, 0x6980, 0xEF88, 0x0272, 0x0372, 0x7804, 0x9071, 0x0D71, + 0x8B8A, 0x000B, 0xB903, 0x8809, 0xBEC6, 0x0309, 0x69A8, 0x90AB, 0x69A8, 0x90AA, 0x0810, 0x660A, + 0xE344, 0x030F, 0x0009, 0x0810, 0x660C, 0xE388, 0x0314, 0x800B, 0xBC20, 0x6961, 0xB801, 0x9061, + 0x7980, 0x02F7, 0x7A80, 0x038A, 0x5D35, 0x0001, 0x6934, 0xB801, 0x9034, 0xBF0A, 0x109E, 0x8B8A, + 0xAF80, 0x8014, 0x4880, 0xAE72, 0x0550, 0xF500, 0xAE72, 0x0510, 0xAE61, 0x1051, 0x7A80, 0x02F6, + 0x7980, 0x03A1, 0x7A80, 0x038A, 0x5D35, 0x0002, 0x695E, 0xB801, 0x905E, 0xBF0A, 0x109E, 0x8B8A, + 0xAF80, 0x8014, 0x4780, 0xAE72, 0x0558, 0xF500, 0xAE72, 0x0518, 0xAE61, 0x105C, 0x7A80, 0x02F6, + 0x7980, 0x03A1, 0x001C, 0x8B88, 0x6980, 0xEF88, 0x901D, 0x0D1D, 0x100F, 0x6610, 0xE38C, 0x0358, + 0x690E, 0x6610, 0x620F, 0x660D, 0xBA0F, 0xE301, 0x037A, 0x0410, 0x8B8A, 0xB903, 0x8809, 0xBEC6, + 0x036C, 0x6A8C, 0x61AA, 0x98AB, 0x6A8C, 0x61AB, 0x98AD, 0x6A8C, 0x61AD, 0x98A9, 0x6A8C, 0x61A9, + 0x98AA, 0x7C04, 0x8B8B, 0x7C04, 0x8B8D, 0x7C04, 0x8B89, 0x7C04, 0x0814, 0x660E, 0xE308, 0x0379, + 0x040D, 0x8410, 0xBC21, 0x691C, 0xB801, 0x901C, 0x7980, 0x034A, 0xB903, 0x8809, 0x8B8A, 0xBEC6, + 0x0388, 0x54AC, 0xBE03, 0x618C, 0x98AA, 0xEF00, 0xBC20, 0xBE46, 0x0809, 0x906B, 0x080A, 0x906C, + 0x080B, 0x906D, 0x081A, 0x9062, 0x081B, 0x9063, 0x081E, 0x9064, 0xBE59, 0x881E, 0x8065, 0x8166, + 0x8267, 0x8368, 0x8469, 0x856A, 0xEF00, 0xBC20, 0x696B, 0x8809, 0x696C, 0x880A, 0x696D, 0x880B, + 0x6962, 0x881A, 0x6963, 0x881B, 0x6964, 0x881E, 0x0065, 0x0166, 0x0267, 0x0368, 0x0469, 0x056A, + 0xBE3A, +}; + +/* + * Mini sample rate converter code image + * that is to be loaded at 0x400 on the DSP. + */ +static u16 assp_minisrc_image[] = { + + 0xBF80, 0x101E, 0x906E, 0x006E, 0x8B88, 0x6980, 0xEF88, 0x906F, 0x0D6F, 0x6900, 0xEB08, 0x0412, + 0xBC20, 0x696E, 0xB801, 0x906E, 0x7980, 0x0403, 0xB90E, 0x8807, 0xBE43, 0xBF01, 0xBE47, 0xBE41, + 0x7A80, 0x002A, 0xBE40, 0x3029, 0xEFCC, 0xBE41, 0x7A80, 0x0028, 0xBE40, 0x3028, 0xEFCC, 0x6907, + 0xE308, 0x042A, 0x6909, 0x902C, 0x7980, 0x042C, 0x690D, 0x902C, 0x1009, 0x881A, 0x100A, 0xBA01, + 0x881B, 0x100D, 0x881C, 0x100E, 0xBA01, 0x881D, 0xBF80, 0x00ED, 0x881E, 0x050C, 0x0124, 0xB904, + 0x9027, 0x6918, 0xE308, 0x04B3, 0x902D, 0x6913, 0xBFA0, 0x7598, 0xF704, 0xAE2D, 0x00FF, 0x8B8D, + 0x6919, 0xE308, 0x0463, 0x691A, 0xE308, 0x0456, 0xB907, 0x8809, 0xBEC6, 0x0453, 0x10A9, 0x90AD, + 0x7980, 0x047C, 0xB903, 0x8809, 0xBEC6, 0x0460, 0x1889, 0x6C22, 0x90AD, 0x10A9, 0x6E23, 0x6C22, + 0x90AD, 0x7980, 0x047C, 0x101A, 0xE308, 0x046F, 0xB903, 0x8809, 0xBEC6, 0x046C, 0x10A9, 0x90A0, + 0x90AD, 0x7980, 0x047C, 0xB901, 0x8809, 0xBEC6, 0x047B, 0x1889, 0x6C22, 0x90A0, 0x90AD, 0x10A9, + 0x6E23, 0x6C22, 0x90A0, 0x90AD, 0x692D, 0xE308, 0x049C, 0x0124, 0xB703, 0xB902, 0x8818, 0x8B89, + 0x022C, 0x108A, 0x7C04, 0x90A0, 0x692B, 0x881F, 0x7E80, 0x055B, 0x692A, 0x8809, 0x8B89, 0x99A0, + 0x108A, 0x90A0, 0x692B, 0x881F, 0x7E80, 0x055B, 0x692A, 0x8809, 0x8B89, 0x99AF, 0x7B99, 0x0484, + 0x0124, 0x060F, 0x101B, 0x2013, 0x901B, 0xBFA0, 0x7FFF, 0xE344, 0x04AC, 0x901B, 0x8B89, 0x7A80, + 0x051A, 0x6927, 0xBA01, 0x9027, 0x7A80, 0x0523, 0x6927, 0xE308, 0x049E, 0x7980, 0x050F, 0x0624, + 0x1026, 0x2013, 0x9026, 0xBFA0, 0x7FFF, 0xE304, 0x04C0, 0x8B8D, 0x7A80, 0x051A, 0x7980, 0x04B4, + 0x9026, 0x1013, 0x3026, 0x901B, 0x8B8D, 0x7A80, 0x051A, 0x7A80, 0x0523, 0x1027, 0xBA01, 0x9027, + 0xE308, 0x04B4, 0x0124, 0x060F, 0x8B89, 0x691A, 0xE308, 0x04EA, 0x6919, 0xE388, 0x04E0, 0xB903, + 0x8809, 0xBEC6, 0x04DD, 0x1FA0, 0x2FAE, 0x98A9, 0x7980, 0x050F, 0xB901, 0x8818, 0xB907, 0x8809, + 0xBEC6, 0x04E7, 0x10EE, 0x90A9, 0x7980, 0x050F, 0x6919, 0xE308, 0x04FE, 0xB903, 0x8809, 0xBE46, + 0xBEC6, 0x04FA, 0x17A0, 0xBE1E, 0x1FAE, 0xBFBF, 0xFF00, 0xBE13, 0xBFDF, 0x8080, 0x99A9, 0xBE47, + 0x7980, 0x050F, 0xB901, 0x8809, 0xBEC6, 0x050E, 0x16A0, 0x26A0, 0xBFB7, 0xFF00, 0xBE1E, 0x1EA0, + 0x2EAE, 0xBFBF, 0xFF00, 0xBE13, 0xBFDF, 0x8080, 0x99A9, 0x850C, 0x860F, 0x6907, 0xE388, 0x0516, + 0x0D07, 0x8510, 0xBE59, 0x881E, 0xBE4A, 0xEF00, 0x101E, 0x901C, 0x101F, 0x901D, 0x10A0, 0x901E, + 0x10A0, 0x901F, 0xEF00, 0x101E, 0x301C, 0x9020, 0x731B, 0x5420, 0xBE03, 0x9825, 0x1025, 0x201C, + 0x9025, 0x7325, 0x5414, 0xBE03, 0x8B8E, 0x9880, 0x692F, 0xE388, 0x0539, 0xBE59, 0xBB07, 0x6180, + 0x9880, 0x8BA0, 0x101F, 0x301D, 0x9021, 0x731B, 0x5421, 0xBE03, 0x982E, 0x102E, 0x201D, 0x902E, + 0x732E, 0x5415, 0xBE03, 0x9880, 0x692F, 0xE388, 0x054F, 0xBE59, 0xBB07, 0x6180, 0x9880, 0x8BA0, + 0x6918, 0xEF08, 0x7325, 0x5416, 0xBE03, 0x98A0, 0x732E, 0x5417, 0xBE03, 0x98A0, 0xEF00, 0x8BA0, + 0xBEC6, 0x056B, 0xBE59, 0xBB04, 0xAA90, 0xBE04, 0xBE1E, 0x99E0, 0x8BE0, 0x69A0, 0x90D0, 0x69A0, + 0x90D0, 0x081F, 0xB805, 0x881F, 0x8B90, 0x69A0, 0x90D0, 0x69A0, 0x9090, 0x8BD0, 0x8BD8, 0xBE1F, + 0xEF00, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + diff --git a/sound/oss/maui.c b/sound/oss/maui.c new file mode 100644 index 000000000000..05cf194eda6b --- /dev/null +++ b/sound/oss/maui.c @@ -0,0 +1,478 @@ +/* + * sound/maui.c + * + * The low level driver for Turtle Beach Maui and Tropez. + * + * + * 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 General clean up, use kernel IRQ + * system + * Christoph Hellwig Adapted to module_init/module_exit + * Bartlomiej Zolnierkiewicz + * Added __init to download_code() + * + * Status: + * Andrew J. Kroll Tested 06/01/1999 with: + * * OSWF.MOT File Version: 1.15 + * * OSWF.MOT File Dated: 09/12/94 + * * Older versions will cause problems. + */ + +#include +#include +#include +#include + +#define USE_SEQ_MACROS +#define USE_SIMPLE_MACROS + +#include "sound_config.h" +#include "sound_firmware.h" + +#include "mpu401.h" + +static int maui_base = 0x330; + +static volatile int irq_ok; +static int *maui_osp; + +#define HOST_DATA_PORT (maui_base + 2) +#define HOST_STAT_PORT (maui_base + 3) +#define HOST_CTRL_PORT (maui_base + 3) + +#define STAT_TX_INTR 0x40 +#define STAT_TX_AVAIL 0x20 +#define STAT_TX_IENA 0x10 +#define STAT_RX_INTR 0x04 +#define STAT_RX_AVAIL 0x02 +#define STAT_RX_IENA 0x01 + +static int (*orig_load_patch)(int dev, int format, const char __user *addr, + int offs, int count, int pmgr_flag) = NULL; + +#include "maui_boot.h" + +static int maui_wait(int mask) +{ + int i; + + /* + * Perform a short initial wait without sleeping + */ + + for (i = 0; i < 100; i++) + if (inb(HOST_STAT_PORT) & mask) + return 1; + + /* + * Wait up to 15 seconds with sleeping + */ + + for (i = 0; i < 150; i++) { + if (inb(HOST_STAT_PORT) & mask) + return 1; + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ / 10); + if (signal_pending(current)) + return 0; + } + return 0; +} + +static int maui_read(void) +{ + if (maui_wait(STAT_RX_AVAIL)) + return inb(HOST_DATA_PORT); + return -1; +} + +static int maui_write(unsigned char data) +{ + if (maui_wait(STAT_TX_AVAIL)) { + outb((data), HOST_DATA_PORT); + return 1; + } + printk(KERN_WARNING "Maui: Write timeout\n"); + return 0; +} + +static irqreturn_t mauiintr(int irq, void *dev_id, struct pt_regs *dummy) +{ + irq_ok = 1; + return IRQ_HANDLED; +} + +static int __init download_code(void) +{ + int i, lines = 0; + int eol_seen = 0, done = 0; + int skip = 1; + + printk(KERN_INFO "Code download (%d bytes): ", maui_osLen); + + for (i = 0; i < maui_osLen; i++) { + if (maui_os[i] != '\r') { + if (!skip || (maui_os[i] == 'S' && (i == 0 || maui_os[i - 1] == '\n'))) { + skip = 0; + + if (maui_os[i] == '\n') + eol_seen = skip = 1; + else if (maui_os[i] == 'S') { + if (maui_os[i + 1] == '8') + done = 1; + if (!maui_write(0xF1)) + goto failure; + if (!maui_write('S')) + goto failure; + } else { + if (!maui_write(maui_os[i])) + goto failure; + } + + if (eol_seen) { + int c = 0; + int n; + + eol_seen = 0; + + for (n = 0; n < 2; n++) { + if (maui_wait(STAT_RX_AVAIL)) { + c = inb(HOST_DATA_PORT); + break; + } + } + if (c != 0x80) { + printk("Download not acknowledged\n"); + return 0; + } + else if (!(lines++ % 10)) + printk("."); + + if (done) { + printk("\n"); + printk(KERN_INFO "Download complete\n"); + return 1; + } + } + } + } + } + +failure: + printk("\n"); + printk(KERN_ERR "Download failed!!!\n"); + return 0; +} + +static int __init maui_init(int irq) +{ + unsigned char bits; + + switch (irq) { + case 9: + bits = 0x00; + break; + case 5: + bits = 0x08; + break; + case 12: + bits = 0x10; + break; + case 15: + bits = 0x18; + break; + + default: + printk(KERN_ERR "Maui: Invalid IRQ %d\n", irq); + return 0; + } + outb((0x00), HOST_CTRL_PORT); /* Reset */ + outb((bits), HOST_DATA_PORT); /* Set the IRQ bits */ + outb((bits | 0x80), HOST_DATA_PORT); /* Set the IRQ bits again? */ + outb((0x80), HOST_CTRL_PORT); /* Leave reset */ + outb((0x80), HOST_CTRL_PORT); /* Leave reset */ + outb((0xD0), HOST_CTRL_PORT); /* Cause interrupt */ + +#ifdef CONFIG_SMP + { + int i; + for (i = 0; i < 1000000 && !irq_ok; i++) + ; + if (!irq_ok) + return 0; + } +#endif + outb((0x80), HOST_CTRL_PORT); /* Leave reset */ + + printk(KERN_INFO "Turtle Beach Maui initialization\n"); + + if (!download_code()) + return 0; + + outb((0xE0), HOST_CTRL_PORT); /* Normal operation */ + + /* Select mpu401 mode */ + + maui_write(0xf0); + maui_write(1); + if (maui_read() != 0x80) { + maui_write(0xf0); + maui_write(1); + if (maui_read() != 0x80) + printk(KERN_ERR "Maui didn't acknowledge set HW mode command\n"); + } + printk(KERN_INFO "Maui initialized OK\n"); + return 1; +} + +static int maui_short_wait(int mask) { + int i; + + for (i = 0; i < 1000; i++) { + if (inb(HOST_STAT_PORT) & mask) { + return 1; + } + } + return 0; +} + +static int maui_load_patch(int dev, int format, const char __user *addr, + int offs, int count, int pmgr_flag) +{ + + struct sysex_info header; + unsigned long left, src_offs; + int hdr_size = (unsigned long) &header.data[0] - (unsigned long) &header; + int i; + + if (format == SYSEX_PATCH) /* Handled by midi_synth.c */ + return orig_load_patch(dev, format, addr, offs, count, pmgr_flag); + + if (format != MAUI_PATCH) + { + printk(KERN_WARNING "Maui: Unknown patch format\n"); + } + if (count < hdr_size) { +/* printk("Maui error: Patch header too short\n");*/ + return -EINVAL; + } + count -= hdr_size; + + /* + * Copy the header from user space but ignore the first bytes which have + * been transferred already. + */ + + if(copy_from_user(&((char *) &header)[offs], &(addr)[offs], hdr_size - offs)) + return -EFAULT; + + if (count < header.len) { + printk(KERN_ERR "Maui warning: Host command record too short (%d<%d)\n", count, (int) header.len); + header.len = count; + } + left = header.len; + src_offs = 0; + + for (i = 0; i < left; i++) { + unsigned char data; + + if(get_user(*(unsigned char *) &data, (unsigned char __user *) &((addr)[hdr_size + i]))) + return -EFAULT; + if (i == 0 && !(data & 0x80)) + return -EINVAL; + + if (maui_write(data) == -1) + return -EIO; + } + + if ((i = maui_read()) != 0x80) { + if (i != -1) + printk("Maui: Error status %02x\n", i); + return -EIO; + } + return 0; +} + +static int __init probe_maui(struct address_info *hw_config) +{ + struct resource *ports; + int this_dev; + int i; + int tmp1, tmp2, ret; + + ports = request_region(hw_config->io_base, 2, "mpu401"); + if (!ports) + return 0; + + if (!request_region(hw_config->io_base + 2, 6, "Maui")) + goto out; + + maui_base = hw_config->io_base; + maui_osp = hw_config->osp; + + if (request_irq(hw_config->irq, mauiintr, 0, "Maui", NULL) < 0) + goto out2; + + /* + * Initialize the processor if necessary + */ + + if (maui_osLen > 0) { + if (!(inb(HOST_STAT_PORT) & STAT_TX_AVAIL) || + !maui_write(0x9F) || /* Report firmware version */ + !maui_short_wait(STAT_RX_AVAIL) || + maui_read() == -1 || maui_read() == -1) + if (!maui_init(hw_config->irq)) + goto out3; + } + if (!maui_write(0xCF)) /* Report hardware version */ { + printk(KERN_ERR "No WaveFront firmware detected (card uninitialized?)\n"); + goto out3; + } + if ((tmp1 = maui_read()) == -1 || (tmp2 = maui_read()) == -1) { + printk(KERN_ERR "No WaveFront firmware detected (card uninitialized?)\n"); + goto out3; + } + if (tmp1 == 0xff || tmp2 == 0xff) + goto out3; + printk(KERN_DEBUG "WaveFront hardware version %d.%d\n", tmp1, tmp2); + + if (!maui_write(0x9F)) /* Report firmware version */ + goto out3; + if ((tmp1 = maui_read()) == -1 || (tmp2 = maui_read()) == -1) + goto out3; + + printk(KERN_DEBUG "WaveFront firmware version %d.%d\n", tmp1, tmp2); + + if (!maui_write(0x85)) /* Report free DRAM */ + goto out3; + tmp1 = 0; + for (i = 0; i < 4; i++) { + tmp1 |= maui_read() << (7 * i); + } + printk(KERN_DEBUG "Available DRAM %dk\n", tmp1 / 1024); + + for (i = 0; i < 1000; i++) + if (probe_mpu401(hw_config, ports)) + break; + + ret = probe_mpu401(hw_config, ports); + if (!ret) + goto out3; + + conf_printf("Maui", hw_config); + + hw_config->irq *= -1; + hw_config->name = "Maui"; + attach_mpu401(hw_config, THIS_MODULE); + + if (hw_config->slots[1] != -1) /* The MPU401 driver installed itself */ { + struct synth_operations *synth; + + this_dev = hw_config->slots[1]; + + /* + * Intercept patch loading calls so that they can be handled + * by the Maui driver. + */ + + synth = midi_devs[this_dev]->converter; + if (synth != NULL) { + synth->id = "MAUI"; + orig_load_patch = synth->load_patch; + synth->load_patch = &maui_load_patch; + } else + printk(KERN_ERR "Maui: Can't install patch loader\n"); + } + return 1; + +out3: + free_irq(hw_config->irq, NULL); +out2: + release_region(hw_config->io_base + 2, 6); +out: + release_region(hw_config->io_base, 2); + return 0; +} + +static void __exit unload_maui(struct address_info *hw_config) +{ + int irq = hw_config->irq; + release_region(hw_config->io_base + 2, 6); + unload_mpu401(hw_config); + + if (irq < 0) + irq = -irq; + if (irq > 0) + free_irq(irq, NULL); +} + +static int fw_load; + +static struct address_info cfg; + +static int __initdata io = -1; +static int __initdata irq = -1; + +module_param(io, int, 0); +module_param(irq, int, 0); + +/* + * Install a Maui card. Needs mpu401 loaded already. + */ + +static int __init init_maui(void) +{ + printk(KERN_INFO "Turtle beach Maui and Tropez driver, Copyright (C) by Hannu Savolainen 1993-1996\n"); + + cfg.io_base = io; + cfg.irq = irq; + + if (cfg.io_base == -1 || cfg.irq == -1) { + printk(KERN_INFO "maui: irq and io must be set.\n"); + return -EINVAL; + } + + if (maui_os == NULL) { + fw_load = 1; + maui_osLen = mod_firmware_load("/etc/sound/oswf.mot", (char **) &maui_os); + } + if (probe_maui(&cfg) == 0) + return -ENODEV; + + return 0; +} + +static void __exit cleanup_maui(void) +{ + if (fw_load && maui_os) + vfree(maui_os); + unload_maui(&cfg); +} + +module_init(init_maui); +module_exit(cleanup_maui); + +#ifndef MODULE +static int __init setup_maui(char *str) +{ + /* io, irq */ + int ints[3]; + + str = get_options(str, ARRAY_SIZE(ints), ints); + + io = ints[1]; + irq = ints[2]; + + return 1; +} + +__setup("maui=", setup_maui); +#endif +MODULE_LICENSE("GPL"); diff --git a/sound/oss/midi_ctrl.h b/sound/oss/midi_ctrl.h new file mode 100644 index 000000000000..3353e5a67c24 --- /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_syms.c b/sound/oss/midi_syms.c new file mode 100644 index 000000000000..5b146ddf5725 --- /dev/null +++ b/sound/oss/midi_syms.c @@ -0,0 +1,29 @@ +/* + * Exported symbols for midi driver. + */ + +#include + +char midi_syms_symbol; + +#include "sound_config.h" +#define _MIDI_SYNTH_C_ +#include "midi_synth.h" + +EXPORT_SYMBOL(do_midi_msg); +EXPORT_SYMBOL(midi_synth_open); +EXPORT_SYMBOL(midi_synth_close); +EXPORT_SYMBOL(midi_synth_ioctl); +EXPORT_SYMBOL(midi_synth_kill_note); +EXPORT_SYMBOL(midi_synth_start_note); +EXPORT_SYMBOL(midi_synth_set_instr); +EXPORT_SYMBOL(midi_synth_reset); +EXPORT_SYMBOL(midi_synth_hw_control); +EXPORT_SYMBOL(midi_synth_aftertouch); +EXPORT_SYMBOL(midi_synth_controller); +EXPORT_SYMBOL(midi_synth_panning); +EXPORT_SYMBOL(midi_synth_setup_voice); +EXPORT_SYMBOL(midi_synth_send_sysex); +EXPORT_SYMBOL(midi_synth_bender); +EXPORT_SYMBOL(midi_synth_load_patch); +EXPORT_SYMBOL(MIDIbuf_avail); diff --git a/sound/oss/midi_synth.c b/sound/oss/midi_synth.c new file mode 100644 index 000000000000..972edc62afd1 --- /dev/null +++ b/sound/oss/midi_synth.c @@ -0,0 +1,697 @@ +/* + * sound/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]); */ + ; + } +} + +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; + } +} + +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; +} + +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; +} + +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; +} + +void +midi_synth_reset(int dev) +{ + + leave_sysex(dev); +} + +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; +} + +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); +} + +void +midi_synth_hw_control(int dev, unsigned char *event) +{ +} + +int +midi_synth_load_patch(int dev, int format, const char __user *addr, + int offs, 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; + + if (format != SYSEX_PATCH) + { +/* printk("MIDI Error: Invalid patch format (key) 0x%x\n", format);*/ + return -EINVAL; + } + if (count < hdr_size) + { +/* printk("MIDI Error: Patch header too short\n");*/ + return -EINVAL; + } + count -= hdr_size; + + /* + * Copy the header from user space but ignore the first bytes which have + * been transferred already. + */ + + if(copy_from_user(&((char *) &sysex)[offs], &(addr)[offs], hdr_size - offs)) + return -EFAULT; + + if (count < sysex.len) + { +/* printk(KERN_WARNING "MIDI Warning: Sysex record too short (%d<%d)\n", count, (int) sysex.len);*/ + sysex.len = count; + } + left = sysex.len; + src_offs = 0; + + for (i = 0; i < left && !signal_pending(current); i++) + { + unsigned char data; + + get_user(*(unsigned char *) &data, (unsigned char __user *) &((addr)[hdr_size + i])); + + 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; +} + +void midi_synth_panning(int dev, int channel, int pressure) +{ +} + +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); +} + +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); +} + +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); +} + +void +midi_synth_setup_voice(int dev, int voice, int channel) +{ +} + +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; +} diff --git a/sound/oss/midi_synth.h b/sound/oss/midi_synth.h new file mode 100644 index 000000000000..6bc9d00bc77c --- /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 offs, 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 000000000000..b2676fa34630 --- /dev/null +++ b/sound/oss/midibuf.c @@ -0,0 +1,431 @@ +/* + * sound/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 +#include +#include +#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 struct timer_list poll_timer = TIMER_INITIALIZER(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) + { + int ok = 1; + + while (DATA_AVAIL(midi_out_buf[dev]) && 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); + 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] = (struct midi_buf *) 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] = (struct midi_buf *) 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 - CANT 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; +} + + +void MIDIbuf_init(void) +{ + /* drag in midi_syms.o */ + { + extern char midi_syms_symbol; + midi_syms_symbol = 0; + } +} + +int MIDIbuf_avail(int dev) +{ + if (midi_in_buf[dev]) + return DATA_AVAIL (midi_in_buf[dev]); + return 0; +} diff --git a/sound/oss/mpu401.c b/sound/oss/mpu401.c new file mode 100644 index 000000000000..b66f53fa8db0 --- /dev/null +++ b/sound/oss/mpu401.c @@ -0,0 +1,1826 @@ +/* + * sound/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 +#include +#include +#include +#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(""); + 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("", midic & 0x0f); + break; + + case 0xf9: + printk(""); + 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(" ", 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(""); + 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(""); + 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; +} + +int intchk_mpu401(void *dev_id) +{ + struct mpu_config *devc; + int dev = (int) dev_id; + + devc = &dev_conf[dev]; + return input_avail(devc); +} + +irqreturn_t mpuintr(int irq, void *dev_id, struct pt_regs *dummy) +{ + struct mpu_config *devc; + int dev = (int) 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; + unsigned long flags; + + devc->version = devc->revision = 0; + + spin_lock_irqsave(&devc->lock,flags); + if ((tmp = mpu_cmd(n, 0xAC, 0)) < 0) + { + spin_unlock_irqrestore(&devc->lock,flags); + return; + } + if ((tmp & 0xf0) > 0x20) /* Why it's larger than 2.x ??? */ + { + spin_unlock_irqrestore(&devc->lock,flags); + return; + } + devc->version = tmp; + + if ((tmp = mpu_cmd(n, 0xAD, 0)) < 0) + { + devc->version = 0; + spin_unlock_irqrestore(&devc->lock,flags); + return; + } + devc->revision = tmp; + spin_unlock_irqrestore(&devc->lock,flags); +} + +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", (void *)m) < 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] = (struct synth_operations *)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 interface #%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, (void *)m); +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, (void *)n); + p=mpu401_synth_operations[n]; + sound_unload_mididev(n); + sound_unload_timerdev(hw_config->slots[2]); + if(p) + 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(""); + 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); +EXPORT_SYMBOL(intchk_mpu401); +EXPORT_SYMBOL(mpuintr); + +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 000000000000..bdc5bde641e6 --- /dev/null +++ b/sound/oss/mpu401.h @@ -0,0 +1,14 @@ + +/* 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, struct pt_regs * dummy); + +/* 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); + +int intchk_mpu401(void *dev_id); +irqreturn_t mpuintr(int irq, void *dev_id, struct pt_regs * dummy); diff --git a/sound/oss/msnd.c b/sound/oss/msnd.c new file mode 100644 index 000000000000..4f1ff1bccdce --- /dev/null +++ b/sound/oss/msnd.c @@ -0,0 +1,419 @@ +/********************************************************************* + * + * 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. + * + * $Id: msnd.c,v 1.17 1999/03/21 16:50:09 andrewtv Exp $ + * + ********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "msnd.h" + +#define LOGNAME "msnd" + +#define MSND_MAX_DEVS 4 + +static multisound_dev_t *devs[MSND_MAX_DEVS]; +static int num_devs; + +int __init 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) +{ + if (f->data) { + vfree(f->data); + f->data = NULL; + } +} + +int msnd_fifo_alloc(msnd_fifo *f, size_t n) +{ + msnd_fifo_free(f); + f->data = (char *)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 "); +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 000000000000..05cf7865be5e --- /dev/null +++ b/sound/oss/msnd.h @@ -0,0 +1,280 @@ +/********************************************************************* + * + * 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. + * + * $Id: msnd.h,v 1.36 1999/03/21 17:05:42 andrewtv Exp $ + * + ********************************************************************/ +#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; + mode_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[16]; + int right_levels[16]; + 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 000000000000..3b23a096fa4e --- /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 000000000000..83c3c46ffffe --- /dev/null +++ b/sound/oss/msnd_classic.h @@ -0,0 +1,188 @@ +/********************************************************************* + * + * 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. + * + * $Id: msnd_classic.h,v 1.10 1999/03/21 17:36:09 andrewtv Exp $ + * + ********************************************************************/ +#ifndef __MSND_CLASSIC_H +#define __MSND_CLASSIC_H + +#include + +#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 000000000000..6ba03f89fc43 --- /dev/null +++ b/sound/oss/msnd_pinnacle.c @@ -0,0 +1,1922 @@ +/********************************************************************* + * + * 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. + * + * $Id: msnd_pinnacle.c,v 1.8 2000/12/30 00:33:21 sycamore Exp $ + * + * 12-3-2000 Modified IO port validation Steve Sycamore + * + * + * $$$: msnd_pinnacle.c,v 1.75 1999/03/21 16:50:09 andrewtv $$$ $ + * + ********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 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 int dev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + int minor = iminor(inode); + + if (cmd == OSS_GETVERSION) { + int sound_version = SOUND_VERSION; + return put_user(sound_version, (int __user *)arg); + } + + if (minor == dev.dsp_minor) + return dsp_ioctl(file, cmd, arg); + else if (minor == dev.mixer_minor) + return mixer_ioctl(cmd, arg); + + return -EINVAL; +} + +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; + + 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))) + return -EBUSY; + + 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; + + return err; +} + +static int dev_release(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + int err = 0; + + lock_kernel(); + if (minor == dev.dsp_minor) + err = dsp_release(file); + else if (minor == dev.mixer_minor) { + /* nothing */ + } else + err = -EINVAL; + unlock_kernel(); + 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(PAGE_SIZE); + + 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_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_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, struct pt_regs *regs) +{ + /* 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 struct file_operations dev_fileops = { + .owner = THIS_MODULE, + .read = dev_read, + .write = dev_write, + .ioctl = dev_ioctl, + .open = dev_open, + .release = dev_release, +}; + +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; + } + request_region(dev.io, dev.numio, dev.name); + + if ((err = dsp_full_reset()) < 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 "); +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 000000000000..e85aef4a55e0 --- /dev/null +++ b/sound/oss/msnd_pinnacle.h @@ -0,0 +1,249 @@ +/********************************************************************* + * + * 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. + * + * $Id: msnd_pinnacle.h,v 1.11 1999/03/21 17:36:09 andrewtv Exp $ + * + ********************************************************************/ +#ifndef __MSND_PINNACLE_H +#define __MSND_PINNACLE_H + +#include + +#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/nec_vrc5477.c b/sound/oss/nec_vrc5477.c new file mode 100644 index 000000000000..0481e5e54ddf --- /dev/null +++ b/sound/oss/nec_vrc5477.c @@ -0,0 +1,2059 @@ +/*********************************************************************** + * Copyright 2001 MontaVista Software Inc. + * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net + * + * drivers/sound/nec_vrc5477.c + * AC97 sound dirver for NEC Vrc5477 chip (an integrated, + * multi-function controller chip for MIPS CPUs) + * + * VRA support Copyright 2001 Bradley D. LaRonde + * + * 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 code is derived from ite8172.c, which is written by Steve Longerbeam. + * + * Features: + * Currently we only support the following capabilities: + * . mono output to PCM L/R (line out). + * . stereo output to PCM L/R (line out). + * . mono input from PCM L (line in). + * . stereo output from PCM (line in). + * . sampling rate at 48k or variable sampling rate + * . support /dev/dsp, /dev/mixer devices, standard OSS devices. + * . only support 16-bit PCM format (hardware limit, no software + * translation) + * . support duplex, but no trigger or realtime. + * + * Specifically the following are not supported: + * . app-set frag size. + * . mmap'ed buffer access + */ + +/* + * Original comments from ite8172.c file. + */ + +/* + * + * Notes: + * + * 1. Much of the OSS buffer allocation, ioctl's, and mmap'ing are + * taken, slightly modified or not at all, from the ES1371 driver, + * so refer to the credits in es1371.c for those. The rest of the + * code (probe, open, read, write, the ISR, etc.) is new. + * 2. The following support is untested: + * * Memory mapping the audio buffers, and the ioctl controls that go + * with it. + * * S/PDIF output. + * 3. The following is not supported: + * * I2S input. + * * legacy audio mode. + * 4. Support for volume button interrupts is implemented but doesn't + * work yet. + * + * Revision history + * 02.08.2001 0.1 Initial release + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* -------------------debug macros -------------------------------------- */ +/* #undef VRC5477_AC97_DEBUG */ +#define VRC5477_AC97_DEBUG + +#undef VRC5477_AC97_VERBOSE_DEBUG +/* #define VRC5477_AC97_VERBOSE_DEBUG */ + +#if defined(VRC5477_AC97_VERBOSE_DEBUG) +#define VRC5477_AC97_DEBUG +#endif + +#if defined(VRC5477_AC97_DEBUG) +#define ASSERT(x) if (!(x)) { \ + panic("assertion failed at %s:%d: %s\n", __FILE__, __LINE__, #x); } +#else +#define ASSERT(x) +#endif /* VRC5477_AC97_DEBUG */ + +#if defined(VRC5477_AC97_VERBOSE_DEBUG) +static u16 inTicket; /* check sync between intr & write */ +static u16 outTicket; +#endif + +/* --------------------------------------------------------------------- */ + +#undef OSS_DOCUMENTED_MIXER_SEMANTICS + +static const unsigned sample_shift[] = { 0, 1, 1, 2 }; + +#define VRC5477_INT_CLR 0x0 +#define VRC5477_INT_STATUS 0x0 +#define VRC5477_CODEC_WR 0x4 +#define VRC5477_CODEC_RD 0x8 +#define VRC5477_CTRL 0x18 +#define VRC5477_ACLINK_CTRL 0x1c +#define VRC5477_INT_MASK 0x24 + +#define VRC5477_DAC1_CTRL 0x30 +#define VRC5477_DAC1L 0x34 +#define VRC5477_DAC1_BADDR 0x38 +#define VRC5477_DAC2_CTRL 0x3c +#define VRC5477_DAC2L 0x40 +#define VRC5477_DAC2_BADDR 0x44 +#define VRC5477_DAC3_CTRL 0x48 +#define VRC5477_DAC3L 0x4c +#define VRC5477_DAC3_BADDR 0x50 + +#define VRC5477_ADC1_CTRL 0x54 +#define VRC5477_ADC1L 0x58 +#define VRC5477_ADC1_BADDR 0x5c +#define VRC5477_ADC2_CTRL 0x60 +#define VRC5477_ADC2L 0x64 +#define VRC5477_ADC2_BADDR 0x68 +#define VRC5477_ADC3_CTRL 0x6c +#define VRC5477_ADC3L 0x70 +#define VRC5477_ADC3_BADDR 0x74 + +#define VRC5477_CODEC_WR_RWC (1 << 23) + +#define VRC5477_CODEC_RD_RRDYA (1 << 31) +#define VRC5477_CODEC_RD_RRDYD (1 << 30) + +#define VRC5477_ACLINK_CTRL_RST_ON (1 << 15) +#define VRC5477_ACLINK_CTRL_RST_TIME 0x7f +#define VRC5477_ACLINK_CTRL_SYNC_ON (1 << 30) +#define VRC5477_ACLINK_CTRL_CK_STOP_ON (1 << 31) + +#define VRC5477_CTRL_DAC2ENB (1 << 15) +#define VRC5477_CTRL_ADC2ENB (1 << 14) +#define VRC5477_CTRL_DAC1ENB (1 << 13) +#define VRC5477_CTRL_ADC1ENB (1 << 12) + +#define VRC5477_INT_MASK_NMASK (1 << 31) +#define VRC5477_INT_MASK_DAC1END (1 << 5) +#define VRC5477_INT_MASK_DAC2END (1 << 4) +#define VRC5477_INT_MASK_DAC3END (1 << 3) +#define VRC5477_INT_MASK_ADC1END (1 << 2) +#define VRC5477_INT_MASK_ADC2END (1 << 1) +#define VRC5477_INT_MASK_ADC3END (1 << 0) + +#define VRC5477_DMA_ACTIVATION (1 << 31) +#define VRC5477_DMA_WIP (1 << 30) + + +#define VRC5477_AC97_MODULE_NAME "NEC_Vrc5477_audio" +#define PFX VRC5477_AC97_MODULE_NAME ": " + +/* --------------------------------------------------------------------- */ + +struct vrc5477_ac97_state { + /* list of vrc5477_ac97 devices */ + struct list_head devs; + + /* the corresponding pci_dev structure */ + struct pci_dev *dev; + + /* soundcore stuff */ + int dev_audio; + + /* hardware resources */ + unsigned long io; + unsigned int irq; + +#ifdef VRC5477_AC97_DEBUG + /* debug /proc entry */ + struct proc_dir_entry *ps; + struct proc_dir_entry *ac97_ps; +#endif /* VRC5477_AC97_DEBUG */ + + struct ac97_codec *codec; + + unsigned dacChannels, adcChannels; + unsigned short dacRate, adcRate; + unsigned short extended_status; + + spinlock_t lock; + struct semaphore open_sem; + mode_t open_mode; + wait_queue_head_t open_wait; + + struct dmabuf { + void *lbuf, *rbuf; + dma_addr_t lbufDma, rbufDma; + unsigned bufOrder; + unsigned numFrag; + unsigned fragShift; + unsigned fragSize; /* redundant */ + unsigned fragTotalSize; /* = numFrag * fragSize(real) */ + unsigned nextIn; + unsigned nextOut; + int count; + unsigned error; /* over/underrun */ + wait_queue_head_t wait; + /* OSS stuff */ + unsigned stopped:1; + unsigned ready:1; + } dma_dac, dma_adc; + + #define WORK_BUF_SIZE 2048 + struct { + u16 lchannel; + u16 rchannel; + } workBuf[WORK_BUF_SIZE/4]; +}; + +/* --------------------------------------------------------------------- */ + +static LIST_HEAD(devs); + +/* --------------------------------------------------------------------- */ + +static inline unsigned ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +/* --------------------------------------------------------------------- */ + +static u16 rdcodec(struct ac97_codec *codec, u8 addr) +{ + struct vrc5477_ac97_state *s = + (struct vrc5477_ac97_state *)codec->private_data; + unsigned long flags; + u32 result; + + spin_lock_irqsave(&s->lock, flags); + + /* wait until we can access codec registers */ + while (inl(s->io + VRC5477_CODEC_WR) & 0x80000000); + + /* write the address and "read" command to codec */ + addr = addr & 0x7f; + outl((addr << 16) | VRC5477_CODEC_WR_RWC, s->io + VRC5477_CODEC_WR); + + /* get the return result */ + udelay(100); /* workaround hardware bug */ + while ( (result = inl(s->io + VRC5477_CODEC_RD)) & + (VRC5477_CODEC_RD_RRDYA | VRC5477_CODEC_RD_RRDYD) ) { + /* we get either addr or data, or both */ + if (result & VRC5477_CODEC_RD_RRDYA) { + ASSERT(addr == ((result >> 16) & 0x7f) ); + } + if (result & VRC5477_CODEC_RD_RRDYD) { + break; + } + } + + spin_unlock_irqrestore(&s->lock, flags); + + return result & 0xffff; +} + + +static void wrcodec(struct ac97_codec *codec, u8 addr, u16 data) +{ + struct vrc5477_ac97_state *s = + (struct vrc5477_ac97_state *)codec->private_data; + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + + /* wait until we can access codec registers */ + while (inl(s->io + VRC5477_CODEC_WR) & 0x80000000); + + /* write the address and value to codec */ + outl((addr << 16) | data, s->io + VRC5477_CODEC_WR); + + spin_unlock_irqrestore(&s->lock, flags); +} + + +static void waitcodec(struct ac97_codec *codec) +{ + struct vrc5477_ac97_state *s = + (struct vrc5477_ac97_state *)codec->private_data; + + /* wait until we can access codec registers */ + while (inl(s->io + VRC5477_CODEC_WR) & 0x80000000); +} + +static int ac97_codec_not_present(struct ac97_codec *codec) +{ + struct vrc5477_ac97_state *s = + (struct vrc5477_ac97_state *)codec->private_data; + unsigned long flags; + unsigned short count = 0xffff; + + spin_lock_irqsave(&s->lock, flags); + + /* wait until we can access codec registers */ + do { + if (!(inl(s->io + VRC5477_CODEC_WR) & 0x80000000)) + break; + } while (--count); + + if (count == 0) { + spin_unlock_irqrestore(&s->lock, flags); + return -1; + } + + /* write 0 to reset */ + outl((AC97_RESET << 16) | 0, s->io + VRC5477_CODEC_WR); + + /* test whether we get a response from ac97 chip */ + count = 0xffff; + do { + if (!(inl(s->io + VRC5477_CODEC_WR) & 0x80000000)) + break; + } while (--count); + + if (count == 0) { + spin_unlock_irqrestore(&s->lock, flags); + return -1; + } + spin_unlock_irqrestore(&s->lock, flags); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static void vrc5477_ac97_delay(int msec) +{ + unsigned long tmo; + signed long tmo2; + + if (in_interrupt()) + return; + + tmo = jiffies + (msec*HZ)/1000; + for (;;) { + tmo2 = tmo - jiffies; + if (tmo2 <= 0) + break; + schedule_timeout(tmo2); + } +} + + +static void set_adc_rate(struct vrc5477_ac97_state *s, unsigned rate) +{ + wrcodec(s->codec, AC97_PCM_LR_ADC_RATE, rate); + s->adcRate = rate; +} + + +static void set_dac_rate(struct vrc5477_ac97_state *s, unsigned rate) +{ + if(s->extended_status & AC97_EXTSTAT_VRA) { + wrcodec(s->codec, AC97_PCM_FRONT_DAC_RATE, rate); + s->dacRate = rdcodec(s->codec, AC97_PCM_FRONT_DAC_RATE); + } +} + +static int ac97_codec_not_present(struct ac97_codec *codec) +{ + struct vrc5477_ac97_state *s = + (struct vrc5477_ac97_state *)codec->private_data; + unsigned long flags; + unsigned short count = 0xffff; + + spin_lock_irqsave(&s->lock, flags); + + /* wait until we can access codec registers */ + do { + if (!(inl(s->io + VRC5477_CODEC_WR) & 0x80000000)) + break; + } while (--count); + + if (count == 0) { + spin_unlock_irqrestore(&s->lock, flags); + return -1; + } + + /* write 0 to reset */ + outl((AC97_RESET << 16) | 0, s->io + VRC5477_CODEC_WR); + + /* test whether we get a response from ac97 chip */ + count = 0xffff; + do { + if (!(inl(s->io + VRC5477_CODEC_WR) & 0x80000000)) + break; + } while (--count); + + if (count == 0) { + spin_unlock_irqrestore(&s->lock, flags); + return -1; + } + spin_unlock_irqrestore(&s->lock, flags); + return 0; +} + +/* --------------------------------------------------------------------- */ + +extern inline void +stop_dac(struct vrc5477_ac97_state *s) +{ + struct dmabuf* db = &s->dma_dac; + unsigned long flags; + u32 temp; + + spin_lock_irqsave(&s->lock, flags); + + if (db->stopped) { + spin_unlock_irqrestore(&s->lock, flags); + return; + } + + /* deactivate the dma */ + outl(0, s->io + VRC5477_DAC1_CTRL); + outl(0, s->io + VRC5477_DAC2_CTRL); + + /* wait for DAM completely stop */ + while (inl(s->io + VRC5477_DAC1_CTRL) & VRC5477_DMA_WIP); + while (inl(s->io + VRC5477_DAC2_CTRL) & VRC5477_DMA_WIP); + + /* disable dac slots in aclink */ + temp = inl(s->io + VRC5477_CTRL); + temp &= ~ (VRC5477_CTRL_DAC1ENB | VRC5477_CTRL_DAC2ENB); + outl (temp, s->io + VRC5477_CTRL); + + /* disable interrupts */ + temp = inl(s->io + VRC5477_INT_MASK); + temp &= ~ (VRC5477_INT_MASK_DAC1END | VRC5477_INT_MASK_DAC2END); + outl (temp, s->io + VRC5477_INT_MASK); + + /* clear pending ones */ + outl(VRC5477_INT_MASK_DAC1END | VRC5477_INT_MASK_DAC2END, + s->io + VRC5477_INT_CLR); + + db->stopped = 1; + + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_dac(struct vrc5477_ac97_state *s) +{ + struct dmabuf* db = &s->dma_dac; + unsigned long flags; + u32 dmaLength; + u32 temp; + + spin_lock_irqsave(&s->lock, flags); + + if (!db->stopped) { + spin_unlock_irqrestore(&s->lock, flags); + return; + } + + /* we should have some data to do the DMA trasnfer */ + ASSERT(db->count >= db->fragSize); + + /* clear pending fales interrupts */ + outl(VRC5477_INT_MASK_DAC1END | VRC5477_INT_MASK_DAC2END, + s->io + VRC5477_INT_CLR); + + /* enable interrupts */ + temp = inl(s->io + VRC5477_INT_MASK); + temp |= VRC5477_INT_MASK_DAC1END | VRC5477_INT_MASK_DAC2END; + outl(temp, s->io + VRC5477_INT_MASK); + + /* setup dma base addr */ + outl(db->lbufDma + db->nextOut, s->io + VRC5477_DAC1_BADDR); + if (s->dacChannels == 1) { + outl(db->lbufDma + db->nextOut, s->io + VRC5477_DAC2_BADDR); + } else { + outl(db->rbufDma + db->nextOut, s->io + VRC5477_DAC2_BADDR); + } + + /* set dma length, in the unit of 0x10 bytes */ + dmaLength = db->fragSize >> 4; + outl(dmaLength, s->io + VRC5477_DAC1L); + outl(dmaLength, s->io + VRC5477_DAC2L); + + /* activate dma */ + outl(VRC5477_DMA_ACTIVATION, s->io + VRC5477_DAC1_CTRL); + outl(VRC5477_DMA_ACTIVATION, s->io + VRC5477_DAC2_CTRL); + + /* enable dac slots - we should hear the music now! */ + temp = inl(s->io + VRC5477_CTRL); + temp |= (VRC5477_CTRL_DAC1ENB | VRC5477_CTRL_DAC2ENB); + outl (temp, s->io + VRC5477_CTRL); + + /* it is time to setup next dma transfer */ + ASSERT(inl(s->io + VRC5477_DAC1_CTRL) & VRC5477_DMA_WIP); + ASSERT(inl(s->io + VRC5477_DAC2_CTRL) & VRC5477_DMA_WIP); + + temp = db->nextOut + db->fragSize; + if (temp >= db->fragTotalSize) { + ASSERT(temp == db->fragTotalSize); + temp = 0; + } + + outl(db->lbufDma + temp, s->io + VRC5477_DAC1_BADDR); + if (s->dacChannels == 1) { + outl(db->lbufDma + temp, s->io + VRC5477_DAC2_BADDR); + } else { + outl(db->rbufDma + temp, s->io + VRC5477_DAC2_BADDR); + } + + db->stopped = 0; + +#if defined(VRC5477_AC97_VERBOSE_DEBUG) + outTicket = *(u16*)(db->lbuf+db->nextOut); + if (db->count > db->fragSize) { + ASSERT((u16)(outTicket+1) == *(u16*)(db->lbuf+temp)); + } +#endif + + spin_unlock_irqrestore(&s->lock, flags); +} + +extern inline void stop_adc(struct vrc5477_ac97_state *s) +{ + struct dmabuf* db = &s->dma_adc; + unsigned long flags; + u32 temp; + + spin_lock_irqsave(&s->lock, flags); + + if (db->stopped) { + spin_unlock_irqrestore(&s->lock, flags); + return; + } + + /* deactivate the dma */ + outl(0, s->io + VRC5477_ADC1_CTRL); + outl(0, s->io + VRC5477_ADC2_CTRL); + + /* disable adc slots in aclink */ + temp = inl(s->io + VRC5477_CTRL); + temp &= ~ (VRC5477_CTRL_ADC1ENB | VRC5477_CTRL_ADC2ENB); + outl (temp, s->io + VRC5477_CTRL); + + /* disable interrupts */ + temp = inl(s->io + VRC5477_INT_MASK); + temp &= ~ (VRC5477_INT_MASK_ADC1END | VRC5477_INT_MASK_ADC2END); + outl (temp, s->io + VRC5477_INT_MASK); + + /* clear pending ones */ + outl(VRC5477_INT_MASK_ADC1END | VRC5477_INT_MASK_ADC2END, + s->io + VRC5477_INT_CLR); + + db->stopped = 1; + + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_adc(struct vrc5477_ac97_state *s) +{ + struct dmabuf* db = &s->dma_adc; + unsigned long flags; + u32 dmaLength; + u32 temp; + + spin_lock_irqsave(&s->lock, flags); + + if (!db->stopped) { + spin_unlock_irqrestore(&s->lock, flags); + return; + } + + /* we should at least have some free space in the buffer */ + ASSERT(db->count < db->fragTotalSize - db->fragSize * 2); + + /* clear pending ones */ + outl(VRC5477_INT_MASK_ADC1END | VRC5477_INT_MASK_ADC2END, + s->io + VRC5477_INT_CLR); + + /* enable interrupts */ + temp = inl(s->io + VRC5477_INT_MASK); + temp |= VRC5477_INT_MASK_ADC1END | VRC5477_INT_MASK_ADC2END; + outl(temp, s->io + VRC5477_INT_MASK); + + /* setup dma base addr */ + outl(db->lbufDma + db->nextIn, s->io + VRC5477_ADC1_BADDR); + outl(db->rbufDma + db->nextIn, s->io + VRC5477_ADC2_BADDR); + + /* setup dma length */ + dmaLength = db->fragSize >> 4; + outl(dmaLength, s->io + VRC5477_ADC1L); + outl(dmaLength, s->io + VRC5477_ADC2L); + + /* activate dma */ + outl(VRC5477_DMA_ACTIVATION, s->io + VRC5477_ADC1_CTRL); + outl(VRC5477_DMA_ACTIVATION, s->io + VRC5477_ADC2_CTRL); + + /* enable adc slots */ + temp = inl(s->io + VRC5477_CTRL); + temp |= (VRC5477_CTRL_ADC1ENB | VRC5477_CTRL_ADC2ENB); + outl (temp, s->io + VRC5477_CTRL); + + /* it is time to setup next dma transfer */ + temp = db->nextIn + db->fragSize; + if (temp >= db->fragTotalSize) { + ASSERT(temp == db->fragTotalSize); + temp = 0; + } + outl(db->lbufDma + temp, s->io + VRC5477_ADC1_BADDR); + outl(db->rbufDma + temp, s->io + VRC5477_ADC2_BADDR); + + db->stopped = 0; + + spin_unlock_irqrestore(&s->lock, flags); +} + +/* --------------------------------------------------------------------- */ + +#define DMABUF_DEFAULTORDER (16-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +extern inline void dealloc_dmabuf(struct vrc5477_ac97_state *s, + struct dmabuf *db) +{ + if (db->lbuf) { + ASSERT(db->rbuf); + pci_free_consistent(s->dev, PAGE_SIZE << db->bufOrder, + db->lbuf, db->lbufDma); + pci_free_consistent(s->dev, PAGE_SIZE << db->bufOrder, + db->rbuf, db->rbufDma); + db->lbuf = db->rbuf = NULL; + } + db->nextIn = db->nextOut = 0; + db->ready = 0; +} + +static int prog_dmabuf(struct vrc5477_ac97_state *s, + struct dmabuf *db, + unsigned rate) +{ + int order; + unsigned bufsize; + + if (!db->lbuf) { + ASSERT(!db->rbuf); + + db->ready = 0; + for (order = DMABUF_DEFAULTORDER; + order >= DMABUF_MINORDER; + order--) { + db->lbuf = pci_alloc_consistent(s->dev, + PAGE_SIZE << order, + &db->lbufDma); + db->rbuf = pci_alloc_consistent(s->dev, + PAGE_SIZE << order, + &db->rbufDma); + if (db->lbuf && db->rbuf) break; + if (db->lbuf) { + ASSERT(!db->rbuf); + pci_free_consistent(s->dev, + PAGE_SIZE << order, + db->lbuf, + db->lbufDma); + } + } + if (!db->lbuf) { + ASSERT(!db->rbuf); + return -ENOMEM; + } + + db->bufOrder = order; + } + + db->count = 0; + db->nextIn = db->nextOut = 0; + + bufsize = PAGE_SIZE << db->bufOrder; + db->fragShift = ld2(rate * 2 / 100); + if (db->fragShift < 4) db->fragShift = 4; + + db->numFrag = bufsize >> db->fragShift; + while (db->numFrag < 4 && db->fragShift > 4) { + db->fragShift--; + db->numFrag = bufsize >> db->fragShift; + } + db->fragSize = 1 << db->fragShift; + db->fragTotalSize = db->numFrag << db->fragShift; + memset(db->lbuf, 0, db->fragTotalSize); + memset(db->rbuf, 0, db->fragTotalSize); + + db->ready = 1; + + return 0; +} + +static inline int prog_dmabuf_adc(struct vrc5477_ac97_state *s) +{ + stop_adc(s); + return prog_dmabuf(s, &s->dma_adc, s->adcRate); +} + +static inline int prog_dmabuf_dac(struct vrc5477_ac97_state *s) +{ + stop_dac(s); + return prog_dmabuf(s, &s->dma_dac, s->dacRate); +} + + +/* --------------------------------------------------------------------- */ +/* hold spinlock for the following! */ + +static inline void vrc5477_ac97_adc_interrupt(struct vrc5477_ac97_state *s) +{ + struct dmabuf* adc = &s->dma_adc; + unsigned temp; + + /* we need two frags avaiable because one is already being used + * and the other will be used when next interrupt happens. + */ + if (adc->count >= adc->fragTotalSize - adc->fragSize) { + stop_adc(s); + adc->error++; + printk(KERN_INFO PFX "adc overrun\n"); + return; + } + + /* set the base addr for next DMA transfer */ + temp = adc->nextIn + 2*adc->fragSize; + if (temp >= adc->fragTotalSize) { + ASSERT( (temp == adc->fragTotalSize) || + (temp == adc->fragTotalSize + adc->fragSize) ); + temp -= adc->fragTotalSize; + } + outl(adc->lbufDma + temp, s->io + VRC5477_ADC1_BADDR); + outl(adc->rbufDma + temp, s->io + VRC5477_ADC2_BADDR); + + /* adjust nextIn */ + adc->nextIn += adc->fragSize; + if (adc->nextIn >= adc->fragTotalSize) { + ASSERT(adc->nextIn == adc->fragTotalSize); + adc->nextIn = 0; + } + + /* adjust count */ + adc->count += adc->fragSize; + + /* wake up anybody listening */ + if (waitqueue_active(&adc->wait)) { + wake_up_interruptible(&adc->wait); + } +} + +static inline void vrc5477_ac97_dac_interrupt(struct vrc5477_ac97_state *s) +{ + struct dmabuf* dac = &s->dma_dac; + unsigned temp; + + /* next DMA transfer should already started */ + // ASSERT(inl(s->io + VRC5477_DAC1_CTRL) & VRC5477_DMA_WIP); + // ASSERT(inl(s->io + VRC5477_DAC2_CTRL) & VRC5477_DMA_WIP); + + /* let us set for next next DMA transfer */ + temp = dac->nextOut + dac->fragSize*2; + if (temp >= dac->fragTotalSize) { + ASSERT( (temp == dac->fragTotalSize) || + (temp == dac->fragTotalSize + dac->fragSize) ); + temp -= dac->fragTotalSize; + } + outl(dac->lbufDma + temp, s->io + VRC5477_DAC1_BADDR); + if (s->dacChannels == 1) { + outl(dac->lbufDma + temp, s->io + VRC5477_DAC2_BADDR); + } else { + outl(dac->rbufDma + temp, s->io + VRC5477_DAC2_BADDR); + } + +#if defined(VRC5477_AC97_VERBOSE_DEBUG) + if (*(u16*)(dac->lbuf + dac->nextOut) != outTicket) { + printk("assert fail: - %d vs %d\n", + *(u16*)(dac->lbuf + dac->nextOut), + outTicket); + ASSERT(1 == 0); + } +#endif + + /* adjust nextOut pointer */ + dac->nextOut += dac->fragSize; + if (dac->nextOut >= dac->fragTotalSize) { + ASSERT(dac->nextOut == dac->fragTotalSize); + dac->nextOut = 0; + } + + /* adjust count */ + dac->count -= dac->fragSize; + if (dac->count <=0 ) { + /* buffer under run */ + dac->count = 0; + dac->nextIn = dac->nextOut; + stop_dac(s); + } + +#if defined(VRC5477_AC97_VERBOSE_DEBUG) + if (dac->count) { + outTicket ++; + ASSERT(*(u16*)(dac->lbuf + dac->nextOut) == outTicket); + } +#endif + + /* we cannot have both under run and someone is waiting on us */ + ASSERT(! (waitqueue_active(&dac->wait) && (dac->count <= 0)) ); + + /* wake up anybody listening */ + if (waitqueue_active(&dac->wait)) + wake_up_interruptible(&dac->wait); +} + +static irqreturn_t vrc5477_ac97_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct vrc5477_ac97_state *s = (struct vrc5477_ac97_state *)dev_id; + u32 irqStatus; + u32 adcInterrupts, dacInterrupts; + + spin_lock(&s->lock); + + /* get irqStatus and clear the detected ones */ + irqStatus = inl(s->io + VRC5477_INT_STATUS); + outl(irqStatus, s->io + VRC5477_INT_CLR); + + /* let us see what we get */ + dacInterrupts = VRC5477_INT_MASK_DAC1END | VRC5477_INT_MASK_DAC2END; + adcInterrupts = VRC5477_INT_MASK_ADC1END | VRC5477_INT_MASK_ADC2END; + if (irqStatus & dacInterrupts) { + /* we should get both interrupts, but just in case ... */ + if (irqStatus & VRC5477_INT_MASK_DAC1END) { + vrc5477_ac97_dac_interrupt(s); + } + if ( (irqStatus & dacInterrupts) != dacInterrupts ) { + printk(KERN_WARNING "vrc5477_ac97 : dac interrupts not in sync!!!\n"); + stop_dac(s); + start_dac(s); + } + } else if (irqStatus & adcInterrupts) { + /* we should get both interrupts, but just in case ... */ + if(irqStatus & VRC5477_INT_MASK_ADC1END) { + vrc5477_ac97_adc_interrupt(s); + } + if ( (irqStatus & adcInterrupts) != adcInterrupts ) { + printk(KERN_WARNING "vrc5477_ac97 : adc interrupts not in sync!!!\n"); + stop_adc(s); + start_adc(s); + } + } + + spin_unlock(&s->lock); + return IRQ_HANDLED; +} + +/* --------------------------------------------------------------------- */ + +static int vrc5477_ac97_open_mixdev(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct list_head *list; + struct vrc5477_ac97_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct vrc5477_ac97_state, devs); + if (s->codec->dev_mixer == minor) + break; + } + file->private_data = s; + return nonseekable_open(inode, file); +} + +static int vrc5477_ac97_release_mixdev(struct inode *inode, struct file *file) +{ + return 0; +} + + +static int mixdev_ioctl(struct ac97_codec *codec, unsigned int cmd, + unsigned long arg) +{ + return codec->mixer_ioctl(codec, cmd, arg); +} + +static int vrc5477_ac97_ioctl_mixdev(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct vrc5477_ac97_state *s = + (struct vrc5477_ac97_state *)file->private_data; + struct ac97_codec *codec = s->codec; + + return mixdev_ioctl(codec, cmd, arg); +} + +static /*const*/ struct file_operations vrc5477_ac97_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = vrc5477_ac97_ioctl_mixdev, + .open = vrc5477_ac97_open_mixdev, + .release = vrc5477_ac97_release_mixdev, +}; + +/* --------------------------------------------------------------------- */ + +static int drain_dac(struct vrc5477_ac97_state *s, int nonblock) +{ + unsigned long flags; + int count, tmo; + + if (!s->dma_dac.ready) + return 0; + + for (;;) { + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) + return -EBUSY; + tmo = 1000 * count / s->dacRate / 2; + vrc5477_ac97_delay(tmo); + } + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static inline int +copy_two_channel_adc_to_user(struct vrc5477_ac97_state *s, + char *buffer, + int copyCount) +{ + struct dmabuf *db = &s->dma_adc; + int bufStart = db->nextOut; + for (; copyCount > 0; ) { + int i; + int count = copyCount; + if (count > WORK_BUF_SIZE/2) count = WORK_BUF_SIZE/2; + for (i=0; i< count/2; i++) { + s->workBuf[i].lchannel = + *(u16*)(db->lbuf + bufStart + i*2); + s->workBuf[i].rchannel = + *(u16*)(db->rbuf + bufStart + i*2); + } + if (copy_to_user(buffer, s->workBuf, count*2)) { + return -1; + } + + copyCount -= count; + bufStart += count; + ASSERT(bufStart <= db->fragTotalSize); + buffer += count *2; + } + return 0; +} + +/* return the total bytes that is copied */ +static inline int +copy_adc_to_user(struct vrc5477_ac97_state *s, + char * buffer, + size_t count, + int avail) +{ + struct dmabuf *db = &s->dma_adc; + int copyCount=0; + int copyFragCount=0; + int totalCopyCount = 0; + int totalCopyFragCount = 0; + unsigned long flags; + + /* adjust count to signel channel byte count */ + count >>= s->adcChannels - 1; + + /* we may have to "copy" twice as ring buffer wraps around */ + for (; (avail > 0) && (count > 0); ) { + /* determine max possible copy count for single channel */ + copyCount = count; + if (copyCount > avail) { + copyCount = avail; + } + if (copyCount + db->nextOut > db->fragTotalSize) { + copyCount = db->fragTotalSize - db->nextOut; + ASSERT((copyCount % db->fragSize) == 0); + } + + copyFragCount = (copyCount-1) >> db->fragShift; + copyFragCount = (copyFragCount+1) << db->fragShift; + ASSERT(copyFragCount >= copyCount); + + /* we copy differently based on adc channels */ + if (s->adcChannels == 1) { + if (copy_to_user(buffer, + db->lbuf + db->nextOut, + copyCount)) + return -1; + } else { + /* *sigh* we have to mix two streams into one */ + if (copy_two_channel_adc_to_user(s, buffer, copyCount)) + return -1; + } + + count -= copyCount; + totalCopyCount += copyCount; + avail -= copyFragCount; + totalCopyFragCount += copyFragCount; + + buffer += copyCount << (s->adcChannels-1); + + db->nextOut += copyFragCount; + if (db->nextOut >= db->fragTotalSize) { + ASSERT(db->nextOut == db->fragTotalSize); + db->nextOut = 0; + } + + ASSERT((copyFragCount % db->fragSize) == 0); + ASSERT( (count == 0) || (copyCount == copyFragCount)); + } + + spin_lock_irqsave(&s->lock, flags); + db->count -= totalCopyFragCount; + spin_unlock_irqrestore(&s->lock, flags); + + return totalCopyCount << (s->adcChannels-1); +} + +static ssize_t +vrc5477_ac97_read(struct file *file, + char *buffer, + size_t count, + loff_t *ppos) +{ + struct vrc5477_ac97_state *s = + (struct vrc5477_ac97_state *)file->private_data; + struct dmabuf *db = &s->dma_adc; + ssize_t ret = 0; + unsigned long flags; + int copyCount; + size_t avail; + + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + + ASSERT(db->ready); + + while (count > 0) { + // wait for samples in capture buffer + do { + spin_lock_irqsave(&s->lock, flags); + if (db->stopped) + start_adc(s); + avail = db->count; + spin_unlock_irqrestore(&s->lock, flags); + if (avail <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + return ret; + } + interruptible_sleep_on(&db->wait); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + return ret; + } + } + } while (avail <= 0); + + ASSERT( (avail % db->fragSize) == 0); + copyCount = copy_adc_to_user(s, buffer, count, avail); + if (copyCount <=0 ) { + if (!ret) ret = -EFAULT; + return ret; + } + + count -= copyCount; + buffer += copyCount; + ret += copyCount; + } // while (count > 0) + + return ret; +} + +static inline int +copy_two_channel_dac_from_user(struct vrc5477_ac97_state *s, + const char *buffer, + int copyCount) +{ + struct dmabuf *db = &s->dma_dac; + int bufStart = db->nextIn; + + ASSERT(db->ready); + + for (; copyCount > 0; ) { + int i; + int count = copyCount; + if (count > WORK_BUF_SIZE/2) count = WORK_BUF_SIZE/2; + if (copy_from_user(s->workBuf, buffer, count*2)) { + return -1; + } + for (i=0; i< count/2; i++) { + *(u16*)(db->lbuf + bufStart + i*2) = + s->workBuf[i].lchannel; + *(u16*)(db->rbuf + bufStart + i*2) = + s->workBuf[i].rchannel; + } + + copyCount -= count; + bufStart += count; + ASSERT(bufStart <= db->fragTotalSize); + buffer += count *2; + } + return 0; + +} + +/* return the total bytes that is copied */ +static inline int +copy_dac_from_user(struct vrc5477_ac97_state *s, + const char *buffer, + size_t count, + int avail) +{ + struct dmabuf *db = &s->dma_dac; + int copyCount=0; + int copyFragCount=0; + int totalCopyCount = 0; + int totalCopyFragCount = 0; + unsigned long flags; +#if defined(VRC5477_AC97_VERBOSE_DEBUG) + int i; +#endif + + /* adjust count to signel channel byte count */ + count >>= s->dacChannels - 1; + + /* we may have to "copy" twice as ring buffer wraps around */ + for (; (avail > 0) && (count > 0); ) { + /* determine max possible copy count for single channel */ + copyCount = count; + if (copyCount > avail) { + copyCount = avail; + } + if (copyCount + db->nextIn > db->fragTotalSize) { + copyCount = db->fragTotalSize - db->nextIn; + ASSERT(copyCount > 0); + } + + copyFragCount = copyCount; + ASSERT(copyFragCount >= copyCount); + + /* we copy differently based on the number channels */ + if (s->dacChannels == 1) { + if (copy_from_user(db->lbuf + db->nextIn, + buffer, + copyCount)) + return -1; + /* fill gaps with 0 */ + memset(db->lbuf + db->nextIn + copyCount, + 0, + copyFragCount - copyCount); + } else { + /* we have demux the stream into two separate ones */ + if (copy_two_channel_dac_from_user(s, buffer, copyCount)) + return -1; + /* fill gaps with 0 */ + memset(db->lbuf + db->nextIn + copyCount, + 0, + copyFragCount - copyCount); + memset(db->rbuf + db->nextIn + copyCount, + 0, + copyFragCount - copyCount); + } + +#if defined(VRC5477_AC97_VERBOSE_DEBUG) + for (i=0; i< copyFragCount; i+= db->fragSize) { + *(u16*)(db->lbuf + db->nextIn + i) = inTicket ++; + } +#endif + + count -= copyCount; + totalCopyCount += copyCount; + avail -= copyFragCount; + totalCopyFragCount += copyFragCount; + + buffer += copyCount << (s->dacChannels - 1); + + db->nextIn += copyFragCount; + if (db->nextIn >= db->fragTotalSize) { + ASSERT(db->nextIn == db->fragTotalSize); + db->nextIn = 0; + } + + ASSERT( (count == 0) || (copyCount == copyFragCount)); + } + + spin_lock_irqsave(&s->lock, flags); + db->count += totalCopyFragCount; + if (db->stopped) { + start_dac(s); + } + + /* nextIn should not be equal to nextOut unless we are full */ + ASSERT( ( (db->count == db->fragTotalSize) && + (db->nextIn == db->nextOut) ) || + ( (db->count < db->fragTotalSize) && + (db->nextIn != db->nextOut) ) ); + + spin_unlock_irqrestore(&s->lock, flags); + + return totalCopyCount << (s->dacChannels-1); + +} + +static ssize_t vrc5477_ac97_write(struct file *file, const char *buffer, + size_t count, loff_t *ppos) +{ + struct vrc5477_ac97_state *s = + (struct vrc5477_ac97_state *)file->private_data; + struct dmabuf *db = &s->dma_dac; + ssize_t ret; + unsigned long flags; + int copyCount, avail; + + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; + + while (count > 0) { + // wait for space in playback buffer + do { + spin_lock_irqsave(&s->lock, flags); + avail = db->fragTotalSize - db->count; + spin_unlock_irqrestore(&s->lock, flags); + if (avail <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + return ret; + } + interruptible_sleep_on(&db->wait); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + return ret; + } + } + } while (avail <= 0); + + copyCount = copy_dac_from_user(s, buffer, count, avail); + if (copyCount < 0) { + if (!ret) ret = -EFAULT; + return ret; + } + + count -= copyCount; + buffer += copyCount; + ret += copyCount; + } // while (count > 0) + + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int vrc5477_ac97_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct vrc5477_ac97_state *s = (struct vrc5477_ac97_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + if (file->f_mode & FMODE_WRITE) + poll_wait(file, &s->dma_dac.wait, wait); + if (file->f_mode & FMODE_READ) + poll_wait(file, &s->dma_adc.wait, wait); + spin_lock_irqsave(&s->lock, flags); + if (file->f_mode & FMODE_READ) { + if (s->dma_adc.count >= (signed)s->dma_adc.fragSize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if ((signed)s->dma_dac.fragTotalSize >= + s->dma_dac.count + (signed)s->dma_dac.fragSize) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +#ifdef VRC5477_AC97_DEBUG +static struct ioctl_str_t { + unsigned int cmd; + const char* str; +} ioctl_str[] = { + {SNDCTL_DSP_RESET, "SNDCTL_DSP_RESET"}, + {SNDCTL_DSP_SYNC, "SNDCTL_DSP_SYNC"}, + {SNDCTL_DSP_SPEED, "SNDCTL_DSP_SPEED"}, + {SNDCTL_DSP_STEREO, "SNDCTL_DSP_STEREO"}, + {SNDCTL_DSP_GETBLKSIZE, "SNDCTL_DSP_GETBLKSIZE"}, + {SNDCTL_DSP_SETFMT, "SNDCTL_DSP_SETFMT"}, + {SNDCTL_DSP_SAMPLESIZE, "SNDCTL_DSP_SAMPLESIZE"}, + {SNDCTL_DSP_CHANNELS, "SNDCTL_DSP_CHANNELS"}, + {SOUND_PCM_WRITE_CHANNELS, "SOUND_PCM_WRITE_CHANNELS"}, + {SOUND_PCM_WRITE_FILTER, "SOUND_PCM_WRITE_FILTER"}, + {SNDCTL_DSP_POST, "SNDCTL_DSP_POST"}, + {SNDCTL_DSP_SUBDIVIDE, "SNDCTL_DSP_SUBDIVIDE"}, + {SNDCTL_DSP_SETFRAGMENT, "SNDCTL_DSP_SETFRAGMENT"}, + {SNDCTL_DSP_GETFMTS, "SNDCTL_DSP_GETFMTS"}, + {SNDCTL_DSP_GETOSPACE, "SNDCTL_DSP_GETOSPACE"}, + {SNDCTL_DSP_GETISPACE, "SNDCTL_DSP_GETISPACE"}, + {SNDCTL_DSP_NONBLOCK, "SNDCTL_DSP_NONBLOCK"}, + {SNDCTL_DSP_GETCAPS, "SNDCTL_DSP_GETCAPS"}, + {SNDCTL_DSP_GETTRIGGER, "SNDCTL_DSP_GETTRIGGER"}, + {SNDCTL_DSP_SETTRIGGER, "SNDCTL_DSP_SETTRIGGER"}, + {SNDCTL_DSP_GETIPTR, "SNDCTL_DSP_GETIPTR"}, + {SNDCTL_DSP_GETOPTR, "SNDCTL_DSP_GETOPTR"}, + {SNDCTL_DSP_MAPINBUF, "SNDCTL_DSP_MAPINBUF"}, + {SNDCTL_DSP_MAPOUTBUF, "SNDCTL_DSP_MAPOUTBUF"}, + {SNDCTL_DSP_SETSYNCRO, "SNDCTL_DSP_SETSYNCRO"}, + {SNDCTL_DSP_SETDUPLEX, "SNDCTL_DSP_SETDUPLEX"}, + {SNDCTL_DSP_GETODELAY, "SNDCTL_DSP_GETODELAY"}, + {SNDCTL_DSP_GETCHANNELMASK, "SNDCTL_DSP_GETCHANNELMASK"}, + {SNDCTL_DSP_BIND_CHANNEL, "SNDCTL_DSP_BIND_CHANNEL"}, + {OSS_GETVERSION, "OSS_GETVERSION"}, + {SOUND_PCM_READ_RATE, "SOUND_PCM_READ_RATE"}, + {SOUND_PCM_READ_CHANNELS, "SOUND_PCM_READ_CHANNELS"}, + {SOUND_PCM_READ_BITS, "SOUND_PCM_READ_BITS"}, + {SOUND_PCM_READ_FILTER, "SOUND_PCM_READ_FILTER"} +}; +#endif + +static int vrc5477_ac97_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct vrc5477_ac97_state *s = (struct vrc5477_ac97_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + int count; + int val, ret; + +#ifdef VRC5477_AC97_DEBUG + for (count=0; countf_mode & FMODE_WRITE) + return drain_dac(s, file->f_flags & O_NONBLOCK); + return 0; + + case SNDCTL_DSP_SETDUPLEX: + return 0; + + case SNDCTL_DSP_GETCAPS: + return put_user(DSP_CAP_DUPLEX, (int *)arg); + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + synchronize_irq(s->irq); + s->dma_dac.count = 0; + s->dma_dac.nextIn = s->dma_dac.nextOut = 0; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(s->irq); + s->dma_adc.count = 0; + s->dma_adc.nextIn = s->dma_adc.nextOut = 0; + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, (int *)arg)) + return -EFAULT; + if (val >= 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + set_adc_rate(s, val); + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + set_dac_rate(s, val); + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + } + return put_user((file->f_mode & FMODE_READ) ? + s->adcRate : s->dacRate, (int *)arg); + + case SNDCTL_DSP_STEREO: + if (get_user(val, (int *)arg)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + if (val) + s->adcChannels = 2; + else + s->adcChannels = 1; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + if (val) + s->dacChannels = 2; + else + s->dacChannels = 1; + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, (int *)arg)) + return -EFAULT; + if (val != 0) { + if ( (val != 1) && (val != 2)) val = 2; + + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dacChannels = val; + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dacChannels = val; + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + } + return put_user(val, (int *)arg); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_LE, (int *)arg); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, (int *)arg)) + return -EFAULT; + if (val != AFMT_QUERY) { + if (val != AFMT_S16_LE) return -EINVAL; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + if ((ret = prog_dmabuf_adc(s))) + return ret; + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + if ((ret = prog_dmabuf_dac(s))) + return ret; + } + } else { + val = AFMT_S16_LE; + } + return put_user(val, (int *)arg); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + case SNDCTL_DSP_SETTRIGGER: + /* NO trigger */ + return -EINVAL; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + abinfo.fragsize = s->dma_dac.fragSize << (s->dacChannels-1); + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + abinfo.bytes = (s->dma_dac.fragTotalSize - count) << + (s->dacChannels-1); + abinfo.fragstotal = s->dma_dac.numFrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac.fragShift >> + (s->dacChannels-1); + return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + abinfo.fragsize = s->dma_adc.fragSize << (s->adcChannels-1); + spin_lock_irqsave(&s->lock, flags); + count = s->dma_adc.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + abinfo.bytes = count << (s->adcChannels-1); + abinfo.fragstotal = s->dma_adc.numFrag; + abinfo.fragments = (abinfo.bytes >> s->dma_adc.fragShift) >> + (s->adcChannels-1); + return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + return put_user(count, (int *)arg); + + case SNDCTL_DSP_GETIPTR: + case SNDCTL_DSP_GETOPTR: + /* we cannot get DMA ptr */ + return -EINVAL; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) + return put_user(s->dma_dac.fragSize << (s->dacChannels-1), (int *)arg); + else + return put_user(s->dma_adc.fragSize << (s->adcChannels-1), (int *)arg); + + case SNDCTL_DSP_SETFRAGMENT: + /* we ignore fragment size request */ + return 0; + + case SNDCTL_DSP_SUBDIVIDE: + /* what is this for? [jsun] */ + return 0; + + case SOUND_PCM_READ_RATE: + return put_user((file->f_mode & FMODE_READ) ? + s->adcRate : s->dacRate, (int *)arg); + + case SOUND_PCM_READ_CHANNELS: + if (file->f_mode & FMODE_READ) + return put_user(s->adcChannels, (int *)arg); + else + return put_user(s->dacChannels ? 2 : 1, (int *)arg); + + case SOUND_PCM_READ_BITS: + return put_user(16, (int *)arg); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + } + + return mixdev_ioctl(s->codec, cmd, arg); +} + + +static int vrc5477_ac97_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + struct list_head *list; + struct vrc5477_ac97_state *s; + int ret=0; + + nonseekable_open(inode, file); + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct vrc5477_ac97_state, devs); + if (!((s->dev_audio ^ minor) & ~0xf)) + break; + } + file->private_data = s; + + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & file->f_mode) { + + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + + spin_lock_irqsave(&s->lock, flags); + + if (file->f_mode & FMODE_READ) { + /* set default settings */ + set_adc_rate(s, 48000); + s->adcChannels = 2; + + ret = prog_dmabuf_adc(s); + if (ret) goto bailout; + } + if (file->f_mode & FMODE_WRITE) { + /* set default settings */ + set_dac_rate(s, 48000); + s->dacChannels = 2; + + ret = prog_dmabuf_dac(s); + if (ret) goto bailout; + } + + s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + + bailout: + spin_unlock_irqrestore(&s->lock, flags); + + up(&s->open_sem); + return ret; +} + +static int vrc5477_ac97_release(struct inode *inode, struct file *file) +{ + struct vrc5477_ac97_state *s = + (struct vrc5477_ac97_state *)file->private_data; + + lock_kernel(); + if (file->f_mode & FMODE_WRITE) + drain_dac(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem); + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + dealloc_dmabuf(s, &s->dma_dac); + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + dealloc_dmabuf(s, &s->dma_adc); + } + s->open_mode &= (~file->f_mode) & (FMODE_READ|FMODE_WRITE); + up(&s->open_sem); + wake_up(&s->open_wait); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations vrc5477_ac97_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = vrc5477_ac97_read, + .write = vrc5477_ac97_write, + .poll = vrc5477_ac97_poll, + .ioctl = vrc5477_ac97_ioctl, + // .mmap = vrc5477_ac97_mmap, + .open = vrc5477_ac97_open, + .release = vrc5477_ac97_release, +}; + + +/* --------------------------------------------------------------------- */ + + +/* --------------------------------------------------------------------- */ + +/* + * for debugging purposes, we'll create a proc device that dumps the + * CODEC chipstate + */ + +#ifdef VRC5477_AC97_DEBUG + +struct { + const char *regname; + unsigned regaddr; +} vrc5477_ac97_regs[] = { + {"VRC5477_INT_STATUS", VRC5477_INT_STATUS}, + {"VRC5477_CODEC_WR", VRC5477_CODEC_WR}, + {"VRC5477_CODEC_RD", VRC5477_CODEC_RD}, + {"VRC5477_CTRL", VRC5477_CTRL}, + {"VRC5477_ACLINK_CTRL", VRC5477_ACLINK_CTRL}, + {"VRC5477_INT_MASK", VRC5477_INT_MASK}, + {"VRC5477_DAC1_CTRL", VRC5477_DAC1_CTRL}, + {"VRC5477_DAC1L", VRC5477_DAC1L}, + {"VRC5477_DAC1_BADDR", VRC5477_DAC1_BADDR}, + {"VRC5477_DAC2_CTRL", VRC5477_DAC2_CTRL}, + {"VRC5477_DAC2L", VRC5477_DAC2L}, + {"VRC5477_DAC2_BADDR", VRC5477_DAC2_BADDR}, + {"VRC5477_DAC3_CTRL", VRC5477_DAC3_CTRL}, + {"VRC5477_DAC3L", VRC5477_DAC3L}, + {"VRC5477_DAC3_BADDR", VRC5477_DAC3_BADDR}, + {"VRC5477_ADC1_CTRL", VRC5477_ADC1_CTRL}, + {"VRC5477_ADC1L", VRC5477_ADC1L}, + {"VRC5477_ADC1_BADDR", VRC5477_ADC1_BADDR}, + {"VRC5477_ADC2_CTRL", VRC5477_ADC2_CTRL}, + {"VRC5477_ADC2L", VRC5477_ADC2L}, + {"VRC5477_ADC2_BADDR", VRC5477_ADC2_BADDR}, + {"VRC5477_ADC3_CTRL", VRC5477_ADC3_CTRL}, + {"VRC5477_ADC3L", VRC5477_ADC3L}, + {"VRC5477_ADC3_BADDR", VRC5477_ADC3_BADDR}, + {NULL, 0x0} +}; + +static int proc_vrc5477_ac97_dump (char *buf, char **start, off_t fpos, + int length, int *eof, void *data) +{ + struct vrc5477_ac97_state *s; + int cnt, len = 0; + + if (list_empty(&devs)) + return 0; + s = list_entry(devs.next, struct vrc5477_ac97_state, devs); + + /* print out header */ + len += sprintf(buf + len, "\n\t\tVrc5477 Audio Debug\n\n"); + + // print out digital controller state + len += sprintf (buf + len, "NEC Vrc5477 Audio Controller registers\n"); + len += sprintf (buf + len, "---------------------------------\n"); + for (cnt=0; vrc5477_ac97_regs[cnt].regname != NULL; cnt++) { + len+= sprintf (buf + len, "%-20s = %08x\n", + vrc5477_ac97_regs[cnt].regname, + inl(s->io + vrc5477_ac97_regs[cnt].regaddr)); + } + + /* print out driver state */ + len += sprintf (buf + len, "NEC Vrc5477 Audio driver states\n"); + len += sprintf (buf + len, "---------------------------------\n"); + len += sprintf (buf + len, "dacChannels = %d\n", s->dacChannels); + len += sprintf (buf + len, "adcChannels = %d\n", s->adcChannels); + len += sprintf (buf + len, "dacRate = %d\n", s->dacRate); + len += sprintf (buf + len, "adcRate = %d\n", s->adcRate); + + len += sprintf (buf + len, "dma_dac is %s ready\n", + s->dma_dac.ready? "" : "not"); + if (s->dma_dac.ready) { + len += sprintf (buf + len, "dma_dac is %s stopped.\n", + s->dma_dac.stopped? "" : "not"); + len += sprintf (buf + len, "dma_dac.fragSize = %x\n", + s->dma_dac.fragSize); + len += sprintf (buf + len, "dma_dac.fragShift = %x\n", + s->dma_dac.fragShift); + len += sprintf (buf + len, "dma_dac.numFrag = %x\n", + s->dma_dac.numFrag); + len += sprintf (buf + len, "dma_dac.fragTotalSize = %x\n", + s->dma_dac.fragTotalSize); + len += sprintf (buf + len, "dma_dac.nextIn = %x\n", + s->dma_dac.nextIn); + len += sprintf (buf + len, "dma_dac.nextOut = %x\n", + s->dma_dac.nextOut); + len += sprintf (buf + len, "dma_dac.count = %x\n", + s->dma_dac.count); + } + + len += sprintf (buf + len, "dma_adc is %s ready\n", + s->dma_adc.ready? "" : "not"); + if (s->dma_adc.ready) { + len += sprintf (buf + len, "dma_adc is %s stopped.\n", + s->dma_adc.stopped? "" : "not"); + len += sprintf (buf + len, "dma_adc.fragSize = %x\n", + s->dma_adc.fragSize); + len += sprintf (buf + len, "dma_adc.fragShift = %x\n", + s->dma_adc.fragShift); + len += sprintf (buf + len, "dma_adc.numFrag = %x\n", + s->dma_adc.numFrag); + len += sprintf (buf + len, "dma_adc.fragTotalSize = %x\n", + s->dma_adc.fragTotalSize); + len += sprintf (buf + len, "dma_adc.nextIn = %x\n", + s->dma_adc.nextIn); + len += sprintf (buf + len, "dma_adc.nextOut = %x\n", + s->dma_adc.nextOut); + len += sprintf (buf + len, "dma_adc.count = %x\n", + s->dma_adc.count); + } + + /* print out CODEC state */ + len += sprintf (buf + len, "\nAC97 CODEC registers\n"); + len += sprintf (buf + len, "----------------------\n"); + for (cnt=0; cnt <= 0x7e; cnt = cnt +2) + len+= sprintf (buf + len, "reg %02x = %04x\n", + cnt, rdcodec(s->codec, cnt)); + + if (fpos >=len){ + *start = buf; + *eof =1; + return 0; + } + *start = buf + fpos; + if ((len -= fpos) > length) + return length; + *eof =1; + return len; + +} +#endif /* VRC5477_AC97_DEBUG */ + +/* --------------------------------------------------------------------- */ + +/* maximum number of devices; only used for command line params */ +#define NR_DEVICE 5 + +static unsigned int devindex; + +MODULE_AUTHOR("Monta Vista Software, jsun@mvista.com or jsun@junsun.net"); +MODULE_DESCRIPTION("NEC Vrc5477 audio (AC97) Driver"); +MODULE_LICENSE("GPL"); + +static int __devinit vrc5477_ac97_probe(struct pci_dev *pcidev, + const struct pci_device_id *pciid) +{ + struct vrc5477_ac97_state *s; +#ifdef VRC5477_AC97_DEBUG + char proc_str[80]; +#endif + + if (pcidev->irq == 0) + return -1; + + if (!(s = kmalloc(sizeof(struct vrc5477_ac97_state), GFP_KERNEL))) { + printk(KERN_ERR PFX "alloc of device struct failed\n"); + return -1; + } + memset(s, 0, sizeof(struct vrc5477_ac97_state)); + + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac.wait); + init_waitqueue_head(&s->open_wait); + init_MUTEX(&s->open_sem); + spin_lock_init(&s->lock); + + s->dev = pcidev; + s->io = pci_resource_start(pcidev, 0); + s->irq = pcidev->irq; + + s->codec = ac97_alloc_codec(); + + s->codec->private_data = s; + s->codec->id = 0; + s->codec->codec_read = rdcodec; + s->codec->codec_write = wrcodec; + s->codec->codec_wait = waitcodec; + + /* setting some other default values such as + * adcChannels, adcRate is done in open() so that + * no persistent state across file opens. + */ + + /* test if get response from ac97, if not return */ + if (ac97_codec_not_present(s->codec)) { + printk(KERN_ERR PFX "no ac97 codec\n"); + goto err_region; + + } + + /* test if get response from ac97, if not return */ + if (ac97_codec_not_present(&(s->codec))) { + printk(KERN_ERR PFX "no ac97 codec\n"); + goto err_region; + + } + + if (!request_region(s->io, pci_resource_len(pcidev,0), + VRC5477_AC97_MODULE_NAME)) { + printk(KERN_ERR PFX "io ports %#lx->%#lx in use\n", + s->io, s->io + pci_resource_len(pcidev,0)-1); + goto err_region; + } + if (request_irq(s->irq, vrc5477_ac97_interrupt, SA_INTERRUPT, + VRC5477_AC97_MODULE_NAME, s)) { + printk(KERN_ERR PFX "irq %u in use\n", s->irq); + goto err_irq; + } + + printk(KERN_INFO PFX "IO at %#lx, IRQ %d\n", s->io, s->irq); + + /* register devices */ + if ((s->dev_audio = register_sound_dsp(&vrc5477_ac97_audio_fops, -1)) < 0) + goto err_dev1; + if ((s->codec->dev_mixer = + register_sound_mixer(&vrc5477_ac97_mixer_fops, -1)) < 0) + goto err_dev2; + +#ifdef VRC5477_AC97_DEBUG + /* initialize the debug proc device */ + s->ps = create_proc_read_entry(VRC5477_AC97_MODULE_NAME, 0, NULL, + proc_vrc5477_ac97_dump, NULL); +#endif /* VRC5477_AC97_DEBUG */ + + /* enable pci io and bus mastering */ + if (pci_enable_device(pcidev)) + goto err_dev3; + pci_set_master(pcidev); + + /* cold reset the AC97 */ + outl(VRC5477_ACLINK_CTRL_RST_ON | VRC5477_ACLINK_CTRL_RST_TIME, + s->io + VRC5477_ACLINK_CTRL); + while (inl(s->io + VRC5477_ACLINK_CTRL) & VRC5477_ACLINK_CTRL_RST_ON); + + /* codec init */ + if (!ac97_probe_codec(s->codec)) + goto err_dev3; + +#ifdef VRC5477_AC97_DEBUG + sprintf(proc_str, "driver/%s/%d/ac97", + VRC5477_AC97_MODULE_NAME, s->codec->id); + s->ac97_ps = create_proc_read_entry (proc_str, 0, NULL, + ac97_read_proc, s->codec); + /* TODO : why this proc file does not show up? */ +#endif + + /* Try to enable variable rate audio mode. */ + wrcodec(s->codec, AC97_EXTENDED_STATUS, + rdcodec(s->codec, AC97_EXTENDED_STATUS) | AC97_EXTSTAT_VRA); + /* Did we enable it? */ + if(rdcodec(s->codec, AC97_EXTENDED_STATUS) & AC97_EXTSTAT_VRA) + s->extended_status |= AC97_EXTSTAT_VRA; + else { + s->dacRate = 48000; + printk(KERN_INFO PFX "VRA mode not enabled; rate fixed at %d.", + s->dacRate); + } + + /* let us get the default volumne louder */ + wrcodec(s->codec, 0x2, 0x1010); /* master volume, middle */ + wrcodec(s->codec, 0xc, 0x10); /* phone volume, middle */ + // wrcodec(s->codec, 0xe, 0x10); /* misc volume, middle */ + wrcodec(s->codec, 0x10, 0x8000); /* line-in 2 line-out disable */ + wrcodec(s->codec, 0x18, 0x0707); /* PCM out (line out) middle */ + + + /* by default we select line in the input */ + wrcodec(s->codec, 0x1a, 0x0404); + wrcodec(s->codec, 0x1c, 0x0f0f); + wrcodec(s->codec, 0x1e, 0x07); + + /* enable the master interrupt but disable all others */ + outl(VRC5477_INT_MASK_NMASK, s->io + VRC5477_INT_MASK); + + /* store it in the driver field */ + pci_set_drvdata(pcidev, s); + pcidev->dma_mask = 0xffffffff; + /* put it into driver list */ + list_add_tail(&s->devs, &devs); + /* increment devindex */ + if (devindex < NR_DEVICE-1) + devindex++; + return 0; + + err_dev3: + unregister_sound_mixer(s->codec->dev_mixer); + err_dev2: + unregister_sound_dsp(s->dev_audio); + err_dev1: + printk(KERN_ERR PFX "cannot register misc device\n"); + free_irq(s->irq, s); + err_irq: + release_region(s->io, pci_resource_len(pcidev,0)); + err_region: + ac97_release_codec(codec); + kfree(s); + return -1; +} + +static void __devexit vrc5477_ac97_remove(struct pci_dev *dev) +{ + struct vrc5477_ac97_state *s = pci_get_drvdata(dev); + + if (!s) + return; + list_del(&s->devs); + +#ifdef VRC5477_AC97_DEBUG + if (s->ps) + remove_proc_entry(VRC5477_AC97_MODULE_NAME, NULL); +#endif /* VRC5477_AC97_DEBUG */ + + synchronize_irq(); + free_irq(s->irq, s); + release_region(s->io, pci_resource_len(dev,0)); + unregister_sound_dsp(s->dev_audio); + unregister_sound_mixer(s->codec->dev_mixer); + ac97_release_codec(s->codec); + kfree(s); + pci_set_drvdata(dev, NULL); +} + + +static struct pci_device_id id_table[] = { + { PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_VRC5477_AC97, + PCI_ANY_ID, PCI_ANY_ID, 0, 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, id_table); + +static struct pci_driver vrc5477_ac97_driver = { + .name = VRC5477_AC97_MODULE_NAME, + .id_table = id_table, + .probe = vrc5477_ac97_probe, + .remove = __devexit_p(vrc5477_ac97_remove) +}; + +static int __init init_vrc5477_ac97(void) +{ + printk("Vrc5477 AC97 driver: version v0.2 time " __TIME__ " " __DATE__ " by Jun Sun\n"); + return pci_module_init(&vrc5477_ac97_driver); +} + +static void __exit cleanup_vrc5477_ac97(void) +{ + printk(KERN_INFO PFX "unloading\n"); + pci_unregister_driver(&vrc5477_ac97_driver); +} + +module_init(init_vrc5477_ac97); +module_exit(cleanup_vrc5477_ac97); + diff --git a/sound/oss/nm256.h b/sound/oss/nm256.h new file mode 100644 index 000000000000..eae7d99d6826 --- /dev/null +++ b/sound/oss/nm256.h @@ -0,0 +1,295 @@ +#ifndef _NM256_H_ +#define _NM256_H_ + +#include +#include + +#include "ac97.h" + +/* The revisions that we currently handle. */ +enum nm256rev { + REV_NM256AV, REV_NM256ZX +}; + +/* Per-card structure. */ +struct nm256_info +{ + /* Magic number used to verify that this struct is valid. */ +#define NM_MAGIC_SIG 0x55aa00ff + int magsig; + + /* Revision number */ + enum nm256rev rev; + + struct ac97_hwint mdev; + + /* Our audio device numbers. */ + int dev[2]; + + /* The # of times each device has been opened. (Should only be + 0 or 1). */ + int opencnt[2]; + + /* We use two devices, because we can do simultaneous play and record. + This keeps track of which device is being used for what purpose; + these are the actual device numbers. */ + int dev_for_play; + int dev_for_record; + + spinlock_t lock; + + /* The mixer device. */ + int mixer_oss_dev; + + /* + * Can only be opened once for each operation. These aren't set + * until an actual I/O operation is performed; this allows one + * device to be open for read/write without inhibiting I/O to + * the other device. + */ + int is_open_play; + int is_open_record; + + /* Non-zero if we're currently playing a sample. */ + int playing; + /* Ditto for recording a sample. */ + int recording; + + /* The two memory ports. */ + struct nm256_ports { + /* Physical address of the port. */ + u32 physaddr; + /* Our mapped-in pointer. */ + char __iomem *ptr; + /* PTR's offset within the physical port. */ + u32 start_offset; + /* And the offset of the end of the buffer. */ + u32 end_offset; + } port[2]; + + /* The following are offsets within memory port 1. */ + u32 coeffBuf; + u32 allCoeffBuf; + + /* Record and playback buffers. */ + u32 abuf1, abuf2; + + /* Offset of the AC97 mixer in memory port 2. */ + u32 mixer; + + /* Offset of the mixer status register in memory port 2. */ + u32 mixer_status_offset; + + /* Non-zero if we have written initial values to the mixer. */ + u8 mixer_values_init; + + /* + * Status mask bit; (*mixer_status_loc & mixer_status_mask) == 0 means + * it's ready. + */ + u16 mixer_status_mask; + + /* The sizes of the playback and record ring buffers. */ + u32 playbackBufferSize; + u32 recordBufferSize; + + /* Are the coefficient values in the memory cache current? */ + u8 coeffsCurrent; + + /* For writes, the amount we last wrote. */ + u32 requested_amt; + /* The start of the block currently playing. */ + u32 curPlayPos; + + /* The amount of data we were requested to record. */ + u32 requestedRecAmt; + /* The offset of the currently-recording block. */ + u32 curRecPos; + /* The destination buffer. */ + char *recBuf; + + /* Our IRQ number. */ + int irq; + + /* A flag indicating how many times we've grabbed the IRQ. */ + int has_irq; + + /* The card interrupt service routine. */ + irqreturn_t (*introutine) (int, void *, struct pt_regs *); + + /* Current audio config, cached. */ + struct sinfo { + u32 samplerate; + u8 bits; + u8 stereo; + } sinfo[2]; /* goes with each device */ + + /* The cards are stored in a chain; this is the next card. */ + struct nm256_info *next_card; +}; + +/* Debug flag--bigger numbers mean more output. */ +extern int nm256_debug; + +/* The BIOS signature. */ +#define NM_SIGNATURE 0x4e4d0000 +/* Signature mask. */ +#define NM_SIG_MASK 0xffff0000 + +/* Size of the second memory area. */ +#define NM_PORT2_SIZE 4096 + +/* The base offset of the mixer in the second memory area. */ +#define NM_MIXER_OFFSET 0x600 + +/* The maximum size of a coefficient entry. */ +#define NM_MAX_COEFFICIENT 0x5000 + +/* The interrupt register. */ +#define NM_INT_REG 0xa04 +/* And its bits. */ +#define NM_PLAYBACK_INT 0x40 +#define NM_RECORD_INT 0x100 +#define NM_MISC_INT_1 0x4000 +#define NM_MISC_INT_2 0x1 +#define NM_ACK_INT(CARD, X) nm256_writePort16((CARD), 2, NM_INT_REG, (X) << 1) + +/* The AV's "mixer ready" status bit and location. */ +#define NM_MIXER_STATUS_OFFSET 0xa04 +#define NM_MIXER_READY_MASK 0x0800 +#define NM_MIXER_PRESENCE 0xa06 +#define NM_PRESENCE_MASK 0x0050 +#define NM_PRESENCE_VALUE 0x0040 + +/* + * For the ZX. It uses the same interrupt register, but it holds 32 + * bits instead of 16. + */ +#define NM2_PLAYBACK_INT 0x10000 +#define NM2_RECORD_INT 0x80000 +#define NM2_MISC_INT_1 0x8 +#define NM2_MISC_INT_2 0x2 +#define NM2_ACK_INT(CARD, X) nm256_writePort32((CARD), 2, NM_INT_REG, (X)) + +/* The ZX's "mixer ready" status bit and location. */ +#define NM2_MIXER_STATUS_OFFSET 0xa06 +#define NM2_MIXER_READY_MASK 0x0800 + +/* The playback registers start from here. */ +#define NM_PLAYBACK_REG_OFFSET 0x0 +/* The record registers start from here. */ +#define NM_RECORD_REG_OFFSET 0x200 + +/* The rate register is located 2 bytes from the start of the register area. */ +#define NM_RATE_REG_OFFSET 2 + +/* Mono/stereo flag, number of bits on playback, and rate mask. */ +#define NM_RATE_STEREO 1 +#define NM_RATE_BITS_16 2 +#define NM_RATE_MASK 0xf0 + +/* Playback enable register. */ +#define NM_PLAYBACK_ENABLE_REG (NM_PLAYBACK_REG_OFFSET + 0x1) +#define NM_PLAYBACK_ENABLE_FLAG 1 +#define NM_PLAYBACK_ONESHOT 2 +#define NM_PLAYBACK_FREERUN 4 + +/* Mutes the audio output. */ +#define NM_AUDIO_MUTE_REG (NM_PLAYBACK_REG_OFFSET + 0x18) +#define NM_AUDIO_MUTE_LEFT 0x8000 +#define NM_AUDIO_MUTE_RIGHT 0x0080 + +/* Recording enable register. */ +#define NM_RECORD_ENABLE_REG (NM_RECORD_REG_OFFSET + 0) +#define NM_RECORD_ENABLE_FLAG 1 +#define NM_RECORD_FREERUN 2 + +#define NM_RBUFFER_START (NM_RECORD_REG_OFFSET + 0x4) +#define NM_RBUFFER_END (NM_RECORD_REG_OFFSET + 0x10) +#define NM_RBUFFER_WMARK (NM_RECORD_REG_OFFSET + 0xc) +#define NM_RBUFFER_CURRP (NM_RECORD_REG_OFFSET + 0x8) + +#define NM_PBUFFER_START (NM_PLAYBACK_REG_OFFSET + 0x4) +#define NM_PBUFFER_END (NM_PLAYBACK_REG_OFFSET + 0x14) +#define NM_PBUFFER_WMARK (NM_PLAYBACK_REG_OFFSET + 0xc) +#define NM_PBUFFER_CURRP (NM_PLAYBACK_REG_OFFSET + 0x8) + +/* A few trivial routines to make it easier to work with the registers + on the chip. */ + +/* This is a common code portion used to fix up the port offsets. */ +#define NM_FIX_PORT \ + if (port < 1 || port > 2 || card == NULL) \ + return -1; \ +\ + if (offset < card->port[port - 1].start_offset \ + || offset >= card->port[port - 1].end_offset) { \ + printk (KERN_ERR "Bad access: port %d, offset 0x%x\n", port, offset); \ + return -1; \ + } \ + offset -= card->port[port - 1].start_offset; + +#define DEFwritePortX(X, func) \ +static inline int nm256_writePort##X (struct nm256_info *card,\ + int port, int offset, int value)\ +{\ + u##X __iomem *addr;\ +\ + if (nm256_debug > 1)\ + printk (KERN_DEBUG "Writing 0x%x to %d:0x%x\n", value, port, offset);\ +\ + NM_FIX_PORT;\ +\ + addr = (u##X __iomem *)(card->port[port - 1].ptr + offset);\ + func (value, addr);\ + return 0;\ +} + +DEFwritePortX (8, writeb) +DEFwritePortX (16, writew) +DEFwritePortX (32, writel) + +#define DEFreadPortX(X, func) \ +static inline u##X nm256_readPort##X (struct nm256_info *card,\ + int port, int offset)\ +{\ + u##X __iomem *addr;\ +\ + NM_FIX_PORT\ +\ + addr = (u##X __iomem *)(card->port[port - 1].ptr + offset);\ + return func(addr);\ +} + +DEFreadPortX (8, readb) +DEFreadPortX (16, readw) +DEFreadPortX (32, readl) + +static inline int +nm256_writeBuffer8 (struct nm256_info *card, u8 *src, int port, int offset, + int amt) +{ + NM_FIX_PORT; + memcpy_toio (card->port[port - 1].ptr + offset, src, amt); + return 0; +} + +static inline int +nm256_readBuffer8 (struct nm256_info *card, u8 *dst, int port, int offset, + int amt) +{ + NM_FIX_PORT; + memcpy_fromio (dst, card->port[port - 1].ptr + offset, amt); + return 0; +} + +/* Returns a non-zero value if we should use the coefficient cache. */ +extern int nm256_cachedCoefficients (struct nm256_info *card); + +#endif + +/* + * Local variables: + * c-basic-offset: 4 + * End: + */ diff --git a/sound/oss/nm256_audio.c b/sound/oss/nm256_audio.c new file mode 100644 index 000000000000..f9166e135192 --- /dev/null +++ b/sound/oss/nm256_audio.c @@ -0,0 +1,1707 @@ +/* + * Audio driver for the NeoMagic 256AV and 256ZX chipsets in native + * mode, with AC97 mixer support. + * + * Overall design and parts of this code stolen from vidc_*.c and + * skeleton.c. + * + * Yeah, there are a lot of magic constants in here. You tell ME what + * they are. I just get this stuff psychically, remember? + * + * This driver was written by someone who wishes to remain anonymous. + * It is in the public domain, so share and enjoy. Try to make a profit + * off of it; go on, I dare you. + * + * Changes: + * 11-10-2000 Bartlomiej Zolnierkiewicz + * Added some __init + * 19-04-2001 Marcus Meissner + * Ported to 2.4 PCI API. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sound_config.h" +#include "nm256.h" +#include "nm256_coeff.h" + +int nm256_debug; +static int force_load; + +/* + * The size of the playback reserve. When the playback buffer has less + * than NM256_PLAY_WMARK_SIZE bytes to output, we request a new + * buffer. + */ +#define NM256_PLAY_WMARK_SIZE 512 + +static struct audio_driver nm256_audio_driver; + +static int nm256_grabInterrupt (struct nm256_info *card); +static int nm256_releaseInterrupt (struct nm256_info *card); +static irqreturn_t nm256_interrupt (int irq, void *dev_id, struct pt_regs *dummy); +static irqreturn_t nm256_interrupt_zx (int irq, void *dev_id, struct pt_regs *dummy); +static int handle_pm_event (struct pm_dev *dev, pm_request_t rqst, void *data); + +/* These belong in linux/pci.h. */ +#define PCI_DEVICE_ID_NEOMAGIC_NM256AV_AUDIO 0x8005 +#define PCI_DEVICE_ID_NEOMAGIC_NM256ZX_AUDIO 0x8006 +#define PCI_DEVICE_ID_NEOMAGIC_NM256XL_PLUS_AUDIO 0x8016 + +/* List of cards. */ +static struct nm256_info *nmcard_list; + +/* Release the mapped-in memory for CARD. */ +static void +nm256_release_ports (struct nm256_info *card) +{ + int x; + + for (x = 0; x < 2; x++) { + if (card->port[x].ptr != NULL) { + iounmap (card->port[x].ptr); + card->port[x].ptr = NULL; + } + } +} + +/* + * Map in the memory ports for CARD, if they aren't already mapped in + * and have been configured. If successful, a zero value is returned; + * otherwise any previously mapped-in areas are released and a non-zero + * value is returned. + * + * This is invoked twice, once for each port. Ideally it would only be + * called once, but we now need to map in the second port in order to + * check how much memory the card has on the 256ZX. + */ +static int +nm256_remap_ports (struct nm256_info *card) +{ + int x; + + for (x = 0; x < 2; x++) { + if (card->port[x].ptr == NULL && card->port[x].end_offset > 0) { + u32 physaddr + = card->port[x].physaddr + card->port[x].start_offset; + u32 size + = card->port[x].end_offset - card->port[x].start_offset; + + card->port[x].ptr = ioremap_nocache (physaddr, size); + + if (card->port[x].ptr == NULL) { + printk (KERN_ERR "NM256: Unable to remap port %d\n", x + 1); + nm256_release_ports (card); + return -1; + } + } + } + return 0; +} + +/* Locate the card in our list. */ +static struct nm256_info * +nm256_find_card (int dev) +{ + struct nm256_info *card; + + for (card = nmcard_list; card != NULL; card = card->next_card) + if (card->dev[0] == dev || card->dev[1] == dev) + return card; + + return NULL; +} + +/* + * Ditto, but find the card struct corresponding to the mixer device DEV + * instead. + */ +static struct nm256_info * +nm256_find_card_for_mixer (int dev) +{ + struct nm256_info *card; + + for (card = nmcard_list; card != NULL; card = card->next_card) + if (card->mixer_oss_dev == dev) + return card; + + return NULL; +} + +static int usecache; +static int buffertop; + +/* Check to see if we're using the bank of cached coefficients. */ +int +nm256_cachedCoefficients (struct nm256_info *card) +{ + return usecache; +} + +/* The actual rates supported by the card. */ +static int samplerates[9] = { + 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000, 99999999 +}; + +/* + * Set the card samplerate, word size and stereo mode to correspond to + * the settings in the CARD struct for the specified device in DEV. + * We keep two separate sets of information, one for each device; the + * hardware is not actually configured until a read or write is + * attempted. + */ + +static int +nm256_setInfo (int dev, struct nm256_info *card) +{ + int x; + int w; + int targetrate; + + if (card->dev[0] == dev) + w = 0; + else if (card->dev[1] == dev) + w = 1; + else + return -ENODEV; + + targetrate = card->sinfo[w].samplerate; + + if ((card->sinfo[w].bits != 8 && card->sinfo[w].bits != 16) + || targetrate < samplerates[0] + || targetrate > samplerates[7]) + return -EINVAL; + + for (x = 0; x < 8; x++) + if (targetrate < ((samplerates[x] + samplerates[x + 1]) / 2)) + break; + + if (x < 8) { + u8 ratebits = ((x << 4) & NM_RATE_MASK); + if (card->sinfo[w].bits == 16) + ratebits |= NM_RATE_BITS_16; + if (card->sinfo[w].stereo) + ratebits |= NM_RATE_STEREO; + + card->sinfo[w].samplerate = samplerates[x]; + + + if (card->dev_for_play == dev && card->playing) { + if (nm256_debug) + printk (KERN_DEBUG "Setting play ratebits to 0x%x\n", + ratebits); + nm256_loadCoefficient (card, 0, x); + nm256_writePort8 (card, 2, + NM_PLAYBACK_REG_OFFSET + NM_RATE_REG_OFFSET, + ratebits); + } + + if (card->dev_for_record == dev && card->recording) { + if (nm256_debug) + printk (KERN_DEBUG "Setting record ratebits to 0x%x\n", + ratebits); + nm256_loadCoefficient (card, 1, x); + nm256_writePort8 (card, 2, + NM_RECORD_REG_OFFSET + NM_RATE_REG_OFFSET, + ratebits); + } + return 0; + } + else + return -EINVAL; +} + +/* Start the play process going. */ +static void +startPlay (struct nm256_info *card) +{ + if (! card->playing) { + card->playing = 1; + if (nm256_grabInterrupt (card) == 0) { + nm256_setInfo (card->dev_for_play, card); + + /* Enable playback engine and interrupts. */ + nm256_writePort8 (card, 2, NM_PLAYBACK_ENABLE_REG, + NM_PLAYBACK_ENABLE_FLAG | NM_PLAYBACK_FREERUN); + + /* Enable both channels. */ + nm256_writePort16 (card, 2, NM_AUDIO_MUTE_REG, 0x0); + } + } +} + +/* + * Request one chunk of AMT bytes from the recording device. When the + * operation is complete, the data will be copied into BUFFER and the + * function DMAbuf_inputintr will be invoked. + */ + +static void +nm256_startRecording (struct nm256_info *card, char *buffer, u32 amt) +{ + u32 endpos; + int enableEngine = 0; + u32 ringsize = card->recordBufferSize; + unsigned long flags; + + if (amt > (ringsize / 2)) { + /* + * Of course this won't actually work right, because the + * caller is going to assume we will give what we got asked + * for. + */ + printk (KERN_ERR "NM256: Read request too large: %d\n", amt); + amt = ringsize / 2; + } + + if (amt < 8) { + printk (KERN_ERR "NM256: Read request too small; %d\n", amt); + return; + } + + spin_lock_irqsave(&card->lock,flags); + /* + * If we're not currently recording, set up the start and end registers + * for the recording engine. + */ + if (! card->recording) { + card->recording = 1; + if (nm256_grabInterrupt (card) == 0) { + card->curRecPos = 0; + nm256_setInfo (card->dev_for_record, card); + nm256_writePort32 (card, 2, NM_RBUFFER_START, card->abuf2); + nm256_writePort32 (card, 2, NM_RBUFFER_END, + card->abuf2 + ringsize); + + nm256_writePort32 (card, 2, NM_RBUFFER_CURRP, + card->abuf2 + card->curRecPos); + enableEngine = 1; + } + else { + /* Not sure what else to do here. */ + spin_unlock_irqrestore(&card->lock,flags); + return; + } + } + + /* + * If we happen to go past the end of the buffer a bit (due to a + * delayed interrupt) it's OK. So might as well set the watermark + * right at the end of the data we want. + */ + endpos = card->abuf2 + ((card->curRecPos + amt) % ringsize); + + card->recBuf = buffer; + card->requestedRecAmt = amt; + nm256_writePort32 (card, 2, NM_RBUFFER_WMARK, endpos); + /* Enable recording engine and interrupts. */ + if (enableEngine) + nm256_writePort8 (card, 2, NM_RECORD_ENABLE_REG, + NM_RECORD_ENABLE_FLAG | NM_RECORD_FREERUN); + + spin_unlock_irqrestore(&card->lock,flags); +} + +/* Stop the play engine. */ +static void +stopPlay (struct nm256_info *card) +{ + /* Shut off sound from both channels. */ + nm256_writePort16 (card, 2, NM_AUDIO_MUTE_REG, + NM_AUDIO_MUTE_LEFT | NM_AUDIO_MUTE_RIGHT); + /* Disable play engine. */ + nm256_writePort8 (card, 2, NM_PLAYBACK_ENABLE_REG, 0); + if (card->playing) { + nm256_releaseInterrupt (card); + + /* Reset the relevant state bits. */ + card->playing = 0; + card->curPlayPos = 0; + } +} + +/* Stop recording. */ +static void +stopRecord (struct nm256_info *card) +{ + /* Disable recording engine. */ + nm256_writePort8 (card, 2, NM_RECORD_ENABLE_REG, 0); + + if (card->recording) { + nm256_releaseInterrupt (card); + + card->recording = 0; + card->curRecPos = 0; + } +} + +/* + * Ring buffers, man. That's where the hip-hop, wild-n-wooly action's at. + * 1972? (Well, I suppose it was cheep-n-easy to implement.) + * + * Write AMT bytes of BUFFER to the playback ring buffer, and start the + * playback engine running. It will only accept up to 1/2 of the total + * size of the ring buffer. No check is made that we're about to overwrite + * the currently-playing sample. + */ + +static void +nm256_write_block (struct nm256_info *card, char *buffer, u32 amt) +{ + u32 ringsize = card->playbackBufferSize; + u32 endstop; + unsigned long flags; + + if (amt > (ringsize / 2)) { + printk (KERN_ERR "NM256: Write request too large: %d\n", amt); + amt = (ringsize / 2); + } + + if (amt < NM256_PLAY_WMARK_SIZE) { + printk (KERN_ERR "NM256: Write request too small: %d\n", amt); + return; + } + + card->curPlayPos %= ringsize; + + card->requested_amt = amt; + + spin_lock_irqsave(&card->lock,flags); + + if ((card->curPlayPos + amt) >= ringsize) { + u32 rem = ringsize - card->curPlayPos; + + nm256_writeBuffer8 (card, buffer, 1, + card->abuf1 + card->curPlayPos, + rem); + if (amt > rem) + nm256_writeBuffer8 (card, buffer + rem, 1, card->abuf1, + amt - rem); + } + else + nm256_writeBuffer8 (card, buffer, 1, + card->abuf1 + card->curPlayPos, + amt); + + /* + * Setup the start-n-stop-n-limit registers, and start that engine + * goin'. + * + * Normally we just let it wrap around to avoid the click-click + * action scene. + */ + if (! card->playing) { + /* The PBUFFER_END register in this case points to one sample + before the end of the buffer. */ + int w = (card->dev_for_play == card->dev[0] ? 0 : 1); + int sampsize = (card->sinfo[w].bits == 16 ? 2 : 1); + + if (card->sinfo[w].stereo) + sampsize *= 2; + + /* Need to set the not-normally-changing-registers up. */ + nm256_writePort32 (card, 2, NM_PBUFFER_START, + card->abuf1 + card->curPlayPos); + nm256_writePort32 (card, 2, NM_PBUFFER_END, + card->abuf1 + ringsize - sampsize); + nm256_writePort32 (card, 2, NM_PBUFFER_CURRP, + card->abuf1 + card->curPlayPos); + } + endstop = (card->curPlayPos + amt - NM256_PLAY_WMARK_SIZE) % ringsize; + nm256_writePort32 (card, 2, NM_PBUFFER_WMARK, card->abuf1 + endstop); + + if (! card->playing) + startPlay (card); + + spin_unlock_irqrestore(&card->lock,flags); +} + +/* We just got a card playback interrupt; process it. */ +static void +nm256_get_new_block (struct nm256_info *card) +{ + /* Check to see how much got played so far. */ + u32 amt = nm256_readPort32 (card, 2, NM_PBUFFER_CURRP) - card->abuf1; + + if (amt >= card->playbackBufferSize) { + printk (KERN_ERR "NM256: Sound playback pointer invalid!\n"); + amt = 0; + } + + if (amt < card->curPlayPos) + amt = (card->playbackBufferSize - card->curPlayPos) + amt; + else + amt -= card->curPlayPos; + + if (card->requested_amt > (amt + NM256_PLAY_WMARK_SIZE)) { + u32 endstop = + card->curPlayPos + card->requested_amt - NM256_PLAY_WMARK_SIZE; + nm256_writePort32 (card, 2, NM_PBUFFER_WMARK, card->abuf1 + endstop); + } + else { + card->curPlayPos += card->requested_amt; + /* Get a new block to write. This will eventually invoke + nm256_write_block () or stopPlay (). */ + DMAbuf_outputintr (card->dev_for_play, 1); + } +} + +/* + * Read the last-recorded block from the ring buffer, copy it into the + * saved buffer pointer, and invoke DMAuf_inputintr() with the recording + * device. + */ + +static void +nm256_read_block (struct nm256_info *card) +{ + /* Grab the current position of the recording pointer. */ + u32 currptr = nm256_readPort32 (card, 2, NM_RBUFFER_CURRP) - card->abuf2; + u32 amtToRead = card->requestedRecAmt; + u32 ringsize = card->recordBufferSize; + + if (currptr >= card->recordBufferSize) { + printk (KERN_ERR "NM256: Sound buffer record pointer invalid!\n"); + currptr = 0; + } + + /* + * This test is probably redundant; we shouldn't be here unless + * it's true. + */ + if (card->recording) { + /* If we wrapped around, copy everything from the start of our + recording buffer to the end of the buffer. */ + if (currptr < card->curRecPos) { + u32 amt = min (ringsize - card->curRecPos, amtToRead); + + nm256_readBuffer8 (card, card->recBuf, 1, + card->abuf2 + card->curRecPos, + amt); + amtToRead -= amt; + card->curRecPos += amt; + card->recBuf += amt; + if (card->curRecPos == ringsize) + card->curRecPos = 0; + } + + if ((card->curRecPos < currptr) && (amtToRead > 0)) { + u32 amt = min (currptr - card->curRecPos, amtToRead); + nm256_readBuffer8 (card, card->recBuf, 1, + card->abuf2 + card->curRecPos, amt); + card->curRecPos = ((card->curRecPos + amt) % ringsize); + } + card->recBuf = NULL; + card->requestedRecAmt = 0; + DMAbuf_inputintr (card->dev_for_record); + } +} + +/* + * Initialize the hardware. + */ +static void +nm256_initHw (struct nm256_info *card) +{ + /* Reset everything. */ + nm256_writePort8 (card, 2, 0x0, 0x11); + nm256_writePort16 (card, 2, 0x214, 0); + + stopRecord (card); + stopPlay (card); +} + +/* + * Handle a potential interrupt for the device referred to by DEV_ID. + * + * I don't like the cut-n-paste job here either between the two routines, + * but there are sufficient differences between the two interrupt handlers + * that parameterizing it isn't all that great either. (Could use a macro, + * I suppose...yucky bleah.) + */ + +static irqreturn_t +nm256_interrupt (int irq, void *dev_id, struct pt_regs *dummy) +{ + struct nm256_info *card = (struct nm256_info *)dev_id; + u16 status; + static int badintrcount; + int handled = 0; + + if ((card == NULL) || (card->magsig != NM_MAGIC_SIG)) { + printk (KERN_ERR "NM256: Bad card pointer\n"); + return IRQ_NONE; + } + + status = nm256_readPort16 (card, 2, NM_INT_REG); + + /* Not ours. */ + if (status == 0) { + if (badintrcount++ > 1000) { + /* + * I'm not sure if the best thing is to stop the card from + * playing or just release the interrupt (after all, we're in + * a bad situation, so doing fancy stuff may not be such a good + * idea). + * + * I worry about the card engine continuing to play noise + * over and over, however--that could become a very + * obnoxious problem. And we know that when this usually + * happens things are fairly safe, it just means the user's + * inserted a PCMCIA card and someone's spamming us with IRQ 9s. + */ + + handled = 1; + if (card->playing) + stopPlay (card); + if (card->recording) + stopRecord (card); + badintrcount = 0; + } + return IRQ_RETVAL(handled); + } + + badintrcount = 0; + + /* Rather boring; check for individual interrupts and process them. */ + + if (status & NM_PLAYBACK_INT) { + handled = 1; + status &= ~NM_PLAYBACK_INT; + NM_ACK_INT (card, NM_PLAYBACK_INT); + + if (card->playing) + nm256_get_new_block (card); + } + + if (status & NM_RECORD_INT) { + handled = 1; + status &= ~NM_RECORD_INT; + NM_ACK_INT (card, NM_RECORD_INT); + + if (card->recording) + nm256_read_block (card); + } + + if (status & NM_MISC_INT_1) { + u8 cbyte; + + handled = 1; + status &= ~NM_MISC_INT_1; + printk (KERN_ERR "NM256: Got misc interrupt #1\n"); + NM_ACK_INT (card, NM_MISC_INT_1); + nm256_writePort16 (card, 2, NM_INT_REG, 0x8000); + cbyte = nm256_readPort8 (card, 2, 0x400); + nm256_writePort8 (card, 2, 0x400, cbyte | 2); + } + + if (status & NM_MISC_INT_2) { + u8 cbyte; + + handled = 1; + status &= ~NM_MISC_INT_2; + printk (KERN_ERR "NM256: Got misc interrupt #2\n"); + NM_ACK_INT (card, NM_MISC_INT_2); + cbyte = nm256_readPort8 (card, 2, 0x400); + nm256_writePort8 (card, 2, 0x400, cbyte & ~2); + } + + /* Unknown interrupt. */ + if (status) { + handled = 1; + printk (KERN_ERR "NM256: Fire in the hole! Unknown status 0x%x\n", + status); + /* Pray. */ + NM_ACK_INT (card, status); + } + return IRQ_RETVAL(handled); +} + +/* + * Handle a potential interrupt for the device referred to by DEV_ID. + * This handler is for the 256ZX, and is very similar to the non-ZX + * routine. + */ + +static irqreturn_t +nm256_interrupt_zx (int irq, void *dev_id, struct pt_regs *dummy) +{ + struct nm256_info *card = (struct nm256_info *)dev_id; + u32 status; + static int badintrcount; + int handled = 0; + + if ((card == NULL) || (card->magsig != NM_MAGIC_SIG)) { + printk (KERN_ERR "NM256: Bad card pointer\n"); + return IRQ_NONE; + } + + status = nm256_readPort32 (card, 2, NM_INT_REG); + + /* Not ours. */ + if (status == 0) { + if (badintrcount++ > 1000) { + printk (KERN_ERR "NM256: Releasing interrupt, over 1000 invalid interrupts\n"); + /* + * I'm not sure if the best thing is to stop the card from + * playing or just release the interrupt (after all, we're in + * a bad situation, so doing fancy stuff may not be such a good + * idea). + * + * I worry about the card engine continuing to play noise + * over and over, however--that could become a very + * obnoxious problem. And we know that when this usually + * happens things are fairly safe, it just means the user's + * inserted a PCMCIA card and someone's spamming us with + * IRQ 9s. + */ + + handled = 1; + if (card->playing) + stopPlay (card); + if (card->recording) + stopRecord (card); + badintrcount = 0; + } + return IRQ_RETVAL(handled); + } + + badintrcount = 0; + + /* Rather boring; check for individual interrupts and process them. */ + + if (status & NM2_PLAYBACK_INT) { + handled = 1; + status &= ~NM2_PLAYBACK_INT; + NM2_ACK_INT (card, NM2_PLAYBACK_INT); + + if (card->playing) + nm256_get_new_block (card); + } + + if (status & NM2_RECORD_INT) { + handled = 1; + status &= ~NM2_RECORD_INT; + NM2_ACK_INT (card, NM2_RECORD_INT); + + if (card->recording) + nm256_read_block (card); + } + + if (status & NM2_MISC_INT_1) { + u8 cbyte; + + handled = 1; + status &= ~NM2_MISC_INT_1; + printk (KERN_ERR "NM256: Got misc interrupt #1\n"); + NM2_ACK_INT (card, NM2_MISC_INT_1); + cbyte = nm256_readPort8 (card, 2, 0x400); + nm256_writePort8 (card, 2, 0x400, cbyte | 2); + } + + if (status & NM2_MISC_INT_2) { + u8 cbyte; + + handled = 1; + status &= ~NM2_MISC_INT_2; + printk (KERN_ERR "NM256: Got misc interrupt #2\n"); + NM2_ACK_INT (card, NM2_MISC_INT_2); + cbyte = nm256_readPort8 (card, 2, 0x400); + nm256_writePort8 (card, 2, 0x400, cbyte & ~2); + } + + /* Unknown interrupt. */ + if (status) { + handled = 1; + printk (KERN_ERR "NM256: Fire in the hole! Unknown status 0x%x\n", + status); + /* Pray. */ + NM2_ACK_INT (card, status); + } + return IRQ_RETVAL(handled); +} + +/* + * Request our interrupt. + */ +static int +nm256_grabInterrupt (struct nm256_info *card) +{ + if (card->has_irq++ == 0) { + if (request_irq (card->irq, card->introutine, SA_SHIRQ, + "NM256_audio", card) < 0) { + printk (KERN_ERR "NM256: can't obtain IRQ %d\n", card->irq); + return -1; + } + } + return 0; +} + +/* + * Release our interrupt. + */ +static int +nm256_releaseInterrupt (struct nm256_info *card) +{ + if (card->has_irq <= 0) { + printk (KERN_ERR "nm256: too many calls to releaseInterrupt\n"); + return -1; + } + card->has_irq--; + if (card->has_irq == 0) { + free_irq (card->irq, card); + } + return 0; +} + +/* + * Waits for the mixer to become ready to be written; returns a zero value + * if it timed out. + */ + +static int +nm256_isReady (struct ac97_hwint *dev) +{ + struct nm256_info *card = (struct nm256_info *)dev->driver_private; + int t2 = 10; + u32 testaddr; + u16 testb; + int done = 0; + + if (card->magsig != NM_MAGIC_SIG) { + printk (KERN_ERR "NM256: Bad magic signature in isReady!\n"); + return 0; + } + + testaddr = card->mixer_status_offset; + testb = card->mixer_status_mask; + + /* + * Loop around waiting for the mixer to become ready. + */ + while (! done && t2-- > 0) { + if ((nm256_readPort16 (card, 2, testaddr) & testb) == 0) + done = 1; + else + udelay (100); + } + return done; +} + +/* + * Return the contents of the AC97 mixer register REG. Returns a positive + * value if successful, or a negative error code. + */ +static int +nm256_readAC97Reg (struct ac97_hwint *dev, u8 reg) +{ + struct nm256_info *card = (struct nm256_info *)dev->driver_private; + + if (card->magsig != NM_MAGIC_SIG) { + printk (KERN_ERR "NM256: Bad magic signature in readAC97Reg!\n"); + return -EINVAL; + } + + if (reg < 128) { + int res; + + nm256_isReady (dev); + res = nm256_readPort16 (card, 2, card->mixer + reg); + /* Magic delay. Bleah yucky. */ + udelay (1000); + return res; + } + else + return -EINVAL; +} + +/* + * Writes VALUE to AC97 mixer register REG. Returns 0 if successful, or + * a negative error code. + */ +static int +nm256_writeAC97Reg (struct ac97_hwint *dev, u8 reg, u16 value) +{ + unsigned long flags; + int tries = 2; + int done = 0; + u32 base; + + struct nm256_info *card = (struct nm256_info *)dev->driver_private; + + if (card->magsig != NM_MAGIC_SIG) { + printk (KERN_ERR "NM256: Bad magic signature in writeAC97Reg!\n"); + return -EINVAL; + } + + base = card->mixer; + + spin_lock_irqsave(&card->lock,flags); + + nm256_isReady (dev); + + /* Wait for the write to take, too. */ + while ((tries-- > 0) && !done) { + nm256_writePort16 (card, 2, base + reg, value); + if (nm256_isReady (dev)) { + done = 1; + break; + } + + } + + spin_unlock_irqrestore(&card->lock,flags); + udelay (1000); + + return ! done; +} + +/* + * Initial register values to be written to the AC97 mixer. + * While most of these are identical to the reset values, we do this + * so that we have most of the register contents cached--this avoids + * reading from the mixer directly (which seems to be problematic, + * probably due to ignorance). + */ +struct initialValues +{ + unsigned short port; + unsigned short value; +}; + +static struct initialValues nm256_ac97_initial_values[] = +{ + { AC97_MASTER_VOL_STEREO, 0x8000 }, + { AC97_HEADPHONE_VOL, 0x8000 }, + { AC97_MASTER_VOL_MONO, 0x0000 }, + { AC97_PCBEEP_VOL, 0x0000 }, + { AC97_PHONE_VOL, 0x0008 }, + { AC97_MIC_VOL, 0x8000 }, + { AC97_LINEIN_VOL, 0x8808 }, + { AC97_CD_VOL, 0x8808 }, + { AC97_VIDEO_VOL, 0x8808 }, + { AC97_AUX_VOL, 0x8808 }, + { AC97_PCMOUT_VOL, 0x0808 }, + { AC97_RECORD_SELECT, 0x0000 }, + { AC97_RECORD_GAIN, 0x0B0B }, + { AC97_GENERAL_PURPOSE, 0x0000 }, + { 0xffff, 0xffff } +}; + +/* Initialize the AC97 into a known state. */ +static int +nm256_resetAC97 (struct ac97_hwint *dev) +{ + struct nm256_info *card = (struct nm256_info *)dev->driver_private; + int x; + + if (card->magsig != NM_MAGIC_SIG) { + printk (KERN_ERR "NM256: Bad magic signature in resetAC97!\n"); + return -EINVAL; + } + + /* Reset the mixer. 'Tis magic! */ + nm256_writePort8 (card, 2, 0x6c0, 1); +// nm256_writePort8 (card, 2, 0x6cc, 0x87); /* This crashes Dell latitudes */ + nm256_writePort8 (card, 2, 0x6cc, 0x80); + nm256_writePort8 (card, 2, 0x6cc, 0x0); + + if (! card->mixer_values_init) { + for (x = 0; nm256_ac97_initial_values[x].port != 0xffff; x++) { + ac97_put_register (dev, + nm256_ac97_initial_values[x].port, + nm256_ac97_initial_values[x].value); + card->mixer_values_init = 1; + } + } + + return 0; +} + +/* + * We don't do anything particularly special here; it just passes the + * mixer ioctl to the AC97 driver. + */ +static int +nm256_default_mixer_ioctl (int dev, unsigned int cmd, void __user *arg) +{ + struct nm256_info *card = nm256_find_card_for_mixer (dev); + if (card != NULL) + return ac97_mixer_ioctl (&(card->mdev), cmd, arg); + else + return -ENODEV; +} + +static struct mixer_operations nm256_mixer_operations = { + .owner = THIS_MODULE, + .id = "NeoMagic", + .name = "NM256AC97Mixer", + .ioctl = nm256_default_mixer_ioctl +}; + +/* + * Default settings for the OSS mixer. These are set last, after the + * mixer is initialized. + * + * I "love" C sometimes. Got braces? + */ +static struct ac97_mixer_value_list mixer_defaults[] = { + { SOUND_MIXER_VOLUME, { { 85, 85 } } }, + { SOUND_MIXER_SPEAKER, { { 100 } } }, + { SOUND_MIXER_PCM, { { 65, 65 } } }, + { SOUND_MIXER_CD, { { 65, 65 } } }, + { -1, { { 0, 0 } } } +}; + + +/* Installs the AC97 mixer into CARD. */ +static int __init +nm256_install_mixer (struct nm256_info *card) +{ + int mixer; + + card->mdev.reset_device = nm256_resetAC97; + card->mdev.read_reg = nm256_readAC97Reg; + card->mdev.write_reg = nm256_writeAC97Reg; + card->mdev.driver_private = (void *)card; + + if (ac97_init (&(card->mdev))) + return -1; + + mixer = sound_alloc_mixerdev(); + if (num_mixers >= MAX_MIXER_DEV) { + printk ("NM256 mixer: Unable to alloc mixerdev\n"); + return -1; + } + + mixer_devs[mixer] = &nm256_mixer_operations; + card->mixer_oss_dev = mixer; + + /* Some reasonable default values. */ + ac97_set_values (&(card->mdev), mixer_defaults); + + printk(KERN_INFO "Initialized AC97 mixer\n"); + return 0; +} + +/* Perform a full reset on the hardware; this is invoked when an APM + resume event occurs. */ +static void +nm256_full_reset (struct nm256_info *card) +{ + nm256_initHw (card); + ac97_reset (&(card->mdev)); +} + +/* + * See if the signature left by the NM256 BIOS is intact; if so, we use + * the associated address as the end of our audio buffer in the video + * RAM. + */ + +static void __init +nm256_peek_for_sig (struct nm256_info *card) +{ + u32 port1offset + = card->port[0].physaddr + card->port[0].end_offset - 0x0400; + /* The signature is located 1K below the end of video RAM. */ + char __iomem *temp = ioremap_nocache (port1offset, 16); + /* Default buffer end is 5120 bytes below the top of RAM. */ + u32 default_value = card->port[0].end_offset - 0x1400; + u32 sig; + + /* Install the default value first, so we don't have to repeatedly + do it if there is a problem. */ + card->port[0].end_offset = default_value; + + if (temp == NULL) { + printk (KERN_ERR "NM256: Unable to scan for card signature in video RAM\n"); + return; + } + sig = readl (temp); + if ((sig & NM_SIG_MASK) == NM_SIGNATURE) { + u32 pointer = readl (temp + 4); + + /* + * If it's obviously invalid, don't use it (the port already has a + * suitable default value set). + */ + if (pointer != 0xffffffff) + card->port[0].end_offset = pointer; + + printk (KERN_INFO "NM256: Found card signature in video RAM: 0x%x\n", + pointer); + } + + iounmap (temp); +} + +/* + * Install a driver for the PCI device referenced by PCIDEV. + * VERSTR is a human-readable version string. + */ + +static int __devinit +nm256_install(struct pci_dev *pcidev, enum nm256rev rev, char *verstr) +{ + struct nm256_info *card; + struct pm_dev *pmdev; + int x; + + if (pci_enable_device(pcidev)) + return 0; + + card = kmalloc (sizeof (struct nm256_info), GFP_KERNEL); + if (card == NULL) { + printk (KERN_ERR "NM256: out of memory!\n"); + return 0; + } + + card->magsig = NM_MAGIC_SIG; + card->playing = 0; + card->recording = 0; + card->rev = rev; + spin_lock_init(&card->lock); + + /* Init the memory port info. */ + for (x = 0; x < 2; x++) { + card->port[x].physaddr = pci_resource_start (pcidev, x); + card->port[x].ptr = NULL; + card->port[x].start_offset = 0; + card->port[x].end_offset = 0; + } + + /* Port 2 is easy. */ + card->port[1].start_offset = 0; + card->port[1].end_offset = NM_PORT2_SIZE; + + /* Yuck. But we have to map in port 2 so we can check how much RAM the + card has. */ + if (nm256_remap_ports (card)) { + kfree (card); + return 0; + } + + /* + * The NM256 has two memory ports. The first port is nothing + * more than a chunk of video RAM, which is used as the I/O ring + * buffer. The second port has the actual juicy stuff (like the + * mixer and the playback engine control registers). + */ + + if (card->rev == REV_NM256AV) { + /* Ok, try to see if this is a non-AC97 version of the hardware. */ + int pval = nm256_readPort16 (card, 2, NM_MIXER_PRESENCE); + if ((pval & NM_PRESENCE_MASK) != NM_PRESENCE_VALUE) { + if (! force_load) { + printk (KERN_ERR "NM256: This doesn't look to me like the AC97-compatible version.\n"); + printk (KERN_ERR " You can force the driver to load by passing in the module\n"); + printk (KERN_ERR " parameter:\n"); + printk (KERN_ERR " force_load = 1\n"); + printk (KERN_ERR "\n"); + printk (KERN_ERR " More likely, you should be using the appropriate SB-16 or\n"); + printk (KERN_ERR " CS4232 driver instead. (If your BIOS has settings for\n"); + printk (KERN_ERR " IRQ and/or DMA for the sound card, this is *not* the correct\n"); + printk (KERN_ERR " driver to use.)\n"); + nm256_release_ports (card); + kfree (card); + return 0; + } + else { + printk (KERN_INFO "NM256: Forcing driver load as per user request.\n"); + } + } + else { + /* printk (KERN_INFO "NM256: Congratulations. You're not running Eunice.\n")*/; + } + card->port[0].end_offset = 2560 * 1024; + card->introutine = nm256_interrupt; + card->mixer_status_offset = NM_MIXER_STATUS_OFFSET; + card->mixer_status_mask = NM_MIXER_READY_MASK; + } + else { + /* Not sure if there is any relevant detect for the ZX or not. */ + if (nm256_readPort8 (card, 2, 0xa0b) != 0) + card->port[0].end_offset = 6144 * 1024; + else + card->port[0].end_offset = 4096 * 1024; + + card->introutine = nm256_interrupt_zx; + card->mixer_status_offset = NM2_MIXER_STATUS_OFFSET; + card->mixer_status_mask = NM2_MIXER_READY_MASK; + } + + if (buffertop >= 98304 && buffertop < card->port[0].end_offset) + card->port[0].end_offset = buffertop; + else + nm256_peek_for_sig (card); + + card->port[0].start_offset = card->port[0].end_offset - 98304; + + printk (KERN_INFO "NM256: Mapping port 1 from 0x%x - 0x%x\n", + card->port[0].start_offset, card->port[0].end_offset); + + if (nm256_remap_ports (card)) { + kfree (card); + return 0; + } + + /* See if we can get the interrupt. */ + + card->irq = pcidev->irq; + card->has_irq = 0; + + if (nm256_grabInterrupt (card) != 0) { + nm256_release_ports (card); + kfree (card); + return 0; + } + + nm256_releaseInterrupt (card); + + /* + * Init the board. + */ + + card->playbackBufferSize = 16384; + card->recordBufferSize = 16384; + + card->coeffBuf = card->port[0].end_offset - NM_MAX_COEFFICIENT; + card->abuf2 = card->coeffBuf - card->recordBufferSize; + card->abuf1 = card->abuf2 - card->playbackBufferSize; + card->allCoeffBuf = card->abuf2 - (NM_TOTAL_COEFF_COUNT * 4); + + /* Fixed setting. */ + card->mixer = NM_MIXER_OFFSET; + card->mixer_values_init = 0; + + card->is_open_play = 0; + card->is_open_record = 0; + + card->coeffsCurrent = 0; + + card->opencnt[0] = 0; card->opencnt[1] = 0; + + /* Reasonable default settings, but largely unnecessary. */ + for (x = 0; x < 2; x++) { + card->sinfo[x].bits = 8; + card->sinfo[x].stereo = 0; + card->sinfo[x].samplerate = 8000; + } + + nm256_initHw (card); + + for (x = 0; x < 2; x++) { + if ((card->dev[x] = + sound_install_audiodrv(AUDIO_DRIVER_VERSION, + "NM256", &nm256_audio_driver, + sizeof(struct audio_driver), + DMA_NODMA, AFMT_U8 | AFMT_S16_LE, + NULL, -1, -1)) >= 0) { + /* 1K minimum buffer size. */ + audio_devs[card->dev[x]]->min_fragment = 10; + /* Maximum of 8K buffer size. */ + audio_devs[card->dev[x]]->max_fragment = 13; + } + else { + printk(KERN_ERR "NM256: Too many PCM devices available\n"); + nm256_release_ports (card); + kfree (card); + return 0; + } + } + + pci_set_drvdata(pcidev,card); + + /* Insert the card in the list. */ + card->next_card = nmcard_list; + nmcard_list = card; + + printk(KERN_INFO "Initialized NeoMagic %s audio in PCI native mode\n", + verstr); + + /* + * And our mixer. (We should allow support for other mixers, maybe.) + */ + + nm256_install_mixer (card); + + pmdev = pm_register(PM_PCI_DEV, PM_PCI_ID(pcidev), handle_pm_event); + if (pmdev) + pmdev->data = card; + + return 1; +} + + +/* + * PM event handler, so the card is properly reinitialized after a power + * event. + */ +static int +handle_pm_event (struct pm_dev *dev, pm_request_t rqst, void *data) +{ + struct nm256_info *crd = (struct nm256_info*) dev->data; + if (crd) { + switch (rqst) { + case PM_SUSPEND: + break; + case PM_RESUME: + { + int playing = crd->playing; + nm256_full_reset (crd); + /* + * A little ugly, but that's ok; pretend the + * block we were playing is done. + */ + if (playing) + DMAbuf_outputintr (crd->dev_for_play, 1); + } + break; + } + } + return 0; +} + +static int __devinit +nm256_probe(struct pci_dev *pcidev,const struct pci_device_id *pciid) +{ + if (pcidev->device == PCI_DEVICE_ID_NEOMAGIC_NM256AV_AUDIO) + return nm256_install(pcidev, REV_NM256AV, "256AV"); + if (pcidev->device == PCI_DEVICE_ID_NEOMAGIC_NM256ZX_AUDIO) + return nm256_install(pcidev, REV_NM256ZX, "256ZX"); + if (pcidev->device == PCI_DEVICE_ID_NEOMAGIC_NM256XL_PLUS_AUDIO) + return nm256_install(pcidev, REV_NM256ZX, "256XL+"); + return -1; /* should not come here ... */ +} + +static void __devinit +nm256_remove(struct pci_dev *pcidev) { + struct nm256_info *xcard = pci_get_drvdata(pcidev); + struct nm256_info *card,*next_card = NULL; + + for (card = nmcard_list; card != NULL; card = next_card) { + next_card = card->next_card; + if (card == xcard) { + stopPlay (card); + stopRecord (card); + if (card->has_irq) + free_irq (card->irq, card); + nm256_release_ports (card); + sound_unload_mixerdev (card->mixer_oss_dev); + sound_unload_audiodev (card->dev[0]); + sound_unload_audiodev (card->dev[1]); + kfree (card); + break; + } + } + if (nmcard_list == card) + nmcard_list = next_card; +} + +/* + * Open the device + * + * DEV - device + * MODE - mode to open device (logical OR of OPEN_READ and OPEN_WRITE) + * + * Called when opening the DMAbuf (dmabuf.c:259) + */ +static int +nm256_audio_open(int dev, int mode) +{ + struct nm256_info *card = nm256_find_card (dev); + int w; + + if (card == NULL) + return -ENODEV; + + if (card->dev[0] == dev) + w = 0; + else if (card->dev[1] == dev) + w = 1; + else + return -ENODEV; + + if (card->opencnt[w] > 0) + return -EBUSY; + + /* No bits set? Huh? */ + if (! ((mode & OPEN_READ) || (mode & OPEN_WRITE))) + return -EIO; + + /* + * If it's open for both read and write, and the card's currently + * being read or written to, then do the opposite of what has + * already been done. Otherwise, don't specify any mode until the + * user actually tries to do I/O. (Some programs open the device + * for both read and write, but only actually do reading or writing.) + */ + + if ((mode & OPEN_WRITE) && (mode & OPEN_READ)) { + if (card->is_open_play) + mode = OPEN_WRITE; + else if (card->is_open_record) + mode = OPEN_READ; + else mode = 0; + } + + if (mode & OPEN_WRITE) { + if (card->is_open_play == 0) { + card->dev_for_play = dev; + card->is_open_play = 1; + } + else + return -EBUSY; + } + + if (mode & OPEN_READ) { + if (card->is_open_record == 0) { + card->dev_for_record = dev; + card->is_open_record = 1; + } + else + return -EBUSY; + } + + card->opencnt[w]++; + return 0; +} + +/* + * Close the device + * + * DEV - device + * + * Called when closing the DMAbuf (dmabuf.c:477) + * after halt_xfer + */ +static void +nm256_audio_close(int dev) +{ + struct nm256_info *card = nm256_find_card (dev); + + if (card != NULL) { + int w; + + if (card->dev[0] == dev) + w = 0; + else if (card->dev[1] == dev) + w = 1; + else + return; + + card->opencnt[w]--; + if (card->opencnt[w] <= 0) { + card->opencnt[w] = 0; + + if (card->dev_for_play == dev) { + stopPlay (card); + card->is_open_play = 0; + card->dev_for_play = -1; + } + + if (card->dev_for_record == dev) { + stopRecord (card); + card->is_open_record = 0; + card->dev_for_record = -1; + } + } + } +} + +/* Standard ioctl handler. */ +static int +nm256_audio_ioctl(int dev, unsigned int cmd, void __user *arg) +{ + int ret; + u32 oldinfo; + int w; + + struct nm256_info *card = nm256_find_card (dev); + + if (card == NULL) + return -ENODEV; + + if (dev == card->dev[0]) + w = 0; + else + w = 1; + + /* + * The code here is messy. There are probably better ways to do + * it. (It should be possible to handle it the same way the AC97 mixer + * is done.) + */ + switch (cmd) + { + case SOUND_PCM_WRITE_RATE: + if (get_user(ret, (int __user *) arg)) + return -EFAULT; + + if (ret != 0) { + oldinfo = card->sinfo[w].samplerate; + card->sinfo[w].samplerate = ret; + ret = nm256_setInfo(dev, card); + if (ret != 0) + card->sinfo[w].samplerate = oldinfo; + } + if (ret == 0) + ret = card->sinfo[w].samplerate; + break; + + case SOUND_PCM_READ_RATE: + ret = card->sinfo[w].samplerate; + break; + + case SNDCTL_DSP_STEREO: + if (get_user(ret, (int __user *) arg)) + return -EFAULT; + + card->sinfo[w].stereo = ret ? 1 : 0; + ret = nm256_setInfo (dev, card); + if (ret == 0) + ret = card->sinfo[w].stereo; + + break; + + case SOUND_PCM_WRITE_CHANNELS: + if (get_user(ret, (int __user *) arg)) + return -EFAULT; + + if (ret < 1 || ret > 3) + ret = card->sinfo[w].stereo + 1; + else { + card->sinfo[w].stereo = ret - 1; + ret = nm256_setInfo (dev, card); + if (ret == 0) + ret = card->sinfo[w].stereo + 1; + } + break; + + case SOUND_PCM_READ_CHANNELS: + ret = card->sinfo[w].stereo + 1; + break; + + case SNDCTL_DSP_SETFMT: + if (get_user(ret, (int __user *) arg)) + return -EFAULT; + + if (ret != 0) { + oldinfo = card->sinfo[w].bits; + card->sinfo[w].bits = ret; + ret = nm256_setInfo (dev, card); + if (ret != 0) + card->sinfo[w].bits = oldinfo; + } + if (ret == 0) + ret = card->sinfo[w].bits; + break; + + case SOUND_PCM_READ_BITS: + ret = card->sinfo[w].bits; + break; + + default: + return -EINVAL; + } + return put_user(ret, (int __user *) arg); +} + +/* + * Given the sound device DEV and an associated physical buffer PHYSBUF, + * return a pointer to the actual buffer in kernel space. + * + * This routine should exist as part of the soundcore routines. + */ + +static char * +nm256_getDMAbuffer (int dev, unsigned long physbuf) +{ + struct audio_operations *adev = audio_devs[dev]; + struct dma_buffparms *dmap = adev->dmap_out; + char *dma_start = + (char *)(physbuf - (unsigned long)dmap->raw_buf_phys + + (unsigned long)dmap->raw_buf); + + return dma_start; +} + + +/* + * Output a block to sound device + * + * dev - device number + * buf - physical address of buffer + * total_count - total byte count in buffer + * intrflag - set if this has been called from an interrupt + * (via DMAbuf_outputintr) + * restart_dma - set if engine needs to be re-initialised + * + * Called when: + * 1. Starting output (dmabuf.c:1327) + * 2. (dmabuf.c:1504) + * 3. A new buffer needs to be sent to the device (dmabuf.c:1579) + */ +static void +nm256_audio_output_block(int dev, unsigned long physbuf, + int total_count, int intrflag) +{ + struct nm256_info *card = nm256_find_card (dev); + + if (card != NULL) { + char *dma_buf = nm256_getDMAbuffer (dev, physbuf); + card->is_open_play = 1; + card->dev_for_play = dev; + nm256_write_block (card, dma_buf, total_count); + } +} + +/* Ditto, but do recording instead. */ +static void +nm256_audio_start_input(int dev, unsigned long physbuf, int count, + int intrflag) +{ + struct nm256_info *card = nm256_find_card (dev); + + if (card != NULL) { + char *dma_buf = nm256_getDMAbuffer (dev, physbuf); + card->is_open_record = 1; + card->dev_for_record = dev; + nm256_startRecording (card, dma_buf, count); + } +} + +/* + * Prepare for inputting samples to DEV. + * Each requested buffer will be BSIZE byes long, with a total of + * BCOUNT buffers. + */ + +static int +nm256_audio_prepare_for_input(int dev, int bsize, int bcount) +{ + struct nm256_info *card = nm256_find_card (dev); + + if (card == NULL) + return -ENODEV; + + if (card->is_open_record && card->dev_for_record != dev) + return -EBUSY; + + audio_devs[dev]->dmap_in->flags |= DMA_NODMA; + return 0; +} + +/* + * Prepare for outputting samples to `dev' + * + * Each buffer that will be passed will be `bsize' bytes long, + * with a total of `bcount' buffers. + * + * Called when: + * 1. A trigger enables audio output (dmabuf.c:978) + * 2. We get a write buffer without dma_mode setup (dmabuf.c:1152) + * 3. We restart a transfer (dmabuf.c:1324) + */ + +static int +nm256_audio_prepare_for_output(int dev, int bsize, int bcount) +{ + struct nm256_info *card = nm256_find_card (dev); + + if (card == NULL) + return -ENODEV; + + if (card->is_open_play && card->dev_for_play != dev) + return -EBUSY; + + audio_devs[dev]->dmap_out->flags |= DMA_NODMA; + return 0; +} + +/* Stop the current operations associated with DEV. */ +static void +nm256_audio_reset(int dev) +{ + struct nm256_info *card = nm256_find_card (dev); + + if (card != NULL) { + if (card->dev_for_play == dev) + stopPlay (card); + if (card->dev_for_record == dev) + stopRecord (card); + } +} + +static int +nm256_audio_local_qlen(int dev) +{ + return 0; +} + +static struct audio_driver nm256_audio_driver = +{ + .owner = THIS_MODULE, + .open = nm256_audio_open, + .close = nm256_audio_close, + .output_block = nm256_audio_output_block, + .start_input = nm256_audio_start_input, + .ioctl = nm256_audio_ioctl, + .prepare_for_input = nm256_audio_prepare_for_input, + .prepare_for_output = nm256_audio_prepare_for_output, + .halt_io = nm256_audio_reset, + .local_qlen = nm256_audio_local_qlen, +}; + +static struct pci_device_id nm256_pci_tbl[] = { + {PCI_VENDOR_ID_NEOMAGIC, PCI_DEVICE_ID_NEOMAGIC_NM256AV_AUDIO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0}, + {PCI_VENDOR_ID_NEOMAGIC, PCI_DEVICE_ID_NEOMAGIC_NM256ZX_AUDIO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0}, + {PCI_VENDOR_ID_NEOMAGIC, PCI_DEVICE_ID_NEOMAGIC_NM256XL_PLUS_AUDIO, + PCI_ANY_ID, PCI_ANY_ID, 0, 0}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, nm256_pci_tbl); +MODULE_LICENSE("GPL"); + + +static struct pci_driver nm256_pci_driver = { + .name = "nm256_audio", + .id_table = nm256_pci_tbl, + .probe = nm256_probe, + .remove = nm256_remove, +}; + +module_param(usecache, bool, 0); +module_param(buffertop, int, 0); +module_param(nm256_debug, bool, 0644); +module_param(force_load, bool, 0); + +static int __init do_init_nm256(void) +{ + printk (KERN_INFO "NeoMagic 256AV/256ZX audio driver, version 1.1p\n"); + return pci_module_init(&nm256_pci_driver); +} + +static void __exit cleanup_nm256 (void) +{ + pci_unregister_driver(&nm256_pci_driver); + pm_unregister_all (&handle_pm_event); +} + +module_init(do_init_nm256); +module_exit(cleanup_nm256); + +/* + * Local variables: + * c-basic-offset: 4 + * End: + */ diff --git a/sound/oss/nm256_coeff.h b/sound/oss/nm256_coeff.h new file mode 100644 index 000000000000..0ceecc20077b --- /dev/null +++ b/sound/oss/nm256_coeff.h @@ -0,0 +1,4697 @@ +#ifndef NM256_COEFF_H +#define NM256_COEFF_H + +#define NM_TOTAL_COEFF_COUNT 0x3158 + +static char coefficients[NM_TOTAL_COEFF_COUNT * 4] = { + 0xFF, 0xFF, 0x2F, 0x00, 0x4B, 0xFF, 0xA5, 0x01, 0xEF, 0xFC, 0x21, + 0x05, 0x87, 0xF7, 0x62, 0x11, 0xE9, 0x45, 0x5E, 0xF9, 0xB5, 0x01, + 0xDE, 0xFF, 0xA4, 0xFF, 0x60, 0x00, 0xCA, 0xFF, 0x0D, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD6, 0x06, + 0x4C, 0xF3, 0xED, 0x20, 0x3D, 0x3D, 0x4A, 0xF3, 0x4E, 0x05, 0xB1, + 0xFD, 0xE1, 0x00, 0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFD, 0xFF, + 0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E, + 0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC, + 0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x02, 0x00, 0x05, + 0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1, 0xFD, 0x4E, 0x05, 0x4A, 0xF3, + 0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3, 0xD6, 0x06, 0x3D, 0xFC, 0xE6, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCA, 0xFF, + 0x60, 0x00, 0xA4, 0xFF, 0xDE, 0xFF, 0xB5, 0x01, 0x5E, 0xF9, 0xE9, + 0x45, 0x62, 0x11, 0x87, 0xF7, 0x21, 0x05, 0xEF, 0xFC, 0xA5, 0x01, + 0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84, + 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, + 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, + 0x01, 0x84, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, + 0xCA, 0x01, 0x95, 0xFC, 0xEA, 0x05, 0xBB, 0xF5, 0x25, 0x17, 0x3C, + 0x43, 0x8D, 0xF6, 0x43, 0x03, 0xF5, 0xFE, 0x26, 0x00, 0x20, 0x00, + 0xE2, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4D, 0xFF, 0xC5, + 0x01, 0x4C, 0xFC, 0x26, 0x07, 0xA3, 0xF1, 0xAB, 0x2C, 0xBB, 0x33, + 0x8F, 0xF1, 0xCA, 0x06, 0xA6, 0xFC, 0x85, 0x01, 0x6F, 0xFF, 0x24, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFE, 0xFF, 0xD5, 0xFF, 0xBC, 0x00, + 0xF0, 0xFD, 0xEC, 0x04, 0xD9, 0xF3, 0xB1, 0x3E, 0xCD, 0x1E, 0xC1, + 0xF3, 0xAF, 0x06, 0x49, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x16, 0x00, 0xA6, 0xFF, 0xBB, 0x00, 0xE9, 0xFE, 0x38, + 0x01, 0x4B, 0xFF, 0x28, 0xFE, 0x3A, 0x48, 0x04, 0x0A, 0x2E, 0xFA, + 0xDF, 0x03, 0x8A, 0xFD, 0x60, 0x01, 0x65, 0xFF, 0x27, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0x98, 0x01, 0x0D, 0xFD, + 0xE0, 0x04, 0x14, 0xF8, 0xC3, 0x0F, 0x89, 0x46, 0x4C, 0xFA, 0x38, + 0x01, 0x25, 0x00, 0x7D, 0xFF, 0x73, 0x00, 0xC2, 0xFF, 0x0F, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x0F, + 0x07, 0x84, 0xF2, 0x29, 0x25, 0x1A, 0x3A, 0x67, 0xF2, 0xF6, 0x05, + 0x41, 0xFD, 0x24, 0x01, 0xA1, 0xFF, 0x12, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x15, 0x00, 0x97, 0xFF, 0x37, 0x01, 0x22, 0xFD, 0x23, 0x06, + 0x2F, 0xF2, 0x11, 0x39, 0x7B, 0x26, 0x50, 0xF2, 0x1B, 0x07, 0x32, + 0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, + 0xC8, 0xFF, 0x64, 0x00, 0x9B, 0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93, + 0xF9, 0x10, 0x46, 0x03, 0x11, 0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC, + 0xA2, 0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, + 0x00, 0x6A, 0xFF, 0x53, 0x01, 0xA6, 0xFD, 0xA6, 0x03, 0xA1, 0xFA, + 0xDE, 0x08, 0x76, 0x48, 0x0C, 0xFF, 0xDE, 0xFE, 0x73, 0x01, 0xC9, + 0xFE, 0xCA, 0x00, 0xA0, 0xFF, 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE1, 0x01, 0x52, 0xFC, 0x93, 0x06, 0x10, 0xF4, 0x78, + 0x1D, 0x90, 0x3F, 0x3E, 0xF4, 0xAA, 0x04, 0x19, 0xFE, 0xA4, 0x00, + 0xE2, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x68, + 0xFF, 0x93, 0x01, 0x92, 0xFC, 0xE2, 0x06, 0x83, 0xF1, 0x8C, 0x32, + 0xED, 0x2D, 0x90, 0xF1, 0x1E, 0x07, 0x57, 0xFC, 0xBD, 0x01, 0x51, + 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE8, 0xFF, 0x12, 0x00, + 0x42, 0x00, 0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42, 0x76, + 0x18, 0x5C, 0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x8A, 0xFF, 0x03, 0x01, 0x53, + 0xFE, 0x53, 0x02, 0x39, 0xFD, 0xA9, 0x02, 0xF2, 0x48, 0xB9, 0x04, + 0x54, 0xFC, 0xCA, 0x02, 0x16, 0xFE, 0x20, 0x01, 0x7F, 0xFF, 0x20, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF, 0xC3, 0x01, + 0xA7, 0xFC, 0xC0, 0x05, 0x1E, 0xF6, 0xD8, 0x15, 0xE7, 0x43, 0x20, + 0xF7, 0xEF, 0x02, 0x27, 0xFF, 0x0A, 0x00, 0x2E, 0x00, 0xDD, 0xFF, + 0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCD, 0x01, 0x43, + 0xFC, 0x2A, 0x07, 0xBC, 0xF1, 0x64, 0x2B, 0xE3, 0x34, 0xA3, 0xF1, + 0xAE, 0x06, 0xBD, 0xFC, 0x77, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, + 0xFF, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4, 0x00, 0xC8, 0xFD, + 0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20, 0x76, 0xF3, 0xC8, + 0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x14, 0x00, 0xAC, 0xFF, 0xAC, 0x00, 0x08, 0xFF, 0xFD, 0x00, 0xB5, + 0xFF, 0x4B, 0xFD, 0xF4, 0x47, 0x30, 0x0B, 0xBC, 0xF9, 0x17, 0x04, + 0x6E, 0xFD, 0x6D, 0x01, 0x60, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x2C, 0x00, 0x54, 0xFF, 0x8D, 0x01, 0x26, 0xFD, 0xAD, 0x04, + 0x82, 0xF8, 0x87, 0x0E, 0xF9, 0x46, 0x0C, 0xFB, 0xD4, 0x00, 0x5D, + 0x00, 0x5E, 0xFF, 0x82, 0x00, 0xBD, 0xFF, 0x10, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0x01, 0x07, 0xBE, + 0xF2, 0xD6, 0x23, 0x1F, 0x3B, 0xA5, 0xF2, 0xC5, 0x05, 0x62, 0xFD, + 0x10, 0x01, 0xAB, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x19, + 0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04, 0xFD, 0x4D, 0x06, 0x00, 0xF2, + 0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2, 0x23, 0x07, 0x34, 0xFC, 0xDD, + 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xCE, 0xFF, + 0x56, 0x00, 0xB9, 0xFF, 0xB8, 0xFF, 0xF7, 0x01, 0xE2, 0xF8, 0x8D, + 0x45, 0x46, 0x12, 0x3C, 0xF7, 0x43, 0x05, 0xDF, 0xFC, 0xAC, 0x01, + 0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x70, + 0xFF, 0x46, 0x01, 0xC3, 0xFD, 0x6D, 0x03, 0x14, 0xFB, 0xBE, 0x07, + 0xA6, 0x48, 0xF8, 0xFF, 0x70, 0xFE, 0xAE, 0x01, 0xAA, 0xFE, 0xD9, + 0x00, 0x9A, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xDE, 0x01, 0x5D, 0xFC, 0x74, 0x06, 0x63, 0xF4, 0x23, 0x1C, 0x66, + 0x40, 0xAA, 0xF4, 0x65, 0x04, 0x44, 0xFE, 0x8B, 0x00, 0xEE, 0xFF, + 0xF5, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F, + 0x01, 0x80, 0xFC, 0xF7, 0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F, + 0x83, 0xF1, 0x13, 0x07, 0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C, + 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xED, 0xFF, 0x05, 0x00, 0x5D, 0x00, + 0x95, 0xFE, 0xE2, 0x03, 0x7F, 0xF5, 0xCC, 0x41, 0xC7, 0x19, 0xFF, + 0xF4, 0x37, 0x06, 0x75, 0xFC, 0xD6, 0x01, 0x39, 0xFF, 0x35, 0x00, + 0xFE, 0xFF, 0x1B, 0x00, 0x90, 0xFF, 0xF4, 0x00, 0x72, 0xFE, 0x18, + 0x02, 0xAA, 0xFD, 0xAB, 0x01, 0xDF, 0x48, 0xCA, 0x05, 0xE1, 0xFB, + 0x05, 0x03, 0xF7, 0xFD, 0x2E, 0x01, 0x79, 0xFF, 0x21, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x43, 0xFF, 0xBB, 0x01, 0xBA, 0xFC, + 0x95, 0x05, 0x83, 0xF6, 0x8C, 0x14, 0x87, 0x44, 0xBB, 0xF7, 0x98, + 0x02, 0x5A, 0xFF, 0xEE, 0xFF, 0x3C, 0x00, 0xD8, 0xFF, 0x0A, 0x00, + 0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A, + 0x07, 0xDC, 0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06, + 0xD5, 0xFC, 0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x01, + 0x00, 0x07, 0x00, 0xBE, 0xFF, 0xEA, 0x00, 0xA2, 0xFD, 0x65, 0x05, + 0x28, 0xF3, 0xDB, 0x3C, 0x78, 0x21, 0x30, 0xF3, 0xDF, 0x06, 0x3A, + 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, + 0xB2, 0xFF, 0x9D, 0x00, 0x27, 0xFF, 0xC3, 0x00, 0x1F, 0x00, 0x76, + 0xFC, 0xA3, 0x47, 0x60, 0x0C, 0x4A, 0xF9, 0x4E, 0x04, 0x53, 0xFD, + 0x79, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, + 0x00, 0x58, 0xFF, 0x82, 0x01, 0x3F, 0xFD, 0x78, 0x04, 0xF2, 0xF8, + 0x50, 0x0D, 0x5E, 0x47, 0xD5, 0xFB, 0x6F, 0x00, 0x96, 0x00, 0x40, + 0xFF, 0x91, 0x00, 0xB7, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC, 0xF2, 0x81, + 0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD, 0xFB, 0x00, + 0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x85, + 0xFF, 0x5B, 0x01, 0xE9, 0xFC, 0x73, 0x06, 0xD8, 0xF1, 0xE5, 0x36, + 0x19, 0x29, 0xF8, 0xF1, 0x29, 0x07, 0x37, 0xFC, 0xD8, 0x01, 0x42, + 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD3, 0xFF, 0x47, 0x00, + 0xD7, 0xFF, 0x82, 0xFF, 0x53, 0x02, 0x39, 0xF8, 0xFD, 0x44, 0x8D, + 0x13, 0xD3, 0xF6, 0x72, 0x05, 0xCA, 0xFC, 0xB5, 0x01, 0x45, 0xFF, + 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x75, 0xFF, 0x39, + 0x01, 0xE0, 0xFD, 0x33, 0x03, 0x87, 0xFB, 0xA2, 0x06, 0xCB, 0x48, + 0xEA, 0x00, 0x01, 0xFE, 0xE9, 0x01, 0x8A, 0xFE, 0xE8, 0x00, 0x95, + 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x38, 0xFF, 0xDA, 0x01, + 0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4, 0xCE, 0x1A, 0x32, 0x41, 0x1F, + 0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71, 0x00, 0xFB, 0xFF, 0xF0, 0xFF, + 0x05, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5B, 0xFF, 0xAB, 0x01, 0x6F, + 0xFC, 0x08, 0x07, 0x7E, 0xF1, 0x21, 0x30, 0x67, 0x30, 0x7D, 0xF1, + 0x05, 0x07, 0x73, 0xFC, 0xA8, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD, + 0xFF, 0x05, 0x00, 0xF2, 0xFF, 0xF8, 0xFF, 0x77, 0x00, 0x67, 0xFE, + 0x2D, 0x04, 0x04, 0xF5, 0x07, 0x41, 0x1B, 0x1B, 0xA6, 0xF4, 0x5A, + 0x06, 0x67, 0xFC, 0xDB, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, + 0x1A, 0x00, 0x96, 0xFF, 0xE5, 0x00, 0x91, 0xFE, 0xDC, 0x01, 0x1A, + 0xFE, 0xB3, 0x00, 0xC3, 0x48, 0xE1, 0x06, 0x6E, 0xFB, 0x40, 0x03, + 0xDA, 0xFD, 0x3C, 0x01, 0x74, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB3, 0x01, 0xCF, 0xFC, 0x67, 0x05, + 0xEA, 0xF6, 0x44, 0x13, 0x1E, 0x45, 0x5E, 0xF8, 0x3F, 0x02, 0x8E, + 0xFF, 0xD0, 0xFF, 0x4A, 0x00, 0xD2, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, + 0x33, 0x00, 0x41, 0xFF, 0xD9, 0x01, 0x36, 0xFC, 0x28, 0x07, 0x01, + 0xF2, 0xCE, 0x28, 0x23, 0x37, 0xE0, 0xF1, 0x6B, 0x06, 0xEF, 0xFC, + 0x57, 0x01, 0x87, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0B, + 0x00, 0xB4, 0xFF, 0x00, 0x01, 0x7E, 0xFD, 0x9C, 0x05, 0xDC, 0xF2, + 0xE4, 0x3B, 0xCD, 0x22, 0xEE, 0xF2, 0xF3, 0x06, 0x35, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, 0x00, 0xB8, 0xFF, + 0x8E, 0x00, 0x46, 0xFF, 0x8A, 0x00, 0x86, 0x00, 0xA7, 0xFB, 0x48, + 0x47, 0x95, 0x0D, 0xD9, 0xF8, 0x84, 0x04, 0x39, 0xFD, 0x85, 0x01, + 0x57, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D, + 0xFF, 0x76, 0x01, 0x59, 0xFD, 0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C, + 0xB6, 0x47, 0xA4, 0xFC, 0x07, 0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0, + 0x00, 0xB1, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE6, 0x01, 0x3B, 0xFC, 0xDA, 0x06, 0x3F, 0xF3, 0x2C, 0x21, 0x11, + 0x3D, 0x3A, 0xF3, 0x58, 0x05, 0xAA, 0xFD, 0xE5, 0x00, 0xC1, 0xFF, + 0x06, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1F, 0x00, 0x7D, 0xFF, 0x6B, + 0x01, 0xCF, 0xFC, 0x96, 0x06, 0xB7, 0xF1, 0xC6, 0x35, 0x64, 0x2A, + 0xD4, 0xF1, 0x2B, 0x07, 0x3D, 0xFC, 0xD2, 0x01, 0x45, 0xFF, 0x32, + 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD9, 0xFF, 0x39, 0x00, 0xF4, 0xFF, + 0x4E, 0xFF, 0xAC, 0x02, 0x98, 0xF7, 0x65, 0x44, 0xD6, 0x14, 0x6C, + 0xF6, 0x9F, 0x05, 0xB6, 0xFC, 0xBD, 0x01, 0x42, 0xFF, 0x32, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF, 0x2B, 0x01, 0xFE, + 0xFD, 0xF8, 0x02, 0xFB, 0xFB, 0x8D, 0x05, 0xE5, 0x48, 0xE3, 0x01, + 0x91, 0xFD, 0x25, 0x02, 0x6B, 0xFE, 0xF7, 0x00, 0x8F, 0xFF, 0x1C, + 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD5, 0x01, 0x78, 0xFC, + 0x2F, 0x06, 0x13, 0xF5, 0x7C, 0x19, 0xF7, 0x41, 0x9B, 0xF5, 0xD1, + 0x03, 0x9F, 0xFE, 0x57, 0x00, 0x08, 0x00, 0xEC, 0xFF, 0x06, 0x00, + 0xFD, 0xFF, 0x2D, 0x00, 0x55, 0xFF, 0xB5, 0x01, 0x61, 0xFC, 0x16, + 0x07, 0x85, 0xF1, 0xE6, 0x2E, 0x9E, 0x31, 0x7D, 0xF1, 0xF3, 0x06, + 0x84, 0xFC, 0x9D, 0x01, 0x63, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x04, + 0x00, 0xF6, 0xFF, 0xEB, 0xFF, 0x91, 0x00, 0x3B, 0xFE, 0x75, 0x04, + 0x92, 0xF4, 0x36, 0x40, 0x6E, 0x1C, 0x50, 0xF4, 0x7B, 0x06, 0x5B, + 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00, + 0x9C, 0xFF, 0xD6, 0x00, 0xB1, 0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3, + 0xFF, 0x9C, 0x48, 0xFD, 0x07, 0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD, + 0x49, 0x01, 0x6E, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, + 0x00, 0x49, 0xFF, 0xAA, 0x01, 0xE4, 0xFC, 0x38, 0x05, 0x54, 0xF7, + 0xFE, 0x11, 0xAA, 0x45, 0x09, 0xF9, 0xE2, 0x01, 0xC4, 0xFF, 0xB3, + 0xFF, 0x59, 0x00, 0xCD, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x3E, 0xFF, 0xDE, 0x01, 0x33, 0xFC, 0x22, 0x07, 0x2B, 0xF2, 0x80, + 0x27, 0x3B, 0x38, 0x0A, 0xF2, 0x44, 0x06, 0x0B, 0xFD, 0x45, 0x01, + 0x90, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA9, + 0xFF, 0x15, 0x01, 0x5B, 0xFD, 0xD0, 0x05, 0x97, 0xF2, 0xE6, 0x3A, + 0x21, 0x24, 0xB1, 0xF2, 0x04, 0x07, 0x33, 0xFC, 0xE5, 0x01, 0x39, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBE, 0xFF, 0x7F, 0x00, + 0x65, 0xFF, 0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46, 0xCD, + 0x0E, 0x6A, 0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53, 0xFF, + 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x62, 0xFF, 0x6A, + 0x01, 0x74, 0xFD, 0x0A, 0x04, 0xD5, 0xF9, 0xED, 0x0A, 0x03, 0x48, + 0x7C, 0xFD, 0x9E, 0xFF, 0x0A, 0x01, 0x01, 0xFF, 0xAF, 0x00, 0xAB, + 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, + 0x42, 0xFC, 0xC3, 0x06, 0x87, 0xF3, 0xD7, 0x1F, 0xFE, 0x3D, 0x91, + 0xF3, 0x1D, 0x05, 0xD1, 0xFD, 0xCE, 0x00, 0xCC, 0xFF, 0x02, 0x00, + 0x02, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x75, 0xFF, 0x7A, 0x01, 0xB8, + 0xFC, 0xB4, 0x06, 0x9E, 0xF1, 0xA2, 0x34, 0xAD, 0x2B, 0xB6, 0xF1, + 0x29, 0x07, 0x45, 0xFC, 0xCB, 0x01, 0x49, 0xFF, 0x31, 0x00, 0xFD, + 0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11, 0x00, 0x1B, 0xFF, + 0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16, 0x07, 0xF6, 0xCA, + 0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x20, 0x00, 0x80, 0xFF, 0x1C, 0x01, 0x1C, 0xFE, 0xBD, + 0x02, 0x6E, 0xFC, 0x7D, 0x04, 0xF3, 0x48, 0xE2, 0x02, 0x1F, 0xFD, + 0x60, 0x02, 0x4C, 0xFE, 0x06, 0x01, 0x89, 0xFF, 0x1D, 0x00, 0xFE, + 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCF, 0x01, 0x88, 0xFC, 0x09, 0x06, + 0x71, 0xF5, 0x2B, 0x18, 0xB2, 0x42, 0x20, 0xF6, 0x83, 0x03, 0xCF, + 0xFE, 0x3C, 0x00, 0x15, 0x00, 0xE6, 0xFF, 0x07, 0x00, 0xFD, 0xFF, + 0x2E, 0x00, 0x50, 0xFF, 0xBF, 0x01, 0x54, 0xFC, 0x20, 0x07, 0x94, + 0xF1, 0xA6, 0x2D, 0xD0, 0x32, 0x85, 0xF1, 0xDD, 0x06, 0x96, 0xFC, + 0x90, 0x01, 0x69, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFB, + 0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10, 0xFE, 0xB9, 0x04, 0x27, 0xF4, + 0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3, 0x99, 0x06, 0x50, 0xFC, 0xE2, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0xA2, 0xFF, + 0xC7, 0x00, 0xD0, 0xFE, 0x65, 0x01, 0xF6, 0xFE, 0xD9, 0xFE, 0x6A, + 0x48, 0x1F, 0x09, 0x87, 0xFA, 0xB3, 0x03, 0xA0, 0xFD, 0x56, 0x01, + 0x69, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4D, + 0xFF, 0xA0, 0x01, 0xFB, 0xFC, 0x07, 0x05, 0xBF, 0xF7, 0xBB, 0x10, + 0x2B, 0x46, 0xBB, 0xF9, 0x83, 0x01, 0xFA, 0xFF, 0x95, 0xFF, 0x68, + 0x00, 0xC7, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, + 0xE1, 0x01, 0x31, 0xFC, 0x19, 0x07, 0x5B, 0xF2, 0x30, 0x26, 0x4B, + 0x39, 0x3B, 0xF2, 0x1A, 0x06, 0x29, 0xFD, 0x33, 0x01, 0x99, 0xFF, + 0x15, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28, + 0x01, 0x3A, 0xFD, 0x00, 0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25, + 0x79, 0xF2, 0x12, 0x07, 0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, + 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC4, 0xFF, 0x70, 0x00, 0x84, 0xFF, + 0x19, 0x00, 0x4D, 0x01, 0x22, 0xFA, 0x70, 0x46, 0x0A, 0x10, 0xFC, + 0xF7, 0xEB, 0x04, 0x08, 0xFD, 0x9A, 0x01, 0x4F, 0xFF, 0x2E, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x66, 0xFF, 0x5E, 0x01, 0x90, + 0xFD, 0xD2, 0x03, 0x47, 0xFA, 0xC3, 0x09, 0x48, 0x48, 0x5A, 0xFE, + 0x33, 0xFF, 0x45, 0x01, 0xE2, 0xFE, 0xBE, 0x00, 0xA5, 0xFF, 0x16, + 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4B, 0xFC, + 0xA9, 0x06, 0xD2, 0xF3, 0x81, 0x1E, 0xE4, 0x3E, 0xEF, 0xF3, 0xDE, + 0x04, 0xF9, 0xFD, 0xB7, 0x00, 0xD8, 0xFF, 0xFD, 0xFF, 0x03, 0x00, + 0xFD, 0xFF, 0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0, + 0x06, 0x8C, 0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07, + 0x4E, 0xFC, 0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x08, + 0x00, 0xE4, 0xFF, 0x1D, 0x00, 0x2D, 0x00, 0xEA, 0xFE, 0x56, 0x03, + 0x6D, 0xF6, 0x17, 0x43, 0x70, 0x17, 0xA6, 0xF5, 0xF3, 0x05, 0x91, + 0xFC, 0xCC, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1E, 0x00, + 0x86, 0xFF, 0x0E, 0x01, 0x3B, 0xFE, 0x82, 0x02, 0xE0, 0xFC, 0x73, + 0x03, 0xF6, 0x48, 0xE9, 0x03, 0xAD, 0xFC, 0x9C, 0x02, 0x2D, 0xFE, + 0x14, 0x01, 0x83, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, + 0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x99, 0xFC, 0xE1, 0x05, 0xD1, 0xF5, + 0xDC, 0x16, 0x65, 0x43, 0xAD, 0xF6, 0x31, 0x03, 0x00, 0xFF, 0x20, + 0x00, 0x23, 0x00, 0xE1, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00, + 0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8, 0xF1, 0x62, + 0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC, 0x82, 0x01, + 0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0xFF, 0xFF, 0xD3, + 0xFF, 0xC1, 0x00, 0xE7, 0xFD, 0xFA, 0x04, 0xC4, 0xF3, 0x7E, 0x3E, + 0x19, 0x1F, 0xB0, 0xF3, 0xB5, 0x06, 0x47, 0xFC, 0xE4, 0x01, 0x36, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x15, 0x00, 0xA8, 0xFF, 0xB8, 0x00, + 0xF0, 0xFE, 0x2B, 0x01, 0x63, 0xFF, 0xF6, 0xFD, 0x2C, 0x48, 0x47, + 0x0A, 0x14, 0xFA, 0xEB, 0x03, 0x84, 0xFD, 0x63, 0x01, 0x64, 0xFF, + 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x96, + 0x01, 0x13, 0xFD, 0xD5, 0x04, 0x2C, 0xF8, 0x7D, 0x0F, 0xA3, 0x46, + 0x76, 0xFA, 0x22, 0x01, 0x32, 0x00, 0x76, 0xFF, 0x76, 0x00, 0xC1, + 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A, 0xFF, 0xE4, 0x01, + 0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2, 0xDD, 0x24, 0x54, 0x3A, 0x74, + 0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20, 0x01, 0xA3, 0xFF, 0x11, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x95, 0xFF, 0x3B, 0x01, 0x1B, + 0xFD, 0x2D, 0x06, 0x24, 0xF2, 0xD3, 0x38, 0xC6, 0x26, 0x45, 0xF2, + 0x1D, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x0D, 0x00, 0xC9, 0xFF, 0x61, 0x00, 0xA2, 0xFF, 0xE2, 0xFF, + 0xAE, 0x01, 0x6B, 0xF9, 0xF2, 0x45, 0x4A, 0x11, 0x8F, 0xF7, 0x1D, + 0x05, 0xF1, 0xFC, 0xA4, 0x01, 0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x25, 0x00, 0x6C, 0xFF, 0x51, 0x01, 0xAC, 0xFD, 0x9A, + 0x03, 0xBA, 0xFA, 0x9E, 0x08, 0x81, 0x48, 0x40, 0xFF, 0xC6, 0xFE, + 0x80, 0x01, 0xC2, 0xFE, 0xCE, 0x00, 0x9F, 0xFF, 0x17, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE1, 0x01, 0x55, 0xFC, 0x8C, 0x06, + 0x22, 0xF4, 0x2C, 0x1D, 0xC0, 0x3F, 0x55, 0xF4, 0x9B, 0x04, 0x23, + 0xFE, 0x9F, 0x00, 0xE4, 0xFF, 0xF9, 0xFF, 0x04, 0x00, 0xFD, 0xFF, + 0x27, 0x00, 0x66, 0xFF, 0x96, 0x01, 0x8E, 0xFC, 0xE7, 0x06, 0x81, + 0xF1, 0x48, 0x32, 0x34, 0x2E, 0x8D, 0xF1, 0x1C, 0x07, 0x5A, 0xFC, + 0xBB, 0x01, 0x53, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE9, + 0xFF, 0x0F, 0x00, 0x48, 0x00, 0xB9, 0xFE, 0xA6, 0x03, 0xE4, 0xF5, + 0x60, 0x42, 0xC1, 0x18, 0x47, 0xF5, 0x1A, 0x06, 0x81, 0xFC, 0xD2, + 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8B, 0xFF, + 0xFF, 0x00, 0x5A, 0xFE, 0x46, 0x02, 0x52, 0xFD, 0x70, 0x02, 0xED, + 0x48, 0xF5, 0x04, 0x3B, 0xFC, 0xD7, 0x02, 0x0F, 0xFE, 0x23, 0x01, + 0x7E, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, + 0xFF, 0xC1, 0x01, 0xAB, 0xFC, 0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15, + 0x0B, 0x44, 0x42, 0xF7, 0xDC, 0x02, 0x32, 0xFF, 0x04, 0x00, 0x31, + 0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x47, 0xFF, + 0xCE, 0x01, 0x41, 0xFC, 0x2A, 0x07, 0xC2, 0xF1, 0x1B, 0x2B, 0x25, + 0x35, 0xA8, 0xF1, 0xA7, 0x06, 0xC2, 0xFC, 0x74, 0x01, 0x78, 0xFF, + 0x20, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x04, 0x00, 0xC7, 0xFF, 0xD9, + 0x00, 0xBF, 0xFD, 0x38, 0x05, 0x69, 0xF3, 0x96, 0x3D, 0x6F, 0x20, + 0x66, 0xF3, 0xCE, 0x06, 0x3F, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAE, 0xFF, 0xA9, 0x00, 0x0F, 0xFF, + 0xF0, 0x00, 0xCD, 0xFF, 0x1B, 0xFD, 0xE4, 0x47, 0x73, 0x0B, 0xA2, + 0xF9, 0x23, 0x04, 0x68, 0xFD, 0x70, 0x01, 0x5F, 0xFF, 0x29, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x55, 0xFF, 0x8B, 0x01, 0x2B, + 0xFD, 0xA1, 0x04, 0x9B, 0xF8, 0x42, 0x0E, 0x0F, 0x47, 0x38, 0xFB, + 0xBE, 0x00, 0x6A, 0x00, 0x58, 0xFF, 0x85, 0x00, 0xBB, 0xFF, 0x10, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC, + 0xFD, 0x06, 0xCB, 0xF2, 0x8A, 0x23, 0x58, 0x3B, 0xB4, 0xF2, 0xBA, + 0x05, 0x6A, 0xFD, 0x0B, 0x01, 0xAE, 0xFF, 0x0D, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x19, 0x00, 0x8C, 0xFF, 0x4D, 0x01, 0xFE, 0xFC, 0x56, + 0x06, 0xF7, 0xF1, 0xBF, 0x37, 0x15, 0x28, 0x18, 0xF2, 0x25, 0x07, + 0x34, 0xFC, 0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, + 0x00, 0xCF, 0xFF, 0x52, 0x00, 0xC0, 0xFF, 0xAC, 0xFF, 0x0C, 0x02, + 0xBC, 0xF8, 0x6D, 0x45, 0x8E, 0x12, 0x24, 0xF7, 0x4D, 0x05, 0xDB, + 0xFC, 0xAE, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x24, 0x00, 0x71, 0xFF, 0x43, 0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E, + 0xFB, 0x7E, 0x07, 0xAF, 0x48, 0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01, + 0xA3, 0xFE, 0xDD, 0x00, 0x99, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xDD, 0x01, 0x60, 0xFC, 0x6D, 0x06, 0x76, 0xF4, + 0xD8, 0x1B, 0x95, 0x40, 0xC3, 0xF4, 0x56, 0x04, 0x4E, 0xFE, 0x85, + 0x00, 0xF1, 0xFF, 0xF4, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x29, 0x00, + 0x60, 0xFF, 0xA2, 0x01, 0x7C, 0xFC, 0xFB, 0x06, 0x7C, 0xF1, 0x15, + 0x31, 0x73, 0x2F, 0x81, 0xF1, 0x10, 0x07, 0x67, 0xFC, 0xB1, 0x01, + 0x58, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x02, + 0x00, 0x63, 0x00, 0x8A, 0xFE, 0xF3, 0x03, 0x63, 0xF5, 0xA1, 0x41, + 0x12, 0x1A, 0xEB, 0xF4, 0x3F, 0x06, 0x72, 0xFC, 0xD7, 0x01, 0x39, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x91, 0xFF, 0xF1, 0x00, + 0x79, 0xFE, 0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48, 0x07, + 0x06, 0xC7, 0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78, 0xFF, + 0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x43, 0xFF, 0xBA, + 0x01, 0xBF, 0xFC, 0x8B, 0x05, 0x99, 0xF6, 0x43, 0x14, 0xA9, 0x44, + 0xDE, 0xF7, 0x85, 0x02, 0x65, 0xFF, 0xE7, 0xFF, 0x3F, 0x00, 0xD6, + 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD5, 0x01, + 0x3A, 0xFC, 0x2A, 0x07, 0xE3, 0xF1, 0xD1, 0x29, 0x46, 0x36, 0xC5, + 0xF1, 0x87, 0x06, 0xDA, 0xFC, 0x64, 0x01, 0x80, 0xFF, 0x1E, 0x00, + 0xFE, 0xFF, 0x01, 0x00, 0x08, 0x00, 0xBC, 0xFF, 0xEF, 0x00, 0x9A, + 0xFD, 0x72, 0x05, 0x16, 0xF3, 0xA5, 0x3C, 0xC4, 0x21, 0x21, 0xF3, + 0xE4, 0x06, 0x39, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E, 0xFF, 0xB6, 0x00, + 0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C, 0x31, 0xF9, 0x5A, + 0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF, 0x80, 0x01, 0x45, 0xFD, 0x6C, + 0x04, 0x0B, 0xF9, 0x0B, 0x0D, 0x73, 0x47, 0x02, 0xFC, 0x58, 0x00, + 0xA3, 0x00, 0x39, 0xFF, 0x94, 0x00, 0xB5, 0xFF, 0x12, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x37, 0xFC, 0xEB, 0x06, + 0x0B, 0xF3, 0x35, 0x22, 0x52, 0x3C, 0xFD, 0xF2, 0x84, 0x05, 0x8D, + 0xFD, 0xF6, 0x00, 0xB8, 0xFF, 0x09, 0x00, 0x01, 0x00, 0xFE, 0xFF, + 0x1D, 0x00, 0x83, 0xFF, 0x5E, 0x01, 0xE3, 0xFC, 0x7B, 0x06, 0xD0, + 0xF1, 0xA5, 0x36, 0x62, 0x29, 0xEF, 0xF1, 0x29, 0x07, 0x39, 0xFC, + 0xD7, 0x01, 0x42, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD5, + 0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77, 0xFF, 0x67, 0x02, 0x14, 0xF8, + 0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6, 0x7C, 0x05, 0xC5, 0xFC, 0xB7, + 0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, + 0x76, 0xFF, 0x35, 0x01, 0xE7, 0xFD, 0x26, 0x03, 0xA1, 0xFB, 0x64, + 0x06, 0xD2, 0x48, 0x21, 0x01, 0xE8, 0xFD, 0xF7, 0x01, 0x83, 0xFE, + 0xEC, 0x00, 0x93, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, + 0xFF, 0xD9, 0x01, 0x6D, 0xFC, 0x4B, 0x06, 0xCD, 0xF4, 0x83, 0x1A, + 0x5F, 0x41, 0x3A, 0xF5, 0x0C, 0x04, 0x7B, 0xFE, 0x6C, 0x00, 0xFE, + 0xFF, 0xEF, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5A, 0xFF, + 0xAD, 0x01, 0x6C, 0xFC, 0x0C, 0x07, 0x7F, 0xF1, 0xDC, 0x2F, 0xAD, + 0x30, 0x7D, 0xF1, 0x01, 0x07, 0x76, 0xFC, 0xA6, 0x01, 0x5E, 0xFF, + 0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D, + 0x00, 0x5D, 0xFE, 0x3E, 0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B, + 0x93, 0xF4, 0x62, 0x06, 0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0x19, 0x00, 0x97, 0xFF, 0xE2, 0x00, 0x98, 0xFE, + 0xCF, 0x01, 0x33, 0xFE, 0x7D, 0x00, 0xBB, 0x48, 0x1F, 0x07, 0x54, + 0xFB, 0x4C, 0x03, 0xD3, 0xFD, 0x3F, 0x01, 0x73, 0xFF, 0x23, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB1, 0x01, 0xD3, + 0xFC, 0x5D, 0x05, 0x01, 0xF7, 0xFB, 0x12, 0x3F, 0x45, 0x83, 0xF8, + 0x2A, 0x02, 0x9A, 0xFF, 0xCA, 0xFF, 0x4E, 0x00, 0xD1, 0xFF, 0x0C, + 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x40, 0xFF, 0xDA, 0x01, 0x35, 0xFC, + 0x27, 0x07, 0x09, 0xF2, 0x85, 0x28, 0x63, 0x37, 0xE9, 0xF1, 0x63, + 0x06, 0xF5, 0xFC, 0x53, 0x01, 0x89, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, + 0x00, 0x00, 0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8, + 0x05, 0xCC, 0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06, + 0x35, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, + 0x00, 0xB9, 0xFF, 0x8A, 0x00, 0x4D, 0xFF, 0x7D, 0x00, 0x9C, 0x00, + 0x7B, 0xFB, 0x31, 0x47, 0xD9, 0x0D, 0xC0, 0xF8, 0x8F, 0x04, 0x34, + 0xFD, 0x87, 0x01, 0x56, 0xFF, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x29, 0x00, 0x5E, 0xFF, 0x74, 0x01, 0x5F, 0xFD, 0x35, 0x04, 0x7C, + 0xF9, 0xD8, 0x0B, 0xC9, 0x47, 0xD4, 0xFC, 0xF0, 0xFF, 0xDD, 0x00, + 0x19, 0xFF, 0xA4, 0x00, 0xAF, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD5, 0x06, 0x4F, 0xF3, + 0xE0, 0x20, 0x45, 0x3D, 0x4D, 0xF3, 0x4B, 0x05, 0xB3, 0xFD, 0xE0, + 0x00, 0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00, + 0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1, 0xF1, 0x86, + 0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC, 0xD1, 0x01, + 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xDA, 0xFF, 0x36, + 0x00, 0xFA, 0xFF, 0x43, 0xFF, 0xBF, 0x02, 0x75, 0xF7, 0x42, 0x44, + 0x20, 0x15, 0x55, 0xF6, 0xA9, 0x05, 0xB2, 0xFC, 0xBF, 0x01, 0x41, + 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7C, 0xFF, + 0x27, 0x01, 0x05, 0xFE, 0xEB, 0x02, 0x14, 0xFC, 0x50, 0x05, 0xEA, + 0x48, 0x1B, 0x02, 0x78, 0xFD, 0x32, 0x02, 0x64, 0xFE, 0xFA, 0x00, + 0x8D, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD4, + 0x01, 0x7C, 0xFC, 0x27, 0x06, 0x28, 0xF5, 0x31, 0x19, 0x21, 0x42, + 0xB8, 0xF5, 0xC0, 0x03, 0xAA, 0xFE, 0x51, 0x00, 0x0B, 0x00, 0xEA, + 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x54, 0xFF, 0xB7, 0x01, + 0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1, 0x9F, 0x2E, 0xE3, 0x31, 0x7E, + 0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A, 0x01, 0x64, 0xFF, 0x28, 0x00, + 0xFD, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xE8, 0xFF, 0x96, 0x00, 0x31, + 0xFE, 0x84, 0x04, 0x79, 0xF4, 0x07, 0x40, 0xBA, 0x1C, 0x3E, 0xF4, + 0x82, 0x06, 0x58, 0xFC, 0xE0, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0x18, 0x00, 0x9D, 0xFF, 0xD3, 0x00, 0xB8, 0xFE, 0x93, 0x01, + 0xA1, 0xFE, 0x8E, 0xFF, 0x92, 0x48, 0x3D, 0x08, 0xE1, 0xFA, 0x86, + 0x03, 0xB6, 0xFD, 0x4C, 0x01, 0x6D, 0xFF, 0x25, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xA8, 0x01, 0xE9, 0xFC, 0x2D, + 0x05, 0x6B, 0xF7, 0xB6, 0x11, 0xC8, 0x45, 0x30, 0xF9, 0xCD, 0x01, + 0xD0, 0xFF, 0xAC, 0xFF, 0x5C, 0x00, 0xCB, 0xFF, 0x0D, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDF, 0x01, 0x33, 0xFC, 0x20, 0x07, + 0x35, 0xF2, 0x36, 0x27, 0x78, 0x38, 0x14, 0xF2, 0x3B, 0x06, 0x11, + 0xFD, 0x41, 0x01, 0x92, 0xFF, 0x17, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x10, 0x00, 0xA7, 0xFF, 0x19, 0x01, 0x53, 0xFD, 0xDB, 0x05, 0x88, + 0xF2, 0xAD, 0x3A, 0x6D, 0x24, 0xA4, 0xF2, 0x08, 0x07, 0x32, 0xFC, + 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBF, + 0xFF, 0x7B, 0x00, 0x6C, 0xFF, 0x44, 0x00, 0x01, 0x01, 0xB6, 0xFA, + 0xC8, 0x46, 0x13, 0x0F, 0x51, 0xF8, 0xC4, 0x04, 0x1B, 0xFD, 0x92, + 0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, + 0x63, 0xFF, 0x67, 0x01, 0x7A, 0xFD, 0xFE, 0x03, 0xEE, 0xF9, 0xAA, + 0x0A, 0x16, 0x48, 0xAC, 0xFD, 0x86, 0xFF, 0x17, 0x01, 0xFA, 0xFE, + 0xB3, 0x00, 0xAA, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE5, 0x01, 0x44, 0xFC, 0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F, + 0x31, 0x3E, 0xA5, 0xF3, 0x0F, 0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF, + 0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x73, 0xFF, + 0x7D, 0x01, 0xB3, 0xFC, 0xBB, 0x06, 0x9A, 0xF1, 0x60, 0x34, 0xF5, + 0x2B, 0xB0, 0xF1, 0x28, 0x07, 0x47, 0xFC, 0xCA, 0x01, 0x4A, 0xFF, + 0x30, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDF, 0xFF, 0x28, 0x00, 0x17, + 0x00, 0x10, 0xFF, 0x15, 0x03, 0xDD, 0xF6, 0x9E, 0x43, 0x6C, 0x16, + 0xF1, 0xF5, 0xD3, 0x05, 0x9F, 0xFC, 0xC6, 0x01, 0x3F, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81, 0xFF, 0x19, 0x01, + 0x23, 0xFE, 0xB0, 0x02, 0x87, 0xFC, 0x41, 0x04, 0xF4, 0x48, 0x1C, + 0x03, 0x06, 0xFD, 0x6E, 0x02, 0x45, 0xFE, 0x09, 0x01, 0x88, 0xFF, + 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCE, 0x01, 0x8C, + 0xFC, 0x00, 0x06, 0x86, 0xF5, 0xE0, 0x17, 0xDB, 0x42, 0x3F, 0xF6, + 0x71, 0x03, 0xD9, 0xFE, 0x36, 0x00, 0x18, 0x00, 0xE5, 0xFF, 0x07, + 0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4F, 0xFF, 0xC1, 0x01, 0x52, 0xFC, + 0x22, 0x07, 0x98, 0xF1, 0x5E, 0x2D, 0x13, 0x33, 0x87, 0xF1, 0xD8, + 0x06, 0x9B, 0xFC, 0x8D, 0x01, 0x6B, 0xFF, 0x25, 0x00, 0xFD, 0xFF, + 0x03, 0x00, 0xFC, 0xFF, 0xDC, 0xFF, 0xAF, 0x00, 0x07, 0xFE, 0xC8, + 0x04, 0x10, 0xF4, 0x2D, 0x3F, 0x0F, 0x1E, 0xED, 0xF3, 0xA0, 0x06, + 0x4E, 0xFC, 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, + 0x00, 0xA3, 0xFF, 0xC3, 0x00, 0xD7, 0xFE, 0x58, 0x01, 0x0F, 0xFF, + 0xA6, 0xFE, 0x5D, 0x48, 0x61, 0x09, 0x6E, 0xFA, 0xC0, 0x03, 0x99, + 0xFD, 0x59, 0x01, 0x68, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x2E, 0x00, 0x4E, 0xFF, 0x9E, 0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7, + 0xF7, 0x75, 0x10, 0x48, 0x46, 0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00, + 0x8E, 0xFF, 0x6B, 0x00, 0xC6, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, + 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x16, 0x07, 0x67, 0xF2, + 0xE5, 0x25, 0x87, 0x39, 0x47, 0xF2, 0x10, 0x06, 0x30, 0xFD, 0x2F, + 0x01, 0x9C, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x13, 0x00, + 0x9D, 0xFF, 0x2D, 0x01, 0x33, 0xFD, 0x0B, 0x06, 0x4D, 0xF2, 0xA5, + 0x39, 0xBF, 0x25, 0x6D, 0xF2, 0x15, 0x07, 0x31, 0xFC, 0xE2, 0x01, + 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, 0xC5, 0xFF, 0x6D, + 0x00, 0x8B, 0xFF, 0x0D, 0x00, 0x63, 0x01, 0xF9, 0xF9, 0x55, 0x46, + 0x51, 0x10, 0xE3, 0xF7, 0xF7, 0x04, 0x03, 0xFD, 0x9D, 0x01, 0x4E, + 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x68, 0xFF, + 0x5B, 0x01, 0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09, 0x57, + 0x48, 0x8D, 0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2, 0x00, + 0xA4, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, + 0x01, 0x4D, 0xFC, 0xA3, 0x06, 0xE4, 0xF3, 0x36, 0x1E, 0x16, 0x3F, + 0x05, 0xF4, 0xCF, 0x04, 0x02, 0xFE, 0xB2, 0x00, 0xDB, 0xFF, 0xFC, + 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6C, 0xFF, 0x8B, 0x01, + 0x9D, 0xFC, 0xD5, 0x06, 0x89, 0xF1, 0x35, 0x33, 0x3A, 0x2D, 0x9A, + 0xF1, 0x23, 0x07, 0x51, 0xFC, 0xC2, 0x01, 0x4F, 0xFF, 0x2F, 0x00, + 0xFD, 0xFF, 0x07, 0x00, 0xE5, 0xFF, 0x1A, 0x00, 0x33, 0x00, 0xDF, + 0xFE, 0x68, 0x03, 0x4E, 0xF6, 0xEE, 0x42, 0xBB, 0x17, 0x90, 0xF5, + 0xFC, 0x05, 0x8E, 0xFC, 0xCD, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42, 0xFE, 0x74, 0x02, + 0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04, 0x94, 0xFC, 0xA9, + 0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF, 0xC7, 0x01, 0x9D, 0xFC, 0xD8, + 0x05, 0xE7, 0xF5, 0x91, 0x16, 0x89, 0x43, 0xCD, 0xF6, 0x1E, 0x03, + 0x0B, 0xFF, 0x1A, 0x00, 0x26, 0x00, 0xE0, 0xFF, 0x08, 0x00, 0xFD, + 0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC9, 0x01, 0x48, 0xFC, 0x28, 0x07, + 0xAD, 0xF1, 0x19, 0x2C, 0x3F, 0x34, 0x97, 0xF1, 0xBE, 0x06, 0xB0, + 0xFC, 0x7F, 0x01, 0x72, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x02, 0x00, + 0x00, 0x00, 0xD0, 0xFF, 0xC7, 0x00, 0xDE, 0xFD, 0x08, 0x05, 0xB0, + 0xF3, 0x4A, 0x3E, 0x64, 0x1F, 0xA0, 0xF3, 0xBB, 0x06, 0x45, 0xFC, + 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x15, 0x00, 0xA9, + 0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D, 0x01, 0x7A, 0xFF, 0xC5, 0xFD, + 0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9, 0xF8, 0x03, 0x7D, 0xFD, 0x66, + 0x01, 0x63, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, + 0x52, 0xFF, 0x93, 0x01, 0x18, 0xFD, 0xC9, 0x04, 0x45, 0xF8, 0x36, + 0x0F, 0xBB, 0x46, 0xA1, 0xFA, 0x0C, 0x01, 0x3E, 0x00, 0x70, 0xFF, + 0x7A, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, + 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x09, 0x07, 0x9D, 0xF2, 0x92, 0x24, + 0x8F, 0x3A, 0x82, 0xF2, 0xE1, 0x05, 0x50, 0xFD, 0x1B, 0x01, 0xA6, + 0xFF, 0x10, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x17, 0x00, 0x93, 0xFF, + 0x3F, 0x01, 0x15, 0xFD, 0x36, 0x06, 0x19, 0xF2, 0x97, 0x38, 0x11, + 0x27, 0x3B, 0xF2, 0x1F, 0x07, 0x32, 0xFC, 0xDF, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9, + 0xFF, 0xD6, 0xFF, 0xC3, 0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11, + 0x77, 0xF7, 0x28, 0x05, 0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6D, 0xFF, 0x4E, 0x01, + 0xB3, 0xFD, 0x8D, 0x03, 0xD4, 0xFA, 0x5D, 0x08, 0x8D, 0x48, 0x74, + 0xFF, 0xAE, 0xFE, 0x8D, 0x01, 0xBB, 0xFE, 0xD1, 0x00, 0x9E, 0xFF, + 0x18, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE0, 0x01, 0x57, + 0xFC, 0x85, 0x06, 0x34, 0xF4, 0xE0, 0x1C, 0xF0, 0x3F, 0x6D, 0xF4, + 0x8C, 0x04, 0x2C, 0xFE, 0x99, 0x00, 0xE7, 0xFF, 0xF8, 0xFF, 0x04, + 0x00, 0xFD, 0xFF, 0x27, 0x00, 0x65, 0xFF, 0x98, 0x01, 0x8A, 0xFC, + 0xEC, 0x06, 0x7F, 0xF1, 0x04, 0x32, 0x7B, 0x2E, 0x8A, 0xF1, 0x1A, + 0x07, 0x5D, 0xFC, 0xB8, 0x01, 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, + 0x06, 0x00, 0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8, + 0x03, 0xC7, 0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06, + 0x7D, 0xFC, 0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, + 0x00, 0x8D, 0xFF, 0xFC, 0x00, 0x61, 0xFE, 0x39, 0x02, 0x6B, 0xFD, + 0x37, 0x02, 0xEB, 0x48, 0x31, 0x05, 0x21, 0xFC, 0xE4, 0x02, 0x08, + 0xFE, 0x26, 0x01, 0x7C, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x32, 0x00, 0x41, 0xFF, 0xC0, 0x01, 0xAF, 0xFC, 0xAD, 0x05, 0x4A, + 0xF6, 0x44, 0x15, 0x2F, 0x44, 0x64, 0xF7, 0xC9, 0x02, 0x3D, 0xFF, + 0xFE, 0xFF, 0x34, 0x00, 0xDB, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x32, + 0x00, 0x47, 0xFF, 0xD0, 0x01, 0x40, 0xFC, 0x2A, 0x07, 0xCA, 0xF1, + 0xD1, 0x2A, 0x65, 0x35, 0xAE, 0xF1, 0xA0, 0x06, 0xC7, 0xFC, 0x70, + 0x01, 0x7A, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x05, 0x00, + 0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56, 0xF3, 0x61, + 0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC, 0xE6, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA5, + 0x00, 0x16, 0xFF, 0xE3, 0x00, 0xE4, 0xFF, 0xEB, 0xFC, 0xD2, 0x47, + 0xB6, 0x0B, 0x89, 0xF9, 0x2F, 0x04, 0x62, 0xFD, 0x72, 0x01, 0x5E, + 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x56, 0xFF, + 0x88, 0x01, 0x31, 0xFD, 0x95, 0x04, 0xB4, 0xF8, 0xFC, 0x0D, 0x26, + 0x47, 0x64, 0xFB, 0xA7, 0x00, 0x77, 0x00, 0x51, 0xFF, 0x89, 0x00, + 0xBA, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, + 0x01, 0x34, 0xFC, 0xF9, 0x06, 0xD9, 0xF2, 0x3F, 0x23, 0x90, 0x3B, + 0xC4, 0xF2, 0xAE, 0x05, 0x72, 0xFD, 0x07, 0x01, 0xB0, 0xFF, 0x0C, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8A, 0xFF, 0x51, 0x01, + 0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1, 0x82, 0x37, 0x60, 0x28, 0x0E, + 0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40, 0xFF, 0x34, 0x00, + 0xFD, 0xFF, 0x0C, 0x00, 0xD0, 0xFF, 0x4F, 0x00, 0xC7, 0xFF, 0xA0, + 0xFF, 0x20, 0x02, 0x96, 0xF8, 0x4E, 0x45, 0xD7, 0x12, 0x0D, 0xF7, + 0x58, 0x05, 0xD6, 0xFC, 0xB0, 0x01, 0x47, 0xFF, 0x30, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x23, 0x00, 0x72, 0xFF, 0x40, 0x01, 0xD0, 0xFD, + 0x53, 0x03, 0x47, 0xFB, 0x3F, 0x07, 0xB8, 0x48, 0x62, 0x00, 0x3F, + 0xFE, 0xC8, 0x01, 0x9C, 0xFE, 0xE0, 0x00, 0x98, 0xFF, 0x19, 0x00, + 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDC, 0x01, 0x63, 0xFC, 0x66, + 0x06, 0x89, 0xF4, 0x8C, 0x1B, 0xC3, 0x40, 0xDD, 0xF4, 0x46, 0x04, + 0x58, 0xFE, 0x80, 0x00, 0xF4, 0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFD, + 0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA5, 0x01, 0x78, 0xFC, 0xFF, 0x06, + 0x7D, 0xF1, 0xCF, 0x30, 0xB8, 0x2F, 0x80, 0xF1, 0x0D, 0x07, 0x6A, + 0xFC, 0xAE, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00, + 0xEF, 0xFF, 0xFF, 0xFF, 0x69, 0x00, 0x80, 0xFE, 0x04, 0x04, 0x48, + 0xF5, 0x74, 0x41, 0x5D, 0x1A, 0xD7, 0xF4, 0x47, 0x06, 0x6F, 0xFC, + 0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x93, + 0xFF, 0xED, 0x00, 0x80, 0xFE, 0xFD, 0x01, 0xDC, 0xFD, 0x3C, 0x01, + 0xD5, 0x48, 0x45, 0x06, 0xAE, 0xFB, 0x1F, 0x03, 0xEA, 0xFD, 0x34, + 0x01, 0x77, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, + 0x44, 0xFF, 0xB8, 0x01, 0xC3, 0xFC, 0x81, 0x05, 0xB0, 0xF6, 0xFA, + 0x13, 0xCC, 0x44, 0x02, 0xF8, 0x71, 0x02, 0x71, 0xFF, 0xE1, 0xFF, + 0x42, 0x00, 0xD5, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x43, + 0xFF, 0xD6, 0x01, 0x39, 0xFC, 0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29, + 0x85, 0x36, 0xCC, 0xF1, 0x7F, 0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82, + 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x09, 0x00, 0xBA, 0xFF, + 0xF4, 0x00, 0x91, 0xFD, 0x7E, 0x05, 0x05, 0xF3, 0x6E, 0x3C, 0x10, + 0x22, 0x12, 0xF3, 0xE9, 0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB5, 0xFF, 0x96, 0x00, 0x35, + 0xFF, 0xA9, 0x00, 0x4D, 0x00, 0x19, 0xFC, 0x7C, 0x47, 0xE8, 0x0C, + 0x18, 0xF9, 0x66, 0x04, 0x48, 0xFD, 0x7E, 0x01, 0x5A, 0xFF, 0x2B, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5A, 0xFF, 0x7D, 0x01, + 0x4B, 0xFD, 0x60, 0x04, 0x24, 0xF9, 0xC6, 0x0C, 0x86, 0x47, 0x30, + 0xFC, 0x41, 0x00, 0xB0, 0x00, 0x32, 0xFF, 0x98, 0x00, 0xB4, 0xFF, + 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x38, + 0xFC, 0xE6, 0x06, 0x19, 0xF3, 0xEA, 0x21, 0x8A, 0x3C, 0x0E, 0xF3, + 0x78, 0x05, 0x96, 0xFD, 0xF1, 0x00, 0xBB, 0xFF, 0x08, 0x00, 0x01, + 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x81, 0xFF, 0x62, 0x01, 0xDD, 0xFC, + 0x83, 0x06, 0xC9, 0xF1, 0x66, 0x36, 0xAC, 0x29, 0xE7, 0xF1, 0x2A, + 0x07, 0x3A, 0xFC, 0xD5, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x0B, 0x00, 0xD6, 0xFF, 0x41, 0x00, 0xE4, 0xFF, 0x6B, 0xFF, 0x7B, + 0x02, 0xF0, 0xF7, 0xBA, 0x44, 0x1E, 0x14, 0xA5, 0xF6, 0x86, 0x05, + 0xC1, 0xFC, 0xB9, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x22, 0x00, 0x77, 0xFF, 0x32, 0x01, 0xED, 0xFD, 0x19, 0x03, + 0xBB, 0xFB, 0x26, 0x06, 0xD7, 0x48, 0x58, 0x01, 0xCF, 0xFD, 0x04, + 0x02, 0x7D, 0xFE, 0xEF, 0x00, 0x92, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, + 0x35, 0x00, 0x39, 0xFF, 0xD8, 0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1, + 0xF4, 0x38, 0x1A, 0x8C, 0x41, 0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE, + 0x66, 0x00, 0x01, 0x00, 0xEE, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2B, + 0x00, 0x59, 0xFF, 0xB0, 0x01, 0x69, 0xFC, 0x0F, 0x07, 0x80, 0xF1, + 0x96, 0x2F, 0xF2, 0x30, 0x7C, 0xF1, 0xFD, 0x06, 0x7A, 0xFC, 0xA3, + 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF4, 0xFF, + 0xF2, 0xFF, 0x83, 0x00, 0x53, 0xFE, 0x4E, 0x04, 0xD0, 0xF4, 0xAB, + 0x40, 0xB2, 0x1B, 0x7F, 0xF4, 0x69, 0x06, 0x62, 0xFC, 0xDD, 0x01, + 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, 0x98, 0xFF, 0xDE, + 0x00, 0x9F, 0xFE, 0xC2, 0x01, 0x4B, 0xFE, 0x48, 0x00, 0xB3, 0x48, + 0x5E, 0x07, 0x3B, 0xFB, 0x59, 0x03, 0xCD, 0xFD, 0x42, 0x01, 0x71, + 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x47, 0xFF, + 0xAF, 0x01, 0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12, 0x5C, + 0x45, 0xA9, 0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51, 0x00, + 0xD0, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x40, 0xFF, 0xDB, + 0x01, 0x35, 0xFC, 0x25, 0x07, 0x13, 0xF2, 0x3A, 0x28, 0xA0, 0x37, + 0xF2, 0xF1, 0x5A, 0x06, 0xFB, 0xFC, 0x4F, 0x01, 0x8B, 0xFF, 0x1A, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0D, 0x00, 0xAF, 0xFF, 0x09, 0x01, + 0x6E, 0xFD, 0xB4, 0x05, 0xBC, 0xF2, 0x73, 0x3B, 0x64, 0x23, 0xD2, + 0xF2, 0xFB, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x11, 0x00, 0xBB, 0xFF, 0x87, 0x00, 0x54, 0xFF, 0x70, + 0x00, 0xB3, 0x00, 0x4E, 0xFB, 0x1A, 0x47, 0x1F, 0x0E, 0xA8, 0xF8, + 0x9B, 0x04, 0x2E, 0xFD, 0x8A, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71, 0x01, 0x65, 0xFD, + 0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47, 0x03, 0xFD, 0xD9, + 0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE, 0xFF, 0x14, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3E, 0xFC, 0xD0, + 0x06, 0x5E, 0xF3, 0x94, 0x20, 0x7B, 0x3D, 0x60, 0xF3, 0x3E, 0x05, + 0xBB, 0xFD, 0xDB, 0x00, 0xC6, 0xFF, 0x04, 0x00, 0x02, 0x00, 0xFE, + 0xFF, 0x20, 0x00, 0x79, 0xFF, 0x72, 0x01, 0xC4, 0xFC, 0xA4, 0x06, + 0xAB, 0xF1, 0x46, 0x35, 0xF7, 0x2A, 0xC6, 0xF1, 0x2A, 0x07, 0x40, + 0xFC, 0xCF, 0x01, 0x47, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, + 0xDB, 0xFF, 0x33, 0x00, 0x01, 0x00, 0x38, 0xFF, 0xD3, 0x02, 0x53, + 0xF7, 0x1F, 0x44, 0x69, 0x15, 0x3F, 0xF6, 0xB2, 0x05, 0xAD, 0xFC, + 0xC1, 0x01, 0x41, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, + 0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C, 0xFE, 0xDE, 0x02, 0x2E, 0xFC, + 0x13, 0x05, 0xEC, 0x48, 0x54, 0x02, 0x5E, 0xFD, 0x3F, 0x02, 0x5D, + 0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x3B, 0xFF, 0xD3, 0x01, 0x7F, 0xFC, 0x1F, 0x06, 0x3C, 0xF5, 0xE6, + 0x18, 0x4D, 0x42, 0xD5, 0xF5, 0xAF, 0x03, 0xB4, 0xFE, 0x4B, 0x00, + 0x0E, 0x00, 0xE9, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x53, + 0xFF, 0xBA, 0x01, 0x5B, 0xFC, 0x1B, 0x07, 0x8B, 0xF1, 0x58, 0x2E, + 0x26, 0x32, 0x80, 0xF1, 0xEA, 0x06, 0x8C, 0xFC, 0x97, 0x01, 0x66, + 0xFF, 0x27, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF8, 0xFF, 0xE6, 0xFF, + 0x9C, 0x00, 0x27, 0xFE, 0x94, 0x04, 0x61, 0xF4, 0xD7, 0x3F, 0x06, + 0x1D, 0x2B, 0xF4, 0x89, 0x06, 0x56, 0xFC, 0xE0, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF, + 0xFE, 0x86, 0x01, 0xBA, 0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08, + 0xC7, 0xFA, 0x93, 0x03, 0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4B, 0xFF, 0xA6, 0x01, + 0xEE, 0xFC, 0x23, 0x05, 0x83, 0xF7, 0x6E, 0x11, 0xE5, 0x45, 0x57, + 0xF9, 0xB8, 0x01, 0xDC, 0xFF, 0xA5, 0xFF, 0x5F, 0x00, 0xCA, 0xFF, + 0x0D, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3D, 0xFF, 0xDF, 0x01, 0x32, + 0xFC, 0x1E, 0x07, 0x40, 0xF2, 0xEB, 0x26, 0xB5, 0x38, 0x1F, 0xF2, + 0x32, 0x06, 0x18, 0xFD, 0x3D, 0x01, 0x94, 0xFF, 0x16, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x11, 0x00, 0xA4, 0xFF, 0x1D, 0x01, 0x4C, 0xFD, + 0xE6, 0x05, 0x7B, 0xF2, 0x71, 0x3A, 0xB8, 0x24, 0x97, 0xF2, 0x0B, + 0x07, 0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x0F, 0x00, 0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17, + 0x01, 0x8B, 0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04, + 0x15, 0xFD, 0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x28, 0x00, 0x64, 0xFF, 0x65, 0x01, 0x81, 0xFD, 0xF2, 0x03, + 0x08, 0xFA, 0x68, 0x0A, 0x25, 0x48, 0xDE, 0xFD, 0x6E, 0xFF, 0x24, + 0x01, 0xF3, 0xFE, 0xB6, 0x00, 0xA8, 0xFF, 0x15, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x46, 0xFC, 0xB8, 0x06, 0xA8, + 0xF3, 0x3F, 0x1F, 0x64, 0x3E, 0xBA, 0xF3, 0x01, 0x05, 0xE2, 0xFD, + 0xC4, 0x00, 0xD2, 0xFF, 0x00, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x23, + 0x00, 0x71, 0xFF, 0x81, 0x01, 0xAE, 0xFC, 0xC1, 0x06, 0x95, 0xF1, + 0x1E, 0x34, 0x3E, 0x2C, 0xAB, 0xF1, 0x27, 0x07, 0x49, 0xFC, 0xC8, + 0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE1, 0xFF, + 0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD, 0xF6, 0x77, + 0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC, 0xC8, 0x01, + 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83, + 0xFF, 0x16, 0x01, 0x2A, 0xFE, 0xA3, 0x02, 0xA1, 0xFC, 0x06, 0x04, + 0xF5, 0x48, 0x56, 0x03, 0xED, 0xFC, 0x7B, 0x02, 0x3E, 0xFE, 0x0C, + 0x01, 0x86, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, + 0xCC, 0x01, 0x8F, 0xFC, 0xF8, 0x05, 0x9B, 0xF5, 0x96, 0x17, 0x02, + 0x43, 0x5E, 0xF6, 0x5F, 0x03, 0xE4, 0xFE, 0x30, 0x00, 0x1B, 0x00, + 0xE4, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3, + 0x01, 0x4F, 0xFC, 0x24, 0x07, 0x9C, 0xF1, 0x17, 0x2D, 0x57, 0x33, + 0x8A, 0xF1, 0xD3, 0x06, 0x9F, 0xFC, 0x8A, 0x01, 0x6D, 0xFF, 0x25, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0xD9, 0xFF, 0xB4, 0x00, + 0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3, 0xFC, 0x3E, 0x5B, 0x1E, 0xDB, + 0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC0, 0x00, 0xDE, 0xFE, 0x4B, + 0x01, 0x27, 0xFF, 0x73, 0xFE, 0x4F, 0x48, 0xA2, 0x09, 0x54, 0xFA, + 0xCC, 0x03, 0x93, 0xFD, 0x5C, 0x01, 0x67, 0xFF, 0x27, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9C, 0x01, 0x05, 0xFD, + 0xF1, 0x04, 0xF0, 0xF7, 0x2D, 0x10, 0x61, 0x46, 0x0D, 0xFA, 0x58, + 0x01, 0x13, 0x00, 0x87, 0xFF, 0x6E, 0x00, 0xC4, 0xFF, 0x0E, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x14, + 0x07, 0x73, 0xF2, 0x99, 0x25, 0xC2, 0x39, 0x54, 0xF2, 0x05, 0x06, + 0x37, 0xFD, 0x2B, 0x01, 0x9E, 0xFF, 0x13, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0x14, 0x00, 0x9B, 0xFF, 0x31, 0x01, 0x2C, 0xFD, 0x15, 0x06, + 0x41, 0xF2, 0x6A, 0x39, 0x0A, 0x26, 0x61, 0xF2, 0x17, 0x07, 0x31, + 0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, + 0xC6, 0xFF, 0x69, 0x00, 0x91, 0xFF, 0x00, 0x00, 0x78, 0x01, 0xD0, + 0xF9, 0x39, 0x46, 0x98, 0x10, 0xCB, 0xF7, 0x02, 0x05, 0xFE, 0xFC, + 0x9F, 0x01, 0x4D, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, + 0x00, 0x69, 0xFF, 0x58, 0x01, 0x9D, 0xFD, 0xB9, 0x03, 0x7B, 0xFA, + 0x40, 0x09, 0x63, 0x48, 0xBF, 0xFE, 0x03, 0xFF, 0x5F, 0x01, 0xD4, + 0xFE, 0xC5, 0x00, 0xA2, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE2, 0x01, 0x4F, 0xFC, 0x9C, 0x06, 0xF5, 0xF3, 0xEA, + 0x1D, 0x47, 0x3F, 0x1B, 0xF4, 0xC1, 0x04, 0x0B, 0xFE, 0xAC, 0x00, + 0xDE, 0xFF, 0xFB, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6A, + 0xFF, 0x8E, 0x01, 0x99, 0xFC, 0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32, + 0x82, 0x2D, 0x96, 0xF1, 0x21, 0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50, + 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE6, 0xFF, 0x17, 0x00, + 0x39, 0x00, 0xD4, 0xFE, 0x7A, 0x03, 0x2F, 0xF6, 0xC7, 0x42, 0x06, + 0x18, 0x7B, 0xF5, 0x05, 0x06, 0x8A, 0xFC, 0xCF, 0x01, 0x3C, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x88, 0xFF, 0x07, 0x01, 0x49, + 0xFE, 0x67, 0x02, 0x13, 0xFD, 0xFF, 0x02, 0xF4, 0x48, 0x5F, 0x04, + 0x7A, 0xFC, 0xB6, 0x02, 0x20, 0xFE, 0x1B, 0x01, 0x81, 0xFF, 0x1F, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC6, 0x01, + 0xA1, 0xFC, 0xCF, 0x05, 0xFC, 0xF5, 0x47, 0x16, 0xB0, 0x43, 0xEE, + 0xF6, 0x0C, 0x03, 0x16, 0xFF, 0x14, 0x00, 0x29, 0x00, 0xDF, 0xFF, + 0x09, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xCA, 0x01, 0x46, + 0xFC, 0x29, 0x07, 0xB3, 0xF1, 0xD1, 0x2B, 0x81, 0x34, 0x9C, 0xF1, + 0xB8, 0x06, 0xB5, 0xFC, 0x7C, 0x01, 0x74, 0xFF, 0x22, 0x00, 0xFE, + 0xFF, 0x02, 0x00, 0x01, 0x00, 0xCE, 0xFF, 0xCC, 0x00, 0xD5, 0xFD, + 0x16, 0x05, 0x9B, 0xF3, 0x18, 0x3E, 0xB1, 0x1F, 0x8F, 0xF3, 0xC0, + 0x06, 0x43, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x15, 0x00, 0xAA, 0xFF, 0xB1, 0x00, 0xFE, 0xFE, 0x10, 0x01, 0x92, + 0xFF, 0x94, 0xFD, 0x0D, 0x48, 0xCB, 0x0A, 0xE2, 0xF9, 0x04, 0x04, + 0x77, 0xFD, 0x69, 0x01, 0x62, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x91, 0x01, 0x1E, 0xFD, 0xBE, 0x04, + 0x5E, 0xF8, 0xF0, 0x0E, 0xD3, 0x46, 0xCB, 0xFA, 0xF6, 0x00, 0x4B, + 0x00, 0x69, 0xFF, 0x7D, 0x00, 0xBE, 0xFF, 0x10, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA, + 0xF2, 0x46, 0x24, 0xC8, 0x3A, 0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD, + 0x17, 0x01, 0xA8, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, + 0x00, 0x91, 0xFF, 0x43, 0x01, 0x0E, 0xFD, 0x40, 0x06, 0x0F, 0xF2, + 0x5B, 0x38, 0x5C, 0x27, 0x30, 0xF2, 0x21, 0x07, 0x33, 0xFC, 0xDE, + 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCC, 0xFF, + 0x5A, 0x00, 0xAF, 0xFF, 0xCA, 0xFF, 0xD8, 0x01, 0x1C, 0xF9, 0xB8, + 0x45, 0xDA, 0x11, 0x60, 0xF7, 0x33, 0x05, 0xE7, 0xFC, 0xA9, 0x01, + 0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6E, + 0xFF, 0x4B, 0x01, 0xB9, 0xFD, 0x80, 0x03, 0xEE, 0xFA, 0x1D, 0x08, + 0x98, 0x48, 0xA8, 0xFF, 0x95, 0xFE, 0x9A, 0x01, 0xB4, 0xFE, 0xD4, + 0x00, 0x9C, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xDF, 0x01, 0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C, 0x1F, + 0x40, 0x85, 0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA, 0xFF, + 0xF7, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9B, + 0x01, 0x86, 0xFC, 0xF1, 0x06, 0x7E, 0xF1, 0xC0, 0x31, 0xC2, 0x2E, + 0x87, 0xF1, 0x17, 0x07, 0x5F, 0xFC, 0xB6, 0x01, 0x55, 0xFF, 0x2D, + 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEB, 0xFF, 0x09, 0x00, 0x54, 0x00, + 0xA4, 0xFE, 0xC9, 0x03, 0xAA, 0xF5, 0x0C, 0x42, 0x56, 0x19, 0x1E, + 0xF5, 0x2B, 0x06, 0x7A, 0xFC, 0xD4, 0x01, 0x3A, 0xFF, 0x35, 0x00, + 0xFE, 0xFF, 0x1C, 0x00, 0x8E, 0xFF, 0xF9, 0x00, 0x68, 0xFE, 0x2C, + 0x02, 0x84, 0xFD, 0xFF, 0x01, 0xE6, 0x48, 0x6E, 0x05, 0x07, 0xFC, + 0xF1, 0x02, 0x01, 0xFE, 0x29, 0x01, 0x7B, 0xFF, 0x21, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE, 0x01, 0xB4, 0xFC, + 0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44, 0x86, 0xF7, 0xB6, + 0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9, 0xFF, 0x0A, 0x00, + 0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF, 0xD1, 0x01, 0x3E, 0xFC, 0x2B, + 0x07, 0xD0, 0xF1, 0x89, 0x2A, 0xA6, 0x35, 0xB4, 0xF1, 0x99, 0x06, + 0xCD, 0xFC, 0x6D, 0x01, 0x7C, 0xFF, 0x1F, 0x00, 0xFE, 0xFF, 0x01, + 0x00, 0x06, 0x00, 0xC2, 0xFF, 0xE3, 0x00, 0xAE, 0xFD, 0x52, 0x05, + 0x44, 0xF3, 0x2A, 0x3D, 0x06, 0x21, 0x47, 0xF3, 0xD8, 0x06, 0x3C, + 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, + 0xB0, 0xFF, 0xA2, 0x00, 0x1D, 0xFF, 0xD6, 0x00, 0xFC, 0xFF, 0xBC, + 0xFC, 0xC0, 0x47, 0xFA, 0x0B, 0x70, 0xF9, 0x3C, 0x04, 0x5C, 0xFD, + 0x75, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, + 0x00, 0x57, 0xFF, 0x86, 0x01, 0x36, 0xFD, 0x89, 0x04, 0xCD, 0xF8, + 0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB, 0x91, 0x00, 0x83, 0x00, 0x4A, + 0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF5, 0x06, 0xE7, 0xF2, 0xF2, + 0x22, 0xC7, 0x3B, 0xD4, 0xF2, 0xA2, 0x05, 0x7A, 0xFD, 0x02, 0x01, + 0xB2, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x88, + 0xFF, 0x55, 0x01, 0xF2, 0xFC, 0x67, 0x06, 0xE4, 0xF1, 0x44, 0x37, + 0xAA, 0x28, 0x05, 0xF2, 0x27, 0x07, 0x36, 0xFC, 0xDA, 0x01, 0x41, + 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD2, 0xFF, 0x4C, 0x00, + 0xCD, 0xFF, 0x94, 0xFF, 0x34, 0x02, 0x70, 0xF8, 0x2E, 0x45, 0x20, + 0x13, 0xF6, 0xF6, 0x62, 0x05, 0xD1, 0xFC, 0xB2, 0x01, 0x46, 0xFF, + 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D, + 0x01, 0xD6, 0xFD, 0x46, 0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48, + 0x98, 0x00, 0x26, 0xFE, 0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96, + 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDB, 0x01, + 0x66, 0xFC, 0x5E, 0x06, 0x9C, 0xF4, 0x40, 0x1B, 0xEF, 0x40, 0xF7, + 0xF4, 0x35, 0x04, 0x62, 0xFE, 0x7A, 0x00, 0xF7, 0xFF, 0xF2, 0xFF, + 0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5D, 0xFF, 0xA7, 0x01, 0x75, + 0xFC, 0x03, 0x07, 0x7D, 0xF1, 0x8A, 0x30, 0xFF, 0x2F, 0x7E, 0xF1, + 0x0A, 0x07, 0x6E, 0xFC, 0xAC, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0xFD, + 0xFF, 0x05, 0x00, 0xF0, 0xFF, 0xFC, 0xFF, 0x6E, 0x00, 0x76, 0xFE, + 0x15, 0x04, 0x2C, 0xF5, 0x49, 0x41, 0xA9, 0x1A, 0xC3, 0xF4, 0x4F, + 0x06, 0x6C, 0xFC, 0xD9, 0x01, 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, + 0x1A, 0x00, 0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5, + 0xFD, 0x05, 0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03, + 0xE4, 0xFD, 0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x31, 0x00, 0x45, 0xFF, 0xB6, 0x01, 0xC8, 0xFC, 0x77, 0x05, + 0xC7, 0xF6, 0xB1, 0x13, 0xED, 0x44, 0x26, 0xF8, 0x5D, 0x02, 0x7D, + 0xFF, 0xDA, 0xFF, 0x46, 0x00, 0xD4, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, + 0x33, 0x00, 0x42, 0xFF, 0xD7, 0x01, 0x38, 0xFC, 0x29, 0x07, 0xF3, + 0xF1, 0x3E, 0x29, 0xC6, 0x36, 0xD4, 0xF1, 0x77, 0x06, 0xE6, 0xFC, + 0x5C, 0x01, 0x84, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A, + 0x00, 0xB7, 0xFF, 0xF9, 0x00, 0x89, 0xFD, 0x8A, 0x05, 0xF4, 0xF2, + 0x37, 0x3C, 0x5B, 0x22, 0x03, 0xF3, 0xED, 0x06, 0x37, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB6, 0xFF, + 0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB, 0xFB, 0x69, + 0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD, 0x81, 0x01, + 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5B, + 0xFF, 0x7A, 0x01, 0x50, 0xFD, 0x54, 0x04, 0x3D, 0xF9, 0x82, 0x0C, + 0x9A, 0x47, 0x5E, 0xFC, 0x2A, 0x00, 0xBD, 0x00, 0x2B, 0xFF, 0x9B, + 0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE6, 0x01, 0x3A, 0xFC, 0xE2, 0x06, 0x28, 0xF3, 0x9E, 0x21, 0xC0, + 0x3C, 0x1F, 0xF3, 0x6C, 0x05, 0x9E, 0xFD, 0xED, 0x00, 0xBD, 0xFF, + 0x07, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x80, 0xFF, 0x66, + 0x01, 0xD8, 0xFC, 0x8B, 0x06, 0xC1, 0xF1, 0x27, 0x36, 0xF6, 0x29, + 0xDF, 0xF1, 0x2A, 0x07, 0x3B, 0xFC, 0xD4, 0x01, 0x44, 0xFF, 0x32, + 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD7, 0xFF, 0x3E, 0x00, 0xEA, 0xFF, + 0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7, 0x99, 0x44, 0x68, 0x14, 0x8E, + 0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA, 0x01, 0x43, 0xFF, 0x32, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x79, 0xFF, 0x2F, 0x01, 0xF4, + 0xFD, 0x0C, 0x03, 0xD4, 0xFB, 0xE9, 0x05, 0xDE, 0x48, 0x8F, 0x01, + 0xB6, 0xFD, 0x11, 0x02, 0x76, 0xFE, 0xF2, 0x00, 0x91, 0xFF, 0x1B, + 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7, 0x01, 0x73, 0xFC, + 0x3B, 0x06, 0xF5, 0xF4, 0xED, 0x19, 0xB7, 0x41, 0x71, 0xF5, 0xEB, + 0x03, 0x90, 0xFE, 0x60, 0x00, 0x04, 0x00, 0xED, 0xFF, 0x06, 0x00, + 0xFD, 0xFF, 0x2C, 0x00, 0x57, 0xFF, 0xB2, 0x01, 0x65, 0xFC, 0x12, + 0x07, 0x82, 0xF1, 0x50, 0x2F, 0x38, 0x31, 0x7C, 0xF1, 0xF9, 0x06, + 0x7E, 0xFC, 0xA1, 0x01, 0x61, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x04, + 0x00, 0xF5, 0xFF, 0xEF, 0xFF, 0x88, 0x00, 0x49, 0xFE, 0x5D, 0x04, + 0xB7, 0xF4, 0x7D, 0x40, 0xFD, 0x1B, 0x6C, 0xF4, 0x70, 0x06, 0x5F, + 0xFC, 0xDE, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, + 0x9A, 0xFF, 0xDB, 0x00, 0xA6, 0xFE, 0xB4, 0x01, 0x64, 0xFE, 0x12, + 0x00, 0xAA, 0x48, 0x9E, 0x07, 0x21, 0xFB, 0x66, 0x03, 0xC6, 0xFD, + 0x45, 0x01, 0x70, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, + 0x00, 0x48, 0xFF, 0xAD, 0x01, 0xDD, 0xFC, 0x48, 0x05, 0x30, 0xF7, + 0x6B, 0x12, 0x7D, 0x45, 0xCF, 0xF8, 0x01, 0x02, 0xB2, 0xFF, 0xBD, + 0xFF, 0x54, 0x00, 0xCE, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x3F, 0xFF, 0xDC, 0x01, 0x34, 0xFC, 0x24, 0x07, 0x1C, 0xF2, 0xF0, + 0x27, 0xDF, 0x37, 0xFB, 0xF1, 0x51, 0x06, 0x01, 0xFD, 0x4B, 0x01, + 0x8D, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAC, + 0xFF, 0x0E, 0x01, 0x66, 0xFD, 0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B, + 0xB0, 0x23, 0xC4, 0xF2, 0xFF, 0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBC, 0xFF, 0x84, 0x00, + 0x5B, 0xFF, 0x64, 0x00, 0xC9, 0x00, 0x22, 0xFB, 0x02, 0x47, 0x64, + 0x0E, 0x8F, 0xF8, 0xA7, 0x04, 0x29, 0xFD, 0x8C, 0x01, 0x54, 0xFF, + 0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x60, 0xFF, 0x6E, + 0x01, 0x6B, 0xFD, 0x1D, 0x04, 0xAF, 0xF9, 0x51, 0x0B, 0xEC, 0x47, + 0x33, 0xFD, 0xC1, 0xFF, 0xF7, 0x00, 0x0C, 0xFF, 0xAA, 0x00, 0xAD, + 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, + 0x40, 0xFC, 0xCB, 0x06, 0x6E, 0xF3, 0x49, 0x20, 0xB0, 0x3D, 0x73, + 0xF3, 0x31, 0x05, 0xC4, 0xFD, 0xD6, 0x00, 0xC8, 0xFF, 0x03, 0x00, + 0x02, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x77, 0xFF, 0x75, 0x01, 0xBF, + 0xFC, 0xAB, 0x06, 0xA6, 0xF1, 0x05, 0x35, 0x40, 0x2B, 0xBF, 0xF1, + 0x2A, 0x07, 0x42, 0xFC, 0xCE, 0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD, + 0xFF, 0x09, 0x00, 0xDC, 0xFF, 0x2F, 0x00, 0x07, 0x00, 0x2C, 0xFF, + 0xE6, 0x02, 0x31, 0xF7, 0xFA, 0x43, 0xB3, 0x15, 0x29, 0xF6, 0xBC, + 0x05, 0xA9, 0xFC, 0xC2, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x20, 0x00, 0x7E, 0xFF, 0x21, 0x01, 0x12, 0xFE, 0xD1, + 0x02, 0x47, 0xFC, 0xD7, 0x04, 0xF0, 0x48, 0x8D, 0x02, 0x45, 0xFD, + 0x4D, 0x02, 0x56, 0xFE, 0x01, 0x01, 0x8B, 0xFF, 0x1D, 0x00, 0xFE, + 0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD1, 0x01, 0x83, 0xFC, 0x16, 0x06, + 0x51, 0xF5, 0x9B, 0x18, 0x75, 0x42, 0xF3, 0xF5, 0x9D, 0x03, 0xBF, + 0xFE, 0x45, 0x00, 0x11, 0x00, 0xE8, 0xFF, 0x07, 0x00, 0xFD, 0xFF, + 0x2E, 0x00, 0x52, 0xFF, 0xBC, 0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E, + 0xF1, 0x11, 0x2E, 0x6B, 0x32, 0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC, + 0x94, 0x01, 0x67, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF9, + 0xFF, 0xE3, 0xFF, 0xA1, 0x00, 0x1E, 0xFE, 0xA3, 0x04, 0x49, 0xF4, + 0xA8, 0x3F, 0x52, 0x1D, 0x19, 0xF4, 0x90, 0x06, 0x53, 0xFC, 0xE1, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0xA0, 0xFF, + 0xCC, 0x00, 0xC6, 0xFE, 0x79, 0x01, 0xD2, 0xFE, 0x26, 0xFF, 0x7C, + 0x48, 0xBE, 0x08, 0xAE, 0xFA, 0xA0, 0x03, 0xA9, 0xFD, 0x52, 0x01, + 0x6B, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, + 0xFF, 0xA3, 0x01, 0xF3, 0xFC, 0x18, 0x05, 0x9B, 0xF7, 0x27, 0x11, + 0x02, 0x46, 0x7F, 0xF9, 0xA3, 0x01, 0xE8, 0xFF, 0x9F, 0xFF, 0x63, + 0x00, 0xC9, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, + 0xE0, 0x01, 0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26, 0xF2, + 0x38, 0x2A, 0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96, 0xFF, + 0x16, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x11, 0x00, 0xA2, 0xFF, 0x22, + 0x01, 0x45, 0xFD, 0xF1, 0x05, 0x6D, 0xF2, 0x38, 0x3A, 0x03, 0x25, + 0x8B, 0xF2, 0x0E, 0x07, 0x32, 0xFC, 0xE4, 0x01, 0x3A, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC2, 0xFF, 0x75, 0x00, 0x7A, 0xFF, + 0x2B, 0x00, 0x2D, 0x01, 0x61, 0xFA, 0x97, 0x46, 0xA0, 0x0F, 0x20, + 0xF8, 0xDA, 0x04, 0x10, 0xFD, 0x97, 0x01, 0x50, 0xFF, 0x2E, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x62, 0x01, 0x87, + 0xFD, 0xE5, 0x03, 0x21, 0xFA, 0x25, 0x0A, 0x33, 0x48, 0x0F, 0xFE, + 0x57, 0xFF, 0x31, 0x01, 0xEC, 0xFE, 0xB9, 0x00, 0xA7, 0xFF, 0x15, + 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x48, 0xFC, + 0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E, 0xCF, 0xF3, 0xF3, + 0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF, 0xFF, 0x03, 0x00, + 0xFE, 0xFF, 0x23, 0x00, 0x70, 0xFF, 0x84, 0x01, 0xA9, 0xFC, 0xC7, + 0x06, 0x91, 0xF1, 0xDC, 0x33, 0x87, 0x2C, 0xA5, 0xF1, 0x26, 0x07, + 0x4B, 0xFC, 0xC6, 0x01, 0x4C, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, + 0x00, 0xE2, 0xFF, 0x21, 0x00, 0x23, 0x00, 0xFA, 0xFE, 0x3A, 0x03, + 0x9D, 0xF6, 0x50, 0x43, 0x00, 0x17, 0xC6, 0xF5, 0xE6, 0x05, 0x97, + 0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x00, 0x00, + 0x1E, 0x00, 0x84, 0xFF, 0x13, 0x01, 0x31, 0xFE, 0x95, 0x02, 0xBA, + 0xFC, 0xCB, 0x03, 0xF7, 0x48, 0x91, 0x03, 0xD3, 0xFC, 0x88, 0x02, + 0x38, 0xFE, 0x10, 0x01, 0x85, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, + 0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93, 0xFC, 0xEF, 0x05, 0xB0, 0xF5, + 0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6, 0x4D, 0x03, 0xEF, 0xFE, 0x2A, + 0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x2F, 0x00, + 0x4D, 0xFF, 0xC4, 0x01, 0x4D, 0xFC, 0x25, 0x07, 0xA1, 0xF1, 0xCE, + 0x2C, 0x99, 0x33, 0x8E, 0xF1, 0xCD, 0x06, 0xA4, 0xFC, 0x87, 0x01, + 0x6E, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFE, 0xFF, 0xD7, + 0xFF, 0xBA, 0x00, 0xF4, 0xFD, 0xE5, 0x04, 0xE4, 0xF3, 0xCA, 0x3E, + 0xA7, 0x1E, 0xCA, 0xF3, 0xAC, 0x06, 0x4A, 0xFC, 0xE4, 0x01, 0x36, + 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA6, 0xFF, 0xBD, 0x00, + 0xE5, 0xFE, 0x3E, 0x01, 0x3F, 0xFF, 0x41, 0xFE, 0x41, 0x48, 0xE4, + 0x09, 0x3B, 0xFA, 0xD9, 0x03, 0x8D, 0xFD, 0x5F, 0x01, 0x66, 0xFF, + 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99, + 0x01, 0x0B, 0xFD, 0xE6, 0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46, + 0x37, 0xFA, 0x42, 0x01, 0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3, + 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01, + 0x31, 0xFC, 0x11, 0x07, 0x7F, 0xF2, 0x4E, 0x25, 0xFD, 0x39, 0x60, + 0xF2, 0xFB, 0x05, 0x3E, 0xFD, 0x26, 0x01, 0xA0, 0xFF, 0x12, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x15, 0x00, 0x98, 0xFF, 0x35, 0x01, 0x25, + 0xFD, 0x1E, 0x06, 0x35, 0xF2, 0x2E, 0x39, 0x55, 0x26, 0x56, 0xF2, + 0x1A, 0x07, 0x31, 0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x0E, 0x00, 0xC7, 0xFF, 0x66, 0x00, 0x98, 0xFF, 0xF4, 0xFF, + 0x8E, 0x01, 0xA7, 0xF9, 0x1D, 0x46, 0xDF, 0x10, 0xB3, 0xF7, 0x0D, + 0x05, 0xF8, 0xFC, 0xA1, 0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD, + 0x03, 0x94, 0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE, + 0x6C, 0x01, 0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x51, 0xFC, 0x96, 0x06, + 0x07, 0xF4, 0x9E, 0x1D, 0x77, 0x3F, 0x32, 0xF4, 0xB2, 0x04, 0x15, + 0xFE, 0xA7, 0x00, 0xE0, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0xFD, 0xFF, + 0x26, 0x00, 0x69, 0xFF, 0x91, 0x01, 0x94, 0xFC, 0xE0, 0x06, 0x84, + 0xF1, 0xAF, 0x32, 0xCA, 0x2D, 0x92, 0xF1, 0x1F, 0x07, 0x56, 0xFC, + 0xBE, 0x01, 0x51, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE7, + 0xFF, 0x14, 0x00, 0x3F, 0x00, 0xC9, 0xFE, 0x8C, 0x03, 0x11, 0xF6, + 0x9E, 0x42, 0x50, 0x18, 0x66, 0xF5, 0x0D, 0x06, 0x86, 0xFC, 0xD0, + 0x01, 0x3B, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x8A, 0xFF, + 0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6, 0x02, 0xF2, + 0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE, 0x1E, 0x01, + 0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, + 0xFF, 0xC4, 0x01, 0xA5, 0xFC, 0xC5, 0x05, 0x13, 0xF6, 0xFD, 0x15, + 0xD4, 0x43, 0x0F, 0xF7, 0xF9, 0x02, 0x21, 0xFF, 0x0D, 0x00, 0x2C, + 0x00, 0xDE, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x49, 0xFF, + 0xCC, 0x01, 0x44, 0xFC, 0x29, 0x07, 0xB9, 0xF1, 0x89, 0x2B, 0xC3, + 0x34, 0xA0, 0xF1, 0xB1, 0x06, 0xBA, 0xFC, 0x79, 0x01, 0x76, 0xFF, + 0x21, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x02, 0x00, 0xCB, 0xFF, 0xD1, + 0x00, 0xCC, 0xFD, 0x24, 0x05, 0x87, 0xF3, 0xE4, 0x3D, 0xFD, 0x1F, + 0x7F, 0xF3, 0xC6, 0x06, 0x41, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAC, 0xFF, 0xAE, 0x00, 0x05, 0xFF, + 0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD, 0xFD, 0x47, 0x0E, 0x0B, 0xC8, + 0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C, 0x01, 0x61, 0xFF, 0x28, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0x8F, 0x01, 0x23, + 0xFD, 0xB2, 0x04, 0x76, 0xF8, 0xAA, 0x0E, 0xED, 0x46, 0xF7, 0xFA, + 0xDF, 0x00, 0x57, 0x00, 0x62, 0xFF, 0x80, 0x00, 0xBD, 0xFF, 0x10, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x33, 0xFC, + 0x03, 0x07, 0xB7, 0xF2, 0xFC, 0x23, 0x03, 0x3B, 0x9E, 0xF2, 0xCB, + 0x05, 0x5F, 0xFD, 0x12, 0x01, 0xAA, 0xFF, 0x0E, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x18, 0x00, 0x8F, 0xFF, 0x47, 0x01, 0x08, 0xFD, 0x49, + 0x06, 0x05, 0xF2, 0x1D, 0x38, 0xA6, 0x27, 0x26, 0xF2, 0x23, 0x07, + 0x33, 0xFC, 0xDD, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, + 0x00, 0xCD, 0xFF, 0x57, 0x00, 0xB6, 0xFF, 0xBE, 0xFF, 0xED, 0x01, + 0xF5, 0xF8, 0x9B, 0x45, 0x22, 0x12, 0x48, 0xF7, 0x3D, 0x05, 0xE2, + 0xFC, 0xAB, 0x01, 0x49, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x24, 0x00, 0x6F, 0xFF, 0x48, 0x01, 0xC0, 0xFD, 0x73, 0x03, 0x07, + 0xFB, 0xDD, 0x07, 0xA1, 0x48, 0xDD, 0xFF, 0x7D, 0xFE, 0xA7, 0x01, + 0xAD, 0xFE, 0xD8, 0x00, 0x9B, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5C, 0xFC, 0x78, 0x06, 0x5A, 0xF4, + 0x49, 0x1C, 0x4E, 0x40, 0x9E, 0xF4, 0x6D, 0x04, 0x3F, 0xFE, 0x8E, + 0x00, 0xED, 0xFF, 0xF6, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00, + 0x62, 0xFF, 0x9E, 0x01, 0x82, 0xFC, 0xF5, 0x06, 0x7D, 0xF1, 0x7B, + 0x31, 0x09, 0x2F, 0x84, 0xF1, 0x15, 0x07, 0x62, 0xFC, 0xB4, 0x01, + 0x56, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEC, 0xFF, 0x06, + 0x00, 0x5A, 0x00, 0x9A, 0xFE, 0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41, + 0xA1, 0x19, 0x09, 0xF5, 0x33, 0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x8F, 0xFF, 0xF5, 0x00, + 0x6F, 0xFE, 0x1E, 0x02, 0x9D, 0xFD, 0xC7, 0x01, 0xE1, 0x48, 0xAB, + 0x05, 0xEE, 0xFB, 0xFE, 0x02, 0xFB, 0xFD, 0x2C, 0x01, 0x7A, 0xFF, + 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBC, + 0x01, 0xB8, 0xFC, 0x9A, 0x05, 0x77, 0xF6, 0xB1, 0x14, 0x77, 0x44, + 0xA9, 0xF7, 0xA2, 0x02, 0x54, 0xFF, 0xF1, 0xFF, 0x3A, 0x00, 0xD8, + 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45, 0xFF, 0xD3, 0x01, + 0x3C, 0xFC, 0x2A, 0x07, 0xD8, 0xF1, 0x3F, 0x2A, 0xE6, 0x35, 0xBB, + 0xF1, 0x92, 0x06, 0xD2, 0xFC, 0x69, 0x01, 0x7E, 0xFF, 0x1F, 0x00, + 0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xE8, 0x00, 0xA6, + 0xFD, 0x5F, 0x05, 0x31, 0xF3, 0xF6, 0x3C, 0x52, 0x21, 0x37, 0xF3, + 0xDD, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x13, 0x00, 0xB1, 0xFF, 0x9F, 0x00, 0x24, 0xFF, 0xC9, 0x00, + 0x13, 0x00, 0x8D, 0xFC, 0xAE, 0x47, 0x3E, 0x0C, 0x56, 0xF9, 0x48, + 0x04, 0x56, 0xFD, 0x78, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2B, 0x00, 0x58, 0xFF, 0x83, 0x01, 0x3C, 0xFD, 0x7E, + 0x04, 0xE6, 0xF8, 0x72, 0x0D, 0x52, 0x47, 0xBE, 0xFB, 0x7A, 0x00, + 0x90, 0x00, 0x43, 0xFF, 0x8F, 0x00, 0xB7, 0xFF, 0x11, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xF1, 0x06, + 0xF5, 0xF2, 0xA7, 0x22, 0xFF, 0x3B, 0xE4, 0xF2, 0x96, 0x05, 0x81, + 0xFD, 0xFD, 0x00, 0xB5, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0xFE, 0xFF, + 0x1C, 0x00, 0x86, 0xFF, 0x59, 0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC, + 0xF1, 0x04, 0x37, 0xF3, 0x28, 0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC, + 0xD8, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD3, + 0xFF, 0x49, 0x00, 0xD4, 0xFF, 0x88, 0xFF, 0x49, 0x02, 0x4B, 0xF8, + 0x0D, 0x45, 0x68, 0x13, 0xDF, 0xF6, 0x6C, 0x05, 0xCC, 0xFC, 0xB4, + 0x01, 0x45, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, + 0x74, 0xFF, 0x3A, 0x01, 0xDD, 0xFD, 0x39, 0x03, 0x7B, 0xFB, 0xC1, + 0x06, 0xC7, 0x48, 0xCF, 0x00, 0x0D, 0xFE, 0xE3, 0x01, 0x8E, 0xFE, + 0xE7, 0x00, 0x95, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, + 0xFF, 0xDA, 0x01, 0x69, 0xFC, 0x57, 0x06, 0xAF, 0xF4, 0xF5, 0x1A, + 0x1D, 0x41, 0x11, 0xF5, 0x25, 0x04, 0x6C, 0xFE, 0x74, 0x00, 0xF9, + 0xFF, 0xF1, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5C, 0xFF, + 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30, 0x44, + 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C, 0xFF, + 0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF1, 0xFF, 0xF9, 0xFF, 0x74, + 0x00, 0x6C, 0xFE, 0x25, 0x04, 0x11, 0xF5, 0x1D, 0x41, 0xF5, 0x1A, + 0xAF, 0xF4, 0x57, 0x06, 0x69, 0xFC, 0xDA, 0x01, 0x38, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE7, 0x00, 0x8E, 0xFE, + 0xE3, 0x01, 0x0D, 0xFE, 0xCF, 0x00, 0xC7, 0x48, 0xC1, 0x06, 0x7B, + 0xFB, 0x39, 0x03, 0xDD, 0xFD, 0x3A, 0x01, 0x74, 0xFF, 0x23, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x45, 0xFF, 0xB4, 0x01, 0xCC, + 0xFC, 0x6C, 0x05, 0xDF, 0xF6, 0x68, 0x13, 0x0D, 0x45, 0x4B, 0xF8, + 0x49, 0x02, 0x88, 0xFF, 0xD4, 0xFF, 0x49, 0x00, 0xD3, 0xFF, 0x0B, + 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8, 0x01, 0x37, 0xFC, + 0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37, 0xDC, 0xF1, 0x6F, + 0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, + 0x01, 0x00, 0x0B, 0x00, 0xB5, 0xFF, 0xFD, 0x00, 0x81, 0xFD, 0x96, + 0x05, 0xE4, 0xF2, 0xFF, 0x3B, 0xA7, 0x22, 0xF5, 0xF2, 0xF1, 0x06, + 0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, + 0x00, 0xB7, 0xFF, 0x8F, 0x00, 0x43, 0xFF, 0x90, 0x00, 0x7A, 0x00, + 0xBE, 0xFB, 0x52, 0x47, 0x72, 0x0D, 0xE6, 0xF8, 0x7E, 0x04, 0x3C, + 0xFD, 0x83, 0x01, 0x58, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2A, 0x00, 0x5C, 0xFF, 0x78, 0x01, 0x56, 0xFD, 0x48, 0x04, 0x56, + 0xF9, 0x3E, 0x0C, 0xAE, 0x47, 0x8D, 0xFC, 0x13, 0x00, 0xC9, 0x00, + 0x24, 0xFF, 0x9F, 0x00, 0xB1, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B, 0xFC, 0xDD, 0x06, 0x37, 0xF3, + 0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3, 0x5F, 0x05, 0xA6, 0xFD, 0xE8, + 0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1F, 0x00, + 0x7E, 0xFF, 0x69, 0x01, 0xD2, 0xFC, 0x92, 0x06, 0xBB, 0xF1, 0xE6, + 0x35, 0x3F, 0x2A, 0xD8, 0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01, + 0x45, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD8, 0xFF, 0x3A, + 0x00, 0xF1, 0xFF, 0x54, 0xFF, 0xA2, 0x02, 0xA9, 0xF7, 0x77, 0x44, + 0xB1, 0x14, 0x77, 0xF6, 0x9A, 0x05, 0xB8, 0xFC, 0xBC, 0x01, 0x42, + 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF, + 0x2C, 0x01, 0xFB, 0xFD, 0xFE, 0x02, 0xEE, 0xFB, 0xAB, 0x05, 0xE1, + 0x48, 0xC7, 0x01, 0x9D, 0xFD, 0x1E, 0x02, 0x6F, 0xFE, 0xF5, 0x00, + 0x8F, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6, + 0x01, 0x77, 0xFC, 0x33, 0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41, + 0x8D, 0xF5, 0xDA, 0x03, 0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC, + 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x56, 0xFF, 0xB4, 0x01, + 0x62, 0xFC, 0x15, 0x07, 0x84, 0xF1, 0x09, 0x2F, 0x7B, 0x31, 0x7D, + 0xF1, 0xF5, 0x06, 0x82, 0xFC, 0x9E, 0x01, 0x62, 0xFF, 0x28, 0x00, + 0xFD, 0xFF, 0x04, 0x00, 0xF6, 0xFF, 0xED, 0xFF, 0x8E, 0x00, 0x3F, + 0xFE, 0x6D, 0x04, 0x9E, 0xF4, 0x4E, 0x40, 0x49, 0x1C, 0x5A, 0xF4, + 0x78, 0x06, 0x5C, 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0x18, 0x00, 0x9B, 0xFF, 0xD8, 0x00, 0xAD, 0xFE, 0xA7, 0x01, + 0x7D, 0xFE, 0xDD, 0xFF, 0xA1, 0x48, 0xDD, 0x07, 0x07, 0xFB, 0x73, + 0x03, 0xC0, 0xFD, 0x48, 0x01, 0x6F, 0xFF, 0x24, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D, + 0x05, 0x48, 0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01, + 0xBE, 0xFF, 0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDD, 0x01, 0x33, 0xFC, 0x23, 0x07, + 0x26, 0xF2, 0xA6, 0x27, 0x1D, 0x38, 0x05, 0xF2, 0x49, 0x06, 0x08, + 0xFD, 0x47, 0x01, 0x8F, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x0E, 0x00, 0xAA, 0xFF, 0x12, 0x01, 0x5F, 0xFD, 0xCB, 0x05, 0x9E, + 0xF2, 0x03, 0x3B, 0xFC, 0x23, 0xB7, 0xF2, 0x03, 0x07, 0x33, 0xFC, + 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBD, + 0xFF, 0x80, 0x00, 0x62, 0xFF, 0x57, 0x00, 0xDF, 0x00, 0xF7, 0xFA, + 0xED, 0x46, 0xAA, 0x0E, 0x76, 0xF8, 0xB2, 0x04, 0x23, 0xFD, 0x8F, + 0x01, 0x53, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, + 0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8, 0xF9, 0x0E, + 0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01, 0x05, 0xFF, + 0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE5, 0x01, 0x41, 0xFC, 0xC6, 0x06, 0x7F, 0xF3, 0xFD, 0x1F, + 0xE4, 0x3D, 0x87, 0xF3, 0x24, 0x05, 0xCC, 0xFD, 0xD1, 0x00, 0xCB, + 0xFF, 0x02, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x76, 0xFF, + 0x79, 0x01, 0xBA, 0xFC, 0xB1, 0x06, 0xA0, 0xF1, 0xC3, 0x34, 0x89, + 0x2B, 0xB9, 0xF1, 0x29, 0x07, 0x44, 0xFC, 0xCC, 0x01, 0x49, 0xFF, + 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2C, 0x00, 0x0D, + 0x00, 0x21, 0xFF, 0xF9, 0x02, 0x0F, 0xF7, 0xD4, 0x43, 0xFD, 0x15, + 0x13, 0xF6, 0xC5, 0x05, 0xA5, 0xFC, 0xC4, 0x01, 0x40, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F, 0xFF, 0x1E, 0x01, + 0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC, 0x9B, 0x04, 0xF2, 0x48, 0xC6, + 0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50, 0xFE, 0x04, 0x01, 0x8A, 0xFF, + 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD0, 0x01, 0x86, + 0xFC, 0x0D, 0x06, 0x66, 0xF5, 0x50, 0x18, 0x9E, 0x42, 0x11, 0xF6, + 0x8C, 0x03, 0xC9, 0xFE, 0x3F, 0x00, 0x14, 0x00, 0xE7, 0xFF, 0x07, + 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBE, 0x01, 0x56, 0xFC, + 0x1F, 0x07, 0x92, 0xF1, 0xCA, 0x2D, 0xAF, 0x32, 0x84, 0xF1, 0xE0, + 0x06, 0x94, 0xFC, 0x91, 0x01, 0x69, 0xFF, 0x26, 0x00, 0xFD, 0xFF, + 0x03, 0x00, 0xFA, 0xFF, 0xE0, 0xFF, 0xA7, 0x00, 0x15, 0xFE, 0xB2, + 0x04, 0x32, 0xF4, 0x77, 0x3F, 0x9E, 0x1D, 0x07, 0xF4, 0x96, 0x06, + 0x51, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, + 0x00, 0xA1, 0xFF, 0xC9, 0x00, 0xCD, 0xFE, 0x6C, 0x01, 0xEA, 0xFE, + 0xF3, 0xFE, 0x70, 0x48, 0xFF, 0x08, 0x94, 0xFA, 0xAD, 0x03, 0xA3, + 0xFD, 0x55, 0x01, 0x6A, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x2F, 0x00, 0x4C, 0xFF, 0xA1, 0x01, 0xF8, 0xFC, 0x0D, 0x05, 0xB3, + 0xF7, 0xDF, 0x10, 0x1D, 0x46, 0xA7, 0xF9, 0x8E, 0x01, 0xF4, 0xFF, + 0x98, 0xFF, 0x66, 0x00, 0xC7, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, + 0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x31, 0xFC, 0x1A, 0x07, 0x56, 0xF2, + 0x55, 0x26, 0x2E, 0x39, 0x35, 0xF2, 0x1E, 0x06, 0x25, 0xFD, 0x35, + 0x01, 0x98, 0xFF, 0x15, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x12, 0x00, + 0xA0, 0xFF, 0x26, 0x01, 0x3E, 0xFD, 0xFB, 0x05, 0x60, 0xF2, 0xFD, + 0x39, 0x4E, 0x25, 0x7F, 0xF2, 0x11, 0x07, 0x31, 0xFC, 0xE3, 0x01, + 0x3A, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC3, 0xFF, 0x71, + 0x00, 0x81, 0xFF, 0x1F, 0x00, 0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46, + 0xE7, 0x0F, 0x08, 0xF8, 0xE6, 0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F, + 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x66, 0xFF, + 0x5F, 0x01, 0x8D, 0xFD, 0xD9, 0x03, 0x3B, 0xFA, 0xE4, 0x09, 0x41, + 0x48, 0x41, 0xFE, 0x3F, 0xFF, 0x3E, 0x01, 0xE5, 0xFE, 0xBD, 0x00, + 0xA6, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, + 0x01, 0x4A, 0xFC, 0xAC, 0x06, 0xCA, 0xF3, 0xA7, 0x1E, 0xCA, 0x3E, + 0xE4, 0xF3, 0xE5, 0x04, 0xF4, 0xFD, 0xBA, 0x00, 0xD7, 0xFF, 0xFE, + 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6E, 0xFF, 0x87, 0x01, + 0xA4, 0xFC, 0xCD, 0x06, 0x8E, 0xF1, 0x99, 0x33, 0xCE, 0x2C, 0xA1, + 0xF1, 0x25, 0x07, 0x4D, 0xFC, 0xC4, 0x01, 0x4D, 0xFF, 0x2F, 0x00, + 0xFD, 0xFF, 0x08, 0x00, 0xE3, 0xFF, 0x1E, 0x00, 0x2A, 0x00, 0xEF, + 0xFE, 0x4D, 0x03, 0x7D, 0xF6, 0x2A, 0x43, 0x4B, 0x17, 0xB0, 0xF5, + 0xEF, 0x05, 0x93, 0xFC, 0xCB, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0x1E, 0x00, 0x85, 0xFF, 0x10, 0x01, 0x38, 0xFE, 0x88, 0x02, + 0xD3, 0xFC, 0x91, 0x03, 0xF7, 0x48, 0xCB, 0x03, 0xBA, 0xFC, 0x95, + 0x02, 0x31, 0xFE, 0x13, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x97, 0xFC, 0xE6, + 0x05, 0xC6, 0xF5, 0x00, 0x17, 0x50, 0x43, 0x9D, 0xF6, 0x3A, 0x03, + 0xFA, 0xFE, 0x23, 0x00, 0x21, 0x00, 0xE2, 0xFF, 0x08, 0x00, 0xFD, + 0xFF, 0x30, 0x00, 0x4C, 0xFF, 0xC6, 0x01, 0x4B, 0xFC, 0x26, 0x07, + 0xA5, 0xF1, 0x87, 0x2C, 0xDC, 0x33, 0x91, 0xF1, 0xC7, 0x06, 0xA9, + 0xFC, 0x84, 0x01, 0x70, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x03, 0x00, + 0xFF, 0xFF, 0xD4, 0xFF, 0xBF, 0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF, + 0xF3, 0x98, 0x3E, 0xF3, 0x1E, 0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC, + 0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x15, 0x00, 0xA7, + 0xFF, 0xB9, 0x00, 0xEC, 0xFE, 0x31, 0x01, 0x57, 0xFF, 0x0F, 0xFE, + 0x33, 0x48, 0x25, 0x0A, 0x21, 0xFA, 0xE5, 0x03, 0x87, 0xFD, 0x62, + 0x01, 0x65, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, + 0x50, 0xFF, 0x97, 0x01, 0x10, 0xFD, 0xDA, 0x04, 0x20, 0xF8, 0xA0, + 0x0F, 0x97, 0x46, 0x61, 0xFA, 0x2D, 0x01, 0x2B, 0x00, 0x7A, 0xFF, + 0x75, 0x00, 0xC2, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A, + 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0E, 0x07, 0x8B, 0xF2, 0x03, 0x25, + 0x38, 0x3A, 0x6D, 0xF2, 0xF1, 0x05, 0x45, 0xFD, 0x22, 0x01, 0xA2, + 0xFF, 0x11, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x96, 0xFF, + 0x39, 0x01, 0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38, 0xA0, + 0x26, 0x4B, 0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C, 0xFF, + 0x35, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xC9, 0xFF, 0x63, 0x00, 0x9F, + 0xFF, 0xE8, 0xFF, 0xA3, 0x01, 0x7F, 0xF9, 0x02, 0x46, 0x27, 0x11, + 0x9B, 0xF7, 0x18, 0x05, 0xF3, 0xFC, 0xA3, 0x01, 0x4C, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6B, 0xFF, 0x52, 0x01, + 0xA9, 0xFD, 0xA0, 0x03, 0xAE, 0xFA, 0xBE, 0x08, 0x7C, 0x48, 0x26, + 0xFF, 0xD2, 0xFE, 0x79, 0x01, 0xC6, 0xFE, 0xCC, 0x00, 0xA0, 0xFF, + 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE1, 0x01, 0x53, + 0xFC, 0x90, 0x06, 0x19, 0xF4, 0x52, 0x1D, 0xA8, 0x3F, 0x49, 0xF4, + 0xA3, 0x04, 0x1E, 0xFE, 0xA1, 0x00, 0xE3, 0xFF, 0xF9, 0xFF, 0x04, + 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94, 0x01, 0x90, 0xFC, + 0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E, 0x8E, 0xF1, 0x1D, + 0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, + 0x07, 0x00, 0xE8, 0xFF, 0x11, 0x00, 0x45, 0x00, 0xBF, 0xFE, 0x9D, + 0x03, 0xF3, 0xF5, 0x75, 0x42, 0x9B, 0x18, 0x51, 0xF5, 0x16, 0x06, + 0x83, 0xFC, 0xD1, 0x01, 0x3B, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, + 0x00, 0x8B, 0xFF, 0x01, 0x01, 0x56, 0xFE, 0x4D, 0x02, 0x45, 0xFD, + 0x8D, 0x02, 0xF0, 0x48, 0xD7, 0x04, 0x47, 0xFC, 0xD1, 0x02, 0x12, + 0xFE, 0x21, 0x01, 0x7E, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x33, 0x00, 0x40, 0xFF, 0xC2, 0x01, 0xA9, 0xFC, 0xBC, 0x05, 0x29, + 0xF6, 0xB3, 0x15, 0xFA, 0x43, 0x31, 0xF7, 0xE6, 0x02, 0x2C, 0xFF, + 0x07, 0x00, 0x2F, 0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, + 0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42, 0xFC, 0x2A, 0x07, 0xBF, 0xF1, + 0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1, 0xAB, 0x06, 0xBF, 0xFC, 0x75, + 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x03, 0x00, + 0xC8, 0xFF, 0xD6, 0x00, 0xC4, 0xFD, 0x31, 0x05, 0x73, 0xF3, 0xB0, + 0x3D, 0x49, 0x20, 0x6E, 0xF3, 0xCB, 0x06, 0x40, 0xFC, 0xE6, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAD, 0xFF, 0xAA, + 0x00, 0x0C, 0xFF, 0xF7, 0x00, 0xC1, 0xFF, 0x33, 0xFD, 0xEC, 0x47, + 0x51, 0x0B, 0xAF, 0xF9, 0x1D, 0x04, 0x6B, 0xFD, 0x6E, 0x01, 0x60, + 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x54, 0xFF, + 0x8C, 0x01, 0x29, 0xFD, 0xA7, 0x04, 0x8F, 0xF8, 0x64, 0x0E, 0x02, + 0x47, 0x22, 0xFB, 0xC9, 0x00, 0x64, 0x00, 0x5B, 0xFF, 0x84, 0x00, + 0xBC, 0xFF, 0x10, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5, + 0x01, 0x33, 0xFC, 0xFF, 0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B, + 0xAD, 0xF2, 0xBF, 0x05, 0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x19, 0x00, 0x8D, 0xFF, 0x4B, 0x01, + 0x01, 0xFD, 0x51, 0x06, 0xFB, 0xF1, 0xDF, 0x37, 0xF0, 0x27, 0x1C, + 0xF2, 0x24, 0x07, 0x34, 0xFC, 0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00, + 0xFD, 0xFF, 0x0C, 0x00, 0xCE, 0xFF, 0x54, 0x00, 0xBD, 0xFF, 0xB2, + 0xFF, 0x01, 0x02, 0xCF, 0xF8, 0x7D, 0x45, 0x6B, 0x12, 0x30, 0xF7, + 0x48, 0x05, 0xDD, 0xFC, 0xAD, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x24, 0x00, 0x70, 0xFF, 0x45, 0x01, 0xC6, 0xFD, + 0x66, 0x03, 0x21, 0xFB, 0x9E, 0x07, 0xAA, 0x48, 0x12, 0x00, 0x64, + 0xFE, 0xB4, 0x01, 0xA6, 0xFE, 0xDB, 0x00, 0x9A, 0xFF, 0x19, 0x00, + 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70, + 0x06, 0x6C, 0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04, + 0x49, 0xFE, 0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0xFD, + 0xFF, 0x29, 0x00, 0x61, 0xFF, 0xA1, 0x01, 0x7E, 0xFC, 0xF9, 0x06, + 0x7C, 0xF1, 0x38, 0x31, 0x50, 0x2F, 0x82, 0xF1, 0x12, 0x07, 0x65, + 0xFC, 0xB2, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00, + 0xED, 0xFF, 0x04, 0x00, 0x60, 0x00, 0x90, 0xFE, 0xEB, 0x03, 0x71, + 0xF5, 0xB7, 0x41, 0xED, 0x19, 0xF5, 0xF4, 0x3B, 0x06, 0x73, 0xFC, + 0xD7, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x91, + 0xFF, 0xF2, 0x00, 0x76, 0xFE, 0x11, 0x02, 0xB6, 0xFD, 0x8F, 0x01, + 0xDE, 0x48, 0xE9, 0x05, 0xD4, 0xFB, 0x0C, 0x03, 0xF4, 0xFD, 0x2F, + 0x01, 0x79, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, + 0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E, 0xF6, 0x68, + 0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF, 0xEA, 0xFF, + 0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44, + 0xFF, 0xD4, 0x01, 0x3B, 0xFC, 0x2A, 0x07, 0xDF, 0xF1, 0xF6, 0x29, + 0x27, 0x36, 0xC1, 0xF1, 0x8B, 0x06, 0xD8, 0xFC, 0x66, 0x01, 0x80, + 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xBD, 0xFF, + 0xED, 0x00, 0x9E, 0xFD, 0x6C, 0x05, 0x1F, 0xF3, 0xC0, 0x3C, 0x9E, + 0x21, 0x28, 0xF3, 0xE2, 0x06, 0x3A, 0xFC, 0xE6, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x9B, 0x00, 0x2B, + 0xFF, 0xBD, 0x00, 0x2A, 0x00, 0x5E, 0xFC, 0x9A, 0x47, 0x82, 0x0C, + 0x3D, 0xF9, 0x54, 0x04, 0x50, 0xFD, 0x7A, 0x01, 0x5B, 0xFF, 0x2A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF, 0x81, 0x01, + 0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8, 0x2D, 0x0D, 0x69, 0x47, 0xEB, + 0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C, 0xFF, 0x93, 0x00, 0xB6, 0xFF, + 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x37, + 0xFC, 0xED, 0x06, 0x03, 0xF3, 0x5B, 0x22, 0x37, 0x3C, 0xF4, 0xF2, + 0x8A, 0x05, 0x89, 0xFD, 0xF9, 0x00, 0xB7, 0xFF, 0x0A, 0x00, 0x01, + 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x84, 0xFF, 0x5C, 0x01, 0xE6, 0xFC, + 0x77, 0x06, 0xD4, 0xF1, 0xC6, 0x36, 0x3E, 0x29, 0xF3, 0xF1, 0x29, + 0x07, 0x38, 0xFC, 0xD7, 0x01, 0x42, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x0B, 0x00, 0xD4, 0xFF, 0x46, 0x00, 0xDA, 0xFF, 0x7D, 0xFF, 0x5D, + 0x02, 0x26, 0xF8, 0xED, 0x44, 0xB1, 0x13, 0xC7, 0xF6, 0x77, 0x05, + 0xC8, 0xFC, 0xB6, 0x01, 0x45, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x22, 0x00, 0x76, 0xFF, 0x37, 0x01, 0xE4, 0xFD, 0x2C, 0x03, + 0x94, 0xFB, 0x83, 0x06, 0xCE, 0x48, 0x05, 0x01, 0xF5, 0xFD, 0xF0, + 0x01, 0x87, 0xFE, 0xEA, 0x00, 0x94, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, + 0x35, 0x00, 0x38, 0xFF, 0xD9, 0x01, 0x6C, 0xFC, 0x4F, 0x06, 0xC3, + 0xF4, 0xA9, 0x1A, 0x49, 0x41, 0x2C, 0xF5, 0x15, 0x04, 0x76, 0xFE, + 0x6E, 0x00, 0xFC, 0xFF, 0xF0, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2B, + 0x00, 0x5A, 0xFF, 0xAC, 0x01, 0x6E, 0xFC, 0x0A, 0x07, 0x7E, 0xF1, + 0xFF, 0x2F, 0x8A, 0x30, 0x7D, 0xF1, 0x03, 0x07, 0x75, 0xFC, 0xA7, + 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF2, 0xFF, + 0xF7, 0xFF, 0x7A, 0x00, 0x62, 0xFE, 0x35, 0x04, 0xF7, 0xF4, 0xEF, + 0x40, 0x40, 0x1B, 0x9C, 0xF4, 0x5E, 0x06, 0x66, 0xFC, 0xDB, 0x01, + 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x96, 0xFF, 0xE3, + 0x00, 0x95, 0xFE, 0xD5, 0x01, 0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48, + 0x00, 0x07, 0x61, 0xFB, 0x46, 0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73, + 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF, + 0xB2, 0x01, 0xD1, 0xFC, 0x62, 0x05, 0xF6, 0xF6, 0x20, 0x13, 0x2E, + 0x45, 0x70, 0xF8, 0x34, 0x02, 0x94, 0xFF, 0xCD, 0xFF, 0x4C, 0x00, + 0xD2, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xDA, + 0x01, 0x36, 0xFC, 0x27, 0x07, 0x05, 0xF2, 0xAA, 0x28, 0x44, 0x37, + 0xE4, 0xF1, 0x67, 0x06, 0xF2, 0xFC, 0x55, 0x01, 0x88, 0xFF, 0x1B, + 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0B, 0x00, 0xB2, 0xFF, 0x02, 0x01, + 0x7A, 0xFD, 0xA2, 0x05, 0xD4, 0xF2, 0xC7, 0x3B, 0xF2, 0x22, 0xE7, + 0xF2, 0xF5, 0x06, 0x35, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x11, 0x00, 0xB9, 0xFF, 0x8C, 0x00, 0x4A, 0xFF, 0x83, + 0x00, 0x91, 0x00, 0x91, 0xFB, 0x3D, 0x47, 0xB7, 0x0D, 0xCD, 0xF8, + 0x89, 0x04, 0x36, 0xFD, 0x86, 0x01, 0x57, 0xFF, 0x2B, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D, 0xFF, 0x75, 0x01, 0x5C, 0xFD, + 0x3C, 0x04, 0x70, 0xF9, 0xFA, 0x0B, 0xC0, 0x47, 0xBC, 0xFC, 0xFC, + 0xFF, 0xD6, 0x00, 0x1D, 0xFF, 0xA2, 0x00, 0xB0, 0xFF, 0x13, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3C, 0xFC, 0xD8, + 0x06, 0x47, 0xF3, 0x06, 0x21, 0x2A, 0x3D, 0x44, 0xF3, 0x52, 0x05, + 0xAE, 0xFD, 0xE3, 0x00, 0xC2, 0xFF, 0x06, 0x00, 0x01, 0x00, 0xFE, + 0xFF, 0x1F, 0x00, 0x7C, 0xFF, 0x6D, 0x01, 0xCD, 0xFC, 0x99, 0x06, + 0xB4, 0xF1, 0xA6, 0x35, 0x89, 0x2A, 0xD0, 0xF1, 0x2B, 0x07, 0x3E, + 0xFC, 0xD1, 0x01, 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, + 0xD9, 0xFF, 0x37, 0x00, 0xF7, 0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86, + 0xF7, 0x53, 0x44, 0xFB, 0x14, 0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC, + 0xBE, 0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, + 0x00, 0x7B, 0xFF, 0x29, 0x01, 0x01, 0xFE, 0xF1, 0x02, 0x07, 0xFC, + 0x6E, 0x05, 0xE6, 0x48, 0xFF, 0x01, 0x84, 0xFD, 0x2C, 0x02, 0x68, + 0xFE, 0xF9, 0x00, 0x8E, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x3A, 0xFF, 0xD4, 0x01, 0x7A, 0xFC, 0x2B, 0x06, 0x1E, 0xF5, 0x56, + 0x19, 0x0C, 0x42, 0xAA, 0xF5, 0xC9, 0x03, 0xA4, 0xFE, 0x54, 0x00, + 0x09, 0x00, 0xEB, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x55, + 0xFF, 0xB6, 0x01, 0x5F, 0xFC, 0x17, 0x07, 0x87, 0xF1, 0xC2, 0x2E, + 0xC0, 0x31, 0x7E, 0xF1, 0xF1, 0x06, 0x86, 0xFC, 0x9B, 0x01, 0x63, + 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xEA, 0xFF, + 0x93, 0x00, 0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40, 0x94, + 0x1C, 0x47, 0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00, 0x9C, 0xFF, 0xD4, 0x00, 0xB4, + 0xFE, 0x9A, 0x01, 0x95, 0xFE, 0xA8, 0xFF, 0x98, 0x48, 0x1D, 0x08, + 0xEE, 0xFA, 0x80, 0x03, 0xB9, 0xFD, 0x4B, 0x01, 0x6E, 0xFF, 0x25, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xA9, 0x01, + 0xE7, 0xFC, 0x33, 0x05, 0x60, 0xF7, 0xDA, 0x11, 0xB8, 0x45, 0x1C, + 0xF9, 0xD8, 0x01, 0xCA, 0xFF, 0xAF, 0xFF, 0x5A, 0x00, 0xCC, 0xFF, + 0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDE, 0x01, 0x33, + 0xFC, 0x21, 0x07, 0x30, 0xF2, 0x5C, 0x27, 0x5B, 0x38, 0x0F, 0xF2, + 0x40, 0x06, 0x0E, 0xFD, 0x43, 0x01, 0x91, 0xFF, 0x18, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17, 0x01, 0x57, 0xFD, + 0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24, 0xAA, 0xF2, 0x06, + 0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x10, 0x00, 0xBE, 0xFF, 0x7D, 0x00, 0x69, 0xFF, 0x4B, 0x00, 0xF6, + 0x00, 0xCB, 0xFA, 0xD3, 0x46, 0xF0, 0x0E, 0x5E, 0xF8, 0xBE, 0x04, + 0x1E, 0xFD, 0x91, 0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x28, 0x00, 0x62, 0xFF, 0x69, 0x01, 0x77, 0xFD, 0x04, 0x04, + 0xE2, 0xF9, 0xCB, 0x0A, 0x0D, 0x48, 0x94, 0xFD, 0x92, 0xFF, 0x10, + 0x01, 0xFE, 0xFE, 0xB1, 0x00, 0xAA, 0xFF, 0x15, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x43, 0xFC, 0xC0, 0x06, 0x8F, + 0xF3, 0xB1, 0x1F, 0x18, 0x3E, 0x9B, 0xF3, 0x16, 0x05, 0xD5, 0xFD, + 0xCC, 0x00, 0xCE, 0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x22, + 0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5, 0xFC, 0xB8, 0x06, 0x9C, 0xF1, + 0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1, 0x29, 0x07, 0x46, 0xFC, 0xCA, + 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDF, 0xFF, + 0x29, 0x00, 0x14, 0x00, 0x16, 0xFF, 0x0C, 0x03, 0xEE, 0xF6, 0xB0, + 0x43, 0x47, 0x16, 0xFC, 0xF5, 0xCF, 0x05, 0xA1, 0xFC, 0xC6, 0x01, + 0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81, + 0xFF, 0x1B, 0x01, 0x20, 0xFE, 0xB6, 0x02, 0x7A, 0xFC, 0x5F, 0x04, + 0xF4, 0x48, 0xFF, 0x02, 0x13, 0xFD, 0x67, 0x02, 0x49, 0xFE, 0x07, + 0x01, 0x88, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, + 0xCF, 0x01, 0x8A, 0xFC, 0x05, 0x06, 0x7B, 0xF5, 0x06, 0x18, 0xC7, + 0x42, 0x2F, 0xF6, 0x7A, 0x03, 0xD4, 0xFE, 0x39, 0x00, 0x17, 0x00, + 0xE6, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0, + 0x01, 0x53, 0xFC, 0x21, 0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32, + 0x86, 0xF1, 0xDB, 0x06, 0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFB, 0xFF, 0xDE, 0xFF, 0xAC, 0x00, + 0x0B, 0xFE, 0xC1, 0x04, 0x1B, 0xF4, 0x47, 0x3F, 0xEA, 0x1D, 0xF5, + 0xF3, 0x9C, 0x06, 0x4F, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x16, 0x00, 0xA2, 0xFF, 0xC5, 0x00, 0xD4, 0xFE, 0x5F, + 0x01, 0x03, 0xFF, 0xBF, 0xFE, 0x63, 0x48, 0x40, 0x09, 0x7B, 0xFA, + 0xB9, 0x03, 0x9D, 0xFD, 0x58, 0x01, 0x69, 0xFF, 0x26, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4D, 0xFF, 0x9F, 0x01, 0xFE, 0xFC, + 0x02, 0x05, 0xCB, 0xF7, 0x98, 0x10, 0x39, 0x46, 0xD0, 0xF9, 0x78, + 0x01, 0x00, 0x00, 0x91, 0xFF, 0x69, 0x00, 0xC6, 0xFF, 0x0E, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17, + 0x07, 0x61, 0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06, + 0x2C, 0xFD, 0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0x13, 0x00, 0x9E, 0xFF, 0x2B, 0x01, 0x37, 0xFD, 0x05, 0x06, + 0x54, 0xF2, 0xC2, 0x39, 0x99, 0x25, 0x73, 0xF2, 0x14, 0x07, 0x31, + 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, + 0xC4, 0xFF, 0x6E, 0x00, 0x87, 0xFF, 0x13, 0x00, 0x58, 0x01, 0x0D, + 0xFA, 0x61, 0x46, 0x2D, 0x10, 0xF0, 0xF7, 0xF1, 0x04, 0x05, 0xFD, + 0x9C, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, + 0x00, 0x67, 0xFF, 0x5C, 0x01, 0x93, 0xFD, 0xCC, 0x03, 0x54, 0xFA, + 0xA2, 0x09, 0x4F, 0x48, 0x73, 0xFE, 0x27, 0xFF, 0x4B, 0x01, 0xDE, + 0xFE, 0xC0, 0x00, 0xA4, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB, 0xF3, 0x5B, + 0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD, 0xB4, 0x00, + 0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6D, + 0xFF, 0x8A, 0x01, 0x9F, 0xFC, 0xD3, 0x06, 0x8A, 0xF1, 0x57, 0x33, + 0x17, 0x2D, 0x9C, 0xF1, 0x24, 0x07, 0x4F, 0xFC, 0xC3, 0x01, 0x4E, + 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE4, 0xFF, 0x1B, 0x00, + 0x30, 0x00, 0xE4, 0xFE, 0x5F, 0x03, 0x5E, 0xF6, 0x02, 0x43, 0x96, + 0x17, 0x9B, 0xF5, 0xF8, 0x05, 0x8F, 0xFC, 0xCC, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x86, 0xFF, 0x0C, 0x01, 0x3E, + 0xFE, 0x7B, 0x02, 0xED, 0xFC, 0x56, 0x03, 0xF5, 0x48, 0x06, 0x04, + 0xA1, 0xFC, 0xA3, 0x02, 0x2A, 0xFE, 0x16, 0x01, 0x83, 0xFF, 0x1F, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF, 0xC8, 0x01, + 0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5, 0xB6, 0x16, 0x77, 0x43, 0xBD, + 0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D, 0x00, 0x25, 0x00, 0xE1, 0xFF, + 0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC8, 0x01, 0x49, + 0xFC, 0x27, 0x07, 0xAB, 0xF1, 0x3E, 0x2C, 0x1E, 0x34, 0x95, 0xF1, + 0xC1, 0x06, 0xAE, 0xFC, 0x81, 0x01, 0x71, 0xFF, 0x23, 0x00, 0xFE, + 0xFF, 0x02, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xC4, 0x00, 0xE2, 0xFD, + 0x01, 0x05, 0xBA, 0xF3, 0x64, 0x3E, 0x3F, 0x1F, 0xA8, 0xF3, 0xB8, + 0x06, 0x46, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x15, 0x00, 0xA8, 0xFF, 0xB6, 0x00, 0xF3, 0xFE, 0x24, 0x01, 0x6E, + 0xFF, 0xDE, 0xFD, 0x25, 0x48, 0x68, 0x0A, 0x08, 0xFA, 0xF2, 0x03, + 0x81, 0xFD, 0x65, 0x01, 0x64, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x95, 0x01, 0x15, 0xFD, 0xCF, 0x04, + 0x39, 0xF8, 0x59, 0x0F, 0xAF, 0x46, 0x8B, 0xFA, 0x17, 0x01, 0x38, + 0x00, 0x73, 0xFF, 0x78, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x39, 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0B, 0x07, 0x97, + 0xF2, 0xB8, 0x24, 0x71, 0x3A, 0x7B, 0xF2, 0xE6, 0x05, 0x4C, 0xFD, + 0x1D, 0x01, 0xA4, 0xFF, 0x11, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x16, + 0x00, 0x94, 0xFF, 0x3D, 0x01, 0x18, 0xFD, 0x32, 0x06, 0x1F, 0xF2, + 0xB5, 0x38, 0xEB, 0x26, 0x40, 0xF2, 0x1E, 0x07, 0x32, 0xFC, 0xDF, + 0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCA, 0xFF, + 0x5F, 0x00, 0xA5, 0xFF, 0xDC, 0xFF, 0xB8, 0x01, 0x57, 0xF9, 0xE5, + 0x45, 0x6E, 0x11, 0x83, 0xF7, 0x23, 0x05, 0xEE, 0xFC, 0xA6, 0x01, + 0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6C, + 0xFF, 0x4F, 0x01, 0xB0, 0xFD, 0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08, + 0x86, 0x48, 0x5A, 0xFF, 0xBA, 0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF, + 0x00, 0x9E, 0xFF, 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE0, 0x01, 0x56, 0xFC, 0x89, 0x06, 0x2B, 0xF4, 0x06, 0x1D, 0xD7, + 0x3F, 0x61, 0xF4, 0x94, 0x04, 0x27, 0xFE, 0x9C, 0x00, 0xE6, 0xFF, + 0xF8, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x27, 0x00, 0x66, 0xFF, 0x97, + 0x01, 0x8C, 0xFC, 0xEA, 0x06, 0x80, 0xF1, 0x26, 0x32, 0x58, 0x2E, + 0x8B, 0xF1, 0x1B, 0x07, 0x5B, 0xFC, 0xBA, 0x01, 0x53, 0xFF, 0x2D, + 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE9, 0xFF, 0x0E, 0x00, 0x4B, 0x00, + 0xB4, 0xFE, 0xAF, 0x03, 0xD5, 0xF5, 0x4D, 0x42, 0xE6, 0x18, 0x3C, + 0xF5, 0x1F, 0x06, 0x7F, 0xFC, 0xD3, 0x01, 0x3B, 0xFF, 0x35, 0x00, + 0xFE, 0xFF, 0x1C, 0x00, 0x8C, 0xFF, 0xFE, 0x00, 0x5D, 0xFE, 0x3F, + 0x02, 0x5E, 0xFD, 0x54, 0x02, 0xEC, 0x48, 0x13, 0x05, 0x2E, 0xFC, + 0xDE, 0x02, 0x0C, 0xFE, 0x24, 0x01, 0x7D, 0xFF, 0x20, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x41, 0xFF, 0xC1, 0x01, 0xAD, 0xFC, + 0xB2, 0x05, 0x3F, 0xF6, 0x69, 0x15, 0x1F, 0x44, 0x53, 0xF7, 0xD3, + 0x02, 0x38, 0xFF, 0x01, 0x00, 0x33, 0x00, 0xDB, 0xFF, 0x09, 0x00, + 0xFD, 0xFF, 0x31, 0x00, 0x47, 0xFF, 0xCF, 0x01, 0x40, 0xFC, 0x2A, + 0x07, 0xC6, 0xF1, 0xF7, 0x2A, 0x46, 0x35, 0xAB, 0xF1, 0xA4, 0x06, + 0xC4, 0xFC, 0x72, 0x01, 0x79, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x02, + 0x00, 0x04, 0x00, 0xC6, 0xFF, 0xDB, 0x00, 0xBB, 0xFD, 0x3E, 0x05, + 0x60, 0xF3, 0x7B, 0x3D, 0x94, 0x20, 0x5E, 0xF3, 0xD0, 0x06, 0x3E, + 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00, + 0xAE, 0xFF, 0xA7, 0x00, 0x12, 0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03, + 0xFD, 0xDC, 0x47, 0x95, 0x0B, 0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD, + 0x71, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C, + 0x00, 0x55, 0xFF, 0x8A, 0x01, 0x2E, 0xFD, 0x9B, 0x04, 0xA8, 0xF8, + 0x1F, 0x0E, 0x1A, 0x47, 0x4E, 0xFB, 0xB3, 0x00, 0x70, 0x00, 0x54, + 0xFF, 0x87, 0x00, 0xBB, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC, 0xFB, 0x06, 0xD2, 0xF2, 0x64, + 0x23, 0x73, 0x3B, 0xBC, 0xF2, 0xB4, 0x05, 0x6E, 0xFD, 0x09, 0x01, + 0xAF, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8B, + 0xFF, 0x4F, 0x01, 0xFB, 0xFC, 0x5A, 0x06, 0xF2, 0xF1, 0xA0, 0x37, + 0x3A, 0x28, 0x13, 0xF2, 0x25, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40, + 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xD0, 0xFF, 0x51, 0x00, + 0xC3, 0xFF, 0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45, 0xB2, + 0x12, 0x19, 0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47, 0xFF, + 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x71, 0xFF, 0x42, + 0x01, 0xCD, 0xFD, 0x59, 0x03, 0x3B, 0xFB, 0x5E, 0x07, 0xB3, 0x48, + 0x48, 0x00, 0x4B, 0xFE, 0xC2, 0x01, 0x9F, 0xFE, 0xDE, 0x00, 0x98, + 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDD, 0x01, + 0x62, 0xFC, 0x69, 0x06, 0x7F, 0xF4, 0xB2, 0x1B, 0xAB, 0x40, 0xD0, + 0xF4, 0x4E, 0x04, 0x53, 0xFE, 0x83, 0x00, 0xF2, 0xFF, 0xF4, 0xFF, + 0x05, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA3, 0x01, 0x7A, + 0xFC, 0xFD, 0x06, 0x7C, 0xF1, 0xF2, 0x30, 0x96, 0x2F, 0x80, 0xF1, + 0x0F, 0x07, 0x69, 0xFC, 0xB0, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0xFD, + 0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66, 0x00, 0x85, 0xFE, + 0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A, 0xE1, 0xF4, 0x43, + 0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, + 0x1B, 0x00, 0x92, 0xFF, 0xEF, 0x00, 0x7D, 0xFE, 0x04, 0x02, 0xCF, + 0xFD, 0x58, 0x01, 0xD7, 0x48, 0x26, 0x06, 0xBB, 0xFB, 0x19, 0x03, + 0xED, 0xFD, 0x32, 0x01, 0x77, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xB9, 0x01, 0xC1, 0xFC, 0x86, 0x05, + 0xA5, 0xF6, 0x1E, 0x14, 0xBA, 0x44, 0xF0, 0xF7, 0x7B, 0x02, 0x6B, + 0xFF, 0xE4, 0xFF, 0x41, 0x00, 0xD6, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, + 0x33, 0x00, 0x43, 0xFF, 0xD5, 0x01, 0x3A, 0xFC, 0x2A, 0x07, 0xE7, + 0xF1, 0xAC, 0x29, 0x66, 0x36, 0xC9, 0xF1, 0x83, 0x06, 0xDD, 0xFC, + 0x62, 0x01, 0x81, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x08, + 0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96, 0xFD, 0x78, 0x05, 0x0E, 0xF3, + 0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3, 0xE6, 0x06, 0x38, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB4, 0xFF, + 0x98, 0x00, 0x32, 0xFF, 0xB0, 0x00, 0x41, 0x00, 0x30, 0xFC, 0x86, + 0x47, 0xC6, 0x0C, 0x24, 0xF9, 0x60, 0x04, 0x4B, 0xFD, 0x7D, 0x01, + 0x5A, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x5A, + 0xFF, 0x7E, 0x01, 0x48, 0xFD, 0x66, 0x04, 0x18, 0xF9, 0xE8, 0x0C, + 0x7C, 0x47, 0x19, 0xFC, 0x4D, 0x00, 0xA9, 0x00, 0x35, 0xFF, 0x96, + 0x00, 0xB5, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE6, 0x01, 0x38, 0xFC, 0xE9, 0x06, 0x12, 0xF3, 0x10, 0x22, 0x6E, + 0x3C, 0x05, 0xF3, 0x7E, 0x05, 0x91, 0xFD, 0xF4, 0x00, 0xBA, 0xFF, + 0x09, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60, + 0x01, 0xE0, 0xFC, 0x7F, 0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29, + 0xEB, 0xF1, 0x2A, 0x07, 0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33, + 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD5, 0xFF, 0x42, 0x00, 0xE1, 0xFF, + 0x71, 0xFF, 0x71, 0x02, 0x02, 0xF8, 0xCC, 0x44, 0xFA, 0x13, 0xB0, + 0xF6, 0x81, 0x05, 0xC3, 0xFC, 0xB8, 0x01, 0x44, 0xFF, 0x31, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x77, 0xFF, 0x34, 0x01, 0xEA, + 0xFD, 0x1F, 0x03, 0xAE, 0xFB, 0x45, 0x06, 0xD5, 0x48, 0x3C, 0x01, + 0xDC, 0xFD, 0xFD, 0x01, 0x80, 0xFE, 0xED, 0x00, 0x93, 0xFF, 0x1B, + 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD8, 0x01, 0x6F, 0xFC, + 0x47, 0x06, 0xD7, 0xF4, 0x5D, 0x1A, 0x74, 0x41, 0x48, 0xF5, 0x04, + 0x04, 0x80, 0xFE, 0x69, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0x05, 0x00, + 0xFD, 0xFF, 0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D, + 0x07, 0x80, 0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06, + 0x78, 0xFC, 0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x05, + 0x00, 0xF3, 0xFF, 0xF4, 0xFF, 0x80, 0x00, 0x58, 0xFE, 0x46, 0x04, + 0xDD, 0xF4, 0xC3, 0x40, 0x8C, 0x1B, 0x89, 0xF4, 0x66, 0x06, 0x63, + 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, + 0x98, 0xFF, 0xE0, 0x00, 0x9C, 0xFE, 0xC8, 0x01, 0x3F, 0xFE, 0x62, + 0x00, 0xB8, 0x48, 0x3F, 0x07, 0x47, 0xFB, 0x53, 0x03, 0xD0, 0xFD, + 0x40, 0x01, 0x72, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, + 0x00, 0x47, 0xFF, 0xB0, 0x01, 0xD6, 0xFC, 0x58, 0x05, 0x0D, 0xF7, + 0xD7, 0x12, 0x4E, 0x45, 0x96, 0xF8, 0x20, 0x02, 0xA0, 0xFF, 0xC7, + 0xFF, 0x4F, 0x00, 0xD0, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E, 0xF2, 0x60, + 0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC, 0x51, 0x01, + 0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0C, 0x00, 0xB0, + 0xFF, 0x07, 0x01, 0x72, 0xFD, 0xAE, 0x05, 0xC4, 0xF2, 0x90, 0x3B, + 0x3F, 0x23, 0xD9, 0xF2, 0xF9, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, 0x00, 0xBA, 0xFF, 0x89, 0x00, + 0x51, 0xFF, 0x77, 0x00, 0xA7, 0x00, 0x64, 0xFB, 0x26, 0x47, 0xFC, + 0x0D, 0xB4, 0xF8, 0x95, 0x04, 0x31, 0xFD, 0x88, 0x01, 0x56, 0xFF, + 0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x5E, 0xFF, 0x72, + 0x01, 0x62, 0xFD, 0x2F, 0x04, 0x89, 0xF9, 0xB6, 0x0B, 0xD2, 0x47, + 0xEB, 0xFC, 0xE4, 0xFF, 0xE3, 0x00, 0x16, 0xFF, 0xA5, 0x00, 0xAF, + 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, + 0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3, 0xBA, 0x20, 0x61, 0x3D, 0x56, + 0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE, 0x00, 0xC5, 0xFF, 0x05, 0x00, + 0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x7A, 0xFF, 0x70, 0x01, 0xC7, + 0xFC, 0xA0, 0x06, 0xAE, 0xF1, 0x65, 0x35, 0xD1, 0x2A, 0xCA, 0xF1, + 0x2A, 0x07, 0x40, 0xFC, 0xD0, 0x01, 0x47, 0xFF, 0x32, 0x00, 0xFD, + 0xFF, 0x09, 0x00, 0xDB, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x3D, 0xFF, + 0xC9, 0x02, 0x64, 0xF7, 0x2F, 0x44, 0x44, 0x15, 0x4A, 0xF6, 0xAD, + 0x05, 0xAF, 0xFC, 0xC0, 0x01, 0x41, 0xFF, 0x32, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x21, 0x00, 0x7C, 0xFF, 0x26, 0x01, 0x08, 0xFE, 0xE4, + 0x02, 0x21, 0xFC, 0x31, 0x05, 0xEB, 0x48, 0x37, 0x02, 0x6B, 0xFD, + 0x39, 0x02, 0x61, 0xFE, 0xFC, 0x00, 0x8D, 0xFF, 0x1C, 0x00, 0xFE, + 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD3, 0x01, 0x7D, 0xFC, 0x23, 0x06, + 0x32, 0xF5, 0x0C, 0x19, 0x38, 0x42, 0xC7, 0xF5, 0xB8, 0x03, 0xAF, + 0xFE, 0x4E, 0x00, 0x0C, 0x00, 0xEA, 0xFF, 0x06, 0x00, 0xFD, 0xFF, + 0x2D, 0x00, 0x54, 0xFF, 0xB8, 0x01, 0x5D, 0xFC, 0x1A, 0x07, 0x8A, + 0xF1, 0x7B, 0x2E, 0x04, 0x32, 0x7F, 0xF1, 0xEC, 0x06, 0x8A, 0xFC, + 0x98, 0x01, 0x65, 0xFF, 0x27, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF8, + 0xFF, 0xE7, 0xFF, 0x99, 0x00, 0x2C, 0xFE, 0x8C, 0x04, 0x6D, 0xF4, + 0xF0, 0x3F, 0xE0, 0x1C, 0x34, 0xF4, 0x85, 0x06, 0x57, 0xFC, 0xE0, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00, 0x9E, 0xFF, + 0xD1, 0x00, 0xBB, 0xFE, 0x8D, 0x01, 0xAE, 0xFE, 0x74, 0xFF, 0x8D, + 0x48, 0x5D, 0x08, 0xD4, 0xFA, 0x8D, 0x03, 0xB3, 0xFD, 0x4E, 0x01, + 0x6D, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4A, + 0xFF, 0xA7, 0x01, 0xEC, 0xFC, 0x28, 0x05, 0x77, 0xF7, 0x92, 0x11, + 0xD7, 0x45, 0x43, 0xF9, 0xC3, 0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E, + 0x00, 0xCB, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3D, 0xFF, + 0xDF, 0x01, 0x32, 0xFC, 0x1F, 0x07, 0x3B, 0xF2, 0x11, 0x27, 0x97, + 0x38, 0x19, 0xF2, 0x36, 0x06, 0x15, 0xFD, 0x3F, 0x01, 0x93, 0xFF, + 0x17, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x10, 0x00, 0xA6, 0xFF, 0x1B, + 0x01, 0x50, 0xFD, 0xE1, 0x05, 0x82, 0xF2, 0x8F, 0x3A, 0x92, 0x24, + 0x9D, 0xF2, 0x09, 0x07, 0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0x7A, 0x00, 0x70, 0xFF, + 0x3E, 0x00, 0x0C, 0x01, 0xA1, 0xFA, 0xBB, 0x46, 0x36, 0x0F, 0x45, + 0xF8, 0xC9, 0x04, 0x18, 0xFD, 0x93, 0x01, 0x52, 0xFF, 0x2D, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x63, 0xFF, 0x66, 0x01, 0x7D, + 0xFD, 0xF8, 0x03, 0xFB, 0xF9, 0x89, 0x0A, 0x1D, 0x48, 0xC5, 0xFD, + 0x7A, 0xFF, 0x1D, 0x01, 0xF7, 0xFE, 0xB4, 0x00, 0xA9, 0xFF, 0x15, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x45, 0xFC, + 0xBB, 0x06, 0xA0, 0xF3, 0x64, 0x1F, 0x4A, 0x3E, 0xB0, 0xF3, 0x08, + 0x05, 0xDE, 0xFD, 0xC7, 0x00, 0xD0, 0xFF, 0x00, 0x00, 0x02, 0x00, + 0xFE, 0xFF, 0x23, 0x00, 0x72, 0xFF, 0x7F, 0x01, 0xB0, 0xFC, 0xBE, + 0x06, 0x97, 0xF1, 0x3F, 0x34, 0x19, 0x2C, 0xAD, 0xF1, 0x28, 0x07, + 0x48, 0xFC, 0xC9, 0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, + 0x00, 0xE0, 0xFF, 0x26, 0x00, 0x1A, 0x00, 0x0B, 0xFF, 0x1E, 0x03, + 0xCD, 0xF6, 0x89, 0x43, 0x91, 0x16, 0xE7, 0xF5, 0xD8, 0x05, 0x9D, + 0xFC, 0xC7, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x1F, 0x00, 0x82, 0xFF, 0x18, 0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94, + 0xFC, 0x24, 0x04, 0xF5, 0x48, 0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02, + 0x42, 0xFE, 0x0B, 0x01, 0x87, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, + 0x00, 0x3C, 0xFF, 0xCD, 0x01, 0x8E, 0xFC, 0xFC, 0x05, 0x90, 0xF5, + 0xBB, 0x17, 0xEE, 0x42, 0x4E, 0xF6, 0x68, 0x03, 0xDF, 0xFE, 0x33, + 0x00, 0x1A, 0x00, 0xE5, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2F, 0x00, + 0x4F, 0xFF, 0xC2, 0x01, 0x51, 0xFC, 0x23, 0x07, 0x9A, 0xF1, 0x3A, + 0x2D, 0x35, 0x33, 0x89, 0xF1, 0xD5, 0x06, 0x9D, 0xFC, 0x8B, 0x01, + 0x6C, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFC, 0xFF, 0xDB, + 0xFF, 0xB2, 0x00, 0x02, 0xFE, 0xCF, 0x04, 0x05, 0xF4, 0x16, 0x3F, + 0x36, 0x1E, 0xE4, 0xF3, 0xA3, 0x06, 0x4D, 0xFC, 0xE3, 0x01, 0x36, + 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC2, 0x00, + 0xDB, 0xFE, 0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48, 0x81, + 0x09, 0x61, 0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68, 0xFF, + 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9D, + 0x01, 0x03, 0xFD, 0xF7, 0x04, 0xE3, 0xF7, 0x51, 0x10, 0x55, 0x46, + 0xF9, 0xF9, 0x63, 0x01, 0x0D, 0x00, 0x8B, 0xFF, 0x6D, 0x00, 0xC5, + 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, + 0x31, 0xFC, 0x15, 0x07, 0x6D, 0xF2, 0xBF, 0x25, 0xA5, 0x39, 0x4D, + 0xF2, 0x0B, 0x06, 0x33, 0xFD, 0x2D, 0x01, 0x9D, 0xFF, 0x13, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x14, 0x00, 0x9C, 0xFF, 0x2F, 0x01, 0x30, + 0xFD, 0x10, 0x06, 0x47, 0xF2, 0x87, 0x39, 0xE5, 0x25, 0x67, 0xF2, + 0x16, 0x07, 0x31, 0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E, 0xFF, 0x06, 0x00, + 0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10, 0xD7, 0xF7, 0xFC, + 0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x26, 0x00, 0x68, 0xFF, 0x59, 0x01, 0x99, 0xFD, 0xC0, + 0x03, 0x6E, 0xFA, 0x61, 0x09, 0x5D, 0x48, 0xA6, 0xFE, 0x0F, 0xFF, + 0x58, 0x01, 0xD7, 0xFE, 0xC3, 0x00, 0xA3, 0xFF, 0x16, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4E, 0xFC, 0xA0, 0x06, + 0xED, 0xF3, 0x0F, 0x1E, 0x2D, 0x3F, 0x10, 0xF4, 0xC8, 0x04, 0x07, + 0xFE, 0xAF, 0x00, 0xDC, 0xFF, 0xFC, 0xFF, 0x03, 0x00, 0xFD, 0xFF, + 0x25, 0x00, 0x6B, 0xFF, 0x8D, 0x01, 0x9B, 0xFC, 0xD8, 0x06, 0x87, + 0xF1, 0x13, 0x33, 0x5E, 0x2D, 0x98, 0xF1, 0x22, 0x07, 0x52, 0xFC, + 0xC1, 0x01, 0x4F, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE5, + 0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9, 0xFE, 0x71, 0x03, 0x3F, 0xF6, + 0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5, 0x00, 0x06, 0x8C, 0xFC, 0xCE, + 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x88, 0xFF, + 0x09, 0x01, 0x45, 0xFE, 0x6E, 0x02, 0x06, 0xFD, 0x1C, 0x03, 0xF4, + 0x48, 0x41, 0x04, 0x87, 0xFC, 0xB0, 0x02, 0x23, 0xFE, 0x19, 0x01, + 0x81, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F, + 0xFF, 0xC6, 0x01, 0x9F, 0xFC, 0xD3, 0x05, 0xF1, 0xF5, 0x6C, 0x16, + 0x9E, 0x43, 0xDD, 0xF6, 0x15, 0x03, 0x10, 0xFF, 0x17, 0x00, 0x28, + 0x00, 0xDF, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF, + 0xCA, 0x01, 0x47, 0xFC, 0x28, 0x07, 0xB0, 0xF1, 0xF5, 0x2B, 0x60, + 0x34, 0x9A, 0xF1, 0xBB, 0x06, 0xB3, 0xFC, 0x7D, 0x01, 0x73, 0xFF, + 0x22, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9, + 0x00, 0xDA, 0xFD, 0x0F, 0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F, + 0x97, 0xF3, 0xBD, 0x06, 0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x15, 0x00, 0xAA, 0xFF, 0xB3, 0x00, 0xFA, 0xFE, + 0x17, 0x01, 0x86, 0xFF, 0xAC, 0xFD, 0x16, 0x48, 0xAA, 0x0A, 0xEE, + 0xF9, 0xFE, 0x03, 0x7A, 0xFD, 0x67, 0x01, 0x63, 0xFF, 0x28, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x92, 0x01, 0x1B, + 0xFD, 0xC4, 0x04, 0x51, 0xF8, 0x13, 0x0F, 0xC8, 0x46, 0xB6, 0xFA, + 0x01, 0x01, 0x44, 0x00, 0x6C, 0xFF, 0x7B, 0x00, 0xBF, 0xFF, 0x10, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC, + 0x08, 0x07, 0xA4, 0xF2, 0x6D, 0x24, 0xAD, 0x3A, 0x88, 0xF2, 0xDB, + 0x05, 0x53, 0xFD, 0x19, 0x01, 0xA7, 0xFF, 0x10, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B, + 0x06, 0x14, 0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07, + 0x33, 0xFC, 0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0D, + 0x00, 0xCB, 0xFF, 0x5C, 0x00, 0xAC, 0xFF, 0xD0, 0xFF, 0xCD, 0x01, + 0x30, 0xF9, 0xC8, 0x45, 0xB6, 0x11, 0x6B, 0xF7, 0x2D, 0x05, 0xE9, + 0xFC, 0xA8, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x25, 0x00, 0x6D, 0xFF, 0x4C, 0x01, 0xB6, 0xFD, 0x86, 0x03, 0xE1, + 0xFA, 0x3D, 0x08, 0x92, 0x48, 0x8E, 0xFF, 0xA1, 0xFE, 0x93, 0x01, + 0xB8, 0xFE, 0xD3, 0x00, 0x9D, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xE0, 0x01, 0x58, 0xFC, 0x82, 0x06, 0x3E, 0xF4, + 0xBA, 0x1C, 0x07, 0x40, 0x79, 0xF4, 0x84, 0x04, 0x31, 0xFE, 0x96, + 0x00, 0xE8, 0xFF, 0xF7, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00, + 0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E, 0xF1, 0xE3, + 0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC, 0xB7, 0x01, + 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEA, 0xFF, 0x0B, + 0x00, 0x51, 0x00, 0xAA, 0xFE, 0xC0, 0x03, 0xB8, 0xF5, 0x21, 0x42, + 0x31, 0x19, 0x28, 0xF5, 0x27, 0x06, 0x7C, 0xFC, 0xD4, 0x01, 0x3A, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8D, 0xFF, 0xFA, 0x00, + 0x64, 0xFE, 0x32, 0x02, 0x78, 0xFD, 0x1B, 0x02, 0xEA, 0x48, 0x50, + 0x05, 0x14, 0xFC, 0xEB, 0x02, 0x05, 0xFE, 0x27, 0x01, 0x7C, 0xFF, + 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x41, 0xFF, 0xBF, + 0x01, 0xB2, 0xFC, 0xA9, 0x05, 0x55, 0xF6, 0x20, 0x15, 0x42, 0x44, + 0x75, 0xF7, 0xBF, 0x02, 0x43, 0xFF, 0xFA, 0xFF, 0x36, 0x00, 0xDA, + 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF, 0xD1, 0x01, + 0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1, 0xAE, 0x2A, 0x86, 0x35, 0xB1, + 0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E, 0x01, 0x7B, 0xFF, 0x20, 0x00, + 0xFE, 0xFF, 0x02, 0x00, 0x05, 0x00, 0xC3, 0xFF, 0xE0, 0x00, 0xB3, + 0xFD, 0x4B, 0x05, 0x4D, 0xF3, 0x45, 0x3D, 0xE0, 0x20, 0x4F, 0xF3, + 0xD5, 0x06, 0x3D, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA4, 0x00, 0x19, 0xFF, 0xDD, 0x00, + 0xF0, 0xFF, 0xD4, 0xFC, 0xC9, 0x47, 0xD8, 0x0B, 0x7C, 0xF9, 0x35, + 0x04, 0x5F, 0xFD, 0x74, 0x01, 0x5E, 0xFF, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2C, 0x00, 0x56, 0xFF, 0x87, 0x01, 0x34, 0xFD, 0x8F, + 0x04, 0xC0, 0xF8, 0xD9, 0x0D, 0x31, 0x47, 0x7B, 0xFB, 0x9C, 0x00, + 0x7D, 0x00, 0x4D, 0xFF, 0x8A, 0x00, 0xB9, 0xFF, 0x11, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF7, 0x06, + 0xE0, 0xF2, 0x18, 0x23, 0xAB, 0x3B, 0xCC, 0xF2, 0xA8, 0x05, 0x76, + 0xFD, 0x04, 0x01, 0xB1, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0xFE, 0xFF, + 0x1A, 0x00, 0x89, 0xFF, 0x53, 0x01, 0xF5, 0xFC, 0x63, 0x06, 0xE9, + 0xF1, 0x63, 0x37, 0x85, 0x28, 0x09, 0xF2, 0x27, 0x07, 0x35, 0xFC, + 0xDA, 0x01, 0x40, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xD1, + 0xFF, 0x4E, 0x00, 0xCA, 0xFF, 0x9A, 0xFF, 0x2A, 0x02, 0x83, 0xF8, + 0x3F, 0x45, 0xFB, 0x12, 0x01, 0xF7, 0x5D, 0x05, 0xD3, 0xFC, 0xB1, + 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, + 0x73, 0xFF, 0x3F, 0x01, 0xD3, 0xFD, 0x4C, 0x03, 0x54, 0xFB, 0x1F, + 0x07, 0xBB, 0x48, 0x7D, 0x00, 0x33, 0xFE, 0xCF, 0x01, 0x98, 0xFE, + 0xE2, 0x00, 0x97, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, + 0xFF, 0xDC, 0x01, 0x64, 0xFC, 0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B, + 0xD9, 0x40, 0xEA, 0xF4, 0x3E, 0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5, + 0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5E, 0xFF, + 0xA6, 0x01, 0x76, 0xFC, 0x01, 0x07, 0x7D, 0xF1, 0xAD, 0x30, 0xDC, + 0x2F, 0x7F, 0xF1, 0x0C, 0x07, 0x6C, 0xFC, 0xAD, 0x01, 0x5A, 0xFF, + 0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xEF, 0xFF, 0xFE, 0xFF, 0x6C, + 0x00, 0x7B, 0xFE, 0x0C, 0x04, 0x3A, 0xF5, 0x5F, 0x41, 0x83, 0x1A, + 0xCD, 0xF4, 0x4B, 0x06, 0x6D, 0xFC, 0xD9, 0x01, 0x39, 0xFF, 0x35, + 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x93, 0xFF, 0xEC, 0x00, 0x83, 0xFE, + 0xF7, 0x01, 0xE8, 0xFD, 0x21, 0x01, 0xD2, 0x48, 0x64, 0x06, 0xA1, + 0xFB, 0x26, 0x03, 0xE7, 0xFD, 0x35, 0x01, 0x76, 0xFF, 0x22, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x44, 0xFF, 0xB7, 0x01, 0xC5, + 0xFC, 0x7C, 0x05, 0xBC, 0xF6, 0xD5, 0x13, 0xDC, 0x44, 0x14, 0xF8, + 0x67, 0x02, 0x77, 0xFF, 0xDD, 0xFF, 0x44, 0x00, 0xD5, 0xFF, 0x0B, + 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x42, 0xFF, 0xD7, 0x01, 0x39, 0xFC, + 0x29, 0x07, 0xEF, 0xF1, 0x62, 0x29, 0xA5, 0x36, 0xD0, 0xF1, 0x7B, + 0x06, 0xE3, 0xFC, 0x5E, 0x01, 0x83, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, + 0x01, 0x00, 0x09, 0x00, 0xB8, 0xFF, 0xF6, 0x00, 0x8D, 0xFD, 0x84, + 0x05, 0xFD, 0xF2, 0x52, 0x3C, 0x35, 0x22, 0x0B, 0xF3, 0xEB, 0x06, + 0x37, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12, + 0x00, 0xB5, 0xFF, 0x94, 0x00, 0x39, 0xFF, 0xA3, 0x00, 0x58, 0x00, + 0x02, 0xFC, 0x73, 0x47, 0x0B, 0x0D, 0x0B, 0xF9, 0x6C, 0x04, 0x45, + 0xFD, 0x80, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2A, 0x00, 0x5B, 0xFF, 0x7C, 0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31, + 0xF9, 0xA4, 0x0C, 0x90, 0x47, 0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00, + 0x2E, 0xFF, 0x99, 0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x39, 0xFC, 0xE4, 0x06, 0x21, 0xF3, + 0xC4, 0x21, 0xA5, 0x3C, 0x16, 0xF3, 0x72, 0x05, 0x9A, 0xFD, 0xEF, + 0x00, 0xBC, 0xFF, 0x08, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1E, 0x00, + 0x80, 0xFF, 0x64, 0x01, 0xDA, 0xFC, 0x87, 0x06, 0xC5, 0xF1, 0x46, + 0x36, 0xD1, 0x29, 0xE3, 0xF1, 0x2A, 0x07, 0x3A, 0xFC, 0xD5, 0x01, + 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD6, 0xFF, 0x3F, + 0x00, 0xE7, 0xFF, 0x65, 0xFF, 0x85, 0x02, 0xDE, 0xF7, 0xA9, 0x44, + 0x43, 0x14, 0x99, 0xF6, 0x8B, 0x05, 0xBF, 0xFC, 0xBA, 0x01, 0x43, + 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x78, 0xFF, + 0x31, 0x01, 0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06, 0xDB, + 0x48, 0x73, 0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1, 0x00, + 0x91, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7, + 0x01, 0x72, 0xFC, 0x3F, 0x06, 0xEB, 0xF4, 0x12, 0x1A, 0xA1, 0x41, + 0x63, 0xF5, 0xF3, 0x03, 0x8A, 0xFE, 0x63, 0x00, 0x02, 0x00, 0xEE, + 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x58, 0xFF, 0xB1, 0x01, + 0x67, 0xFC, 0x10, 0x07, 0x81, 0xF1, 0x73, 0x2F, 0x15, 0x31, 0x7C, + 0xF1, 0xFB, 0x06, 0x7C, 0xFC, 0xA2, 0x01, 0x60, 0xFF, 0x29, 0x00, + 0xFD, 0xFF, 0x04, 0x00, 0xF4, 0xFF, 0xF1, 0xFF, 0x85, 0x00, 0x4E, + 0xFE, 0x56, 0x04, 0xC3, 0xF4, 0x95, 0x40, 0xD8, 0x1B, 0x76, 0xF4, + 0x6D, 0x06, 0x60, 0xFC, 0xDD, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3, 0xFE, 0xBB, 0x01, + 0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07, 0x2E, 0xFB, 0x60, + 0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAE, 0x01, 0xDB, 0xFC, 0x4D, + 0x05, 0x24, 0xF7, 0x8E, 0x12, 0x6D, 0x45, 0xBC, 0xF8, 0x0C, 0x02, + 0xAC, 0xFF, 0xC0, 0xFF, 0x52, 0x00, 0xCF, 0xFF, 0x0C, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDC, 0x01, 0x34, 0xFC, 0x25, 0x07, + 0x18, 0xF2, 0x15, 0x28, 0xBF, 0x37, 0xF7, 0xF1, 0x56, 0x06, 0xFE, + 0xFC, 0x4D, 0x01, 0x8C, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x0D, 0x00, 0xAE, 0xFF, 0x0B, 0x01, 0x6A, 0xFD, 0xBA, 0x05, 0xB4, + 0xF2, 0x58, 0x3B, 0x8A, 0x23, 0xCB, 0xF2, 0xFD, 0x06, 0x34, 0xFC, + 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBB, + 0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A, 0x00, 0xBE, 0x00, 0x38, 0xFB, + 0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8, 0xA1, 0x04, 0x2B, 0xFD, 0x8B, + 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00, + 0x5F, 0xFF, 0x70, 0x01, 0x68, 0xFD, 0x23, 0x04, 0xA2, 0xF9, 0x73, + 0x0B, 0xE4, 0x47, 0x1B, 0xFD, 0xCD, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, + 0xA9, 0x00, 0xAE, 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE6, 0x01, 0x3F, 0xFC, 0xCE, 0x06, 0x66, 0xF3, 0x6F, 0x20, + 0x96, 0x3D, 0x69, 0xF3, 0x38, 0x05, 0xBF, 0xFD, 0xD9, 0x00, 0xC7, + 0xFF, 0x04, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x78, 0xFF, + 0x74, 0x01, 0xC2, 0xFC, 0xA7, 0x06, 0xA8, 0xF1, 0x25, 0x35, 0x1B, + 0x2B, 0xC2, 0xF1, 0x2A, 0x07, 0x41, 0xFC, 0xCE, 0x01, 0x47, 0xFF, + 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04, + 0x00, 0x32, 0xFF, 0xDC, 0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15, + 0x34, 0xF6, 0xB7, 0x05, 0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7E, 0xFF, 0x23, 0x01, + 0x0F, 0xFE, 0xD7, 0x02, 0x3B, 0xFC, 0xF5, 0x04, 0xED, 0x48, 0x70, + 0x02, 0x52, 0xFD, 0x46, 0x02, 0x5A, 0xFE, 0xFF, 0x00, 0x8B, 0xFF, + 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xD2, 0x01, 0x81, + 0xFC, 0x1A, 0x06, 0x47, 0xF5, 0xC1, 0x18, 0x60, 0x42, 0xE4, 0xF5, + 0xA6, 0x03, 0xB9, 0xFE, 0x48, 0x00, 0x0F, 0x00, 0xE9, 0xFF, 0x07, + 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x53, 0xFF, 0xBB, 0x01, 0x5A, 0xFC, + 0x1C, 0x07, 0x8D, 0xF1, 0x34, 0x2E, 0x48, 0x32, 0x81, 0xF1, 0xE7, + 0x06, 0x8E, 0xFC, 0x96, 0x01, 0x66, 0xFF, 0x27, 0x00, 0xFD, 0xFF, + 0x04, 0x00, 0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B, + 0x04, 0x55, 0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06, + 0x55, 0xFC, 0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, + 0x00, 0x9F, 0xFF, 0xCE, 0x00, 0xC2, 0xFE, 0x80, 0x01, 0xC6, 0xFE, + 0x40, 0xFF, 0x81, 0x48, 0x9E, 0x08, 0xBA, 0xFA, 0x9A, 0x03, 0xAC, + 0xFD, 0x51, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x2F, 0x00, 0x4B, 0xFF, 0xA4, 0x01, 0xF1, 0xFC, 0x1D, 0x05, 0x8F, + 0xF7, 0x4A, 0x11, 0xF2, 0x45, 0x6B, 0xF9, 0xAE, 0x01, 0xE2, 0xFF, + 0xA2, 0xFF, 0x61, 0x00, 0xC9, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x35, + 0x00, 0x3D, 0xFF, 0xE0, 0x01, 0x32, 0xFC, 0x1D, 0x07, 0x45, 0xF2, + 0xC6, 0x26, 0xD3, 0x38, 0x24, 0xF2, 0x2D, 0x06, 0x1B, 0xFD, 0x3B, + 0x01, 0x95, 0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x11, 0x00, + 0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74, 0xF2, 0x54, + 0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC, 0xE4, 0x01, + 0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC1, 0xFF, 0x76, + 0x00, 0x76, 0xFF, 0x32, 0x00, 0x22, 0x01, 0x76, 0xFA, 0xA3, 0x46, + 0x7D, 0x0F, 0x2C, 0xF8, 0xD5, 0x04, 0x13, 0xFD, 0x96, 0x01, 0x51, + 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x64, 0xFF, + 0x63, 0x01, 0x84, 0xFD, 0xEB, 0x03, 0x14, 0xFA, 0x47, 0x0A, 0x2C, + 0x48, 0xF6, 0xFD, 0x63, 0xFF, 0x2B, 0x01, 0xF0, 0xFE, 0xB8, 0x00, + 0xA8, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, + 0x01, 0x47, 0xFC, 0xB5, 0x06, 0xB0, 0xF3, 0x19, 0x1F, 0x7E, 0x3E, + 0xC4, 0xF3, 0xFA, 0x04, 0xE7, 0xFD, 0xC1, 0x00, 0xD3, 0xFF, 0xFF, + 0xFF, 0x02, 0x00, 0xFE, 0xFF, 0x23, 0x00, 0x71, 0xFF, 0x82, 0x01, + 0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1, 0xFD, 0x33, 0x62, 0x2C, 0xA8, + 0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7, 0x01, 0x4C, 0xFF, 0x30, 0x00, + 0xFD, 0xFF, 0x08, 0x00, 0xE1, 0xFF, 0x23, 0x00, 0x20, 0x00, 0x00, + 0xFF, 0x31, 0x03, 0xAD, 0xF6, 0x65, 0x43, 0xDC, 0x16, 0xD1, 0xF5, + 0xE1, 0x05, 0x99, 0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83, 0xFF, 0x14, 0x01, 0x2D, 0xFE, + 0x9C, 0x02, 0xAD, 0xFC, 0xE9, 0x03, 0xF6, 0x48, 0x73, 0x03, 0xE0, + 0xFC, 0x82, 0x02, 0x3B, 0xFE, 0x0E, 0x01, 0x86, 0xFF, 0x1E, 0x00, + 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCC, 0x01, 0x91, 0xFC, 0xF3, + 0x05, 0xA6, 0xF5, 0x70, 0x17, 0x17, 0x43, 0x6D, 0xF6, 0x56, 0x03, + 0xEA, 0xFE, 0x2D, 0x00, 0x1D, 0x00, 0xE4, 0xFF, 0x08, 0x00, 0xFD, + 0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3, 0x01, 0x4E, 0xFC, 0x24, 0x07, + 0x9E, 0xF1, 0xF2, 0x2C, 0x78, 0x33, 0x8C, 0xF1, 0xD0, 0x06, 0xA2, + 0xFC, 0x88, 0x01, 0x6D, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x03, 0x00, + 0xFD, 0xFF, 0xD8, 0xFF, 0xB7, 0x00, 0xF9, 0xFD, 0xDE, 0x04, 0xEF, + 0xF3, 0xE4, 0x3E, 0x81, 0x1E, 0xD2, 0xF3, 0xA9, 0x06, 0x4B, 0xFC, + 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA5, + 0xFF, 0xBE, 0x00, 0xE2, 0xFE, 0x45, 0x01, 0x33, 0xFF, 0x5A, 0xFE, + 0x48, 0x48, 0xC3, 0x09, 0x47, 0xFA, 0xD2, 0x03, 0x90, 0xFD, 0x5E, + 0x01, 0x66, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, + 0x4F, 0xFF, 0x9A, 0x01, 0x08, 0xFD, 0xEB, 0x04, 0xFC, 0xF7, 0x0A, + 0x10, 0x70, 0x46, 0x22, 0xFA, 0x4D, 0x01, 0x19, 0x00, 0x84, 0xFF, + 0x70, 0x00, 0xC4, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, + 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x12, 0x07, 0x79, 0xF2, 0x73, 0x25, + 0xDF, 0x39, 0x5A, 0xF2, 0x00, 0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F, + 0xFF, 0x13, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x15, 0x00, 0x99, 0xFF, + 0x33, 0x01, 0x29, 0xFD, 0x1A, 0x06, 0x3B, 0xF2, 0x4B, 0x39, 0x30, + 0x26, 0x5B, 0xF2, 0x19, 0x07, 0x31, 0xFC, 0xE1, 0x01, 0x3C, 0xFF, + 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, 0xC7, 0xFF, 0x68, 0x00, 0x95, + 0xFF, 0xFA, 0xFF, 0x83, 0x01, 0xBB, 0xF9, 0x2B, 0x46, 0xBB, 0x10, + 0xBF, 0xF7, 0x07, 0x05, 0xFB, 0xFC, 0xA0, 0x01, 0x4D, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x69, 0xFF, 0x56, 0x01, + 0xA0, 0xFD, 0xB3, 0x03, 0x87, 0xFA, 0x1F, 0x09, 0x6A, 0x48, 0xD9, + 0xFE, 0xF6, 0xFE, 0x65, 0x01, 0xD0, 0xFE, 0xC7, 0x00, 0xA2, 0xFF, + 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x50, + 0xFC, 0x99, 0x06, 0xFE, 0xF3, 0xC3, 0x1D, 0x5E, 0x3F, 0x27, 0xF4, + 0xB9, 0x04, 0x10, 0xFE, 0xA9, 0x00, 0xDF, 0xFF, 0xFB, 0xFF, 0x03, + 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x69, 0xFF, 0x90, 0x01, 0x96, 0xFC, + 0xDD, 0x06, 0x85, 0xF1, 0xD0, 0x32, 0xA6, 0x2D, 0x94, 0xF1, 0x20, + 0x07, 0x54, 0xFC, 0xBF, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, + 0x07, 0x00, 0xE6, 0xFF, 0x15, 0x00, 0x3C, 0x00, 0xCF, 0xFE, 0x83, + 0x03, 0x20, 0xF6, 0xB2, 0x42, 0x2B, 0x18, 0x71, 0xF5, 0x09, 0x06, + 0x88, 0xFC, 0xCF, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, + 0x00, 0x89, 0xFF, 0x06, 0x01, 0x4C, 0xFE, 0x60, 0x02, 0x1F, 0xFD, + 0xE2, 0x02, 0xF3, 0x48, 0x7D, 0x04, 0x6E, 0xFC, 0xBD, 0x02, 0x1C, + 0xFE, 0x1C, 0x01, 0x80, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x33, 0x00, 0x3F, 0xFF, 0xC5, 0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07, + 0xF6, 0x22, 0x16, 0xC3, 0x43, 0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF, + 0x11, 0x00, 0x2B, 0x00, 0xDE, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, + 0x00, 0x49, 0xFF, 0xCB, 0x01, 0x45, 0xFC, 0x29, 0x07, 0xB6, 0xF1, + 0xAD, 0x2B, 0xA2, 0x34, 0x9E, 0xF1, 0xB4, 0x06, 0xB8, 0xFC, 0x7A, + 0x01, 0x75, 0xFF, 0x22, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x02, 0x00, + 0xCC, 0xFF, 0xCE, 0x00, 0xD1, 0xFD, 0x1D, 0x05, 0x91, 0xF3, 0xFE, + 0x3D, 0xD7, 0x1F, 0x87, 0xF3, 0xC3, 0x06, 0x42, 0xFC, 0xE5, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAB, 0xFF, 0xAF, + 0x00, 0x01, 0xFF, 0x0A, 0x01, 0x9E, 0xFF, 0x7C, 0xFD, 0x03, 0x48, + 0xED, 0x0A, 0xD5, 0xF9, 0x0A, 0x04, 0x74, 0xFD, 0x6A, 0x01, 0x62, + 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF, + 0x90, 0x01, 0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E, 0xE1, + 0x46, 0xE1, 0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F, 0x00, + 0xBE, 0xFF, 0x10, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, + 0x01, 0x33, 0xFC, 0x04, 0x07, 0xB1, 0xF2, 0x21, 0x24, 0xE6, 0x3A, + 0x97, 0xF2, 0xD0, 0x05, 0x5B, 0xFD, 0x15, 0x01, 0xA9, 0xFF, 0x0F, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x00, 0x90, 0xFF, 0x45, 0x01, + 0x0B, 0xFD, 0x44, 0x06, 0x0A, 0xF2, 0x3B, 0x38, 0x80, 0x27, 0x2B, + 0xF2, 0x22, 0x07, 0x33, 0xFC, 0xDE, 0x01, 0x3E, 0xFF, 0x34, 0x00, + 0xFD, 0xFF, 0x0D, 0x00, 0xCD, 0xFF, 0x59, 0x00, 0xB3, 0xFF, 0xC4, + 0xFF, 0xE2, 0x01, 0x09, 0xF9, 0xAA, 0x45, 0xFE, 0x11, 0x54, 0xF7, + 0x38, 0x05, 0xE4, 0xFC, 0xAA, 0x01, 0x49, 0xFF, 0x30, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49, 0x01, 0xBC, 0xFD, + 0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48, 0xC3, 0xFF, 0x89, + 0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C, 0xFF, 0x18, 0x00, + 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5B, 0xFC, 0x7B, + 0x06, 0x50, 0xF4, 0x6E, 0x1C, 0x36, 0x40, 0x92, 0xF4, 0x75, 0x04, + 0x3B, 0xFE, 0x91, 0x00, 0xEB, 0xFF, 0xF6, 0xFF, 0x04, 0x00, 0xFD, + 0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9D, 0x01, 0x84, 0xFC, 0xF3, 0x06, + 0x7D, 0xF1, 0x9E, 0x31, 0xE6, 0x2E, 0x85, 0xF1, 0x16, 0x07, 0x61, + 0xFC, 0xB5, 0x01, 0x55, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x06, 0x00, + 0xEC, 0xFF, 0x08, 0x00, 0x57, 0x00, 0x9F, 0xFE, 0xD1, 0x03, 0x9B, + 0xF5, 0xF7, 0x41, 0x7C, 0x19, 0x13, 0xF5, 0x2F, 0x06, 0x78, 0xFC, + 0xD5, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8F, + 0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25, 0x02, 0x91, 0xFD, 0xE3, 0x01, + 0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB, 0xF8, 0x02, 0xFE, 0xFD, 0x2B, + 0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, + 0x42, 0xFF, 0xBD, 0x01, 0xB6, 0xFC, 0x9F, 0x05, 0x6C, 0xF6, 0xD6, + 0x14, 0x65, 0x44, 0x98, 0xF7, 0xAC, 0x02, 0x4E, 0xFF, 0xF4, 0xFF, + 0x39, 0x00, 0xD9, 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45, + 0xFF, 0xD2, 0x01, 0x3D, 0xFC, 0x2B, 0x07, 0xD4, 0xF1, 0x64, 0x2A, + 0xC6, 0x35, 0xB7, 0xF1, 0x96, 0x06, 0xCF, 0xFC, 0x6B, 0x01, 0x7D, + 0xFF, 0x1F, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x06, 0x00, 0xC1, 0xFF, + 0xE5, 0x00, 0xAA, 0xFD, 0x58, 0x05, 0x3A, 0xF3, 0x11, 0x3D, 0x2C, + 0x21, 0x3F, 0xF3, 0xDA, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20, + 0xFF, 0xD0, 0x00, 0x07, 0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C, + 0x63, 0xF9, 0x42, 0x04, 0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x85, 0x01, + 0x39, 0xFD, 0x84, 0x04, 0xD9, 0xF8, 0x95, 0x0D, 0x48, 0x47, 0xA7, + 0xFB, 0x86, 0x00, 0x8A, 0x00, 0x46, 0xFF, 0x8E, 0x00, 0xB8, 0xFF, + 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x35, + 0xFC, 0xF3, 0x06, 0xEE, 0xF2, 0xCD, 0x22, 0xE4, 0x3B, 0xDC, 0xF2, + 0x9C, 0x05, 0x7E, 0xFD, 0x00, 0x01, 0xB4, 0xFF, 0x0B, 0x00, 0x01, + 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x87, 0xFF, 0x57, 0x01, 0xEF, 0xFC, + 0x6B, 0x06, 0xE0, 0xF1, 0x23, 0x37, 0xCE, 0x28, 0x01, 0xF2, 0x28, + 0x07, 0x36, 0xFC, 0xD9, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x0B, 0x00, 0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F, + 0x02, 0x5E, 0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05, + 0xCF, 0xFC, 0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x23, 0x00, 0x74, 0xFF, 0x3C, 0x01, 0xDA, 0xFD, 0x40, 0x03, + 0x6E, 0xFB, 0xE1, 0x06, 0xC3, 0x48, 0xB3, 0x00, 0x1A, 0xFE, 0xDC, + 0x01, 0x91, 0xFE, 0xE5, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, + 0x36, 0x00, 0x38, 0xFF, 0xDB, 0x01, 0x67, 0xFC, 0x5A, 0x06, 0xA6, + 0xF4, 0x1B, 0x1B, 0x07, 0x41, 0x04, 0xF5, 0x2D, 0x04, 0x67, 0xFE, + 0x77, 0x00, 0xF8, 0xFF, 0xF2, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A, + 0x00, 0x5C, 0xFF, 0xA8, 0x01, 0x73, 0xFC, 0x05, 0x07, 0x7D, 0xF1, + 0x67, 0x30, 0x21, 0x30, 0x7E, 0xF1, 0x08, 0x07, 0x6F, 0xFC, 0xAB, + 0x01, 0x5B, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF0, 0xFF, + 0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F, 0xF5, 0x32, + 0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC, 0xDA, 0x01, + 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE8, + 0x00, 0x8A, 0xFE, 0xE9, 0x01, 0x01, 0xFE, 0xEA, 0x00, 0xCB, 0x48, + 0xA2, 0x06, 0x87, 0xFB, 0x33, 0x03, 0xE0, 0xFD, 0x39, 0x01, 0x75, + 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x45, 0xFF, + 0xB5, 0x01, 0xCA, 0xFC, 0x72, 0x05, 0xD3, 0xF6, 0x8D, 0x13, 0xFD, + 0x44, 0x39, 0xF8, 0x53, 0x02, 0x82, 0xFF, 0xD7, 0xFF, 0x47, 0x00, + 0xD3, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x42, 0xFF, 0xD8, + 0x01, 0x37, 0xFC, 0x29, 0x07, 0xF8, 0xF1, 0x19, 0x29, 0xE5, 0x36, + 0xD8, 0xF1, 0x73, 0x06, 0xE9, 0xFC, 0x5B, 0x01, 0x85, 0xFF, 0x1C, + 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A, 0x00, 0xB6, 0xFF, 0xFB, 0x00, + 0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2, 0x1C, 0x3C, 0x81, 0x22, 0xFC, + 0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x12, 0x00, 0xB7, 0xFF, 0x91, 0x00, 0x40, 0xFF, 0x96, + 0x00, 0x6F, 0x00, 0xD5, 0xFB, 0x5E, 0x47, 0x50, 0x0D, 0xF2, 0xF8, + 0x78, 0x04, 0x3F, 0xFD, 0x82, 0x01, 0x58, 0xFF, 0x2B, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5C, 0xFF, 0x79, 0x01, 0x53, 0xFD, + 0x4E, 0x04, 0x4A, 0xF9, 0x60, 0x0C, 0xA3, 0x47, 0x76, 0xFC, 0x1F, + 0x00, 0xC3, 0x00, 0x27, 0xFF, 0x9D, 0x00, 0xB2, 0xFF, 0x13, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x3A, 0xFC, 0xDF, + 0x06, 0x30, 0xF3, 0x78, 0x21, 0xDB, 0x3C, 0x28, 0xF3, 0x65, 0x05, + 0xA2, 0xFD, 0xEA, 0x00, 0xBE, 0xFF, 0x07, 0x00, 0x01, 0x00, 0xFE, + 0xFF, 0x1E, 0x00, 0x7F, 0xFF, 0x67, 0x01, 0xD5, 0xFC, 0x8E, 0x06, + 0xBE, 0xF1, 0x06, 0x36, 0x1A, 0x2A, 0xDC, 0xF1, 0x2A, 0x07, 0x3C, + 0xFC, 0xD3, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, + 0xD8, 0xFF, 0x3C, 0x00, 0xEE, 0xFF, 0x5A, 0xFF, 0x98, 0x02, 0xBB, + 0xF7, 0x87, 0x44, 0x8C, 0x14, 0x83, 0xF6, 0x95, 0x05, 0xBA, 0xFC, + 0xBB, 0x01, 0x43, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, + 0x00, 0x79, 0xFF, 0x2E, 0x01, 0xF7, 0xFD, 0x05, 0x03, 0xE1, 0xFB, + 0xCA, 0x05, 0xDF, 0x48, 0xAB, 0x01, 0xAA, 0xFD, 0x18, 0x02, 0x72, + 0xFE, 0xF4, 0x00, 0x90, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x39, 0xFF, 0xD6, 0x01, 0x75, 0xFC, 0x37, 0x06, 0xFF, 0xF4, 0xC7, + 0x19, 0xCC, 0x41, 0x7F, 0xF5, 0xE2, 0x03, 0x95, 0xFE, 0x5D, 0x00, + 0x05, 0x00, 0xED, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x57, + 0xFF, 0xB3, 0x01, 0x64, 0xFC, 0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F, + 0x5A, 0x31, 0x7D, 0xF1, 0xF7, 0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61, + 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF5, 0xFF, 0xEE, 0xFF, + 0x8B, 0x00, 0x44, 0xFE, 0x65, 0x04, 0xAA, 0xF4, 0x66, 0x40, 0x23, + 0x1C, 0x63, 0xF4, 0x74, 0x06, 0x5D, 0xFC, 0xDE, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, 0x9A, 0xFF, 0xD9, 0x00, 0xAA, + 0xFE, 0xAE, 0x01, 0x70, 0xFE, 0xF8, 0xFF, 0xA6, 0x48, 0xBE, 0x07, + 0x14, 0xFB, 0x6D, 0x03, 0xC3, 0xFD, 0x46, 0x01, 0x70, 0xFF, 0x24, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAC, 0x01, + 0xDF, 0xFC, 0x43, 0x05, 0x3C, 0xF7, 0x46, 0x12, 0x8D, 0x45, 0xE2, + 0xF8, 0xF7, 0x01, 0xB8, 0xFF, 0xB9, 0xFF, 0x56, 0x00, 0xCE, 0xFF, + 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDD, 0x01, 0x34, + 0xFC, 0x23, 0x07, 0x21, 0xF2, 0xCB, 0x27, 0xFE, 0x37, 0x00, 0xF2, + 0x4D, 0x06, 0x04, 0xFD, 0x49, 0x01, 0x8E, 0xFF, 0x19, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAB, 0xFF, 0x10, 0x01, 0x62, 0xFD, + 0xC5, 0x05, 0xA5, 0xF2, 0x1F, 0x3B, 0xD6, 0x23, 0xBE, 0xF2, 0x01, + 0x07, 0x33, 0xFC, 0xE5, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x10, 0x00, 0xBD, 0xFF, 0x82, 0x00, 0x5E, 0xFF, 0x5D, 0x00, 0xD4, + 0x00, 0x0C, 0xFB, 0xF9, 0x46, 0x87, 0x0E, 0x82, 0xF8, 0xAD, 0x04, + 0x26, 0xFD, 0x8D, 0x01, 0x54, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x29, 0x00, 0x60, 0xFF, 0x6D, 0x01, 0x6E, 0xFD, 0x17, 0x04, + 0xBC, 0xF9, 0x30, 0x0B, 0xF4, 0x47, 0x4B, 0xFD, 0xB5, 0xFF, 0xFD, + 0x00, 0x08, 0xFF, 0xAC, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76, + 0xF3, 0x22, 0x20, 0xCA, 0x3D, 0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD, + 0xD4, 0x00, 0xCA, 0xFF, 0x03, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x21, + 0x00, 0x77, 0xFF, 0x77, 0x01, 0xBD, 0xFC, 0xAE, 0x06, 0xA3, 0xF1, + 0xE3, 0x34, 0x64, 0x2B, 0xBC, 0xF1, 0x2A, 0x07, 0x43, 0xFC, 0xCD, + 0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDD, 0xFF, + 0x2E, 0x00, 0x0A, 0x00, 0x27, 0xFF, 0xEF, 0x02, 0x20, 0xF7, 0xE7, + 0x43, 0xD8, 0x15, 0x1E, 0xF6, 0xC0, 0x05, 0xA7, 0xFC, 0xC3, 0x01, + 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F, + 0xFF, 0x20, 0x01, 0x16, 0xFE, 0xCA, 0x02, 0x54, 0xFC, 0xB9, 0x04, + 0xF2, 0x48, 0xA9, 0x02, 0x39, 0xFD, 0x53, 0x02, 0x53, 0xFE, 0x03, + 0x01, 0x8A, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF, + 0xD1, 0x01, 0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18, 0x89, + 0x42, 0x02, 0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12, 0x00, + 0xE8, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBD, + 0x01, 0x57, 0xFC, 0x1E, 0x07, 0x90, 0xF1, 0xED, 0x2D, 0x8C, 0x32, + 0x83, 0xF1, 0xE2, 0x06, 0x92, 0xFC, 0x93, 0x01, 0x68, 0xFF, 0x26, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFA, 0xFF, 0xE2, 0xFF, 0xA4, 0x00, + 0x19, 0xFE, 0xAA, 0x04, 0x3E, 0xF4, 0x90, 0x3F, 0x78, 0x1D, 0x10, + 0xF4, 0x93, 0x06, 0x52, 0xFC, 0xE1, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x17, 0x00, 0xA0, 0xFF, 0xCA, 0x00, 0xC9, 0xFE, 0x73, + 0x01, 0xDE, 0xFE, 0x0C, 0xFF, 0x76, 0x48, 0xDE, 0x08, 0xA1, 0xFA, + 0xA6, 0x03, 0xA6, 0xFD, 0x53, 0x01, 0x6A, 0xFF, 0x26, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2, 0x01, 0xF6, 0xFC, + 0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46, 0x93, 0xF9, 0x98, + 0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8, 0xFF, 0x0E, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x32, 0xFC, 0x1B, + 0x07, 0x50, 0xF2, 0x7B, 0x26, 0x11, 0x39, 0x2F, 0xF2, 0x23, 0x06, + 0x22, 0xFD, 0x37, 0x01, 0x97, 0xFF, 0x15, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x12, 0x00, 0xA1, 0xFF, 0x24, 0x01, 0x41, 0xFD, 0xF6, 0x05, + 0x67, 0xF2, 0x1A, 0x3A, 0x29, 0x25, 0x84, 0xF2, 0x0F, 0x07, 0x31, + 0xFC, 0xE3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0F, 0x00, + 0xC2, 0xFF, 0x73, 0x00, 0x7D, 0xFF, 0x25, 0x00, 0x38, 0x01, 0x4C, + 0xFA, 0x89, 0x46, 0xC3, 0x0F, 0x14, 0xF8, 0xE0, 0x04, 0x0D, 0xFD, + 0x98, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, + 0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A, 0xFD, 0xDF, 0x03, 0x2E, 0xFA, + 0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE, 0x4B, 0xFF, 0x38, 0x01, 0xE9, + 0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE4, 0x01, 0x49, 0xFC, 0xAF, 0x06, 0xC1, 0xF3, 0xCD, + 0x1E, 0xB1, 0x3E, 0xD9, 0xF3, 0xEC, 0x04, 0xF0, 0xFD, 0xBC, 0x00, + 0xD5, 0xFF, 0xFE, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6F, + 0xFF, 0x85, 0x01, 0xA6, 0xFC, 0xCA, 0x06, 0x8F, 0xF1, 0xBB, 0x33, + 0xAB, 0x2C, 0xA3, 0xF1, 0x26, 0x07, 0x4C, 0xFC, 0xC5, 0x01, 0x4D, + 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE2, 0xFF, 0x20, 0x00, + 0x26, 0x00, 0xF5, 0xFE, 0x43, 0x03, 0x8D, 0xF6, 0x3C, 0x43, 0x25, + 0x17, 0xBB, 0xF5, 0xEA, 0x05, 0x95, 0xFC, 0xCA, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, + 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, + 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, + 0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, + 0x3D, 0xFC, 0xD6, 0x06, 0x4C, 0xF3, 0xED, 0x20, 0x3D, 0x3D, 0x4A, + 0xF3, 0x4E, 0x05, 0xB1, 0xFD, 0xE1, 0x00, 0xC3, 0xFF, 0x05, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x05, 0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1, + 0xFD, 0x4E, 0x05, 0x4A, 0xF3, 0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3, + 0xD6, 0x06, 0x3D, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, + 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, + 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, + 0xFD, 0xFF, 0x30, 0x00, 0x4D, 0xFF, 0xC5, 0x01, 0x4C, 0xFC, 0x26, + 0x07, 0xA3, 0xF1, 0xAB, 0x2C, 0xBB, 0x33, 0x8F, 0xF1, 0xCA, 0x06, + 0xA6, 0xFC, 0x85, 0x01, 0x6F, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x16, + 0x00, 0xA6, 0xFF, 0xBB, 0x00, 0xE9, 0xFE, 0x38, 0x01, 0x4B, 0xFF, + 0x28, 0xFE, 0x3A, 0x48, 0x04, 0x0A, 0x2E, 0xFA, 0xDF, 0x03, 0x8A, + 0xFD, 0x60, 0x01, 0x65, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x0F, 0x07, 0x84, + 0xF2, 0x29, 0x25, 0x1A, 0x3A, 0x67, 0xF2, 0xF6, 0x05, 0x41, 0xFD, + 0x24, 0x01, 0xA1, 0xFF, 0x12, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xC8, + 0xFF, 0x64, 0x00, 0x9B, 0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93, 0xF9, + 0x10, 0x46, 0x03, 0x11, 0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC, 0xA2, + 0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE1, 0x01, 0x52, 0xFC, 0x93, 0x06, 0x10, 0xF4, 0x78, + 0x1D, 0x90, 0x3F, 0x3E, 0xF4, 0xAA, 0x04, 0x19, 0xFE, 0xA4, 0x00, + 0xE2, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0x07, 0x00, 0xE8, 0xFF, 0x12, + 0x00, 0x42, 0x00, 0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42, + 0x76, 0x18, 0x5C, 0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B, + 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF, + 0xC3, 0x01, 0xA7, 0xFC, 0xC0, 0x05, 0x1E, 0xF6, 0xD8, 0x15, 0xE7, + 0x43, 0x20, 0xF7, 0xEF, 0x02, 0x27, 0xFF, 0x0A, 0x00, 0x2E, 0x00, + 0xDD, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4, + 0x00, 0xC8, 0xFD, 0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20, + 0x76, 0xF3, 0xC8, 0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x54, 0xFF, 0x8D, 0x01, + 0x26, 0xFD, 0xAD, 0x04, 0x82, 0xF8, 0x87, 0x0E, 0xF9, 0x46, 0x0C, + 0xFB, 0xD4, 0x00, 0x5D, 0x00, 0x5E, 0xFF, 0x82, 0x00, 0xBD, 0xFF, + 0x10, 0x00, 0xFF, 0xFF, 0x19, 0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04, + 0xFD, 0x4D, 0x06, 0x00, 0xF2, 0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2, + 0x23, 0x07, 0x34, 0xFC, 0xDD, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x24, 0x00, 0x70, 0xFF, 0x46, 0x01, 0xC3, 0xFD, + 0x6D, 0x03, 0x14, 0xFB, 0xBE, 0x07, 0xA6, 0x48, 0xF8, 0xFF, 0x70, + 0xFE, 0xAE, 0x01, 0xAA, 0xFE, 0xD9, 0x00, 0x9A, 0xFF, 0x19, 0x00, + 0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F, 0x01, 0x80, 0xFC, 0xF7, + 0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F, 0x83, 0xF1, 0x13, 0x07, + 0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x1B, + 0x00, 0x90, 0xFF, 0xF4, 0x00, 0x72, 0xFE, 0x18, 0x02, 0xAA, 0xFD, + 0xAB, 0x01, 0xDF, 0x48, 0xCA, 0x05, 0xE1, 0xFB, 0x05, 0x03, 0xF7, + 0xFD, 0x2E, 0x01, 0x79, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A, 0x07, 0xDC, + 0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06, 0xD5, 0xFC, + 0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x13, 0x00, 0xB2, + 0xFF, 0x9D, 0x00, 0x27, 0xFF, 0xC3, 0x00, 0x1F, 0x00, 0x76, 0xFC, + 0xA3, 0x47, 0x60, 0x0C, 0x4A, 0xF9, 0x4E, 0x04, 0x53, 0xFD, 0x79, + 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC, 0xF2, 0x81, + 0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD, 0xFB, 0x00, + 0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0x0B, 0x00, 0xD3, 0xFF, 0x47, + 0x00, 0xD7, 0xFF, 0x82, 0xFF, 0x53, 0x02, 0x39, 0xF8, 0xFD, 0x44, + 0x8D, 0x13, 0xD3, 0xF6, 0x72, 0x05, 0xCA, 0xFC, 0xB5, 0x01, 0x45, + 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x38, 0xFF, + 0xDA, 0x01, 0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4, 0xCE, 0x1A, 0x32, + 0x41, 0x1F, 0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71, 0x00, 0xFB, 0xFF, + 0xF0, 0xFF, 0x05, 0x00, 0x05, 0x00, 0xF2, 0xFF, 0xF8, 0xFF, 0x77, + 0x00, 0x67, 0xFE, 0x2D, 0x04, 0x04, 0xF5, 0x07, 0x41, 0x1B, 0x1B, + 0xA6, 0xF4, 0x5A, 0x06, 0x67, 0xFC, 0xDB, 0x01, 0x38, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB3, 0x01, + 0xCF, 0xFC, 0x67, 0x05, 0xEA, 0xF6, 0x44, 0x13, 0x1E, 0x45, 0x5E, + 0xF8, 0x3F, 0x02, 0x8E, 0xFF, 0xD0, 0xFF, 0x4A, 0x00, 0xD2, 0xFF, + 0x0B, 0x00, 0x01, 0x00, 0x0B, 0x00, 0xB4, 0xFF, 0x00, 0x01, 0x7E, + 0xFD, 0x9C, 0x05, 0xDC, 0xF2, 0xE4, 0x3B, 0xCD, 0x22, 0xEE, 0xF2, + 0xF3, 0x06, 0x35, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x2A, 0x00, 0x5D, 0xFF, 0x76, 0x01, 0x59, 0xFD, + 0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C, 0xB6, 0x47, 0xA4, 0xFC, 0x07, + 0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0, 0x00, 0xB1, 0xFF, 0x13, 0x00, + 0xFE, 0xFF, 0x1F, 0x00, 0x7D, 0xFF, 0x6B, 0x01, 0xCF, 0xFC, 0x96, + 0x06, 0xB7, 0xF1, 0xC6, 0x35, 0x64, 0x2A, 0xD4, 0xF1, 0x2B, 0x07, + 0x3D, 0xFC, 0xD2, 0x01, 0x45, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x21, 0x00, 0x7A, 0xFF, 0x2B, 0x01, 0xFE, 0xFD, 0xF8, 0x02, + 0xFB, 0xFB, 0x8D, 0x05, 0xE5, 0x48, 0xE3, 0x01, 0x91, 0xFD, 0x25, + 0x02, 0x6B, 0xFE, 0xF7, 0x00, 0x8F, 0xFF, 0x1C, 0x00, 0xFD, 0xFF, + 0x2D, 0x00, 0x55, 0xFF, 0xB5, 0x01, 0x61, 0xFC, 0x16, 0x07, 0x85, + 0xF1, 0xE6, 0x2E, 0x9E, 0x31, 0x7D, 0xF1, 0xF3, 0x06, 0x84, 0xFC, + 0x9D, 0x01, 0x63, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x18, 0x00, 0x9C, + 0xFF, 0xD6, 0x00, 0xB1, 0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3, 0xFF, + 0x9C, 0x48, 0xFD, 0x07, 0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD, 0x49, + 0x01, 0x6E, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x3E, 0xFF, 0xDE, 0x01, 0x33, 0xFC, 0x22, 0x07, 0x2B, 0xF2, 0x80, + 0x27, 0x3B, 0x38, 0x0A, 0xF2, 0x44, 0x06, 0x0B, 0xFD, 0x45, 0x01, + 0x90, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x10, 0x00, 0xBE, 0xFF, 0x7F, + 0x00, 0x65, 0xFF, 0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46, + 0xCD, 0x0E, 0x6A, 0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53, + 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE5, 0x01, 0x42, 0xFC, 0xC3, 0x06, 0x87, 0xF3, 0xD7, 0x1F, 0xFE, + 0x3D, 0x91, 0xF3, 0x1D, 0x05, 0xD1, 0xFD, 0xCE, 0x00, 0xCC, 0xFF, + 0x02, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11, + 0x00, 0x1B, 0xFF, 0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16, + 0x07, 0xF6, 0xCA, 0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCF, 0x01, + 0x88, 0xFC, 0x09, 0x06, 0x71, 0xF5, 0x2B, 0x18, 0xB2, 0x42, 0x20, + 0xF6, 0x83, 0x03, 0xCF, 0xFE, 0x3C, 0x00, 0x15, 0x00, 0xE6, 0xFF, + 0x07, 0x00, 0x03, 0x00, 0xFB, 0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10, + 0xFE, 0xB9, 0x04, 0x27, 0xF4, 0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3, + 0x99, 0x06, 0x50, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4D, 0xFF, 0xA0, 0x01, 0xFB, 0xFC, + 0x07, 0x05, 0xBF, 0xF7, 0xBB, 0x10, 0x2B, 0x46, 0xBB, 0xF9, 0x83, + 0x01, 0xFA, 0xFF, 0x95, 0xFF, 0x68, 0x00, 0xC7, 0xFF, 0x0E, 0x00, + 0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28, 0x01, 0x3A, 0xFD, 0x00, + 0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25, 0x79, 0xF2, 0x12, 0x07, + 0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x27, 0x00, 0x66, 0xFF, 0x5E, 0x01, 0x90, 0xFD, 0xD2, 0x03, + 0x47, 0xFA, 0xC3, 0x09, 0x48, 0x48, 0x5A, 0xFE, 0x33, 0xFF, 0x45, + 0x01, 0xE2, 0xFE, 0xBE, 0x00, 0xA5, 0xFF, 0x16, 0x00, 0xFD, 0xFF, + 0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0, 0x06, 0x8C, + 0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07, 0x4E, 0xFC, + 0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x1E, 0x00, 0x86, + 0xFF, 0x0E, 0x01, 0x3B, 0xFE, 0x82, 0x02, 0xE0, 0xFC, 0x73, 0x03, + 0xF6, 0x48, 0xE9, 0x03, 0xAD, 0xFC, 0x9C, 0x02, 0x2D, 0xFE, 0x14, + 0x01, 0x83, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x30, 0x00, + 0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8, 0xF1, 0x62, + 0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC, 0x82, 0x01, + 0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x15, 0x00, 0xA8, 0xFF, 0xB8, + 0x00, 0xF0, 0xFE, 0x2B, 0x01, 0x63, 0xFF, 0xF6, 0xFD, 0x2C, 0x48, + 0x47, 0x0A, 0x14, 0xFA, 0xEB, 0x03, 0x84, 0xFD, 0x63, 0x01, 0x64, + 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A, 0xFF, + 0xE4, 0x01, 0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2, 0xDD, 0x24, 0x54, + 0x3A, 0x74, 0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20, 0x01, 0xA3, 0xFF, + 0x11, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xC9, 0xFF, 0x61, 0x00, 0xA2, + 0xFF, 0xE2, 0xFF, 0xAE, 0x01, 0x6B, 0xF9, 0xF2, 0x45, 0x4A, 0x11, + 0x8F, 0xF7, 0x1D, 0x05, 0xF1, 0xFC, 0xA4, 0x01, 0x4B, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE1, 0x01, + 0x55, 0xFC, 0x8C, 0x06, 0x22, 0xF4, 0x2C, 0x1D, 0xC0, 0x3F, 0x55, + 0xF4, 0x9B, 0x04, 0x23, 0xFE, 0x9F, 0x00, 0xE4, 0xFF, 0xF9, 0xFF, + 0x04, 0x00, 0x07, 0x00, 0xE9, 0xFF, 0x0F, 0x00, 0x48, 0x00, 0xB9, + 0xFE, 0xA6, 0x03, 0xE4, 0xF5, 0x60, 0x42, 0xC1, 0x18, 0x47, 0xF5, + 0x1A, 0x06, 0x81, 0xFC, 0xD2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF, 0xC1, 0x01, 0xAB, 0xFC, + 0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15, 0x0B, 0x44, 0x42, 0xF7, 0xDC, + 0x02, 0x32, 0xFF, 0x04, 0x00, 0x31, 0x00, 0xDC, 0xFF, 0x09, 0x00, + 0x02, 0x00, 0x04, 0x00, 0xC7, 0xFF, 0xD9, 0x00, 0xBF, 0xFD, 0x38, + 0x05, 0x69, 0xF3, 0x96, 0x3D, 0x6F, 0x20, 0x66, 0xF3, 0xCE, 0x06, + 0x3F, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, + 0xFF, 0x2C, 0x00, 0x55, 0xFF, 0x8B, 0x01, 0x2B, 0xFD, 0xA1, 0x04, + 0x9B, 0xF8, 0x42, 0x0E, 0x0F, 0x47, 0x38, 0xFB, 0xBE, 0x00, 0x6A, + 0x00, 0x58, 0xFF, 0x85, 0x00, 0xBB, 0xFF, 0x10, 0x00, 0xFF, 0xFF, + 0x19, 0x00, 0x8C, 0xFF, 0x4D, 0x01, 0xFE, 0xFC, 0x56, 0x06, 0xF7, + 0xF1, 0xBF, 0x37, 0x15, 0x28, 0x18, 0xF2, 0x25, 0x07, 0x34, 0xFC, + 0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x24, + 0x00, 0x71, 0xFF, 0x43, 0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E, 0xFB, + 0x7E, 0x07, 0xAF, 0x48, 0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01, 0xA3, + 0xFE, 0xDD, 0x00, 0x99, 0xFF, 0x19, 0x00, 0xFD, 0xFF, 0x29, 0x00, + 0x60, 0xFF, 0xA2, 0x01, 0x7C, 0xFC, 0xFB, 0x06, 0x7C, 0xF1, 0x15, + 0x31, 0x73, 0x2F, 0x81, 0xF1, 0x10, 0x07, 0x67, 0xFC, 0xB1, 0x01, + 0x58, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x1B, 0x00, 0x91, 0xFF, 0xF1, + 0x00, 0x79, 0xFE, 0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48, + 0x07, 0x06, 0xC7, 0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78, + 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF, + 0xD5, 0x01, 0x3A, 0xFC, 0x2A, 0x07, 0xE3, 0xF1, 0xD1, 0x29, 0x46, + 0x36, 0xC5, 0xF1, 0x87, 0x06, 0xDA, 0xFC, 0x64, 0x01, 0x80, 0xFF, + 0x1E, 0x00, 0xFE, 0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E, + 0xFF, 0xB6, 0x00, 0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C, + 0x31, 0xF9, 0x5A, 0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, + 0x37, 0xFC, 0xEB, 0x06, 0x0B, 0xF3, 0x35, 0x22, 0x52, 0x3C, 0xFD, + 0xF2, 0x84, 0x05, 0x8D, 0xFD, 0xF6, 0x00, 0xB8, 0xFF, 0x09, 0x00, + 0x01, 0x00, 0x0B, 0x00, 0xD5, 0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77, + 0xFF, 0x67, 0x02, 0x14, 0xF8, 0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6, + 0x7C, 0x05, 0xC5, 0xFC, 0xB7, 0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF, + 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD9, 0x01, 0x6D, 0xFC, + 0x4B, 0x06, 0xCD, 0xF4, 0x83, 0x1A, 0x5F, 0x41, 0x3A, 0xF5, 0x0C, + 0x04, 0x7B, 0xFE, 0x6C, 0x00, 0xFE, 0xFF, 0xEF, 0xFF, 0x05, 0x00, + 0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D, 0x00, 0x5D, 0xFE, 0x3E, + 0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B, 0x93, 0xF4, 0x62, 0x06, + 0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, + 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB1, 0x01, 0xD3, 0xFC, 0x5D, 0x05, + 0x01, 0xF7, 0xFB, 0x12, 0x3F, 0x45, 0x83, 0xF8, 0x2A, 0x02, 0x9A, + 0xFF, 0xCA, 0xFF, 0x4E, 0x00, 0xD1, 0xFF, 0x0C, 0x00, 0x00, 0x00, + 0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8, 0x05, 0xCC, + 0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06, 0x35, 0xFC, + 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x29, + 0x00, 0x5E, 0xFF, 0x74, 0x01, 0x5F, 0xFD, 0x35, 0x04, 0x7C, 0xF9, + 0xD8, 0x0B, 0xC9, 0x47, 0xD4, 0xFC, 0xF0, 0xFF, 0xDD, 0x00, 0x19, + 0xFF, 0xA4, 0x00, 0xAF, 0xFF, 0x13, 0x00, 0xFE, 0xFF, 0x20, 0x00, + 0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1, 0xF1, 0x86, + 0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC, 0xD1, 0x01, + 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7C, + 0xFF, 0x27, 0x01, 0x05, 0xFE, 0xEB, 0x02, 0x14, 0xFC, 0x50, 0x05, + 0xEA, 0x48, 0x1B, 0x02, 0x78, 0xFD, 0x32, 0x02, 0x64, 0xFE, 0xFA, + 0x00, 0x8D, 0xFF, 0x1C, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x54, 0xFF, + 0xB7, 0x01, 0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1, 0x9F, 0x2E, 0xE3, + 0x31, 0x7E, 0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A, 0x01, 0x64, 0xFF, + 0x28, 0x00, 0xFD, 0xFF, 0x18, 0x00, 0x9D, 0xFF, 0xD3, 0x00, 0xB8, + 0xFE, 0x93, 0x01, 0xA1, 0xFE, 0x8E, 0xFF, 0x92, 0x48, 0x3D, 0x08, + 0xE1, 0xFA, 0x86, 0x03, 0xB6, 0xFD, 0x4C, 0x01, 0x6D, 0xFF, 0x25, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDF, 0x01, + 0x33, 0xFC, 0x20, 0x07, 0x35, 0xF2, 0x36, 0x27, 0x78, 0x38, 0x14, + 0xF2, 0x3B, 0x06, 0x11, 0xFD, 0x41, 0x01, 0x92, 0xFF, 0x17, 0x00, + 0xFF, 0xFF, 0x10, 0x00, 0xBF, 0xFF, 0x7B, 0x00, 0x6C, 0xFF, 0x44, + 0x00, 0x01, 0x01, 0xB6, 0xFA, 0xC8, 0x46, 0x13, 0x0F, 0x51, 0xF8, + 0xC4, 0x04, 0x1B, 0xFD, 0x92, 0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF, + 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x44, 0xFC, + 0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F, 0x31, 0x3E, 0xA5, 0xF3, 0x0F, + 0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF, 0xFF, 0x01, 0x00, 0x02, 0x00, + 0x09, 0x00, 0xDF, 0xFF, 0x28, 0x00, 0x17, 0x00, 0x10, 0xFF, 0x15, + 0x03, 0xDD, 0xF6, 0x9E, 0x43, 0x6C, 0x16, 0xF1, 0xF5, 0xD3, 0x05, + 0x9F, 0xFC, 0xC6, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCE, 0x01, 0x8C, 0xFC, 0x00, 0x06, + 0x86, 0xF5, 0xE0, 0x17, 0xDB, 0x42, 0x3F, 0xF6, 0x71, 0x03, 0xD9, + 0xFE, 0x36, 0x00, 0x18, 0x00, 0xE5, 0xFF, 0x07, 0x00, 0x03, 0x00, + 0xFC, 0xFF, 0xDC, 0xFF, 0xAF, 0x00, 0x07, 0xFE, 0xC8, 0x04, 0x10, + 0xF4, 0x2D, 0x3F, 0x0F, 0x1E, 0xED, 0xF3, 0xA0, 0x06, 0x4E, 0xFC, + 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E, + 0x00, 0x4E, 0xFF, 0x9E, 0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7, 0xF7, + 0x75, 0x10, 0x48, 0x46, 0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00, 0x8E, + 0xFF, 0x6B, 0x00, 0xC6, 0xFF, 0x0E, 0x00, 0xFF, 0xFF, 0x13, 0x00, + 0x9D, 0xFF, 0x2D, 0x01, 0x33, 0xFD, 0x0B, 0x06, 0x4D, 0xF2, 0xA5, + 0x39, 0xBF, 0x25, 0x6D, 0xF2, 0x15, 0x07, 0x31, 0xFC, 0xE2, 0x01, + 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x68, + 0xFF, 0x5B, 0x01, 0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09, + 0x57, 0x48, 0x8D, 0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2, + 0x00, 0xA4, 0xFF, 0x16, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6C, 0xFF, + 0x8B, 0x01, 0x9D, 0xFC, 0xD5, 0x06, 0x89, 0xF1, 0x35, 0x33, 0x3A, + 0x2D, 0x9A, 0xF1, 0x23, 0x07, 0x51, 0xFC, 0xC2, 0x01, 0x4F, 0xFF, + 0x2F, 0x00, 0xFD, 0xFF, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42, + 0xFE, 0x74, 0x02, 0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04, + 0x94, 0xFC, 0xA9, 0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC9, 0x01, + 0x48, 0xFC, 0x28, 0x07, 0xAD, 0xF1, 0x19, 0x2C, 0x3F, 0x34, 0x97, + 0xF1, 0xBE, 0x06, 0xB0, 0xFC, 0x7F, 0x01, 0x72, 0xFF, 0x23, 0x00, + 0xFE, 0xFF, 0x15, 0x00, 0xA9, 0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D, + 0x01, 0x7A, 0xFF, 0xC5, 0xFD, 0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9, + 0xF8, 0x03, 0x7D, 0xFD, 0x66, 0x01, 0x63, 0xFF, 0x28, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE4, 0x01, 0x32, 0xFC, + 0x09, 0x07, 0x9D, 0xF2, 0x92, 0x24, 0x8F, 0x3A, 0x82, 0xF2, 0xE1, + 0x05, 0x50, 0xFD, 0x1B, 0x01, 0xA6, 0xFF, 0x10, 0x00, 0x00, 0x00, + 0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9, 0xFF, 0xD6, 0xFF, 0xC3, + 0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11, 0x77, 0xF7, 0x28, 0x05, + 0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE0, 0x01, 0x57, 0xFC, 0x85, 0x06, + 0x34, 0xF4, 0xE0, 0x1C, 0xF0, 0x3F, 0x6D, 0xF4, 0x8C, 0x04, 0x2C, + 0xFE, 0x99, 0x00, 0xE7, 0xFF, 0xF8, 0xFF, 0x04, 0x00, 0x06, 0x00, + 0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8, 0x03, 0xC7, + 0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06, 0x7D, 0xFC, + 0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, + 0x00, 0x41, 0xFF, 0xC0, 0x01, 0xAF, 0xFC, 0xAD, 0x05, 0x4A, 0xF6, + 0x44, 0x15, 0x2F, 0x44, 0x64, 0xF7, 0xC9, 0x02, 0x3D, 0xFF, 0xFE, + 0xFF, 0x34, 0x00, 0xDB, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x05, 0x00, + 0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56, 0xF3, 0x61, + 0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC, 0xE6, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x56, + 0xFF, 0x88, 0x01, 0x31, 0xFD, 0x95, 0x04, 0xB4, 0xF8, 0xFC, 0x0D, + 0x26, 0x47, 0x64, 0xFB, 0xA7, 0x00, 0x77, 0x00, 0x51, 0xFF, 0x89, + 0x00, 0xBA, 0xFF, 0x11, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8A, 0xFF, + 0x51, 0x01, 0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1, 0x82, 0x37, 0x60, + 0x28, 0x0E, 0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40, 0xFF, + 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x72, 0xFF, 0x40, + 0x01, 0xD0, 0xFD, 0x53, 0x03, 0x47, 0xFB, 0x3F, 0x07, 0xB8, 0x48, + 0x62, 0x00, 0x3F, 0xFE, 0xC8, 0x01, 0x9C, 0xFE, 0xE0, 0x00, 0x98, + 0xFF, 0x19, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA5, 0x01, + 0x78, 0xFC, 0xFF, 0x06, 0x7D, 0xF1, 0xCF, 0x30, 0xB8, 0x2F, 0x80, + 0xF1, 0x0D, 0x07, 0x6A, 0xFC, 0xAE, 0x01, 0x59, 0xFF, 0x2B, 0x00, + 0xFD, 0xFF, 0x1B, 0x00, 0x93, 0xFF, 0xED, 0x00, 0x80, 0xFE, 0xFD, + 0x01, 0xDC, 0xFD, 0x3C, 0x01, 0xD5, 0x48, 0x45, 0x06, 0xAE, 0xFB, + 0x1F, 0x03, 0xEA, 0xFD, 0x34, 0x01, 0x77, 0xFF, 0x22, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x43, 0xFF, 0xD6, 0x01, 0x39, 0xFC, + 0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29, 0x85, 0x36, 0xCC, 0xF1, 0x7F, + 0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, + 0x12, 0x00, 0xB5, 0xFF, 0x96, 0x00, 0x35, 0xFF, 0xA9, 0x00, 0x4D, + 0x00, 0x19, 0xFC, 0x7C, 0x47, 0xE8, 0x0C, 0x18, 0xF9, 0x66, 0x04, + 0x48, 0xFD, 0x7E, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x38, 0xFC, 0xE6, 0x06, + 0x19, 0xF3, 0xEA, 0x21, 0x8A, 0x3C, 0x0E, 0xF3, 0x78, 0x05, 0x96, + 0xFD, 0xF1, 0x00, 0xBB, 0xFF, 0x08, 0x00, 0x01, 0x00, 0x0B, 0x00, + 0xD6, 0xFF, 0x41, 0x00, 0xE4, 0xFF, 0x6B, 0xFF, 0x7B, 0x02, 0xF0, + 0xF7, 0xBA, 0x44, 0x1E, 0x14, 0xA5, 0xF6, 0x86, 0x05, 0xC1, 0xFC, + 0xB9, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, + 0x00, 0x39, 0xFF, 0xD8, 0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1, 0xF4, + 0x38, 0x1A, 0x8C, 0x41, 0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE, 0x66, + 0x00, 0x01, 0x00, 0xEE, 0xFF, 0x06, 0x00, 0x05, 0x00, 0xF4, 0xFF, + 0xF2, 0xFF, 0x83, 0x00, 0x53, 0xFE, 0x4E, 0x04, 0xD0, 0xF4, 0xAB, + 0x40, 0xB2, 0x1B, 0x7F, 0xF4, 0x69, 0x06, 0x62, 0xFC, 0xDD, 0x01, + 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x47, + 0xFF, 0xAF, 0x01, 0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12, + 0x5C, 0x45, 0xA9, 0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51, + 0x00, 0xD0, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xAF, 0xFF, + 0x09, 0x01, 0x6E, 0xFD, 0xB4, 0x05, 0xBC, 0xF2, 0x73, 0x3B, 0x64, + 0x23, 0xD2, 0xF2, 0xFB, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71, + 0x01, 0x65, 0xFD, 0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47, + 0x03, 0xFD, 0xD9, 0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE, + 0xFF, 0x14, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x79, 0xFF, 0x72, 0x01, + 0xC4, 0xFC, 0xA4, 0x06, 0xAB, 0xF1, 0x46, 0x35, 0xF7, 0x2A, 0xC6, + 0xF1, 0x2A, 0x07, 0x40, 0xFC, 0xCF, 0x01, 0x47, 0xFF, 0x31, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C, + 0xFE, 0xDE, 0x02, 0x2E, 0xFC, 0x13, 0x05, 0xEC, 0x48, 0x54, 0x02, + 0x5E, 0xFD, 0x3F, 0x02, 0x5D, 0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C, + 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0xBA, 0x01, 0x5B, 0xFC, + 0x1B, 0x07, 0x8B, 0xF1, 0x58, 0x2E, 0x26, 0x32, 0x80, 0xF1, 0xEA, + 0x06, 0x8C, 0xFC, 0x97, 0x01, 0x66, 0xFF, 0x27, 0x00, 0xFD, 0xFF, + 0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF, 0xFE, 0x86, 0x01, 0xBA, + 0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08, 0xC7, 0xFA, 0x93, 0x03, + 0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x35, 0x00, 0x3D, 0xFF, 0xDF, 0x01, 0x32, 0xFC, 0x1E, 0x07, + 0x40, 0xF2, 0xEB, 0x26, 0xB5, 0x38, 0x1F, 0xF2, 0x32, 0x06, 0x18, + 0xFD, 0x3D, 0x01, 0x94, 0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x0F, 0x00, + 0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17, 0x01, 0x8B, + 0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04, 0x15, 0xFD, + 0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x46, 0xFC, 0xB8, 0x06, 0xA8, 0xF3, + 0x3F, 0x1F, 0x64, 0x3E, 0xBA, 0xF3, 0x01, 0x05, 0xE2, 0xFD, 0xC4, + 0x00, 0xD2, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x08, 0x00, 0xE1, 0xFF, + 0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD, 0xF6, 0x77, + 0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC, 0xC8, 0x01, + 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3D, + 0xFF, 0xCC, 0x01, 0x8F, 0xFC, 0xF8, 0x05, 0x9B, 0xF5, 0x96, 0x17, + 0x02, 0x43, 0x5E, 0xF6, 0x5F, 0x03, 0xE4, 0xFE, 0x30, 0x00, 0x1B, + 0x00, 0xE4, 0xFF, 0x08, 0x00, 0x03, 0x00, 0xFD, 0xFF, 0xD9, 0xFF, + 0xB4, 0x00, 0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3, 0xFC, 0x3E, 0x5B, + 0x1E, 0xDB, 0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9C, + 0x01, 0x05, 0xFD, 0xF1, 0x04, 0xF0, 0xF7, 0x2D, 0x10, 0x61, 0x46, + 0x0D, 0xFA, 0x58, 0x01, 0x13, 0x00, 0x87, 0xFF, 0x6E, 0x00, 0xC4, + 0xFF, 0x0E, 0x00, 0xFF, 0xFF, 0x14, 0x00, 0x9B, 0xFF, 0x31, 0x01, + 0x2C, 0xFD, 0x15, 0x06, 0x41, 0xF2, 0x6A, 0x39, 0x0A, 0x26, 0x61, + 0xF2, 0x17, 0x07, 0x31, 0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x69, 0xFF, 0x58, 0x01, 0x9D, + 0xFD, 0xB9, 0x03, 0x7B, 0xFA, 0x40, 0x09, 0x63, 0x48, 0xBF, 0xFE, + 0x03, 0xFF, 0x5F, 0x01, 0xD4, 0xFE, 0xC5, 0x00, 0xA2, 0xFF, 0x16, + 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6A, 0xFF, 0x8E, 0x01, 0x99, 0xFC, + 0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32, 0x82, 0x2D, 0x96, 0xF1, 0x21, + 0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, + 0x1D, 0x00, 0x88, 0xFF, 0x07, 0x01, 0x49, 0xFE, 0x67, 0x02, 0x13, + 0xFD, 0xFF, 0x02, 0xF4, 0x48, 0x5F, 0x04, 0x7A, 0xFC, 0xB6, 0x02, + 0x20, 0xFE, 0x1B, 0x01, 0x81, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xCA, 0x01, 0x46, 0xFC, 0x29, 0x07, + 0xB3, 0xF1, 0xD1, 0x2B, 0x81, 0x34, 0x9C, 0xF1, 0xB8, 0x06, 0xB5, + 0xFC, 0x7C, 0x01, 0x74, 0xFF, 0x22, 0x00, 0xFE, 0xFF, 0x15, 0x00, + 0xAA, 0xFF, 0xB1, 0x00, 0xFE, 0xFE, 0x10, 0x01, 0x92, 0xFF, 0x94, + 0xFD, 0x0D, 0x48, 0xCB, 0x0A, 0xE2, 0xF9, 0x04, 0x04, 0x77, 0xFD, + 0x69, 0x01, 0x62, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA, 0xF2, + 0x46, 0x24, 0xC8, 0x3A, 0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD, 0x17, + 0x01, 0xA8, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xCC, 0xFF, + 0x5A, 0x00, 0xAF, 0xFF, 0xCA, 0xFF, 0xD8, 0x01, 0x1C, 0xF9, 0xB8, + 0x45, 0xDA, 0x11, 0x60, 0xF7, 0x33, 0x05, 0xE7, 0xFC, 0xA9, 0x01, + 0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37, + 0xFF, 0xDF, 0x01, 0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C, + 0x1F, 0x40, 0x85, 0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA, + 0xFF, 0xF7, 0xFF, 0x04, 0x00, 0x06, 0x00, 0xEB, 0xFF, 0x09, 0x00, + 0x54, 0x00, 0xA4, 0xFE, 0xC9, 0x03, 0xAA, 0xF5, 0x0C, 0x42, 0x56, + 0x19, 0x1E, 0xF5, 0x2B, 0x06, 0x7A, 0xFC, 0xD4, 0x01, 0x3A, 0xFF, + 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE, + 0x01, 0xB4, 0xFC, 0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44, + 0x86, 0xF7, 0xB6, 0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9, + 0xFF, 0x0A, 0x00, 0x01, 0x00, 0x06, 0x00, 0xC2, 0xFF, 0xE3, 0x00, + 0xAE, 0xFD, 0x52, 0x05, 0x44, 0xF3, 0x2A, 0x3D, 0x06, 0x21, 0x47, + 0xF3, 0xD8, 0x06, 0x3C, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x86, 0x01, 0x36, + 0xFD, 0x89, 0x04, 0xCD, 0xF8, 0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB, + 0x91, 0x00, 0x83, 0x00, 0x4A, 0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11, + 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x88, 0xFF, 0x55, 0x01, 0xF2, 0xFC, + 0x67, 0x06, 0xE4, 0xF1, 0x44, 0x37, 0xAA, 0x28, 0x05, 0xF2, 0x27, + 0x07, 0x36, 0xFC, 0xDA, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D, 0x01, 0xD6, 0xFD, 0x46, + 0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48, 0x98, 0x00, 0x26, 0xFE, + 0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0xFD, + 0xFF, 0x2A, 0x00, 0x5D, 0xFF, 0xA7, 0x01, 0x75, 0xFC, 0x03, 0x07, + 0x7D, 0xF1, 0x8A, 0x30, 0xFF, 0x2F, 0x7E, 0xF1, 0x0A, 0x07, 0x6E, + 0xFC, 0xAC, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x1A, 0x00, + 0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5, 0xFD, 0x05, + 0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03, 0xE4, 0xFD, + 0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x33, + 0x00, 0x42, 0xFF, 0xD7, 0x01, 0x38, 0xFC, 0x29, 0x07, 0xF3, 0xF1, + 0x3E, 0x29, 0xC6, 0x36, 0xD4, 0xF1, 0x77, 0x06, 0xE6, 0xFC, 0x5C, + 0x01, 0x84, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x12, 0x00, 0xB6, 0xFF, + 0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB, 0xFB, 0x69, + 0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD, 0x81, 0x01, + 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, + 0xFF, 0xE6, 0x01, 0x3A, 0xFC, 0xE2, 0x06, 0x28, 0xF3, 0x9E, 0x21, + 0xC0, 0x3C, 0x1F, 0xF3, 0x6C, 0x05, 0x9E, 0xFD, 0xED, 0x00, 0xBD, + 0xFF, 0x07, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xD7, 0xFF, 0x3E, 0x00, + 0xEA, 0xFF, 0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7, 0x99, 0x44, 0x68, + 0x14, 0x8E, 0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA, 0x01, 0x43, 0xFF, + 0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7, + 0x01, 0x73, 0xFC, 0x3B, 0x06, 0xF5, 0xF4, 0xED, 0x19, 0xB7, 0x41, + 0x71, 0xF5, 0xEB, 0x03, 0x90, 0xFE, 0x60, 0x00, 0x04, 0x00, 0xED, + 0xFF, 0x06, 0x00, 0x04, 0x00, 0xF5, 0xFF, 0xEF, 0xFF, 0x88, 0x00, + 0x49, 0xFE, 0x5D, 0x04, 0xB7, 0xF4, 0x7D, 0x40, 0xFD, 0x1B, 0x6C, + 0xF4, 0x70, 0x06, 0x5F, 0xFC, 0xDE, 0x01, 0x37, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAD, 0x01, 0xDD, + 0xFC, 0x48, 0x05, 0x30, 0xF7, 0x6B, 0x12, 0x7D, 0x45, 0xCF, 0xF8, + 0x01, 0x02, 0xB2, 0xFF, 0xBD, 0xFF, 0x54, 0x00, 0xCE, 0xFF, 0x0C, + 0x00, 0x00, 0x00, 0x0E, 0x00, 0xAC, 0xFF, 0x0E, 0x01, 0x66, 0xFD, + 0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B, 0xB0, 0x23, 0xC4, 0xF2, 0xFF, + 0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x29, 0x00, 0x60, 0xFF, 0x6E, 0x01, 0x6B, 0xFD, 0x1D, + 0x04, 0xAF, 0xF9, 0x51, 0x0B, 0xEC, 0x47, 0x33, 0xFD, 0xC1, 0xFF, + 0xF7, 0x00, 0x0C, 0xFF, 0xAA, 0x00, 0xAD, 0xFF, 0x14, 0x00, 0xFE, + 0xFF, 0x21, 0x00, 0x77, 0xFF, 0x75, 0x01, 0xBF, 0xFC, 0xAB, 0x06, + 0xA6, 0xF1, 0x05, 0x35, 0x40, 0x2B, 0xBF, 0xF1, 0x2A, 0x07, 0x42, + 0xFC, 0xCE, 0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x20, 0x00, 0x7E, 0xFF, 0x21, 0x01, 0x12, 0xFE, 0xD1, 0x02, 0x47, + 0xFC, 0xD7, 0x04, 0xF0, 0x48, 0x8D, 0x02, 0x45, 0xFD, 0x4D, 0x02, + 0x56, 0xFE, 0x01, 0x01, 0x8B, 0xFF, 0x1D, 0x00, 0xFD, 0xFF, 0x2E, + 0x00, 0x52, 0xFF, 0xBC, 0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E, 0xF1, + 0x11, 0x2E, 0x6B, 0x32, 0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC, 0x94, + 0x01, 0x67, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x17, 0x00, 0xA0, 0xFF, + 0xCC, 0x00, 0xC6, 0xFE, 0x79, 0x01, 0xD2, 0xFE, 0x26, 0xFF, 0x7C, + 0x48, 0xBE, 0x08, 0xAE, 0xFA, 0xA0, 0x03, 0xA9, 0xFD, 0x52, 0x01, + 0x6B, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, + 0xFF, 0xE0, 0x01, 0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26, + 0xF2, 0x38, 0x2A, 0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96, + 0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x0F, 0x00, 0xC2, 0xFF, 0x75, 0x00, + 0x7A, 0xFF, 0x2B, 0x00, 0x2D, 0x01, 0x61, 0xFA, 0x97, 0x46, 0xA0, + 0x0F, 0x20, 0xF8, 0xDA, 0x04, 0x10, 0xFD, 0x97, 0x01, 0x50, 0xFF, + 0x2E, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, + 0x01, 0x48, 0xFC, 0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E, + 0xCF, 0xF3, 0xF3, 0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF, + 0xFF, 0x03, 0x00, 0x08, 0x00, 0xE2, 0xFF, 0x21, 0x00, 0x23, 0x00, + 0xFA, 0xFE, 0x3A, 0x03, 0x9D, 0xF6, 0x50, 0x43, 0x00, 0x17, 0xC6, + 0xF5, 0xE6, 0x05, 0x97, 0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x34, 0x00, + 0xFE, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93, + 0xFC, 0xEF, 0x05, 0xB0, 0xF5, 0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6, + 0x4D, 0x03, 0xEF, 0xFE, 0x2A, 0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08, + 0x00, 0x03, 0x00, 0xFE, 0xFF, 0xD7, 0xFF, 0xBA, 0x00, 0xF4, 0xFD, + 0xE5, 0x04, 0xE4, 0xF3, 0xCA, 0x3E, 0xA7, 0x1E, 0xCA, 0xF3, 0xAC, + 0x06, 0x4A, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99, 0x01, 0x0B, 0xFD, 0xE6, + 0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46, 0x37, 0xFA, 0x42, 0x01, + 0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3, 0xFF, 0x0F, 0x00, 0xFF, + 0xFF, 0x15, 0x00, 0x98, 0xFF, 0x35, 0x01, 0x25, 0xFD, 0x1E, 0x06, + 0x35, 0xF2, 0x2E, 0x39, 0x55, 0x26, 0x56, 0xF2, 0x1A, 0x07, 0x31, + 0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD, 0x03, 0x94, + 0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE, 0x6C, 0x01, + 0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0xFD, 0xFF, 0x26, + 0x00, 0x69, 0xFF, 0x91, 0x01, 0x94, 0xFC, 0xE0, 0x06, 0x84, 0xF1, + 0xAF, 0x32, 0xCA, 0x2D, 0x92, 0xF1, 0x1F, 0x07, 0x56, 0xFC, 0xBE, + 0x01, 0x51, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x1D, 0x00, 0x8A, 0xFF, + 0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6, 0x02, 0xF2, + 0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE, 0x1E, 0x01, + 0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x49, + 0xFF, 0xCC, 0x01, 0x44, 0xFC, 0x29, 0x07, 0xB9, 0xF1, 0x89, 0x2B, + 0xC3, 0x34, 0xA0, 0xF1, 0xB1, 0x06, 0xBA, 0xFC, 0x79, 0x01, 0x76, + 0xFF, 0x21, 0x00, 0xFE, 0xFF, 0x14, 0x00, 0xAC, 0xFF, 0xAE, 0x00, + 0x05, 0xFF, 0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD, 0xFD, 0x47, 0x0E, + 0x0B, 0xC8, 0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C, 0x01, 0x61, 0xFF, + 0x28, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, + 0x01, 0x33, 0xFC, 0x03, 0x07, 0xB7, 0xF2, 0xFC, 0x23, 0x03, 0x3B, + 0x9E, 0xF2, 0xCB, 0x05, 0x5F, 0xFD, 0x12, 0x01, 0xAA, 0xFF, 0x0E, + 0x00, 0x00, 0x00, 0x0C, 0x00, 0xCD, 0xFF, 0x57, 0x00, 0xB6, 0xFF, + 0xBE, 0xFF, 0xED, 0x01, 0xF5, 0xF8, 0x9B, 0x45, 0x22, 0x12, 0x48, + 0xF7, 0x3D, 0x05, 0xE2, 0xFC, 0xAB, 0x01, 0x49, 0xFF, 0x30, 0x00, + 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5C, + 0xFC, 0x78, 0x06, 0x5A, 0xF4, 0x49, 0x1C, 0x4E, 0x40, 0x9E, 0xF4, + 0x6D, 0x04, 0x3F, 0xFE, 0x8E, 0x00, 0xED, 0xFF, 0xF6, 0xFF, 0x04, + 0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0x5A, 0x00, 0x9A, 0xFE, + 0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41, 0xA1, 0x19, 0x09, 0xF5, 0x33, + 0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, + 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBC, 0x01, 0xB8, 0xFC, 0x9A, + 0x05, 0x77, 0xF6, 0xB1, 0x14, 0x77, 0x44, 0xA9, 0xF7, 0xA2, 0x02, + 0x54, 0xFF, 0xF1, 0xFF, 0x3A, 0x00, 0xD8, 0xFF, 0x0A, 0x00, 0x01, + 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xE8, 0x00, 0xA6, 0xFD, 0x5F, 0x05, + 0x31, 0xF3, 0xF6, 0x3C, 0x52, 0x21, 0x37, 0xF3, 0xDD, 0x06, 0x3B, + 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x2B, 0x00, 0x58, 0xFF, 0x83, 0x01, 0x3C, 0xFD, 0x7E, 0x04, 0xE6, + 0xF8, 0x72, 0x0D, 0x52, 0x47, 0xBE, 0xFB, 0x7A, 0x00, 0x90, 0x00, + 0x43, 0xFF, 0x8F, 0x00, 0xB7, 0xFF, 0x11, 0x00, 0xFE, 0xFF, 0x1C, + 0x00, 0x86, 0xFF, 0x59, 0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC, 0xF1, + 0x04, 0x37, 0xF3, 0x28, 0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC, 0xD8, + 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x23, 0x00, + 0x74, 0xFF, 0x3A, 0x01, 0xDD, 0xFD, 0x39, 0x03, 0x7B, 0xFB, 0xC1, + 0x06, 0xC7, 0x48, 0xCF, 0x00, 0x0D, 0xFE, 0xE3, 0x01, 0x8E, 0xFE, + 0xE7, 0x00, 0x95, 0xFF, 0x1A, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5C, + 0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30, + 0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C, + 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE7, 0x00, + 0x8E, 0xFE, 0xE3, 0x01, 0x0D, 0xFE, 0xCF, 0x00, 0xC7, 0x48, 0xC1, + 0x06, 0x7B, 0xFB, 0x39, 0x03, 0xDD, 0xFD, 0x3A, 0x01, 0x74, 0xFF, + 0x23, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8, + 0x01, 0x37, 0xFC, 0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37, + 0xDC, 0xF1, 0x6F, 0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C, + 0x00, 0xFE, 0xFF, 0x11, 0x00, 0xB7, 0xFF, 0x8F, 0x00, 0x43, 0xFF, + 0x90, 0x00, 0x7A, 0x00, 0xBE, 0xFB, 0x52, 0x47, 0x72, 0x0D, 0xE6, + 0xF8, 0x7E, 0x04, 0x3C, 0xFD, 0x83, 0x01, 0x58, 0xFF, 0x2B, 0x00, + 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B, + 0xFC, 0xDD, 0x06, 0x37, 0xF3, 0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3, + 0x5F, 0x05, 0xA6, 0xFD, 0xE8, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01, + 0x00, 0x0A, 0x00, 0xD8, 0xFF, 0x3A, 0x00, 0xF1, 0xFF, 0x54, 0xFF, + 0xA2, 0x02, 0xA9, 0xF7, 0x77, 0x44, 0xB1, 0x14, 0x77, 0xF6, 0x9A, + 0x05, 0xB8, 0xFC, 0xBC, 0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF, + 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6, 0x01, 0x77, 0xFC, 0x33, + 0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41, 0x8D, 0xF5, 0xDA, 0x03, + 0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0x04, + 0x00, 0xF6, 0xFF, 0xED, 0xFF, 0x8E, 0x00, 0x3F, 0xFE, 0x6D, 0x04, + 0x9E, 0xF4, 0x4E, 0x40, 0x49, 0x1C, 0x5A, 0xF4, 0x78, 0x06, 0x5C, + 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, + 0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D, 0x05, 0x48, + 0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01, 0xBE, 0xFF, + 0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0E, + 0x00, 0xAA, 0xFF, 0x12, 0x01, 0x5F, 0xFD, 0xCB, 0x05, 0x9E, 0xF2, + 0x03, 0x3B, 0xFC, 0x23, 0xB7, 0xF2, 0x03, 0x07, 0x33, 0xFC, 0xE5, + 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x28, 0x00, + 0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8, 0xF9, 0x0E, + 0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01, 0x05, 0xFF, + 0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x76, + 0xFF, 0x79, 0x01, 0xBA, 0xFC, 0xB1, 0x06, 0xA0, 0xF1, 0xC3, 0x34, + 0x89, 0x2B, 0xB9, 0xF1, 0x29, 0x07, 0x44, 0xFC, 0xCC, 0x01, 0x49, + 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F, 0xFF, + 0x1E, 0x01, 0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC, 0x9B, 0x04, 0xF2, + 0x48, 0xC6, 0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50, 0xFE, 0x04, 0x01, + 0x8A, 0xFF, 0x1D, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBE, + 0x01, 0x56, 0xFC, 0x1F, 0x07, 0x92, 0xF1, 0xCA, 0x2D, 0xAF, 0x32, + 0x84, 0xF1, 0xE0, 0x06, 0x94, 0xFC, 0x91, 0x01, 0x69, 0xFF, 0x26, + 0x00, 0xFD, 0xFF, 0x17, 0x00, 0xA1, 0xFF, 0xC9, 0x00, 0xCD, 0xFE, + 0x6C, 0x01, 0xEA, 0xFE, 0xF3, 0xFE, 0x70, 0x48, 0xFF, 0x08, 0x94, + 0xFA, 0xAD, 0x03, 0xA3, 0xFD, 0x55, 0x01, 0x6A, 0xFF, 0x26, 0x00, + 0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x31, + 0xFC, 0x1A, 0x07, 0x56, 0xF2, 0x55, 0x26, 0x2E, 0x39, 0x35, 0xF2, + 0x1E, 0x06, 0x25, 0xFD, 0x35, 0x01, 0x98, 0xFF, 0x15, 0x00, 0xFF, + 0xFF, 0x0F, 0x00, 0xC3, 0xFF, 0x71, 0x00, 0x81, 0xFF, 0x1F, 0x00, + 0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46, 0xE7, 0x0F, 0x08, 0xF8, 0xE6, + 0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, + 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x4A, 0xFC, 0xAC, + 0x06, 0xCA, 0xF3, 0xA7, 0x1E, 0xCA, 0x3E, 0xE4, 0xF3, 0xE5, 0x04, + 0xF4, 0xFD, 0xBA, 0x00, 0xD7, 0xFF, 0xFE, 0xFF, 0x03, 0x00, 0x08, + 0x00, 0xE3, 0xFF, 0x1E, 0x00, 0x2A, 0x00, 0xEF, 0xFE, 0x4D, 0x03, + 0x7D, 0xF6, 0x2A, 0x43, 0x4B, 0x17, 0xB0, 0xF5, 0xEF, 0x05, 0x93, + 0xFC, 0xCB, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFE, 0xFF, + 0x34, 0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x97, 0xFC, 0xE6, 0x05, 0xC6, + 0xF5, 0x00, 0x17, 0x50, 0x43, 0x9D, 0xF6, 0x3A, 0x03, 0xFA, 0xFE, + 0x23, 0x00, 0x21, 0x00, 0xE2, 0xFF, 0x08, 0x00, 0x03, 0x00, 0xFF, + 0xFF, 0xD4, 0xFF, 0xBF, 0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF, 0xF3, + 0x98, 0x3E, 0xF3, 0x1E, 0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC, 0xE4, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E, 0x00, + 0x50, 0xFF, 0x97, 0x01, 0x10, 0xFD, 0xDA, 0x04, 0x20, 0xF8, 0xA0, + 0x0F, 0x97, 0x46, 0x61, 0xFA, 0x2D, 0x01, 0x2B, 0x00, 0x7A, 0xFF, + 0x75, 0x00, 0xC2, 0xFF, 0x0F, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x96, + 0xFF, 0x39, 0x01, 0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38, + 0xA0, 0x26, 0x4B, 0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C, + 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6B, 0xFF, + 0x52, 0x01, 0xA9, 0xFD, 0xA0, 0x03, 0xAE, 0xFA, 0xBE, 0x08, 0x7C, + 0x48, 0x26, 0xFF, 0xD2, 0xFE, 0x79, 0x01, 0xC6, 0xFE, 0xCC, 0x00, + 0xA0, 0xFF, 0x17, 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94, + 0x01, 0x90, 0xFC, 0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E, + 0x8E, 0xF1, 0x1D, 0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E, + 0x00, 0xFD, 0xFF, 0x1D, 0x00, 0x8B, 0xFF, 0x01, 0x01, 0x56, 0xFE, + 0x4D, 0x02, 0x45, 0xFD, 0x8D, 0x02, 0xF0, 0x48, 0xD7, 0x04, 0x47, + 0xFC, 0xD1, 0x02, 0x12, 0xFE, 0x21, 0x01, 0x7E, 0xFF, 0x20, 0x00, + 0x00, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42, + 0xFC, 0x2A, 0x07, 0xBF, 0xF1, 0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1, + 0xAB, 0x06, 0xBF, 0xFC, 0x75, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, + 0xFF, 0x14, 0x00, 0xAD, 0xFF, 0xAA, 0x00, 0x0C, 0xFF, 0xF7, 0x00, + 0xC1, 0xFF, 0x33, 0xFD, 0xEC, 0x47, 0x51, 0x0B, 0xAF, 0xF9, 0x1D, + 0x04, 0x6B, 0xFD, 0x6E, 0x01, 0x60, 0xFF, 0x29, 0x00, 0x00, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0xFF, + 0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B, 0xAD, 0xF2, 0xBF, 0x05, + 0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x0C, + 0x00, 0xCE, 0xFF, 0x54, 0x00, 0xBD, 0xFF, 0xB2, 0xFF, 0x01, 0x02, + 0xCF, 0xF8, 0x7D, 0x45, 0x6B, 0x12, 0x30, 0xF7, 0x48, 0x05, 0xDD, + 0xFC, 0xAD, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, + 0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70, 0x06, 0x6C, + 0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04, 0x49, 0xFE, + 0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0x06, 0x00, 0xED, + 0xFF, 0x04, 0x00, 0x60, 0x00, 0x90, 0xFE, 0xEB, 0x03, 0x71, 0xF5, + 0xB7, 0x41, 0xED, 0x19, 0xF5, 0xF4, 0x3B, 0x06, 0x73, 0xFC, 0xD7, + 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00, + 0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E, 0xF6, 0x68, + 0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF, 0xEA, 0xFF, + 0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0x07, 0x00, 0xBD, + 0xFF, 0xED, 0x00, 0x9E, 0xFD, 0x6C, 0x05, 0x1F, 0xF3, 0xC0, 0x3C, + 0x9E, 0x21, 0x28, 0xF3, 0xE2, 0x06, 0x3A, 0xFC, 0xE6, 0x01, 0x37, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF, + 0x81, 0x01, 0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8, 0x2D, 0x0D, 0x69, + 0x47, 0xEB, 0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C, 0xFF, 0x93, 0x00, + 0xB6, 0xFF, 0x12, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x84, 0xFF, 0x5C, + 0x01, 0xE6, 0xFC, 0x77, 0x06, 0xD4, 0xF1, 0xC6, 0x36, 0x3E, 0x29, + 0xF3, 0xF1, 0x29, 0x07, 0x38, 0xFC, 0xD7, 0x01, 0x42, 0xFF, 0x33, + 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x76, 0xFF, 0x37, 0x01, + 0xE4, 0xFD, 0x2C, 0x03, 0x94, 0xFB, 0x83, 0x06, 0xCE, 0x48, 0x05, + 0x01, 0xF5, 0xFD, 0xF0, 0x01, 0x87, 0xFE, 0xEA, 0x00, 0x94, 0xFF, + 0x1A, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5A, 0xFF, 0xAC, 0x01, 0x6E, + 0xFC, 0x0A, 0x07, 0x7E, 0xF1, 0xFF, 0x2F, 0x8A, 0x30, 0x7D, 0xF1, + 0x03, 0x07, 0x75, 0xFC, 0xA7, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0xFD, + 0xFF, 0x1A, 0x00, 0x96, 0xFF, 0xE3, 0x00, 0x95, 0xFE, 0xD5, 0x01, + 0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48, 0x00, 0x07, 0x61, 0xFB, 0x46, + 0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73, 0xFF, 0x23, 0x00, 0x00, 0x00, + 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xDA, 0x01, 0x36, 0xFC, 0x27, + 0x07, 0x05, 0xF2, 0xAA, 0x28, 0x44, 0x37, 0xE4, 0xF1, 0x67, 0x06, + 0xF2, 0xFC, 0x55, 0x01, 0x88, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x11, + 0x00, 0xB9, 0xFF, 0x8C, 0x00, 0x4A, 0xFF, 0x83, 0x00, 0x91, 0x00, + 0x91, 0xFB, 0x3D, 0x47, 0xB7, 0x0D, 0xCD, 0xF8, 0x89, 0x04, 0x36, + 0xFD, 0x86, 0x01, 0x57, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3C, 0xFC, 0xD8, 0x06, 0x47, + 0xF3, 0x06, 0x21, 0x2A, 0x3D, 0x44, 0xF3, 0x52, 0x05, 0xAE, 0xFD, + 0xE3, 0x00, 0xC2, 0xFF, 0x06, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xD9, + 0xFF, 0x37, 0x00, 0xF7, 0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86, 0xF7, + 0x53, 0x44, 0xFB, 0x14, 0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC, 0xBE, + 0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, + 0x3A, 0xFF, 0xD4, 0x01, 0x7A, 0xFC, 0x2B, 0x06, 0x1E, 0xF5, 0x56, + 0x19, 0x0C, 0x42, 0xAA, 0xF5, 0xC9, 0x03, 0xA4, 0xFE, 0x54, 0x00, + 0x09, 0x00, 0xEB, 0xFF, 0x06, 0x00, 0x04, 0x00, 0xF7, 0xFF, 0xEA, + 0xFF, 0x93, 0x00, 0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40, + 0x94, 0x1C, 0x47, 0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37, + 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF, + 0xA9, 0x01, 0xE7, 0xFC, 0x33, 0x05, 0x60, 0xF7, 0xDA, 0x11, 0xB8, + 0x45, 0x1C, 0xF9, 0xD8, 0x01, 0xCA, 0xFF, 0xAF, 0xFF, 0x5A, 0x00, + 0xCC, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17, + 0x01, 0x57, 0xFD, 0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24, + 0xAA, 0xF2, 0x06, 0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x62, 0xFF, 0x69, 0x01, + 0x77, 0xFD, 0x04, 0x04, 0xE2, 0xF9, 0xCB, 0x0A, 0x0D, 0x48, 0x94, + 0xFD, 0x92, 0xFF, 0x10, 0x01, 0xFE, 0xFE, 0xB1, 0x00, 0xAA, 0xFF, + 0x15, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5, + 0xFC, 0xB8, 0x06, 0x9C, 0xF1, 0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1, + 0x29, 0x07, 0x46, 0xFC, 0xCA, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81, 0xFF, 0x1B, 0x01, 0x20, 0xFE, + 0xB6, 0x02, 0x7A, 0xFC, 0x5F, 0x04, 0xF4, 0x48, 0xFF, 0x02, 0x13, + 0xFD, 0x67, 0x02, 0x49, 0xFE, 0x07, 0x01, 0x88, 0xFF, 0x1D, 0x00, + 0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0, 0x01, 0x53, 0xFC, 0x21, + 0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32, 0x86, 0xF1, 0xDB, 0x06, + 0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x16, + 0x00, 0xA2, 0xFF, 0xC5, 0x00, 0xD4, 0xFE, 0x5F, 0x01, 0x03, 0xFF, + 0xBF, 0xFE, 0x63, 0x48, 0x40, 0x09, 0x7B, 0xFA, 0xB9, 0x03, 0x9D, + 0xFD, 0x58, 0x01, 0x69, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17, 0x07, 0x61, + 0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06, 0x2C, 0xFD, + 0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0x0E, 0x00, 0xC4, + 0xFF, 0x6E, 0x00, 0x87, 0xFF, 0x13, 0x00, 0x58, 0x01, 0x0D, 0xFA, + 0x61, 0x46, 0x2D, 0x10, 0xF0, 0xF7, 0xF1, 0x04, 0x05, 0xFD, 0x9C, + 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB, 0xF3, 0x5B, + 0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD, 0xB4, 0x00, + 0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0x08, 0x00, 0xE4, 0xFF, 0x1B, + 0x00, 0x30, 0x00, 0xE4, 0xFE, 0x5F, 0x03, 0x5E, 0xF6, 0x02, 0x43, + 0x96, 0x17, 0x9B, 0xF5, 0xF8, 0x05, 0x8F, 0xFC, 0xCC, 0x01, 0x3D, + 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF, + 0xC8, 0x01, 0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5, 0xB6, 0x16, 0x77, + 0x43, 0xBD, 0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D, 0x00, 0x25, 0x00, + 0xE1, 0xFF, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xC4, + 0x00, 0xE2, 0xFD, 0x01, 0x05, 0xBA, 0xF3, 0x64, 0x3E, 0x3F, 0x1F, + 0xA8, 0xF3, 0xB8, 0x06, 0x46, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x95, 0x01, + 0x15, 0xFD, 0xCF, 0x04, 0x39, 0xF8, 0x59, 0x0F, 0xAF, 0x46, 0x8B, + 0xFA, 0x17, 0x01, 0x38, 0x00, 0x73, 0xFF, 0x78, 0x00, 0xC0, 0xFF, + 0x0F, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x94, 0xFF, 0x3D, 0x01, 0x18, + 0xFD, 0x32, 0x06, 0x1F, 0xF2, 0xB5, 0x38, 0xEB, 0x26, 0x40, 0xF2, + 0x1E, 0x07, 0x32, 0xFC, 0xDF, 0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6C, 0xFF, 0x4F, 0x01, 0xB0, 0xFD, + 0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08, 0x86, 0x48, 0x5A, 0xFF, 0xBA, + 0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF, 0x00, 0x9E, 0xFF, 0x17, 0x00, + 0xFD, 0xFF, 0x27, 0x00, 0x66, 0xFF, 0x97, 0x01, 0x8C, 0xFC, 0xEA, + 0x06, 0x80, 0xF1, 0x26, 0x32, 0x58, 0x2E, 0x8B, 0xF1, 0x1B, 0x07, + 0x5B, 0xFC, 0xBA, 0x01, 0x53, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x1C, + 0x00, 0x8C, 0xFF, 0xFE, 0x00, 0x5D, 0xFE, 0x3F, 0x02, 0x5E, 0xFD, + 0x54, 0x02, 0xEC, 0x48, 0x13, 0x05, 0x2E, 0xFC, 0xDE, 0x02, 0x0C, + 0xFE, 0x24, 0x01, 0x7D, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x31, 0x00, 0x47, 0xFF, 0xCF, 0x01, 0x40, 0xFC, 0x2A, 0x07, 0xC6, + 0xF1, 0xF7, 0x2A, 0x46, 0x35, 0xAB, 0xF1, 0xA4, 0x06, 0xC4, 0xFC, + 0x72, 0x01, 0x79, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x14, 0x00, 0xAE, + 0xFF, 0xA7, 0x00, 0x12, 0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03, 0xFD, + 0xDC, 0x47, 0x95, 0x0B, 0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD, 0x71, + 0x01, 0x5F, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC, 0xFB, 0x06, 0xD2, 0xF2, 0x64, + 0x23, 0x73, 0x3B, 0xBC, 0xF2, 0xB4, 0x05, 0x6E, 0xFD, 0x09, 0x01, + 0xAF, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xD0, 0xFF, 0x51, + 0x00, 0xC3, 0xFF, 0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45, + 0xB2, 0x12, 0x19, 0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47, + 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, + 0xDD, 0x01, 0x62, 0xFC, 0x69, 0x06, 0x7F, 0xF4, 0xB2, 0x1B, 0xAB, + 0x40, 0xD0, 0xF4, 0x4E, 0x04, 0x53, 0xFE, 0x83, 0x00, 0xF2, 0xFF, + 0xF4, 0xFF, 0x05, 0x00, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66, + 0x00, 0x85, 0xFE, 0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A, + 0xE1, 0xF4, 0x43, 0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35, + 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xB9, 0x01, + 0xC1, 0xFC, 0x86, 0x05, 0xA5, 0xF6, 0x1E, 0x14, 0xBA, 0x44, 0xF0, + 0xF7, 0x7B, 0x02, 0x6B, 0xFF, 0xE4, 0xFF, 0x41, 0x00, 0xD6, 0xFF, + 0x0B, 0x00, 0x01, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96, + 0xFD, 0x78, 0x05, 0x0E, 0xF3, 0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3, + 0xE6, 0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x2B, 0x00, 0x5A, 0xFF, 0x7E, 0x01, 0x48, 0xFD, + 0x66, 0x04, 0x18, 0xF9, 0xE8, 0x0C, 0x7C, 0x47, 0x19, 0xFC, 0x4D, + 0x00, 0xA9, 0x00, 0x35, 0xFF, 0x96, 0x00, 0xB5, 0xFF, 0x12, 0x00, + 0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60, 0x01, 0xE0, 0xFC, 0x7F, + 0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29, 0xEB, 0xF1, 0x2A, 0x07, + 0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x22, 0x00, 0x77, 0xFF, 0x34, 0x01, 0xEA, 0xFD, 0x1F, 0x03, + 0xAE, 0xFB, 0x45, 0x06, 0xD5, 0x48, 0x3C, 0x01, 0xDC, 0xFD, 0xFD, + 0x01, 0x80, 0xFE, 0xED, 0x00, 0x93, 0xFF, 0x1B, 0x00, 0xFD, 0xFF, + 0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D, 0x07, 0x80, + 0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06, 0x78, 0xFC, + 0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x19, 0x00, 0x98, + 0xFF, 0xE0, 0x00, 0x9C, 0xFE, 0xC8, 0x01, 0x3F, 0xFE, 0x62, 0x00, + 0xB8, 0x48, 0x3F, 0x07, 0x47, 0xFB, 0x53, 0x03, 0xD0, 0xFD, 0x40, + 0x01, 0x72, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E, 0xF2, 0x60, + 0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC, 0x51, 0x01, + 0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0x11, 0x00, 0xBA, 0xFF, 0x89, + 0x00, 0x51, 0xFF, 0x77, 0x00, 0xA7, 0x00, 0x64, 0xFB, 0x26, 0x47, + 0xFC, 0x0D, 0xB4, 0xF8, 0x95, 0x04, 0x31, 0xFD, 0x88, 0x01, 0x56, + 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE6, 0x01, 0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3, 0xBA, 0x20, 0x61, + 0x3D, 0x56, 0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE, 0x00, 0xC5, 0xFF, + 0x05, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDB, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0x3D, 0xFF, 0xC9, 0x02, 0x64, 0xF7, 0x2F, 0x44, 0x44, 0x15, + 0x4A, 0xF6, 0xAD, 0x05, 0xAF, 0xFC, 0xC0, 0x01, 0x41, 0xFF, 0x32, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD3, 0x01, + 0x7D, 0xFC, 0x23, 0x06, 0x32, 0xF5, 0x0C, 0x19, 0x38, 0x42, 0xC7, + 0xF5, 0xB8, 0x03, 0xAF, 0xFE, 0x4E, 0x00, 0x0C, 0x00, 0xEA, 0xFF, + 0x06, 0x00, 0x04, 0x00, 0xF8, 0xFF, 0xE7, 0xFF, 0x99, 0x00, 0x2C, + 0xFE, 0x8C, 0x04, 0x6D, 0xF4, 0xF0, 0x3F, 0xE0, 0x1C, 0x34, 0xF4, + 0x85, 0x06, 0x57, 0xFC, 0xE0, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4A, 0xFF, 0xA7, 0x01, 0xEC, 0xFC, + 0x28, 0x05, 0x77, 0xF7, 0x92, 0x11, 0xD7, 0x45, 0x43, 0xF9, 0xC3, + 0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E, 0x00, 0xCB, 0xFF, 0x0D, 0x00, + 0x00, 0x00, 0x10, 0x00, 0xA6, 0xFF, 0x1B, 0x01, 0x50, 0xFD, 0xE1, + 0x05, 0x82, 0xF2, 0x8F, 0x3A, 0x92, 0x24, 0x9D, 0xF2, 0x09, 0x07, + 0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x28, 0x00, 0x63, 0xFF, 0x66, 0x01, 0x7D, 0xFD, 0xF8, 0x03, + 0xFB, 0xF9, 0x89, 0x0A, 0x1D, 0x48, 0xC5, 0xFD, 0x7A, 0xFF, 0x1D, + 0x01, 0xF7, 0xFE, 0xB4, 0x00, 0xA9, 0xFF, 0x15, 0x00, 0xFE, 0xFF, + 0x23, 0x00, 0x72, 0xFF, 0x7F, 0x01, 0xB0, 0xFC, 0xBE, 0x06, 0x97, + 0xF1, 0x3F, 0x34, 0x19, 0x2C, 0xAD, 0xF1, 0x28, 0x07, 0x48, 0xFC, + 0xC9, 0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x1F, + 0x00, 0x82, 0xFF, 0x18, 0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94, 0xFC, + 0x24, 0x04, 0xF5, 0x48, 0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02, 0x42, + 0xFE, 0x0B, 0x01, 0x87, 0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x2F, 0x00, + 0x4F, 0xFF, 0xC2, 0x01, 0x51, 0xFC, 0x23, 0x07, 0x9A, 0xF1, 0x3A, + 0x2D, 0x35, 0x33, 0x89, 0xF1, 0xD5, 0x06, 0x9D, 0xFC, 0x8B, 0x01, + 0x6C, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC2, + 0x00, 0xDB, 0xFE, 0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48, + 0x81, 0x09, 0x61, 0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68, + 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, + 0xE2, 0x01, 0x31, 0xFC, 0x15, 0x07, 0x6D, 0xF2, 0xBF, 0x25, 0xA5, + 0x39, 0x4D, 0xF2, 0x0B, 0x06, 0x33, 0xFD, 0x2D, 0x01, 0x9D, 0xFF, + 0x13, 0x00, 0xFF, 0xFF, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E, + 0xFF, 0x06, 0x00, 0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10, + 0xD7, 0xF7, 0xFC, 0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, + 0x4E, 0xFC, 0xA0, 0x06, 0xED, 0xF3, 0x0F, 0x1E, 0x2D, 0x3F, 0x10, + 0xF4, 0xC8, 0x04, 0x07, 0xFE, 0xAF, 0x00, 0xDC, 0xFF, 0xFC, 0xFF, + 0x03, 0x00, 0x07, 0x00, 0xE5, 0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9, + 0xFE, 0x71, 0x03, 0x3F, 0xF6, 0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5, + 0x00, 0x06, 0x8C, 0xFC, 0xCE, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC6, 0x01, 0x9F, 0xFC, + 0xD3, 0x05, 0xF1, 0xF5, 0x6C, 0x16, 0x9E, 0x43, 0xDD, 0xF6, 0x15, + 0x03, 0x10, 0xFF, 0x17, 0x00, 0x28, 0x00, 0xDF, 0xFF, 0x09, 0x00, + 0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9, 0x00, 0xDA, 0xFD, 0x0F, + 0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F, 0x97, 0xF3, 0xBD, 0x06, + 0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, + 0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x92, 0x01, 0x1B, 0xFD, 0xC4, 0x04, + 0x51, 0xF8, 0x13, 0x0F, 0xC8, 0x46, 0xB6, 0xFA, 0x01, 0x01, 0x44, + 0x00, 0x6C, 0xFF, 0x7B, 0x00, 0xBF, 0xFF, 0x10, 0x00, 0xFF, 0xFF, + 0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B, 0x06, 0x14, + 0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07, 0x33, 0xFC, + 0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x25, + 0x00, 0x6D, 0xFF, 0x4C, 0x01, 0xB6, 0xFD, 0x86, 0x03, 0xE1, 0xFA, + 0x3D, 0x08, 0x92, 0x48, 0x8E, 0xFF, 0xA1, 0xFE, 0x93, 0x01, 0xB8, + 0xFE, 0xD3, 0x00, 0x9D, 0xFF, 0x18, 0x00, 0xFD, 0xFF, 0x28, 0x00, + 0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E, 0xF1, 0xE3, + 0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC, 0xB7, 0x01, + 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x1C, 0x00, 0x8D, 0xFF, 0xFA, + 0x00, 0x64, 0xFE, 0x32, 0x02, 0x78, 0xFD, 0x1B, 0x02, 0xEA, 0x48, + 0x50, 0x05, 0x14, 0xFC, 0xEB, 0x02, 0x05, 0xFE, 0x27, 0x01, 0x7C, + 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF, + 0xD1, 0x01, 0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1, 0xAE, 0x2A, 0x86, + 0x35, 0xB1, 0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E, 0x01, 0x7B, 0xFF, + 0x20, 0x00, 0xFE, 0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA4, 0x00, 0x19, + 0xFF, 0xDD, 0x00, 0xF0, 0xFF, 0xD4, 0xFC, 0xC9, 0x47, 0xD8, 0x0B, + 0x7C, 0xF9, 0x35, 0x04, 0x5F, 0xFD, 0x74, 0x01, 0x5E, 0xFF, 0x29, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01, + 0x35, 0xFC, 0xF7, 0x06, 0xE0, 0xF2, 0x18, 0x23, 0xAB, 0x3B, 0xCC, + 0xF2, 0xA8, 0x05, 0x76, 0xFD, 0x04, 0x01, 0xB1, 0xFF, 0x0C, 0x00, + 0x00, 0x00, 0x0C, 0x00, 0xD1, 0xFF, 0x4E, 0x00, 0xCA, 0xFF, 0x9A, + 0xFF, 0x2A, 0x02, 0x83, 0xF8, 0x3F, 0x45, 0xFB, 0x12, 0x01, 0xF7, + 0x5D, 0x05, 0xD3, 0xFC, 0xB1, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, + 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDC, 0x01, 0x64, 0xFC, + 0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B, 0xD9, 0x40, 0xEA, 0xF4, 0x3E, + 0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5, 0xFF, 0xF3, 0xFF, 0x05, 0x00, + 0x05, 0x00, 0xEF, 0xFF, 0xFE, 0xFF, 0x6C, 0x00, 0x7B, 0xFE, 0x0C, + 0x04, 0x3A, 0xF5, 0x5F, 0x41, 0x83, 0x1A, 0xCD, 0xF4, 0x4B, 0x06, + 0x6D, 0xFC, 0xD9, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, + 0xFF, 0x31, 0x00, 0x44, 0xFF, 0xB7, 0x01, 0xC5, 0xFC, 0x7C, 0x05, + 0xBC, 0xF6, 0xD5, 0x13, 0xDC, 0x44, 0x14, 0xF8, 0x67, 0x02, 0x77, + 0xFF, 0xDD, 0xFF, 0x44, 0x00, 0xD5, 0xFF, 0x0B, 0x00, 0x01, 0x00, + 0x09, 0x00, 0xB8, 0xFF, 0xF6, 0x00, 0x8D, 0xFD, 0x84, 0x05, 0xFD, + 0xF2, 0x52, 0x3C, 0x35, 0x22, 0x0B, 0xF3, 0xEB, 0x06, 0x37, 0xFC, + 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2A, + 0x00, 0x5B, 0xFF, 0x7C, 0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31, 0xF9, + 0xA4, 0x0C, 0x90, 0x47, 0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00, 0x2E, + 0xFF, 0x99, 0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFE, 0xFF, 0x1E, 0x00, + 0x80, 0xFF, 0x64, 0x01, 0xDA, 0xFC, 0x87, 0x06, 0xC5, 0xF1, 0x46, + 0x36, 0xD1, 0x29, 0xE3, 0xF1, 0x2A, 0x07, 0x3A, 0xFC, 0xD5, 0x01, + 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x78, + 0xFF, 0x31, 0x01, 0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06, + 0xDB, 0x48, 0x73, 0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1, + 0x00, 0x91, 0xFF, 0x1B, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x58, 0xFF, + 0xB1, 0x01, 0x67, 0xFC, 0x10, 0x07, 0x81, 0xF1, 0x73, 0x2F, 0x15, + 0x31, 0x7C, 0xF1, 0xFB, 0x06, 0x7C, 0xFC, 0xA2, 0x01, 0x60, 0xFF, + 0x29, 0x00, 0xFD, 0xFF, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3, + 0xFE, 0xBB, 0x01, 0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07, + 0x2E, 0xFB, 0x60, 0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDC, 0x01, + 0x34, 0xFC, 0x25, 0x07, 0x18, 0xF2, 0x15, 0x28, 0xBF, 0x37, 0xF7, + 0xF1, 0x56, 0x06, 0xFE, 0xFC, 0x4D, 0x01, 0x8C, 0xFF, 0x19, 0x00, + 0xFF, 0xFF, 0x10, 0x00, 0xBB, 0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A, + 0x00, 0xBE, 0x00, 0x38, 0xFB, 0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8, + 0xA1, 0x04, 0x2B, 0xFD, 0x8B, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, + 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3F, 0xFC, + 0xCE, 0x06, 0x66, 0xF3, 0x6F, 0x20, 0x96, 0x3D, 0x69, 0xF3, 0x38, + 0x05, 0xBF, 0xFD, 0xD9, 0x00, 0xC7, 0xFF, 0x04, 0x00, 0x02, 0x00, + 0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04, 0x00, 0x32, 0xFF, 0xDC, + 0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15, 0x34, 0xF6, 0xB7, 0x05, + 0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xD2, 0x01, 0x81, 0xFC, 0x1A, 0x06, + 0x47, 0xF5, 0xC1, 0x18, 0x60, 0x42, 0xE4, 0xF5, 0xA6, 0x03, 0xB9, + 0xFE, 0x48, 0x00, 0x0F, 0x00, 0xE9, 0xFF, 0x07, 0x00, 0x04, 0x00, + 0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B, 0x04, 0x55, + 0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06, 0x55, 0xFC, + 0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2F, + 0x00, 0x4B, 0xFF, 0xA4, 0x01, 0xF1, 0xFC, 0x1D, 0x05, 0x8F, 0xF7, + 0x4A, 0x11, 0xF2, 0x45, 0x6B, 0xF9, 0xAE, 0x01, 0xE2, 0xFF, 0xA2, + 0xFF, 0x61, 0x00, 0xC9, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x11, 0x00, + 0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74, 0xF2, 0x54, + 0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC, 0xE4, 0x01, + 0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x64, + 0xFF, 0x63, 0x01, 0x84, 0xFD, 0xEB, 0x03, 0x14, 0xFA, 0x47, 0x0A, + 0x2C, 0x48, 0xF6, 0xFD, 0x63, 0xFF, 0x2B, 0x01, 0xF0, 0xFE, 0xB8, + 0x00, 0xA8, 0xFF, 0x15, 0x00, 0xFE, 0xFF, 0x23, 0x00, 0x71, 0xFF, + 0x82, 0x01, 0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1, 0xFD, 0x33, 0x62, + 0x2C, 0xA8, 0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7, 0x01, 0x4C, 0xFF, + 0x30, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83, 0xFF, 0x14, + 0x01, 0x2D, 0xFE, 0x9C, 0x02, 0xAD, 0xFC, 0xE9, 0x03, 0xF6, 0x48, + 0x73, 0x03, 0xE0, 0xFC, 0x82, 0x02, 0x3B, 0xFE, 0x0E, 0x01, 0x86, + 0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3, 0x01, + 0x4E, 0xFC, 0x24, 0x07, 0x9E, 0xF1, 0xF2, 0x2C, 0x78, 0x33, 0x8C, + 0xF1, 0xD0, 0x06, 0xA2, 0xFC, 0x88, 0x01, 0x6D, 0xFF, 0x24, 0x00, + 0xFD, 0xFF, 0x16, 0x00, 0xA5, 0xFF, 0xBE, 0x00, 0xE2, 0xFE, 0x45, + 0x01, 0x33, 0xFF, 0x5A, 0xFE, 0x48, 0x48, 0xC3, 0x09, 0x47, 0xFA, + 0xD2, 0x03, 0x90, 0xFD, 0x5E, 0x01, 0x66, 0xFF, 0x27, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE3, 0x01, 0x31, 0xFC, + 0x12, 0x07, 0x79, 0xF2, 0x73, 0x25, 0xDF, 0x39, 0x5A, 0xF2, 0x00, + 0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F, 0xFF, 0x13, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0xC7, 0xFF, 0x68, 0x00, 0x95, 0xFF, 0xFA, 0xFF, 0x83, + 0x01, 0xBB, 0xF9, 0x2B, 0x46, 0xBB, 0x10, 0xBF, 0xF7, 0x07, 0x05, + 0xFB, 0xFC, 0xA0, 0x01, 0x4D, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x50, 0xFC, 0x99, 0x06, + 0xFE, 0xF3, 0xC3, 0x1D, 0x5E, 0x3F, 0x27, 0xF4, 0xB9, 0x04, 0x10, + 0xFE, 0xA9, 0x00, 0xDF, 0xFF, 0xFB, 0xFF, 0x03, 0x00, 0x07, 0x00, + 0xE6, 0xFF, 0x15, 0x00, 0x3C, 0x00, 0xCF, 0xFE, 0x83, 0x03, 0x20, + 0xF6, 0xB2, 0x42, 0x2B, 0x18, 0x71, 0xF5, 0x09, 0x06, 0x88, 0xFC, + 0xCF, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33, + 0x00, 0x3F, 0xFF, 0xC5, 0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07, 0xF6, + 0x22, 0x16, 0xC3, 0x43, 0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF, 0x11, + 0x00, 0x2B, 0x00, 0xDE, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x02, 0x00, + 0xCC, 0xFF, 0xCE, 0x00, 0xD1, 0xFD, 0x1D, 0x05, 0x91, 0xF3, 0xFE, + 0x3D, 0xD7, 0x1F, 0x87, 0xF3, 0xC3, 0x06, 0x42, 0xFC, 0xE5, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2D, 0x00, 0x53, + 0xFF, 0x90, 0x01, 0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E, + 0xE1, 0x46, 0xE1, 0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F, + 0x00, 0xBE, 0xFF, 0x10, 0x00, 0xFF, 0xFF, 0x18, 0x00, 0x90, 0xFF, + 0x45, 0x01, 0x0B, 0xFD, 0x44, 0x06, 0x0A, 0xF2, 0x3B, 0x38, 0x80, + 0x27, 0x2B, 0xF2, 0x22, 0x07, 0x33, 0xFC, 0xDE, 0x01, 0x3E, 0xFF, + 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49, + 0x01, 0xBC, 0xFD, 0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48, + 0xC3, 0xFF, 0x89, 0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C, + 0xFF, 0x18, 0x00, 0xFD, 0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9D, 0x01, + 0x84, 0xFC, 0xF3, 0x06, 0x7D, 0xF1, 0x9E, 0x31, 0xE6, 0x2E, 0x85, + 0xF1, 0x16, 0x07, 0x61, 0xFC, 0xB5, 0x01, 0x55, 0xFF, 0x2D, 0x00, + 0xFD, 0xFF, 0x1C, 0x00, 0x8F, 0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25, + 0x02, 0x91, 0xFD, 0xE3, 0x01, 0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB, + 0xF8, 0x02, 0xFE, 0xFD, 0x2B, 0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45, 0xFF, 0xD2, 0x01, 0x3D, 0xFC, + 0x2B, 0x07, 0xD4, 0xF1, 0x64, 0x2A, 0xC6, 0x35, 0xB7, 0xF1, 0x96, + 0x06, 0xCF, 0xFC, 0x6B, 0x01, 0x7D, 0xFF, 0x1F, 0x00, 0xFE, 0xFF, + 0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20, 0xFF, 0xD0, 0x00, 0x07, + 0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C, 0x63, 0xF9, 0x42, 0x04, + 0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF3, 0x06, + 0xEE, 0xF2, 0xCD, 0x22, 0xE4, 0x3B, 0xDC, 0xF2, 0x9C, 0x05, 0x7E, + 0xFD, 0x00, 0x01, 0xB4, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0x0B, 0x00, + 0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F, 0x02, 0x5E, + 0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05, 0xCF, 0xFC, + 0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, + 0x00, 0x38, 0xFF, 0xDB, 0x01, 0x67, 0xFC, 0x5A, 0x06, 0xA6, 0xF4, + 0x1B, 0x1B, 0x07, 0x41, 0x04, 0xF5, 0x2D, 0x04, 0x67, 0xFE, 0x77, + 0x00, 0xF8, 0xFF, 0xF2, 0xFF, 0x05, 0x00, 0x05, 0x00, 0xF0, 0xFF, + 0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F, 0xF5, 0x32, + 0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC, 0xDA, 0x01, + 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x31, 0x00, 0x45, + 0xFF, 0xB5, 0x01, 0xCA, 0xFC, 0x72, 0x05, 0xD3, 0xF6, 0x8D, 0x13, + 0xFD, 0x44, 0x39, 0xF8, 0x53, 0x02, 0x82, 0xFF, 0xD7, 0xFF, 0x47, + 0x00, 0xD3, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xB6, 0xFF, + 0xFB, 0x00, 0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2, 0x1C, 0x3C, 0x81, + 0x22, 0xFC, 0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2A, 0x00, 0x5C, 0xFF, 0x79, + 0x01, 0x53, 0xFD, 0x4E, 0x04, 0x4A, 0xF9, 0x60, 0x0C, 0xA3, 0x47, + 0x76, 0xFC, 0x1F, 0x00, 0xC3, 0x00, 0x27, 0xFF, 0x9D, 0x00, 0xB2, + 0xFF, 0x13, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x7F, 0xFF, 0x67, 0x01, + 0xD5, 0xFC, 0x8E, 0x06, 0xBE, 0xF1, 0x06, 0x36, 0x1A, 0x2A, 0xDC, + 0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01, 0x44, 0xFF, 0x32, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x79, 0xFF, 0x2E, 0x01, 0xF7, + 0xFD, 0x05, 0x03, 0xE1, 0xFB, 0xCA, 0x05, 0xDF, 0x48, 0xAB, 0x01, + 0xAA, 0xFD, 0x18, 0x02, 0x72, 0xFE, 0xF4, 0x00, 0x90, 0xFF, 0x1B, + 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x57, 0xFF, 0xB3, 0x01, 0x64, 0xFC, + 0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F, 0x5A, 0x31, 0x7D, 0xF1, 0xF7, + 0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61, 0xFF, 0x29, 0x00, 0xFD, 0xFF, + 0x19, 0x00, 0x9A, 0xFF, 0xD9, 0x00, 0xAA, 0xFE, 0xAE, 0x01, 0x70, + 0xFE, 0xF8, 0xFF, 0xA6, 0x48, 0xBE, 0x07, 0x14, 0xFB, 0x6D, 0x03, + 0xC3, 0xFD, 0x46, 0x01, 0x70, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDD, 0x01, 0x34, 0xFC, 0x23, 0x07, + 0x21, 0xF2, 0xCB, 0x27, 0xFE, 0x37, 0x00, 0xF2, 0x4D, 0x06, 0x04, + 0xFD, 0x49, 0x01, 0x8E, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x10, 0x00, + 0xBD, 0xFF, 0x82, 0x00, 0x5E, 0xFF, 0x5D, 0x00, 0xD4, 0x00, 0x0C, + 0xFB, 0xF9, 0x46, 0x87, 0x0E, 0x82, 0xF8, 0xAD, 0x04, 0x26, 0xFD, + 0x8D, 0x01, 0x54, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76, 0xF3, + 0x22, 0x20, 0xCA, 0x3D, 0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD, 0xD4, + 0x00, 0xCA, 0xFF, 0x03, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDD, 0xFF, + 0x2E, 0x00, 0x0A, 0x00, 0x27, 0xFF, 0xEF, 0x02, 0x20, 0xF7, 0xE7, + 0x43, 0xD8, 0x15, 0x1E, 0xF6, 0xC0, 0x05, 0xA7, 0xFC, 0xC3, 0x01, + 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3B, + 0xFF, 0xD1, 0x01, 0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18, + 0x89, 0x42, 0x02, 0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12, + 0x00, 0xE8, 0xFF, 0x07, 0x00, 0x03, 0x00, 0xFA, 0xFF, 0xE2, 0xFF, + 0xA4, 0x00, 0x19, 0xFE, 0xAA, 0x04, 0x3E, 0xF4, 0x90, 0x3F, 0x78, + 0x1D, 0x10, 0xF4, 0x93, 0x06, 0x52, 0xFC, 0xE1, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2, + 0x01, 0xF6, 0xFC, 0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46, + 0x93, 0xF9, 0x98, 0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8, + 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0xA1, 0xFF, 0x24, 0x01, + 0x41, 0xFD, 0xF6, 0x05, 0x67, 0xF2, 0x1A, 0x3A, 0x29, 0x25, 0x84, + 0xF2, 0x0F, 0x07, 0x31, 0xFC, 0xE3, 0x01, 0x3A, 0xFF, 0x35, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A, + 0xFD, 0xDF, 0x03, 0x2E, 0xFA, 0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE, + 0x4B, 0xFF, 0x38, 0x01, 0xE9, 0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16, + 0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6F, 0xFF, 0x85, 0x01, 0xA6, 0xFC, + 0xCA, 0x06, 0x8F, 0xF1, 0xBB, 0x33, 0xAB, 0x2C, 0xA3, 0xF1, 0x26, + 0x07, 0x4C, 0xFC, 0xC5, 0x01, 0x4D, 0xFF, 0x30, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, + 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, + 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0xFD, + 0xFF, 0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, + 0x7E, 0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, + 0xFC, 0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, + 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, + 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x02, 0x00, 0x05, + 0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1, 0xFD, 0x4E, 0x05, 0x4A, 0xF3, + 0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3, 0xD6, 0x06, 0x3D, 0xFC, 0xE6, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD6, 0x06, 0x4C, 0xF3, 0xED, + 0x20, 0x3D, 0x3D, 0x4A, 0xF3, 0x4E, 0x05, 0xB1, 0xFD, 0xE1, 0x00, + 0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x84, + 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, + 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, + 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x16, 0x00, 0xA6, 0xFF, 0xBB, 0x00, + 0xE9, 0xFE, 0x38, 0x01, 0x4B, 0xFF, 0x28, 0xFE, 0x3A, 0x48, 0x04, + 0x0A, 0x2E, 0xFA, 0xDF, 0x03, 0x8A, 0xFD, 0x60, 0x01, 0x65, 0xFF, + 0x27, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xC8, 0xFF, 0x64, 0x00, 0x9B, + 0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93, 0xF9, 0x10, 0x46, 0x03, 0x11, + 0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC, 0xA2, 0x01, 0x4C, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x07, 0x00, 0xE8, 0xFF, 0x12, 0x00, 0x42, 0x00, + 0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42, 0x76, 0x18, 0x5C, + 0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B, 0xFF, 0x34, 0x00, + 0xFE, 0xFF, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4, 0x00, 0xC8, + 0xFD, 0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20, 0x76, 0xF3, + 0xC8, 0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0xFF, 0xFF, 0x19, 0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04, 0xFD, + 0x4D, 0x06, 0x00, 0xF2, 0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2, 0x23, + 0x07, 0x34, 0xFC, 0xDD, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, + 0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F, 0x01, 0x80, 0xFC, 0xF7, + 0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F, 0x83, 0xF1, 0x13, 0x07, + 0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A, 0x07, + 0xDC, 0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06, 0xD5, + 0xFC, 0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, + 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC, + 0xF2, 0x81, 0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD, + 0xFB, 0x00, 0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x35, + 0x00, 0x38, 0xFF, 0xDA, 0x01, 0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4, + 0xCE, 0x1A, 0x32, 0x41, 0x1F, 0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71, + 0x00, 0xFB, 0xFF, 0xF0, 0xFF, 0x05, 0x00, 0xFF, 0xFF, 0x31, 0x00, + 0x46, 0xFF, 0xB3, 0x01, 0xCF, 0xFC, 0x67, 0x05, 0xEA, 0xF6, 0x44, + 0x13, 0x1E, 0x45, 0x5E, 0xF8, 0x3F, 0x02, 0x8E, 0xFF, 0xD0, 0xFF, + 0x4A, 0x00, 0xD2, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D, + 0xFF, 0x76, 0x01, 0x59, 0xFD, 0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C, + 0xB6, 0x47, 0xA4, 0xFC, 0x07, 0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0, + 0x00, 0xB1, 0xFF, 0x13, 0x00, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF, + 0x2B, 0x01, 0xFE, 0xFD, 0xF8, 0x02, 0xFB, 0xFB, 0x8D, 0x05, 0xE5, + 0x48, 0xE3, 0x01, 0x91, 0xFD, 0x25, 0x02, 0x6B, 0xFE, 0xF7, 0x00, + 0x8F, 0xFF, 0x1C, 0x00, 0x18, 0x00, 0x9C, 0xFF, 0xD6, 0x00, 0xB1, + 0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3, 0xFF, 0x9C, 0x48, 0xFD, 0x07, + 0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD, 0x49, 0x01, 0x6E, 0xFF, 0x24, + 0x00, 0x00, 0x00, 0x10, 0x00, 0xBE, 0xFF, 0x7F, 0x00, 0x65, 0xFF, + 0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46, 0xCD, 0x0E, 0x6A, + 0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53, 0xFF, 0x2D, 0x00, + 0xFF, 0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11, 0x00, 0x1B, + 0xFF, 0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16, 0x07, 0xF6, + 0xCA, 0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF, + 0xFF, 0x03, 0x00, 0xFB, 0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10, 0xFE, + 0xB9, 0x04, 0x27, 0xF4, 0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3, 0x99, + 0x06, 0x50, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, + 0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28, 0x01, 0x3A, 0xFD, 0x00, + 0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25, 0x79, 0xF2, 0x12, 0x07, + 0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0, 0x06, + 0x8C, 0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07, 0x4E, + 0xFC, 0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, + 0x30, 0x00, 0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8, + 0xF1, 0x62, 0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC, + 0x82, 0x01, 0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x3A, 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2, + 0xDD, 0x24, 0x54, 0x3A, 0x74, 0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20, + 0x01, 0xA3, 0xFF, 0x11, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x37, 0xFF, 0xE1, 0x01, 0x55, 0xFC, 0x8C, 0x06, 0x22, 0xF4, 0x2C, + 0x1D, 0xC0, 0x3F, 0x55, 0xF4, 0x9B, 0x04, 0x23, 0xFE, 0x9F, 0x00, + 0xE4, 0xFF, 0xF9, 0xFF, 0x04, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, + 0xFF, 0xC1, 0x01, 0xAB, 0xFC, 0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15, + 0x0B, 0x44, 0x42, 0xF7, 0xDC, 0x02, 0x32, 0xFF, 0x04, 0x00, 0x31, + 0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x55, 0xFF, + 0x8B, 0x01, 0x2B, 0xFD, 0xA1, 0x04, 0x9B, 0xF8, 0x42, 0x0E, 0x0F, + 0x47, 0x38, 0xFB, 0xBE, 0x00, 0x6A, 0x00, 0x58, 0xFF, 0x85, 0x00, + 0xBB, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x71, 0xFF, 0x43, + 0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E, 0xFB, 0x7E, 0x07, 0xAF, 0x48, + 0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01, 0xA3, 0xFE, 0xDD, 0x00, 0x99, + 0xFF, 0x19, 0x00, 0x1B, 0x00, 0x91, 0xFF, 0xF1, 0x00, 0x79, 0xFE, + 0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48, 0x07, 0x06, 0xC7, + 0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78, 0xFF, 0x22, 0x00, + 0x00, 0x00, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E, 0xFF, 0xB6, + 0x00, 0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C, 0x31, 0xF9, + 0x5A, 0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A, 0x00, 0x00, + 0x00, 0x0B, 0x00, 0xD5, 0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77, 0xFF, + 0x67, 0x02, 0x14, 0xF8, 0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6, 0x7C, + 0x05, 0xC5, 0xFC, 0xB7, 0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF, 0xFF, + 0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D, 0x00, 0x5D, 0xFE, 0x3E, + 0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B, 0x93, 0xF4, 0x62, 0x06, + 0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00, + 0x00, 0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8, 0x05, + 0xCC, 0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06, 0x35, + 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, + 0x20, 0x00, 0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1, + 0xF1, 0x86, 0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC, + 0xD1, 0x01, 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2D, + 0x00, 0x54, 0xFF, 0xB7, 0x01, 0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1, + 0x9F, 0x2E, 0xE3, 0x31, 0x7E, 0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A, + 0x01, 0x64, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x34, 0x00, + 0x3E, 0xFF, 0xDF, 0x01, 0x33, 0xFC, 0x20, 0x07, 0x35, 0xF2, 0x36, + 0x27, 0x78, 0x38, 0x14, 0xF2, 0x3B, 0x06, 0x11, 0xFD, 0x41, 0x01, + 0x92, 0xFF, 0x17, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE5, 0x01, 0x44, 0xFC, 0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F, + 0x31, 0x3E, 0xA5, 0xF3, 0x0F, 0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF, + 0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, + 0xCE, 0x01, 0x8C, 0xFC, 0x00, 0x06, 0x86, 0xF5, 0xE0, 0x17, 0xDB, + 0x42, 0x3F, 0xF6, 0x71, 0x03, 0xD9, 0xFE, 0x36, 0x00, 0x18, 0x00, + 0xE5, 0xFF, 0x07, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9E, + 0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7, 0xF7, 0x75, 0x10, 0x48, 0x46, + 0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00, 0x8E, 0xFF, 0x6B, 0x00, 0xC6, + 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x26, 0x00, 0x68, 0xFF, 0x5B, 0x01, + 0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09, 0x57, 0x48, 0x8D, + 0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2, 0x00, 0xA4, 0xFF, + 0x16, 0x00, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42, 0xFE, 0x74, + 0x02, 0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04, 0x94, 0xFC, + 0xA9, 0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F, 0x00, 0x00, + 0x00, 0x15, 0x00, 0xA9, 0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D, 0x01, + 0x7A, 0xFF, 0xC5, 0xFD, 0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9, 0xF8, + 0x03, 0x7D, 0xFD, 0x66, 0x01, 0x63, 0xFF, 0x28, 0x00, 0x00, 0x00, + 0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9, 0xFF, 0xD6, 0xFF, 0xC3, + 0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11, 0x77, 0xF7, 0x28, 0x05, + 0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x06, + 0x00, 0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8, 0x03, + 0xC7, 0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06, 0x7D, + 0xFC, 0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x02, 0x00, + 0x05, 0x00, 0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56, + 0xF3, 0x61, 0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC, + 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x1A, + 0x00, 0x8A, 0xFF, 0x51, 0x01, 0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1, + 0x82, 0x37, 0x60, 0x28, 0x0E, 0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB, + 0x01, 0x40, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x29, 0x00, + 0x5F, 0xFF, 0xA5, 0x01, 0x78, 0xFC, 0xFF, 0x06, 0x7D, 0xF1, 0xCF, + 0x30, 0xB8, 0x2F, 0x80, 0xF1, 0x0D, 0x07, 0x6A, 0xFC, 0xAE, 0x01, + 0x59, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x33, 0x00, 0x43, + 0xFF, 0xD6, 0x01, 0x39, 0xFC, 0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29, + 0x85, 0x36, 0xCC, 0xF1, 0x7F, 0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82, + 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE6, 0x01, 0x38, 0xFC, 0xE6, 0x06, 0x19, 0xF3, 0xEA, 0x21, 0x8A, + 0x3C, 0x0E, 0xF3, 0x78, 0x05, 0x96, 0xFD, 0xF1, 0x00, 0xBB, 0xFF, + 0x08, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD8, + 0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1, 0xF4, 0x38, 0x1A, 0x8C, 0x41, + 0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE, 0x66, 0x00, 0x01, 0x00, 0xEE, + 0xFF, 0x06, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x47, 0xFF, 0xAF, 0x01, + 0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12, 0x5C, 0x45, 0xA9, + 0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51, 0x00, 0xD0, 0xFF, + 0x0C, 0x00, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71, 0x01, 0x65, + 0xFD, 0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47, 0x03, 0xFD, + 0xD9, 0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE, 0xFF, 0x14, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C, 0xFE, + 0xDE, 0x02, 0x2E, 0xFC, 0x13, 0x05, 0xEC, 0x48, 0x54, 0x02, 0x5E, + 0xFD, 0x3F, 0x02, 0x5D, 0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C, 0x00, + 0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF, 0xFE, 0x86, 0x01, 0xBA, + 0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08, 0xC7, 0xFA, 0x93, 0x03, + 0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0x0F, + 0x00, 0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17, 0x01, + 0x8B, 0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04, 0x15, + 0xFD, 0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x08, 0x00, + 0xE1, 0xFF, 0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD, + 0xF6, 0x77, 0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC, + 0xC8, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0xFD, + 0xFF, 0xD9, 0xFF, 0xB4, 0x00, 0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3, + 0xFC, 0x3E, 0x5B, 0x1E, 0xDB, 0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x14, 0x00, + 0x9B, 0xFF, 0x31, 0x01, 0x2C, 0xFD, 0x15, 0x06, 0x41, 0xF2, 0x6A, + 0x39, 0x0A, 0x26, 0x61, 0xF2, 0x17, 0x07, 0x31, 0xFC, 0xE2, 0x01, + 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x25, 0x00, 0x6A, + 0xFF, 0x8E, 0x01, 0x99, 0xFC, 0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32, + 0x82, 0x2D, 0x96, 0xF1, 0x21, 0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50, + 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF, + 0xCA, 0x01, 0x46, 0xFC, 0x29, 0x07, 0xB3, 0xF1, 0xD1, 0x2B, 0x81, + 0x34, 0x9C, 0xF1, 0xB8, 0x06, 0xB5, 0xFC, 0x7C, 0x01, 0x74, 0xFF, + 0x22, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, + 0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA, 0xF2, 0x46, 0x24, 0xC8, 0x3A, + 0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD, 0x17, 0x01, 0xA8, 0xFF, 0x0F, + 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01, + 0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C, 0x1F, 0x40, 0x85, + 0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA, 0xFF, 0xF7, 0xFF, + 0x04, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE, 0x01, 0xB4, + 0xFC, 0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44, 0x86, 0xF7, + 0xB6, 0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9, 0xFF, 0x0A, + 0x00, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x86, 0x01, 0x36, 0xFD, + 0x89, 0x04, 0xCD, 0xF8, 0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB, 0x91, + 0x00, 0x83, 0x00, 0x4A, 0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11, 0x00, + 0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D, 0x01, 0xD6, 0xFD, 0x46, + 0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48, 0x98, 0x00, 0x26, 0xFE, + 0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0x1A, + 0x00, 0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5, 0xFD, + 0x05, 0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03, 0xE4, + 0xFD, 0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0x12, 0x00, + 0xB6, 0xFF, 0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB, + 0xFB, 0x69, 0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD, + 0x81, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xD7, + 0xFF, 0x3E, 0x00, 0xEA, 0xFF, 0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7, + 0x99, 0x44, 0x68, 0x14, 0x8E, 0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA, + 0x01, 0x43, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x04, 0x00, 0xF5, 0xFF, + 0xEF, 0xFF, 0x88, 0x00, 0x49, 0xFE, 0x5D, 0x04, 0xB7, 0xF4, 0x7D, + 0x40, 0xFD, 0x1B, 0x6C, 0xF4, 0x70, 0x06, 0x5F, 0xFC, 0xDE, 0x01, + 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAC, + 0xFF, 0x0E, 0x01, 0x66, 0xFD, 0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B, + 0xB0, 0x23, 0xC4, 0xF2, 0xFF, 0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x21, 0x00, 0x77, 0xFF, + 0x75, 0x01, 0xBF, 0xFC, 0xAB, 0x06, 0xA6, 0xF1, 0x05, 0x35, 0x40, + 0x2B, 0xBF, 0xF1, 0x2A, 0x07, 0x42, 0xFC, 0xCE, 0x01, 0x48, 0xFF, + 0x31, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2E, 0x00, 0x52, 0xFF, 0xBC, + 0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E, 0xF1, 0x11, 0x2E, 0x6B, 0x32, + 0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC, 0x94, 0x01, 0x67, 0xFF, 0x26, + 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE0, 0x01, + 0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26, 0xF2, 0x38, 0x2A, + 0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96, 0xFF, 0x16, 0x00, + 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x48, + 0xFC, 0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E, 0xCF, 0xF3, + 0xF3, 0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF, 0xFF, 0x03, + 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93, 0xFC, + 0xEF, 0x05, 0xB0, 0xF5, 0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6, 0x4D, + 0x03, 0xEF, 0xFE, 0x2A, 0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08, 0x00, + 0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99, 0x01, 0x0B, 0xFD, 0xE6, + 0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46, 0x37, 0xFA, 0x42, 0x01, + 0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3, 0xFF, 0x0F, 0x00, 0x00, + 0x00, 0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD, 0x03, + 0x94, 0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE, 0x6C, + 0x01, 0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0x1D, 0x00, + 0x8A, 0xFF, 0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6, + 0x02, 0xF2, 0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE, + 0x1E, 0x01, 0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0xAC, + 0xFF, 0xAE, 0x00, 0x05, 0xFF, 0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD, + 0xFD, 0x47, 0x0E, 0x0B, 0xC8, 0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C, + 0x01, 0x61, 0xFF, 0x28, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xCD, 0xFF, + 0x57, 0x00, 0xB6, 0xFF, 0xBE, 0xFF, 0xED, 0x01, 0xF5, 0xF8, 0x9B, + 0x45, 0x22, 0x12, 0x48, 0xF7, 0x3D, 0x05, 0xE2, 0xFC, 0xAB, 0x01, + 0x49, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x06, 0x00, 0xEC, 0xFF, 0x06, + 0x00, 0x5A, 0x00, 0x9A, 0xFE, 0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41, + 0xA1, 0x19, 0x09, 0xF5, 0x33, 0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, + 0xE8, 0x00, 0xA6, 0xFD, 0x5F, 0x05, 0x31, 0xF3, 0xF6, 0x3C, 0x52, + 0x21, 0x37, 0xF3, 0xDD, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x1C, 0x00, 0x86, 0xFF, 0x59, + 0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC, 0xF1, 0x04, 0x37, 0xF3, 0x28, + 0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC, 0xD8, 0x01, 0x41, 0xFF, 0x33, + 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01, + 0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E, + 0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00, + 0xFD, 0xFF, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8, 0x01, 0x37, + 0xFC, 0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37, 0xDC, 0xF1, + 0x6F, 0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C, 0x00, 0xFE, + 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B, 0xFC, + 0xDD, 0x06, 0x37, 0xF3, 0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3, 0x5F, + 0x05, 0xA6, 0xFD, 0xE8, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01, 0x00, + 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6, 0x01, 0x77, 0xFC, 0x33, + 0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41, 0x8D, 0xF5, 0xDA, 0x03, + 0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0xFF, + 0xFF, 0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D, 0x05, + 0x48, 0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01, 0xBE, + 0xFF, 0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8, + 0xF9, 0x0E, 0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01, + 0x05, 0xFF, 0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x7F, 0xFF, 0x1E, 0x01, 0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC, + 0x9B, 0x04, 0xF2, 0x48, 0xC6, 0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50, + 0xFE, 0x04, 0x01, 0x8A, 0xFF, 0x1D, 0x00, 0x17, 0x00, 0xA1, 0xFF, + 0xC9, 0x00, 0xCD, 0xFE, 0x6C, 0x01, 0xEA, 0xFE, 0xF3, 0xFE, 0x70, + 0x48, 0xFF, 0x08, 0x94, 0xFA, 0xAD, 0x03, 0xA3, 0xFD, 0x55, 0x01, + 0x6A, 0xFF, 0x26, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xC3, 0xFF, 0x71, + 0x00, 0x81, 0xFF, 0x1F, 0x00, 0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46, + 0xE7, 0x0F, 0x08, 0xF8, 0xE6, 0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F, + 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x08, 0x00, 0xE3, 0xFF, 0x1E, 0x00, + 0x2A, 0x00, 0xEF, 0xFE, 0x4D, 0x03, 0x7D, 0xF6, 0x2A, 0x43, 0x4B, + 0x17, 0xB0, 0xF5, 0xEF, 0x05, 0x93, 0xFC, 0xCB, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0xD4, 0xFF, 0xBF, + 0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF, 0xF3, 0x98, 0x3E, 0xF3, 0x1E, + 0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x16, 0x00, 0x96, 0xFF, 0x39, 0x01, + 0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38, 0xA0, 0x26, 0x4B, + 0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C, 0xFF, 0x35, 0x00, + 0xFD, 0xFF, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94, 0x01, 0x90, + 0xFC, 0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E, 0x8E, 0xF1, + 0x1D, 0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E, 0x00, 0xFD, + 0xFF, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42, 0xFC, + 0x2A, 0x07, 0xBF, 0xF1, 0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1, 0xAB, + 0x06, 0xBF, 0xFC, 0x75, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, 0xFF, + 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0xFF, + 0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B, 0xAD, 0xF2, 0xBF, 0x05, + 0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70, 0x06, + 0x6C, 0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04, 0x49, + 0xFE, 0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0xFF, 0xFF, + 0x32, 0x00, 0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E, + 0xF6, 0x68, 0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF, + 0xEA, 0xFF, 0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x2B, + 0x00, 0x59, 0xFF, 0x81, 0x01, 0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8, + 0x2D, 0x0D, 0x69, 0x47, 0xEB, 0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C, + 0xFF, 0x93, 0x00, 0xB6, 0xFF, 0x12, 0x00, 0x00, 0x00, 0x22, 0x00, + 0x76, 0xFF, 0x37, 0x01, 0xE4, 0xFD, 0x2C, 0x03, 0x94, 0xFB, 0x83, + 0x06, 0xCE, 0x48, 0x05, 0x01, 0xF5, 0xFD, 0xF0, 0x01, 0x87, 0xFE, + 0xEA, 0x00, 0x94, 0xFF, 0x1A, 0x00, 0x1A, 0x00, 0x96, 0xFF, 0xE3, + 0x00, 0x95, 0xFE, 0xD5, 0x01, 0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48, + 0x00, 0x07, 0x61, 0xFB, 0x46, 0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73, + 0xFF, 0x23, 0x00, 0x00, 0x00, 0x11, 0x00, 0xB9, 0xFF, 0x8C, 0x00, + 0x4A, 0xFF, 0x83, 0x00, 0x91, 0x00, 0x91, 0xFB, 0x3D, 0x47, 0xB7, + 0x0D, 0xCD, 0xF8, 0x89, 0x04, 0x36, 0xFD, 0x86, 0x01, 0x57, 0xFF, + 0x2B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xD9, 0xFF, 0x37, 0x00, 0xF7, + 0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86, 0xF7, 0x53, 0x44, 0xFB, 0x14, + 0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC, 0xBE, 0x01, 0x42, 0xFF, 0x32, + 0x00, 0xFF, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xEA, 0xFF, 0x93, 0x00, + 0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40, 0x94, 0x1C, 0x47, + 0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17, 0x01, 0x57, + 0xFD, 0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24, 0xAA, 0xF2, + 0x06, 0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0xFE, 0xFF, 0x22, 0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5, 0xFC, + 0xB8, 0x06, 0x9C, 0xF1, 0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1, 0x29, + 0x07, 0x46, 0xFC, 0xCA, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD, 0xFF, + 0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0, 0x01, 0x53, 0xFC, 0x21, + 0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32, 0x86, 0xF1, 0xDB, 0x06, + 0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17, 0x07, + 0x61, 0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06, 0x2C, + 0xFD, 0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB, + 0xF3, 0x5B, 0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD, + 0xB4, 0x00, 0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0x33, + 0x00, 0x3E, 0xFF, 0xC8, 0x01, 0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5, + 0xB6, 0x16, 0x77, 0x43, 0xBD, 0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D, + 0x00, 0x25, 0x00, 0xE1, 0xFF, 0x08, 0x00, 0xFF, 0xFF, 0x2D, 0x00, + 0x51, 0xFF, 0x95, 0x01, 0x15, 0xFD, 0xCF, 0x04, 0x39, 0xF8, 0x59, + 0x0F, 0xAF, 0x46, 0x8B, 0xFA, 0x17, 0x01, 0x38, 0x00, 0x73, 0xFF, + 0x78, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x25, 0x00, 0x6C, + 0xFF, 0x4F, 0x01, 0xB0, 0xFD, 0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08, + 0x86, 0x48, 0x5A, 0xFF, 0xBA, 0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF, + 0x00, 0x9E, 0xFF, 0x17, 0x00, 0x1C, 0x00, 0x8C, 0xFF, 0xFE, 0x00, + 0x5D, 0xFE, 0x3F, 0x02, 0x5E, 0xFD, 0x54, 0x02, 0xEC, 0x48, 0x13, + 0x05, 0x2E, 0xFC, 0xDE, 0x02, 0x0C, 0xFE, 0x24, 0x01, 0x7D, 0xFF, + 0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0xAE, 0xFF, 0xA7, 0x00, 0x12, + 0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03, 0xFD, 0xDC, 0x47, 0x95, 0x0B, + 0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD, 0x71, 0x01, 0x5F, 0xFF, 0x29, + 0x00, 0x00, 0x00, 0x0C, 0x00, 0xD0, 0xFF, 0x51, 0x00, 0xC3, 0xFF, + 0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45, 0xB2, 0x12, 0x19, + 0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47, 0xFF, 0x30, 0x00, + 0xFF, 0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66, 0x00, 0x85, + 0xFE, 0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A, 0xE1, 0xF4, + 0x43, 0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, + 0xFF, 0x01, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96, 0xFD, + 0x78, 0x05, 0x0E, 0xF3, 0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3, 0xE6, + 0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60, 0x01, 0xE0, 0xFC, 0x7F, + 0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29, 0xEB, 0xF1, 0x2A, 0x07, + 0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D, 0x07, + 0x80, 0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06, 0x78, + 0xFC, 0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, + 0x34, 0x00, 0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E, + 0xF2, 0x60, 0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC, + 0x51, 0x01, 0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3, + 0xBA, 0x20, 0x61, 0x3D, 0x56, 0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE, + 0x00, 0xC5, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x3A, 0xFF, 0xD3, 0x01, 0x7D, 0xFC, 0x23, 0x06, 0x32, 0xF5, 0x0C, + 0x19, 0x38, 0x42, 0xC7, 0xF5, 0xB8, 0x03, 0xAF, 0xFE, 0x4E, 0x00, + 0x0C, 0x00, 0xEA, 0xFF, 0x06, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4A, + 0xFF, 0xA7, 0x01, 0xEC, 0xFC, 0x28, 0x05, 0x77, 0xF7, 0x92, 0x11, + 0xD7, 0x45, 0x43, 0xF9, 0xC3, 0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E, + 0x00, 0xCB, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x28, 0x00, 0x63, 0xFF, + 0x66, 0x01, 0x7D, 0xFD, 0xF8, 0x03, 0xFB, 0xF9, 0x89, 0x0A, 0x1D, + 0x48, 0xC5, 0xFD, 0x7A, 0xFF, 0x1D, 0x01, 0xF7, 0xFE, 0xB4, 0x00, + 0xA9, 0xFF, 0x15, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x82, 0xFF, 0x18, + 0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94, 0xFC, 0x24, 0x04, 0xF5, 0x48, + 0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02, 0x42, 0xFE, 0x0B, 0x01, 0x87, + 0xFF, 0x1E, 0x00, 0x16, 0x00, 0xA4, 0xFF, 0xC2, 0x00, 0xDB, 0xFE, + 0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48, 0x81, 0x09, 0x61, + 0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68, 0xFF, 0x26, 0x00, + 0x00, 0x00, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E, 0xFF, 0x06, + 0x00, 0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10, 0xD7, 0xF7, + 0xFC, 0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, + 0xFF, 0x07, 0x00, 0xE5, 0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9, 0xFE, + 0x71, 0x03, 0x3F, 0xF6, 0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5, 0x00, + 0x06, 0x8C, 0xFC, 0xCE, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, + 0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9, 0x00, 0xDA, 0xFD, 0x0F, + 0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F, 0x97, 0xF3, 0xBD, 0x06, + 0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, + 0xFF, 0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B, 0x06, + 0x14, 0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07, 0x33, + 0xFC, 0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, + 0x28, 0x00, 0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E, + 0xF1, 0xE3, 0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC, + 0xB7, 0x01, 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x32, + 0x00, 0x46, 0xFF, 0xD1, 0x01, 0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1, + 0xAE, 0x2A, 0x86, 0x35, 0xB1, 0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E, + 0x01, 0x7B, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF7, 0x06, 0xE0, 0xF2, 0x18, + 0x23, 0xAB, 0x3B, 0xCC, 0xF2, 0xA8, 0x05, 0x76, 0xFD, 0x04, 0x01, + 0xB1, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, + 0xFF, 0xDC, 0x01, 0x64, 0xFC, 0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B, + 0xD9, 0x40, 0xEA, 0xF4, 0x3E, 0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5, + 0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x44, 0xFF, + 0xB7, 0x01, 0xC5, 0xFC, 0x7C, 0x05, 0xBC, 0xF6, 0xD5, 0x13, 0xDC, + 0x44, 0x14, 0xF8, 0x67, 0x02, 0x77, 0xFF, 0xDD, 0xFF, 0x44, 0x00, + 0xD5, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5B, 0xFF, 0x7C, + 0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31, 0xF9, 0xA4, 0x0C, 0x90, 0x47, + 0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00, 0x2E, 0xFF, 0x99, 0x00, 0xB3, + 0xFF, 0x12, 0x00, 0x00, 0x00, 0x22, 0x00, 0x78, 0xFF, 0x31, 0x01, + 0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06, 0xDB, 0x48, 0x73, + 0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1, 0x00, 0x91, 0xFF, + 0x1B, 0x00, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3, 0xFE, 0xBB, + 0x01, 0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07, 0x2E, 0xFB, + 0x60, 0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24, 0x00, 0x00, + 0x00, 0x10, 0x00, 0xBB, 0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A, 0x00, + 0xBE, 0x00, 0x38, 0xFB, 0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8, 0xA1, + 0x04, 0x2B, 0xFD, 0x8B, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, + 0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04, 0x00, 0x32, 0xFF, 0xDC, + 0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15, 0x34, 0xF6, 0xB7, 0x05, + 0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x04, + 0x00, 0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B, 0x04, + 0x55, 0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06, 0x55, + 0xFC, 0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00, 0x00, + 0x11, 0x00, 0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74, + 0xF2, 0x54, 0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC, + 0xE4, 0x01, 0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x23, + 0x00, 0x71, 0xFF, 0x82, 0x01, 0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1, + 0xFD, 0x33, 0x62, 0x2C, 0xA8, 0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7, + 0x01, 0x4C, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2F, 0x00, + 0x4E, 0xFF, 0xC3, 0x01, 0x4E, 0xFC, 0x24, 0x07, 0x9E, 0xF1, 0xF2, + 0x2C, 0x78, 0x33, 0x8C, 0xF1, 0xD0, 0x06, 0xA2, 0xFC, 0x88, 0x01, + 0x6D, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x35, 0x00, 0x3B, + 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x12, 0x07, 0x79, 0xF2, 0x73, 0x25, + 0xDF, 0x39, 0x5A, 0xF2, 0x00, 0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F, + 0xFF, 0x13, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE2, 0x01, 0x50, 0xFC, 0x99, 0x06, 0xFE, 0xF3, 0xC3, 0x1D, 0x5E, + 0x3F, 0x27, 0xF4, 0xB9, 0x04, 0x10, 0xFE, 0xA9, 0x00, 0xDF, 0xFF, + 0xFB, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC5, + 0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07, 0xF6, 0x22, 0x16, 0xC3, 0x43, + 0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF, 0x11, 0x00, 0x2B, 0x00, 0xDE, + 0xFF, 0x09, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0x90, 0x01, + 0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E, 0xE1, 0x46, 0xE1, + 0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F, 0x00, 0xBE, 0xFF, + 0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49, 0x01, 0xBC, + 0xFD, 0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48, 0xC3, 0xFF, + 0x89, 0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C, 0xFF, 0x18, + 0x00, 0x1C, 0x00, 0x8F, 0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25, 0x02, + 0x91, 0xFD, 0xE3, 0x01, 0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB, 0xF8, + 0x02, 0xFE, 0xFD, 0x2B, 0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00, 0x00, + 0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20, 0xFF, 0xD0, 0x00, 0x07, + 0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C, 0x63, 0xF9, 0x42, 0x04, + 0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x0B, + 0x00, 0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F, 0x02, + 0x5E, 0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05, 0xCF, + 0xFC, 0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x05, 0x00, + 0xF0, 0xFF, 0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F, + 0xF5, 0x32, 0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC, + 0xDA, 0x01, 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A, + 0x00, 0xB6, 0xFF, 0xFB, 0x00, 0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2, + 0x1C, 0x3C, 0x81, 0x22, 0xFC, 0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x1E, 0x00, + 0x7F, 0xFF, 0x67, 0x01, 0xD5, 0xFC, 0x8E, 0x06, 0xBE, 0xF1, 0x06, + 0x36, 0x1A, 0x2A, 0xDC, 0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01, + 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2C, 0x00, 0x57, + 0xFF, 0xB3, 0x01, 0x64, 0xFC, 0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F, + 0x5A, 0x31, 0x7D, 0xF1, 0xF7, 0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61, + 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF, + 0xDD, 0x01, 0x34, 0xFC, 0x23, 0x07, 0x21, 0xF2, 0xCB, 0x27, 0xFE, + 0x37, 0x00, 0xF2, 0x4D, 0x06, 0x04, 0xFD, 0x49, 0x01, 0x8E, 0xFF, + 0x19, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, + 0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76, 0xF3, 0x22, 0x20, 0xCA, 0x3D, + 0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD, 0xD4, 0x00, 0xCA, 0xFF, 0x03, + 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD1, 0x01, + 0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18, 0x89, 0x42, 0x02, + 0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12, 0x00, 0xE8, 0xFF, + 0x07, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2, 0x01, 0xF6, + 0xFC, 0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46, 0x93, 0xF9, + 0x98, 0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8, 0xFF, 0x0E, + 0x00, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A, 0xFD, + 0xDF, 0x03, 0x2E, 0xFA, 0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE, 0x4B, + 0xFF, 0x38, 0x01, 0xE9, 0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16, 0x00, + 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, + 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, + 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x00, + 0x00, 0xF4, 0xFF, 0x1A, 0x00, 0xFF, 0x00, 0x07, 0x03, 0x16, 0x06, + 0x7C, 0x09, 0x2A, 0x0C, 0x2E, 0x0D, 0x2A, 0x0C, 0x7C, 0x09, 0x16, + 0x06, 0x07, 0x03, 0xFF, 0x00, 0x1A, 0x00, 0xF4, 0xFF, 0xF2, 0xFF, + 0xA0, 0xFF, 0x71, 0xFF, 0x71, 0x00, 0x86, 0x03, 0x73, 0x08, 0x88, + 0x0D, 0x78, 0x10, 0xC9, 0x0F, 0xD5, 0x0B, 0x8B, 0x06, 0x28, 0x02, + 0xDF, 0xFF, 0x6F, 0xFF, 0xC3, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDC, + 0xFF, 0x80, 0xFF, 0x9A, 0xFF, 0x46, 0x01, 0x1E, 0x05, 0x5A, 0x0A, + 0xED, 0x0E, 0xAA, 0x10, 0xAF, 0x0E, 0xFD, 0x09, 0xCB, 0x04, 0x18, + 0x01, 0x8E, 0xFF, 0x85, 0xFF, 0xE1, 0xFF, 0xFC, 0xFF, 0xBD, 0xFF, + 0x6D, 0xFF, 0xF6, 0xFF, 0x65, 0x02, 0xE5, 0x06, 0x2B, 0x0C, 0xF3, + 0x0F, 0x60, 0x10, 0x3B, 0x0D, 0x16, 0x08, 0x3F, 0x03, 0x50, 0x00, + 0x6E, 0xFF, 0xA7, 0xFF, 0xF5, 0xFF, 0xEF, 0xFF, 0x9A, 0xFF, 0x75, + 0xFF, 0x91, 0x00, 0xC9, 0x03, 0xC8, 0x08, 0xCC, 0x0D, 0x89, 0x10, + 0x9F, 0x0F, 0x85, 0x0B, 0x3B, 0x06, 0xF4, 0x01, 0xCD, 0xFF, 0x72, + 0xFF, 0xC9, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD7, 0xFF, 0x7B, 0xFF, + 0xA5, 0xFF, 0x73, 0x01, 0x6A, 0x05, 0xAD, 0x0A, 0x21, 0x0F, 0xA6, + 0x10, 0x74, 0x0E, 0xA9, 0x09, 0x83, 0x04, 0xF0, 0x00, 0x85, 0xFF, + 0x8B, 0xFF, 0xE5, 0xFF, 0xFA, 0xFF, 0xB7, 0xFF, 0x6C, 0xFF, 0x0C, + 0x00, 0x9D, 0x02, 0x37, 0x07, 0x78, 0x0C, 0x15, 0x10, 0x47, 0x10, + 0xF3, 0x0C, 0xC2, 0x07, 0x01, 0x03, 0x35, 0x00, 0x6D, 0xFF, 0xAD, + 0xFF, 0xF7, 0xFF, 0xEB, 0xFF, 0x94, 0xFF, 0x7A, 0xFF, 0xB3, 0x00, + 0x0D, 0x04, 0x1C, 0x09, 0x0D, 0x0E, 0x97, 0x10, 0x73, 0x0F, 0x35, + 0x0B, 0xEB, 0x05, 0xC1, 0x01, 0xBD, 0xFF, 0x75, 0xFF, 0xCE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0xFF, 0x77, 0xFF, 0xB3, 0xFF, 0xA1, + 0x01, 0xB7, 0x05, 0xFF, 0x0A, 0x53, 0x0F, 0x9E, 0x10, 0x37, 0x0E, + 0x55, 0x09, 0x3B, 0x04, 0xCB, 0x00, 0x7E, 0xFF, 0x90, 0xFF, 0xE9, + 0xFF, 0xF8, 0xFF, 0xB1, 0xFF, 0x6C, 0xFF, 0x24, 0x00, 0xD8, 0x02, + 0x8A, 0x07, 0xC2, 0x0C, 0x34, 0x10, 0x2A, 0x10, 0xAA, 0x0C, 0x6F, + 0x07, 0xC4, 0x02, 0x1C, 0x00, 0x6C, 0xFF, 0xB3, 0xFF, 0xF9, 0xFF, + 0xE8, 0xFF, 0x8E, 0xFF, 0x80, 0xFF, 0xD7, 0x00, 0x53, 0x04, 0x71, + 0x09, 0x4C, 0x0E, 0xA1, 0x10, 0x43, 0x0F, 0xE3, 0x0A, 0x9D, 0x05, + 0x91, 0x01, 0xAE, 0xFF, 0x79, 0xFF, 0xD4, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0xCD, 0xFF, 0x74, 0xFF, 0xC2, 0xFF, 0xD2, 0x01, 0x06, 0x06, + 0x50, 0x0B, 0x82, 0x0F, 0x93, 0x10, 0xF8, 0x0D, 0x00, 0x09, 0xF6, + 0x03, 0xA7, 0x00, 0x78, 0xFF, 0x96, 0xFF, 0xEC, 0xFF, 0xF6, 0xFF, + 0xAB, 0xFF, 0x6D, 0xFF, 0x3E, 0x00, 0x15, 0x03, 0xDE, 0x07, 0x0B, + 0x0D, 0x50, 0x10, 0x0A, 0x10, 0x5E, 0x0C, 0x1C, 0x07, 0x8A, 0x02, + 0x04, 0x00, 0x6C, 0xFF, 0xB9, 0xFF, 0xFB, 0xFF, 0xE4, 0xFF, 0x89, + 0xFF, 0x88, 0xFF, 0xFD, 0x00, 0x9B, 0x04, 0xC5, 0x09, 0x88, 0x0E, + 0xA8, 0x10, 0x10, 0x0F, 0x91, 0x0A, 0x50, 0x05, 0x64, 0x01, 0xA1, + 0xFF, 0x7D, 0xFF, 0xD9, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC7, 0xFF, + 0x71, 0xFF, 0xD3, 0xFF, 0x05, 0x02, 0x55, 0x06, 0xA0, 0x0B, 0xAD, + 0x0F, 0x84, 0x10, 0xB6, 0x0D, 0xAC, 0x08, 0xB3, 0x03, 0x86, 0x00, + 0x74, 0xFF, 0x9C, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xA5, 0xFF, 0x6F, + 0xFF, 0x5A, 0x00, 0x54, 0x03, 0x32, 0x08, 0x52, 0x0D, 0x68, 0x10, + 0xE6, 0x0F, 0x11, 0x0C, 0xCA, 0x06, 0x52, 0x02, 0xEF, 0xFF, 0x6E, + 0xFF, 0xBF, 0xFF, 0xFC, 0xFF, 0xDF, 0xFF, 0x84, 0xFF, 0x91, 0xFF, + 0x25, 0x01, 0xE4, 0x04, 0x19, 0x0A, 0xC2, 0x0E, 0xAA, 0x10, 0xDA, + 0x0E, 0x3E, 0x0A, 0x05, 0x05, 0x38, 0x01, 0x96, 0xFF, 0x81, 0xFF, + 0xDD, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC1, 0xFF, 0x6E, 0xFF, 0xE6, + 0xFF, 0x3A, 0x02, 0xA6, 0x06, 0xEF, 0x0B, 0xD6, 0x0F, 0x71, 0x10, + 0x71, 0x0D, 0x57, 0x08, 0x71, 0x03, 0x67, 0x00, 0x70, 0xFF, 0xA2, + 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0x9F, 0xFF, 0x72, 0xFF, 0x78, 0x00, + 0x95, 0x03, 0x86, 0x08, 0x98, 0x0D, 0x7C, 0x10, 0xC0, 0x0F, 0xC3, + 0x0B, 0x79, 0x06, 0x1C, 0x02, 0xDB, 0xFF, 0x70, 0xFF, 0xC5, 0xFF, + 0xFE, 0xFF, 0x00, 0x00, 0xDB, 0xFF, 0x7F, 0xFF, 0x9C, 0xFF, 0x50, + 0x01, 0x2F, 0x05, 0x6C, 0x0A, 0xF9, 0x0E, 0xA9, 0x10, 0xA2, 0x0E, + 0xEA, 0x09, 0xBB, 0x04, 0x0F, 0x01, 0x8C, 0xFF, 0x87, 0xFF, 0xE2, + 0xFF, 0xFC, 0xFF, 0xBC, 0xFF, 0x6D, 0xFF, 0xFA, 0xFF, 0x71, 0x02, + 0xF7, 0x06, 0x3C, 0x0C, 0xFB, 0x0F, 0x5B, 0x10, 0x2B, 0x0D, 0x03, + 0x08, 0x31, 0x03, 0x4A, 0x00, 0x6E, 0xFF, 0xA8, 0xFF, 0xF5, 0xFF, + 0xEE, 0xFF, 0x99, 0xFF, 0x76, 0xFF, 0x98, 0x00, 0xD8, 0x03, 0xDB, + 0x08, 0xDB, 0x0D, 0x8D, 0x10, 0x96, 0x0F, 0x73, 0x0B, 0x29, 0x06, + 0xE8, 0x01, 0xC9, 0xFF, 0x72, 0xFF, 0xCA, 0xFF, 0xFE, 0xFF, 0x00, + 0x00, 0xD6, 0xFF, 0x7A, 0xFF, 0xA8, 0xFF, 0x7D, 0x01, 0x7B, 0x05, + 0xBF, 0x0A, 0x2D, 0x0F, 0xA5, 0x10, 0x67, 0x0E, 0x96, 0x09, 0x73, + 0x04, 0xE7, 0x00, 0x84, 0xFF, 0x8C, 0xFF, 0xE6, 0xFF, 0xFA, 0xFF, + 0xB6, 0xFF, 0x6C, 0xFF, 0x11, 0x00, 0xAA, 0x02, 0x4A, 0x07, 0x88, + 0x0C, 0x1C, 0x10, 0x41, 0x10, 0xE3, 0x0C, 0xAF, 0x07, 0xF3, 0x02, + 0x2F, 0x00, 0x6C, 0xFF, 0xAE, 0xFF, 0xF7, 0xFF, 0xEA, 0xFF, 0x93, + 0xFF, 0x7B, 0xFF, 0xBB, 0x00, 0x1C, 0x04, 0x2F, 0x09, 0x1B, 0x0E, + 0x9A, 0x10, 0x68, 0x0F, 0x23, 0x0B, 0xDA, 0x05, 0xB7, 0x01, 0xB9, + 0xFF, 0x76, 0xFF, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0xFF, + 0x76, 0xFF, 0xB6, 0xFF, 0xAC, 0x01, 0xC8, 0x05, 0x11, 0x0B, 0x5E, + 0x0F, 0x9C, 0x10, 0x29, 0x0E, 0x42, 0x09, 0x2C, 0x04, 0xC2, 0x00, + 0x7D, 0xFF, 0x92, 0xFF, 0xEA, 0xFF, 0xF8, 0xFF, 0xB0, 0xFF, 0x6C, + 0xFF, 0x29, 0x00, 0xE6, 0x02, 0x9D, 0x07, 0xD3, 0x0C, 0x3B, 0x10, + 0x23, 0x10, 0x99, 0x0C, 0x5C, 0x07, 0xB7, 0x02, 0x16, 0x00, 0x6C, + 0xFF, 0xB4, 0xFF, 0xF9, 0xFF, 0xE7, 0xFF, 0x8D, 0xFF, 0x82, 0xFF, + 0xDF, 0x00, 0x63, 0x04, 0x84, 0x09, 0x59, 0x0E, 0xA3, 0x10, 0x38, + 0x0F, 0xD1, 0x0A, 0x8C, 0x05, 0x87, 0x01, 0xAB, 0xFF, 0x79, 0xFF, + 0xD5, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xCB, 0xFF, 0x73, 0xFF, 0xC6, + 0xFF, 0xDD, 0x01, 0x17, 0x06, 0x62, 0x0B, 0x8C, 0x0F, 0x90, 0x10, + 0xE9, 0x0D, 0xED, 0x08, 0xE7, 0x03, 0xA0, 0x00, 0x77, 0xFF, 0x97, + 0xFF, 0xED, 0xFF, 0xF6, 0xFF, 0xA9, 0xFF, 0x6D, 0xFF, 0x44, 0x00, + 0x23, 0x03, 0xF1, 0x07, 0x1B, 0x0D, 0x55, 0x10, 0x02, 0x10, 0x4D, + 0x0C, 0x0A, 0x07, 0x7E, 0x02, 0xFF, 0xFF, 0x6D, 0xFF, 0xBA, 0xFF, + 0xFB, 0xFF, 0xE3, 0xFF, 0x88, 0xFF, 0x8A, 0xFF, 0x06, 0x01, 0xAB, + 0x04, 0xD8, 0x09, 0x95, 0x0E, 0xA9, 0x10, 0x05, 0x0F, 0x7F, 0x0A, + 0x40, 0x05, 0x5A, 0x01, 0x9F, 0xFF, 0x7E, 0xFF, 0xDA, 0xFF, 0x00, + 0x00, 0xFE, 0xFF, 0xC6, 0xFF, 0x70, 0xFF, 0xD7, 0xFF, 0x10, 0x02, + 0x67, 0x06, 0xB1, 0x0B, 0xB7, 0x0F, 0x80, 0x10, 0xA7, 0x0D, 0x99, + 0x08, 0xA4, 0x03, 0x7F, 0x00, 0x73, 0xFF, 0x9D, 0xFF, 0xF0, 0xFF, + 0xF3, 0xFF, 0xA3, 0xFF, 0x70, 0xFF, 0x60, 0x00, 0x62, 0x03, 0x45, + 0x08, 0x62, 0x0D, 0x6C, 0x10, 0xDE, 0x0F, 0x00, 0x0C, 0xB8, 0x06, + 0x46, 0x02, 0xEA, 0xFF, 0x6E, 0xFF, 0xC0, 0xFF, 0xFD, 0xFF, 0x00, + 0x00, 0xDE, 0xFF, 0x83, 0xFF, 0x94, 0xFF, 0x2F, 0x01, 0xF4, 0x04, + 0x2B, 0x0A, 0xCE, 0x0E, 0xAA, 0x10, 0xCE, 0x0E, 0x2B, 0x0A, 0xF4, + 0x04, 0x2F, 0x01, 0x94, 0xFF, 0x83, 0xFF, 0xDE, 0xFF, 0xFD, 0xFF, + 0xC0, 0xFF, 0x6E, 0xFF, 0xEA, 0xFF, 0x46, 0x02, 0xB8, 0x06, 0x00, + 0x0C, 0xDE, 0x0F, 0x6C, 0x10, 0x62, 0x0D, 0x45, 0x08, 0x62, 0x03, + 0x60, 0x00, 0x70, 0xFF, 0xA3, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0x9D, + 0xFF, 0x73, 0xFF, 0x7F, 0x00, 0xA4, 0x03, 0x99, 0x08, 0xA7, 0x0D, + 0x80, 0x10, 0xB7, 0x0F, 0xB1, 0x0B, 0x67, 0x06, 0x10, 0x02, 0xD7, + 0xFF, 0x70, 0xFF, 0xC6, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xDA, 0xFF, + 0x7E, 0xFF, 0x9F, 0xFF, 0x5A, 0x01, 0x40, 0x05, 0x7F, 0x0A, 0x05, + 0x0F, 0xA9, 0x10, 0x95, 0x0E, 0xD8, 0x09, 0xAB, 0x04, 0x06, 0x01, + 0x8A, 0xFF, 0x88, 0xFF, 0xE3, 0xFF, 0xFB, 0xFF, 0xBA, 0xFF, 0x6D, + 0xFF, 0xFF, 0xFF, 0x7E, 0x02, 0x0A, 0x07, 0x4D, 0x0C, 0x02, 0x10, + 0x55, 0x10, 0x1B, 0x0D, 0xF1, 0x07, 0x23, 0x03, 0x44, 0x00, 0x6D, + 0xFF, 0xA9, 0xFF, 0xF6, 0xFF, 0xED, 0xFF, 0x97, 0xFF, 0x77, 0xFF, + 0xA0, 0x00, 0xE7, 0x03, 0xED, 0x08, 0xE9, 0x0D, 0x90, 0x10, 0x8C, + 0x0F, 0x62, 0x0B, 0x17, 0x06, 0xDD, 0x01, 0xC6, 0xFF, 0x73, 0xFF, + 0xCB, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD5, 0xFF, 0x79, 0xFF, 0xAB, + 0xFF, 0x87, 0x01, 0x8C, 0x05, 0xD1, 0x0A, 0x38, 0x0F, 0xA3, 0x10, + 0x59, 0x0E, 0x84, 0x09, 0x63, 0x04, 0xDF, 0x00, 0x82, 0xFF, 0x8D, + 0xFF, 0xE7, 0xFF, 0xF9, 0xFF, 0xB4, 0xFF, 0x6C, 0xFF, 0x16, 0x00, + 0xB7, 0x02, 0x5C, 0x07, 0x99, 0x0C, 0x23, 0x10, 0x3B, 0x10, 0xD3, + 0x0C, 0x9D, 0x07, 0xE6, 0x02, 0x29, 0x00, 0x6C, 0xFF, 0xB0, 0xFF, + 0xF8, 0xFF, 0xEA, 0xFF, 0x92, 0xFF, 0x7D, 0xFF, 0xC2, 0x00, 0x2C, + 0x04, 0x42, 0x09, 0x29, 0x0E, 0x9C, 0x10, 0x5E, 0x0F, 0x11, 0x0B, + 0xC8, 0x05, 0xAC, 0x01, 0xB6, 0xFF, 0x76, 0xFF, 0xD1, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0x76, 0xFF, 0xB9, 0xFF, 0xB7, 0x01, + 0xDA, 0x05, 0x23, 0x0B, 0x68, 0x0F, 0x9A, 0x10, 0x1B, 0x0E, 0x2F, + 0x09, 0x1C, 0x04, 0xBB, 0x00, 0x7B, 0xFF, 0x93, 0xFF, 0xEA, 0xFF, + 0xF7, 0xFF, 0xAE, 0xFF, 0x6C, 0xFF, 0x2F, 0x00, 0xF3, 0x02, 0xAF, + 0x07, 0xE3, 0x0C, 0x41, 0x10, 0x1C, 0x10, 0x88, 0x0C, 0x4A, 0x07, + 0xAA, 0x02, 0x11, 0x00, 0x6C, 0xFF, 0xB6, 0xFF, 0xFA, 0xFF, 0xE6, + 0xFF, 0x8C, 0xFF, 0x84, 0xFF, 0xE7, 0x00, 0x73, 0x04, 0x96, 0x09, + 0x67, 0x0E, 0xA5, 0x10, 0x2D, 0x0F, 0xBF, 0x0A, 0x7B, 0x05, 0x7D, + 0x01, 0xA8, 0xFF, 0x7A, 0xFF, 0xD6, 0xFF, 0x00, 0x00, 0xFE, 0xFF, + 0xCA, 0xFF, 0x72, 0xFF, 0xC9, 0xFF, 0xE8, 0x01, 0x29, 0x06, 0x73, + 0x0B, 0x96, 0x0F, 0x8D, 0x10, 0xDB, 0x0D, 0xDB, 0x08, 0xD8, 0x03, + 0x98, 0x00, 0x76, 0xFF, 0x99, 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xA8, + 0xFF, 0x6E, 0xFF, 0x4A, 0x00, 0x31, 0x03, 0x03, 0x08, 0x2B, 0x0D, + 0x5B, 0x10, 0xFB, 0x0F, 0x3C, 0x0C, 0xF7, 0x06, 0x71, 0x02, 0xFA, + 0xFF, 0x6D, 0xFF, 0xBC, 0xFF, 0xFC, 0xFF, 0xE2, 0xFF, 0x87, 0xFF, + 0x8C, 0xFF, 0x0F, 0x01, 0xBB, 0x04, 0xEA, 0x09, 0xA2, 0x0E, 0xA9, + 0x10, 0xF9, 0x0E, 0x6C, 0x0A, 0x2F, 0x05, 0x50, 0x01, 0x9C, 0xFF, + 0x7F, 0xFF, 0xDB, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC5, 0xFF, 0x70, + 0xFF, 0xDB, 0xFF, 0x1C, 0x02, 0x79, 0x06, 0xC3, 0x0B, 0xC0, 0x0F, + 0x7C, 0x10, 0x98, 0x0D, 0x86, 0x08, 0x95, 0x03, 0x78, 0x00, 0x72, + 0xFF, 0x9F, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xA2, 0xFF, 0x70, 0xFF, + 0x67, 0x00, 0x71, 0x03, 0x57, 0x08, 0x71, 0x0D, 0x71, 0x10, 0xD6, + 0x0F, 0xEF, 0x0B, 0xA6, 0x06, 0x3A, 0x02, 0xE6, 0xFF, 0x6E, 0xFF, + 0xC1, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDD, 0xFF, 0x81, 0xFF, 0x96, + 0xFF, 0x38, 0x01, 0x05, 0x05, 0x3E, 0x0A, 0xDA, 0x0E, 0xAA, 0x10, + 0xC2, 0x0E, 0x19, 0x0A, 0xE4, 0x04, 0x25, 0x01, 0x91, 0xFF, 0x84, + 0xFF, 0xDF, 0xFF, 0xFC, 0xFF, 0xBF, 0xFF, 0x6E, 0xFF, 0xEF, 0xFF, + 0x52, 0x02, 0xCA, 0x06, 0x11, 0x0C, 0xE6, 0x0F, 0x68, 0x10, 0x52, + 0x0D, 0x32, 0x08, 0x54, 0x03, 0x5A, 0x00, 0x6F, 0xFF, 0xA5, 0xFF, + 0xF4, 0xFF, 0xF0, 0xFF, 0x9C, 0xFF, 0x74, 0xFF, 0x86, 0x00, 0xB3, + 0x03, 0xAC, 0x08, 0xB6, 0x0D, 0x84, 0x10, 0xAD, 0x0F, 0xA0, 0x0B, + 0x55, 0x06, 0x05, 0x02, 0xD3, 0xFF, 0x71, 0xFF, 0xC7, 0xFF, 0xFE, + 0xFF, 0x00, 0x00, 0xD9, 0xFF, 0x7D, 0xFF, 0xA1, 0xFF, 0x64, 0x01, + 0x50, 0x05, 0x91, 0x0A, 0x10, 0x0F, 0xA8, 0x10, 0x88, 0x0E, 0xC5, + 0x09, 0x9B, 0x04, 0xFD, 0x00, 0x88, 0xFF, 0x89, 0xFF, 0xE4, 0xFF, + 0xFB, 0xFF, 0xB9, 0xFF, 0x6C, 0xFF, 0x04, 0x00, 0x8A, 0x02, 0x1C, + 0x07, 0x5E, 0x0C, 0x0A, 0x10, 0x50, 0x10, 0x0B, 0x0D, 0xDE, 0x07, + 0x15, 0x03, 0x3E, 0x00, 0x6D, 0xFF, 0xAB, 0xFF, 0xF6, 0xFF, 0xEC, + 0xFF, 0x96, 0xFF, 0x78, 0xFF, 0xA7, 0x00, 0xF6, 0x03, 0x00, 0x09, + 0xF8, 0x0D, 0x93, 0x10, 0x82, 0x0F, 0x50, 0x0B, 0x06, 0x06, 0xD2, + 0x01, 0xC2, 0xFF, 0x74, 0xFF, 0xCD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xD4, 0xFF, 0x79, 0xFF, 0xAE, 0xFF, 0x91, 0x01, 0x9D, 0x05, 0xE3, + 0x0A, 0x43, 0x0F, 0xA1, 0x10, 0x4C, 0x0E, 0x71, 0x09, 0x53, 0x04, + 0xD7, 0x00, 0x80, 0xFF, 0x8E, 0xFF, 0xE8, 0xFF, 0xF9, 0xFF, 0xB3, + 0xFF, 0x6C, 0xFF, 0x1C, 0x00, 0xC4, 0x02, 0x6F, 0x07, 0xAA, 0x0C, + 0x2A, 0x10, 0x34, 0x10, 0xC2, 0x0C, 0x8A, 0x07, 0xD8, 0x02, 0x24, + 0x00, 0x6C, 0xFF, 0xB1, 0xFF, 0xF8, 0xFF, 0xE9, 0xFF, 0x90, 0xFF, + 0x7E, 0xFF, 0xCB, 0x00, 0x3B, 0x04, 0x55, 0x09, 0x37, 0x0E, 0x9E, + 0x10, 0x53, 0x0F, 0xFF, 0x0A, 0xB7, 0x05, 0xA1, 0x01, 0xB3, 0xFF, + 0x77, 0xFF, 0xD2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCE, 0xFF, 0x75, + 0xFF, 0xBD, 0xFF, 0xC1, 0x01, 0xEB, 0x05, 0x35, 0x0B, 0x73, 0x0F, + 0x97, 0x10, 0x0D, 0x0E, 0x1C, 0x09, 0x0D, 0x04, 0xB3, 0x00, 0x7A, + 0xFF, 0x94, 0xFF, 0xEB, 0xFF, 0xF7, 0xFF, 0xAD, 0xFF, 0x6D, 0xFF, + 0x35, 0x00, 0x01, 0x03, 0xC2, 0x07, 0xF3, 0x0C, 0x47, 0x10, 0x15, + 0x10, 0x78, 0x0C, 0x37, 0x07, 0x9D, 0x02, 0x0C, 0x00, 0x6C, 0xFF, + 0xB7, 0xFF, 0xFA, 0xFF, 0xE5, 0xFF, 0x8B, 0xFF, 0x85, 0xFF, 0xF0, + 0x00, 0x83, 0x04, 0xA9, 0x09, 0x74, 0x0E, 0xA6, 0x10, 0x21, 0x0F, + 0xAD, 0x0A, 0x6A, 0x05, 0x73, 0x01, 0xA5, 0xFF, 0x7B, 0xFF, 0xD7, + 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC9, 0xFF, 0x72, 0xFF, 0xCD, 0xFF, + 0xF4, 0x01, 0x3B, 0x06, 0x85, 0x0B, 0x9F, 0x0F, 0x89, 0x10, 0xCC, + 0x0D, 0xC8, 0x08, 0xC9, 0x03, 0x91, 0x00, 0x75, 0xFF, 0x9A, 0xFF, + 0xEF, 0xFF, 0xF5, 0xFF, 0xA7, 0xFF, 0x6E, 0xFF, 0x50, 0x00, 0x3F, + 0x03, 0x16, 0x08, 0x3B, 0x0D, 0x60, 0x10, 0xF3, 0x0F, 0x2B, 0x0C, + 0xE5, 0x06, 0x65, 0x02, 0xF6, 0xFF, 0x6D, 0xFF, 0xBD, 0xFF, 0xFC, + 0xFF, 0xE1, 0xFF, 0x85, 0xFF, 0x8E, 0xFF, 0x18, 0x01, 0xCB, 0x04, + 0xFD, 0x09, 0xAF, 0x0E, 0xAA, 0x10, 0xED, 0x0E, 0x5A, 0x0A, 0x1E, + 0x05, 0x46, 0x01, 0x9A, 0xFF, 0x80, 0xFF, 0xDC, 0xFF, 0x00, 0x00, + 0xFD, 0xFF, 0xC3, 0xFF, 0x6F, 0xFF, 0xDF, 0xFF, 0x28, 0x02, 0x8B, + 0x06, 0xD5, 0x0B, 0xC9, 0x0F, 0x78, 0x10, 0x88, 0x0D, 0x73, 0x08, + 0x86, 0x03, 0x71, 0x00, 0x71, 0xFF, 0xA0, 0xFF, 0xF2, 0xFF, 0xF2, + 0xFF, 0xA1, 0xFF, 0x71, 0xFF, 0x6E, 0x00, 0x7F, 0x03, 0x6A, 0x08, + 0x81, 0x0D, 0x76, 0x10, 0xCD, 0x0F, 0xDD, 0x0B, 0x94, 0x06, 0x2E, + 0x02, 0xE1, 0xFF, 0x6F, 0xFF, 0xC3, 0xFF, 0xFD, 0xFF, 0x00, 0x00, + 0xDC, 0xFF, 0x80, 0xFF, 0x98, 0xFF, 0x42, 0x01, 0x16, 0x05, 0x50, + 0x0A, 0xE7, 0x0E, 0xAA, 0x10, 0xB5, 0x0E, 0x06, 0x0A, 0xD3, 0x04, + 0x1C, 0x01, 0x8F, 0xFF, 0x85, 0xFF, 0xE0, 0xFF, 0xFC, 0xFF, 0xBE, + 0xFF, 0x6D, 0xFF, 0xF3, 0xFF, 0x5E, 0x02, 0xDC, 0x06, 0x23, 0x0C, + 0xEF, 0x0F, 0x63, 0x10, 0x43, 0x0D, 0x1F, 0x08, 0x46, 0x03, 0x53, + 0x00, 0x6E, 0xFF, 0xA6, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0x9B, 0xFF, + 0x75, 0xFF, 0x8D, 0x00, 0xC1, 0x03, 0xBE, 0x08, 0xC4, 0x0D, 0x88, + 0x10, 0xA4, 0x0F, 0x8E, 0x0B, 0x43, 0x06, 0xF9, 0x01, 0xCF, 0xFF, + 0x71, 0xFF, 0xC8, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD8, 0xFF, 0x7C, + 0xFF, 0xA4, 0xFF, 0x6E, 0x01, 0x61, 0x05, 0xA3, 0x0A, 0x1C, 0x0F, + 0xA7, 0x10, 0x7B, 0x0E, 0xB2, 0x09, 0x8B, 0x04, 0xF4, 0x00, 0x86, + 0xFF, 0x8A, 0xFF, 0xE4, 0xFF, 0xFA, 0xFF, 0xB8, 0xFF, 0x6C, 0xFF, + 0x09, 0x00, 0x97, 0x02, 0x2E, 0x07, 0x6F, 0x0C, 0x11, 0x10, 0x4A, + 0x10, 0xFB, 0x0C, 0xCB, 0x07, 0x07, 0x03, 0x38, 0x00, 0x6D, 0xFF, + 0xAC, 0xFF, 0xF7, 0xFF, 0xEC, 0xFF, 0x95, 0xFF, 0x79, 0xFF, 0xAF, + 0x00, 0x05, 0x04, 0x13, 0x09, 0x06, 0x0E, 0x96, 0x10, 0x78, 0x0F, + 0x3E, 0x0B, 0xF4, 0x05, 0xC7, 0x01, 0xBF, 0xFF, 0x74, 0xFF, 0xCE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0xFF, 0x78, 0xFF, 0xB1, 0xFF, + 0x9C, 0x01, 0xAE, 0x05, 0xF6, 0x0A, 0x4E, 0x0F, 0x9F, 0x10, 0x3E, + 0x0E, 0x5E, 0x09, 0x43, 0x04, 0xCF, 0x00, 0x7F, 0xFF, 0x90, 0xFF, + 0xE8, 0xFF, 0xF9, 0xFF, 0xB2, 0xFF, 0x6C, 0xFF, 0x21, 0x00, 0xD2, + 0x02, 0x81, 0x07, 0xBA, 0x0C, 0x31, 0x10, 0x2E, 0x10, 0xB2, 0x0C, + 0x78, 0x07, 0xCB, 0x02, 0x1E, 0x00, 0x6C, 0xFF, 0xB2, 0xFF, 0xF9, + 0xFF, 0xE8, 0xFF, 0x8F, 0xFF, 0x80, 0xFF, 0xD3, 0x00, 0x4B, 0x04, + 0x67, 0x09, 0x45, 0x0E, 0xA0, 0x10, 0x48, 0x0F, 0xEC, 0x0A, 0xA6, + 0x05, 0x97, 0x01, 0xB0, 0xFF, 0x78, 0xFF, 0xD3, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xCD, 0xFF, 0x74, 0xFF, 0xC0, 0xFF, 0xCC, 0x01, 0xFD, + 0x05, 0x47, 0x0B, 0x7D, 0x0F, 0x94, 0x10, 0xFF, 0x0D, 0x0A, 0x09, + 0xFE, 0x03, 0xAB, 0x00, 0x79, 0xFF, 0x95, 0xFF, 0xEC, 0xFF, 0xF7, + 0xFF, 0xAC, 0xFF, 0x6D, 0xFF, 0x3B, 0x00, 0x0E, 0x03, 0xD5, 0x07, + 0x03, 0x0D, 0x4D, 0x10, 0x0E, 0x10, 0x67, 0x0C, 0x25, 0x07, 0x91, + 0x02, 0x07, 0x00, 0x6C, 0xFF, 0xB8, 0xFF, 0xFB, 0xFF, 0xE4, 0xFF, + 0x89, 0xFF, 0x87, 0xFF, 0xF9, 0x00, 0x93, 0x04, 0xBC, 0x09, 0x82, + 0x0E, 0xA7, 0x10, 0x16, 0x0F, 0x9A, 0x0A, 0x59, 0x05, 0x69, 0x01, + 0xA3, 0xFF, 0x7C, 0xFF, 0xD8, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC8, + 0xFF, 0x71, 0xFF, 0xD1, 0xFF, 0xFF, 0x01, 0x4C, 0x06, 0x97, 0x0B, + 0xA9, 0x0F, 0x86, 0x10, 0xBD, 0x0D, 0xB5, 0x08, 0xBA, 0x03, 0x8A, + 0x00, 0x74, 0xFF, 0x9B, 0xFF, 0xEF, 0xFF, 0xF4, 0xFF, 0xA5, 0xFF, + 0x6F, 0xFF, 0x57, 0x00, 0x4D, 0x03, 0x29, 0x08, 0x4B, 0x0D, 0x65, + 0x10, 0xEB, 0x0F, 0x1A, 0x0C, 0xD3, 0x06, 0x58, 0x02, 0xF1, 0xFF, + 0x6D, 0xFF, 0xBE, 0xFF, 0xFC, 0xFF, 0xE0, 0xFF, 0x84, 0xFF, 0x90, + 0xFF, 0x21, 0x01, 0xDC, 0x04, 0x10, 0x0A, 0xBB, 0x0E, 0xAA, 0x10, + 0xE1, 0x0E, 0x47, 0x0A, 0x0D, 0x05, 0x3D, 0x01, 0x97, 0xFF, 0x81, + 0xFF, 0xDD, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC2, 0xFF, 0x6F, 0xFF, + 0xE4, 0xFF, 0x34, 0x02, 0x9D, 0x06, 0xE6, 0x0B, 0xD1, 0x0F, 0x73, + 0x10, 0x79, 0x0D, 0x61, 0x08, 0x78, 0x03, 0x6A, 0x00, 0x70, 0xFF, + 0xA1, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0x9F, 0xFF, 0x72, 0xFF, 0x74, + 0x00, 0x8E, 0x03, 0x7D, 0x08, 0x90, 0x0D, 0x7A, 0x10, 0xC4, 0x0F, + 0xCC, 0x0B, 0x82, 0x06, 0x22, 0x02, 0xDD, 0xFF, 0x6F, 0xFF, 0xC4, + 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDB, 0xFF, 0x7F, 0xFF, 0x9B, 0xFF, + 0x4B, 0x01, 0x26, 0x05, 0x63, 0x0A, 0xF3, 0x0E, 0xAA, 0x10, 0xA8, + 0x0E, 0xF4, 0x09, 0xC3, 0x04, 0x13, 0x01, 0x8D, 0xFF, 0x86, 0xFF, + 0xE1, 0xFF, 0xFC, 0xFF, 0xBC, 0xFF, 0x6D, 0xFF, 0xF8, 0xFF, 0x6B, + 0x02, 0xEE, 0x06, 0x34, 0x0C, 0xF7, 0x0F, 0x5D, 0x10, 0x33, 0x0D, + 0x0D, 0x08, 0x38, 0x03, 0x4D, 0x00, 0x6E, 0xFF, 0xA7, 0xFF, 0xF5, + 0xFF, 0xEE, 0xFF, 0x99, 0xFF, 0x76, 0xFF, 0x94, 0x00, 0xD0, 0x03, + 0xD1, 0x08, 0xD3, 0x0D, 0x8B, 0x10, 0x9A, 0x0F, 0x7C, 0x0B, 0x32, + 0x06, 0xEE, 0x01, 0xCB, 0xFF, 0x72, 0xFF, 0xCA, 0xFF, 0xFE, 0xFF, + 0x00, 0x00, 0xD6, 0xFF, 0x7B, 0xFF, 0xA7, 0xFF, 0x78, 0x01, 0x72, + 0x05, 0xB6, 0x0A, 0x27, 0x0F, 0xA5, 0x10, 0x6E, 0x0E, 0xA0, 0x09, + 0x7B, 0x04, 0xEC, 0x00, 0x85, 0xFF, 0x8B, 0xFF, 0xE5, 0xFF, 0xFA, + 0xFF, 0xB6, 0xFF, 0x6C, 0xFF, 0x0E, 0x00, 0xA4, 0x02, 0x41, 0x07, + 0x80, 0x0C, 0x19, 0x10, 0x44, 0x10, 0xEB, 0x0C, 0xB9, 0x07, 0xFA, + 0x02, 0x32, 0x00, 0x6D, 0xFF, 0xAE, 0xFF, 0xF7, 0xFF, 0xEB, 0xFF, + 0x93, 0xFF, 0x7B, 0xFF, 0xB7, 0x00, 0x15, 0x04, 0x26, 0x09, 0x14, + 0x0E, 0x98, 0x10, 0x6D, 0x0F, 0x2C, 0x0B, 0xE3, 0x05, 0xBC, 0x01, + 0xBB, 0xFF, 0x75, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, + 0xFF, 0x77, 0xFF, 0xB5, 0xFF, 0xA6, 0x01, 0xC0, 0x05, 0x08, 0x0B, + 0x58, 0x0F, 0x9D, 0x10, 0x30, 0x0E, 0x4B, 0x09, 0x34, 0x04, 0xC6, + 0x00, 0x7D, 0xFF, 0x91, 0xFF, 0xE9, 0xFF, 0xF8, 0xFF, 0xB0, 0xFF, + 0x6C, 0xFF, 0x27, 0x00, 0xDF, 0x02, 0x94, 0x07, 0xCA, 0x0C, 0x37, + 0x10, 0x27, 0x10, 0xA1, 0x0C, 0x65, 0x07, 0xBE, 0x02, 0x19, 0x00, + 0x6C, 0xFF, 0xB4, 0xFF, 0xF9, 0xFF, 0xE7, 0xFF, 0x8E, 0xFF, 0x81, + 0xFF, 0xDB, 0x00, 0x5B, 0x04, 0x7A, 0x09, 0x53, 0x0E, 0xA2, 0x10, + 0x3D, 0x0F, 0xDA, 0x0A, 0x95, 0x05, 0x8C, 0x01, 0xAD, 0xFF, 0x79, + 0xFF, 0xD4, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xCC, 0xFF, 0x73, 0xFF, + 0xC4, 0xFF, 0xD7, 0x01, 0x0E, 0x06, 0x59, 0x0B, 0x87, 0x0F, 0x91, + 0x10, 0xF0, 0x0D, 0xF7, 0x08, 0xEF, 0x03, 0xA3, 0x00, 0x78, 0xFF, + 0x97, 0xFF, 0xED, 0xFF, 0xF6, 0xFF, 0xAA, 0xFF, 0x6D, 0xFF, 0x41, + 0x00, 0x1C, 0x03, 0xE7, 0x07, 0x13, 0x0D, 0x52, 0x10, 0x06, 0x10, + 0x56, 0x0C, 0x13, 0x07, 0x84, 0x02, 0x02, 0x00, 0x6D, 0xFF, 0xBA, + 0xFF, 0xFB, 0xFF, 0xE3, 0xFF, 0x88, 0xFF, 0x89, 0xFF, 0x01, 0x01, + 0xA3, 0x04, 0xCE, 0x09, 0x8F, 0x0E, 0xA8, 0x10, 0x0A, 0x0F, 0x88, + 0x0A, 0x48, 0x05, 0x5F, 0x01, 0xA0, 0xFF, 0x7D, 0xFF, 0xD9, 0xFF, + 0x00, 0x00, 0xFE, 0xFF, 0xC7, 0xFF, 0x70, 0xFF, 0xD5, 0xFF, 0x0B, + 0x02, 0x5E, 0x06, 0xA9, 0x0B, 0xB2, 0x0F, 0x82, 0x10, 0xAE, 0x0D, + 0xA2, 0x08, 0xAB, 0x03, 0x82, 0x00, 0x73, 0xFF, 0x9D, 0xFF, 0xF0, + 0xFF, 0xF3, 0xFF, 0xA4, 0xFF, 0x6F, 0xFF, 0x5D, 0x00, 0x5B, 0x03, + 0x3B, 0x08, 0x5A, 0x0D, 0x6A, 0x10, 0xE2, 0x0F, 0x09, 0x0C, 0xC1, + 0x06, 0x4C, 0x02, 0xEC, 0xFF, 0x6E, 0xFF, 0xC0, 0xFF, 0xFC, 0xFF, + 0xDF, 0xFF, 0x83, 0xFF, 0x93, 0xFF, 0x2A, 0x01, 0xEC, 0x04, 0x22, + 0x0A, 0xC8, 0x0E, 0xAB, 0x10, 0xD4, 0x0E, 0x35, 0x0A, 0xFD, 0x04, + 0x33, 0x01, 0x95, 0xFF, 0x82, 0xFF, 0xDE, 0xFF, 0x00, 0x00, 0xFD, + 0xFF, 0xC1, 0xFF, 0x6E, 0xFF, 0xE8, 0xFF, 0x40, 0x02, 0xAF, 0x06, + 0xF7, 0x0B, 0xDA, 0x0F, 0x6F, 0x10, 0x6A, 0x0D, 0x4E, 0x08, 0x6A, + 0x03, 0x64, 0x00, 0x70, 0xFF, 0xA3, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, + 0x9E, 0xFF, 0x72, 0xFF, 0x7B, 0x00, 0x9C, 0x03, 0x90, 0x08, 0x9F, + 0x0D, 0x7E, 0x10, 0xBB, 0x0F, 0xBA, 0x0B, 0x70, 0x06, 0x16, 0x02, + 0xD9, 0xFF, 0x70, 0xFF, 0xC5, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xDA, + 0xFF, 0x7E, 0xFF, 0x9D, 0xFF, 0x55, 0x01, 0x37, 0x05, 0x75, 0x0A, + 0xFF, 0x0E, 0xA9, 0x10, 0x9C, 0x0E, 0xE1, 0x09, 0xB3, 0x04, 0x0A, + 0x01, 0x8B, 0xFF, 0x87, 0xFF, 0xE2, 0xFF, 0xFB, 0xFF, 0xBB, 0xFF, + 0x6D, 0xFF, 0xFD, 0xFF, 0x77, 0x02, 0x01, 0x07, 0x45, 0x0C, 0xFF, + 0x0F, 0x58, 0x10, 0x23, 0x0D, 0xFA, 0x07, 0x2A, 0x03, 0x47, 0x00, + 0x6E, 0xFF, 0xA9, 0xFF, 0xF5, 0xFF, 0xED, 0xFF, 0x98, 0xFF, 0x77, + 0xFF, 0x9C, 0x00, 0xDF, 0x03, 0xE4, 0x08, 0xE2, 0x0D, 0x8E, 0x10, + 0x91, 0x0F, 0x6B, 0x0B, 0x20, 0x06, 0xE3, 0x01, 0xC8, 0xFF, 0x73, + 0xFF, 0xCB, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD5, 0xFF, 0x7A, 0xFF, + 0xAA, 0xFF, 0x82, 0x01, 0x83, 0x05, 0xC8, 0x0A, 0x32, 0x0F, 0xA4, + 0x10, 0x60, 0x0E, 0x8D, 0x09, 0x6B, 0x04, 0xE3, 0x00, 0x83, 0xFF, + 0x8D, 0xFF, 0xE6, 0xFF, 0xFA, 0xFF, 0xB5, 0xFF, 0x6C, 0xFF, 0x14, + 0x00, 0xB1, 0x02, 0x53, 0x07, 0x91, 0x0C, 0x20, 0x10, 0x3E, 0x10, + 0xDB, 0x0C, 0xA6, 0x07, 0xEC, 0x02, 0x2C, 0x00, 0x6C, 0xFF, 0xAF, + 0xFF, 0xF8, 0xFF, 0xEA, 0xFF, 0x92, 0xFF, 0x7C, 0xFF, 0xBE, 0x00, + 0x24, 0x04, 0x38, 0x09, 0x22, 0x0E, 0x9B, 0x10, 0x63, 0x0F, 0x1A, + 0x0B, 0xD1, 0x05, 0xB1, 0x01, 0xB8, 0xFF, 0x76, 0xFF, 0xD0, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0x76, 0xFF, 0xB8, 0xFF, 0xB1, + 0x01, 0xD1, 0x05, 0x1A, 0x0B, 0x63, 0x0F, 0x9B, 0x10, 0x22, 0x0E, + 0x38, 0x09, 0x24, 0x04, 0xBE, 0x00, 0x7C, 0xFF, 0x92, 0xFF, 0xEA, + 0xFF, 0xF8, 0xFF, 0xAF, 0xFF, 0x6C, 0xFF, 0x2C, 0x00, 0xEC, 0x02, + 0xA6, 0x07, 0xDB, 0x0C, 0x3E, 0x10, 0x20, 0x10, 0x91, 0x0C, 0x53, + 0x07, 0xB1, 0x02, 0x14, 0x00, 0x6C, 0xFF, 0xB5, 0xFF, 0xFA, 0xFF, + 0xE6, 0xFF, 0x8D, 0xFF, 0x83, 0xFF, 0xE3, 0x00, 0x6B, 0x04, 0x8D, + 0x09, 0x60, 0x0E, 0xA4, 0x10, 0x32, 0x0F, 0xC8, 0x0A, 0x83, 0x05, + 0x82, 0x01, 0xAA, 0xFF, 0x7A, 0xFF, 0xD5, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0xCB, 0xFF, 0x73, 0xFF, 0xC8, 0xFF, 0xE3, 0x01, 0x20, 0x06, + 0x6B, 0x0B, 0x91, 0x0F, 0x8E, 0x10, 0xE2, 0x0D, 0xE4, 0x08, 0xDF, + 0x03, 0x9C, 0x00, 0x77, 0xFF, 0x98, 0xFF, 0xED, 0xFF, 0xF5, 0xFF, + 0xA9, 0xFF, 0x6E, 0xFF, 0x47, 0x00, 0x2A, 0x03, 0xFA, 0x07, 0x23, + 0x0D, 0x58, 0x10, 0xFF, 0x0F, 0x45, 0x0C, 0x01, 0x07, 0x77, 0x02, + 0xFD, 0xFF, 0x6D, 0xFF, 0xBB, 0xFF, 0xFB, 0xFF, 0xE2, 0xFF, 0x87, + 0xFF, 0x8B, 0xFF, 0x0A, 0x01, 0xB3, 0x04, 0xE1, 0x09, 0x9C, 0x0E, + 0xA9, 0x10, 0xFF, 0x0E, 0x75, 0x0A, 0x37, 0x05, 0x55, 0x01, 0x9D, + 0xFF, 0x7E, 0xFF, 0xDA, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC5, 0xFF, + 0x70, 0xFF, 0xD9, 0xFF, 0x16, 0x02, 0x70, 0x06, 0xBA, 0x0B, 0xBB, + 0x0F, 0x7E, 0x10, 0x9F, 0x0D, 0x90, 0x08, 0x9C, 0x03, 0x7B, 0x00, + 0x72, 0xFF, 0x9E, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xA3, 0xFF, 0x70, + 0xFF, 0x64, 0x00, 0x6A, 0x03, 0x4E, 0x08, 0x6A, 0x0D, 0x6F, 0x10, + 0xDA, 0x0F, 0xF7, 0x0B, 0xAF, 0x06, 0x40, 0x02, 0xE8, 0xFF, 0x6E, + 0xFF, 0xC1, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDE, 0xFF, 0x82, 0xFF, + 0x95, 0xFF, 0x33, 0x01, 0xFD, 0x04, 0x35, 0x0A, 0xD4, 0x0E, 0xAB, + 0x10, 0xC8, 0x0E, 0x22, 0x0A, 0xEC, 0x04, 0x2A, 0x01, 0x93, 0xFF, + 0x83, 0xFF, 0xDF, 0xFF, 0xFC, 0xFF, 0xC0, 0xFF, 0x6E, 0xFF, 0xEC, + 0xFF, 0x4C, 0x02, 0xC1, 0x06, 0x09, 0x0C, 0xE2, 0x0F, 0x6A, 0x10, + 0x5A, 0x0D, 0x3B, 0x08, 0x5B, 0x03, 0x5D, 0x00, 0x6F, 0xFF, 0xA4, + 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0x9D, 0xFF, 0x73, 0xFF, 0x82, 0x00, + 0xAB, 0x03, 0xA2, 0x08, 0xAE, 0x0D, 0x82, 0x10, 0xB2, 0x0F, 0xA9, + 0x0B, 0x5E, 0x06, 0x0B, 0x02, 0xD5, 0xFF, 0x70, 0xFF, 0xC7, 0xFF, + 0xFE, 0xFF, 0x00, 0x00, 0xD9, 0xFF, 0x7D, 0xFF, 0xA0, 0xFF, 0x5F, + 0x01, 0x48, 0x05, 0x88, 0x0A, 0x0A, 0x0F, 0xA8, 0x10, 0x8F, 0x0E, + 0xCE, 0x09, 0xA3, 0x04, 0x01, 0x01, 0x89, 0xFF, 0x88, 0xFF, 0xE3, + 0xFF, 0xFB, 0xFF, 0xBA, 0xFF, 0x6D, 0xFF, 0x02, 0x00, 0x84, 0x02, + 0x13, 0x07, 0x56, 0x0C, 0x06, 0x10, 0x52, 0x10, 0x13, 0x0D, 0xE7, + 0x07, 0x1C, 0x03, 0x41, 0x00, 0x6D, 0xFF, 0xAA, 0xFF, 0xF6, 0xFF, + 0xED, 0xFF, 0x97, 0xFF, 0x78, 0xFF, 0xA3, 0x00, 0xEF, 0x03, 0xF7, + 0x08, 0xF0, 0x0D, 0x91, 0x10, 0x87, 0x0F, 0x59, 0x0B, 0x0E, 0x06, + 0xD7, 0x01, 0xC4, 0xFF, 0x73, 0xFF, 0xCC, 0xFF, 0xFF, 0xFF, 0x00, + 0x00, 0xD4, 0xFF, 0x79, 0xFF, 0xAD, 0xFF, 0x8C, 0x01, 0x95, 0x05, + 0xDA, 0x0A, 0x3D, 0x0F, 0xA2, 0x10, 0x53, 0x0E, 0x7A, 0x09, 0x5B, + 0x04, 0xDB, 0x00, 0x81, 0xFF, 0x8E, 0xFF, 0xE7, 0xFF, 0xF9, 0xFF, + 0xB4, 0xFF, 0x6C, 0xFF, 0x19, 0x00, 0xBE, 0x02, 0x65, 0x07, 0xA1, + 0x0C, 0x27, 0x10, 0x37, 0x10, 0xCA, 0x0C, 0x94, 0x07, 0xDF, 0x02, + 0x27, 0x00, 0x6C, 0xFF, 0xB0, 0xFF, 0xF8, 0xFF, 0xE9, 0xFF, 0x91, + 0xFF, 0x7D, 0xFF, 0xC6, 0x00, 0x34, 0x04, 0x4B, 0x09, 0x30, 0x0E, + 0x9D, 0x10, 0x58, 0x0F, 0x08, 0x0B, 0xC0, 0x05, 0xA6, 0x01, 0xB5, + 0xFF, 0x77, 0xFF, 0xD1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, + 0x75, 0xFF, 0xBB, 0xFF, 0xBC, 0x01, 0xE3, 0x05, 0x2C, 0x0B, 0x6D, + 0x0F, 0x98, 0x10, 0x14, 0x0E, 0x26, 0x09, 0x15, 0x04, 0xB7, 0x00, + 0x7B, 0xFF, 0x93, 0xFF, 0xEB, 0xFF, 0xF7, 0xFF, 0xAE, 0xFF, 0x6D, + 0xFF, 0x32, 0x00, 0xFA, 0x02, 0xB9, 0x07, 0xEB, 0x0C, 0x44, 0x10, + 0x19, 0x10, 0x80, 0x0C, 0x41, 0x07, 0xA4, 0x02, 0x0E, 0x00, 0x6C, + 0xFF, 0xB6, 0xFF, 0xFA, 0xFF, 0xE5, 0xFF, 0x8B, 0xFF, 0x85, 0xFF, + 0xEC, 0x00, 0x7B, 0x04, 0xA0, 0x09, 0x6E, 0x0E, 0xA5, 0x10, 0x27, + 0x0F, 0xB6, 0x0A, 0x72, 0x05, 0x78, 0x01, 0xA7, 0xFF, 0x7B, 0xFF, + 0xD6, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xCA, 0xFF, 0x72, 0xFF, 0xCB, + 0xFF, 0xEE, 0x01, 0x32, 0x06, 0x7C, 0x0B, 0x9A, 0x0F, 0x8B, 0x10, + 0xD3, 0x0D, 0xD1, 0x08, 0xD0, 0x03, 0x94, 0x00, 0x76, 0xFF, 0x99, + 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xA7, 0xFF, 0x6E, 0xFF, 0x4D, 0x00, + 0x38, 0x03, 0x0D, 0x08, 0x33, 0x0D, 0x5D, 0x10, 0xF7, 0x0F, 0x34, + 0x0C, 0xEE, 0x06, 0x6B, 0x02, 0xF8, 0xFF, 0x6D, 0xFF, 0xBC, 0xFF, + 0xFC, 0xFF, 0xE1, 0xFF, 0x86, 0xFF, 0x8D, 0xFF, 0x13, 0x01, 0xC3, + 0x04, 0xF4, 0x09, 0xA8, 0x0E, 0xAA, 0x10, 0xF3, 0x0E, 0x63, 0x0A, + 0x26, 0x05, 0x4B, 0x01, 0x9B, 0xFF, 0x7F, 0xFF, 0xDB, 0xFF, 0x00, + 0x00, 0xFD, 0xFF, 0xC4, 0xFF, 0x6F, 0xFF, 0xDD, 0xFF, 0x22, 0x02, + 0x82, 0x06, 0xCC, 0x0B, 0xC4, 0x0F, 0x7A, 0x10, 0x90, 0x0D, 0x7D, + 0x08, 0x8E, 0x03, 0x74, 0x00, 0x72, 0xFF, 0x9F, 0xFF, 0xF1, 0xFF, + 0xF2, 0xFF, 0xA1, 0xFF, 0x70, 0xFF, 0x6A, 0x00, 0x78, 0x03, 0x61, + 0x08, 0x79, 0x0D, 0x73, 0x10, 0xD1, 0x0F, 0xE6, 0x0B, 0x9D, 0x06, + 0x34, 0x02, 0xE4, 0xFF, 0x6F, 0xFF, 0xC2, 0xFF, 0xFD, 0xFF, 0x00, + 0x00, 0xDD, 0xFF, 0x81, 0xFF, 0x97, 0xFF, 0x3D, 0x01, 0x0D, 0x05, + 0x47, 0x0A, 0xE1, 0x0E, 0xAA, 0x10, 0xBB, 0x0E, 0x10, 0x0A, 0xDC, + 0x04, 0x21, 0x01, 0x90, 0xFF, 0x84, 0xFF, 0xE0, 0xFF, 0xFC, 0xFF, + 0xBE, 0xFF, 0x6D, 0xFF, 0xF1, 0xFF, 0x58, 0x02, 0xD3, 0x06, 0x1A, + 0x0C, 0xEB, 0x0F, 0x65, 0x10, 0x4B, 0x0D, 0x29, 0x08, 0x4D, 0x03, + 0x57, 0x00, 0x6F, 0xFF, 0xA5, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0x9B, + 0xFF, 0x74, 0xFF, 0x8A, 0x00, 0xBA, 0x03, 0xB5, 0x08, 0xBD, 0x0D, + 0x86, 0x10, 0xA9, 0x0F, 0x97, 0x0B, 0x4C, 0x06, 0xFF, 0x01, 0xD1, + 0xFF, 0x71, 0xFF, 0xC8, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD8, 0xFF, + 0x7C, 0xFF, 0xA3, 0xFF, 0x69, 0x01, 0x59, 0x05, 0x9A, 0x0A, 0x16, + 0x0F, 0xA7, 0x10, 0x82, 0x0E, 0xBC, 0x09, 0x93, 0x04, 0xF9, 0x00, + 0x87, 0xFF, 0x89, 0xFF, 0xE4, 0xFF, 0xFB, 0xFF, 0xB8, 0xFF, 0x6C, + 0xFF, 0x07, 0x00, 0x91, 0x02, 0x25, 0x07, 0x67, 0x0C, 0x0E, 0x10, + 0x4D, 0x10, 0x03, 0x0D, 0xD5, 0x07, 0x0E, 0x03, 0x3B, 0x00, 0x6D, + 0xFF, 0xAC, 0xFF, 0xF7, 0xFF, 0xEC, 0xFF, 0x95, 0xFF, 0x79, 0xFF, + 0xAB, 0x00, 0xFE, 0x03, 0x0A, 0x09, 0xFF, 0x0D, 0x94, 0x10, 0x7D, + 0x0F, 0x47, 0x0B, 0xFD, 0x05, 0xCC, 0x01, 0xC0, 0xFF, 0x74, 0xFF, + 0xCD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD3, 0xFF, 0x78, 0xFF, 0xB0, + 0xFF, 0x97, 0x01, 0xA6, 0x05, 0xEC, 0x0A, 0x48, 0x0F, 0xA0, 0x10, + 0x45, 0x0E, 0x67, 0x09, 0x4B, 0x04, 0xD3, 0x00, 0x80, 0xFF, 0x8F, + 0xFF, 0xE8, 0xFF, 0xF9, 0xFF, 0xB2, 0xFF, 0x6C, 0xFF, 0x1E, 0x00, + 0xCB, 0x02, 0x78, 0x07, 0xB2, 0x0C, 0x2E, 0x10, 0x31, 0x10, 0xBA, + 0x0C, 0x81, 0x07, 0xD2, 0x02, 0x21, 0x00, 0x6C, 0xFF, 0xB2, 0xFF, + 0xF9, 0xFF, 0xE8, 0xFF, 0x90, 0xFF, 0x7F, 0xFF, 0xCF, 0x00, 0x43, + 0x04, 0x5E, 0x09, 0x3E, 0x0E, 0x9F, 0x10, 0x4E, 0x0F, 0xF6, 0x0A, + 0xAE, 0x05, 0x9C, 0x01, 0xB1, 0xFF, 0x78, 0xFF, 0xD2, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xCE, 0xFF, 0x74, 0xFF, 0xBF, 0xFF, 0xC7, 0x01, + 0xF4, 0x05, 0x3E, 0x0B, 0x78, 0x0F, 0x96, 0x10, 0x06, 0x0E, 0x13, + 0x09, 0x05, 0x04, 0xAF, 0x00, 0x79, 0xFF, 0x95, 0xFF, 0xEC, 0xFF, + 0xF7, 0xFF, 0xAC, 0xFF, 0x6D, 0xFF, 0x38, 0x00, 0x07, 0x03, 0xCB, + 0x07, 0xFB, 0x0C, 0x4A, 0x10, 0x11, 0x10, 0x6F, 0x0C, 0x2E, 0x07, + 0x97, 0x02, 0x09, 0x00, 0x6C, 0xFF, 0xB8, 0xFF, 0xFA, 0xFF, 0xE4, + 0xFF, 0x8A, 0xFF, 0x86, 0xFF, 0xF4, 0x00, 0x8B, 0x04, 0xB2, 0x09, + 0x7B, 0x0E, 0xA7, 0x10, 0x1C, 0x0F, 0xA3, 0x0A, 0x61, 0x05, 0x6E, + 0x01, 0xA4, 0xFF, 0x7C, 0xFF, 0xD8, 0xFF, 0x00, 0x00, 0xFE, 0xFF, + 0xC8, 0xFF, 0x71, 0xFF, 0xCF, 0xFF, 0xF9, 0x01, 0x43, 0x06, 0x8E, + 0x0B, 0xA4, 0x0F, 0x88, 0x10, 0xC4, 0x0D, 0xBE, 0x08, 0xC1, 0x03, + 0x8D, 0x00, 0x75, 0xFF, 0x9B, 0xFF, 0xEF, 0xFF, 0xF4, 0xFF, 0xA6, + 0xFF, 0x6E, 0xFF, 0x53, 0x00, 0x46, 0x03, 0x1F, 0x08, 0x43, 0x0D, + 0x63, 0x10, 0xEF, 0x0F, 0x23, 0x0C, 0xDC, 0x06, 0x5E, 0x02, 0xF3, + 0xFF, 0x6D, 0xFF, 0xBE, 0xFF, 0xFC, 0xFF, 0xE0, 0xFF, 0x85, 0xFF, + 0x8F, 0xFF, 0x1C, 0x01, 0xD3, 0x04, 0x06, 0x0A, 0xB5, 0x0E, 0xAA, + 0x10, 0xE7, 0x0E, 0x50, 0x0A, 0x16, 0x05, 0x42, 0x01, 0x98, 0xFF, + 0x80, 0xFF, 0xDC, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC3, 0xFF, 0x6F, + 0xFF, 0xE1, 0xFF, 0x2E, 0x02, 0x94, 0x06, 0xDD, 0x0B, 0xCD, 0x0F, + 0x76, 0x10, 0x81, 0x0D, 0x6A, 0x08, 0x7F, 0x03, 0x6E, 0x00, 0x71, + 0xFF, 0xA1, 0xFF, 0xF2, 0xFF, 0x00, 0x00, 0x15, 0x00, 0xD1, 0xFF, + 0x8B, 0xFE, 0xBC, 0xFD, 0xE1, 0x00, 0x84, 0x09, 0xB0, 0x13, 0x47, + 0x18, 0xB0, 0x13, 0x84, 0x09, 0xE1, 0x00, 0xBC, 0xFD, 0x8B, 0xFE, + 0xD1, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xDA, 0x00, 0x30, + 0x00, 0x5D, 0xFC, 0xB3, 0xFC, 0x35, 0x0A, 0xC2, 0x1C, 0x24, 0x20, + 0x48, 0x10, 0x5D, 0xFF, 0x74, 0xFB, 0x3A, 0xFF, 0xFB, 0x00, 0x42, + 0x00, 0xF8, 0xFF, 0xFA, 0xFF, 0x2C, 0x00, 0xF3, 0x00, 0xAD, 0xFF, + 0xC5, 0xFB, 0x11, 0xFE, 0xAF, 0x0D, 0xEF, 0x1E, 0x68, 0x1E, 0xBC, + 0x0C, 0xA7, 0xFD, 0xEA, 0xFB, 0xD3, 0xFF, 0xEE, 0x00, 0x24, 0x00, + 0xFA, 0xFF, 0xF7, 0xFF, 0x4C, 0x00, 0xFB, 0x00, 0x0C, 0xFF, 0x5F, + 0xFB, 0xE8, 0xFF, 0x3D, 0x11, 0x7E, 0x20, 0x13, 0x1C, 0x4C, 0x09, + 0x6A, 0xFC, 0x8C, 0xFC, 0x4E, 0x00, 0xD1, 0x00, 0x0E, 0x00, 0xFD, + 0xFF, 0xF7, 0xFF, 0x72, 0x00, 0xEC, 0x00, 0x55, 0xFE, 0x3D, 0xFB, + 0x37, 0x02, 0xBE, 0x14, 0x5D, 0x21, 0x40, 0x19, 0x18, 0x06, 0xA2, + 0xFB, 0x47, 0xFD, 0xA7, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0xFF, 0x9B, 0x00, 0xC0, 0x00, 0x92, 0xFD, 0x73, + 0xFB, 0xF2, 0x04, 0x0E, 0x18, 0x81, 0x21, 0x0C, 0x16, 0x37, 0x03, + 0x47, 0xFB, 0x0B, 0xFE, 0xDF, 0x00, 0x82, 0x00, 0xF9, 0xFF, 0xFE, + 0xFF, 0x08, 0x00, 0xC3, 0x00, 0x74, 0x00, 0xD2, 0xFC, 0x10, 0xFC, + 0x08, 0x08, 0x0A, 0x1B, 0xE9, 0x20, 0x9A, 0x12, 0xBE, 0x00, 0x49, + 0xFB, 0xC8, 0xFE, 0xF9, 0x00, 0x5A, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, + 0x1B, 0x00, 0xE4, 0x00, 0x06, 0x00, 0x24, 0xFC, 0x1E, 0xFD, 0x65, + 0x0B, 0x94, 0x1D, 0x9D, 0x1F, 0x0D, 0x0F, 0xB8, 0xFE, 0x96, 0xFB, + 0x72, 0xFF, 0xF9, 0x00, 0x37, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x36, + 0x00, 0xF8, 0x00, 0x78, 0xFF, 0x9B, 0xFB, 0xA6, 0xFE, 0xE9, 0x0E, + 0x8D, 0x1F, 0xAA, 0x1D, 0x87, 0x0B, 0x2B, 0xFD, 0x1E, 0xFC, 0x02, + 0x00, 0xE5, 0x00, 0x1C, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x58, 0x00, + 0xF9, 0x00, 0xCF, 0xFE, 0x4A, 0xFB, 0xA7, 0x00, 0x77, 0x12, 0xE0, + 0x20, 0x26, 0x1B, 0x28, 0x08, 0x18, 0xFC, 0xCB, 0xFC, 0x71, 0x00, + 0xC5, 0x00, 0x08, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x80, 0x00, 0xE1, + 0x00, 0x13, 0xFE, 0x45, 0xFB, 0x1D, 0x03, 0xEB, 0x15, 0x7F, 0x21, + 0x2D, 0x18, 0x0E, 0x05, 0x77, 0xFB, 0x8B, 0xFD, 0xBE, 0x00, 0x9D, + 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xA9, 0x00, + 0xAA, 0x00, 0x4F, 0xFD, 0x9D, 0xFB, 0xFA, 0x05, 0x22, 0x19, 0x62, + 0x21, 0xE0, 0x14, 0x50, 0x02, 0x3E, 0xFB, 0x4E, 0xFE, 0xEB, 0x00, + 0x73, 0x00, 0xF7, 0xFF, 0xFE, 0xFF, 0x0D, 0x00, 0xD0, 0x00, 0x52, + 0x00, 0x93, 0xFC, 0x60, 0xFC, 0x2C, 0x09, 0xFA, 0x1B, 0x8A, 0x20, + 0x60, 0x11, 0xFD, 0xFF, 0x5C, 0xFB, 0x06, 0xFF, 0xFB, 0x00, 0x4D, + 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x23, 0x00, 0xED, 0x00, 0xD9, 0xFF, + 0xEF, 0xFB, 0x98, 0xFD, 0x99, 0x0C, 0x54, 0x1E, 0x02, 0x1F, 0xD2, + 0x0D, 0x20, 0xFE, 0xC0, 0xFB, 0xA7, 0xFF, 0xF4, 0x00, 0x2D, 0x00, + 0xF9, 0xFF, 0xF8, 0xFF, 0x41, 0x00, 0xFB, 0x00, 0x41, 0xFF, 0x78, + 0xFB, 0x4A, 0xFF, 0x25, 0x10, 0x16, 0x20, 0xDA, 0x1C, 0x56, 0x0A, + 0xBE, 0xFC, 0x56, 0xFC, 0x2C, 0x00, 0xDB, 0x00, 0x14, 0x00, 0xFD, + 0xFF, 0xF7, 0xFF, 0x66, 0x00, 0xF4, 0x00, 0x8F, 0xFE, 0x3F, 0xFB, + 0x75, 0x01, 0xAE, 0x13, 0x2C, 0x21, 0x2A, 0x1A, 0x0D, 0x07, 0xD4, + 0xFB, 0x0C, 0xFD, 0x8F, 0x00, 0xB7, 0x00, 0x03, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0xFA, 0xFF, 0x8E, 0x00, 0xD1, 0x00, 0xCF, 0xFD, 0x58, + 0xFB, 0x10, 0x04, 0x10, 0x17, 0x8A, 0x21, 0x10, 0x17, 0x10, 0x04, + 0x58, 0xFB, 0xCF, 0xFD, 0xD1, 0x00, 0x8E, 0x00, 0xFA, 0xFF, 0xFF, + 0xFF, 0x03, 0x00, 0xB7, 0x00, 0x8F, 0x00, 0x0C, 0xFD, 0xD4, 0xFB, + 0x0D, 0x07, 0x2A, 0x1A, 0x2C, 0x21, 0xAE, 0x13, 0x75, 0x01, 0x3F, + 0xFB, 0x8F, 0xFE, 0xF4, 0x00, 0x66, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, + 0x14, 0x00, 0xDB, 0x00, 0x2C, 0x00, 0x56, 0xFC, 0xBE, 0xFC, 0x56, + 0x0A, 0xDA, 0x1C, 0x16, 0x20, 0x25, 0x10, 0x4A, 0xFF, 0x78, 0xFB, + 0x41, 0xFF, 0xFB, 0x00, 0x41, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x2D, + 0x00, 0xF4, 0x00, 0xA7, 0xFF, 0xC0, 0xFB, 0x20, 0xFE, 0xD2, 0x0D, + 0x02, 0x1F, 0x54, 0x1E, 0x99, 0x0C, 0x98, 0xFD, 0xEF, 0xFB, 0xD9, + 0xFF, 0xED, 0x00, 0x23, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x4D, 0x00, + 0xFB, 0x00, 0x06, 0xFF, 0x5C, 0xFB, 0xFD, 0xFF, 0x60, 0x11, 0x8A, + 0x20, 0xFA, 0x1B, 0x2C, 0x09, 0x60, 0xFC, 0x93, 0xFC, 0x52, 0x00, + 0xD0, 0x00, 0x0D, 0x00, 0xFE, 0xFF, 0xF7, 0xFF, 0x73, 0x00, 0xEB, + 0x00, 0x4E, 0xFE, 0x3E, 0xFB, 0x50, 0x02, 0xE0, 0x14, 0x62, 0x21, + 0x22, 0x19, 0xFA, 0x05, 0x9D, 0xFB, 0x4F, 0xFD, 0xAA, 0x00, 0xA9, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x9D, 0x00, + 0xBE, 0x00, 0x8B, 0xFD, 0x77, 0xFB, 0x0E, 0x05, 0x2D, 0x18, 0x7F, + 0x21, 0xEB, 0x15, 0x1D, 0x03, 0x45, 0xFB, 0x13, 0xFE, 0xE1, 0x00, + 0x80, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x08, 0x00, 0xC5, 0x00, 0x71, + 0x00, 0xCB, 0xFC, 0x18, 0xFC, 0x28, 0x08, 0x26, 0x1B, 0xE0, 0x20, + 0x77, 0x12, 0xA7, 0x00, 0x4A, 0xFB, 0xCF, 0xFE, 0xF9, 0x00, 0x58, + 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1C, 0x00, 0xE5, 0x00, 0x02, 0x00, + 0x1E, 0xFC, 0x2B, 0xFD, 0x87, 0x0B, 0xAA, 0x1D, 0x8D, 0x1F, 0xE9, + 0x0E, 0xA6, 0xFE, 0x9B, 0xFB, 0x78, 0xFF, 0xF8, 0x00, 0x36, 0x00, + 0xF9, 0xFF, 0xF8, 0xFF, 0x37, 0x00, 0xF9, 0x00, 0x72, 0xFF, 0x96, + 0xFB, 0xB8, 0xFE, 0x0D, 0x0F, 0x9D, 0x1F, 0x94, 0x1D, 0x65, 0x0B, + 0x1E, 0xFD, 0x24, 0xFC, 0x06, 0x00, 0xE4, 0x00, 0x1B, 0x00, 0xFC, + 0xFF, 0xF7, 0xFF, 0x5A, 0x00, 0xF9, 0x00, 0xC8, 0xFE, 0x49, 0xFB, + 0xBE, 0x00, 0x9A, 0x12, 0xE9, 0x20, 0x0A, 0x1B, 0x08, 0x08, 0x10, + 0xFC, 0xD2, 0xFC, 0x74, 0x00, 0xC3, 0x00, 0x08, 0x00, 0xFE, 0xFF, + 0xF9, 0xFF, 0x82, 0x00, 0xDF, 0x00, 0x0B, 0xFE, 0x47, 0xFB, 0x37, + 0x03, 0x0C, 0x16, 0x81, 0x21, 0x0E, 0x18, 0xF2, 0x04, 0x73, 0xFB, + 0x92, 0xFD, 0xC0, 0x00, 0x9B, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xAB, 0x00, 0xA7, 0x00, 0x47, 0xFD, 0xA2, 0xFB, + 0x18, 0x06, 0x40, 0x19, 0x5D, 0x21, 0xBE, 0x14, 0x37, 0x02, 0x3D, + 0xFB, 0x55, 0xFE, 0xEC, 0x00, 0x72, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, + 0x0E, 0x00, 0xD1, 0x00, 0x4E, 0x00, 0x8C, 0xFC, 0x6A, 0xFC, 0x4C, + 0x09, 0x13, 0x1C, 0x7E, 0x20, 0x3D, 0x11, 0xE8, 0xFF, 0x5F, 0xFB, + 0x0C, 0xFF, 0xFB, 0x00, 0x4C, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x24, + 0x00, 0xEE, 0x00, 0xD3, 0xFF, 0xEA, 0xFB, 0xA7, 0xFD, 0xBC, 0x0C, + 0x68, 0x1E, 0xEF, 0x1E, 0xAF, 0x0D, 0x11, 0xFE, 0xC5, 0xFB, 0xAD, + 0xFF, 0xF3, 0x00, 0x2C, 0x00, 0xFA, 0xFF, 0xF8, 0xFF, 0x42, 0x00, + 0xFB, 0x00, 0x3A, 0xFF, 0x74, 0xFB, 0x5D, 0xFF, 0x48, 0x10, 0x24, + 0x20, 0xC2, 0x1C, 0x35, 0x0A, 0xB3, 0xFC, 0x5D, 0xFC, 0x30, 0x00, + 0xDA, 0x00, 0x13, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x67, 0x00, 0xF3, + 0x00, 0x88, 0xFE, 0x3E, 0xFB, 0x8C, 0x01, 0xD0, 0x13, 0x33, 0x21, + 0x0D, 0x1A, 0xEE, 0x06, 0xCD, 0xFB, 0x13, 0xFD, 0x92, 0x00, 0xB6, + 0x00, 0x03, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFA, 0xFF, 0x90, 0x00, + 0xCF, 0x00, 0xC7, 0xFD, 0x5B, 0xFB, 0x2B, 0x04, 0x31, 0x17, 0x8A, + 0x21, 0xF0, 0x16, 0xF4, 0x03, 0x56, 0xFB, 0xD6, 0xFD, 0xD3, 0x00, + 0x8D, 0x00, 0xFA, 0xFF, 0xFF, 0xFF, 0x04, 0x00, 0xB9, 0x00, 0x8C, + 0x00, 0x05, 0xFD, 0xDB, 0xFB, 0x2C, 0x07, 0x47, 0x1A, 0x25, 0x21, + 0x8B, 0x13, 0x5D, 0x01, 0x40, 0xFB, 0x97, 0xFE, 0xF5, 0x00, 0x64, + 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x15, 0x00, 0xDC, 0x00, 0x27, 0x00, + 0x50, 0xFC, 0xCA, 0xFC, 0x78, 0x0A, 0xF2, 0x1C, 0x07, 0x20, 0x02, + 0x10, 0x37, 0xFF, 0x7B, 0xFB, 0x47, 0xFF, 0xFB, 0x00, 0x40, 0x00, + 0xF8, 0xFF, 0xF9, 0xFF, 0x2E, 0x00, 0xF5, 0x00, 0xA2, 0xFF, 0xBB, + 0xFB, 0x31, 0xFE, 0xF5, 0x0D, 0x14, 0x1F, 0x3F, 0x1E, 0x77, 0x0C, + 0x8A, 0xFD, 0xF5, 0xFB, 0xDE, 0xFF, 0xEC, 0x00, 0x22, 0x00, 0xFB, + 0xFF, 0xF7, 0xFF, 0x4E, 0x00, 0xFB, 0x00, 0xFF, 0xFE, 0x59, 0xFB, + 0x11, 0x00, 0x83, 0x11, 0x96, 0x20, 0xE0, 0x1B, 0x0B, 0x09, 0x56, + 0xFC, 0x99, 0xFC, 0x56, 0x00, 0xCE, 0x00, 0x0D, 0x00, 0xFE, 0xFF, + 0xF8, 0xFF, 0x75, 0x00, 0xEA, 0x00, 0x47, 0xFE, 0x3E, 0xFB, 0x69, + 0x02, 0x02, 0x15, 0x66, 0x21, 0x04, 0x19, 0xDC, 0x05, 0x98, 0xFB, + 0x56, 0xFD, 0xAD, 0x00, 0xA8, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x9E, 0x00, 0xBC, 0x00, 0x83, 0xFD, 0x7B, 0xFB, + 0x2B, 0x05, 0x4C, 0x18, 0x7C, 0x21, 0xCA, 0x15, 0x03, 0x03, 0x44, + 0xFB, 0x1A, 0xFE, 0xE2, 0x00, 0x7E, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, + 0x09, 0x00, 0xC6, 0x00, 0x6D, 0x00, 0xC3, 0xFC, 0x20, 0xFC, 0x49, + 0x08, 0x41, 0x1B, 0xD6, 0x20, 0x54, 0x12, 0x92, 0x00, 0x4C, 0xFB, + 0xD6, 0xFE, 0xFA, 0x00, 0x57, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1D, + 0x00, 0xE6, 0x00, 0xFD, 0xFF, 0x18, 0xFC, 0x38, 0xFD, 0xA9, 0x0B, + 0xC0, 0x1D, 0x7C, 0x1F, 0xC6, 0x0E, 0x95, 0xFE, 0x9F, 0xFB, 0x7E, + 0xFF, 0xF8, 0x00, 0x35, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x38, 0x00, + 0xF9, 0x00, 0x6C, 0xFF, 0x92, 0xFB, 0xC9, 0xFE, 0x2F, 0x0F, 0xAD, + 0x1F, 0x7D, 0x1D, 0x42, 0x0B, 0x12, 0xFD, 0x2A, 0xFC, 0x0B, 0x00, + 0xE3, 0x00, 0x1A, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x5B, 0x00, 0xF8, + 0x00, 0xC1, 0xFE, 0x47, 0xFB, 0xD4, 0x00, 0xBC, 0x12, 0xF3, 0x20, + 0xEF, 0x1A, 0xE9, 0x07, 0x08, 0xFC, 0xD9, 0xFC, 0x78, 0x00, 0xC2, + 0x00, 0x07, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, 0x83, 0x00, 0xDD, 0x00, + 0x04, 0xFE, 0x49, 0xFB, 0x52, 0x03, 0x2D, 0x16, 0x83, 0x21, 0xEF, + 0x17, 0xD5, 0x04, 0x6F, 0xFB, 0x9A, 0xFD, 0xC3, 0x00, 0x9A, 0x00, + 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xAD, 0x00, 0xA4, + 0x00, 0x40, 0xFD, 0xA8, 0xFB, 0x36, 0x06, 0x5E, 0x19, 0x58, 0x21, + 0x9C, 0x14, 0x1E, 0x02, 0x3D, 0xFB, 0x5D, 0xFE, 0xED, 0x00, 0x70, + 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x0F, 0x00, 0xD2, 0x00, 0x4A, 0x00, + 0x85, 0xFC, 0x74, 0xFC, 0x6D, 0x09, 0x2D, 0x1C, 0x72, 0x20, 0x1A, + 0x11, 0xD4, 0xFF, 0x61, 0xFB, 0x13, 0xFF, 0xFC, 0x00, 0x4A, 0x00, + 0xF7, 0xFF, 0xFA, 0xFF, 0x25, 0x00, 0xEF, 0x00, 0xCE, 0xFF, 0xE4, + 0xFB, 0xB5, 0xFD, 0xDE, 0x0C, 0x7C, 0x1E, 0xDD, 0x1E, 0x8C, 0x0D, + 0x01, 0xFE, 0xCA, 0xFB, 0xB3, 0xFF, 0xF3, 0x00, 0x2B, 0x00, 0xFA, + 0xFF, 0xF8, 0xFF, 0x44, 0x00, 0xFB, 0x00, 0x34, 0xFF, 0x71, 0xFB, + 0x71, 0xFF, 0x6B, 0x10, 0x32, 0x20, 0xA9, 0x1C, 0x13, 0x0A, 0xA8, + 0xFC, 0x63, 0xFC, 0x35, 0x00, 0xD9, 0x00, 0x12, 0x00, 0xFD, 0xFF, + 0xF7, 0xFF, 0x69, 0x00, 0xF2, 0x00, 0x81, 0xFE, 0x3E, 0xFB, 0xA4, + 0x01, 0xF2, 0x13, 0x3A, 0x21, 0xF0, 0x19, 0xCF, 0x06, 0xC7, 0xFB, + 0x1B, 0xFD, 0x96, 0x00, 0xB4, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0xFB, 0xFF, 0x92, 0x00, 0xCD, 0x00, 0xC0, 0xFD, 0x5E, 0xFB, + 0x47, 0x04, 0x51, 0x17, 0x8A, 0x21, 0xD0, 0x16, 0xD9, 0x03, 0x53, + 0xFB, 0xDE, 0xFD, 0xD5, 0x00, 0x8B, 0x00, 0xFA, 0xFF, 0xFF, 0xFF, + 0x04, 0x00, 0xBA, 0x00, 0x89, 0x00, 0xFD, 0xFC, 0xE2, 0xFB, 0x4B, + 0x07, 0x63, 0x1A, 0x1D, 0x21, 0x69, 0x13, 0x46, 0x01, 0x41, 0xFB, + 0x9E, 0xFE, 0xF5, 0x00, 0x63, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x16, + 0x00, 0xDD, 0x00, 0x23, 0x00, 0x49, 0xFC, 0xD5, 0xFC, 0x99, 0x0A, + 0x09, 0x1D, 0xF9, 0x1F, 0xDF, 0x0F, 0x24, 0xFF, 0x7F, 0xFB, 0x4D, + 0xFF, 0xFB, 0x00, 0x3F, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x2F, 0x00, + 0xF5, 0x00, 0x9C, 0xFF, 0xB6, 0xFB, 0x41, 0xFE, 0x17, 0x0E, 0x26, + 0x1F, 0x2B, 0x1E, 0x54, 0x0C, 0x7C, 0xFD, 0xFA, 0xFB, 0xE3, 0xFF, + 0xEB, 0x00, 0x21, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x50, 0x00, 0xFB, + 0x00, 0xF8, 0xFE, 0x57, 0xFB, 0x26, 0x00, 0xA6, 0x11, 0xA1, 0x20, + 0xC6, 0x1B, 0xEA, 0x08, 0x4D, 0xFC, 0xA0, 0xFC, 0x5A, 0x00, 0xCD, + 0x00, 0x0C, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x77, 0x00, 0xE9, 0x00, + 0x3F, 0xFE, 0x3F, 0xFB, 0x82, 0x02, 0x23, 0x15, 0x6B, 0x21, 0xE5, + 0x18, 0xBE, 0x05, 0x93, 0xFB, 0x5E, 0xFD, 0xAF, 0x00, 0xA6, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0xA0, 0x00, 0xB9, + 0x00, 0x7C, 0xFD, 0x80, 0xFB, 0x48, 0x05, 0x6B, 0x18, 0x79, 0x21, + 0xA9, 0x15, 0xE9, 0x02, 0x43, 0xFB, 0x21, 0xFE, 0xE3, 0x00, 0x7D, + 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x09, 0x00, 0xC7, 0x00, 0x69, 0x00, + 0xBC, 0xFC, 0x29, 0xFC, 0x69, 0x08, 0x5C, 0x1B, 0xCC, 0x20, 0x32, + 0x12, 0x7C, 0x00, 0x4E, 0xFB, 0xDD, 0xFE, 0xFA, 0x00, 0x56, 0x00, + 0xF7, 0xFF, 0xFB, 0xFF, 0x1D, 0x00, 0xE7, 0x00, 0xF8, 0xFF, 0x12, + 0xFC, 0x45, 0xFD, 0xCB, 0x0B, 0xD6, 0x1D, 0x6C, 0x1F, 0xA3, 0x0E, + 0x84, 0xFE, 0xA4, 0xFB, 0x84, 0xFF, 0xF7, 0x00, 0x34, 0x00, 0xF9, + 0xFF, 0xF8, 0xFF, 0x3A, 0x00, 0xFA, 0x00, 0x66, 0xFF, 0x8E, 0xFB, + 0xDB, 0xFE, 0x53, 0x0F, 0xBD, 0x1F, 0x66, 0x1D, 0x21, 0x0B, 0x05, + 0xFD, 0x30, 0xFC, 0x10, 0x00, 0xE2, 0x00, 0x19, 0x00, 0xFC, 0xFF, + 0xF7, 0xFF, 0x5D, 0x00, 0xF8, 0x00, 0xBA, 0xFE, 0x46, 0xFB, 0xEA, + 0x00, 0xDF, 0x12, 0xFC, 0x20, 0xD3, 0x1A, 0xC9, 0x07, 0x00, 0xFC, + 0xE0, 0xFC, 0x7B, 0x00, 0xC0, 0x00, 0x07, 0x00, 0xFF, 0xFF, 0xF9, + 0xFF, 0x85, 0x00, 0xDC, 0x00, 0xFC, 0xFD, 0x4A, 0xFB, 0x6C, 0x03, + 0x4E, 0x16, 0x85, 0x21, 0xCF, 0x17, 0xB8, 0x04, 0x6C, 0xFB, 0xA2, + 0xFD, 0xC5, 0x00, 0x98, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFF, + 0x01, 0x00, 0xAE, 0x00, 0xA1, 0x00, 0x38, 0xFD, 0xAE, 0xFB, 0x54, + 0x06, 0x7C, 0x19, 0x53, 0x21, 0x7B, 0x14, 0x05, 0x02, 0x3D, 0xFB, + 0x64, 0xFE, 0xEE, 0x00, 0x6F, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x0F, + 0x00, 0xD4, 0x00, 0x46, 0x00, 0x7E, 0xFC, 0x7E, 0xFC, 0x8E, 0x09, + 0x46, 0x1C, 0x66, 0x20, 0xF7, 0x10, 0xC0, 0xFF, 0x64, 0xFB, 0x1A, + 0xFF, 0xFC, 0x00, 0x49, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x26, 0x00, + 0xF0, 0x00, 0xC9, 0xFF, 0xDF, 0xFB, 0xC4, 0xFD, 0x01, 0x0D, 0x90, + 0x1E, 0xCA, 0x1E, 0x69, 0x0D, 0xF1, 0xFD, 0xCF, 0xFB, 0xB8, 0xFF, + 0xF2, 0x00, 0x29, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x45, 0x00, 0xFC, + 0x00, 0x2D, 0xFF, 0x6D, 0xFB, 0x84, 0xFF, 0x8E, 0x10, 0x3F, 0x20, + 0x91, 0x1C, 0xF2, 0x09, 0x9D, 0xFC, 0x6A, 0xFC, 0x39, 0x00, 0xD7, + 0x00, 0x12, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x6A, 0x00, 0xF1, 0x00, + 0x7A, 0xFE, 0x3D, 0xFB, 0xBC, 0x01, 0x14, 0x14, 0x41, 0x21, 0xD4, + 0x19, 0xB0, 0x06, 0xC0, 0xFB, 0x22, 0xFD, 0x99, 0x00, 0xB3, 0x00, + 0x02, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFB, 0xFF, 0x93, 0x00, 0xCB, + 0x00, 0xB8, 0xFD, 0x61, 0xFB, 0x63, 0x04, 0x71, 0x17, 0x89, 0x21, + 0xB0, 0x16, 0xBD, 0x03, 0x51, 0xFB, 0xE6, 0xFD, 0xD7, 0x00, 0x8A, + 0x00, 0xFA, 0xFF, 0xFF, 0xFF, 0x05, 0x00, 0xBC, 0x00, 0x86, 0x00, + 0xF6, 0xFC, 0xE9, 0xFB, 0x6A, 0x07, 0x80, 0x1A, 0x15, 0x21, 0x47, + 0x13, 0x2F, 0x01, 0x42, 0xFB, 0xA5, 0xFE, 0xF6, 0x00, 0x61, 0x00, + 0xF7, 0xFF, 0xFC, 0xFF, 0x16, 0x00, 0xDF, 0x00, 0x1E, 0x00, 0x43, + 0xFC, 0xE1, 0xFC, 0xBB, 0x0A, 0x21, 0x1D, 0xEA, 0x1F, 0xBC, 0x0F, + 0x12, 0xFF, 0x82, 0xFB, 0x54, 0xFF, 0xFA, 0x00, 0x3D, 0x00, 0xF8, + 0xFF, 0xF9, 0xFF, 0x30, 0x00, 0xF6, 0x00, 0x96, 0xFF, 0xB1, 0xFB, + 0x51, 0xFE, 0x3A, 0x0E, 0x38, 0x1F, 0x16, 0x1E, 0x32, 0x0C, 0x6E, + 0xFD, 0x00, 0xFC, 0xE8, 0xFF, 0xEA, 0x00, 0x20, 0x00, 0xFB, 0xFF, + 0xF7, 0xFF, 0x51, 0x00, 0xFB, 0x00, 0xF1, 0xFE, 0x54, 0xFB, 0x3B, + 0x00, 0xC9, 0x11, 0xAD, 0x20, 0xAC, 0x1B, 0xCA, 0x08, 0x44, 0xFC, + 0xA7, 0xFC, 0x5E, 0x00, 0xCC, 0x00, 0x0B, 0x00, 0xFE, 0xFF, 0xF8, + 0xFF, 0x78, 0x00, 0xE7, 0x00, 0x38, 0xFE, 0x40, 0xFB, 0x9B, 0x02, + 0x45, 0x15, 0x6F, 0x21, 0xC7, 0x18, 0xA1, 0x05, 0x8E, 0xFB, 0x65, + 0xFD, 0xB2, 0x00, 0xA5, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0xA2, 0x00, 0xB7, 0x00, 0x74, 0xFD, 0x84, 0xFB, 0x66, + 0x05, 0x8A, 0x18, 0x76, 0x21, 0x87, 0x15, 0xCF, 0x02, 0x41, 0xFB, + 0x29, 0xFE, 0xE5, 0x00, 0x7B, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0A, + 0x00, 0xC9, 0x00, 0x66, 0x00, 0xB5, 0xFC, 0x32, 0xFC, 0x89, 0x08, + 0x77, 0x1B, 0xC2, 0x20, 0x0F, 0x12, 0x66, 0x00, 0x50, 0xFB, 0xE4, + 0xFE, 0xFA, 0x00, 0x54, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1E, 0x00, + 0xE8, 0x00, 0xF3, 0xFF, 0x0C, 0xFC, 0x53, 0xFD, 0xED, 0x0B, 0xEB, + 0x1D, 0x5A, 0x1F, 0x80, 0x0E, 0x73, 0xFE, 0xA8, 0xFB, 0x8A, 0xFF, + 0xF7, 0x00, 0x32, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x3B, 0x00, 0xFA, + 0x00, 0x60, 0xFF, 0x8A, 0xFB, 0xED, 0xFE, 0x76, 0x0F, 0xCC, 0x1F, + 0x4F, 0x1D, 0xFF, 0x0A, 0xF9, 0xFC, 0x36, 0xFC, 0x15, 0x00, 0xE1, + 0x00, 0x18, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x5E, 0x00, 0xF7, 0x00, + 0xB3, 0xFE, 0x44, 0xFB, 0x01, 0x01, 0x02, 0x13, 0x04, 0x21, 0xB8, + 0x1A, 0xA9, 0x07, 0xF8, 0xFB, 0xE7, 0xFC, 0x7F, 0x00, 0xBF, 0x00, + 0x06, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, 0x86, 0x00, 0xDA, 0x00, 0xF5, + 0xFD, 0x4C, 0xFB, 0x87, 0x03, 0x6E, 0x16, 0x86, 0x21, 0xB0, 0x17, + 0x9C, 0x04, 0x68, 0xFB, 0xA9, 0xFD, 0xC7, 0x00, 0x96, 0x00, 0xFB, + 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xB0, 0x00, 0x9F, 0x00, + 0x31, 0xFD, 0xB4, 0xFB, 0x73, 0x06, 0x99, 0x19, 0x4D, 0x21, 0x59, + 0x14, 0xED, 0x01, 0x3D, 0xFB, 0x6B, 0xFE, 0xEF, 0x00, 0x6D, 0x00, + 0xF7, 0xFF, 0xFD, 0xFF, 0x10, 0x00, 0xD5, 0x00, 0x42, 0x00, 0x77, + 0xFC, 0x88, 0xFC, 0xAF, 0x09, 0x5F, 0x1C, 0x59, 0x20, 0xD4, 0x10, + 0xAC, 0xFF, 0x67, 0xFB, 0x20, 0xFF, 0xFC, 0x00, 0x48, 0x00, 0xF7, + 0xFF, 0xFA, 0xFF, 0x27, 0x00, 0xF0, 0x00, 0xC3, 0xFF, 0xD9, 0xFB, + 0xD3, 0xFD, 0x24, 0x0D, 0xA3, 0x1E, 0xB7, 0x1E, 0x46, 0x0D, 0xE2, + 0xFD, 0xD4, 0xFB, 0xBE, 0xFF, 0xF1, 0x00, 0x28, 0x00, 0xFA, 0xFF, + 0xF7, 0xFF, 0x46, 0x00, 0xFC, 0x00, 0x27, 0xFF, 0x6A, 0xFB, 0x98, + 0xFF, 0xB1, 0x10, 0x4C, 0x20, 0x78, 0x1C, 0xD1, 0x09, 0x93, 0xFC, + 0x71, 0xFC, 0x3D, 0x00, 0xD6, 0x00, 0x11, 0x00, 0xFD, 0xFF, 0xF7, + 0xFF, 0x6C, 0x00, 0xF0, 0x00, 0x72, 0xFE, 0x3D, 0xFB, 0xD4, 0x01, + 0x36, 0x14, 0x47, 0x21, 0xB6, 0x19, 0x91, 0x06, 0xBA, 0xFB, 0x29, + 0xFD, 0x9C, 0x00, 0xB1, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0xFB, 0xFF, 0x95, 0x00, 0xC9, 0x00, 0xB1, 0xFD, 0x65, 0xFB, 0x80, + 0x04, 0x90, 0x17, 0x88, 0x21, 0x8F, 0x16, 0xA2, 0x03, 0x4E, 0xFB, + 0xED, 0xFD, 0xD9, 0x00, 0x88, 0x00, 0xF9, 0xFF, 0xFF, 0xFF, 0x05, + 0x00, 0xBD, 0x00, 0x82, 0x00, 0xEF, 0xFC, 0xF0, 0xFB, 0x8A, 0x07, + 0x9C, 0x1A, 0x0D, 0x21, 0x24, 0x13, 0x18, 0x01, 0x43, 0xFB, 0xAC, + 0xFE, 0xF7, 0x00, 0x60, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x17, 0x00, + 0xE0, 0x00, 0x1A, 0x00, 0x3D, 0xFC, 0xED, 0xFC, 0xDD, 0x0A, 0x38, + 0x1D, 0xDB, 0x1F, 0x99, 0x0F, 0xFF, 0xFE, 0x86, 0xFB, 0x5A, 0xFF, + 0xFA, 0x00, 0x3C, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x31, 0x00, 0xF6, + 0x00, 0x90, 0xFF, 0xAD, 0xFB, 0x62, 0xFE, 0x5D, 0x0E, 0x49, 0x1F, + 0x01, 0x1E, 0x10, 0x0C, 0x60, 0xFD, 0x06, 0xFC, 0xEE, 0xFF, 0xE9, + 0x00, 0x1F, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x53, 0x00, 0xFB, 0x00, + 0xEB, 0xFE, 0x52, 0xFB, 0x51, 0x00, 0xEC, 0x11, 0xB7, 0x20, 0x91, + 0x1B, 0xA9, 0x08, 0x3B, 0xFC, 0xAE, 0xFC, 0x62, 0x00, 0xCA, 0x00, + 0x0B, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7A, 0x00, 0xE6, 0x00, 0x30, + 0xFE, 0x40, 0xFB, 0xB5, 0x02, 0x66, 0x15, 0x73, 0x21, 0xA9, 0x18, + 0x83, 0x05, 0x89, 0xFB, 0x6D, 0xFD, 0xB4, 0x00, 0xA3, 0x00, 0xFE, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xA3, 0x00, 0xB4, 0x00, + 0x6D, 0xFD, 0x89, 0xFB, 0x83, 0x05, 0xA9, 0x18, 0x73, 0x21, 0x66, + 0x15, 0xB5, 0x02, 0x40, 0xFB, 0x30, 0xFE, 0xE6, 0x00, 0x7A, 0x00, + 0xF8, 0xFF, 0xFE, 0xFF, 0x0B, 0x00, 0xCA, 0x00, 0x62, 0x00, 0xAE, + 0xFC, 0x3B, 0xFC, 0xA9, 0x08, 0x91, 0x1B, 0xB7, 0x20, 0xEC, 0x11, + 0x51, 0x00, 0x52, 0xFB, 0xEB, 0xFE, 0xFB, 0x00, 0x53, 0x00, 0xF7, + 0xFF, 0xFB, 0xFF, 0x1F, 0x00, 0xE9, 0x00, 0xEE, 0xFF, 0x06, 0xFC, + 0x60, 0xFD, 0x10, 0x0C, 0x01, 0x1E, 0x49, 0x1F, 0x5D, 0x0E, 0x62, + 0xFE, 0xAD, 0xFB, 0x90, 0xFF, 0xF6, 0x00, 0x31, 0x00, 0xF9, 0xFF, + 0xF8, 0xFF, 0x3C, 0x00, 0xFA, 0x00, 0x5A, 0xFF, 0x86, 0xFB, 0xFF, + 0xFE, 0x99, 0x0F, 0xDB, 0x1F, 0x38, 0x1D, 0xDD, 0x0A, 0xED, 0xFC, + 0x3D, 0xFC, 0x1A, 0x00, 0xE0, 0x00, 0x17, 0x00, 0xFC, 0xFF, 0xF7, + 0xFF, 0x60, 0x00, 0xF7, 0x00, 0xAC, 0xFE, 0x43, 0xFB, 0x18, 0x01, + 0x24, 0x13, 0x0D, 0x21, 0x9C, 0x1A, 0x8A, 0x07, 0xF0, 0xFB, 0xEF, + 0xFC, 0x82, 0x00, 0xBD, 0x00, 0x05, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, + 0x88, 0x00, 0xD9, 0x00, 0xED, 0xFD, 0x4E, 0xFB, 0xA2, 0x03, 0x8F, + 0x16, 0x88, 0x21, 0x90, 0x17, 0x80, 0x04, 0x65, 0xFB, 0xB1, 0xFD, + 0xC9, 0x00, 0x95, 0x00, 0xFB, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x02, + 0x00, 0xB1, 0x00, 0x9C, 0x00, 0x29, 0xFD, 0xBA, 0xFB, 0x91, 0x06, + 0xB6, 0x19, 0x47, 0x21, 0x36, 0x14, 0xD4, 0x01, 0x3D, 0xFB, 0x72, + 0xFE, 0xF0, 0x00, 0x6C, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x11, 0x00, + 0xD6, 0x00, 0x3D, 0x00, 0x71, 0xFC, 0x93, 0xFC, 0xD1, 0x09, 0x78, + 0x1C, 0x4C, 0x20, 0xB1, 0x10, 0x98, 0xFF, 0x6A, 0xFB, 0x27, 0xFF, + 0xFC, 0x00, 0x46, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x28, 0x00, 0xF1, + 0x00, 0xBE, 0xFF, 0xD4, 0xFB, 0xE2, 0xFD, 0x46, 0x0D, 0xB7, 0x1E, + 0xA3, 0x1E, 0x24, 0x0D, 0xD3, 0xFD, 0xD9, 0xFB, 0xC3, 0xFF, 0xF0, + 0x00, 0x27, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x48, 0x00, 0xFC, 0x00, + 0x20, 0xFF, 0x67, 0xFB, 0xAC, 0xFF, 0xD4, 0x10, 0x59, 0x20, 0x5F, + 0x1C, 0xAF, 0x09, 0x88, 0xFC, 0x77, 0xFC, 0x42, 0x00, 0xD5, 0x00, + 0x10, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x6D, 0x00, 0xEF, 0x00, 0x6B, + 0xFE, 0x3D, 0xFB, 0xED, 0x01, 0x59, 0x14, 0x4D, 0x21, 0x99, 0x19, + 0x73, 0x06, 0xB4, 0xFB, 0x31, 0xFD, 0x9F, 0x00, 0xB0, 0x00, 0x01, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFB, 0xFF, 0x96, 0x00, 0xC7, 0x00, + 0xA9, 0xFD, 0x68, 0xFB, 0x9C, 0x04, 0xB0, 0x17, 0x86, 0x21, 0x6E, + 0x16, 0x87, 0x03, 0x4C, 0xFB, 0xF5, 0xFD, 0xDA, 0x00, 0x86, 0x00, + 0xF9, 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0xBF, 0x00, 0x7F, 0x00, 0xE7, + 0xFC, 0xF8, 0xFB, 0xA9, 0x07, 0xB8, 0x1A, 0x04, 0x21, 0x02, 0x13, + 0x01, 0x01, 0x44, 0xFB, 0xB3, 0xFE, 0xF7, 0x00, 0x5E, 0x00, 0xF7, + 0xFF, 0xFC, 0xFF, 0x18, 0x00, 0xE1, 0x00, 0x15, 0x00, 0x36, 0xFC, + 0xF9, 0xFC, 0xFF, 0x0A, 0x4F, 0x1D, 0xCC, 0x1F, 0x76, 0x0F, 0xED, + 0xFE, 0x8A, 0xFB, 0x60, 0xFF, 0xFA, 0x00, 0x3B, 0x00, 0xF8, 0xFF, + 0xF9, 0xFF, 0x32, 0x00, 0xF7, 0x00, 0x8A, 0xFF, 0xA8, 0xFB, 0x73, + 0xFE, 0x80, 0x0E, 0x5A, 0x1F, 0xEB, 0x1D, 0xED, 0x0B, 0x53, 0xFD, + 0x0C, 0xFC, 0xF3, 0xFF, 0xE8, 0x00, 0x1E, 0x00, 0xFB, 0xFF, 0xF7, + 0xFF, 0x54, 0x00, 0xFA, 0x00, 0xE4, 0xFE, 0x50, 0xFB, 0x66, 0x00, + 0x0F, 0x12, 0xC2, 0x20, 0x77, 0x1B, 0x89, 0x08, 0x32, 0xFC, 0xB5, + 0xFC, 0x66, 0x00, 0xC9, 0x00, 0x0A, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, + 0x7B, 0x00, 0xE5, 0x00, 0x29, 0xFE, 0x41, 0xFB, 0xCF, 0x02, 0x87, + 0x15, 0x76, 0x21, 0x8A, 0x18, 0x66, 0x05, 0x84, 0xFB, 0x74, 0xFD, + 0xB7, 0x00, 0xA2, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0xFF, 0xA5, 0x00, 0xB2, 0x00, 0x65, 0xFD, 0x8E, 0xFB, 0xA1, 0x05, + 0xC7, 0x18, 0x6F, 0x21, 0x45, 0x15, 0x9B, 0x02, 0x40, 0xFB, 0x38, + 0xFE, 0xE7, 0x00, 0x78, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0B, 0x00, + 0xCC, 0x00, 0x5E, 0x00, 0xA7, 0xFC, 0x44, 0xFC, 0xCA, 0x08, 0xAC, + 0x1B, 0xAD, 0x20, 0xC9, 0x11, 0x3B, 0x00, 0x54, 0xFB, 0xF1, 0xFE, + 0xFB, 0x00, 0x51, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x20, 0x00, 0xEA, + 0x00, 0xE8, 0xFF, 0x00, 0xFC, 0x6E, 0xFD, 0x32, 0x0C, 0x16, 0x1E, + 0x38, 0x1F, 0x3A, 0x0E, 0x51, 0xFE, 0xB1, 0xFB, 0x96, 0xFF, 0xF6, + 0x00, 0x30, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x3D, 0x00, 0xFA, 0x00, + 0x54, 0xFF, 0x82, 0xFB, 0x12, 0xFF, 0xBC, 0x0F, 0xEA, 0x1F, 0x21, + 0x1D, 0xBB, 0x0A, 0xE1, 0xFC, 0x43, 0xFC, 0x1E, 0x00, 0xDF, 0x00, + 0x16, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x61, 0x00, 0xF6, 0x00, 0xA5, + 0xFE, 0x42, 0xFB, 0x2F, 0x01, 0x47, 0x13, 0x15, 0x21, 0x80, 0x1A, + 0x6A, 0x07, 0xE9, 0xFB, 0xF6, 0xFC, 0x86, 0x00, 0xBC, 0x00, 0x05, + 0x00, 0xFF, 0xFF, 0xFA, 0xFF, 0x8A, 0x00, 0xD7, 0x00, 0xE6, 0xFD, + 0x51, 0xFB, 0xBD, 0x03, 0xB0, 0x16, 0x89, 0x21, 0x71, 0x17, 0x63, + 0x04, 0x61, 0xFB, 0xB8, 0xFD, 0xCB, 0x00, 0x93, 0x00, 0xFB, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00, 0xB3, 0x00, 0x99, 0x00, 0x22, + 0xFD, 0xC0, 0xFB, 0xB0, 0x06, 0xD4, 0x19, 0x41, 0x21, 0x14, 0x14, + 0xBC, 0x01, 0x3D, 0xFB, 0x7A, 0xFE, 0xF1, 0x00, 0x6A, 0x00, 0xF7, + 0xFF, 0xFD, 0xFF, 0x12, 0x00, 0xD7, 0x00, 0x39, 0x00, 0x6A, 0xFC, + 0x9D, 0xFC, 0xF2, 0x09, 0x91, 0x1C, 0x3F, 0x20, 0x8E, 0x10, 0x84, + 0xFF, 0x6D, 0xFB, 0x2D, 0xFF, 0xFC, 0x00, 0x45, 0x00, 0xF7, 0xFF, + 0xFA, 0xFF, 0x29, 0x00, 0xF2, 0x00, 0xB8, 0xFF, 0xCF, 0xFB, 0xF1, + 0xFD, 0x69, 0x0D, 0xCA, 0x1E, 0x90, 0x1E, 0x01, 0x0D, 0xC4, 0xFD, + 0xDF, 0xFB, 0xC9, 0xFF, 0xF0, 0x00, 0x26, 0x00, 0xFA, 0xFF, 0xF7, + 0xFF, 0x49, 0x00, 0xFC, 0x00, 0x1A, 0xFF, 0x64, 0xFB, 0xC0, 0xFF, + 0xF7, 0x10, 0x66, 0x20, 0x46, 0x1C, 0x8E, 0x09, 0x7E, 0xFC, 0x7E, + 0xFC, 0x46, 0x00, 0xD4, 0x00, 0x0F, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, + 0x6F, 0x00, 0xEE, 0x00, 0x64, 0xFE, 0x3D, 0xFB, 0x05, 0x02, 0x7B, + 0x14, 0x53, 0x21, 0x7C, 0x19, 0x54, 0x06, 0xAE, 0xFB, 0x38, 0xFD, + 0xA1, 0x00, 0xAE, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFC, + 0xFF, 0x98, 0x00, 0xC5, 0x00, 0xA2, 0xFD, 0x6C, 0xFB, 0xB8, 0x04, + 0xCF, 0x17, 0x85, 0x21, 0x4E, 0x16, 0x6C, 0x03, 0x4A, 0xFB, 0xFC, + 0xFD, 0xDC, 0x00, 0x85, 0x00, 0xF9, 0xFF, 0xFF, 0xFF, 0x07, 0x00, + 0xC0, 0x00, 0x7B, 0x00, 0xE0, 0xFC, 0x00, 0xFC, 0xC9, 0x07, 0xD3, + 0x1A, 0xFC, 0x20, 0xDF, 0x12, 0xEA, 0x00, 0x46, 0xFB, 0xBA, 0xFE, + 0xF8, 0x00, 0x5D, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x19, 0x00, 0xE2, + 0x00, 0x10, 0x00, 0x30, 0xFC, 0x05, 0xFD, 0x21, 0x0B, 0x66, 0x1D, + 0xBD, 0x1F, 0x53, 0x0F, 0xDB, 0xFE, 0x8E, 0xFB, 0x66, 0xFF, 0xFA, + 0x00, 0x3A, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x34, 0x00, 0xF7, 0x00, + 0x84, 0xFF, 0xA4, 0xFB, 0x84, 0xFE, 0xA3, 0x0E, 0x6C, 0x1F, 0xD6, + 0x1D, 0xCB, 0x0B, 0x45, 0xFD, 0x12, 0xFC, 0xF8, 0xFF, 0xE7, 0x00, + 0x1D, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x56, 0x00, 0xFA, 0x00, 0xDD, + 0xFE, 0x4E, 0xFB, 0x7C, 0x00, 0x32, 0x12, 0xCC, 0x20, 0x5C, 0x1B, + 0x69, 0x08, 0x29, 0xFC, 0xBC, 0xFC, 0x69, 0x00, 0xC7, 0x00, 0x09, + 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7D, 0x00, 0xE3, 0x00, 0x21, 0xFE, + 0x43, 0xFB, 0xE9, 0x02, 0xA9, 0x15, 0x79, 0x21, 0x6B, 0x18, 0x48, + 0x05, 0x80, 0xFB, 0x7C, 0xFD, 0xB9, 0x00, 0xA0, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xA6, 0x00, 0xAF, 0x00, 0x5E, + 0xFD, 0x93, 0xFB, 0xBE, 0x05, 0xE5, 0x18, 0x6B, 0x21, 0x23, 0x15, + 0x82, 0x02, 0x3F, 0xFB, 0x3F, 0xFE, 0xE9, 0x00, 0x77, 0x00, 0xF8, + 0xFF, 0xFE, 0xFF, 0x0C, 0x00, 0xCD, 0x00, 0x5A, 0x00, 0xA0, 0xFC, + 0x4D, 0xFC, 0xEA, 0x08, 0xC6, 0x1B, 0xA1, 0x20, 0xA6, 0x11, 0x26, + 0x00, 0x57, 0xFB, 0xF8, 0xFE, 0xFB, 0x00, 0x50, 0x00, 0xF7, 0xFF, + 0xFB, 0xFF, 0x21, 0x00, 0xEB, 0x00, 0xE3, 0xFF, 0xFA, 0xFB, 0x7C, + 0xFD, 0x54, 0x0C, 0x2B, 0x1E, 0x26, 0x1F, 0x17, 0x0E, 0x41, 0xFE, + 0xB6, 0xFB, 0x9C, 0xFF, 0xF5, 0x00, 0x2F, 0x00, 0xF9, 0xFF, 0xF8, + 0xFF, 0x3F, 0x00, 0xFB, 0x00, 0x4D, 0xFF, 0x7F, 0xFB, 0x24, 0xFF, + 0xDF, 0x0F, 0xF9, 0x1F, 0x09, 0x1D, 0x99, 0x0A, 0xD5, 0xFC, 0x49, + 0xFC, 0x23, 0x00, 0xDD, 0x00, 0x16, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, + 0x63, 0x00, 0xF5, 0x00, 0x9E, 0xFE, 0x41, 0xFB, 0x46, 0x01, 0x69, + 0x13, 0x1D, 0x21, 0x63, 0x1A, 0x4B, 0x07, 0xE2, 0xFB, 0xFD, 0xFC, + 0x89, 0x00, 0xBA, 0x00, 0x04, 0x00, 0xFF, 0xFF, 0xFA, 0xFF, 0x8B, + 0x00, 0xD5, 0x00, 0xDE, 0xFD, 0x53, 0xFB, 0xD9, 0x03, 0xD0, 0x16, + 0x8A, 0x21, 0x51, 0x17, 0x47, 0x04, 0x5E, 0xFB, 0xC0, 0xFD, 0xCD, + 0x00, 0x92, 0x00, 0xFB, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00, + 0xB4, 0x00, 0x96, 0x00, 0x1B, 0xFD, 0xC7, 0xFB, 0xCF, 0x06, 0xF0, + 0x19, 0x3A, 0x21, 0xF2, 0x13, 0xA4, 0x01, 0x3E, 0xFB, 0x81, 0xFE, + 0xF2, 0x00, 0x69, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x12, 0x00, 0xD9, + 0x00, 0x35, 0x00, 0x63, 0xFC, 0xA8, 0xFC, 0x13, 0x0A, 0xA9, 0x1C, + 0x32, 0x20, 0x6B, 0x10, 0x71, 0xFF, 0x71, 0xFB, 0x34, 0xFF, 0xFB, + 0x00, 0x44, 0x00, 0xF8, 0xFF, 0xFA, 0xFF, 0x2B, 0x00, 0xF3, 0x00, + 0xB3, 0xFF, 0xCA, 0xFB, 0x01, 0xFE, 0x8C, 0x0D, 0xDD, 0x1E, 0x7C, + 0x1E, 0xDE, 0x0C, 0xB5, 0xFD, 0xE4, 0xFB, 0xCE, 0xFF, 0xEF, 0x00, + 0x25, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x4A, 0x00, 0xFC, 0x00, 0x13, + 0xFF, 0x61, 0xFB, 0xD4, 0xFF, 0x1A, 0x11, 0x72, 0x20, 0x2D, 0x1C, + 0x6D, 0x09, 0x74, 0xFC, 0x85, 0xFC, 0x4A, 0x00, 0xD2, 0x00, 0x0F, + 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x70, 0x00, 0xED, 0x00, 0x5D, 0xFE, + 0x3D, 0xFB, 0x1E, 0x02, 0x9C, 0x14, 0x58, 0x21, 0x5E, 0x19, 0x36, + 0x06, 0xA8, 0xFB, 0x40, 0xFD, 0xA4, 0x00, 0xAD, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0xFC, 0xFF, 0x9A, 0x00, 0xC3, 0x00, 0x9A, + 0xFD, 0x6F, 0xFB, 0xD5, 0x04, 0xEF, 0x17, 0x83, 0x21, 0x2D, 0x16, + 0x52, 0x03, 0x49, 0xFB, 0x04, 0xFE, 0xDD, 0x00, 0x83, 0x00, 0xF9, + 0xFF, 0xFF, 0xFF, 0x07, 0x00, 0xC2, 0x00, 0x78, 0x00, 0xD9, 0xFC, + 0x08, 0xFC, 0xE9, 0x07, 0xEF, 0x1A, 0xF3, 0x20, 0xBC, 0x12, 0xD4, + 0x00, 0x47, 0xFB, 0xC1, 0xFE, 0xF8, 0x00, 0x5B, 0x00, 0xF7, 0xFF, + 0xFC, 0xFF, 0x1A, 0x00, 0xE3, 0x00, 0x0B, 0x00, 0x2A, 0xFC, 0x12, + 0xFD, 0x42, 0x0B, 0x7D, 0x1D, 0xAD, 0x1F, 0x2F, 0x0F, 0xC9, 0xFE, + 0x92, 0xFB, 0x6C, 0xFF, 0xF9, 0x00, 0x38, 0x00, 0xF8, 0xFF, 0xF9, + 0xFF, 0x35, 0x00, 0xF8, 0x00, 0x7E, 0xFF, 0x9F, 0xFB, 0x95, 0xFE, + 0xC6, 0x0E, 0x7C, 0x1F, 0xC0, 0x1D, 0xA9, 0x0B, 0x38, 0xFD, 0x18, + 0xFC, 0xFD, 0xFF, 0xE6, 0x00, 0x1D, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, + 0x57, 0x00, 0xFA, 0x00, 0xD6, 0xFE, 0x4C, 0xFB, 0x92, 0x00, 0x54, + 0x12, 0xD6, 0x20, 0x41, 0x1B, 0x49, 0x08, 0x20, 0xFC, 0xC3, 0xFC, + 0x6D, 0x00, 0xC6, 0x00, 0x09, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7E, + 0x00, 0xE2, 0x00, 0x1A, 0xFE, 0x44, 0xFB, 0x03, 0x03, 0xCA, 0x15, + 0x7C, 0x21, 0x4C, 0x18, 0x2B, 0x05, 0x7B, 0xFB, 0x83, 0xFD, 0xBC, + 0x00, 0x9E, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xA8, 0x00, 0xAD, 0x00, 0x56, 0xFD, 0x98, 0xFB, 0xDC, 0x05, 0x04, + 0x19, 0x66, 0x21, 0x02, 0x15, 0x69, 0x02, 0x3E, 0xFB, 0x47, 0xFE, + 0xEA, 0x00, 0x75, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0D, 0x00, 0xCE, + 0x00, 0x56, 0x00, 0x99, 0xFC, 0x56, 0xFC, 0x0B, 0x09, 0xE0, 0x1B, + 0x96, 0x20, 0x83, 0x11, 0x11, 0x00, 0x59, 0xFB, 0xFF, 0xFE, 0xFB, + 0x00, 0x4E, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x22, 0x00, 0xEC, 0x00, + 0xDE, 0xFF, 0xF5, 0xFB, 0x8A, 0xFD, 0x77, 0x0C, 0x3F, 0x1E, 0x14, + 0x1F, 0xF5, 0x0D, 0x31, 0xFE, 0xBB, 0xFB, 0xA2, 0xFF, 0xF5, 0x00, + 0x2E, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x40, 0x00, 0xFB, 0x00, 0x47, + 0xFF, 0x7B, 0xFB, 0x37, 0xFF, 0x02, 0x10, 0x07, 0x20, 0xF2, 0x1C, + 0x78, 0x0A, 0xCA, 0xFC, 0x50, 0xFC, 0x27, 0x00, 0xDC, 0x00, 0x15, + 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x64, 0x00, 0xF5, 0x00, 0x97, 0xFE, + 0x40, 0xFB, 0x5D, 0x01, 0x8B, 0x13, 0x25, 0x21, 0x47, 0x1A, 0x2C, + 0x07, 0xDB, 0xFB, 0x05, 0xFD, 0x8C, 0x00, 0xB9, 0x00, 0x04, 0x00, + 0xFF, 0xFF, 0xFA, 0xFF, 0x8D, 0x00, 0xD3, 0x00, 0xD6, 0xFD, 0x56, + 0xFB, 0xF4, 0x03, 0xF0, 0x16, 0x8A, 0x21, 0x31, 0x17, 0x2B, 0x04, + 0x5B, 0xFB, 0xC7, 0xFD, 0xCF, 0x00, 0x90, 0x00, 0xFA, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0x03, 0x00, 0xB6, 0x00, 0x92, 0x00, 0x13, 0xFD, + 0xCD, 0xFB, 0xEE, 0x06, 0x0D, 0x1A, 0x33, 0x21, 0xD0, 0x13, 0x8C, + 0x01, 0x3E, 0xFB, 0x88, 0xFE, 0xF3, 0x00, 0x67, 0x00, 0xF7, 0xFF, + 0x06, 0x00, 0x1D, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0xA1, 0x02, 0xA6, + 0xF8, 0x56, 0x02, 0xA5, 0x28, 0xA5, 0x28, 0x56, 0x02, 0xA6, 0xF8, + 0xA1, 0x02, 0xFE, 0x00, 0x03, 0xFF, 0x1D, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x21, 0x00, 0xA6, 0xFF, 0x3F, 0xFF, 0x0B, 0x03, 0x42, 0xFE, + 0x3E, 0xF8, 0x7F, 0x15, 0xAC, 0x30, 0x7F, 0x15, 0x3E, 0xF8, 0x42, + 0xFE, 0x0B, 0x03, 0x3F, 0xFF, 0xA6, 0xFF, 0x21, 0x00, 0x00, 0x00, + 0xFA, 0xFF, 0xCE, 0xFF, 0x14, 0x01, 0x00, 0xFD, 0x35, 0x06, 0xD5, + 0xF4, 0xDA, 0x15, 0x92, 0x40, 0xAE, 0xFE, 0xF3, 0xFC, 0x68, 0x03, + 0x86, 0xFD, 0x51, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xEC, + 0xFF, 0xF9, 0xFF, 0xC6, 0x00, 0x55, 0xFD, 0x35, 0x06, 0x90, 0xF3, + 0xE5, 0x1C, 0x6B, 0x3D, 0x71, 0xFA, 0x34, 0xFF, 0x46, 0x02, 0xFF, + 0xFD, 0x2D, 0x01, 0x90, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDB, 0xFF, + 0x2D, 0x00, 0x60, 0x00, 0xE1, 0xFD, 0xCE, 0x05, 0xED, 0xF2, 0xF3, + 0x23, 0x20, 0x39, 0x22, 0xF7, 0x44, 0x01, 0x1F, 0x01, 0x89, 0xFE, + 0xFB, 0x00, 0x9C, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC9, 0xFF, 0x68, + 0x00, 0xE5, 0xFF, 0xA0, 0xFE, 0xFB, 0x04, 0x0C, 0xF3, 0xC5, 0x2A, + 0xD8, 0x33, 0xC9, 0xF4, 0x0B, 0x03, 0x05, 0x00, 0x1A, 0xFF, 0xC1, + 0x00, 0xAD, 0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB5, 0xFF, 0xA5, 0x00, + 0x5C, 0xFF, 0x8C, 0xFF, 0xBF, 0x03, 0x06, 0xF4, 0x22, 0x31, 0xC8, + 0x2D, 0x63, 0xF3, 0x76, 0x04, 0x08, 0xFF, 0xA7, 0xFF, 0x84, 0x00, + 0xC0, 0xFF, 0x07, 0x00, 0x0C, 0x00, 0xA4, 0xFF, 0xE1, 0x00, 0xCB, + 0xFE, 0x9B, 0x00, 0x21, 0x02, 0xEE, 0xF5, 0xCD, 0x36, 0x24, 0x27, + 0xE1, 0xF2, 0x7A, 0x05, 0x33, 0xFE, 0x2A, 0x00, 0x47, 0x00, 0xD3, + 0xFF, 0x04, 0x00, 0x0F, 0x00, 0x95, 0xFF, 0x17, 0x01, 0x3D, 0xFE, + 0xBD, 0x01, 0x30, 0x00, 0xCC, 0xF8, 0x92, 0x3B, 0x2A, 0x20, 0x2E, + 0xF3, 0x12, 0x06, 0x8F, 0xFD, 0x9A, 0x00, 0x10, 0x00, 0xE5, 0xFF, + 0x02, 0x00, 0x10, 0x00, 0x8C, 0xFF, 0x42, 0x01, 0xBB, 0xFD, 0xE4, + 0x02, 0x01, 0xFE, 0x9C, 0xFC, 0x45, 0x3F, 0x16, 0x19, 0x2D, 0xF4, + 0x41, 0x06, 0x21, 0xFD, 0xF3, 0x00, 0xE0, 0xFF, 0xF4, 0xFF, 0x01, + 0x00, 0x10, 0x00, 0x8B, 0xFF, 0x5D, 0x01, 0x4F, 0xFD, 0xFB, 0x03, + 0xB2, 0xFB, 0x53, 0x01, 0xC2, 0x41, 0x24, 0x12, 0xBA, 0xF5, 0x0F, + 0x06, 0xE9, 0xFC, 0x33, 0x01, 0xBB, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x0D, 0x00, 0x93, 0xFF, 0x63, 0x01, 0x04, 0xFD, 0xEF, 0x04, 0x62, + 0xF9, 0xD7, 0x06, 0xF2, 0x42, 0x8D, 0x0B, 0xB0, 0xF7, 0x87, 0x05, + 0xE6, 0xFC, 0x58, 0x01, 0xA0, 0xFF, 0x09, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0x00, 0xA5, 0xFF, 0x52, 0x01, 0xE2, 0xFC, 0xAD, 0x05, + 0x35, 0xF7, 0x08, 0x0D, 0xCB, 0x42, 0x81, 0x05, 0xE8, 0xF9, 0xBB, + 0x04, 0x12, 0xFD, 0x64, 0x01, 0x90, 0xFF, 0x0E, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0xC2, 0xFF, 0x27, 0x01, 0xF1, 0xFC, 0x22, 0x06, 0x54, + 0xF5, 0xB8, 0x13, 0x4A, 0x41, 0x29, 0x00, 0x3C, 0xFC, 0xBD, 0x03, + 0x66, 0xFD, 0x58, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xF1, + 0xFF, 0xEB, 0xFF, 0xE1, 0x00, 0x35, 0xFD, 0x40, 0x06, 0xE4, 0xF3, + 0xB7, 0x1A, 0x85, 0x3E, 0xA6, 0xFB, 0x86, 0xFE, 0xA0, 0x02, 0xD7, + 0xFD, 0x39, 0x01, 0x8E, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xE1, 0xFF, + 0x1C, 0x00, 0x82, 0x00, 0xB0, 0xFD, 0xF9, 0x05, 0x0C, 0xF3, 0xCB, + 0x21, 0x8F, 0x3A, 0x0D, 0xF8, 0xA9, 0x00, 0x79, 0x01, 0x5D, 0xFE, + 0x0B, 0x01, 0x98, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCE, 0xFF, 0x55, + 0x00, 0x0D, 0x00, 0x60, 0xFE, 0x48, 0x05, 0xEC, 0xF2, 0xB6, 0x28, + 0x91, 0x35, 0x68, 0xF5, 0x88, 0x02, 0x5A, 0x00, 0xED, 0xFE, 0xD4, + 0x00, 0xA8, 0xFF, 0x0B, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0x92, 0x00, + 0x87, 0xFF, 0x3F, 0xFF, 0x2B, 0x04, 0xA1, 0xF3, 0x3D, 0x2F, 0xB8, + 0x2F, 0xB8, 0xF3, 0x11, 0x04, 0x52, 0xFF, 0x7C, 0xFF, 0x97, 0x00, + 0xBA, 0xFF, 0x08, 0x00, 0x0B, 0x00, 0xA9, 0xFF, 0xCF, 0x00, 0xF8, + 0xFE, 0x44, 0x00, 0xAA, 0x02, 0x3E, 0xF5, 0x24, 0x35, 0x3B, 0x29, + 0xF2, 0xF2, 0x35, 0x05, 0x70, 0xFE, 0x03, 0x00, 0x5A, 0x00, 0xCD, + 0xFF, 0x05, 0x00, 0x0E, 0x00, 0x99, 0xFF, 0x07, 0x01, 0x68, 0xFE, + 0x63, 0x01, 0xD0, 0x00, 0xD0, 0xF7, 0x35, 0x3A, 0x55, 0x22, 0x02, + 0xF3, 0xEF, 0x05, 0xBC, 0xFD, 0x7A, 0x00, 0x20, 0x00, 0xDF, 0xFF, + 0x03, 0x00, 0x10, 0x00, 0x8E, 0xFF, 0x36, 0x01, 0xE1, 0xFD, 0x8A, + 0x02, 0xB2, 0xFE, 0x56, 0xFB, 0x40, 0x3E, 0x42, 0x1B, 0xCE, 0xF3, + 0x3E, 0x06, 0x3D, 0xFD, 0xDB, 0x00, 0xEE, 0xFF, 0xF0, 0xFF, 0x01, + 0x00, 0x11, 0x00, 0x8A, 0xFF, 0x57, 0x01, 0x6D, 0xFD, 0xA8, 0x03, + 0x69, 0xFC, 0xC8, 0xFF, 0x20, 0x41, 0x40, 0x14, 0x33, 0xF5, 0x28, + 0x06, 0xF5, 0xFC, 0x22, 0x01, 0xC5, 0xFF, 0xFD, 0xFF, 0x00, 0x00, + 0x0F, 0x00, 0x8F, 0xFF, 0x64, 0x01, 0x17, 0xFD, 0xA9, 0x04, 0x16, + 0xFA, 0x10, 0x05, 0xB8, 0x42, 0x87, 0x0D, 0x0D, 0xF7, 0xB9, 0x05, + 0xE2, 0xFC, 0x50, 0x01, 0xA7, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0A, 0x00, 0x9E, 0xFF, 0x5A, 0x01, 0xE8, 0xFC, 0x7A, 0x05, + 0xDA, 0xF7, 0x10, 0x0B, 0xFB, 0x42, 0x4B, 0x07, 0x35, 0xF9, 0x00, + 0x05, 0x00, 0xFD, 0x63, 0x01, 0x94, 0xFF, 0x0D, 0x00, 0x00, 0x00, + 0x01, 0x00, 0xB8, 0xFF, 0x37, 0x01, 0xE7, 0xFC, 0x07, 0x06, 0xDE, + 0xF5, 0x9F, 0x11, 0xE4, 0x41, 0xB8, 0x01, 0x84, 0xFB, 0x0F, 0x04, + 0x48, 0xFD, 0x5E, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF5, + 0xFF, 0xDD, 0xFF, 0xF9, 0x00, 0x1B, 0xFD, 0x41, 0x06, 0x47, 0xF4, + 0x8B, 0x18, 0x81, 0x3F, 0xF1, 0xFC, 0xD5, 0xFD, 0xFA, 0x02, 0xB2, + 0xFD, 0x45, 0x01, 0x8C, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE6, 0xFF, + 0x0C, 0x00, 0xA2, 0x00, 0x85, 0xFD, 0x1A, 0x06, 0x3C, 0xF3, 0x9F, + 0x1F, 0xE6, 0x3B, 0x0E, 0xF9, 0x07, 0x00, 0xD4, 0x01, 0x33, 0xFE, + 0x1B, 0x01, 0x94, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD4, 0xFF, 0x43, + 0x00, 0x33, 0x00, 0x25, 0xFE, 0x89, 0x05, 0xE0, 0xF2, 0x9C, 0x26, + 0x33, 0x37, 0x1E, 0xF6, 0xFD, 0x01, 0xB0, 0x00, 0xC0, 0xFE, 0xE6, + 0x00, 0xA2, 0xFF, 0x0C, 0x00, 0x07, 0x00, 0xC1, 0xFF, 0x7F, 0x00, + 0xB2, 0xFF, 0xF6, 0xFE, 0x8E, 0x04, 0x51, 0xF3, 0x49, 0x2D, 0x98, + 0x31, 0x23, 0xF4, 0xA2, 0x03, 0xA0, 0xFF, 0x51, 0xFF, 0xAA, 0x00, + 0xB4, 0xFF, 0x09, 0x00, 0x0A, 0x00, 0xAE, 0xFF, 0xBD, 0x00, 0x25, + 0xFF, 0xF1, 0xFF, 0x2B, 0x03, 0xA5, 0xF4, 0x68, 0x33, 0x48, 0x2B, + 0x17, 0xF3, 0xE7, 0x04, 0xB1, 0xFE, 0xDB, 0xFF, 0x6C, 0x00, 0xC7, + 0xFF, 0x06, 0x00, 0x0D, 0x00, 0x9E, 0xFF, 0xF7, 0x00, 0x94, 0xFE, + 0x09, 0x01, 0x6A, 0x01, 0xEB, 0xF6, 0xC1, 0x38, 0x7D, 0x24, 0xE8, + 0xF2, 0xC1, 0x05, 0xEE, 0xFD, 0x57, 0x00, 0x31, 0x00, 0xDA, 0xFF, + 0x03, 0x00, 0x10, 0x00, 0x91, 0xFF, 0x29, 0x01, 0x09, 0xFE, 0x2F, + 0x02, 0x5F, 0xFF, 0x27, 0xFA, 0x20, 0x3D, 0x70, 0x1D, 0x7D, 0xF3, + 0x31, 0x06, 0x5E, 0xFD, 0xBF, 0x00, 0xFD, 0xFF, 0xEB, 0xFF, 0x02, + 0x00, 0x11, 0x00, 0x8B, 0xFF, 0x4E, 0x01, 0x8E, 0xFD, 0x52, 0x03, + 0x20, 0xFD, 0x52, 0xFE, 0x60, 0x40, 0x63, 0x16, 0xB7, 0xF4, 0x39, + 0x06, 0x05, 0xFD, 0x0F, 0x01, 0xD1, 0xFF, 0xF9, 0xFF, 0x00, 0x00, + 0x10, 0x00, 0x8D, 0xFF, 0x62, 0x01, 0x2E, 0xFD, 0x5E, 0x04, 0xCC, + 0xFA, 0x5B, 0x03, 0x5E, 0x42, 0x8E, 0x0F, 0x71, 0xF6, 0xE4, 0x05, + 0xE2, 0xFC, 0x45, 0x01, 0xAF, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0B, 0x00, 0x99, 0xFF, 0x60, 0x01, 0xF2, 0xFC, 0x40, 0x05, + 0x85, 0xF8, 0x26, 0x09, 0x0C, 0x43, 0x26, 0x09, 0x85, 0xF8, 0x40, + 0x05, 0xF2, 0xFC, 0x60, 0x01, 0x99, 0xFF, 0x0B, 0x00, 0x00, 0x00, + 0x04, 0x00, 0xAF, 0xFF, 0x45, 0x01, 0xE2, 0xFC, 0xE4, 0x05, 0x71, + 0xF6, 0x8E, 0x0F, 0x5E, 0x42, 0x5B, 0x03, 0xCC, 0xFA, 0x5E, 0x04, + 0x2E, 0xFD, 0x62, 0x01, 0x8D, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xF9, + 0xFF, 0xD1, 0xFF, 0x0F, 0x01, 0x05, 0xFD, 0x39, 0x06, 0xB7, 0xF4, + 0x63, 0x16, 0x60, 0x40, 0x52, 0xFE, 0x20, 0xFD, 0x52, 0x03, 0x8E, + 0xFD, 0x4E, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xEB, 0xFF, + 0xFD, 0xFF, 0xBF, 0x00, 0x5E, 0xFD, 0x31, 0x06, 0x7D, 0xF3, 0x70, + 0x1D, 0x20, 0x3D, 0x27, 0xFA, 0x5F, 0xFF, 0x2F, 0x02, 0x09, 0xFE, + 0x29, 0x01, 0x91, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDA, 0xFF, 0x31, + 0x00, 0x57, 0x00, 0xEE, 0xFD, 0xC1, 0x05, 0xE8, 0xF2, 0x7D, 0x24, + 0xC1, 0x38, 0xEB, 0xF6, 0x6A, 0x01, 0x09, 0x01, 0x94, 0xFE, 0xF7, + 0x00, 0x9E, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC7, 0xFF, 0x6C, 0x00, + 0xDB, 0xFF, 0xB1, 0xFE, 0xE7, 0x04, 0x17, 0xF3, 0x48, 0x2B, 0x68, + 0x33, 0xA5, 0xF4, 0x2B, 0x03, 0xF1, 0xFF, 0x25, 0xFF, 0xBD, 0x00, + 0xAE, 0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB4, 0xFF, 0xAA, 0x00, 0x51, + 0xFF, 0xA0, 0xFF, 0xA2, 0x03, 0x23, 0xF4, 0x98, 0x31, 0x49, 0x2D, + 0x51, 0xF3, 0x8E, 0x04, 0xF6, 0xFE, 0xB2, 0xFF, 0x7F, 0x00, 0xC1, + 0xFF, 0x07, 0x00, 0x0C, 0x00, 0xA2, 0xFF, 0xE6, 0x00, 0xC0, 0xFE, + 0xB0, 0x00, 0xFD, 0x01, 0x1E, 0xF6, 0x33, 0x37, 0x9C, 0x26, 0xE0, + 0xF2, 0x89, 0x05, 0x25, 0xFE, 0x33, 0x00, 0x43, 0x00, 0xD4, 0xFF, + 0x04, 0x00, 0x0F, 0x00, 0x94, 0xFF, 0x1B, 0x01, 0x33, 0xFE, 0xD4, + 0x01, 0x07, 0x00, 0x0E, 0xF9, 0xE6, 0x3B, 0x9F, 0x1F, 0x3C, 0xF3, + 0x1A, 0x06, 0x85, 0xFD, 0xA2, 0x00, 0x0C, 0x00, 0xE6, 0xFF, 0x02, + 0x00, 0x11, 0x00, 0x8C, 0xFF, 0x45, 0x01, 0xB2, 0xFD, 0xFA, 0x02, + 0xD5, 0xFD, 0xF1, 0xFC, 0x81, 0x3F, 0x8B, 0x18, 0x47, 0xF4, 0x41, + 0x06, 0x1B, 0xFD, 0xF9, 0x00, 0xDD, 0xFF, 0xF5, 0xFF, 0x01, 0x00, + 0x10, 0x00, 0x8B, 0xFF, 0x5E, 0x01, 0x48, 0xFD, 0x0F, 0x04, 0x84, + 0xFB, 0xB8, 0x01, 0xE4, 0x41, 0x9F, 0x11, 0xDE, 0xF5, 0x07, 0x06, + 0xE7, 0xFC, 0x37, 0x01, 0xB8, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x0D, + 0x00, 0x94, 0xFF, 0x63, 0x01, 0x00, 0xFD, 0x00, 0x05, 0x35, 0xF9, + 0x4B, 0x07, 0xFB, 0x42, 0x10, 0x0B, 0xDA, 0xF7, 0x7A, 0x05, 0xE8, + 0xFC, 0x5A, 0x01, 0x9E, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0xA7, 0xFF, 0x50, 0x01, 0xE2, 0xFC, 0xB9, 0x05, 0x0D, + 0xF7, 0x87, 0x0D, 0xB8, 0x42, 0x10, 0x05, 0x16, 0xFA, 0xA9, 0x04, + 0x17, 0xFD, 0x64, 0x01, 0x8F, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0xC5, 0xFF, 0x22, 0x01, 0xF5, 0xFC, 0x28, 0x06, 0x33, 0xF5, + 0x40, 0x14, 0x20, 0x41, 0xC8, 0xFF, 0x69, 0xFC, 0xA8, 0x03, 0x6D, + 0xFD, 0x57, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xF0, 0xFF, + 0xEE, 0xFF, 0xDB, 0x00, 0x3D, 0xFD, 0x3E, 0x06, 0xCE, 0xF3, 0x42, + 0x1B, 0x40, 0x3E, 0x56, 0xFB, 0xB2, 0xFE, 0x8A, 0x02, 0xE1, 0xFD, + 0x36, 0x01, 0x8E, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDF, 0xFF, 0x20, + 0x00, 0x7A, 0x00, 0xBC, 0xFD, 0xEF, 0x05, 0x02, 0xF3, 0x55, 0x22, + 0x35, 0x3A, 0xD0, 0xF7, 0xD0, 0x00, 0x63, 0x01, 0x68, 0xFE, 0x07, + 0x01, 0x99, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCD, 0xFF, 0x5A, 0x00, + 0x03, 0x00, 0x70, 0xFE, 0x35, 0x05, 0xF2, 0xF2, 0x3B, 0x29, 0x24, + 0x35, 0x3E, 0xF5, 0xAA, 0x02, 0x44, 0x00, 0xF8, 0xFE, 0xCF, 0x00, + 0xA9, 0xFF, 0x0B, 0x00, 0x08, 0x00, 0xBA, 0xFF, 0x97, 0x00, 0x7C, + 0xFF, 0x52, 0xFF, 0x11, 0x04, 0xB8, 0xF3, 0xB8, 0x2F, 0x3D, 0x2F, + 0xA1, 0xF3, 0x2B, 0x04, 0x3F, 0xFF, 0x87, 0xFF, 0x92, 0x00, 0xBB, + 0xFF, 0x08, 0x00, 0x0B, 0x00, 0xA8, 0xFF, 0xD4, 0x00, 0xED, 0xFE, + 0x5A, 0x00, 0x88, 0x02, 0x68, 0xF5, 0x91, 0x35, 0xB6, 0x28, 0xEC, + 0xF2, 0x48, 0x05, 0x60, 0xFE, 0x0D, 0x00, 0x55, 0x00, 0xCE, 0xFF, + 0x05, 0x00, 0x0E, 0x00, 0x98, 0xFF, 0x0B, 0x01, 0x5D, 0xFE, 0x79, + 0x01, 0xA9, 0x00, 0x0D, 0xF8, 0x8F, 0x3A, 0xCB, 0x21, 0x0C, 0xF3, + 0xF9, 0x05, 0xB0, 0xFD, 0x82, 0x00, 0x1C, 0x00, 0xE1, 0xFF, 0x03, + 0x00, 0x10, 0x00, 0x8E, 0xFF, 0x39, 0x01, 0xD7, 0xFD, 0xA0, 0x02, + 0x86, 0xFE, 0xA6, 0xFB, 0x85, 0x3E, 0xB7, 0x1A, 0xE4, 0xF3, 0x40, + 0x06, 0x35, 0xFD, 0xE1, 0x00, 0xEB, 0xFF, 0xF1, 0xFF, 0x01, 0x00, + 0x11, 0x00, 0x8A, 0xFF, 0x58, 0x01, 0x66, 0xFD, 0xBD, 0x03, 0x3C, + 0xFC, 0x29, 0x00, 0x4A, 0x41, 0xB8, 0x13, 0x54, 0xF5, 0x22, 0x06, + 0xF1, 0xFC, 0x27, 0x01, 0xC2, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0x0E, + 0x00, 0x90, 0xFF, 0x64, 0x01, 0x12, 0xFD, 0xBB, 0x04, 0xE8, 0xF9, + 0x81, 0x05, 0xCB, 0x42, 0x08, 0x0D, 0x35, 0xF7, 0xAD, 0x05, 0xE2, + 0xFC, 0x52, 0x01, 0xA5, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0xA0, 0xFF, 0x58, 0x01, 0xE6, 0xFC, 0x87, 0x05, 0xB0, + 0xF7, 0x8D, 0x0B, 0xF2, 0x42, 0xD7, 0x06, 0x62, 0xF9, 0xEF, 0x04, + 0x04, 0xFD, 0x63, 0x01, 0x93, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xBB, 0xFF, 0x33, 0x01, 0xE9, 0xFC, 0x0F, 0x06, 0xBA, 0xF5, + 0x24, 0x12, 0xC2, 0x41, 0x53, 0x01, 0xB2, 0xFB, 0xFB, 0x03, 0x4F, + 0xFD, 0x5D, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF4, 0xFF, + 0xE0, 0xFF, 0xF3, 0x00, 0x21, 0xFD, 0x41, 0x06, 0x2D, 0xF4, 0x16, + 0x19, 0x45, 0x3F, 0x9C, 0xFC, 0x01, 0xFE, 0xE4, 0x02, 0xBB, 0xFD, + 0x42, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE5, 0xFF, 0x10, + 0x00, 0x9A, 0x00, 0x8F, 0xFD, 0x12, 0x06, 0x2E, 0xF3, 0x2A, 0x20, + 0x92, 0x3B, 0xCC, 0xF8, 0x30, 0x00, 0xBD, 0x01, 0x3D, 0xFE, 0x17, + 0x01, 0x95, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD3, 0xFF, 0x47, 0x00, + 0x2A, 0x00, 0x33, 0xFE, 0x7A, 0x05, 0xE1, 0xF2, 0x24, 0x27, 0xCD, + 0x36, 0xEE, 0xF5, 0x21, 0x02, 0x9B, 0x00, 0xCB, 0xFE, 0xE1, 0x00, + 0xA4, 0xFF, 0x0C, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0x84, 0x00, 0xA7, + 0xFF, 0x08, 0xFF, 0x76, 0x04, 0x63, 0xF3, 0xC8, 0x2D, 0x22, 0x31, + 0x06, 0xF4, 0xBF, 0x03, 0x8C, 0xFF, 0x5C, 0xFF, 0xA5, 0x00, 0xB5, + 0xFF, 0x09, 0x00, 0x0A, 0x00, 0xAD, 0xFF, 0xC1, 0x00, 0x1A, 0xFF, + 0x05, 0x00, 0x0B, 0x03, 0xC9, 0xF4, 0xD8, 0x33, 0xC5, 0x2A, 0x0C, + 0xF3, 0xFB, 0x04, 0xA0, 0xFE, 0xE5, 0xFF, 0x68, 0x00, 0xC9, 0xFF, + 0x06, 0x00, 0x0D, 0x00, 0x9C, 0xFF, 0xFB, 0x00, 0x89, 0xFE, 0x1F, + 0x01, 0x44, 0x01, 0x22, 0xF7, 0x20, 0x39, 0xF3, 0x23, 0xED, 0xF2, + 0xCE, 0x05, 0xE1, 0xFD, 0x60, 0x00, 0x2D, 0x00, 0xDB, 0xFF, 0x03, + 0x00, 0x10, 0x00, 0x90, 0xFF, 0x2D, 0x01, 0xFF, 0xFD, 0x46, 0x02, + 0x34, 0xFF, 0x71, 0xFA, 0x6B, 0x3D, 0xE5, 0x1C, 0x90, 0xF3, 0x35, + 0x06, 0x55, 0xFD, 0xC6, 0x00, 0xF9, 0xFF, 0xEC, 0xFF, 0x01, 0x00, + 0x11, 0x00, 0x8B, 0xFF, 0x51, 0x01, 0x86, 0xFD, 0x68, 0x03, 0xF3, + 0xFC, 0xAE, 0xFE, 0x92, 0x40, 0xDA, 0x15, 0xD5, 0xF4, 0x35, 0x06, + 0x00, 0xFD, 0x14, 0x01, 0xCE, 0xFF, 0xFA, 0xFF, 0x00, 0x00, 0x0F, + 0x00, 0x8D, 0xFF, 0x63, 0x01, 0x28, 0xFD, 0x71, 0x04, 0x9E, 0xFA, + 0xC7, 0x03, 0x79, 0x42, 0x0B, 0x0F, 0x97, 0xF6, 0xDA, 0x05, 0xE2, + 0xFC, 0x48, 0x01, 0xAD, 0xFF, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0B, 0x00, 0x9A, 0xFF, 0x5F, 0x01, 0xEF, 0xFC, 0x4F, 0x05, 0x5A, + 0xF8, 0x9F, 0x09, 0x0A, 0x43, 0xAE, 0x08, 0xB1, 0xF8, 0x30, 0x05, + 0xF5, 0xFC, 0x61, 0x01, 0x97, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x03, + 0x00, 0xB1, 0xFF, 0x41, 0x01, 0xE3, 0xFC, 0xED, 0x05, 0x4C, 0xF6, + 0x11, 0x10, 0x42, 0x42, 0xF1, 0x02, 0xFA, 0xFA, 0x4B, 0x04, 0x34, + 0xFD, 0x61, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF8, 0xFF, + 0xD4, 0xFF, 0x0A, 0x01, 0x0A, 0xFD, 0x3C, 0x06, 0x9A, 0xF4, 0xED, + 0x16, 0x2A, 0x40, 0xF8, 0xFD, 0x4D, 0xFD, 0x3C, 0x03, 0x97, 0xFD, + 0x4C, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xEA, 0xFF, 0x00, + 0x00, 0xB8, 0x00, 0x67, 0xFD, 0x2C, 0x06, 0x6B, 0xF3, 0xFC, 0x1D, + 0xD3, 0x3C, 0xDF, 0xF9, 0x89, 0xFF, 0x18, 0x02, 0x13, 0xFE, 0x26, + 0x01, 0x92, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD9, 0xFF, 0x36, 0x00, + 0x4E, 0x00, 0xFB, 0xFD, 0xB4, 0x05, 0xE4, 0xF2, 0x04, 0x25, 0x5F, + 0x38, 0xB6, 0xF6, 0x90, 0x01, 0xF3, 0x00, 0x9F, 0xFE, 0xF3, 0x00, + 0x9F, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC6, 0xFF, 0x71, 0x00, 0xD1, + 0xFF, 0xC2, 0xFE, 0xD1, 0x04, 0x23, 0xF3, 0xC9, 0x2B, 0xF5, 0x32, + 0x83, 0xF4, 0x49, 0x03, 0xDC, 0xFF, 0x30, 0xFF, 0xB8, 0x00, 0xB0, + 0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB3, 0xFF, 0xAE, 0x00, 0x46, 0xFF, + 0xB4, 0xFF, 0x85, 0x03, 0x42, 0xF4, 0x0E, 0x32, 0xCA, 0x2C, 0x41, + 0xF3, 0xA5, 0x04, 0xE4, 0xFE, 0xBC, 0xFF, 0x7A, 0x00, 0xC3, 0xFF, + 0x07, 0x00, 0x0D, 0x00, 0xA1, 0xFF, 0xEA, 0x00, 0xB5, 0xFE, 0xC6, + 0x00, 0xD9, 0x01, 0x4F, 0xF6, 0x99, 0x37, 0x16, 0x26, 0xE0, 0xF2, + 0x98, 0x05, 0x16, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0xD6, 0xFF, 0x04, + 0x00, 0x0F, 0x00, 0x93, 0xFF, 0x1F, 0x01, 0x28, 0xFE, 0xEB, 0x01, + 0xDD, 0xFF, 0x52, 0xF9, 0x36, 0x3C, 0x13, 0x1F, 0x4B, 0xF3, 0x20, + 0x06, 0x7B, 0xFD, 0xA9, 0x00, 0x08, 0x00, 0xE7, 0xFF, 0x02, 0x00, + 0x11, 0x00, 0x8C, 0xFF, 0x47, 0x01, 0xA9, 0xFD, 0x10, 0x03, 0xA8, + 0xFD, 0x47, 0xFD, 0xBB, 0x3F, 0x01, 0x18, 0x62, 0xF4, 0x40, 0x06, + 0x15, 0xFD, 0xFF, 0x00, 0xDA, 0xFF, 0xF6, 0xFF, 0x01, 0x00, 0x10, + 0x00, 0x8B, 0xFF, 0x5F, 0x01, 0x41, 0xFD, 0x23, 0x04, 0x56, 0xFB, + 0x1F, 0x02, 0x06, 0x42, 0x19, 0x11, 0x02, 0xF6, 0xFF, 0x05, 0xE5, + 0xFC, 0x3B, 0x01, 0xB6, 0xFF, 0x02, 0x00, 0x00, 0x00, 0x0D, 0x00, + 0x95, 0xFF, 0x62, 0x01, 0xFC, 0xFC, 0x10, 0x05, 0x09, 0xF9, 0xC1, + 0x07, 0x03, 0x43, 0x94, 0x0A, 0x05, 0xF8, 0x6C, 0x05, 0xEA, 0xFC, + 0x5C, 0x01, 0x9D, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x00, 0xA9, 0xFF, 0x4D, 0x01, 0xE1, 0xFC, 0xC4, 0x05, 0xE6, 0xF6, + 0x08, 0x0E, 0xA5, 0x42, 0xA1, 0x04, 0x43, 0xFA, 0x97, 0x04, 0x1D, + 0xFD, 0x64, 0x01, 0x8F, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFC, 0xFF, + 0xC8, 0xFF, 0x1E, 0x01, 0xF8, 0xFC, 0x2D, 0x06, 0x13, 0xF5, 0xC8, + 0x14, 0xF2, 0x40, 0x69, 0xFF, 0x97, 0xFC, 0x92, 0x03, 0x75, 0xFD, + 0x55, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xEF, 0xFF, 0xF2, + 0xFF, 0xD4, 0x00, 0x45, 0xFD, 0x3B, 0x06, 0xB8, 0xF3, 0xCE, 0x1B, + 0xFB, 0x3D, 0x08, 0xFB, 0xDE, 0xFE, 0x73, 0x02, 0xEB, 0xFD, 0x33, + 0x01, 0x8F, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDE, 0xFF, 0x25, 0x00, + 0x71, 0x00, 0xC8, 0xFD, 0xE5, 0x05, 0xFA, 0xF2, 0xDF, 0x22, 0xDB, + 0x39, 0x94, 0xF7, 0xF7, 0x00, 0x4C, 0x01, 0x73, 0xFE, 0x03, 0x01, + 0x9A, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCC, 0xFF, 0x5E, 0x00, 0xF9, + 0xFF, 0x80, 0xFE, 0x23, 0x05, 0xF9, 0xF2, 0xC0, 0x29, 0xB8, 0x34, + 0x16, 0xF5, 0xCB, 0x02, 0x2F, 0x00, 0x03, 0xFF, 0xCA, 0x00, 0xAA, + 0xFF, 0x0B, 0x00, 0x08, 0x00, 0xB8, 0xFF, 0x9B, 0x00, 0x72, 0xFF, + 0x65, 0xFF, 0xF6, 0x03, 0xD1, 0xF3, 0x31, 0x30, 0xC1, 0x2E, 0x8B, + 0xF3, 0x45, 0x04, 0x2D, 0xFF, 0x92, 0xFF, 0x8D, 0x00, 0xBD, 0xFF, + 0x08, 0x00, 0x0C, 0x00, 0xA6, 0xFF, 0xD8, 0x00, 0xE2, 0xFE, 0x6F, + 0x00, 0x66, 0x02, 0x93, 0xF5, 0xFB, 0x35, 0x31, 0x28, 0xE7, 0xF2, + 0x59, 0x05, 0x51, 0xFE, 0x17, 0x00, 0x50, 0x00, 0xD0, 0xFF, 0x05, + 0x00, 0x0E, 0x00, 0x97, 0xFF, 0x0F, 0x01, 0x53, 0xFE, 0x90, 0x01, + 0x81, 0x00, 0x4B, 0xF8, 0xE6, 0x3A, 0x3F, 0x21, 0x16, 0xF3, 0x02, + 0x06, 0xA5, 0xFD, 0x8A, 0x00, 0x18, 0x00, 0xE2, 0xFF, 0x02, 0x00, + 0x10, 0x00, 0x8D, 0xFF, 0x3C, 0x01, 0xCE, 0xFD, 0xB7, 0x02, 0x5A, + 0xFE, 0xF7, 0xFB, 0xC6, 0x3E, 0x2C, 0x1A, 0xFC, 0xF3, 0x41, 0x06, + 0x2E, 0xFD, 0xE7, 0x00, 0xE7, 0xFF, 0xF2, 0xFF, 0x01, 0x00, 0x10, + 0x00, 0x8B, 0xFF, 0x5A, 0x01, 0x5E, 0xFD, 0xD2, 0x03, 0x0E, 0xFC, + 0x8B, 0x00, 0x75, 0x41, 0x32, 0x13, 0x75, 0xF5, 0x1C, 0x06, 0xEE, + 0xFC, 0x2B, 0x01, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0E, 0x00, + 0x91, 0xFF, 0x64, 0x01, 0x0D, 0xFD, 0xCD, 0x04, 0xBB, 0xF9, 0xF2, + 0x05, 0xD9, 0x42, 0x88, 0x0C, 0x5E, 0xF7, 0xA1, 0x05, 0xE3, 0xFC, + 0x54, 0x01, 0xA3, 0xFF, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, + 0x00, 0xA2, 0xFF, 0x56, 0x01, 0xE5, 0xFC, 0x94, 0x05, 0x87, 0xF7, + 0x0A, 0x0C, 0xE6, 0x42, 0x64, 0x06, 0x8E, 0xF9, 0xDE, 0x04, 0x09, + 0xFD, 0x64, 0x01, 0x92, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xBD, 0xFF, 0x2F, 0x01, 0xEC, 0xFC, 0x16, 0x06, 0x98, 0xF5, 0xAB, + 0x12, 0x9C, 0x41, 0xEE, 0x00, 0xE0, 0xFB, 0xE6, 0x03, 0x57, 0xFD, + 0x5B, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF3, 0xFF, 0xE4, + 0xFF, 0xED, 0x00, 0x27, 0xFD, 0x41, 0x06, 0x14, 0xF4, 0xA1, 0x19, + 0x06, 0x3F, 0x49, 0xFC, 0x2E, 0xFE, 0xCD, 0x02, 0xC4, 0xFD, 0x3F, + 0x01, 0x8D, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE3, 0xFF, 0x14, 0x00, + 0x92, 0x00, 0x9A, 0xFD, 0x0A, 0x06, 0x22, 0xF3, 0xB4, 0x20, 0x3C, + 0x3B, 0x8B, 0xF8, 0x58, 0x00, 0xA7, 0x01, 0x48, 0xFE, 0x13, 0x01, + 0x96, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD1, 0xFF, 0x4C, 0x00, 0x20, + 0x00, 0x42, 0xFE, 0x6A, 0x05, 0xE3, 0xF2, 0xAB, 0x27, 0x66, 0x36, + 0xC0, 0xF5, 0x44, 0x02, 0x85, 0x00, 0xD7, 0xFE, 0xDD, 0x00, 0xA5, + 0xFF, 0x0C, 0x00, 0x07, 0x00, 0xBE, 0xFF, 0x89, 0x00, 0x9D, 0xFF, + 0x1A, 0xFF, 0x5E, 0x04, 0x76, 0xF3, 0x45, 0x2E, 0xAA, 0x30, 0xEB, + 0xF3, 0xDB, 0x03, 0x79, 0xFF, 0x67, 0xFF, 0xA0, 0x00, 0xB7, 0xFF, + 0x09, 0x00, 0x0B, 0x00, 0xAC, 0xFF, 0xC6, 0x00, 0x0E, 0xFF, 0x1A, + 0x00, 0xEB, 0x02, 0xEF, 0xF4, 0x49, 0x34, 0x43, 0x2A, 0x02, 0xF3, + 0x0F, 0x05, 0x90, 0xFE, 0xEF, 0xFF, 0x63, 0x00, 0xCA, 0xFF, 0x06, + 0x00, 0x0E, 0x00, 0x9B, 0xFF, 0xFF, 0x00, 0x7E, 0xFE, 0x36, 0x01, + 0x1E, 0x01, 0x5B, 0xF7, 0x7E, 0x39, 0x69, 0x23, 0xF3, 0xF2, 0xD9, + 0x05, 0xD4, 0xFD, 0x69, 0x00, 0x29, 0x00, 0xDD, 0xFF, 0x03, 0x00, + 0x10, 0x00, 0x90, 0xFF, 0x30, 0x01, 0xF5, 0xFD, 0x5C, 0x02, 0x09, + 0xFF, 0xBC, 0xFA, 0xB5, 0x3D, 0x5A, 0x1C, 0xA3, 0xF3, 0x38, 0x06, + 0x4D, 0xFD, 0xCD, 0x00, 0xF5, 0xFF, 0xED, 0xFF, 0x01, 0x00, 0x11, + 0x00, 0x8B, 0xFF, 0x53, 0x01, 0x7E, 0xFD, 0x7D, 0x03, 0xC5, 0xFC, + 0x0B, 0xFF, 0xC3, 0x40, 0x51, 0x15, 0xF4, 0xF4, 0x31, 0x06, 0xFC, + 0xFC, 0x19, 0x01, 0xCB, 0xFF, 0xFB, 0xFF, 0x00, 0x00, 0x0F, 0x00, + 0x8E, 0xFF, 0x63, 0x01, 0x22, 0xFD, 0x84, 0x04, 0x71, 0xFA, 0x34, + 0x04, 0x90, 0x42, 0x89, 0x0E, 0xBE, 0xF6, 0xCF, 0x05, 0xE1, 0xFC, + 0x4A, 0x01, 0xAB, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, + 0x00, 0x9B, 0xFF, 0x5D, 0x01, 0xEC, 0xFC, 0x5D, 0x05, 0x2F, 0xF8, + 0x19, 0x0A, 0x07, 0x43, 0x37, 0x08, 0xDD, 0xF8, 0x21, 0x05, 0xF8, + 0xFC, 0x62, 0x01, 0x96, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, + 0xB4, 0xFF, 0x3E, 0x01, 0xE4, 0xFC, 0xF6, 0x05, 0x26, 0xF6, 0x95, + 0x10, 0x26, 0x42, 0x87, 0x02, 0x28, 0xFB, 0x37, 0x04, 0x3B, 0xFD, + 0x60, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF7, 0xFF, 0xD7, + 0xFF, 0x04, 0x01, 0x0F, 0xFD, 0x3E, 0x06, 0x7D, 0xF4, 0x76, 0x17, + 0xF4, 0x3F, 0x9F, 0xFD, 0x7B, 0xFD, 0x26, 0x03, 0xA0, 0xFD, 0x4A, + 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE9, 0xFF, 0x04, 0x00, + 0xB1, 0x00, 0x71, 0xFD, 0x26, 0x06, 0x5A, 0xF3, 0x88, 0x1E, 0x87, + 0x3C, 0x98, 0xF9, 0xB3, 0xFF, 0x02, 0x02, 0x1E, 0xFE, 0x22, 0x01, + 0x93, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD7, 0xFF, 0x3A, 0x00, 0x45, + 0x00, 0x09, 0xFE, 0xA7, 0x05, 0xE1, 0xF2, 0x8D, 0x25, 0xFD, 0x37, + 0x82, 0xF6, 0xB5, 0x01, 0xDC, 0x00, 0xAA, 0xFE, 0xEE, 0x00, 0xA0, + 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC4, 0xFF, 0x76, 0x00, 0xC7, 0xFF, + 0xD3, 0xFE, 0xBC, 0x04, 0x31, 0xF3, 0x4A, 0x2C, 0x83, 0x32, 0x61, + 0xF4, 0x68, 0x03, 0xC8, 0xFF, 0x3B, 0xFF, 0xB3, 0x00, 0xB1, 0xFF, + 0x0A, 0x00, 0x0A, 0x00, 0xB1, 0xFF, 0xB3, 0x00, 0x3B, 0xFF, 0xC8, + 0xFF, 0x68, 0x03, 0x61, 0xF4, 0x83, 0x32, 0x4A, 0x2C, 0x31, 0xF3, + 0xBC, 0x04, 0xD3, 0xFE, 0xC7, 0xFF, 0x76, 0x00, 0xC4, 0xFF, 0x06, + 0x00, 0x0D, 0x00, 0xA0, 0xFF, 0xEE, 0x00, 0xAA, 0xFE, 0xDC, 0x00, + 0xB5, 0x01, 0x82, 0xF6, 0xFD, 0x37, 0x8D, 0x25, 0xE1, 0xF2, 0xA7, + 0x05, 0x09, 0xFE, 0x45, 0x00, 0x3A, 0x00, 0xD7, 0xFF, 0x04, 0x00, + 0x0F, 0x00, 0x93, 0xFF, 0x22, 0x01, 0x1E, 0xFE, 0x02, 0x02, 0xB3, + 0xFF, 0x98, 0xF9, 0x87, 0x3C, 0x88, 0x1E, 0x5A, 0xF3, 0x26, 0x06, + 0x71, 0xFD, 0xB1, 0x00, 0x04, 0x00, 0xE9, 0xFF, 0x02, 0x00, 0x11, + 0x00, 0x8B, 0xFF, 0x4A, 0x01, 0xA0, 0xFD, 0x26, 0x03, 0x7B, 0xFD, + 0x9F, 0xFD, 0xF4, 0x3F, 0x76, 0x17, 0x7D, 0xF4, 0x3E, 0x06, 0x0F, + 0xFD, 0x04, 0x01, 0xD7, 0xFF, 0xF7, 0xFF, 0x01, 0x00, 0x10, 0x00, + 0x8C, 0xFF, 0x60, 0x01, 0x3B, 0xFD, 0x37, 0x04, 0x28, 0xFB, 0x87, + 0x02, 0x26, 0x42, 0x95, 0x10, 0x26, 0xF6, 0xF6, 0x05, 0xE4, 0xFC, + 0x3E, 0x01, 0xB4, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x96, + 0xFF, 0x62, 0x01, 0xF8, 0xFC, 0x21, 0x05, 0xDD, 0xF8, 0x37, 0x08, + 0x07, 0x43, 0x19, 0x0A, 0x2F, 0xF8, 0x5D, 0x05, 0xEC, 0xFC, 0x5D, + 0x01, 0x9B, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, + 0xAB, 0xFF, 0x4A, 0x01, 0xE1, 0xFC, 0xCF, 0x05, 0xBE, 0xF6, 0x89, + 0x0E, 0x90, 0x42, 0x34, 0x04, 0x71, 0xFA, 0x84, 0x04, 0x22, 0xFD, + 0x63, 0x01, 0x8E, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFB, 0xFF, 0xCB, + 0xFF, 0x19, 0x01, 0xFC, 0xFC, 0x31, 0x06, 0xF4, 0xF4, 0x51, 0x15, + 0xC3, 0x40, 0x0B, 0xFF, 0xC5, 0xFC, 0x7D, 0x03, 0x7E, 0xFD, 0x53, + 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xED, 0xFF, 0xF5, 0xFF, + 0xCD, 0x00, 0x4D, 0xFD, 0x38, 0x06, 0xA3, 0xF3, 0x5A, 0x1C, 0xB5, + 0x3D, 0xBC, 0xFA, 0x09, 0xFF, 0x5C, 0x02, 0xF5, 0xFD, 0x30, 0x01, + 0x90, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDD, 0xFF, 0x29, 0x00, 0x69, + 0x00, 0xD4, 0xFD, 0xD9, 0x05, 0xF3, 0xF2, 0x69, 0x23, 0x7E, 0x39, + 0x5B, 0xF7, 0x1E, 0x01, 0x36, 0x01, 0x7E, 0xFE, 0xFF, 0x00, 0x9B, + 0xFF, 0x0E, 0x00, 0x06, 0x00, 0xCA, 0xFF, 0x63, 0x00, 0xEF, 0xFF, + 0x90, 0xFE, 0x0F, 0x05, 0x02, 0xF3, 0x43, 0x2A, 0x49, 0x34, 0xEF, + 0xF4, 0xEB, 0x02, 0x1A, 0x00, 0x0E, 0xFF, 0xC6, 0x00, 0xAC, 0xFF, + 0x0B, 0x00, 0x09, 0x00, 0xB7, 0xFF, 0xA0, 0x00, 0x67, 0xFF, 0x79, + 0xFF, 0xDB, 0x03, 0xEB, 0xF3, 0xAA, 0x30, 0x45, 0x2E, 0x76, 0xF3, + 0x5E, 0x04, 0x1A, 0xFF, 0x9D, 0xFF, 0x89, 0x00, 0xBE, 0xFF, 0x07, + 0x00, 0x0C, 0x00, 0xA5, 0xFF, 0xDD, 0x00, 0xD7, 0xFE, 0x85, 0x00, + 0x44, 0x02, 0xC0, 0xF5, 0x66, 0x36, 0xAB, 0x27, 0xE3, 0xF2, 0x6A, + 0x05, 0x42, 0xFE, 0x20, 0x00, 0x4C, 0x00, 0xD1, 0xFF, 0x04, 0x00, + 0x0F, 0x00, 0x96, 0xFF, 0x13, 0x01, 0x48, 0xFE, 0xA7, 0x01, 0x58, + 0x00, 0x8B, 0xF8, 0x3C, 0x3B, 0xB4, 0x20, 0x22, 0xF3, 0x0A, 0x06, + 0x9A, 0xFD, 0x92, 0x00, 0x14, 0x00, 0xE3, 0xFF, 0x02, 0x00, 0x10, + 0x00, 0x8D, 0xFF, 0x3F, 0x01, 0xC4, 0xFD, 0xCD, 0x02, 0x2E, 0xFE, + 0x49, 0xFC, 0x06, 0x3F, 0xA1, 0x19, 0x14, 0xF4, 0x41, 0x06, 0x27, + 0xFD, 0xED, 0x00, 0xE4, 0xFF, 0xF3, 0xFF, 0x01, 0x00, 0x10, 0x00, + 0x8B, 0xFF, 0x5B, 0x01, 0x57, 0xFD, 0xE6, 0x03, 0xE0, 0xFB, 0xEE, + 0x00, 0x9C, 0x41, 0xAB, 0x12, 0x98, 0xF5, 0x16, 0x06, 0xEC, 0xFC, + 0x2F, 0x01, 0xBD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x92, + 0xFF, 0x64, 0x01, 0x09, 0xFD, 0xDE, 0x04, 0x8E, 0xF9, 0x64, 0x06, + 0xE6, 0x42, 0x0A, 0x0C, 0x87, 0xF7, 0x94, 0x05, 0xE5, 0xFC, 0x56, + 0x01, 0xA2, 0xFF, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0xA3, 0xFF, 0x54, 0x01, 0xE3, 0xFC, 0xA1, 0x05, 0x5E, 0xF7, 0x88, + 0x0C, 0xD9, 0x42, 0xF2, 0x05, 0xBB, 0xF9, 0xCD, 0x04, 0x0D, 0xFD, + 0x64, 0x01, 0x91, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, + 0xFF, 0x2B, 0x01, 0xEE, 0xFC, 0x1C, 0x06, 0x75, 0xF5, 0x32, 0x13, + 0x75, 0x41, 0x8B, 0x00, 0x0E, 0xFC, 0xD2, 0x03, 0x5E, 0xFD, 0x5A, + 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF2, 0xFF, 0xE7, 0xFF, + 0xE7, 0x00, 0x2E, 0xFD, 0x41, 0x06, 0xFC, 0xF3, 0x2C, 0x1A, 0xC6, + 0x3E, 0xF7, 0xFB, 0x5A, 0xFE, 0xB7, 0x02, 0xCE, 0xFD, 0x3C, 0x01, + 0x8D, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE2, 0xFF, 0x18, 0x00, 0x8A, + 0x00, 0xA5, 0xFD, 0x02, 0x06, 0x16, 0xF3, 0x3F, 0x21, 0xE6, 0x3A, + 0x4B, 0xF8, 0x81, 0x00, 0x90, 0x01, 0x53, 0xFE, 0x0F, 0x01, 0x97, + 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xD0, 0xFF, 0x50, 0x00, 0x17, 0x00, + 0x51, 0xFE, 0x59, 0x05, 0xE7, 0xF2, 0x31, 0x28, 0xFB, 0x35, 0x93, + 0xF5, 0x66, 0x02, 0x6F, 0x00, 0xE2, 0xFE, 0xD8, 0x00, 0xA6, 0xFF, + 0x0C, 0x00, 0x08, 0x00, 0xBD, 0xFF, 0x8D, 0x00, 0x92, 0xFF, 0x2D, + 0xFF, 0x45, 0x04, 0x8B, 0xF3, 0xC1, 0x2E, 0x31, 0x30, 0xD1, 0xF3, + 0xF6, 0x03, 0x65, 0xFF, 0x72, 0xFF, 0x9B, 0x00, 0xB8, 0xFF, 0x08, + 0x00, 0x0B, 0x00, 0xAA, 0xFF, 0xCA, 0x00, 0x03, 0xFF, 0x2F, 0x00, + 0xCB, 0x02, 0x16, 0xF5, 0xB8, 0x34, 0xC0, 0x29, 0xF9, 0xF2, 0x23, + 0x05, 0x80, 0xFE, 0xF9, 0xFF, 0x5E, 0x00, 0xCC, 0xFF, 0x05, 0x00, + 0x0E, 0x00, 0x9A, 0xFF, 0x03, 0x01, 0x73, 0xFE, 0x4C, 0x01, 0xF7, + 0x00, 0x94, 0xF7, 0xDB, 0x39, 0xDF, 0x22, 0xFA, 0xF2, 0xE5, 0x05, + 0xC8, 0xFD, 0x71, 0x00, 0x25, 0x00, 0xDE, 0xFF, 0x03, 0x00, 0x10, + 0x00, 0x8F, 0xFF, 0x33, 0x01, 0xEB, 0xFD, 0x73, 0x02, 0xDE, 0xFE, + 0x08, 0xFB, 0xFB, 0x3D, 0xCE, 0x1B, 0xB8, 0xF3, 0x3B, 0x06, 0x45, + 0xFD, 0xD4, 0x00, 0xF2, 0xFF, 0xEF, 0xFF, 0x01, 0x00, 0x11, 0x00, + 0x8A, 0xFF, 0x55, 0x01, 0x75, 0xFD, 0x92, 0x03, 0x97, 0xFC, 0x69, + 0xFF, 0xF2, 0x40, 0xC8, 0x14, 0x13, 0xF5, 0x2D, 0x06, 0xF8, 0xFC, + 0x1E, 0x01, 0xC8, 0xFF, 0xFC, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0x8F, + 0xFF, 0x64, 0x01, 0x1D, 0xFD, 0x97, 0x04, 0x43, 0xFA, 0xA1, 0x04, + 0xA5, 0x42, 0x08, 0x0E, 0xE6, 0xF6, 0xC4, 0x05, 0xE1, 0xFC, 0x4D, + 0x01, 0xA9, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, + 0x9D, 0xFF, 0x5C, 0x01, 0xEA, 0xFC, 0x6C, 0x05, 0x05, 0xF8, 0x94, + 0x0A, 0x03, 0x43, 0xC1, 0x07, 0x09, 0xF9, 0x10, 0x05, 0xFC, 0xFC, + 0x62, 0x01, 0x95, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x02, 0x00, 0xB6, + 0xFF, 0x3B, 0x01, 0xE5, 0xFC, 0xFF, 0x05, 0x02, 0xF6, 0x19, 0x11, + 0x06, 0x42, 0x1F, 0x02, 0x56, 0xFB, 0x23, 0x04, 0x41, 0xFD, 0x5F, + 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF6, 0xFF, 0xDA, 0xFF, + 0xFF, 0x00, 0x15, 0xFD, 0x40, 0x06, 0x62, 0xF4, 0x01, 0x18, 0xBB, + 0x3F, 0x47, 0xFD, 0xA8, 0xFD, 0x10, 0x03, 0xA9, 0xFD, 0x47, 0x01, + 0x8C, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE7, 0xFF, 0x08, 0x00, 0xA9, + 0x00, 0x7B, 0xFD, 0x20, 0x06, 0x4B, 0xF3, 0x13, 0x1F, 0x36, 0x3C, + 0x52, 0xF9, 0xDD, 0xFF, 0xEB, 0x01, 0x28, 0xFE, 0x1F, 0x01, 0x93, + 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD6, 0xFF, 0x3F, 0x00, 0x3C, 0x00, + 0x16, 0xFE, 0x98, 0x05, 0xE0, 0xF2, 0x16, 0x26, 0x99, 0x37, 0x4F, + 0xF6, 0xD9, 0x01, 0xC6, 0x00, 0xB5, 0xFE, 0xEA, 0x00, 0xA1, 0xFF, + 0x0D, 0x00, 0x07, 0x00, 0xC3, 0xFF, 0x7A, 0x00, 0xBC, 0xFF, 0xE4, + 0xFE, 0xA5, 0x04, 0x41, 0xF3, 0xCA, 0x2C, 0x0E, 0x32, 0x42, 0xF4, + 0x85, 0x03, 0xB4, 0xFF, 0x46, 0xFF, 0xAE, 0x00, 0xB3, 0xFF, 0x09, + 0x00, 0x0A, 0x00, 0xB0, 0xFF, 0xB8, 0x00, 0x30, 0xFF, 0xDC, 0xFF, + 0x49, 0x03, 0x83, 0xF4, 0xF5, 0x32, 0xC9, 0x2B, 0x23, 0xF3, 0xD1, + 0x04, 0xC2, 0xFE, 0xD1, 0xFF, 0x71, 0x00, 0xC6, 0xFF, 0x06, 0x00, + 0x0D, 0x00, 0x9F, 0xFF, 0xF3, 0x00, 0x9F, 0xFE, 0xF3, 0x00, 0x90, + 0x01, 0xB6, 0xF6, 0x5F, 0x38, 0x04, 0x25, 0xE4, 0xF2, 0xB4, 0x05, + 0xFB, 0xFD, 0x4E, 0x00, 0x36, 0x00, 0xD9, 0xFF, 0x04, 0x00, 0x0F, + 0x00, 0x92, 0xFF, 0x26, 0x01, 0x13, 0xFE, 0x18, 0x02, 0x89, 0xFF, + 0xDF, 0xF9, 0xD3, 0x3C, 0xFC, 0x1D, 0x6B, 0xF3, 0x2C, 0x06, 0x67, + 0xFD, 0xB8, 0x00, 0x00, 0x00, 0xEA, 0xFF, 0x02, 0x00, 0x11, 0x00, + 0x8B, 0xFF, 0x4C, 0x01, 0x97, 0xFD, 0x3C, 0x03, 0x4D, 0xFD, 0xF8, + 0xFD, 0x2A, 0x40, 0xED, 0x16, 0x9A, 0xF4, 0x3C, 0x06, 0x0A, 0xFD, + 0x0A, 0x01, 0xD4, 0xFF, 0xF8, 0xFF, 0x01, 0x00, 0x10, 0x00, 0x8C, + 0xFF, 0x61, 0x01, 0x34, 0xFD, 0x4B, 0x04, 0xFA, 0xFA, 0xF1, 0x02, + 0x42, 0x42, 0x11, 0x10, 0x4C, 0xF6, 0xED, 0x05, 0xE3, 0xFC, 0x41, + 0x01, 0xB1, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x97, 0xFF, + 0x61, 0x01, 0xF5, 0xFC, 0x30, 0x05, 0xB1, 0xF8, 0xAE, 0x08, 0x0A, + 0x43, 0x9F, 0x09, 0x5A, 0xF8, 0x4F, 0x05, 0xEF, 0xFC, 0x5F, 0x01, + 0x9A, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0xAD, + 0xFF, 0x48, 0x01, 0xE2, 0xFC, 0xDA, 0x05, 0x97, 0xF6, 0x0B, 0x0F, + 0x79, 0x42, 0xC7, 0x03, 0x9E, 0xFA, 0x71, 0x04, 0x28, 0xFD, 0x63, + 0x01, 0x8D, 0xFF, 0x0F, 0x00 +}; + +static u16 +CoefficientSizes[] = { + /* Playback */ + 0x00C0, 0x5000, 0x0060, 0x2800, 0x0040, 0x0060, 0x1400, 0x0000, + /* Record */ + 0x0020, 0x1260, 0x0020, 0x1260, 0x0000, 0x0040, 0x1260, 0x0000, +}; + +#ifndef JUST_DATA + +static u16 +nm256_getStartOffset (u8 which) +{ + u16 offset = 0; + + while (which-- > 0) + offset += CoefficientSizes[which]; + + return offset; +} + +static void +nm256_loadOneCoefficient (struct nm256_info *card, int devnum, u32 port, + u16 which) +{ + u32 coeffBuf = (which < 8) ? card->coeffBuf : card->allCoeffBuf; + u16 offset = nm256_getStartOffset (which); + u16 size = CoefficientSizes[which]; + + card->coeffsCurrent = 0; + + if (nm256_debug) + printk (KERN_INFO "NM256: Loading coefficient buffer 0x%x-0x%x with coefficient %d, size %d, port 0x%x\n", + coeffBuf, coeffBuf + size - 1, which, size, port); + nm256_writeBuffer8 (card, coefficients + offset, 1, coeffBuf, size); + nm256_writePort32 (card, 2, port + 0, coeffBuf); + /* ??? Record seems to behave differently than playback. */ + if (devnum == 0) + size--; + nm256_writePort32 (card, 2, port + 4, coeffBuf + size); +} + +static void +nm256_loadAllCoefficients (struct nm256_info *card) +{ + nm256_writeBuffer8 (card, coefficients, 1, card->allCoeffBuf, + NM_TOTAL_COEFF_COUNT * 4); + card->coeffsCurrent = 1; +} + +void +nm256_loadCoefficient (struct nm256_info *card, int which, int number) +{ + static u16 addrs[3] = { 0x1c, 0x21c, 0x408 }; + /* The enable register for the specified engine. */ + u32 poffset = (which == 1 ? 0x200 : 1); + + if (nm256_readPort8 (card, 2, poffset) & 1) { + printk (KERN_ERR "NM256: Engine was enabled while loading coefficients!\n"); + return; + } + + /* The recording engine uses coefficient values 8-15. */ + if (which == 1) + number += 8; + + if (! nm256_cachedCoefficients (card)) + nm256_loadOneCoefficient (card, which, addrs[which], number); + else { + u32 base = card->allCoeffBuf; + u32 offset = nm256_getStartOffset (number); + u32 endOffset = offset + CoefficientSizes[number]; + + if (nm256_debug) + printk (KERN_DEBUG "loading coefficient %d at port 0x%x, offset %d (0x%x-0x%x)\n", + number, addrs[which], offset, base + offset, + base + endOffset - 1); + + if (! card->coeffsCurrent) + nm256_loadAllCoefficients (card); + + nm256_writePort32 (card, 2, addrs[which], base + offset); + nm256_writePort32 (card, 2, addrs[which] + 4, base + endOffset - 1); + } +} + +#endif /* JUST_DATA */ + +#endif + +/* + * Local variables: + * c-basic-offset: 4 + * End: + */ diff --git a/sound/oss/opl3.c b/sound/oss/opl3.c new file mode 100644 index 000000000000..a31734b7842f --- /dev/null +++ b/sound/oss/opl3.c @@ -0,0 +1,1257 @@ +/* + * sound/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 +#include +#include + +/* + * Major improvements to the FM handling 30AUG92 by Rob Hooft, + * hooft@chem.ruu.nl + */ + +#include "sound_config.h" + +#include "opl3.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; + int *osp; +} 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; + } +} + +int opl3_detect(int ioaddr, int *osp) +{ + /* + * 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 = (struct opl_devinfo *)kmalloc(sizeof(*devc), GFP_KERNEL); + + if (devc == NULL) + { + printk(KERN_ERR "opl3: Can't allocate memory for the device control " + "structure \n "); + return 0; + } + + memset(devc, 0, sizeof(*devc)); + 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->osp = osp; + 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 offs, int count, int pmgr_flag) +{ + struct sbi_instrument ins; + + if (count = 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) +{ + 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 = + &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 +}; + +int opl3_init(int ioaddr, int *osp, 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; +} + +EXPORT_SYMBOL(opl3_init); +EXPORT_SYMBOL(opl3_detect); + +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, NULL)) + { + return -ENODEV; + } + + me = opl3_init(io, NULL, 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.h b/sound/oss/opl3.h new file mode 100644 index 000000000000..0bc9a4bcda13 --- /dev/null +++ b/sound/oss/opl3.h @@ -0,0 +1,5 @@ + +int opl3_detect (int ioaddr, int *osp); +int opl3_init(int ioaddr, int *osp, struct module *owner); + +void enable_opl3_mode(int left, int right, int both); diff --git a/sound/oss/opl3_hw.h b/sound/oss/opl3_hw.h new file mode 100644 index 000000000000..8b11c893e869 --- /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/opl3sa.c b/sound/oss/opl3sa.c new file mode 100644 index 000000000000..fe4907c6e8fc --- /dev/null +++ b/sound/oss/opl3sa.c @@ -0,0 +1,329 @@ +/* + * sound/opl3sa.c + * + * Low level driver for Yamaha YMF701B aka OPL3-SA 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 + * Christoph Hellwig Adapted to module_init/module_exit + * Arnaldo C. de Melo got rid of attach_uart401 + * + * FIXME: + * Check for install of mpu etc is wrong, should check result of the mss stuff + */ + +#include +#include +#include + +#undef SB_OK + +#include "sound_config.h" + +#include "ad1848.h" +#include "mpu401.h" + +#ifdef SB_OK +#include "sb.h" +static int sb_initialized; +#endif + +static DEFINE_SPINLOCK(lock); + +static unsigned char opl3sa_read(int addr) +{ + unsigned long flags; + unsigned char tmp; + + spin_lock_irqsave(&lock,flags); + outb((0x1d), 0xf86); /* password */ + outb(((unsigned char) addr), 0xf86); /* address */ + tmp = inb(0xf87); /* data */ + spin_unlock_irqrestore(&lock,flags); + + return tmp; +} + +static void opl3sa_write(int addr, int data) +{ + unsigned long flags; + + spin_lock_irqsave(&lock,flags); + outb((0x1d), 0xf86); /* password */ + outb(((unsigned char) addr), 0xf86); /* address */ + outb(((unsigned char) data), 0xf87); /* data */ + spin_unlock_irqrestore(&lock,flags); +} + +static int __init opl3sa_detect(void) +{ + int tmp; + + if (((tmp = opl3sa_read(0x01)) & 0xc4) != 0x04) + { + DDB(printk("OPL3-SA detect error 1 (%x)\n", opl3sa_read(0x01))); + /* return 0; */ + } + + /* + * Check that the password feature has any effect + */ + + if (inb(0xf87) == tmp) + { + DDB(printk("OPL3-SA detect failed 2 (%x/%x)\n", tmp, inb(0xf87))); + return 0; + } + tmp = (opl3sa_read(0x04) & 0xe0) >> 5; + + if (tmp != 0 && tmp != 1) + { + DDB(printk("OPL3-SA detect failed 3 (%d)\n", tmp)); + return 0; + } + DDB(printk("OPL3-SA mode %x detected\n", tmp)); + + opl3sa_write(0x01, 0x00); /* Disable MSS */ + opl3sa_write(0x02, 0x00); /* Disable SB */ + opl3sa_write(0x03, 0x00); /* Disable MPU */ + + return 1; +} + +/* + * Probe and attach routines for the Windows Sound System mode of + * OPL3-SA + */ + +static int __init probe_opl3sa_wss(struct address_info *hw_config, struct resource *ports) +{ + unsigned char tmp = 0x24; /* WSS enable */ + + /* + * Check if the IO port returns valid signature. The original MS Sound + * system returns 0x04 while some cards (OPL3-SA for example) + * return 0x00. + */ + + if (!opl3sa_detect()) + { + printk(KERN_ERR "OSS: OPL3-SA chip not found\n"); + return 0; + } + + switch (hw_config->io_base) + { + case 0x530: + tmp |= 0x00; + break; + case 0xe80: + tmp |= 0x08; + break; + case 0xf40: + tmp |= 0x10; + break; + case 0x604: + tmp |= 0x18; + break; + default: + printk(KERN_ERR "OSS: Unsupported OPL3-SA/WSS base %x\n", hw_config->io_base); + return 0; + } + + opl3sa_write(0x01, tmp); /* WSS setup register */ + + return probe_ms_sound(hw_config, ports); +} + +static void __init attach_opl3sa_wss(struct address_info *hw_config, struct resource *ports) +{ + int nm = num_mixers; + + /* FIXME */ + attach_ms_sound(hw_config, ports, THIS_MODULE); + if (num_mixers > nm) /* A mixer was installed */ + { + AD1848_REROUTE(SOUND_MIXER_LINE1, SOUND_MIXER_CD); + AD1848_REROUTE(SOUND_MIXER_LINE2, SOUND_MIXER_SYNTH); + AD1848_REROUTE(SOUND_MIXER_LINE3, SOUND_MIXER_LINE); + } +} + + +static int __init probe_opl3sa_mpu(struct address_info *hw_config) +{ + unsigned char conf; + static signed char irq_bits[] = { + -1, -1, -1, -1, -1, 1, -1, 2, -1, 3, 4 + }; + + if (hw_config->irq > 10) + { + printk(KERN_ERR "OPL3-SA: Bad MPU IRQ %d\n", hw_config->irq); + return 0; + } + if (irq_bits[hw_config->irq] == -1) + { + printk(KERN_ERR "OPL3-SA: Bad MPU IRQ %d\n", hw_config->irq); + return 0; + } + switch (hw_config->io_base) + { + case 0x330: + conf = 0x00; + break; + case 0x332: + conf = 0x20; + break; + case 0x334: + conf = 0x40; + break; + case 0x300: + conf = 0x60; + break; + default: + return 0; /* Invalid port */ + } + + conf |= 0x83; /* MPU & OPL3 (synth) & game port enable */ + conf |= irq_bits[hw_config->irq] << 2; + + opl3sa_write(0x03, conf); + + hw_config->name = "OPL3-SA (MPU401)"; + + return probe_uart401(hw_config, THIS_MODULE); +} + +static void __exit unload_opl3sa_wss(struct address_info *hw_config) +{ + int dma2 = hw_config->dma2; + + if (dma2 == -1) + dma2 = hw_config->dma; + + release_region(0xf86, 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_opl3sa_mpu(struct address_info *hw_config) +{ + unload_uart401(hw_config); +} + +#ifdef SB_OK +static inline void __exit unload_opl3sa_sb(struct address_info *hw_config) +{ + sb_dsp_unload(hw_config); +} +#endif + +static int found_mpu; + +static struct address_info cfg; +static struct address_info cfg_mpu; + +static int __initdata io = -1; +static int __initdata irq = -1; +static int __initdata dma = -1; +static int __initdata dma2 = -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(mpu_io, int, 0); +module_param(mpu_irq, int, 0); + +static int __init init_opl3sa(void) +{ + struct resource *ports; + if (io == -1 || irq == -1 || dma == -1) { + printk(KERN_ERR "opl3sa: dma, irq and io must be set.\n"); + return -EINVAL; + } + + cfg.io_base = io; + cfg.irq = irq; + cfg.dma = dma; + cfg.dma2 = dma2; + + cfg_mpu.io_base = mpu_io; + cfg_mpu.irq = mpu_irq; + + ports = request_region(io + 4, 4, "ad1848"); + if (!ports) + return -EBUSY; + + if (!request_region(0xf86, 2, "OPL3-SA"))/* Control port is busy */ { + release_region(io + 4, 4); + return 0; + } + + if (!request_region(io, 4, "WSS config")) { + release_region(0x86, 2); + release_region(io + 4, 4); + return 0; + } + + if (probe_opl3sa_wss(&cfg, ports) == 0) { + release_region(0xf86, 2); + release_region(io, 4); + release_region(io + 4, 4); + return -ENODEV; + } + + found_mpu=probe_opl3sa_mpu(&cfg_mpu); + + attach_opl3sa_wss(&cfg, ports); + return 0; +} + +static void __exit cleanup_opl3sa(void) +{ + if(found_mpu) + unload_opl3sa_mpu(&cfg_mpu); + unload_opl3sa_wss(&cfg); +} + +module_init(init_opl3sa); +module_exit(cleanup_opl3sa); + +#ifndef MODULE +static int __init setup_opl3sa(char *str) +{ + /* io, irq, dma, dma2, mpu_io, mpu_irq */ + int ints[7]; + + str = get_options(str, ARRAY_SIZE(ints), ints); + + io = ints[1]; + irq = ints[2]; + dma = ints[3]; + dma2 = ints[4]; + mpu_io = ints[5]; + mpu_irq = ints[6]; + + return 1; +} + +__setup("opl3sa=", setup_opl3sa); +#endif +MODULE_LICENSE("GPL"); diff --git a/sound/oss/opl3sa2.c b/sound/oss/opl3sa2.c new file mode 100644 index 000000000000..7b4996e71576 --- /dev/null +++ b/sound/oss/opl3sa2.c @@ -0,0 +1,1129 @@ +/* + * sound/opl3sa2.c + * + * A low level driver for Yamaha OPL3-SA2 and SA3 cards. + * NOTE: All traces of the name OPL3-SAx have now (December 2000) been + * removed from the driver code, as an email exchange with Yamaha + * provided the information that the YMF-719 is indeed just a + * re-badged 715. + * + * Copyright 1998-2001 Scott Murray + * + * Originally based on the CS4232 driver (in cs4232.c) by Hannu Savolainen + * and others. Now incorporates code/ideas from pss.c, also by Hannu + * Savolainen. Both of those files are distributed with the following + * license: + * + * "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." + * + * As such, in accordance with the above license, this file, opl3sa2.c, is + * distributed under the GNU GENERAL PUBLIC LICENSE (GPL) Version 2 (June 1991). + * See the "COPYING" file distributed with this software for more information. + * + * Change History + * -------------- + * Scott Murray Original driver (Jun 14, 1998) + * Paul J.Y. Lahaie Changed probing / attach code order + * Scott Murray Added mixer support (Dec 03, 1998) + * Scott Murray Changed detection code to be more forgiving, + * added force option as last resort, + * fixed ioctl return values. (Dec 30, 1998) + * Scott Murray Simpler detection code should work all the time now + * (with thanks to Ben Hutchings for the heuristic), + * removed now unnecessary force option. (Jan 5, 1999) + * Christoph Hellwig Adapted to module_init/module_exit (Mar 4, 2000) + * Scott Murray Reworked SA2 versus SA3 mixer code, updated chipset + * version detection code (again!). (Dec 5, 2000) + * Scott Murray Adjusted master volume mixer scaling. (Dec 6, 2000) + * Scott Murray Based on a patch by Joel Yliluoma (aka Bisqwit), + * integrated wide mixer and adjusted mic, bass, treble + * scaling. (Dec 6, 2000) + * Scott Murray Based on a patch by Peter Englmaier, integrated + * ymode and loopback options. (Dec 6, 2000) + * Scott Murray Inspired by a patch by Peter Englmaier, and based on + * what ALSA does, added initialization code for the + * default DMA and IRQ settings. (Dec 6, 2000) + * Scott Murray Added some more checks to the card detection code, + * based on what ALSA does. (Dec 12, 2000) + * Scott Murray Inspired by similar patches from John Fremlin, + * Jim Radford, Mike Rolig, and Ingmar Steen, added 2.4 + * ISA PnP API support, mainly based on bits from + * sb_card.c and awe_wave.c. (Dec 12, 2000) + * Scott Murray Some small cleanups to the init code output. + * (Jan 7, 2001) + * Zwane Mwaikambo Added PM support. (Dec 4 2001) + * + * Adam Belay Converted driver to new PnP Layer (Oct 12, 2002) + * Zwane Mwaikambo Code, data structure cleanups. (Feb 15 2002) + * Zwane Mwaikambo Free resources during auxiliary device probe + * failures (Apr 29 2002) + * + */ + +#include +#include +#include +#include +#include +#include +#include "sound_config.h" + +#include "ad1848.h" +#include "mpu401.h" + +#define OPL3SA2_MODULE_NAME "opl3sa2" +#define PFX OPL3SA2_MODULE_NAME ": " + +/* Useful control port indexes: */ +#define OPL3SA2_PM 0x01 +#define OPL3SA2_SYS_CTRL 0x02 +#define OPL3SA2_IRQ_CONFIG 0x03 +#define OPL3SA2_DMA_CONFIG 0x06 +#define OPL3SA2_MASTER_LEFT 0x07 +#define OPL3SA2_MASTER_RIGHT 0x08 +#define OPL3SA2_MIC 0x09 +#define OPL3SA2_MISC 0x0A + +#define OPL3SA3_WIDE 0x14 +#define OPL3SA3_BASS 0x15 +#define OPL3SA3_TREBLE 0x16 + +/* Useful constants: */ +#define DEFAULT_VOLUME 50 +#define DEFAULT_MIC 50 +#define DEFAULT_TIMBRE 0 + +/* Power saving modes */ +#define OPL3SA2_PM_MODE0 0x00 +#define OPL3SA2_PM_MODE1 0x04 /* PSV */ +#define OPL3SA2_PM_MODE2 0x05 /* PSV | PDX */ +#define OPL3SA2_PM_MODE3 0x27 /* ADOWN | PSV | PDN | PDX */ + + +/* For checking against what the card returns: */ +#define VERSION_UNKNOWN 0 +#define VERSION_YMF711 1 +#define VERSION_YMF715 2 +#define VERSION_YMF715B 3 +#define VERSION_YMF715E 4 +/* also assuming that anything > 4 but <= 7 is a 715E */ + +/* Chipset type constants for use below */ +#define CHIPSET_UNKNOWN -1 +#define CHIPSET_OPL3SA2 0 +#define CHIPSET_OPL3SA3 1 +static const char *CHIPSET_TABLE[] = {"OPL3-SA2", "OPL3-SA3"}; + +#ifdef CONFIG_PNP +#define OPL3SA2_CARDS_MAX 4 +#else +#define OPL3SA2_CARDS_MAX 1 +#endif + +/* This should be pretty obvious */ +static int opl3sa2_cards_num; + +typedef struct { + /* device resources */ + unsigned short cfg_port; + struct address_info cfg; + struct address_info cfg_mss; + struct address_info cfg_mpu; +#ifdef CONFIG_PNP + /* PnP Stuff */ + struct pnp_dev* pdev; + int activated; /* Whether said devices have been activated */ +#endif +#ifdef CONFIG_PM + unsigned int in_suspend; + struct pm_dev *pmdev; +#endif + unsigned int card; + int chipset; /* What's my version(s)? */ + char *chipset_name; + + /* mixer data */ + int mixer; + unsigned int volume_l; + unsigned int volume_r; + unsigned int mic; + unsigned int bass_l; + unsigned int bass_r; + unsigned int treble_l; + unsigned int treble_r; + unsigned int wide_l; + unsigned int wide_r; +} opl3sa2_state_t; +static opl3sa2_state_t opl3sa2_state[OPL3SA2_CARDS_MAX]; + + + +/* Our parameters */ +static int __initdata io = -1; +static int __initdata mss_io = -1; +static int __initdata mpu_io = -1; +static int __initdata irq = -1; +static int __initdata dma = -1; +static int __initdata dma2 = -1; +static int __initdata ymode = -1; +static int __initdata loopback = -1; + +#ifdef CONFIG_PNP +/* PnP specific parameters */ +static int __initdata isapnp = 1; +static int __initdata multiple = 1; + +/* Whether said devices have been activated */ +static int opl3sa2_activated[OPL3SA2_CARDS_MAX]; +#else +static int __initdata isapnp; /* = 0 */ +static int __initdata multiple; /* = 0 */ +#endif + +MODULE_DESCRIPTION("Module for OPL3-SA2 and SA3 sound cards (uses AD1848 MSS driver)."); +MODULE_AUTHOR("Scott Murray "); +MODULE_LICENSE("GPL"); + + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "Set I/O base of OPL3-SA2 or SA3 card (usually 0x370. Address must be even and must be from 0x100 to 0xFFE)"); + +module_param(mss_io, int, 0); +MODULE_PARM_DESC(mss_io, "Set MSS (audio) I/O base (0x530, 0xE80, or other. Address must end in 0 or 4 and must be from 0x530 to 0xF48)"); + +module_param(mpu_io, int, 0); +MODULE_PARM_DESC(mpu_io, "Set MIDI I/O base (0x330 or other. Address must be even and must be from 0x300 to 0x334)"); + +module_param(irq, int, 0); +MODULE_PARM_DESC(mss_irq, "Set MSS (audio) IRQ (5, 7, 9, 10, 11, 12)"); + +module_param(dma, int, 0); +MODULE_PARM_DESC(dma, "Set MSS (audio) first DMA channel (0, 1, 3)"); + +module_param(dma2, int, 0); +MODULE_PARM_DESC(dma2, "Set MSS (audio) second DMA channel (0, 1, 3)"); + +module_param(ymode, int, 0); +MODULE_PARM_DESC(ymode, "Set Yamaha 3D enhancement mode (0 = Desktop/Normal, 1 = Notebook PC (1), 2 = Notebook PC (2), 3 = Hi-Fi)"); + +module_param(loopback, int, 0); +MODULE_PARM_DESC(loopback, "Set A/D input source. Useful for echo cancellation (0 = Mic Rch (default), 1 = Mono output loopback)"); + +#ifdef CONFIG_PNP +module_param(isapnp, bool, 0); +MODULE_PARM_DESC(isapnp, "When set to 0, ISA PnP support will be disabled"); + +module_param(multiple, bool, 0); +MODULE_PARM_DESC(multiple, "When set to 0, will not search for multiple cards"); +#endif + + +/* + * Standard read and write functions +*/ + +static inline void opl3sa2_write(unsigned short port, + unsigned char index, + unsigned char data) +{ + outb_p(index, port); + outb(data, port + 1); +} + + +static inline void opl3sa2_read(unsigned short port, + unsigned char index, + unsigned char* data) +{ + outb_p(index, port); + *data = inb(port + 1); +} + + +/* + * All of the mixer functions... + */ + +static void opl3sa2_set_volume(opl3sa2_state_t* devc, int left, int right) +{ + static unsigned char scale[101] = { + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 + }; + unsigned char vol; + + vol = scale[left]; + + /* If level is zero, turn on mute */ + if(!left) + vol |= 0x80; + + opl3sa2_write(devc->cfg_port, OPL3SA2_MASTER_LEFT, vol); + + vol = scale[right]; + + /* If level is zero, turn on mute */ + if(!right) + vol |= 0x80; + + opl3sa2_write(devc->cfg_port, OPL3SA2_MASTER_RIGHT, vol); +} + + +static void opl3sa2_set_mic(opl3sa2_state_t* devc, int level) +{ + unsigned char vol = 0x1F; + + if((level >= 0) && (level <= 100)) + vol = 0x1F - (unsigned char) (32 * level / 101); + + /* If level is zero, turn on mute */ + if(!level) + vol |= 0x80; + + opl3sa2_write(devc->cfg_port, OPL3SA2_MIC, vol); +} + + +static void opl3sa3_set_bass(opl3sa2_state_t* devc, int left, int right) +{ + unsigned char bass; + + bass = left ? ((unsigned char) (8 * left / 101)) : 0; + bass |= (right ? ((unsigned char) (8 * right / 101)) : 0) << 4; + + opl3sa2_write(devc->cfg_port, OPL3SA3_BASS, bass); +} + + +static void opl3sa3_set_treble(opl3sa2_state_t* devc, int left, int right) +{ + unsigned char treble; + + treble = left ? ((unsigned char) (8 * left / 101)) : 0; + treble |= (right ? ((unsigned char) (8 * right / 101)) : 0) << 4; + + opl3sa2_write(devc->cfg_port, OPL3SA3_TREBLE, treble); +} + + + + +static void opl3sa2_mixer_reset(opl3sa2_state_t* devc) +{ + if (devc) { + opl3sa2_set_volume(devc, DEFAULT_VOLUME, DEFAULT_VOLUME); + devc->volume_l = devc->volume_r = DEFAULT_VOLUME; + + opl3sa2_set_mic(devc, DEFAULT_MIC); + devc->mic = DEFAULT_MIC; + + if (devc->chipset == CHIPSET_OPL3SA3) { + opl3sa3_set_bass(devc, DEFAULT_TIMBRE, DEFAULT_TIMBRE); + devc->bass_l = devc->bass_r = DEFAULT_TIMBRE; + opl3sa3_set_treble(devc, DEFAULT_TIMBRE, DEFAULT_TIMBRE); + devc->treble_l = devc->treble_r = DEFAULT_TIMBRE; + } + } +} + +/* Currently only used for power management */ +#ifdef CONFIG_PM +static void opl3sa2_mixer_restore(opl3sa2_state_t* devc) +{ + if (devc) { + opl3sa2_set_volume(devc, devc->volume_l, devc->volume_r); + opl3sa2_set_mic(devc, devc->mic); + + if (devc->chipset == CHIPSET_OPL3SA3) { + opl3sa3_set_bass(devc, devc->bass_l, devc->bass_r); + opl3sa3_set_treble(devc, devc->treble_l, devc->treble_r); + } + } +} +#endif + +static inline void arg_to_vol_mono(unsigned int vol, int* value) +{ + int left; + + left = vol & 0x00ff; + if (left > 100) + left = 100; + *value = left; +} + + +static inline void arg_to_vol_stereo(unsigned int vol, int* aleft, int* aright) +{ + arg_to_vol_mono(vol, aleft); + arg_to_vol_mono(vol >> 8, aright); +} + + +static inline int ret_vol_mono(int vol) +{ + return ((vol << 8) | vol); +} + + +static inline int ret_vol_stereo(int left, int right) +{ + return ((right << 8) | left); +} + + +static int opl3sa2_mixer_ioctl(int dev, unsigned int cmd, void __user *arg) +{ + int retval, value, cmdf = cmd & 0xff; + int __user *p = (int __user *)arg; + + opl3sa2_state_t* devc = &opl3sa2_state[dev]; + + switch (cmdf) { + case SOUND_MIXER_VOLUME: + case SOUND_MIXER_MIC: + case SOUND_MIXER_DEVMASK: + case SOUND_MIXER_STEREODEVS: + case SOUND_MIXER_RECMASK: + case SOUND_MIXER_RECSRC: + case SOUND_MIXER_CAPS: + break; + + default: + return -EINVAL; + } + + if (((cmd >> 8) & 0xff) != 'M') + return -EINVAL; + + retval = 0; + if (_SIOC_DIR (cmd) & _SIOC_WRITE) { + switch (cmdf) { + case SOUND_MIXER_VOLUME: + retval = get_user(value, (unsigned __user *) arg); + if (retval) + break; + arg_to_vol_stereo(value, &devc->volume_l, &devc->volume_r); + opl3sa2_set_volume(devc, devc->volume_l, devc->volume_r); + value = ret_vol_stereo(devc->volume_l, devc->volume_r); + retval = put_user(value, p); + break; + + case SOUND_MIXER_MIC: + retval = get_user(value, (unsigned __user *) arg); + if (retval) + break; + arg_to_vol_mono(value, &devc->mic); + opl3sa2_set_mic(devc, devc->mic); + value = ret_vol_mono(devc->mic); + retval = put_user(value, p); + break; + + default: + retval = -EINVAL; + } + } + else { + /* + * Return parameters + */ + switch (cmdf) { + case SOUND_MIXER_DEVMASK: + retval = put_user(SOUND_MASK_VOLUME | SOUND_MASK_MIC, p); + break; + + case SOUND_MIXER_STEREODEVS: + retval = put_user(SOUND_MASK_VOLUME, p); + break; + + case SOUND_MIXER_RECMASK: + /* No recording devices */ + retval = put_user(0, p); + break; + + case SOUND_MIXER_CAPS: + retval = put_user(SOUND_CAP_EXCL_INPUT, p); + break; + + case SOUND_MIXER_RECSRC: + /* No recording source */ + retval = put_user(0, p); + break; + + case SOUND_MIXER_VOLUME: + value = ret_vol_stereo(devc->volume_l, devc->volume_r); + retval = put_user(value, p); + break; + + case SOUND_MIXER_MIC: + value = ret_vol_mono(devc->mic); + put_user(value, p); + break; + + default: + retval = -EINVAL; + } + } + return retval; +} +/* opl3sa2_mixer_ioctl end */ + + +static int opl3sa3_mixer_ioctl(int dev, unsigned int cmd, void __user * arg) +{ + int value, retval, cmdf = cmd & 0xff; + + opl3sa2_state_t* devc = &opl3sa2_state[dev]; + + switch (cmdf) { + case SOUND_MIXER_BASS: + value = ret_vol_stereo(devc->bass_l, devc->bass_r); + retval = put_user(value, (int __user *) arg); + break; + + case SOUND_MIXER_TREBLE: + value = ret_vol_stereo(devc->treble_l, devc->treble_r); + retval = put_user(value, (int __user *) arg); + break; + + case SOUND_MIXER_DIGITAL1: + value = ret_vol_stereo(devc->wide_l, devc->wide_r); + retval = put_user(value, (int __user *) arg); + break; + + default: + retval = -EINVAL; + } + return retval; +} +/* opl3sa3_mixer_ioctl end */ + + +static struct mixer_operations opl3sa2_mixer_operations = +{ + .owner = THIS_MODULE, + .id = "OPL3-SA2", + .name = "Yamaha OPL3-SA2", + .ioctl = opl3sa2_mixer_ioctl +}; + +static struct mixer_operations opl3sa3_mixer_operations = +{ + .owner = THIS_MODULE, + .id = "OPL3-SA3", + .name = "Yamaha OPL3-SA3", + .ioctl = opl3sa3_mixer_ioctl +}; + +/* End of mixer-related stuff */ + + +/* + * Component probe, attach, unload functions + */ + +static inline void __exit unload_opl3sa2_mpu(struct address_info *hw_config) +{ + unload_mpu401(hw_config); +} + + +static void __init attach_opl3sa2_mss(struct address_info* hw_config, struct resource *ports) +{ + int initial_mixers; + + initial_mixers = num_mixers; + attach_ms_sound(hw_config, ports, THIS_MODULE); /* Slot 0 */ + if (hw_config->slots[0] != -1) { + /* Did the MSS driver install? */ + if(num_mixers == (initial_mixers + 1)) { + /* The MSS mixer is installed, reroute mixers appropiately */ + AD1848_REROUTE(SOUND_MIXER_LINE1, SOUND_MIXER_CD); + AD1848_REROUTE(SOUND_MIXER_LINE2, SOUND_MIXER_SYNTH); + AD1848_REROUTE(SOUND_MIXER_LINE3, SOUND_MIXER_LINE); + } + else { + printk(KERN_ERR PFX "MSS mixer not installed?\n"); + } + } +} + + +static inline void __exit unload_opl3sa2_mss(struct address_info* hw_config) +{ + unload_ms_sound(hw_config); +} + + +static int __init probe_opl3sa2(struct address_info* hw_config, int card) +{ + unsigned char misc; + unsigned char tmp; + unsigned char version; + + /* + * Try and allocate our I/O port range. + */ + if (!request_region(hw_config->io_base, 2, OPL3SA2_MODULE_NAME)) { + printk(KERN_ERR PFX "Control I/O port %#x not free\n", + hw_config->io_base); + goto out_nodev; + } + + /* + * Check if writing to the read-only version bits of the miscellaneous + * register succeeds or not (it should not). + */ + opl3sa2_read(hw_config->io_base, OPL3SA2_MISC, &misc); + opl3sa2_write(hw_config->io_base, OPL3SA2_MISC, misc ^ 0x07); + opl3sa2_read(hw_config->io_base, OPL3SA2_MISC, &tmp); + if(tmp != misc) { + printk(KERN_ERR PFX "Control I/O port %#x is not a YMF7xx chipset!\n", + hw_config->io_base); + goto out_region; + } + + /* + * Check if the MIC register is accessible. + */ + opl3sa2_read(hw_config->io_base, OPL3SA2_MIC, &tmp); + opl3sa2_write(hw_config->io_base, OPL3SA2_MIC, 0x8a); + opl3sa2_read(hw_config->io_base, OPL3SA2_MIC, &tmp); + if((tmp & 0x9f) != 0x8a) { + printk(KERN_ERR + PFX "Control I/O port %#x is not a YMF7xx chipset!\n", + hw_config->io_base); + goto out_region; + } + opl3sa2_write(hw_config->io_base, OPL3SA2_MIC, tmp); + + /* + * Determine chipset type (SA2 or SA3) + * + * This is done by looking at the chipset version in the lower 3 bits + * of the miscellaneous register. + */ + version = misc & 0x07; + printk(KERN_DEBUG PFX "Chipset version = %#x\n", version); + switch (version) { + case 0: + opl3sa2_state[card].chipset = CHIPSET_UNKNOWN; + printk(KERN_ERR + PFX "Unknown Yamaha audio controller version\n"); + break; + + case VERSION_YMF711: + opl3sa2_state[card].chipset = CHIPSET_OPL3SA2; + printk(KERN_INFO PFX "Found OPL3-SA2 (YMF711)\n"); + break; + + case VERSION_YMF715: + opl3sa2_state[card].chipset = CHIPSET_OPL3SA3; + printk(KERN_INFO + PFX "Found OPL3-SA3 (YMF715 or YMF719)\n"); + break; + + case VERSION_YMF715B: + opl3sa2_state[card].chipset = CHIPSET_OPL3SA3; + printk(KERN_INFO + PFX "Found OPL3-SA3 (YMF715B or YMF719B)\n"); + break; + + case VERSION_YMF715E: + default: + opl3sa2_state[card].chipset = CHIPSET_OPL3SA3; + printk(KERN_INFO + PFX "Found OPL3-SA3 (YMF715E or YMF719E)\n"); + break; + } + + if (opl3sa2_state[card].chipset != CHIPSET_UNKNOWN) { + /* Generate a pretty name */ + opl3sa2_state[card].chipset_name = (char *)CHIPSET_TABLE[opl3sa2_state[card].chipset]; + return 0; + } + +out_region: + release_region(hw_config->io_base, 2); +out_nodev: + return -ENODEV; +} + + +static void __init attach_opl3sa2(struct address_info* hw_config, int card) +{ + /* Initialize IRQ configuration to IRQ-B: -, IRQ-A: WSS+MPU+OPL3 */ + opl3sa2_write(hw_config->io_base, OPL3SA2_IRQ_CONFIG, 0x0d); + + /* Initialize DMA configuration */ + if(hw_config->dma2 == hw_config->dma) { + /* Want DMA configuration DMA-B: -, DMA-A: WSS-P+WSS-R */ + opl3sa2_write(hw_config->io_base, OPL3SA2_DMA_CONFIG, 0x03); + } + else { + /* Want DMA configuration DMA-B: WSS-R, DMA-A: WSS-P */ + opl3sa2_write(hw_config->io_base, OPL3SA2_DMA_CONFIG, 0x21); + } +} + + +static void __init attach_opl3sa2_mixer(struct address_info *hw_config, int card) +{ + struct mixer_operations* mixer_operations; + opl3sa2_state_t* devc = &opl3sa2_state[card]; + + /* Install master mixer */ + if (devc->chipset == CHIPSET_OPL3SA3) { + mixer_operations = &opl3sa3_mixer_operations; + } + else { + mixer_operations = &opl3sa2_mixer_operations; + } + + devc->cfg_port = hw_config->io_base; + devc->mixer = sound_install_mixer(MIXER_DRIVER_VERSION, + mixer_operations->name, + mixer_operations, + sizeof(struct mixer_operations), + devc); + if(devc->mixer < 0) { + printk(KERN_ERR PFX "Could not install %s master mixer\n", + mixer_operations->name); + } + else { + opl3sa2_mixer_reset(devc); + + } +} + + +static void opl3sa2_clear_slots(struct address_info* hw_config) +{ + int i; + + for(i = 0; i < 6; i++) { + hw_config->slots[i] = -1; + } +} + + +static void __init opl3sa2_set_ymode(struct address_info* hw_config, int ymode) +{ + /* + * Set the Yamaha 3D enhancement mode (aka Ymersion) if asked to and + * it's supported. + * + * 0: Desktop (aka normal) 5-12 cm speakers + * 1: Notebook PC mode 1 3 cm speakers + * 2: Notebook PC mode 2 1.5 cm speakers + * 3: Hi-fi 16-38 cm speakers + */ + if(ymode >= 0 && ymode <= 3) { + unsigned char sys_ctrl; + + opl3sa2_read(hw_config->io_base, OPL3SA2_SYS_CTRL, &sys_ctrl); + sys_ctrl = (sys_ctrl & 0xcf) | ((ymode & 3) << 4); + opl3sa2_write(hw_config->io_base, OPL3SA2_SYS_CTRL, sys_ctrl); + } + else { + printk(KERN_ERR PFX "not setting ymode, it must be one of 0,1,2,3\n"); + } +} + + +static void __init opl3sa2_set_loopback(struct address_info* hw_config, int loopback) +{ + if(loopback >= 0 && loopback <= 1) { + unsigned char misc; + + opl3sa2_read(hw_config->io_base, OPL3SA2_MISC, &misc); + misc = (misc & 0xef) | ((loopback & 1) << 4); + opl3sa2_write(hw_config->io_base, OPL3SA2_MISC, misc); + } + else { + printk(KERN_ERR PFX "not setting loopback, it must be either 0 or 1\n"); + } +} + + +static void __exit unload_opl3sa2(struct address_info* hw_config, int card) +{ + /* Release control ports */ + release_region(hw_config->io_base, 2); + + /* Unload mixer */ + if(opl3sa2_state[card].mixer >= 0) + sound_unload_mixerdev(opl3sa2_state[card].mixer); + +} + +#ifdef CONFIG_PNP +static struct pnp_device_id pnp_opl3sa2_list[] = { + {.id = "YMH0021", .driver_data = 0}, + {.id = ""} +}; + +MODULE_DEVICE_TABLE(pnp, pnp_opl3sa2_list); + +static int opl3sa2_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) +{ + int card = opl3sa2_cards_num; + + /* we don't actually want to return an error as the user may have specified + * no multiple card search + */ + + if (opl3sa2_cards_num == OPL3SA2_CARDS_MAX) + return 0; + opl3sa2_activated[card] = 1; + + /* Our own config: */ + opl3sa2_state[card].cfg.io_base = pnp_port_start(dev, 4); + opl3sa2_state[card].cfg.irq = pnp_irq(dev, 0); + opl3sa2_state[card].cfg.dma = pnp_dma(dev, 0); + opl3sa2_state[card].cfg.dma2 = pnp_dma(dev, 1); + + /* The MSS config: */ + opl3sa2_state[card].cfg_mss.io_base = pnp_port_start(dev, 1); + opl3sa2_state[card].cfg_mss.irq = pnp_irq(dev, 0); + opl3sa2_state[card].cfg_mss.dma = pnp_dma(dev, 0); + opl3sa2_state[card].cfg_mss.dma2 = pnp_dma(dev, 1); + opl3sa2_state[card].cfg_mss.card_subtype = 1; /* No IRQ or DMA setup */ + + opl3sa2_state[card].cfg_mpu.io_base = pnp_port_start(dev, 3); + opl3sa2_state[card].cfg_mpu.irq = pnp_irq(dev, 0); + opl3sa2_state[card].cfg_mpu.dma = -1; + opl3sa2_state[card].cfg_mpu.dma2 = -1; + opl3sa2_state[card].cfg_mpu.always_detect = 1; /* It's there, so use shared IRQs */ + + /* Call me paranoid: */ + opl3sa2_clear_slots(&opl3sa2_state[card].cfg); + opl3sa2_clear_slots(&opl3sa2_state[card].cfg_mss); + opl3sa2_clear_slots(&opl3sa2_state[card].cfg_mpu); + + opl3sa2_state[card].pdev = dev; + opl3sa2_cards_num++; + + return 0; +} + +static struct pnp_driver opl3sa2_driver = { + .name = "opl3sa2", + .id_table = pnp_opl3sa2_list, + .probe = opl3sa2_pnp_probe, +}; + +#endif /* CONFIG_PNP */ + +/* End of component functions */ + +#ifdef CONFIG_PM +static DEFINE_SPINLOCK(opl3sa2_lock); + +/* Power Management support functions */ +static int opl3sa2_suspend(struct pm_dev *pdev, unsigned int pm_mode) +{ + unsigned long flags; + opl3sa2_state_t *p; + + if (!pdev) + return -EINVAL; + + spin_lock_irqsave(&opl3sa2_lock,flags); + + p = (opl3sa2_state_t *) pdev->data; + switch (pm_mode) { + case 1: + pm_mode = OPL3SA2_PM_MODE1; + break; + case 2: + pm_mode = OPL3SA2_PM_MODE2; + break; + case 3: + pm_mode = OPL3SA2_PM_MODE3; + break; + default: + /* we don't know howto handle this... */ + spin_unlock_irqrestore(&opl3sa2_lock, flags); + return -EBUSY; + } + + p->in_suspend = 1; + + /* its supposed to automute before suspending, so we won't bother */ + opl3sa2_write(p->cfg_port, OPL3SA2_PM, pm_mode); + /* wait a while for the clock oscillator to stabilise */ + mdelay(10); + + spin_unlock_irqrestore(&opl3sa2_lock,flags); + return 0; +} + +static int opl3sa2_resume(struct pm_dev *pdev) +{ + unsigned long flags; + opl3sa2_state_t *p; + + if (!pdev) + return -EINVAL; + + p = (opl3sa2_state_t *) pdev->data; + spin_lock_irqsave(&opl3sa2_lock,flags); + + /* I don't think this is necessary */ + opl3sa2_write(p->cfg_port, OPL3SA2_PM, OPL3SA2_PM_MODE0); + opl3sa2_mixer_restore(p); + p->in_suspend = 0; + + spin_unlock_irqrestore(&opl3sa2_lock,flags); + return 0; +} + +static int opl3sa2_pm_callback(struct pm_dev *pdev, pm_request_t rqst, void *data) +{ + unsigned long mode = (unsigned long)data; + + switch (rqst) { + case PM_SUSPEND: + return opl3sa2_suspend(pdev, mode); + + case PM_RESUME: + return opl3sa2_resume(pdev); + } + return 0; +} +#endif /* CONFIG_PM */ + +/* + * Install OPL3-SA2 based card(s). + * + * Need to have ad1848 and mpu401 loaded ready. + */ +static int __init init_opl3sa2(void) +{ + int card, max; + + /* Sanitize isapnp and multiple settings */ + isapnp = isapnp != 0 ? 1 : 0; + multiple = multiple != 0 ? 1 : 0; + + max = (multiple && isapnp) ? OPL3SA2_CARDS_MAX : 1; + +#ifdef CONFIG_PNP + if (isapnp){ + pnp_register_driver(&opl3sa2_driver); + if(!opl3sa2_cards_num){ + printk(KERN_INFO PFX "No PnP cards found\n"); + isapnp = 0; + } + max = opl3sa2_cards_num; + } +#endif + + for (card = 0; card < max; card++) { + /* If a user wants an I/O then assume they meant it */ + struct resource *ports; + int base; + + if (!isapnp) { + if (io == -1 || irq == -1 || dma == -1 || + dma2 == -1 || mss_io == -1) { + printk(KERN_ERR + PFX "io, mss_io, irq, dma, and dma2 must be set\n"); + return -EINVAL; + } + opl3sa2_cards_num++; + + /* + * Our own config: + * (NOTE: IRQ and DMA aren't used, so they're set to + * give pretty output from conf_printf. :) + */ + opl3sa2_state[card].cfg.io_base = io; + opl3sa2_state[card].cfg.irq = irq; + opl3sa2_state[card].cfg.dma = dma; + opl3sa2_state[card].cfg.dma2 = dma2; + + /* The MSS config: */ + opl3sa2_state[card].cfg_mss.io_base = mss_io; + opl3sa2_state[card].cfg_mss.irq = irq; + opl3sa2_state[card].cfg_mss.dma = dma; + opl3sa2_state[card].cfg_mss.dma2 = dma2; + opl3sa2_state[card].cfg_mss.card_subtype = 1; /* No IRQ or DMA setup */ + + opl3sa2_state[card].cfg_mpu.io_base = mpu_io; + opl3sa2_state[card].cfg_mpu.irq = irq; + opl3sa2_state[card].cfg_mpu.dma = -1; + opl3sa2_state[card].cfg_mpu.always_detect = 1; /* Use shared IRQs */ + + /* Call me paranoid: */ + opl3sa2_clear_slots(&opl3sa2_state[card].cfg); + opl3sa2_clear_slots(&opl3sa2_state[card].cfg_mss); + opl3sa2_clear_slots(&opl3sa2_state[card].cfg_mpu); + } + + /* FIXME: leak */ + if (probe_opl3sa2(&opl3sa2_state[card].cfg, card)) + return -ENODEV; + + base = opl3sa2_state[card].cfg_mss.io_base; + + if (!request_region(base, 4, "WSS config")) + goto failed; + + ports = request_region(base + 4, 4, "ad1848"); + if (!ports) + goto failed2; + + if (!probe_ms_sound(&opl3sa2_state[card].cfg_mss, ports)) { + /* + * If one or more cards are already registered, don't + * return an error but print a warning. Note, this + * should never really happen unless the hardware or + * ISA PnP screwed up. + */ + release_region(base + 4, 4); + failed2: + release_region(base, 4); + failed: + release_region(opl3sa2_state[card].cfg.io_base, 2); + + if (opl3sa2_cards_num) { + printk(KERN_WARNING + PFX "There was a problem probing one " + " of the ISA PNP cards, continuing\n"); + opl3sa2_cards_num--; + continue; + } else + return -ENODEV; + } + + attach_opl3sa2(&opl3sa2_state[card].cfg, card); + conf_printf(opl3sa2_state[card].chipset_name, &opl3sa2_state[card].cfg); + attach_opl3sa2_mixer(&opl3sa2_state[card].cfg, card); + attach_opl3sa2_mss(&opl3sa2_state[card].cfg_mss, ports); + + /* ewww =) */ + opl3sa2_state[card].card = card; +#ifdef CONFIG_PM + /* register our power management capabilities */ + opl3sa2_state[card].pmdev = pm_register(PM_ISA_DEV, card, opl3sa2_pm_callback); + if (opl3sa2_state[card].pmdev) + opl3sa2_state[card].pmdev->data = &opl3sa2_state[card]; +#endif /* CONFIG_PM */ + + /* + * Set the Yamaha 3D enhancement mode (aka Ymersion) if asked to and + * it's supported. + */ + if (ymode != -1) { + if (opl3sa2_state[card].chipset == CHIPSET_OPL3SA2) { + printk(KERN_ERR + PFX "ymode not supported on OPL3-SA2\n"); + } + else { + opl3sa2_set_ymode(&opl3sa2_state[card].cfg, ymode); + } + } + + + /* Set A/D input to Mono loopback if asked to. */ + if (loopback != -1) { + opl3sa2_set_loopback(&opl3sa2_state[card].cfg, loopback); + } + + /* Attach MPU if we've been asked to do so, failure isn't fatal */ + if (opl3sa2_state[card].cfg_mpu.io_base != -1) { + int base = opl3sa2_state[card].cfg_mpu.io_base; + struct resource *ports; + ports = request_region(base, 2, "mpu401"); + if (!ports) + goto out; + if (!probe_mpu401(&opl3sa2_state[card].cfg_mpu, ports)) { + release_region(base, 2); + goto out; + } + if (attach_mpu401(&opl3sa2_state[card].cfg_mpu, THIS_MODULE)) { + printk(KERN_ERR PFX "failed to attach MPU401\n"); + opl3sa2_state[card].cfg_mpu.slots[1] = -1; + } + } + } + +out: + if (isapnp) { + printk(KERN_NOTICE PFX "%d PnP card(s) found.\n", opl3sa2_cards_num); + } + + return 0; +} + + +/* + * Uninstall OPL3-SA2 based card(s). + */ +static void __exit cleanup_opl3sa2(void) +{ + int card; + + for(card = 0; card < opl3sa2_cards_num; card++) { +#ifdef CONFIG_PM + if (opl3sa2_state[card].pmdev) + pm_unregister(opl3sa2_state[card].pmdev); +#endif + if (opl3sa2_state[card].cfg_mpu.slots[1] != -1) { + unload_opl3sa2_mpu(&opl3sa2_state[card].cfg_mpu); + } + unload_opl3sa2_mss(&opl3sa2_state[card].cfg_mss); + unload_opl3sa2(&opl3sa2_state[card].cfg, card); +#ifdef CONFIG_PNP + pnp_unregister_driver(&opl3sa2_driver); +#endif + } +} + +module_init(init_opl3sa2); +module_exit(cleanup_opl3sa2); + +#ifndef MODULE +static int __init setup_opl3sa2(char *str) +{ + /* io, irq, dma, dma2,... */ +#ifdef CONFIG_PNP + int ints[11]; +#else + int ints[9]; +#endif + str = get_options(str, ARRAY_SIZE(ints), ints); + + io = ints[1]; + irq = ints[2]; + dma = ints[3]; + dma2 = ints[4]; + mss_io = ints[5]; + mpu_io = ints[6]; + ymode = ints[7]; + loopback = ints[8]; +#ifdef CONFIG_PNP + isapnp = ints[9]; + multiple = ints[10]; +#endif + return 1; +} + +__setup("opl3sa2=", setup_opl3sa2); +#endif diff --git a/sound/oss/os.h b/sound/oss/os.h new file mode 100644 index 000000000000..d6b96297835c --- /dev/null +++ b/sound/oss/os.h @@ -0,0 +1,51 @@ +#define ALLOW_SELECT +#undef NO_INLINE_ASM +#define SHORT_BANNERS +#define MANUAL_PNP +#undef DO_TIMINGS + +#include +#include + +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __alpha__ +#include +#endif +#include +#include +#include +#include +#endif + +#include + +#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 struct file_operations oss_sound_fops; diff --git a/sound/oss/pas2.h b/sound/oss/pas2.h new file mode 100644 index 000000000000..fa12c55f560e --- /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 000000000000..c9696dc9fdf9 --- /dev/null +++ b/sound/oss/pas2_card.c @@ -0,0 +1,458 @@ +/* + * sound/pas2_card.c + * + * Detection routine for the Pro Audio Spectrum cards. + */ + +#include +#include +#include +#include +#include +#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, struct pt_regs *dummy) +{ + 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 000000000000..79d6a5827b6d --- /dev/null +++ b/sound/oss/pas2_midi.c @@ -0,0 +1,262 @@ +/* + * sound/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 +#include +#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 000000000000..4aade5304587 --- /dev/null +++ b/sound/oss/pas2_mixer.c @@ -0,0 +1,336 @@ + +/* + * sound/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 +#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 000000000000..4af6aafa3d86 --- /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 +#include +#include +#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 000000000000..3ed38765dcc4 --- /dev/null +++ b/sound/oss/pss.c @@ -0,0 +1,1283 @@ +/* + * sound/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 + * 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 + * Fixed computation of mixer volumes + * 04-05-1999: Anthony Barbachan + * 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 + * 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 + * Added module parameter pss_firmware to allow the user to tell + * the driver where the fireware file is located. The default + * setting is the previous hardcoded setting "/etc/sound/pss_synth". + * 00-03-03: Christoph Hellwig + * Adapted to module_init/module_exit + * 11-10-2000: Bartlomiej Zolnierkiewicz + * Added __init to probe_pss(), attach_pss() and probe_pss_mpu() + * 02-Jan-2001: Chris Rankin + * Specify that this module owns the coprocessor + */ + + +#include +#include +#include +#include + +#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 int 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)); + + return 1; +} + +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 && (limit-jiffies >= 0); 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 && 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 && (limit - jiffies >= 0); i++) + val = inw(REG(PSS_STATUS)); + + limit = jiffies + HZ/10; + for (i = 0; i < 32768 && (limit-jiffies >= 0); 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( "", 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, int *aleft) +{ + int left; + unsigned 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, int *aleft, int *aright) +{ + int left, right; + unsigned 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 if(!set_io_base(devc, CONF_CDROM, pss_cdrom_port)) + { + printk(KERN_ERR "PSS: CDROM I/O port could not be set.\n"); + } + else /* CDROM port successfully configured */ + { + 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(); + +#if 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; + } + if (!set_io_base(devc, CONF_MIDI, hw_config->io_base)) { + printk(KERN_ERR "PSS: MIDI base could not be set.\n"); + goto fail; + } + 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 = (copr_buffer *) 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 = (copr_msg *)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 = (copr_msg *)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; + } + if (!set_io_base(devc, CONF_WSS, hw_config->io_base)) { + printk("PSS: WSS base not settable.\n"); + goto fail; + } + 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/rme96xx.c b/sound/oss/rme96xx.c new file mode 100644 index 000000000000..76774bbc1436 --- /dev/null +++ b/sound/oss/rme96xx.c @@ -0,0 +1,1861 @@ +/* (C) 2000 Guenter Geiger + with copy/pastes from the driver of Winfried Ritsch + based on es1370.c + + + + * 10 Jan 2001: 0.1 initial version + * 19 Jan 2001: 0.2 fixed bug in select() + * 27 Apr 2001: 0.3 more than one card usable + * 11 May 2001: 0.4 fixed for SMP, included into kernel source tree + * 17 May 2001: 0.5 draining code didn't work on new cards + * 18 May 2001: 0.6 remove synchronize_irq() call + * 17 Jul 2001: 0.7 updated xrmectrl to make it work for newer cards + * 2 feb 2002: 0.8 fixed pci device handling, see below for patches from Heiko (Thanks!) + Marcus Meissner + + Modifications - Heiko Purnhagen + HP20020108 fixed handling of "large" read() + HP20020116 towards REV 1.5 support, based on ALSA's card-rme9652.c + HP20020118 made mixer ioctl and handling of devices>1 more safe + HP20020201 fixed handling of "large" read() properly + added REV 1.5 S/P-DIF receiver support + SNDCTL_DSP_SPEED now returns the actual speed + * 10 Aug 2002: added synchronize_irq() again + +TODO: + - test more than one card --- done + - check for pci IOREGION (see es1370) in rme96xx_probe ?? + - error detection + - mmap interface + - mixer mmap interface + - mixer ioctl + - get rid of noise upon first open (why ??) + - allow multiple open (at least for read) + - allow multiple open for non overlapping regions + - recheck the multiple devices part (offsets of different devices, etc) + - do decent draining in _release --- done + - SMP support + - what about using fragstotal>2 for small fragsize? (HP20020118) + - add support for AFMT_S32_LE +*/ + +#ifndef RMEVERSION +#define RMEVERSION "0.8" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rme96xx.h" + +#define NR_DEVICE 2 + +static int devices = 1; +module_param(devices, int, 0); +MODULE_PARM_DESC(devices, "number of dsp devices allocated by the driver"); + + +MODULE_AUTHOR("Guenter Geiger, geiger@debian.org"); +MODULE_DESCRIPTION("RME9652/36 \"Hammerfall\" Driver"); +MODULE_LICENSE("GPL"); + + +#ifdef DEBUG +#define DBG(x) printk("RME_DEBUG:");x +#define COMM(x) printk("RME_COMM: " x "\n"); +#else +#define DBG(x) while (0) {} +#define COMM(x) +#endif + +/*-------------------------------------------------------------------------- + Preporcessor Macros and Definitions + --------------------------------------------------------------------------*/ + +#define RME96xx_MAGIC 0x6473 + +/* Registers-Space in offsets from base address with 16MByte size */ + +#define RME96xx_IO_EXTENT 16l*1024l*1024l +#define RME96xx_CHANNELS_PER_CARD 26 + +/* Write - Register */ + +/* 0,4,8,12,16,20,24,28 ... hardware init (erasing fifo-pointer intern) */ +#define RME96xx_num_of_init_regs 8 + +#define RME96xx_init_buffer (0/4) +#define RME96xx_play_buffer (32/4) /* pointer to 26x64kBit RAM from mainboard */ +#define RME96xx_rec_buffer (36/4) /* pointer to 26x64kBit RAM from mainboard */ +#define RME96xx_control_register (64/4) /* exact meaning see below */ +#define RME96xx_irq_clear (96/4) /* irq acknowledge */ +#define RME96xx_time_code (100/4) /* if used with alesis adat */ +#define RME96xx_thru_base (128/4) /* 132...228 Thru for 26 channels */ +#define RME96xx_thru_channels RME96xx_CHANNELS_PER_CARD + +/* Read Register */ + +#define RME96xx_status_register 0 /* meaning see below */ + + + +/* Status Register: */ +/* ------------------------------------------------------------------------ */ +#define RME96xx_IRQ 0x0000001 /* IRQ is High if not reset by RMExx_irq_clear */ +#define RME96xx_lock_2 0x0000002 /* ADAT 3-PLL: 1=locked, 0=unlocked */ +#define RME96xx_lock_1 0x0000004 /* ADAT 2-PLL: 1=locked, 0=unlocked */ +#define RME96xx_lock_0 0x0000008 /* ADAT 1-PLL: 1=locked, 0=unlocked */ + +#define RME96xx_fs48 0x0000010 /* sample rate 0 ...44.1/88.2, 1 ... 48/96 Khz */ +#define RME96xx_wsel_rd 0x0000020 /* if Word-Clock is used and valid then 1 */ +#define RME96xx_buf_pos1 0x0000040 /* Bit 6..15 : Position of buffer-pointer in 64Bytes-blocks */ +#define RME96xx_buf_pos2 0x0000080 /* resolution +/- 1 64Byte/block (since 64Bytes bursts) */ + +#define RME96xx_buf_pos3 0x0000100 /* 10 bits = 1024 values */ +#define RME96xx_buf_pos4 0x0000200 /* if we mask off the first 6 bits, we can take the status */ +#define RME96xx_buf_pos5 0x0000400 /* register as sample counter in the hardware buffer */ +#define RME96xx_buf_pos6 0x0000800 + +#define RME96xx_buf_pos7 0x0001000 +#define RME96xx_buf_pos8 0x0002000 +#define RME96xx_buf_pos9 0x0004000 +#define RME96xx_buf_pos10 0x0008000 + +#define RME96xx_sync_2 0x0010000 /* if ADAT-IN3 synced to system clock */ +#define RME96xx_sync_1 0x0020000 /* if ADAT-IN2 synced to system clock */ +#define RME96xx_sync_0 0x0040000 /* if ADAT-IN1 synced to system clock */ +#define RME96xx_DS_rd 0x0080000 /* 1=Double Speed, 0=Normal Speed */ + +#define RME96xx_tc_busy 0x0100000 /* 1=time-code copy in progress (960ms) */ +#define RME96xx_tc_out 0x0200000 /* time-code out bit */ +#define RME96xx_F_0 0x0400000 /* 000=64kHz, 100=88.2kHz, 011=96kHz */ +#define RME96xx_F_1 0x0800000 /* 111=32kHz, 110=44.1kHz, 101=48kHz, */ + +#define RME96xx_F_2 0x1000000 /* 001=Rev 1.5+ external Crystal Chip */ +#define RME96xx_ERF 0x2000000 /* Error-Flag of SDPIF Receiver (1=No Lock)*/ +#define RME96xx_buffer_id 0x4000000 /* toggles by each interrupt on rec/play */ +#define RME96xx_tc_valid 0x8000000 /* 1 = a signal is detected on time-code input */ +#define RME96xx_SPDIF_READ 0x10000000 /* byte available from Rev 1.5+ SPDIF interface */ + +/* Status Register Fields */ + +#define RME96xx_lock (RME96xx_lock_0|RME96xx_lock_1|RME96xx_lock_2) +#define RME96xx_sync (RME96xx_sync_0|RME96xx_sync_1|RME96xx_sync_2) +#define RME96xx_F (RME96xx_F_0|RME96xx_F_1|RME96xx_F_2) +#define rme96xx_decode_spdif_rate(x) ((x)>>22) + +/* Bit 6..15 : h/w buffer pointer */ +#define RME96xx_buf_pos 0x000FFC0 +/* Bits 31,30,29 are bits 5,4,3 of h/w pointer position on later + Rev G EEPROMS and Rev 1.5 cards or later. +*/ +#define RME96xx_REV15_buf_pos(x) ((((x)&0xE0000000)>>26)|((x)&RME96xx_buf_pos)) + + +/* Control-Register: */ +/*--------------------------------------------------------------------------------*/ + +#define RME96xx_start_bit 0x0001 /* start record/play */ +#define RME96xx_latency0 0x0002 /* Buffer size / latency */ +#define RME96xx_latency1 0x0004 /* buffersize = 512Bytes * 2^n */ +#define RME96xx_latency2 0x0008 /* 0=64samples ... 7=8192samples */ + +#define RME96xx_Master 0x0010 /* Clock Mode 1=Master, 0=Slave/Auto */ +#define RME96xx_IE 0x0020 /* Interupt Enable */ +#define RME96xx_freq 0x0040 /* samplerate 0=44.1/88.2, 1=48/96 kHz*/ +#define RME96xx_freq1 0x0080 /* samplerate 0=32 kHz, 1=other rates ??? (from ALSA, but may be wrong) */ +#define RME96xx_DS 0x0100 /* double speed 0=44.1/48, 1=88.2/96 Khz */ +#define RME96xx_PRO 0x0200 /* SPDIF-OUT 0=consumer, 1=professional */ +#define RME96xx_EMP 0x0400 /* SPDIF-OUT emphasis 0=off, 1=on */ +#define RME96xx_Dolby 0x0800 /* SPDIF-OUT non-audio bit 1=set, 0=unset */ + +#define RME96xx_opt_out 0x1000 /* use 1st optical OUT as SPDIF: 1=yes, 0=no */ +#define RME96xx_wsel 0x2000 /* use Wordclock as sync (overwrites master) */ +#define RME96xx_inp_0 0x4000 /* SPDIF-IN 00=optical (ADAT1), */ +#define RME96xx_inp_1 0x8000 /* 01=coaxial (Cinch), 10=internal CDROM */ + +#define RME96xx_SyncRef0 0x10000 /* preferred sync-source in autosync */ +#define RME96xx_SyncRef1 0x20000 /* 00=ADAT1, 01=ADAT2, 10=ADAT3, 11=SPDIF */ + +#define RME96xx_SPDIF_RESET (1<<18) /* Rev 1.5+: h/w SPDIF receiver */ +#define RME96xx_SPDIF_SELECT (1<<19) +#define RME96xx_SPDIF_CLOCK (1<<20) +#define RME96xx_SPDIF_WRITE (1<<21) +#define RME96xx_ADAT1_INTERNAL (1<<22) /* Rev 1.5+: if set, internal CD connector carries ADAT */ + + +#define RME96xx_ctrl_init (RME96xx_latency0 |\ + RME96xx_Master |\ + RME96xx_inp_1) + + + +/* Control register fields and shortcuts */ + +#define RME96xx_latency (RME96xx_latency0|RME96xx_latency1|RME96xx_latency2) +#define RME96xx_inp (RME96xx_inp_0|RME96xx_inp_1) +#define RME96xx_SyncRef (RME96xx_SyncRef0|RME96xx_SyncRef1) +#define RME96xx_mixer_allowed (RME96xx_Master|RME96xx_PRO|RME96xx_EMP|RME96xx_Dolby|RME96xx_opt_out|RME96xx_wsel|RME96xx_inp|RME96xx_SyncRef|RME96xx_ADAT1_INTERNAL) + +/* latency = 512Bytes * 2^n, where n is made from Bit3 ... Bit1 (??? HP20020201) */ + +#define RME96xx_SET_LATENCY(x) (((x)&0x7)<<1) +#define RME96xx_GET_LATENCY(x) (((x)>>1)&0x7) +#define RME96xx_SET_inp(x) (((x)&0x3)<<14) +#define RME96xx_GET_inp(x) (((x)>>14)&0x3) +#define RME96xx_SET_SyncRef(x) (((x)&0x3)<<17) +#define RME96xx_GET_SyncRef(x) (((x)>>17)&0x3) + + +/* buffer sizes */ +#define RME96xx_BYTES_PER_SAMPLE 4 /* sizeof(u32) */ +#define RME_16K 16*1024 + +#define RME96xx_DMA_MAX_SAMPLES (RME_16K) +#define RME96xx_DMA_MAX_SIZE (RME_16K * RME96xx_BYTES_PER_SAMPLE) +#define RME96xx_DMA_MAX_SIZE_ALL (RME96xx_DMA_MAX_SIZE * RME96xx_CHANNELS_PER_CARD) + +#define RME96xx_NUM_OF_FRAGMENTS 2 +#define RME96xx_FRAGMENT_MAX_SIZE (RME96xx_DMA_MAX_SIZE/2) +#define RME96xx_FRAGMENT_MAX_SAMPLES (RME96xx_DMA_MAX_SAMPLES/2) +#define RME96xx_MAX_LATENCY 7 /* 16k samples */ + + +#define RME96xx_MAX_DEVS 4 /* we provide some OSS stereodevs */ +#define RME96xx_MASK_DEVS 0x3 /* RME96xx_MAX_DEVS-1 */ + +#define RME_MESS "rme96xx:" +/*------------------------------------------------------------------------ + Types, struct and function declarations + ------------------------------------------------------------------------*/ + + +/* --------------------------------------------------------------------- */ + +static const char invalid_magic[] = KERN_CRIT RME_MESS" invalid magic value\n"; + +#define VALIDATE_STATE(s) \ +({ \ + if (!(s) || (s)->magic != RME96xx_MAGIC) { \ + printk(invalid_magic); \ + return -ENXIO; \ + } \ +}) + +/* --------------------------------------------------------------------- */ + + +static struct file_operations rme96xx_audio_fops; +static struct file_operations rme96xx_mixer_fops; +static int numcards; + +typedef int32_t raw_sample_t; + +typedef struct _rme96xx_info { + + /* hardware settings */ + int magic; + struct pci_dev * pcidev; /* pci_dev structure */ + unsigned long __iomem *iobase; + unsigned int irq; + + /* list of rme96xx devices */ + struct list_head devs; + + spinlock_t lock; + + u32 *recbuf; /* memory for rec buffer */ + u32 *playbuf; /* memory for play buffer */ + + u32 control_register; + + u32 thru_bits; /* thru 1=on, 0=off channel 1=Bit1... channel 26= Bit26 */ + + int hw_rev; /* h/w rev * 10 (i.e. 1.5 has hw_rev = 15) */ + char *card_name; /* hammerfall or hammerfall light names */ + + int open_count; /* unused ??? HP20020201 */ + + int rate; + int latency; + unsigned int fragsize; + int started; + + int hwptr; /* can be negativ because of pci burst offset */ + unsigned int hwbufid; /* set by interrupt, buffer which is written/read now */ + + struct dmabuf { + + unsigned int format; + int formatshift; + int inchannels; /* number of channels for device */ + int outchannels; /* number of channels for device */ + int mono; /* if true, we play mono on 2 channels */ + int inoffset; /* which channel is considered the first one */ + int outoffset; + + /* state */ + int opened; /* open() made */ + int started; /* first write/read */ + int mmapped; /* mmap */ + int open_mode; + + struct _rme96xx_info *s; + + /* pointer to read/write position in buffer */ + unsigned readptr; + unsigned writeptr; + + unsigned error; /* over/underruns cleared on sync again */ + + /* waiting and locking */ + wait_queue_head_t wait; + struct semaphore open_sem; + wait_queue_head_t open_wait; + + } dma[RME96xx_MAX_DEVS]; + + int dspnum[RME96xx_MAX_DEVS]; /* register with sound subsystem */ + int mixer; /* register with sound subsystem */ +} rme96xx_info; + + +/* fiddling with the card (first level hardware control) */ + +static inline void rme96xx_set_ctrl(rme96xx_info* s,int mask) +{ + + s->control_register|=mask; + writel(s->control_register,s->iobase + RME96xx_control_register); + +} + +static inline void rme96xx_unset_ctrl(rme96xx_info* s,int mask) +{ + + s->control_register&=(~mask); + writel(s->control_register,s->iobase + RME96xx_control_register); + +} + +static inline int rme96xx_get_sample_rate_status(rme96xx_info* s) +{ + int val; + u32 status; + status = readl(s->iobase + RME96xx_status_register); + val = (status & RME96xx_fs48) ? 48000 : 44100; + if (status & RME96xx_DS_rd) + val *= 2; + return val; +} + +static inline int rme96xx_get_sample_rate_ctrl(rme96xx_info* s) +{ + int val; + val = (s->control_register & RME96xx_freq) ? 48000 : 44100; + if (s->control_register & RME96xx_DS) + val *= 2; + return val; +} + + +/* code from ALSA card-rme9652.c for rev 1.5 SPDIF receiver HP 20020201 */ + +static void rme96xx_spdif_set_bit (rme96xx_info* s, int mask, int onoff) +{ + if (onoff) + s->control_register |= mask; + else + s->control_register &= ~mask; + + writel(s->control_register,s->iobase + RME96xx_control_register); +} + +static void rme96xx_spdif_write_byte (rme96xx_info* s, const int val) +{ + long mask; + long i; + + for (i = 0, mask = 0x80; i < 8; i++, mask >>= 1) { + if (val & mask) + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_WRITE, 1); + else + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_WRITE, 0); + + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_CLOCK, 1); + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_CLOCK, 0); + } +} + +static int rme96xx_spdif_read_byte (rme96xx_info* s) +{ + long mask; + long val; + long i; + + val = 0; + + for (i = 0, mask = 0x80; i < 8; i++, mask >>= 1) { + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_CLOCK, 1); + if (readl(s->iobase + RME96xx_status_register) & RME96xx_SPDIF_READ) + val |= mask; + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_CLOCK, 0); + } + + return val; +} + +static void rme96xx_write_spdif_codec (rme96xx_info* s, const int address, const int data) +{ + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_SELECT, 1); + rme96xx_spdif_write_byte (s, 0x20); + rme96xx_spdif_write_byte (s, address); + rme96xx_spdif_write_byte (s, data); + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_SELECT, 0); +} + + +static int rme96xx_spdif_read_codec (rme96xx_info* s, const int address) +{ + int ret; + + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_SELECT, 1); + rme96xx_spdif_write_byte (s, 0x20); + rme96xx_spdif_write_byte (s, address); + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_SELECT, 0); + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_SELECT, 1); + + rme96xx_spdif_write_byte (s, 0x21); + ret = rme96xx_spdif_read_byte (s); + rme96xx_spdif_set_bit (s, RME96xx_SPDIF_SELECT, 0); + + return ret; +} + +static void rme96xx_initialize_spdif_receiver (rme96xx_info* s) +{ + /* XXX what unsets this ? */ + /* no idea ??? HP 20020201 */ + + s->control_register |= RME96xx_SPDIF_RESET; + + rme96xx_write_spdif_codec (s, 4, 0x40); + rme96xx_write_spdif_codec (s, 17, 0x13); + rme96xx_write_spdif_codec (s, 6, 0x02); +} + +static inline int rme96xx_spdif_sample_rate (rme96xx_info *s, int *spdifrate) +{ + unsigned int rate_bits; + + *spdifrate = 0x1; + if (readl(s->iobase + RME96xx_status_register) & RME96xx_ERF) { + return -1; /* error condition */ + } + + if (s->hw_rev == 15) { + + int x, y, ret; + + x = rme96xx_spdif_read_codec (s, 30); + + if (x != 0) + y = 48000 * 64 / x; + else + y = 0; + + if (y > 30400 && y < 33600) {ret = 32000; *spdifrate = 0x7;} + else if (y > 41900 && y < 46000) {ret = 44100; *spdifrate = 0x6;} + else if (y > 46000 && y < 50400) {ret = 48000; *spdifrate = 0x5;} + else if (y > 60800 && y < 67200) {ret = 64000; *spdifrate = 0x0;} + else if (y > 83700 && y < 92000) {ret = 88200; *spdifrate = 0x4;} + else if (y > 92000 && y < 100000) {ret = 96000; *spdifrate = 0x3;} + else {ret = 0; *spdifrate = 0x1;} + return ret; + } + + rate_bits = readl(s->iobase + RME96xx_status_register) & RME96xx_F; + + switch (*spdifrate = rme96xx_decode_spdif_rate(rate_bits)) { + case 0x7: + return 32000; + break; + + case 0x6: + return 44100; + break; + + case 0x5: + return 48000; + break; + + case 0x4: + return 88200; + break; + + case 0x3: + return 96000; + break; + + case 0x0: + return 64000; + break; + + default: + /* was an ALSA warning ... + snd_printk("%s: unknown S/PDIF input rate (bits = 0x%x)\n", + s->card_name, rate_bits); + */ + return 0; + break; + } +} + +/* end of code from ALSA card-rme9652.c */ + + + +/* the hwbuf in the status register seems to have some jitter, to get rid of + it, we first only let the numbers grow, to be on the secure side we + subtract a certain amount RME96xx_BURSTBYTES from the resulting number */ + +/* the function returns the hardware pointer in bytes */ +#define RME96xx_BURSTBYTES -64 /* bytes by which hwptr could be off */ + +static inline int rme96xx_gethwptr(rme96xx_info* s,int exact) +{ + unsigned long flags; + if (exact) { + unsigned int hwp; +/* the hwptr seems to be rather unreliable :(, so we don't use it */ + spin_lock_irqsave(&s->lock,flags); + + hwp = readl(s->iobase + RME96xx_status_register) & 0xffc0; + s->hwptr = (hwp < s->hwptr) ? s->hwptr : hwp; +// s->hwptr = hwp; + + spin_unlock_irqrestore(&s->lock,flags); + return (s->hwptr+RME96xx_BURSTBYTES) & ((s->fragsize<<1)-1); + } + return (s->hwbufid ? s->fragsize : 0); +} + +static inline void rme96xx_setlatency(rme96xx_info* s,int l) +{ + s->latency = l; + s->fragsize = 1<<(8+l); + rme96xx_unset_ctrl(s,RME96xx_latency); + rme96xx_set_ctrl(s,RME96xx_SET_LATENCY(l)); +} + + +static void rme96xx_clearbufs(struct dmabuf* dma) +{ + int i,j; + unsigned long flags; + + /* clear dmabufs */ + for(i=0;ioutchannels + dma->mono;j++) + memset(&dma->s->playbuf[(dma->outoffset + j)*RME96xx_DMA_MAX_SAMPLES], + 0, RME96xx_DMA_MAX_SIZE); + } + spin_lock_irqsave(&dma->s->lock,flags); + dma->writeptr = 0; + dma->readptr = 0; + spin_unlock_irqrestore(&dma->s->lock,flags); +} + +static int rme96xx_startcard(rme96xx_info *s,int stop) +{ + int i; + unsigned long flags; + + COMM ("startcard"); + if(s->control_register & RME96xx_IE){ + /* disable interrupt first */ + + rme96xx_unset_ctrl( s,RME96xx_start_bit ); + udelay(10); + rme96xx_unset_ctrl( s,RME96xx_IE); + spin_lock_irqsave(&s->lock,flags); /* timing is critical */ + s->started = 0; + spin_unlock_irqrestore(&s->lock,flags); + if (stop) { + COMM("Sound card stopped"); + return 1; + } + } + COMM ("interrupt disabled"); + /* first initialize all pointers on card */ + for(i=0;iiobase + i); + udelay(10); /* ?? */ + } + COMM ("regs cleaned"); + + spin_lock_irqsave(&s->lock,flags); /* timing is critical */ + udelay(10); + s->started = 1; + s->hwptr = 0; + spin_unlock_irqrestore(&s->lock,flags); + + rme96xx_set_ctrl( s, RME96xx_IE | RME96xx_start_bit); + + + COMM("Sound card started"); + + return 1; +} + + +static inline int rme96xx_getospace(struct dmabuf * dma, unsigned int hwp) +{ + int cnt; + int swptr; + unsigned long flags; + + spin_lock_irqsave(&dma->s->lock,flags); + swptr = dma->writeptr; + cnt = (hwp - swptr); + + if (cnt < 0) { + cnt = ((dma->s->fragsize<<1) - swptr); + } + spin_unlock_irqrestore(&dma->s->lock,flags); + return cnt; +} + +static inline int rme96xx_getispace(struct dmabuf * dma, unsigned int hwp) +{ + int cnt; + int swptr; + unsigned long flags; + + spin_lock_irqsave(&dma->s->lock,flags); + swptr = dma->readptr; + cnt = (hwp - swptr); + + if (cnt < 0) { + cnt = ((dma->s->fragsize<<1) - swptr); + } + spin_unlock_irqrestore(&dma->s->lock,flags); + return cnt; +} + + +static inline int rme96xx_copyfromuser(struct dmabuf* dma,const char __user * buffer,int count,int hop) +{ + int swptr = dma->writeptr; + switch (dma->format) { + case AFMT_S32_BLOCKED: + { + char __user * buf = (char __user *)buffer; + int cnt = count/dma->outchannels; + int i; + for (i=0;i < dma->outchannels;i++) { + char* hwbuf =(char*) &dma->s->playbuf[(dma->outoffset + i)*RME96xx_DMA_MAX_SAMPLES]; + hwbuf+=swptr; + + if (copy_from_user(hwbuf,buf, cnt)) + return -1; + buf+=hop; + } + swptr+=cnt; + break; + } + case AFMT_S16_LE: + { + int i,j; + int cnt = count/dma->outchannels; + for (i=0;i < dma->outchannels + dma->mono;i++) { + short __user * sbuf = (short __user *)buffer + i*(!dma->mono); + short* hwbuf =(short*) &dma->s->playbuf[(dma->outoffset + i)*RME96xx_DMA_MAX_SAMPLES]; + hwbuf+=(swptr>>1); + for (j=0;j<(cnt>>1);j++) { + hwbuf++; /* skip the low 16 bits */ + __get_user(*hwbuf++,sbuf++); + sbuf+=(dma->outchannels-1); + } + } + swptr += (cnt<<1); + break; + } + default: + printk(RME_MESS" unsupported format\n"); + return -1; + } /* switch */ + + swptr&=((dma->s->fragsize<<1) -1); + dma->writeptr = swptr; + + return 0; +} + +/* The count argument is the number of bytes */ +static inline int rme96xx_copytouser(struct dmabuf* dma,const char __user* buffer,int count,int hop) +{ + int swptr = dma->readptr; + switch (dma->format) { + case AFMT_S32_BLOCKED: + { + char __user * buf = (char __user *)buffer; + int cnt = count/dma->inchannels; + int i; + + for (i=0;i < dma->inchannels;i++) { + char* hwbuf =(char*) &dma->s->recbuf[(dma->inoffset + i)*RME96xx_DMA_MAX_SAMPLES]; + hwbuf+=swptr; + + if (copy_to_user(buf,hwbuf,cnt)) + return -1; + buf+=hop; + } + swptr+=cnt; + break; + } + case AFMT_S16_LE: + { + int i,j; + int cnt = count/dma->inchannels; + for (i=0;i < dma->inchannels;i++) { + short __user * sbuf = (short __user *)buffer + i; + short* hwbuf =(short*) &dma->s->recbuf[(dma->inoffset + i)*RME96xx_DMA_MAX_SAMPLES]; + hwbuf+=(swptr>>1); + for (j=0;j<(cnt>>1);j++) { + hwbuf++; + __put_user(*hwbuf++,sbuf++); + sbuf+=(dma->inchannels-1); + } + } + swptr += (cnt<<1); + break; + } + default: + printk(RME_MESS" unsupported format\n"); + return -1; + } /* switch */ + + swptr&=((dma->s->fragsize<<1) -1); + dma->readptr = swptr; + return 0; +} + + +static irqreturn_t rme96xx_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + int i; + rme96xx_info *s = (rme96xx_info *)dev_id; + struct dmabuf *db; + u32 status; + unsigned long flags; + + status = readl(s->iobase + RME96xx_status_register); + if (!(status & RME96xx_IRQ)) { + return IRQ_NONE; + } + + spin_lock_irqsave(&s->lock,flags); + writel(0,s->iobase + RME96xx_irq_clear); + + s->hwbufid = (status & RME96xx_buffer_id)>>26; + if ((status & 0xffc0) <= 256) s->hwptr = 0; + for(i=0;idma[i]); + if(db->started > 0) + wake_up(&(db->wait)); + } + spin_unlock_irqrestore(&s->lock,flags); + return IRQ_HANDLED; +} + + + +/*---------------------------------------------------------------------------- + PCI detection and module initialization stuff + ----------------------------------------------------------------------------*/ + +static void* busmaster_malloc(int size) { + int pg; /* 2 s exponent of memory size */ + char *buf; + + DBG(printk("kernel malloc pages ..\n")); + + for (pg = 0; PAGE_SIZE * (1 << pg) < size; pg++); + + buf = (char *) __get_free_pages(GFP_KERNEL | GFP_DMA, pg); + + if (buf) { + struct page* page, *last_page; + + page = virt_to_page(buf); + last_page = virt_to_page(buf + (1 << pg)); + DBG(printk("setting reserved bit\n")); + while (page < last_page) { + SetPageReserved(page); + page++; + } + return buf; + } + DBG(printk("allocated %ld",(long)buf)); + return NULL; +} + +static void busmaster_free(void* ptr,int size) { + int pg; + struct page* page, *last_page; + + if (ptr == NULL) + return; + + for (pg = 0; PAGE_SIZE * (1 << pg) < size; pg++); + + page = virt_to_page(ptr); + last_page = page + (1 << pg); + while (page < last_page) { + ClearPageReserved(page); + page++; + } + DBG(printk("freeing pages\n")); + free_pages((unsigned long) ptr, pg); + DBG(printk("done\n")); +} + +/* initialize those parts of the info structure which are not pci detectable resources */ + +static int rme96xx_dmabuf_init(rme96xx_info * s,struct dmabuf* dma,int ioffset,int ooffset) { + + init_MUTEX(&dma->open_sem); + init_waitqueue_head(&dma->open_wait); + init_waitqueue_head(&dma->wait); + dma->s = s; + dma->error = 0; + + dma->format = AFMT_S32_BLOCKED; + dma->formatshift = 0; + dma->inchannels = dma->outchannels = 1; + dma->inoffset = ioffset; + dma->outoffset = ooffset; + + dma->opened=0; + dma->started=0; + dma->mmapped=0; + dma->open_mode=0; + dma->mono=0; + + rme96xx_clearbufs(dma); + return 0; +} + + +static int rme96xx_init(rme96xx_info* s) +{ + int i; + int status; + unsigned short rev; + + DBG(printk("%s\n", __FUNCTION__)); + numcards++; + + s->magic = RME96xx_MAGIC; + + spin_lock_init(&s->lock); + + COMM ("setup busmaster memory") + s->recbuf = busmaster_malloc(RME96xx_DMA_MAX_SIZE_ALL); + s->playbuf = busmaster_malloc(RME96xx_DMA_MAX_SIZE_ALL); + + if (!s->recbuf || !s->playbuf) { + printk(KERN_ERR RME_MESS" Unable to allocate busmaster memory\n"); + return -ENODEV; + } + + COMM ("setting rec and playbuffers") + + writel((u32) virt_to_bus(s->recbuf),s->iobase + RME96xx_rec_buffer); + writel((u32) virt_to_bus(s->playbuf),s->iobase + RME96xx_play_buffer); + + COMM ("initializing control register") + rme96xx_unset_ctrl(s,0xffffffff); + rme96xx_set_ctrl(s,RME96xx_ctrl_init); + + + COMM ("setup devices") + for (i=0;i < devices;i++) { + struct dmabuf * dma = &s->dma[i]; + rme96xx_dmabuf_init(s,dma,2*i,2*i); + } + + /* code from ALSA card-rme9652.c HP 20020201 */ + /* Determine the h/w rev level of the card. This seems like + a particularly kludgy way to encode it, but its what RME + chose to do, so we follow them ... + */ + + status = readl(s->iobase + RME96xx_status_register); + if (rme96xx_decode_spdif_rate(status&RME96xx_F) == 1) { + s->hw_rev = 15; + } else { + s->hw_rev = 11; + } + + /* Differentiate between the standard Hammerfall, and the + "Light", which does not have the expansion board. This + method comes from information received from Mathhias + Clausen at RME. Display the EEPROM and h/w revID where + relevant. + */ + + pci_read_config_word(s->pcidev, PCI_CLASS_REVISION, &rev); + switch (rev & 0xff) { + case 8: /* original eprom */ + if (s->hw_rev == 15) { + s->card_name = "RME Digi9636 (Rev 1.5)"; + } else { + s->card_name = "RME Digi9636"; + } + break; + case 9: /* W36_G EPROM */ + s->card_name = "RME Digi9636 (Rev G)"; + break; + case 4: /* W52_G EPROM */ + s->card_name = "RME Digi9652 (Rev G)"; + break; + default: + case 3: /* original eprom */ + if (s->hw_rev == 15) { + s->card_name = "RME Digi9652 (Rev 1.5)"; + } else { + s->card_name = "RME Digi9652"; + } + break; + } + + printk(KERN_INFO RME_MESS" detected %s (hw_rev %d)\n",s->card_name,s->hw_rev); + + if (s->hw_rev == 15) + rme96xx_initialize_spdif_receiver (s); + + s->started = 0; + rme96xx_setlatency(s,7); + + printk(KERN_INFO RME_MESS" card %d initialized\n",numcards); + return 0; +} + + +/* open uses this to figure out which device was opened .. this seems to be + unnecessary complex */ + +static LIST_HEAD(devs); + +static int __devinit rme96xx_probe(struct pci_dev *pcidev, const struct pci_device_id *pciid) +{ + int i; + rme96xx_info *s; + + DBG(printk("%s\n", __FUNCTION__)); + + if (pcidev->irq == 0) + return -1; + if (!pci_dma_supported(pcidev, 0xffffffff)) { + printk(KERN_WARNING RME_MESS" architecture does not support 32bit PCI busmaster DMA\n"); + return -1; + } + if (!(s = kmalloc(sizeof(rme96xx_info), GFP_KERNEL))) { + printk(KERN_WARNING RME_MESS" out of memory\n"); + return -1; + } + memset(s, 0, sizeof(rme96xx_info)); + + s->pcidev = pcidev; + s->iobase = ioremap(pci_resource_start(pcidev, 0),RME96xx_IO_EXTENT); + s->irq = pcidev->irq; + + DBG(printk("remapped iobase: %lx irq %d\n",(long)s->iobase,s->irq)); + + if (pci_enable_device(pcidev)) + goto err_irq; + if (request_irq(s->irq, rme96xx_interrupt, SA_SHIRQ, "rme96xx", s)) { + printk(KERN_ERR RME_MESS" irq %u in use\n", s->irq); + goto err_irq; + } + + /* initialize the card */ + + i = 0; + if (rme96xx_init(s) < 0) { + printk(KERN_ERR RME_MESS" initialization failed\n"); + goto err_devices; + } + for (i=0;idspnum[i] = register_sound_dsp(&rme96xx_audio_fops, -1)) < 0) + goto err_devices; + } + + if ((s->mixer = register_sound_mixer(&rme96xx_mixer_fops, -1)) < 0) + goto err_devices; + + pci_set_drvdata(pcidev, s); + pcidev->dma_mask = 0xffffffff; /* ????? */ + /* put it into driver list */ + list_add_tail(&s->devs, &devs); + + DBG(printk("initialization successful\n")); + return 0; + + /* error handler */ + err_devices: + while (i--) + unregister_sound_dsp(s->dspnum[i]); + free_irq(s->irq,s); + err_irq: + kfree(s); + return -1; +} + + +static void __devexit rme96xx_remove(struct pci_dev *dev) +{ + int i; + rme96xx_info *s = pci_get_drvdata(dev); + + if (!s) { + printk(KERN_ERR"device structure not valid\n"); + return ; + } + + if (s->started) rme96xx_startcard(s,0); + + i = devices; + while (i) { + i--; + unregister_sound_dsp(s->dspnum[i]); + } + + unregister_sound_mixer(s->mixer); + synchronize_irq(s->irq); + free_irq(s->irq,s); + busmaster_free(s->recbuf,RME96xx_DMA_MAX_SIZE_ALL); + busmaster_free(s->playbuf,RME96xx_DMA_MAX_SIZE_ALL); + kfree(s); + pci_set_drvdata(dev, NULL); +} + + +#ifndef PCI_VENDOR_ID_RME +#define PCI_VENDOR_ID_RME 0x10ee +#endif +#ifndef PCI_DEVICE_ID_RME9652 +#define PCI_DEVICE_ID_RME9652 0x3fc4 +#endif +#ifndef PCI_ANY_ID +#define PCI_ANY_ID 0 +#endif + +static struct pci_device_id id_table[] = { + { + .vendor = PCI_VENDOR_ID_RME, + .device = PCI_DEVICE_ID_RME9652, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, id_table); + +static struct pci_driver rme96xx_driver = { + .name = "rme96xx", + .id_table = id_table, + .probe = rme96xx_probe, + .remove = __devexit_p(rme96xx_remove), +}; + +static int __init init_rme96xx(void) +{ + printk(KERN_INFO RME_MESS" version "RMEVERSION" time " __TIME__ " " __DATE__ "\n"); + devices = ((devices-1) & RME96xx_MASK_DEVS) + 1; + printk(KERN_INFO RME_MESS" reserving %d dsp device(s)\n",devices); + numcards = 0; + return pci_module_init(&rme96xx_driver); +} + +static void __exit cleanup_rme96xx(void) +{ + printk(KERN_INFO RME_MESS" unloading\n"); + pci_unregister_driver(&rme96xx_driver); +} + +module_init(init_rme96xx); +module_exit(cleanup_rme96xx); + + + + + +/*-------------------------------------------------------------------------- + Implementation of file operations +---------------------------------------------------------------------------*/ + +#define RME96xx_FMT (AFMT_S16_LE|AFMT_U8|AFMT_S32_BLOCKED) +/* AFTM_U8 is not (yet?) supported ... HP20020201 */ + +static int rme96xx_ioctl(struct inode *in, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct dmabuf * dma = (struct dmabuf *)file->private_data; + rme96xx_info *s = dma->s; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int count; + int val = 0; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + VALIDATE_STATE(s); + + DBG(printk("ioctl %ud\n",cmd)); + + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_SYNC: +#if 0 + if (file->f_mode & FMODE_WRITE) + return drain_dac2(s, 0/*file->f_flags & O_NONBLOCK*/); +#endif + 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, p); + + case SNDCTL_DSP_RESET: +// rme96xx_clearbufs(dma); + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { +/* generally it's not a problem if we change the speed + if (dma->open_mode & (~file->f_mode) & (FMODE_READ|FMODE_WRITE)) + return -EINVAL; +*/ + spin_lock_irqsave(&s->lock, flags); + + switch (val) { + case 44100: + case 88200: + rme96xx_unset_ctrl(s,RME96xx_freq); + break; + case 48000: + case 96000: + rme96xx_set_ctrl(s,RME96xx_freq); + break; + /* just report current rate as default + e.g. use 0 to "select" current digital input rate + default: + rme96xx_unset_ctrl(s,RME96xx_freq); + val = 44100; + */ + } + if (val > 50000) + rme96xx_set_ctrl(s,RME96xx_DS); + else + rme96xx_unset_ctrl(s,RME96xx_DS); + /* set val to actual value HP 20020201 */ + /* NOTE: if not "Sync Master", reported rate might be not yet "updated" ... but I don't want to insert a long udelay() here */ + if ((s->control_register & RME96xx_Master) && !(s->control_register & RME96xx_wsel)) + val = rme96xx_get_sample_rate_ctrl(s); + else + val = rme96xx_get_sample_rate_status(s); + s->rate = val; + spin_unlock_irqrestore(&s->lock, flags); + } + DBG(printk("speed set to %d\n",val)); + return put_user(val, p); + + case SNDCTL_DSP_STEREO: /* this plays a mono file on two channels */ + if (get_user(val, p)) + return -EFAULT; + + if (!val) { + DBG(printk("setting to mono\n")); + dma->mono=1; + dma->inchannels = 1; + dma->outchannels = 1; + } + else { + DBG(printk("setting to stereo\n")); + dma->mono = 0; + dma->inchannels = 2; + dma->outchannels = 2; + } + return 0; + case SNDCTL_DSP_CHANNELS: + /* remember to check for resonable offset/channel pairs here */ + if (get_user(val, p)) + return -EFAULT; + + if (file->f_mode & FMODE_WRITE) { + if (val > 0 && (dma->outoffset + val) <= RME96xx_CHANNELS_PER_CARD) + dma->outchannels = val; + else + dma->outchannels = val = 2; + DBG(printk("setting to outchannels %d\n",val)); + } + if (file->f_mode & FMODE_READ) { + if (val > 0 && (dma->inoffset + val) <= RME96xx_CHANNELS_PER_CARD) + dma->inchannels = val; + else + dma->inchannels = val = 2; + DBG(printk("setting to inchannels %d\n",val)); + } + + dma->mono=0; + + return put_user(val, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(RME96xx_FMT, p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + DBG(printk("setting to format %x\n",val)); + if (get_user(val, p)) + return -EFAULT; + if (val != AFMT_QUERY) { + if (val & RME96xx_FMT) + dma->format = val; + switch (dma->format) { + case AFMT_S16_LE: + dma->formatshift=1; + break; + case AFMT_S32_BLOCKED: + dma->formatshift=0; + break; + } + } + return put_user(dma->format, p); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; +#if 0 + if (file->f_mode & FMODE_READ && s->ctrl & CTRL_ADC_EN) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && s->ctrl & CTRL_DAC2_EN) + val |= PCM_ENABLE_OUTPUT; +#endif + return put_user(val, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + return -EFAULT; +#if 0 + 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_dac2.ready && (ret = prog_dmabuf_dac2(s))) + return ret; + start_dac2(s); + } else + stop_dac2(s); + } +#endif + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + + val = rme96xx_gethwptr(dma->s,0); + + + count = rme96xx_getospace(dma,val); + if (!s->started) count = s->fragsize*2; + abinfo.fragsize =(s->fragsize*dma->outchannels)>>dma->formatshift; + abinfo.bytes = (count*dma->outchannels)>>dma->formatshift; + abinfo.fragstotal = 2; + abinfo.fragments = (count > s->fragsize); + + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + + val = rme96xx_gethwptr(dma->s,0); + + count = rme96xx_getispace(dma,val); + + abinfo.fragsize = (s->fragsize*dma->inchannels)>>dma->formatshift; + abinfo.bytes = (count*dma->inchannels)>>dma->formatshift; + abinfo.fragstotal = 2; + abinfo.fragments = count > s->fragsize; + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: /* What should this exactly do ? , + ATM it is just abinfo.bytes */ + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + + val = rme96xx_gethwptr(dma->s,0); + count = val - dma->readptr; + if (count < 0) + count += s->fragsize<<1; + + return put_user(count, p); + + +/* check out how to use mmaped mode (can only be blocked !!!) */ + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + val = rme96xx_gethwptr(dma->s,0); + spin_lock_irqsave(&s->lock,flags); + cinfo.bytes = s->fragsize<<1; + count = val - dma->readptr; + if (count < 0) + count += s->fragsize<<1; + + cinfo.blocks = (count > s->fragsize); + cinfo.ptr = val; + if (dma->mmapped) + dma->readptr &= s->fragsize<<1; + spin_unlock_irqrestore(&s->lock,flags); + + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + val = rme96xx_gethwptr(dma->s,0); + spin_lock_irqsave(&s->lock,flags); + cinfo.bytes = s->fragsize<<1; + count = val - dma->writeptr; + if (count < 0) + count += s->fragsize<<1; + + cinfo.blocks = (count > s->fragsize); + cinfo.ptr = val; + if (dma->mmapped) + dma->writeptr &= s->fragsize<<1; + spin_unlock_irqrestore(&s->lock,flags); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + case SNDCTL_DSP_GETBLKSIZE: + return put_user(s->fragsize, p); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + val&=0xffff; + val -= 7; + if (val < 0) val = 0; + if (val > 7) val = 7; + rme96xx_setlatency(s,val); + return 0; + + case SNDCTL_DSP_SUBDIVIDE: +#if 0 + if ((file->f_mode & FMODE_READ && s->dma_adc.subdivision) || + (file->f_mode & FMODE_WRITE && s->dma_dac2.subdivision)) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + if (file->f_mode & FMODE_READ) + s->dma_adc.subdivision = val; + if (file->f_mode & FMODE_WRITE) + s->dma_dac2.subdivision = val; +#endif + return 0; + + case SOUND_PCM_READ_RATE: + /* HP20020201 */ + s->rate = rme96xx_get_sample_rate_status(s); + return put_user(s->rate, p); + + case SOUND_PCM_READ_CHANNELS: + return put_user(dma->outchannels, p); + + case SOUND_PCM_READ_BITS: + switch (dma->format) { + case AFMT_S32_BLOCKED: + val = 32; + break; + case AFMT_S16_LE: + val = 16; + break; + } + return put_user(val, p); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + + } + + + return -ENODEV; +} + + + +static int rme96xx_open(struct inode *in, struct file *f) +{ + int minor = iminor(in); + struct list_head *list; + int devnum; + rme96xx_info *s; + struct dmabuf* dma; + DECLARE_WAITQUEUE(wait, current); + + DBG(printk("device num %d open\n",devnum)); + + nonseekable_open(in, f); + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, rme96xx_info, devs); + for (devnum=0; devnumdspnum[devnum] ^ minor) & ~0xf)) + break; + if (devnumdma[devnum]; + f->private_data = dma; + /* wait for device to become free */ + down(&dma->open_sem); + while (dma->open_mode & f->f_mode) { + if (f->f_flags & O_NONBLOCK) { + up(&dma->open_sem); + return -EBUSY; + } + add_wait_queue(&dma->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&dma->open_sem); + schedule(); + remove_wait_queue(&dma->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&dma->open_sem); + } + + COMM ("hardware open") + + if (!dma->opened) rme96xx_dmabuf_init(dma->s,dma,dma->inoffset,dma->outoffset); + + dma->open_mode |= (f->f_mode & (FMODE_READ | FMODE_WRITE)); + dma->opened = 1; + up(&dma->open_sem); + + DBG(printk("device num %d open finished\n",devnum)); + return 0; +} + +static int rme96xx_release(struct inode *in, struct file *file) +{ + struct dmabuf * dma = (struct dmabuf*) file->private_data; + /* int hwp; ... was unused HP20020201 */ + DBG(printk("%s\n", __FUNCTION__)); + + COMM ("draining") + if (dma->open_mode & FMODE_WRITE) { +#if 0 /* Why doesn't this work with some cards ?? */ + hwp = rme96xx_gethwptr(dma->s,0); + while (rme96xx_getospace(dma,hwp)) { + interruptible_sleep_on(&(dma->wait)); + hwp = rme96xx_gethwptr(dma->s,0); + } +#endif + rme96xx_clearbufs(dma); + } + + dma->open_mode &= (~file->f_mode) & (FMODE_READ|FMODE_WRITE); + + if (!(dma->open_mode & (FMODE_READ|FMODE_WRITE))) { + dma->opened = 0; + if (dma->s->started) rme96xx_startcard(dma->s,1); + } + + wake_up(&dma->open_wait); + up(&dma->open_sem); + + return 0; +} + + +static ssize_t rme96xx_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct dmabuf *dma = (struct dmabuf *)file->private_data; + ssize_t ret = 0; + int cnt; /* number of bytes from "buffer" that will/can be used */ + int hop = count/dma->outchannels; + int hwp; + int exact = (file->f_flags & O_NONBLOCK); + + + if(dma == NULL || (dma->s) == NULL) + return -ENXIO; + + if (dma->mmapped || !dma->opened) + return -ENXIO; + + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + + if (! (dma->open_mode & FMODE_WRITE)) + return -ENXIO; + + if (!dma->s->started) rme96xx_startcard(dma->s,exact); + hwp = rme96xx_gethwptr(dma->s,0); + + if(!(dma->started)){ + COMM ("first write") + + dma->readptr = hwp; + dma->writeptr = hwp; + dma->started = 1; + } + + while (count > 0) { + cnt = rme96xx_getospace(dma,hwp); + cnt>>=dma->formatshift; + cnt*=dma->outchannels; + if (cnt > count) + cnt = count; + + if (cnt != 0) { + if (rme96xx_copyfromuser(dma,buffer,cnt,hop)) + return ret ? ret : -EFAULT; + count -= cnt; + buffer += cnt; + ret += cnt; + if (count == 0) return ret; + } + if (file->f_flags & O_NONBLOCK) + return ret ? ret : -EAGAIN; + + if ((hwp - dma->writeptr) <= 0) { + interruptible_sleep_on(&(dma->wait)); + + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + } + + hwp = rme96xx_gethwptr(dma->s,exact); + + }; /* count > 0 */ + + return ret; +} + +static ssize_t rme96xx_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct dmabuf *dma = (struct dmabuf *)file->private_data; + ssize_t ret = 0; + int cnt; /* number of bytes from "buffer" that will/can be used */ + int hop = count/dma->inchannels; + int hwp; + int exact = (file->f_flags & O_NONBLOCK); + + + if(dma == NULL || (dma->s) == NULL) + return -ENXIO; + + if (dma->mmapped || !dma->opened) + return -ENXIO; + + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + + if (! (dma->open_mode & FMODE_READ)) + return -ENXIO; + + if (!dma->s->started) rme96xx_startcard(dma->s,exact); + hwp = rme96xx_gethwptr(dma->s,0); + + if(!(dma->started)){ + COMM ("first read") + + dma->writeptr = hwp; + dma->readptr = hwp; + dma->started = 1; + } + + while (count > 0) { + cnt = rme96xx_getispace(dma,hwp); + cnt>>=dma->formatshift; + cnt*=dma->inchannels; + + if (cnt > count) + cnt = count; + + if (cnt != 0) { + + if (rme96xx_copytouser(dma,buffer,cnt,hop)) + return ret ? ret : -EFAULT; + + count -= cnt; + buffer += cnt; + ret += cnt; + if (count == 0) return ret; + } + if (file->f_flags & O_NONBLOCK) + return ret ? ret : -EAGAIN; + + if ((hwp - dma->readptr) <= 0) { + interruptible_sleep_on(&(dma->wait)); + + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + } + hwp = rme96xx_gethwptr(dma->s,exact); + + }; /* count > 0 */ + + return ret; +} + +static int rm96xx_mmap(struct file *file, struct vm_area_struct *vma) { + struct dmabuf *dma = (struct dmabuf *)file->private_data; + rme96xx_info* s = dma->s; + unsigned long size; + + VALIDATE_STATE(s); + lock_kernel(); + + if (vma->vm_pgoff != 0) { + unlock_kernel(); + return -EINVAL; + } + size = vma->vm_end - vma->vm_start; + if (size > RME96xx_DMA_MAX_SIZE) { + unlock_kernel(); + return -EINVAL; + } + + + if (vma->vm_flags & VM_WRITE) { + if (!s->started) rme96xx_startcard(s,1); + + if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(s->playbuf + dma->outoffset*RME96xx_DMA_MAX_SIZE) >> PAGE_SHIFT, size, vma->vm_page_prot)) { + unlock_kernel(); + return -EAGAIN; + } + } + else if (vma->vm_flags & VM_READ) { + if (!s->started) rme96xx_startcard(s,1); + if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(s->playbuf + dma->inoffset*RME96xx_DMA_MAX_SIZE) >> PAGE_SHIFT, size, vma->vm_page_prot)) { + unlock_kernel(); + return -EAGAIN; + } + } else { + unlock_kernel(); + return -EINVAL; + } + + +/* this is the mapping */ + vma->vm_flags &= ~VM_IO; + dma->mmapped = 1; + unlock_kernel(); + return 0; +} + +static unsigned int rme96xx_poll(struct file *file, struct poll_table_struct *wait) +{ + struct dmabuf *dma = (struct dmabuf *)file->private_data; + rme96xx_info* s = dma->s; + unsigned int mask = 0; + unsigned int hwp,cnt; + + DBG(printk("rme96xx poll_wait ...\n")); + VALIDATE_STATE(s); + + if (!s->started) { + mask |= POLLOUT | POLLWRNORM; + } + poll_wait(file, &dma->wait, wait); + + hwp = rme96xx_gethwptr(dma->s,0); + + DBG(printk("rme96xx poll: ..cnt %d > %d\n",cnt,s->fragsize)); + + cnt = rme96xx_getispace(dma,hwp); + + if (file->f_mode & FMODE_READ) + if (cnt > 0) + mask |= POLLIN | POLLRDNORM; + + + + cnt = rme96xx_getospace(dma,hwp); + + if (file->f_mode & FMODE_WRITE) + if (cnt > 0) + mask |= POLLOUT | POLLWRNORM; + + +// printk("rme96xx poll_wait ...%d > %d\n",rme96xx_getospace(dma,hwp),rme96xx_getispace(dma,hwp)); + + return mask; +} + + +static struct file_operations rme96xx_audio_fops = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) + .owner = THIS_MODULE, +#endif + .read = rme96xx_read, + .write = rme96xx_write, + .poll = rme96xx_poll, + .ioctl = rme96xx_ioctl, + .mmap = rm96xx_mmap, + .open = rme96xx_open, + .release = rme96xx_release +}; + +static int rme96xx_mixer_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct list_head *list; + rme96xx_info *s; + + COMM ("mixer open"); + + nonseekable_open(inode, file); + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, rme96xx_info, devs); + if (s->mixer== minor) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + + COMM ("mixer opened") + return 0; +} + +static int rme96xx_mixer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + rme96xx_info *s = (rme96xx_info *)file->private_data; + u32 status; + int spdifrate; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + status = readl(s->iobase + RME96xx_status_register); + /* hack to convert rev 1.5 SPDIF rate to "crystalrate" format HP 20020201 */ + rme96xx_spdif_sample_rate(s,&spdifrate); + status = (status & ~RME96xx_F) | ((spdifrate<<22) & RME96xx_F); + + VALIDATE_STATE(s); + if (cmd == SOUND_MIXER_PRIVATE1) { + rme_mixer mixer; + if (copy_from_user(&mixer,argp,sizeof(mixer))) + return -EFAULT; + + mixer.devnr &= RME96xx_MASK_DEVS; + if (mixer.devnr >= devices) + mixer.devnr = devices-1; + if (file->f_mode & FMODE_WRITE && !s->dma[mixer.devnr].opened) { + /* modify only if device not open */ + if (mixer.o_offset < 0) + mixer.o_offset = 0; + if (mixer.o_offset >= RME96xx_CHANNELS_PER_CARD) + mixer.o_offset = RME96xx_CHANNELS_PER_CARD-1; + if (mixer.i_offset < 0) + mixer.i_offset = 0; + if (mixer.i_offset >= RME96xx_CHANNELS_PER_CARD) + mixer.i_offset = RME96xx_CHANNELS_PER_CARD-1; + s->dma[mixer.devnr].outoffset = mixer.o_offset; + s->dma[mixer.devnr].inoffset = mixer.i_offset; + } + + mixer.o_offset = s->dma[mixer.devnr].outoffset; + mixer.i_offset = s->dma[mixer.devnr].inoffset; + + return copy_to_user(argp, &mixer, sizeof(mixer)) ? -EFAULT : 0; + } + if (cmd == SOUND_MIXER_PRIVATE2) { + return put_user(status, p); + } + if (cmd == SOUND_MIXER_PRIVATE3) { + u32 control; + if (copy_from_user(&control,argp,sizeof(control))) + return -EFAULT; + if (file->f_mode & FMODE_WRITE) { + s->control_register &= ~RME96xx_mixer_allowed; + s->control_register |= control & RME96xx_mixer_allowed; + writel(control,s->iobase + RME96xx_control_register); + } + + return put_user(s->control_register, p); + } + return -1; +} + + + +static int rme96xx_mixer_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static /*const*/ struct file_operations rme96xx_mixer_fops = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) + .owner = THIS_MODULE, +#endif + .ioctl = rme96xx_mixer_ioctl, + .open = rme96xx_mixer_open, + .release = rme96xx_mixer_release, +}; diff --git a/sound/oss/rme96xx.h b/sound/oss/rme96xx.h new file mode 100644 index 000000000000..7a3c188ea0a8 --- /dev/null +++ b/sound/oss/rme96xx.h @@ -0,0 +1,78 @@ +/* (C) 2000 Guenter Geiger + with copy/pastes from the driver of Winfried Ritsch + +Modifications - Heiko Purnhagen + HP20020116 towards REV 1.5 support, based on ALSA's card-rme9652.c + HP20020201 completed? + +A text/graphic control panel (rmectrl/xrmectrl) is available from + http://gige.xdv.org/pages/soft/pages/rme +*/ + + +#ifndef AFMT_S32_BLOCKED +#define AFMT_S32_BLOCKED 0x0000400 +#endif + +/* AFMT_S16_BLOCKED not yet supported */ +#ifndef AFMT_S16_BLOCKED +#define AFMT_S16_BLOCKED 0x0000800 +#endif + + +typedef struct rme_status { + unsigned int irq:1; + unsigned int lockmask:3; /* ADAT input PLLs locked */ + /* 100=ADAT1, 010=ADAT2, 001=ADAT3 */ + unsigned int sr48:1; /* sample rate: 0=44.1/88.2 1=48/96 kHz */ + unsigned int wclock:1; /* 1=wordclock used */ + unsigned int bufpoint:10; + unsigned int syncmask:3; /* ADAT input in sync with system clock */ + /* 100=ADAT1, 010=ADAT2, 001=ADAT3 */ + unsigned int doublespeed:1; /* sample rate: 0=44.1/48 1=88.2/96 kHz */ + unsigned int tc_busy:1; + unsigned int tc_out:1; + unsigned int crystalrate:3; /* spdif input sample rate: */ + /* 000=64kHz, 100=88.2kHz, 011=96kHz */ + /* 111=32kHz, 110=44.1kHz, 101=48kHz */ + unsigned int spdif_error:1; /* 1=no spdif lock */ + unsigned int bufid:1; + unsigned int tc_valid:1; /* 1=timecode input detected */ + unsigned int spdif_read:1; +} rme_status_t; + + +/* only fields marked W: can be modified by writing to SOUND_MIXER_PRIVATE3 */ +typedef struct rme_control { + unsigned int start:1; + unsigned int latency:3; /* buffer size / latency [samples]: */ + /* 0=64 ... 7=8192 */ + unsigned int master:1; /* W: clock mode: 1=master 0=slave/auto */ + unsigned int ie:1; + unsigned int sr48:1; /* samplerate 0=44.1/88.2, 1=48/96 kHz */ + unsigned int spare:1; + unsigned int doublespeed:1; /* double speed 0=44.1/48, 1=88.2/96 Khz */ + unsigned int pro:1; /* W: SPDIF-OUT 0=consumer, 1=professional */ + unsigned int emphasis:1; /* W: SPDIF-OUT emphasis 0=off, 1=on */ + unsigned int dolby:1; /* W: SPDIF-OUT non-audio bit 1=set, 0=unset */ + unsigned int opt_out:1; /* W: use 1st optical OUT as SPDIF: 1=yes, 0=no */ + unsigned int wordclock:1; /* W: use Wordclock as sync (overwrites master) */ + unsigned int spdif_in:2; /* W: SPDIF-IN: */ + /* 00=optical (ADAT1), 01=coaxial (Cinch), 10=internal CDROM */ + unsigned int sync_ref:2; /* W: preferred sync-source in autosync */ + /* 00=ADAT1, 01=ADAT2, 10=ADAT3, 11=SPDIF */ + unsigned int spdif_reset:1; + unsigned int spdif_select:1; + unsigned int spdif_clock:1; + unsigned int spdif_write:1; + unsigned int adat1_cd:1; /* W: Rev 1.5+: if set, internal CD connector carries ADAT */ +} rme_ctrl_t; + + +typedef struct _rme_mixer { + int i_offset; + int o_offset; + int devnr; + int spare[8]; +} rme_mixer; + diff --git a/sound/oss/sb.h b/sound/oss/sb.h new file mode 100644 index 000000000000..77e8891ce333 --- /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 000000000000..75e54f6f638a --- /dev/null +++ b/sound/oss/sb_audio.c @@ -0,0 +1,1098 @@ +/* + * sound/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 + +#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 000000000000..680b82e15298 --- /dev/null +++ b/sound/oss/sb_card.c @@ -0,0 +1,347 @@ +/* + * sound/oss/sb_card.c + * + * Detection routine for the ISA Sound Blaster and compatable 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 + * 02-07-2003 Bug made it into first release. Take two. + */ + +#include +#include +#include +#include +#include "sound_config.h" +#include "sb_mixer.h" +#include "sb.h" +#ifdef CONFIG_PNP +#include +#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 __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 sb_init_legacy(void) +{ + struct sb_module_options sbmo = {0}; + + if((legacy = kmalloc(sizeof(struct sb_card_config), GFP_KERNEL)) == NULL) { + printk(KERN_ERR "sb: Error: Could not allocate memory\n"); + return -ENOMEM; + } + memset(legacy, 0, sizeof(struct sb_card_config)); + + 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); + } +} + +/* 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 = kmalloc(sizeof(struct sb_card_config), GFP_KERNEL)) == NULL) { + printk(KERN_ERR "sb: Error: Could not allocate memory\n"); + return -ENOMEM; + } + memset(scc, 0, sizeof(struct sb_card_config)); + + 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); + + 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 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) { + pres = pnp_register_card_driver(&sb_pnp_driver); + } +#endif + printk(KERN_INFO "sb: Init: Done\n"); + + /* If either PnP or Legacy registered a card then return + * success */ + if (pres <= 0 && lres <= 0) { +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&sb_pnp_driver); +#endif + 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); + } + +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&sb_pnp_driver); +#endif + + if (smw_free) { + 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 000000000000..5535cff800df --- /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 + */ + +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 000000000000..ce359e6c933a --- /dev/null +++ b/sound/oss/sb_common.c @@ -0,0 +1,1291 @@ +/* + * sound/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 + * + * 2000/09/18 - got rid of attach_uart401 + * Arnaldo Carvalho de Melo + * + * 2001/01/26 - replaced CLI/STI with spinlocks + * Chris Rankin + */ + +#include +#include +#include +#include +#include +#include + +#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, NULL); + + 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_WARN "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_WARN "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, struct pt_regs *dummy) +{ + 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 = (sb_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)?SA_SHIRQ: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); + if(detected_devc) + 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 *)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 000000000000..fae05fe3de43 --- /dev/null +++ b/sound/oss/sb_ess.c @@ -0,0 +1,1832 @@ +#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. Interresting ones are 0x7f, 0x7d and 0x7a. They are + * related to the Audio 2 channel. I also was suprised about the consequenses + * 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 decribed + * 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 allways 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 operation, and uses the + * same dma as audio 1: your ess changes into a funny echo machine. + * + * Received the new 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. It's 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 + * it's 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 + * it's 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. Futhermore several devices can be recorded together (which is not + * possible with the ES1688. + * Besides the separate recording level control for each input, the common + * recordig 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 +#include +#include + +#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_WARN "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; + 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 000000000000..38aa072e01f2 --- /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 000000000000..ed3bd0640ffd --- /dev/null +++ b/sound/oss/sb_midi.c @@ -0,0 +1,205 @@ +/* + * sound/sb_dsp.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 + +#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] = (struct midi_operations *)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 = (struct synth_operations *)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 000000000000..f56898c3981e --- /dev/null +++ b/sound/oss/sb_mixer.c @@ -0,0 +1,768 @@ +/* + * sound/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 : Support for AWE 3DSE device (Jun 7 1999) + */ + +#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; + + regoffs = (*devc->iomap)[dev][LEFT_CHN].regno; + + if (regoffs == 0) + return -EINVAL; + + if ((dev < 0) || (dev >= devc->iomap_sz)) + 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] = (struct mixer_operations *)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 000000000000..ab74426157ba --- /dev/null +++ b/sound/oss/sb_mixer.h @@ -0,0 +1,105 @@ +/* + * sound/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 000000000000..698614226c9a --- /dev/null +++ b/sound/oss/sequencer.c @@ -0,0 +1,1684 @@ +/* + * sound/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 +#include +#define SEQUENCER_C +#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); +} + +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); +} + +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 err; + 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 + 4, 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 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) +{ + int dev = event_rec[1]; + int i, l = 0; + unsigned char *buf = &event_rec[2]; + + if ((int) 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(); +} + +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; +} + +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; + if (semitones > 99) + semitones = 99; + 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 */ +} + + +void sequencer_init(void) +{ + /* drag in sequencer_syms.o */ + { + extern char sequencer_syms_symbol; + sequencer_syms_symbol = 0; + } + + if (sequencer_ok) + return; + MIDIbuf_init(); + queue = (unsigned char *)vmalloc(SEQ_MAX_QUEUE * EV_SZ); + if (queue == NULL) + { + printk(KERN_ERR "sequencer: Can't allocate memory for sequencer output queue\n"); + return; + } + iqueue = (unsigned char *)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; +} + +void sequencer_unload(void) +{ + if(queue) + { + vfree(queue); + queue=NULL; + } + if(iqueue) + { + vfree(iqueue); + iqueue=NULL; + } +} diff --git a/sound/oss/sequencer_syms.c b/sound/oss/sequencer_syms.c new file mode 100644 index 000000000000..45edfd767e4e --- /dev/null +++ b/sound/oss/sequencer_syms.c @@ -0,0 +1,30 @@ +/* + * Exported symbols for sequencer driver. + */ + +#include + +char sequencer_syms_symbol; + +#include "sound_config.h" +#include "sound_calls.h" + +EXPORT_SYMBOL(note_to_freq); +EXPORT_SYMBOL(compute_finetune); +EXPORT_SYMBOL(seq_copy_to_input); +EXPORT_SYMBOL(seq_input_event); +EXPORT_SYMBOL(sequencer_init); +EXPORT_SYMBOL(sequencer_timer); + +EXPORT_SYMBOL(sound_timer_init); +EXPORT_SYMBOL(sound_timer_interrupt); +EXPORT_SYMBOL(sound_timer_syncinterval); +EXPORT_SYMBOL(reprogram_timer); + +/* Tuning */ + +#define _SEQUENCER_C_ +#include "tuning.h" + +EXPORT_SYMBOL(cent_tuning); +EXPORT_SYMBOL(semitone_tuning); diff --git a/sound/oss/sgalaxy.c b/sound/oss/sgalaxy.c new file mode 100644 index 000000000000..3f32d4674371 --- /dev/null +++ b/sound/oss/sgalaxy.c @@ -0,0 +1,207 @@ +/* + * sound/sgalaxy.c + * + * Low level driver for Aztech Sound Galaxy cards. + * Copyright 1998 Artur Skawina + * + * Supported cards: + * Aztech Sound Galaxy Waverider Pro 32 - 3D + * Aztech Sound Galaxy Washington 16 + * + * Based on cs4232.c by Hannu Savolainen and Alan Cox. + * + * + * 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 + * Added __init to sb_rst() and sb_cmd() + */ + +#include +#include + +#include "sound_config.h" +#include "ad1848.h" + +static void sleep( unsigned howlong ) +{ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(howlong); +} + +#define DPORT 0x80 + +/* Sound Blaster regs */ + +#define SBDSP_RESET 0x6 +#define SBDSP_READ 0xA +#define SBDSP_COMMAND 0xC +#define SBDSP_STATUS SBDSP_COMMAND +#define SBDSP_DATA_AVAIL 0xE + +static int __init sb_rst(int base) +{ + int i; + + outb( 1, base+SBDSP_RESET ); /* reset the DSP */ + outb( 0, base+SBDSP_RESET ); + + for ( i=0; i<500; i++ ) /* delay */ + inb(DPORT); + + for ( i=0; i<100000; i++ ) + { + if ( inb( base+SBDSP_DATA_AVAIL )&0x80 ) + break; + } + + if ( inb( base+SBDSP_READ )!=0xAA ) + return 0; + + return 1; +} + +static int __init sb_cmd( int base, unsigned char val ) +{ + int i; + + for ( i=100000; i; i-- ) + { + if ( (inb( base+SBDSP_STATUS )&0x80)==0 ) + { + outb( val, base+SBDSP_COMMAND ); + break; + } + } + return i; /* i>0 == success */ +} + + +#define ai_sgbase driver_use_1 + +static int __init probe_sgalaxy( struct address_info *ai ) +{ + struct resource *ports; + int n; + + if (!request_region(ai->io_base, 4, "WSS config")) { + printk(KERN_ERR "sgalaxy: WSS IO port 0x%03x not available\n", ai->io_base); + return 0; + } + + ports = request_region(ai->io_base + 4, 4, "ad1848"); + if (!ports) { + printk(KERN_ERR "sgalaxy: WSS IO port 0x%03x not available\n", ai->io_base); + release_region(ai->io_base, 4); + return 0; + } + + if (!request_region( ai->ai_sgbase, 0x10, "SoundGalaxy SB")) { + printk(KERN_ERR "sgalaxy: SB IO port 0x%03x not available\n", ai->ai_sgbase); + release_region(ai->io_base + 4, 4); + release_region(ai->io_base, 4); + return 0; + } + + if (ad1848_detect(ports, NULL, ai->osp)) + goto out; /* The card is already active, check irq etc... */ + + /* switch to MSS/WSS mode */ + + sb_rst( ai->ai_sgbase ); + + sb_cmd( ai->ai_sgbase, 9 ); + sb_cmd( ai->ai_sgbase, 0 ); + + sleep( HZ/10 ); + +out: + if (!probe_ms_sound(ai, ports)) { + release_region(ai->io_base + 4, 4); + release_region(ai->io_base, 4); + release_region(ai->ai_sgbase, 0x10); + return 0; + } + + attach_ms_sound(ai, ports, THIS_MODULE); + n=ai->slots[0]; + + if (n!=-1 && audio_devs[n]->mixer_dev != -1 ) { + AD1848_REROUTE( SOUND_MIXER_LINE1, SOUND_MIXER_LINE ); /* Line-in */ + AD1848_REROUTE( SOUND_MIXER_LINE2, SOUND_MIXER_SYNTH ); /* FM+Wavetable*/ + AD1848_REROUTE( SOUND_MIXER_LINE3, SOUND_MIXER_CD ); /* CD */ + } + return 1; +} + +static void __exit unload_sgalaxy( struct address_info *ai ) +{ + unload_ms_sound( ai ); + release_region( ai->ai_sgbase, 0x10 ); +} + +static struct address_info cfg; + +static int __initdata io = -1; +static int __initdata irq = -1; +static int __initdata dma = -1; +static int __initdata dma2 = -1; +static int __initdata sgbase = -1; + +module_param(io, int, 0); +module_param(irq, int, 0); +module_param(dma, int, 0); +module_param(dma2, int, 0); +module_param(sgbase, int, 0); + +static int __init init_sgalaxy(void) +{ + cfg.io_base = io; + cfg.irq = irq; + cfg.dma = dma; + cfg.dma2 = dma2; + cfg.ai_sgbase = sgbase; + + if (cfg.io_base == -1 || cfg.irq == -1 || cfg.dma == -1 || cfg.ai_sgbase == -1 ) { + printk(KERN_ERR "sgalaxy: io, irq, dma and sgbase must be set.\n"); + return -EINVAL; + } + + if ( probe_sgalaxy(&cfg) == 0 ) + return -ENODEV; + + return 0; +} + +static void __exit cleanup_sgalaxy(void) +{ + unload_sgalaxy(&cfg); +} + +module_init(init_sgalaxy); +module_exit(cleanup_sgalaxy); + +#ifndef MODULE +static int __init setup_sgalaxy(char *str) +{ + /* io, irq, dma, dma2, sgbase */ + int ints[6]; + + str = get_options(str, ARRAY_SIZE(ints), ints); + io = ints[1]; + irq = ints[2]; + dma = ints[3]; + dma2 = ints[4]; + sgbase = ints[5]; + + return 1; +} + +__setup("sgalaxy=", setup_sgalaxy); +#endif +MODULE_LICENSE("GPL"); diff --git a/sound/oss/sh_dac_audio.c b/sound/oss/sh_dac_audio.c new file mode 100644 index 000000000000..c09cdeedc191 --- /dev/null +++ b/sound/oss/sh_dac_audio.c @@ -0,0 +1,325 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef MACH_HP600 +#include +#include +#endif + +#define MODNAME "sh_dac_audio" + +#define TMU_TOCR_INIT 0x00 + +#define TMU1_TCR_INIT 0x0020 /* Clock/4, rising edge; interrupt on */ +#define TMU1_TSTR_INIT 0x02 /* Bit to turn on TMU1 */ + +#define TMU_TSTR 0xfffffe92 +#define TMU1_TCOR 0xfffffea0 +#define TMU1_TCNT 0xfffffea4 +#define TMU1_TCR 0xfffffea8 + +#define BUFFER_SIZE 48000 + +static int rate; +static int empty; +static char *data_buffer, *buffer_begin, *buffer_end; +static int in_use, device_major; + +static void dac_audio_start_timer(void) +{ + u8 tstr; + + tstr = ctrl_inb(TMU_TSTR); + tstr |= TMU1_TSTR_INIT; + ctrl_outb(tstr, TMU_TSTR); +} + +static void dac_audio_stop_timer(void) +{ + u8 tstr; + + tstr = ctrl_inb(TMU_TSTR); + tstr &= ~TMU1_TSTR_INIT; + ctrl_outb(tstr, TMU_TSTR); +} + +static void dac_audio_reset(void) +{ + dac_audio_stop_timer(); + buffer_begin = buffer_end = data_buffer; + empty = 1; +} + +static void dac_audio_sync(void) +{ + while (!empty) + schedule(); +} + +static void dac_audio_start(void) +{ +#ifdef MACH_HP600 + u16 v; + v = inw(HD64461_GPADR); + v &= ~HD64461_GPADR_SPEAKER; + outw(v, HD64461_GPADR); +#endif + sh_dac_enable(CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL); + ctrl_outw(TMU1_TCR_INIT, TMU1_TCR); +} +static void dac_audio_stop(void) +{ +#ifdef MACH_HP600 + u16 v; +#endif + dac_audio_stop_timer(); +#ifdef MACH_HP600 + v = inw(HD64461_GPADR); + v |= HD64461_GPADR_SPEAKER; + outw(v, HD64461_GPADR); +#endif + sh_dac_disable(CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL); +} + +static void dac_audio_set_rate(void) +{ + unsigned long interval; + + interval = (current_cpu_data.module_clock / 4) / rate; + ctrl_outl(interval, TMU1_TCOR); + ctrl_outl(interval, TMU1_TCNT); +} + +static int dac_audio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int val; + + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, (int *)arg); + + case SNDCTL_DSP_SYNC: + dac_audio_sync(); + return 0; + + case SNDCTL_DSP_RESET: + dac_audio_reset(); + return 0; + + case SNDCTL_DSP_GETFMTS: + return put_user(AFMT_U8, (int *)arg); + + case SNDCTL_DSP_SETFMT: + return put_user(AFMT_U8, (int *)arg); + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETCAPS: + return 0; + + case SOUND_PCM_WRITE_RATE: + val = *(int *)arg; + if (val > 0) { + rate = val; + dac_audio_set_rate(); + } + return put_user(rate, (int *)arg); + + case SNDCTL_DSP_STEREO: + return put_user(0, (int *)arg); + + case SOUND_PCM_WRITE_CHANNELS: + return put_user(1, (int *)arg); + + case SNDCTL_DSP_SETDUPLEX: + return -EINVAL; + + case SNDCTL_DSP_PROFILE: + return -EINVAL; + + case SNDCTL_DSP_GETBLKSIZE: + return put_user(BUFFER_SIZE, (int *)arg); + + case SNDCTL_DSP_SETFRAGMENT: + return 0; + + default: + printk(KERN_ERR "sh_dac_audio: unimplemented ioctl=0x%x\n", + cmd); + return -EINVAL; + } + return -EINVAL; +} + +static ssize_t dac_audio_write(struct file *file, const char *buf, size_t count, + loff_t * ppos) +{ + int free; + int nbytes; + + if (count < 0) + return -EINVAL; + + if (!count) { + dac_audio_sync(); + return 0; + } + + free = buffer_begin - buffer_end; + + if (free < 0) + free += BUFFER_SIZE; + if ((free == 0) && (empty)) + free = BUFFER_SIZE; + if (count > free) + count = free; + if (buffer_begin > buffer_end) { + if (copy_from_user((void *)buffer_end, buf, count)) + return -EFAULT; + + buffer_end += count; + } else { + nbytes = data_buffer + BUFFER_SIZE - buffer_end; + if (nbytes > count) { + if (copy_from_user((void *)buffer_end, buf, count)) + return -EFAULT; + buffer_end += count; + } else { + if (copy_from_user((void *)buffer_end, buf, nbytes)) + return -EFAULT; + if (copy_from_user + ((void *)data_buffer, buf + nbytes, count - nbytes)) + return -EFAULT; + buffer_end = data_buffer + count - nbytes; + } + } + + if (empty) { + empty = 0; + dac_audio_start_timer(); + } + + return count; +} + +static ssize_t dac_audio_read(struct file *file, char *buf, size_t count, + loff_t * ppos) +{ + return -EINVAL; +} + +static int dac_audio_open(struct inode *inode, struct file *file) +{ + if (file->f_mode & FMODE_READ) + return -ENODEV; + if (in_use) + return -EBUSY; + + in_use = 1; + + dac_audio_start(); + + return 0; +} + +static int dac_audio_release(struct inode *inode, struct file *file) +{ + dac_audio_sync(); + dac_audio_stop(); + in_use = 0; + + return 0; +} + +struct file_operations dac_audio_fops = { + .read = dac_audio_read, + .write = dac_audio_write, + .ioctl = dac_audio_ioctl, + .open = dac_audio_open, + .release = dac_audio_release, +}; + +static irqreturn_t timer1_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + unsigned long timer_status; + + timer_status = ctrl_inw(TMU1_TCR); + timer_status &= ~0x100; + ctrl_outw(timer_status, TMU1_TCR); + + if (!empty) { + sh_dac_output(*buffer_begin, CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL); + buffer_begin++; + + if (buffer_begin == data_buffer + BUFFER_SIZE) + buffer_begin = data_buffer; + if (buffer_begin == buffer_end) { + empty = 1; + dac_audio_stop_timer(); + } + } + return IRQ_HANDLED; +} + +static int __init dac_audio_init(void) +{ + int retval; + + if ((device_major = register_sound_dsp(&dac_audio_fops, -1)) < 0) { + printk(KERN_ERR "Cannot register dsp device"); + return device_major; + } + + in_use = 0; + + data_buffer = (char *)kmalloc(BUFFER_SIZE, GFP_KERNEL); + if (data_buffer == NULL) + return -ENOMEM; + + dac_audio_reset(); + rate = 8000; + dac_audio_set_rate(); + + retval = + request_irq(TIMER1_IRQ, timer1_interrupt, SA_INTERRUPT, MODNAME, 0); + if (retval < 0) { + printk(KERN_ERR "sh_dac_audio: IRQ %d request failed\n", + TIMER1_IRQ); + return retval; + } + + return 0; +} + +static void __exit dac_audio_exit(void) +{ + free_irq(TIMER1_IRQ, 0); + + unregister_sound_dsp(device_major); + kfree((void *)data_buffer); +} + +module_init(dac_audio_init); +module_exit(dac_audio_exit); + +MODULE_AUTHOR("Andriy Skulysh, askulysh@image.kiev.ua"); +MODULE_DESCRIPTION("SH DAC sound driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/oss/skeleton.c b/sound/oss/skeleton.c new file mode 100644 index 000000000000..8fea783dd0cb --- /dev/null +++ b/sound/oss/skeleton.c @@ -0,0 +1,219 @@ +/* + * PCI sound skeleton example + * + * (c) 1998 Red Hat Software + * + * This software may be used and distributed according to the + * terms of the GNU General Public License, incorporated herein by + * reference. + * + * This example is designed to be built in the linux/drivers/sound + * directory as part of a kernel build. The example is modular only + * drop me a note once you have a working modular driver and want + * to integrate it with the main code. + * -- Alan + * + * This is a first draft. Please report any errors, corrections or + * improvements to me. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "sound_config.h" + +/* + * Define our PCI vendor ID here + */ + +#ifndef PCI_VENDOR_MYIDENT +#define PCI_VENDOR_MYIDENT 0x125D + +/* + * PCI identity for the card. + */ + +#define PCI_DEVICE_ID_MYIDENT_MYCARD1 0x1969 +#endif + +#define CARD_NAME "ExampleWave 3D Pro Ultra ThingyWotsit" + +#define MAX_CARDS 8 + +/* + * Each address_info object holds the information about one of + * our card resources. In this case the MSS emulation of our + * ficticious card. Its used to manage and attach things. + */ + +static struct address_info mss_data[MAX_CARDS]; +static int cards; + +/* + * Install the actual card. This is an example + */ + +static int mycard_install(struct pci_dev *pcidev) +{ + int iobase; + int mssbase; + int mpubase; + u8 x; + u16 w; + u32 v; + int i; + int dma; + + /* + * Our imaginary code has its I/O on PCI address 0, a + * MSS on PCI address 1 and an MPU on address 2 + * + * For the example we will only initialise the MSS + */ + + iobase = pci_resource_start(pcidev, 0); + mssbase = pci_resource_start(pcidev, 1); + mpubase = pci_resource_start(pcidev, 2); + + /* + * Reset the board + */ + + /* + * Wait for completion. udelay() waits in microseconds + */ + + udelay(100); + + /* + * Ok card ready. Begin setup proper. You might for example + * load the firmware here + */ + + dma = card_specific_magic(ioaddr); + + /* + * Turn on legacy mode (example), There are also byte and + * dword (32bit) PCI configuration function calls + */ + + pci_read_config_word(pcidev, 0x40, &w); + w&=~(1<<15); /* legacy decode on */ + w|=(1<<14); /* Reserved write as 1 in this case */ + w|=(1<<3)|(1<<1)|(1<<0); /* SB on , FM on, MPU on */ + pci_write_config_word(pcidev, 0x40, w); + + /* + * Let the user know we found his toy. + */ + + printk(KERN_INFO "Programmed "CARD_NAME" at 0x%X to legacy mode.\n", + iobase); + + /* + * Now set it up the description of the card + */ + + mss_data[cards].io_base = mssbase; + mss_data[cards].irq = pcidev->irq; + mss_data[cards].dma = dma; + + /* + * Check there is an MSS present + */ + + if(ad1848_detect(mssbase, NULL, mss_data[cards].osp)==0) + return 0; + + /* + * Initialize it + */ + + mss_data[cards].slots[3] = ad1848_init("MyCard MSS 16bit", + mssbase, + mss_data[cards].irq, + mss_data[cards].dma, + mss_data[cards].dma, + 0, + 0, + THIS_MODULE); + + cards++; + return 1; +} + + +/* + * This loop walks the PCI configuration database and finds where + * the sound cards are. + */ + +int init_mycard(void) +{ + struct pci_dev *pcidev=NULL; + int count=0; + + while((pcidev = pci_find_device(PCI_VENDOR_MYIDENT, PCI_DEVICE_ID_MYIDENT_MYCARD1, pcidev))!=NULL) + { + if (pci_enable_device(pcidev)) + continue; + count+=mycard_install(pcidev); + if(count) + return 0; + if(count==MAX_CARDS) + break; + } + + if(count==0) + return -ENODEV; + return 0; +} + +/* + * This function is called when the user or kernel loads the + * module into memory. + */ + + +int init_module(void) +{ + if(init_mycard()<0) + { + printk(KERN_ERR "No "CARD_NAME" cards found.\n"); + return -ENODEV; + } + + return 0; +} + +/* + * This is called when it is removed. It will only be removed + * when its use count is 0. + */ + +void cleanup_module(void) +{ + for(i=0;i< cards; i++) + { + /* + * Free attached resources + */ + + ad1848_unload(mss_data[i].io_base, + mss_data[i].irq, + mss_data[i].dma, + mss_data[i].dma, + 0); + /* + * And disconnect the device from the kernel + */ + sound_unload_audiodevice(mss_data[i].slots[3]); + } +} + diff --git a/sound/oss/sonicvibes.c b/sound/oss/sonicvibes.c new file mode 100644 index 000000000000..e1d69611a257 --- /dev/null +++ b/sound/oss/sonicvibes.c @@ -0,0 +1,2792 @@ +/*****************************************************************************/ + +/* + * sonicvibes.c -- S3 Sonic Vibes audio driver. + * + * Copyright (C) 1998-2001, 2003 Thomas Sailer (t.sailer@alumni.ethz.ch) + * + * 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. + * + * Special thanks to David C. Niemi + * + * + * Module command line parameters: + * none so far + * + * + * 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 + * + * The card has both an FM and a Wavetable synth, but I have to figure + * out first how to drive them... + * + * Revision history + * 06.05.1998 0.1 Initial release + * 10.05.1998 0.2 Fixed many bugs, esp. ADC rate calculation + * First stab at a simple midi interface (no bells&whistles) + * 13.05.1998 0.3 Fix stupid cut&paste error: set_adc_rate was called instead of + * set_dac_rate in the FMODE_WRITE case in sv_open + * Fix hwptr out of bounds (now mpg123 works) + * 14.05.1998 0.4 Don't allow excessive interrupt rates + * 08.06.1998 0.5 First release using Alan Cox' soundcore instead of miscdevice + * 03.08.1998 0.6 Do not include modversions.h + * Now mixer behaviour can basically be selected between + * "OSS documented" and "OSS actual" behaviour + * 31.08.1998 0.7 Fix realplayer problems - dac.count issues + * 10.12.1998 0.8 Fix drain_dac trying to wait on not yet initialized DMA + * 16.12.1998 0.9 Fix a few f_file & FMODE_ bugs + * 06.01.1999 0.10 remove the silly SA_INTERRUPT flag. + * hopefully killed the egcs section type conflict + * 12.03.1999 0.11 cinfo.blocks should be reset after GETxPTR ioctl. + * reported by Johan Maes + * 22.03.1999 0.12 return EAGAIN instead of EBUSY when O_NONBLOCK + * read/write cannot be executed + * 05.04.1999 0.13 added code to sv_read and sv_write which should detect + * lockups of the sound chip and revive it. This is basically + * an ugly hack, but at least applications using this driver + * won't hang forever. I don't know why these lockups happen, + * it might well be the motherboard chipset (an early 486 PCI + * board with ALI chipset), since every busmastering 100MB + * ethernet card I've tried (Realtek 8139 and Macronix tulip clone) + * exhibit similar behaviour (they work for a couple of packets + * and then lock up and can be revived by ifconfig down/up). + * 07.04.1999 0.14 implemented the following ioctl's: SOUND_PCM_READ_RATE, + * SOUND_PCM_READ_CHANNELS, SOUND_PCM_READ_BITS; + * Alpha fixes reported by Peter Jones + * Note: dmaio hack might still be wrong on archs other than i386 + * 15.06.1999 0.15 Fix bad allocation bug. + * Thanks to Deti Fliegl + * 28.06.1999 0.16 Add pci_set_master + * 03.08.1999 0.17 adapt to Linus' new __setup/__initcall + * added kernel command line options "sonicvibes=reverb" and "sonicvibesdmaio=dmaioaddr" + * 12.08.1999 0.18 module_init/__setup fixes + * 24.08.1999 0.19 get rid of the dmaio kludge, replace with allocate_resource + * 31.08.1999 0.20 add spin_lock_init + * use new resource allocation to allocate DDMA IO space + * replaced current->state = x with set_current_state(x) + * 03.09.1999 0.21 change read semantics for MIDI to match + * OSS more closely; remove possible wakeup race + * 28.10.1999 0.22 More waitqueue races fixed + * 01.12.1999 0.23 New argument to allocate_resource + * 07.12.1999 0.24 More allocate_resource semantics change + * 08.01.2000 0.25 Prevent some ioctl's from returning bad count values on underrun/overrun; + * Tim Janik's BSE (Bedevilled Sound Engine) found this + * use Martin Mares' pci_assign_resource + * 07.02.2000 0.26 Use pci_alloc_consistent and pci_register_driver + * 21.11.2000 0.27 Initialize dma buffers in poll, otherwise poll may return a bogus mask + * 12.12.2000 0.28 More dma buffer initializations, patch from + * Tjeerd Mulder + * 31.01.2001 0.29 Register/Unregister gameport + * Fix SETTRIGGER non OSS API conformity + * 18.05.2001 0.30 PCI probing and error values cleaned up by Marcus + * Meissner + * 03.01.2003 0.31 open_mode fixes from Georg Acher + * + */ + +/*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dm.h" + + +/* --------------------------------------------------------------------- */ + +#undef OSS_DOCUMENTED_MIXER_SEMANTICS + +/* --------------------------------------------------------------------- */ + +#ifndef PCI_VENDOR_ID_S3 +#define PCI_VENDOR_ID_S3 0x5333 +#endif +#ifndef PCI_DEVICE_ID_S3_SONICVIBES +#define PCI_DEVICE_ID_S3_SONICVIBES 0xca00 +#endif + +#define SV_MAGIC ((PCI_VENDOR_ID_S3<<16)|PCI_DEVICE_ID_S3_SONICVIBES) + +#define SV_EXTENT_SB 0x10 +#define SV_EXTENT_ENH 0x10 +#define SV_EXTENT_SYNTH 0x4 +#define SV_EXTENT_MIDI 0x4 +#define SV_EXTENT_GAME 0x8 +#define SV_EXTENT_DMA 0x10 + +/* + * we are not a bridge and thus use a resource for DDMA that is used for bridges but + * left empty for normal devices + */ +#define RESOURCE_SB 0 +#define RESOURCE_ENH 1 +#define RESOURCE_SYNTH 2 +#define RESOURCE_MIDI 3 +#define RESOURCE_GAME 4 +#define RESOURCE_DDMA 7 + +#define SV_MIDI_DATA 0 +#define SV_MIDI_COMMAND 1 +#define SV_MIDI_STATUS 1 + +#define SV_DMA_ADDR0 0 +#define SV_DMA_ADDR1 1 +#define SV_DMA_ADDR2 2 +#define SV_DMA_ADDR3 3 +#define SV_DMA_COUNT0 4 +#define SV_DMA_COUNT1 5 +#define SV_DMA_COUNT2 6 +#define SV_DMA_MODE 0xb +#define SV_DMA_RESET 0xd +#define SV_DMA_MASK 0xf + +/* + * DONT reset the DMA controllers unless you understand + * the reset semantics. Assuming reset semantics as in + * the 8237 does not work. + */ + +#define DMA_MODE_AUTOINIT 0x10 +#define DMA_MODE_READ 0x44 /* I/O to memory, no autoinit, increment, single mode */ +#define DMA_MODE_WRITE 0x48 /* memory to I/O, no autoinit, increment, single mode */ + +#define SV_CODEC_CONTROL 0 +#define SV_CODEC_INTMASK 1 +#define SV_CODEC_STATUS 2 +#define SV_CODEC_IADDR 4 +#define SV_CODEC_IDATA 5 + +#define SV_CCTRL_RESET 0x80 +#define SV_CCTRL_INTADRIVE 0x20 +#define SV_CCTRL_WAVETABLE 0x08 +#define SV_CCTRL_REVERB 0x04 +#define SV_CCTRL_ENHANCED 0x01 + +#define SV_CINTMASK_DMAA 0x01 +#define SV_CINTMASK_DMAC 0x04 +#define SV_CINTMASK_SPECIAL 0x08 +#define SV_CINTMASK_UPDOWN 0x40 +#define SV_CINTMASK_MIDI 0x80 + +#define SV_CSTAT_DMAA 0x01 +#define SV_CSTAT_DMAC 0x04 +#define SV_CSTAT_SPECIAL 0x08 +#define SV_CSTAT_UPDOWN 0x40 +#define SV_CSTAT_MIDI 0x80 + +#define SV_CIADDR_TRD 0x80 +#define SV_CIADDR_MCE 0x40 + +/* codec indirect registers */ +#define SV_CIMIX_ADCINL 0x00 +#define SV_CIMIX_ADCINR 0x01 +#define SV_CIMIX_AUX1INL 0x02 +#define SV_CIMIX_AUX1INR 0x03 +#define SV_CIMIX_CDINL 0x04 +#define SV_CIMIX_CDINR 0x05 +#define SV_CIMIX_LINEINL 0x06 +#define SV_CIMIX_LINEINR 0x07 +#define SV_CIMIX_MICIN 0x08 +#define SV_CIMIX_SYNTHINL 0x0A +#define SV_CIMIX_SYNTHINR 0x0B +#define SV_CIMIX_AUX2INL 0x0C +#define SV_CIMIX_AUX2INR 0x0D +#define SV_CIMIX_ANALOGINL 0x0E +#define SV_CIMIX_ANALOGINR 0x0F +#define SV_CIMIX_PCMINL 0x10 +#define SV_CIMIX_PCMINR 0x11 + +#define SV_CIGAMECONTROL 0x09 +#define SV_CIDATAFMT 0x12 +#define SV_CIENABLE 0x13 +#define SV_CIUPDOWN 0x14 +#define SV_CIREVISION 0x15 +#define SV_CIADCOUTPUT 0x16 +#define SV_CIDMAABASECOUNT1 0x18 +#define SV_CIDMAABASECOUNT0 0x19 +#define SV_CIDMACBASECOUNT1 0x1c +#define SV_CIDMACBASECOUNT0 0x1d +#define SV_CIPCMSR0 0x1e +#define SV_CIPCMSR1 0x1f +#define SV_CISYNTHSR0 0x20 +#define SV_CISYNTHSR1 0x21 +#define SV_CIADCCLKSOURCE 0x22 +#define SV_CIADCALTSR 0x23 +#define SV_CIADCPLLM 0x24 +#define SV_CIADCPLLN 0x25 +#define SV_CISYNTHPLLM 0x26 +#define SV_CISYNTHPLLN 0x27 +#define SV_CIUARTCONTROL 0x2a +#define SV_CIDRIVECONTROL 0x2b +#define SV_CISRSSPACE 0x2c +#define SV_CISRSCENTER 0x2d +#define SV_CIWAVETABLESRC 0x2e +#define SV_CIANALOGPWRDOWN 0x30 +#define SV_CIDIGITALPWRDOWN 0x31 + + +#define SV_CIMIX_ADCSRC_CD 0x20 +#define SV_CIMIX_ADCSRC_DAC 0x40 +#define SV_CIMIX_ADCSRC_AUX2 0x60 +#define SV_CIMIX_ADCSRC_LINE 0x80 +#define SV_CIMIX_ADCSRC_AUX1 0xa0 +#define SV_CIMIX_ADCSRC_MIC 0xc0 +#define SV_CIMIX_ADCSRC_MIXOUT 0xe0 +#define SV_CIMIX_ADCSRC_MASK 0xe0 + +#define SV_CFMT_STEREO 0x01 +#define SV_CFMT_16BIT 0x02 +#define SV_CFMT_MASK 0x03 +#define SV_CFMT_ASHIFT 0 +#define SV_CFMT_CSHIFT 4 + +static const unsigned sample_size[] = { 1, 2, 2, 4 }; +static const unsigned sample_shift[] = { 0, 1, 1, 2 }; + +#define SV_CENABLE_PPE 0x4 +#define SV_CENABLE_RE 0x2 +#define SV_CENABLE_PE 0x1 + + +/* MIDI buffer sizes */ + +#define MIDIINBUF 256 +#define MIDIOUTBUF 256 + +#define FMODE_MIDI_SHIFT 2 +#define FMODE_MIDI_READ (FMODE_READ << FMODE_MIDI_SHIFT) +#define FMODE_MIDI_WRITE (FMODE_WRITE << FMODE_MIDI_SHIFT) + +#define FMODE_DMFM 0x10 + +/* --------------------------------------------------------------------- */ + +struct sv_state { + /* magic */ + unsigned int magic; + + /* list of sonicvibes devices */ + struct list_head devs; + + /* the corresponding pci_dev structure */ + struct pci_dev *dev; + + /* soundcore stuff */ + int dev_audio; + int dev_mixer; + int dev_midi; + int dev_dmfm; + + /* hardware resources */ + unsigned long iosb, ioenh, iosynth, iomidi; /* long for SPARC */ + unsigned int iodmaa, iodmac, irq; + + /* mixer stuff */ + struct { + unsigned int modcnt; +#ifndef OSS_DOCUMENTED_MIXER_SEMANTICS + unsigned short vol[13]; +#endif /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + } mix; + + /* wave stuff */ + unsigned int rateadc, ratedac; + unsigned char fmt, enable; + + spinlock_t lock; + struct semaphore open_sem; + mode_t open_mode; + wait_queue_head_t open_wait; + + struct dmabuf { + void *rawbuf; + dma_addr_t dmaaddr; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + unsigned hwptr, swptr; + unsigned total_bytes; + int count; + unsigned error; /* over/underrun */ + wait_queue_head_t wait; + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dmasize; + unsigned fragsamples; + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned endcleared:1; + unsigned enabled:1; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + } dma_dac, dma_adc; + + /* midi stuff */ + struct { + unsigned ird, iwr, icnt; + unsigned ord, owr, ocnt; + wait_queue_head_t iwait; + wait_queue_head_t owait; + struct timer_list timer; + unsigned char ibuf[MIDIINBUF]; + unsigned char obuf[MIDIOUTBUF]; + } midi; + + struct gameport *gameport; +}; + +/* --------------------------------------------------------------------- */ + +static LIST_HEAD(devs); +static unsigned long wavetable_mem; + +/* --------------------------------------------------------------------- */ + +static inline unsigned ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +/* + * hweightN: returns the hamming weight (i.e. the number + * of bits set) of a N-bit word + */ + +#ifdef hweight32 +#undef hweight32 +#endif + +static inline unsigned int hweight32(unsigned int w) +{ + unsigned int res = (w & 0x55555555) + ((w >> 1) & 0x55555555); + res = (res & 0x33333333) + ((res >> 2) & 0x33333333); + res = (res & 0x0F0F0F0F) + ((res >> 4) & 0x0F0F0F0F); + res = (res & 0x00FF00FF) + ((res >> 8) & 0x00FF00FF); + return (res & 0x0000FFFF) + ((res >> 16) & 0x0000FFFF); +} + +/* --------------------------------------------------------------------- */ + +/* + * Why use byte IO? Nobody knows, but S3 does it also in their Windows driver. + */ + +#undef DMABYTEIO + +static void set_dmaa(struct sv_state *s, unsigned int addr, unsigned int count) +{ +#ifdef DMABYTEIO + unsigned io = s->iodmaa, u; + + count--; + for (u = 4; u > 0; u--, addr >>= 8, io++) + outb(addr & 0xff, io); + for (u = 3; u > 0; u--, count >>= 8, io++) + outb(count & 0xff, io); +#else /* DMABYTEIO */ + count--; + outl(addr, s->iodmaa + SV_DMA_ADDR0); + outl(count, s->iodmaa + SV_DMA_COUNT0); +#endif /* DMABYTEIO */ + outb(0x18, s->iodmaa + SV_DMA_MODE); +} + +static void set_dmac(struct sv_state *s, unsigned int addr, unsigned int count) +{ +#ifdef DMABYTEIO + unsigned io = s->iodmac, u; + + count >>= 1; + count--; + for (u = 4; u > 0; u--, addr >>= 8, io++) + outb(addr & 0xff, io); + for (u = 3; u > 0; u--, count >>= 8, io++) + outb(count & 0xff, io); +#else /* DMABYTEIO */ + count >>= 1; + count--; + outl(addr, s->iodmac + SV_DMA_ADDR0); + outl(count, s->iodmac + SV_DMA_COUNT0); +#endif /* DMABYTEIO */ + outb(0x14, s->iodmac + SV_DMA_MODE); +} + +static inline unsigned get_dmaa(struct sv_state *s) +{ +#ifdef DMABYTEIO + unsigned io = s->iodmaa+6, v = 0, u; + + for (u = 3; u > 0; u--, io--) { + v <<= 8; + v |= inb(io); + } + return v + 1; +#else /* DMABYTEIO */ + return (inl(s->iodmaa + SV_DMA_COUNT0) & 0xffffff) + 1; +#endif /* DMABYTEIO */ +} + +static inline unsigned get_dmac(struct sv_state *s) +{ +#ifdef DMABYTEIO + unsigned io = s->iodmac+6, v = 0, u; + + for (u = 3; u > 0; u--, io--) { + v <<= 8; + v |= inb(io); + } + return (v + 1) << 1; +#else /* DMABYTEIO */ + return ((inl(s->iodmac + SV_DMA_COUNT0) & 0xffffff) + 1) << 1; +#endif /* DMABYTEIO */ +} + +static void wrindir(struct sv_state *s, unsigned char idx, unsigned char data) +{ + outb(idx & 0x3f, s->ioenh + SV_CODEC_IADDR); + udelay(10); + outb(data, s->ioenh + SV_CODEC_IDATA); + udelay(10); +} + +static unsigned char rdindir(struct sv_state *s, unsigned char idx) +{ + unsigned char v; + + outb(idx & 0x3f, s->ioenh + SV_CODEC_IADDR); + udelay(10); + v = inb(s->ioenh + SV_CODEC_IDATA); + udelay(10); + return v; +} + +static void set_fmt(struct sv_state *s, unsigned char mask, unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + outb(SV_CIDATAFMT | SV_CIADDR_MCE, s->ioenh + SV_CODEC_IADDR); + if (mask) { + s->fmt = inb(s->ioenh + SV_CODEC_IDATA); + udelay(10); + } + s->fmt = (s->fmt & mask) | data; + outb(s->fmt, s->ioenh + SV_CODEC_IDATA); + udelay(10); + outb(0, s->ioenh + SV_CODEC_IADDR); + spin_unlock_irqrestore(&s->lock, flags); + udelay(10); +} + +static void frobindir(struct sv_state *s, unsigned char idx, unsigned char mask, unsigned char data) +{ + outb(idx & 0x3f, s->ioenh + SV_CODEC_IADDR); + udelay(10); + outb((inb(s->ioenh + SV_CODEC_IDATA) & mask) ^ data, s->ioenh + SV_CODEC_IDATA); + udelay(10); +} + +#define REFFREQUENCY 24576000 +#define ADCMULT 512 +#define FULLRATE 48000 + +static unsigned setpll(struct sv_state *s, unsigned char reg, unsigned rate) +{ + unsigned long flags; + unsigned char r, m=0, n=0; + unsigned xm, xn, xr, xd, metric = ~0U; + /* the warnings about m and n used uninitialized are bogus and may safely be ignored */ + + if (rate < 625000/ADCMULT) + rate = 625000/ADCMULT; + if (rate > 150000000/ADCMULT) + rate = 150000000/ADCMULT; + /* slight violation of specs, needed for continuous sampling rates */ + for (r = 0; rate < 75000000/ADCMULT; r += 0x20, rate <<= 1); + for (xn = 3; xn < 35; xn++) + for (xm = 3; xm < 130; xm++) { + xr = REFFREQUENCY/ADCMULT * xm / xn; + xd = abs((signed)(xr - rate)); + if (xd < metric) { + metric = xd; + m = xm - 2; + n = xn - 2; + } + } + reg &= 0x3f; + spin_lock_irqsave(&s->lock, flags); + outb(reg, s->ioenh + SV_CODEC_IADDR); + udelay(10); + outb(m, s->ioenh + SV_CODEC_IDATA); + udelay(10); + outb(reg+1, s->ioenh + SV_CODEC_IADDR); + udelay(10); + outb(r | n, s->ioenh + SV_CODEC_IDATA); + spin_unlock_irqrestore(&s->lock, flags); + udelay(10); + return (REFFREQUENCY/ADCMULT * (m + 2) / (n + 2)) >> ((r >> 5) & 7); +} + +#if 0 + +static unsigned getpll(struct sv_state *s, unsigned char reg) +{ + unsigned long flags; + unsigned char m, n; + + reg &= 0x3f; + spin_lock_irqsave(&s->lock, flags); + outb(reg, s->ioenh + SV_CODEC_IADDR); + udelay(10); + m = inb(s->ioenh + SV_CODEC_IDATA); + udelay(10); + outb(reg+1, s->ioenh + SV_CODEC_IADDR); + udelay(10); + n = inb(s->ioenh + SV_CODEC_IDATA); + spin_unlock_irqrestore(&s->lock, flags); + udelay(10); + return (REFFREQUENCY/ADCMULT * (m + 2) / ((n & 0x1f) + 2)) >> ((n >> 5) & 7); +} + +#endif + +static void set_dac_rate(struct sv_state *s, unsigned rate) +{ + unsigned div; + unsigned long flags; + + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; + div = (rate * 65536 + FULLRATE/2) / FULLRATE; + if (div > 65535) + div = 65535; + spin_lock_irqsave(&s->lock, flags); + wrindir(s, SV_CIPCMSR1, div >> 8); + wrindir(s, SV_CIPCMSR0, div); + spin_unlock_irqrestore(&s->lock, flags); + s->ratedac = (div * FULLRATE + 32768) / 65536; +} + +static void set_adc_rate(struct sv_state *s, unsigned rate) +{ + unsigned long flags; + unsigned rate1, rate2, div; + + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; + rate1 = setpll(s, SV_CIADCPLLM, rate); + div = (48000 + rate/2) / rate; + if (div > 8) + div = 8; + rate2 = (48000 + div/2) / div; + spin_lock_irqsave(&s->lock, flags); + wrindir(s, SV_CIADCALTSR, (div-1) << 4); + if (abs((signed)(rate-rate2)) <= abs((signed)(rate-rate1))) { + wrindir(s, SV_CIADCCLKSOURCE, 0x10); + s->rateadc = rate2; + } else { + wrindir(s, SV_CIADCCLKSOURCE, 0x00); + s->rateadc = rate1; + } + spin_unlock_irqrestore(&s->lock, flags); +} + +/* --------------------------------------------------------------------- */ + +static inline void stop_adc(struct sv_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->enable &= ~SV_CENABLE_RE; + wrindir(s, SV_CIENABLE, s->enable); + spin_unlock_irqrestore(&s->lock, flags); +} + +static inline void stop_dac(struct sv_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->enable &= ~(SV_CENABLE_PPE | SV_CENABLE_PE); + wrindir(s, SV_CIENABLE, s->enable); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_dac(struct sv_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + if ((s->dma_dac.mapped || s->dma_dac.count > 0) && s->dma_dac.ready) { + s->enable = (s->enable & ~SV_CENABLE_PPE) | SV_CENABLE_PE; + wrindir(s, SV_CIENABLE, s->enable); + } + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_adc(struct sv_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + if ((s->dma_adc.mapped || s->dma_adc.count < (signed)(s->dma_adc.dmasize - 2*s->dma_adc.fragsize)) + && s->dma_adc.ready) { + s->enable |= SV_CENABLE_RE; + wrindir(s, SV_CIENABLE, s->enable); + } + spin_unlock_irqrestore(&s->lock, flags); +} + +/* --------------------------------------------------------------------- */ + +#define DMABUF_DEFAULTORDER (17-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +static void dealloc_dmabuf(struct sv_state *s, struct dmabuf *db) +{ + struct page *page, *pend; + + if (db->rawbuf) { + /* undo marking the pages as reserved */ + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + ClearPageReserved(page); + pci_free_consistent(s->dev, PAGE_SIZE << db->buforder, db->rawbuf, db->dmaaddr); + } + db->rawbuf = NULL; + db->mapped = db->ready = 0; +} + + +/* DMAA is used for playback, DMAC is used for recording */ + +static int prog_dmabuf(struct sv_state *s, unsigned rec) +{ + struct dmabuf *db = rec ? &s->dma_adc : &s->dma_dac; + unsigned rate = rec ? s->rateadc : s->ratedac; + int order; + unsigned bytepersec; + unsigned bufs; + struct page *page, *pend; + unsigned char fmt; + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + fmt = s->fmt; + if (rec) { + s->enable &= ~SV_CENABLE_RE; + fmt >>= SV_CFMT_CSHIFT; + } else { + s->enable &= ~SV_CENABLE_PE; + fmt >>= SV_CFMT_ASHIFT; + } + wrindir(s, SV_CIENABLE, s->enable); + spin_unlock_irqrestore(&s->lock, flags); + fmt &= SV_CFMT_MASK; + db->hwptr = db->swptr = db->total_bytes = db->count = db->error = db->endcleared = 0; + if (!db->rawbuf) { + db->ready = db->mapped = 0; + for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--) + if ((db->rawbuf = pci_alloc_consistent(s->dev, PAGE_SIZE << order, &db->dmaaddr))) + break; + if (!db->rawbuf) + return -ENOMEM; + db->buforder = order; + if ((virt_to_bus(db->rawbuf) ^ (virt_to_bus(db->rawbuf) + (PAGE_SIZE << db->buforder) - 1)) & ~0xffff) + printk(KERN_DEBUG "sv: DMA buffer crosses 64k boundary: busaddr 0x%lx size %ld\n", + virt_to_bus(db->rawbuf), PAGE_SIZE << db->buforder); + if ((virt_to_bus(db->rawbuf) + (PAGE_SIZE << db->buforder) - 1) & ~0xffffff) + printk(KERN_DEBUG "sv: DMA buffer beyond 16MB: busaddr 0x%lx size %ld\n", + virt_to_bus(db->rawbuf), PAGE_SIZE << db->buforder); + /* now mark the pages as reserved; otherwise remap_pfn_range doesn't do what we want */ + pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (page = virt_to_page(db->rawbuf); page <= pend; page++) + SetPageReserved(page); + } + bytepersec = rate << sample_shift[fmt]; + bufs = PAGE_SIZE << db->buforder; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < bytepersec) + db->fragshift = ld2(bytepersec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(bytepersec/100/(db->subdivision ? db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + db->numfrag = bufs >> db->fragshift; + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->numfrag = bufs >> db->fragshift; + } + db->fragsize = 1 << db->fragshift; + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; + db->fragsamples = db->fragsize >> sample_shift[fmt]; + db->dmasize = db->numfrag << db->fragshift; + memset(db->rawbuf, (fmt & SV_CFMT_16BIT) ? 0 : 0x80, db->dmasize); + spin_lock_irqsave(&s->lock, flags); + if (rec) { + set_dmac(s, db->dmaaddr, db->numfrag << db->fragshift); + /* program enhanced mode registers */ + wrindir(s, SV_CIDMACBASECOUNT1, (db->fragsamples-1) >> 8); + wrindir(s, SV_CIDMACBASECOUNT0, db->fragsamples-1); + } else { + set_dmaa(s, db->dmaaddr, db->numfrag << db->fragshift); + /* program enhanced mode registers */ + wrindir(s, SV_CIDMAABASECOUNT1, (db->fragsamples-1) >> 8); + wrindir(s, SV_CIDMAABASECOUNT0, db->fragsamples-1); + } + spin_unlock_irqrestore(&s->lock, flags); + db->enabled = 1; + db->ready = 1; + return 0; +} + +static inline void clear_advance(struct sv_state *s) +{ + unsigned char c = (s->fmt & (SV_CFMT_16BIT << SV_CFMT_ASHIFT)) ? 0 : 0x80; + unsigned char *buf = s->dma_dac.rawbuf; + unsigned bsize = s->dma_dac.dmasize; + unsigned bptr = s->dma_dac.swptr; + unsigned len = s->dma_dac.fragsize; + + if (bptr + len > bsize) { + unsigned x = bsize - bptr; + memset(buf + bptr, c, x); + bptr = 0; + len -= x; + } + memset(buf + bptr, c, len); +} + +/* call with spinlock held! */ +static void sv_update_ptr(struct sv_state *s) +{ + unsigned hwptr; + int diff; + + /* update ADC pointer */ + if (s->dma_adc.ready) { + hwptr = (s->dma_adc.dmasize - get_dmac(s)) % s->dma_adc.dmasize; + diff = (s->dma_adc.dmasize + hwptr - s->dma_adc.hwptr) % s->dma_adc.dmasize; + s->dma_adc.hwptr = hwptr; + s->dma_adc.total_bytes += diff; + s->dma_adc.count += diff; + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + wake_up(&s->dma_adc.wait); + if (!s->dma_adc.mapped) { + if (s->dma_adc.count > (signed)(s->dma_adc.dmasize - ((3 * s->dma_adc.fragsize) >> 1))) { + s->enable &= ~SV_CENABLE_RE; + wrindir(s, SV_CIENABLE, s->enable); + s->dma_adc.error++; + } + } + } + /* update DAC pointer */ + if (s->dma_dac.ready) { + hwptr = (s->dma_dac.dmasize - get_dmaa(s)) % s->dma_dac.dmasize; + diff = (s->dma_dac.dmasize + hwptr - s->dma_dac.hwptr) % s->dma_dac.dmasize; + s->dma_dac.hwptr = hwptr; + s->dma_dac.total_bytes += diff; + if (s->dma_dac.mapped) { + s->dma_dac.count += diff; + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) + wake_up(&s->dma_dac.wait); + } else { + s->dma_dac.count -= diff; + if (s->dma_dac.count <= 0) { + s->enable &= ~SV_CENABLE_PE; + wrindir(s, SV_CIENABLE, s->enable); + s->dma_dac.error++; + } else if (s->dma_dac.count <= (signed)s->dma_dac.fragsize && !s->dma_dac.endcleared) { + clear_advance(s); + s->dma_dac.endcleared = 1; + } + if (s->dma_dac.count + (signed)s->dma_dac.fragsize <= (signed)s->dma_dac.dmasize) + wake_up(&s->dma_dac.wait); + } + } +} + +/* hold spinlock for the following! */ +static void sv_handle_midi(struct sv_state *s) +{ + unsigned char ch; + int wake; + + wake = 0; + while (!(inb(s->iomidi+1) & 0x80)) { + ch = inb(s->iomidi); + if (s->midi.icnt < MIDIINBUF) { + s->midi.ibuf[s->midi.iwr] = ch; + s->midi.iwr = (s->midi.iwr + 1) % MIDIINBUF; + s->midi.icnt++; + } + wake = 1; + } + if (wake) + wake_up(&s->midi.iwait); + wake = 0; + while (!(inb(s->iomidi+1) & 0x40) && s->midi.ocnt > 0) { + outb(s->midi.obuf[s->midi.ord], s->iomidi); + s->midi.ord = (s->midi.ord + 1) % MIDIOUTBUF; + s->midi.ocnt--; + if (s->midi.ocnt < MIDIOUTBUF-16) + wake = 1; + } + if (wake) + wake_up(&s->midi.owait); +} + +static irqreturn_t sv_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct sv_state *s = (struct sv_state *)dev_id; + unsigned int intsrc; + + /* fastpath out, to ease interrupt sharing */ + intsrc = inb(s->ioenh + SV_CODEC_STATUS); + if (!(intsrc & (SV_CSTAT_DMAA | SV_CSTAT_DMAC | SV_CSTAT_MIDI))) + return IRQ_NONE; + spin_lock(&s->lock); + sv_update_ptr(s); + sv_handle_midi(s); + spin_unlock(&s->lock); + return IRQ_HANDLED; +} + +static void sv_midi_timer(unsigned long data) +{ + struct sv_state *s = (struct sv_state *)data; + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + sv_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + s->midi.timer.expires = jiffies+1; + add_timer(&s->midi.timer); +} + +/* --------------------------------------------------------------------- */ + +static const char invalid_magic[] = KERN_CRIT "sv: invalid magic value\n"; + +#define VALIDATE_STATE(s) \ +({ \ + if (!(s) || (s)->magic != SV_MAGIC) { \ + printk(invalid_magic); \ + return -ENXIO; \ + } \ +}) + +/* --------------------------------------------------------------------- */ + +#define MT_4 1 +#define MT_5MUTE 2 +#define MT_4MUTEMONO 3 +#define MT_6MUTE 4 + +static const struct { + unsigned left:5; + unsigned right:5; + unsigned type:3; + unsigned rec:3; +} mixtable[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_RECLEV] = { SV_CIMIX_ADCINL, SV_CIMIX_ADCINR, MT_4, 0 }, + [SOUND_MIXER_LINE1] = { SV_CIMIX_AUX1INL, SV_CIMIX_AUX1INR, MT_5MUTE, 5 }, + [SOUND_MIXER_CD] = { SV_CIMIX_CDINL, SV_CIMIX_CDINR, MT_5MUTE, 1 }, + [SOUND_MIXER_LINE] = { SV_CIMIX_LINEINL, SV_CIMIX_LINEINR, MT_5MUTE, 4 }, + [SOUND_MIXER_MIC] = { SV_CIMIX_MICIN, SV_CIMIX_ADCINL, MT_4MUTEMONO, 6 }, + [SOUND_MIXER_SYNTH] = { SV_CIMIX_SYNTHINL, SV_CIMIX_SYNTHINR, MT_5MUTE, 2 }, + [SOUND_MIXER_LINE2] = { SV_CIMIX_AUX2INL, SV_CIMIX_AUX2INR, MT_5MUTE, 3 }, + [SOUND_MIXER_VOLUME] = { SV_CIMIX_ANALOGINL, SV_CIMIX_ANALOGINR, MT_5MUTE, 7 }, + [SOUND_MIXER_PCM] = { SV_CIMIX_PCMINL, SV_CIMIX_PCMINR, MT_6MUTE, 0 } +}; + +#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS + +static int return_mixval(struct sv_state *s, unsigned i, int *arg) +{ + unsigned long flags; + unsigned char l, r, rl, rr; + + spin_lock_irqsave(&s->lock, flags); + l = rdindir(s, mixtable[i].left); + r = rdindir(s, mixtable[i].right); + spin_unlock_irqrestore(&s->lock, flags); + switch (mixtable[i].type) { + case MT_4: + r &= 0xf; + l &= 0xf; + rl = 10 + 6 * (l & 15); + rr = 10 + 6 * (r & 15); + break; + + case MT_4MUTEMONO: + rl = 55 - 3 * (l & 15); + if (r & 0x10) + rl += 45; + rr = rl; + r = l; + break; + + case MT_5MUTE: + default: + rl = 100 - 3 * (l & 31); + rr = 100 - 3 * (r & 31); + break; + + case MT_6MUTE: + rl = 100 - 3 * (l & 63) / 2; + rr = 100 - 3 * (r & 63) / 2; + break; + } + if (l & 0x80) + rl = 0; + if (r & 0x80) + rr = 0; + return put_user((rr << 8) | rl, arg); +} + +#else /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + +static const unsigned char volidx[SOUND_MIXER_NRDEVICES] = +{ + [SOUND_MIXER_RECLEV] = 1, + [SOUND_MIXER_LINE1] = 2, + [SOUND_MIXER_CD] = 3, + [SOUND_MIXER_LINE] = 4, + [SOUND_MIXER_MIC] = 5, + [SOUND_MIXER_SYNTH] = 6, + [SOUND_MIXER_LINE2] = 7, + [SOUND_MIXER_VOLUME] = 8, + [SOUND_MIXER_PCM] = 9 +}; + +#endif /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + +static unsigned mixer_recmask(struct sv_state *s) +{ + unsigned long flags; + int i, j; + + spin_lock_irqsave(&s->lock, flags); + j = rdindir(s, SV_CIMIX_ADCINL) >> 5; + spin_unlock_irqrestore(&s->lock, flags); + j &= 7; + for (i = 0; i < SOUND_MIXER_NRDEVICES && mixtable[i].rec != j; i++); + return 1 << i; +} + +static int mixer_ioctl(struct sv_state *s, unsigned int cmd, unsigned long arg) +{ + unsigned long flags; + int i, val; + unsigned char l, r, rl, rr; + int __user *p = (int __user *)arg; + + VALIDATE_STATE(s); + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + memset(&info, 0, sizeof(info)); + strlcpy(info.id, "SonicVibes", sizeof(info.id)); + strlcpy(info.name, "S3 SonicVibes", sizeof(info.name)); + info.modify_counter = s->mix.modcnt; + if (copy_to_user((void __user *)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, "SonicVibes", sizeof(info.id)); + strlcpy(info.name, "S3 SonicVibes", sizeof(info.name)); + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, p); + if (cmd == SOUND_MIXER_PRIVATE1) { /* SRS settings */ + if (get_user(val, p)) + return -EFAULT; + spin_lock_irqsave(&s->lock, flags); + if (val & 1) { + if (val & 2) { + l = 4 - ((val >> 2) & 7); + if (l & ~3) + l = 4; + r = 4 - ((val >> 5) & 7); + if (r & ~3) + r = 4; + wrindir(s, SV_CISRSSPACE, l); + wrindir(s, SV_CISRSCENTER, r); + } else + wrindir(s, SV_CISRSSPACE, 0x80); + } + l = rdindir(s, SV_CISRSSPACE); + r = rdindir(s, SV_CISRSCENTER); + spin_unlock_irqrestore(&s->lock, flags); + if (l & 0x80) + return put_user(0, p); + return put_user(((4 - (l & 7)) << 2) | ((4 - (r & 7)) << 5) | 2, p); + } + if (_IOC_TYPE(cmd) != 'M' || _SIOC_SIZE(cmd) != sizeof(int)) + return -EINVAL; + if (_SIOC_DIR(cmd) == _SIOC_READ) { + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + return put_user(mixer_recmask(s), p); + + case SOUND_MIXER_DEVMASK: /* Arg contains a bit for each supported device */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].type) + val |= 1 << i; + return put_user(val, p); + + case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].rec) + val |= 1 << i; + return put_user(val, p); + + case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].type && mixtable[i].type != MT_4MUTEMONO) + val |= 1 << i; + return put_user(val, p); + + case SOUND_MIXER_CAPS: + return put_user(SOUND_CAP_EXCL_INPUT, p); + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES || !mixtable[i].type) + return -EINVAL; +#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS + return return_mixval(s, i, p); +#else /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + if (!volidx[i]) + return -EINVAL; + return put_user(s->mix.vol[volidx[i]-1], p); +#endif /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + } + } + if (_SIOC_DIR(cmd) != (_SIOC_READ|_SIOC_WRITE)) + return -EINVAL; + s->mix.modcnt++; + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + if (get_user(val, p)) + return -EFAULT; + i = hweight32(val); + if (i == 0) + return 0; /*val = mixer_recmask(s);*/ + else if (i > 1) + val &= ~mixer_recmask(s); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!(val & (1 << i))) + continue; + if (mixtable[i].rec) + break; + } + if (!mixtable[i].rec) + return 0; + spin_lock_irqsave(&s->lock, flags); + frobindir(s, SV_CIMIX_ADCINL, 0x1f, mixtable[i].rec << 5); + frobindir(s, SV_CIMIX_ADCINR, 0x1f, mixtable[i].rec << 5); + spin_unlock_irqrestore(&s->lock, flags); + return 0; + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES || !mixtable[i].type) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + l = val & 0xff; + r = (val >> 8) & 0xff; + if (mixtable[i].type == MT_4MUTEMONO) + l = (r + l) / 2; + if (l > 100) + l = 100; + if (r > 100) + r = 100; + spin_lock_irqsave(&s->lock, flags); + switch (mixtable[i].type) { + case MT_4: + if (l >= 10) + l -= 10; + if (r >= 10) + r -= 10; + frobindir(s, mixtable[i].left, 0xf0, l / 6); + frobindir(s, mixtable[i].right, 0xf0, l / 6); + break; + + case MT_4MUTEMONO: + rr = 0; + if (l < 10) + rl = 0x80; + else { + if (l >= 55) { + rr = 0x10; + l -= 45; + } + rl = (55 - l) / 3; + } + wrindir(s, mixtable[i].left, rl); + frobindir(s, mixtable[i].right, ~0x10, rr); + break; + + case MT_5MUTE: + if (l < 7) + rl = 0x80; + else + rl = (100 - l) / 3; + if (r < 7) + rr = 0x80; + else + rr = (100 - r) / 3; + wrindir(s, mixtable[i].left, rl); + wrindir(s, mixtable[i].right, rr); + break; + + case MT_6MUTE: + if (l < 6) + rl = 0x80; + else + rl = (100 - l) * 2 / 3; + if (r < 6) + rr = 0x80; + else + rr = (100 - r) * 2 / 3; + wrindir(s, mixtable[i].left, rl); + wrindir(s, mixtable[i].right, rr); + break; + } + spin_unlock_irqrestore(&s->lock, flags); +#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS + return return_mixval(s, i, p); +#else /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + if (!volidx[i]) + return -EINVAL; + s->mix.vol[volidx[i]-1] = val; + return put_user(s->mix.vol[volidx[i]-1], p); +#endif /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + } +} + +/* --------------------------------------------------------------------- */ + +static int sv_open_mixdev(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct list_head *list; + struct sv_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct sv_state, devs); + if (s->dev_mixer == minor) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + return nonseekable_open(inode, file); +} + +static int sv_release_mixdev(struct inode *inode, struct file *file) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + + VALIDATE_STATE(s); + return 0; +} + +static int sv_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + return mixer_ioctl((struct sv_state *)file->private_data, cmd, arg); +} + +static /*const*/ struct file_operations sv_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = sv_ioctl_mixdev, + .open = sv_open_mixdev, + .release = sv_release_mixdev, +}; + +/* --------------------------------------------------------------------- */ + +static int drain_dac(struct sv_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int count, tmo; + + if (s->dma_dac.mapped || !s->dma_dac.ready) + return 0; + add_wait_queue(&s->dma_dac.wait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_dac.wait, &wait); + set_current_state(TASK_RUNNING); + return -EBUSY; + } + tmo = 3 * HZ * (count + s->dma_dac.fragsize) / 2 / s->ratedac; + tmo >>= sample_shift[(s->fmt >> SV_CFMT_ASHIFT) & SV_CFMT_MASK]; + if (!schedule_timeout(tmo + 1)) + printk(KERN_DEBUG "sv: dma timed out??\n"); + } + remove_wait_queue(&s->dma_dac.wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static ssize_t sv_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_adc.mapped) + return -ENXIO; + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; +#if 0 + spin_lock_irqsave(&s->lock, flags); + sv_update_ptr(s); + spin_unlock_irqrestore(&s->lock, flags); +#endif + add_wait_queue(&s->dma_adc.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + swptr = s->dma_adc.swptr; + cnt = s->dma_adc.dmasize-swptr; + if (s->dma_adc.count < cnt) + cnt = s->dma_adc.count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (s->dma_adc.enabled) + start_adc(s); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + if (!schedule_timeout(HZ)) { + printk(KERN_DEBUG "sv: read: chip lockup? dmasz %u fragsz %u count %i hwptr %u swptr %u\n", + s->dma_adc.dmasize, s->dma_adc.fragsize, s->dma_adc.count, + s->dma_adc.hwptr, s->dma_adc.swptr); + stop_adc(s); + spin_lock_irqsave(&s->lock, flags); + set_dmac(s, virt_to_bus(s->dma_adc.rawbuf), s->dma_adc.numfrag << s->dma_adc.fragshift); + /* program enhanced mode registers */ + wrindir(s, SV_CIDMACBASECOUNT1, (s->dma_adc.fragsamples-1) >> 8); + wrindir(s, SV_CIDMACBASECOUNT0, s->dma_adc.fragsamples-1); + s->dma_adc.count = s->dma_adc.hwptr = s->dma_adc.swptr = 0; + spin_unlock_irqrestore(&s->lock, flags); + } + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_to_user(buffer, s->dma_adc.rawbuf + swptr, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + swptr = (swptr + cnt) % s->dma_adc.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_adc.swptr = swptr; + s->dma_adc.count -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->dma_adc.enabled) + start_adc(s); + } + remove_wait_queue(&s->dma_adc.wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +static ssize_t sv_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (s->dma_dac.mapped) + return -ENXIO; + if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0))) + return ret; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; +#if 0 + spin_lock_irqsave(&s->lock, flags); + sv_update_ptr(s); + spin_unlock_irqrestore(&s->lock, flags); +#endif + add_wait_queue(&s->dma_dac.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + if (s->dma_dac.count < 0) { + s->dma_dac.count = 0; + s->dma_dac.swptr = s->dma_dac.hwptr; + } + swptr = s->dma_dac.swptr; + cnt = s->dma_dac.dmasize-swptr; + if (s->dma_dac.count + cnt > s->dma_dac.dmasize) + cnt = s->dma_dac.dmasize - s->dma_dac.count; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (s->dma_dac.enabled) + start_dac(s); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + if (!schedule_timeout(HZ)) { + printk(KERN_DEBUG "sv: write: chip lockup? dmasz %u fragsz %u count %i hwptr %u swptr %u\n", + s->dma_dac.dmasize, s->dma_dac.fragsize, s->dma_dac.count, + s->dma_dac.hwptr, s->dma_dac.swptr); + stop_dac(s); + spin_lock_irqsave(&s->lock, flags); + set_dmaa(s, virt_to_bus(s->dma_dac.rawbuf), s->dma_dac.numfrag << s->dma_dac.fragshift); + /* program enhanced mode registers */ + wrindir(s, SV_CIDMAABASECOUNT1, (s->dma_dac.fragsamples-1) >> 8); + wrindir(s, SV_CIDMAABASECOUNT0, s->dma_dac.fragsamples-1); + s->dma_dac.count = s->dma_dac.hwptr = s->dma_dac.swptr = 0; + spin_unlock_irqrestore(&s->lock, flags); + } + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_from_user(s->dma_dac.rawbuf + swptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + swptr = (swptr + cnt) % s->dma_dac.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_dac.swptr = swptr; + s->dma_dac.count += cnt; + s->dma_dac.endcleared = 0; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (s->dma_dac.enabled) + start_dac(s); + } + remove_wait_queue(&s->dma_dac.wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int sv_poll(struct file *file, struct poll_table_struct *wait) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) { + if (!s->dma_dac.ready && prog_dmabuf(s, 1)) + return 0; + poll_wait(file, &s->dma_dac.wait, wait); + } + if (file->f_mode & FMODE_READ) { + if (!s->dma_adc.ready && prog_dmabuf(s, 0)) + return 0; + poll_wait(file, &s->dma_adc.wait, wait); + } + spin_lock_irqsave(&s->lock, flags); + sv_update_ptr(s); + if (file->f_mode & FMODE_READ) { + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->dma_dac.mapped) { + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)s->dma_dac.dmasize >= s->dma_dac.count + (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int sv_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + struct dmabuf *db; + int ret = -EINVAL; + unsigned long size; + + VALIDATE_STATE(s); + lock_kernel(); + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf(s, 1)) != 0) + goto out; + db = &s->dma_dac; + } else if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf(s, 0)) != 0) + goto out; + db = &s->dma_adc; + } else + goto out; + ret = -EINVAL; + if (vma->vm_pgoff != 0) + goto out; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << db->buforder)) + goto out; + ret = -EAGAIN; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(db->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + goto out; + db->mapped = 1; + ret = 0; +out: + unlock_kernel(); + return ret; +} + +static int sv_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int count; + int val, mapped, ret; + unsigned char fmtm, fmtd; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + 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: + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_SYNC: + 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, p); + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + synchronize_irq(s->irq); + s->dma_dac.swptr = s->dma_dac.hwptr = s->dma_dac.count = s->dma_dac.total_bytes = 0; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(s->irq); + s->dma_adc.swptr = s->dma_adc.hwptr = s->dma_adc.count = s->dma_adc.total_bytes = 0; + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + if (val >= 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + set_adc_rate(s, val); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + set_dac_rate(s, val); + } + } + return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, p); + + case SNDCTL_DSP_STEREO: + if (get_user(val, p)) + return -EFAULT; + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val) + fmtd |= SV_CFMT_STEREO << SV_CFMT_CSHIFT; + else + fmtm &= ~(SV_CFMT_STEREO << SV_CFMT_CSHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val) + fmtd |= SV_CFMT_STEREO << SV_CFMT_ASHIFT; + else + fmtm &= ~(SV_CFMT_STEREO << SV_CFMT_ASHIFT); + } + set_fmt(s, fmtm, fmtd); + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + if (val != 0) { + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val >= 2) + fmtd |= SV_CFMT_STEREO << SV_CFMT_CSHIFT; + else + fmtm &= ~(SV_CFMT_STEREO << SV_CFMT_CSHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val >= 2) + fmtd |= SV_CFMT_STEREO << SV_CFMT_ASHIFT; + else + fmtm &= ~(SV_CFMT_STEREO << SV_CFMT_ASHIFT); + } + set_fmt(s, fmtm, fmtd); + } + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (SV_CFMT_STEREO << SV_CFMT_CSHIFT) + : (SV_CFMT_STEREO << SV_CFMT_ASHIFT))) ? 2 : 1, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_LE|AFMT_U8, p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, p)) + return -EFAULT; + if (val != AFMT_QUERY) { + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val == AFMT_S16_LE) + fmtd |= SV_CFMT_16BIT << SV_CFMT_CSHIFT; + else + fmtm &= ~(SV_CFMT_16BIT << SV_CFMT_CSHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val == AFMT_S16_LE) + fmtd |= SV_CFMT_16BIT << SV_CFMT_ASHIFT; + else + fmtm &= ~(SV_CFMT_16BIT << SV_CFMT_ASHIFT); + } + set_fmt(s, fmtm, fmtd); + } + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (SV_CFMT_16BIT << SV_CFMT_CSHIFT) + : (SV_CFMT_16BIT << SV_CFMT_ASHIFT))) ? AFMT_S16_LE : AFMT_U8, p); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if (file->f_mode & FMODE_READ && s->enable & SV_CENABLE_RE) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && s->enable & SV_CENABLE_PE) + val |= PCM_ENABLE_OUTPUT; + return put_user(val, p); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + if (val & PCM_ENABLE_INPUT) { + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + s->dma_adc.enabled = 1; + start_adc(s); + } else { + s->dma_adc.enabled = 0; + stop_adc(s); + } + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) { + if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0))) + return ret; + s->dma_dac.enabled = 1; + start_dac(s); + } else { + s->dma_dac.enabled = 0; + 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(s, 0)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + sv_update_ptr(s); + abinfo.fragsize = s->dma_dac.fragsize; + count = s->dma_dac.count; + if (count < 0) + count = 0; + abinfo.bytes = s->dma_dac.dmasize - count; + abinfo.fragstotal = s->dma_dac.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user(argp, &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(s, 1)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + sv_update_ptr(s); + abinfo.fragsize = s->dma_adc.fragsize; + count = s->dma_adc.count; + if (count < 0) + count = 0; + abinfo.bytes = 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(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac.ready && (val = prog_dmabuf(s, 0)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + sv_update_ptr(s); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count < 0) + count = 0; + return put_user(count, p); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!s->dma_adc.ready && (val = prog_dmabuf(s, 1)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + sv_update_ptr(s); + cinfo.bytes = s->dma_adc.total_bytes; + count = s->dma_adc.count; + if (count < 0) + count = 0; + cinfo.blocks = count >> s->dma_adc.fragshift; + 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); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!s->dma_dac.ready && (val = prog_dmabuf(s, 0)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + sv_update_ptr(s); + cinfo.bytes = s->dma_dac.total_bytes; + count = s->dma_dac.count; + if (count < 0) + count = 0; + cinfo.blocks = 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); + if (copy_to_user(argp, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + if ((val = prog_dmabuf(s, 0))) + return val; + return put_user(s->dma_dac.fragsize, p); + } + if ((val = prog_dmabuf(s, 1))) + return val; + return put_user(s->dma_adc.fragsize, p); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = val & 0xffff; + s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_adc.ossfragshift < 4) + s->dma_adc.ossfragshift = 4; + if (s->dma_adc.ossfragshift > 15) + s->dma_adc.ossfragshift = 15; + if (s->dma_adc.ossmaxfrags < 4) + s->dma_adc.ossmaxfrags = 4; + } + if (file->f_mode & FMODE_WRITE) { + s->dma_dac.ossfragshift = val & 0xffff; + s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac.ossfragshift < 4) + s->dma_dac.ossfragshift = 4; + if (s->dma_dac.ossfragshift > 15) + s->dma_dac.ossfragshift = 15; + if (s->dma_dac.ossmaxfrags < 4) + s->dma_dac.ossmaxfrags = 4; + } + return 0; + + 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, p)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + if (file->f_mode & FMODE_READ) + s->dma_adc.subdivision = val; + if (file->f_mode & FMODE_WRITE) + s->dma_dac.subdivision = val; + return 0; + + case SOUND_PCM_READ_RATE: + return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, p); + + case SOUND_PCM_READ_CHANNELS: + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (SV_CFMT_STEREO << SV_CFMT_CSHIFT) + : (SV_CFMT_STEREO << SV_CFMT_ASHIFT))) ? 2 : 1, p); + + case SOUND_PCM_READ_BITS: + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (SV_CFMT_16BIT << SV_CFMT_CSHIFT) + : (SV_CFMT_16BIT << SV_CFMT_ASHIFT))) ? 16 : 8, p); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + + } + return mixer_ioctl(s, cmd, arg); +} + +static int sv_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned char fmtm = ~0, fmts = 0; + struct list_head *list; + struct sv_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct sv_state, devs); + if (!((s->dev_audio ^ minor) & ~0xf)) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & file->f_mode) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + if (file->f_mode & FMODE_READ) { + fmtm &= ~((SV_CFMT_STEREO | SV_CFMT_16BIT) << SV_CFMT_CSHIFT); + if ((minor & 0xf) == SND_DEV_DSP16) + fmts |= SV_CFMT_16BIT << SV_CFMT_CSHIFT; + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = s->dma_adc.subdivision = 0; + s->dma_adc.enabled = 1; + set_adc_rate(s, 8000); + } + if (file->f_mode & FMODE_WRITE) { + fmtm &= ~((SV_CFMT_STEREO | SV_CFMT_16BIT) << SV_CFMT_ASHIFT); + if ((minor & 0xf) == SND_DEV_DSP16) + fmts |= SV_CFMT_16BIT << SV_CFMT_ASHIFT; + s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = s->dma_dac.subdivision = 0; + s->dma_dac.enabled = 1; + set_dac_rate(s, 8000); + } + set_fmt(s, fmtm, fmts); + s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int sv_release(struct inode *inode, struct file *file) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + + VALIDATE_STATE(s); + lock_kernel(); + if (file->f_mode & FMODE_WRITE) + drain_dac(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem); + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + dealloc_dmabuf(s, &s->dma_dac); + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + dealloc_dmabuf(s, &s->dma_adc); + } + s->open_mode &= ~(file->f_mode & (FMODE_READ|FMODE_WRITE)); + wake_up(&s->open_wait); + up(&s->open_sem); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations sv_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = sv_read, + .write = sv_write, + .poll = sv_poll, + .ioctl = sv_ioctl, + .mmap = sv_mmap, + .open = sv_open, + .release = sv_release, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t sv_midi_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + if (count == 0) + return 0; + ret = 0; + add_wait_queue(&s->midi.iwait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.ird; + cnt = MIDIINBUF - ptr; + if (s->midi.icnt < cnt) + cnt = s->midi.icnt; + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_to_user(buffer, s->midi.ibuf + ptr, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + ptr = (ptr + cnt) % MIDIINBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.ird = ptr; + s->midi.icnt -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + break; + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&s->midi.iwait, &wait); + return ret; +} + +static ssize_t sv_midi_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + if (count == 0) + return 0; + ret = 0; + add_wait_queue(&s->midi.owait, &wait); + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.owr; + cnt = MIDIOUTBUF - ptr; + if (s->midi.ocnt + cnt > MIDIOUTBUF) + cnt = MIDIOUTBUF - s->midi.ocnt; + if (cnt <= 0) { + __set_current_state(TASK_INTERRUPTIBLE); + sv_handle_midi(s); + } + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_from_user(s->midi.obuf + ptr, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + ptr = (ptr + cnt) % MIDIOUTBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.owr = ptr; + s->midi.ocnt += cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + spin_lock_irqsave(&s->lock, flags); + sv_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&s->midi.owait, &wait); + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int sv_midi_poll(struct file *file, struct poll_table_struct *wait) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) + poll_wait(file, &s->midi.owait, wait); + if (file->f_mode & FMODE_READ) + poll_wait(file, &s->midi.iwait, wait); + spin_lock_irqsave(&s->lock, flags); + if (file->f_mode & FMODE_READ) { + if (s->midi.icnt > 0) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->midi.ocnt < MIDIOUTBUF) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int sv_midi_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + struct list_head *list; + struct sv_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct sv_state, devs); + if (s->dev_midi == minor) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & (file->f_mode << FMODE_MIDI_SHIFT)) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + //outb(inb(s->ioenh + SV_CODEC_CONTROL) | SV_CCTRL_WAVETABLE, s->ioenh + SV_CODEC_CONTROL); + outb(inb(s->ioenh + SV_CODEC_INTMASK) | SV_CINTMASK_MIDI, s->ioenh + SV_CODEC_INTMASK); + wrindir(s, SV_CIUARTCONTROL, 5); /* output MIDI data to external and internal synth */ + wrindir(s, SV_CIWAVETABLESRC, 1); /* Wavetable in PC RAM */ + outb(0xff, s->iomidi+1); /* reset command */ + outb(0x3f, s->iomidi+1); /* uart command */ + if (!(inb(s->iomidi+1) & 0x80)) + inb(s->iomidi); + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + init_timer(&s->midi.timer); + s->midi.timer.expires = jiffies+1; + s->midi.timer.data = (unsigned long)s; + s->midi.timer.function = sv_midi_timer; + add_timer(&s->midi.timer); + } + if (file->f_mode & FMODE_READ) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + } + if (file->f_mode & FMODE_WRITE) { + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + } + spin_unlock_irqrestore(&s->lock, flags); + s->open_mode |= (file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ | FMODE_MIDI_WRITE); + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int sv_midi_release(struct inode *inode, struct file *file) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + unsigned count, tmo; + + VALIDATE_STATE(s); + + lock_kernel(); + if (file->f_mode & FMODE_WRITE) { + add_wait_queue(&s->midi.owait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&s->lock, flags); + count = s->midi.ocnt; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (file->f_flags & O_NONBLOCK) { + remove_wait_queue(&s->midi.owait, &wait); + set_current_state(TASK_RUNNING); + unlock_kernel(); + return -EBUSY; + } + tmo = (count * HZ) / 3100; + if (!schedule_timeout(tmo ? : 1) && tmo) + printk(KERN_DEBUG "sv: midi timed out??\n"); + } + remove_wait_queue(&s->midi.owait, &wait); + set_current_state(TASK_RUNNING); + } + down(&s->open_sem); + s->open_mode &= ~((file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ|FMODE_MIDI_WRITE)); + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + outb(inb(s->ioenh + SV_CODEC_INTMASK) & ~SV_CINTMASK_MIDI, s->ioenh + SV_CODEC_INTMASK); + del_timer(&s->midi.timer); + } + spin_unlock_irqrestore(&s->lock, flags); + wake_up(&s->open_wait); + up(&s->open_sem); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations sv_midi_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = sv_midi_read, + .write = sv_midi_write, + .poll = sv_midi_poll, + .open = sv_midi_open, + .release = sv_midi_release, +}; + +/* --------------------------------------------------------------------- */ + +static int sv_dmfm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + static const unsigned char op_offset[18] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 + }; + struct sv_state *s = (struct sv_state *)file->private_data; + struct dm_fm_voice v; + struct dm_fm_note n; + struct dm_fm_params p; + unsigned int io; + unsigned int regb; + + switch (cmd) { + case FM_IOCTL_RESET: + for (regb = 0xb0; regb < 0xb9; regb++) { + outb(regb, s->iosynth); + outb(0, s->iosynth+1); + outb(regb, s->iosynth+2); + outb(0, s->iosynth+3); + } + return 0; + + case FM_IOCTL_PLAY_NOTE: + if (copy_from_user(&n, (void __user *)arg, sizeof(n))) + return -EFAULT; + if (n.voice >= 18) + return -EINVAL; + if (n.voice >= 9) { + regb = n.voice - 9; + io = s->iosynth+2; + } else { + regb = n.voice; + io = s->iosynth; + } + outb(0xa0 + regb, io); + outb(n.fnum & 0xff, io+1); + outb(0xb0 + regb, io); + outb(((n.fnum >> 8) & 3) | ((n.octave & 7) << 2) | ((n.key_on & 1) << 5), io+1); + return 0; + + case FM_IOCTL_SET_VOICE: + if (copy_from_user(&v, (void __user *)arg, sizeof(v))) + return -EFAULT; + if (v.voice >= 18) + return -EINVAL; + regb = op_offset[v.voice]; + io = s->iosynth + ((v.op & 1) << 1); + outb(0x20 + regb, io); + outb(((v.am & 1) << 7) | ((v.vibrato & 1) << 6) | ((v.do_sustain & 1) << 5) | + ((v.kbd_scale & 1) << 4) | (v.harmonic & 0xf), io+1); + outb(0x40 + regb, io); + outb(((v.scale_level & 0x3) << 6) | (v.volume & 0x3f), io+1); + outb(0x60 + regb, io); + outb(((v.attack & 0xf) << 4) | (v.decay & 0xf), io+1); + outb(0x80 + regb, io); + outb(((v.sustain & 0xf) << 4) | (v.release & 0xf), io+1); + outb(0xe0 + regb, io); + outb(v.waveform & 0x7, io+1); + if (n.voice >= 9) { + regb = n.voice - 9; + io = s->iosynth+2; + } else { + regb = n.voice; + io = s->iosynth; + } + outb(0xc0 + regb, io); + outb(((v.right & 1) << 5) | ((v.left & 1) << 4) | ((v.feedback & 7) << 1) | + (v.connection & 1), io+1); + return 0; + + case FM_IOCTL_SET_PARAMS: + if (copy_from_user(&p, (void *__user )arg, sizeof(p))) + return -EFAULT; + outb(0x08, s->iosynth); + outb((p.kbd_split & 1) << 6, s->iosynth+1); + outb(0xbd, s->iosynth); + outb(((p.am_depth & 1) << 7) | ((p.vib_depth & 1) << 6) | ((p.rhythm & 1) << 5) | ((p.bass & 1) << 4) | + ((p.snare & 1) << 3) | ((p.tomtom & 1) << 2) | ((p.cymbal & 1) << 1) | (p.hihat & 1), s->iosynth+1); + return 0; + + case FM_IOCTL_SET_OPL: + outb(4, s->iosynth+2); + outb(arg, s->iosynth+3); + return 0; + + case FM_IOCTL_SET_MODE: + outb(5, s->iosynth+2); + outb(arg & 1, s->iosynth+3); + return 0; + + default: + return -EINVAL; + } +} + +static int sv_dmfm_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + struct list_head *list; + struct sv_state *s; + + for (list = devs.next; ; list = list->next) { + if (list == &devs) + return -ENODEV; + s = list_entry(list, struct sv_state, devs); + if (s->dev_dmfm == minor) + break; + } + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & FMODE_DMFM) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + add_wait_queue(&s->open_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE); + up(&s->open_sem); + schedule(); + remove_wait_queue(&s->open_wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + /* init the stuff */ + outb(1, s->iosynth); + outb(0x20, s->iosynth+1); /* enable waveforms */ + outb(4, s->iosynth+2); + outb(0, s->iosynth+3); /* no 4op enabled */ + outb(5, s->iosynth+2); + outb(1, s->iosynth+3); /* enable OPL3 */ + s->open_mode |= FMODE_DMFM; + up(&s->open_sem); + return nonseekable_open(inode, file); +} + +static int sv_dmfm_release(struct inode *inode, struct file *file) +{ + struct sv_state *s = (struct sv_state *)file->private_data; + unsigned int regb; + + VALIDATE_STATE(s); + lock_kernel(); + down(&s->open_sem); + s->open_mode &= ~FMODE_DMFM; + for (regb = 0xb0; regb < 0xb9; regb++) { + outb(regb, s->iosynth); + outb(0, s->iosynth+1); + outb(regb, s->iosynth+2); + outb(0, s->iosynth+3); + } + wake_up(&s->open_wait); + up(&s->open_sem); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations sv_dmfm_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = sv_dmfm_ioctl, + .open = sv_dmfm_open, + .release = sv_dmfm_release, +}; + +/* --------------------------------------------------------------------- */ + +/* maximum number of devices; only used for command line params */ +#define NR_DEVICE 5 + +static int reverb[NR_DEVICE]; + +#if 0 +static int wavetable[NR_DEVICE]; +#endif + +static unsigned int devindex; + +module_param_array(reverb, bool, NULL, 0); +MODULE_PARM_DESC(reverb, "if 1 enables the reverb circuitry. NOTE: your card must have the reverb RAM"); +#if 0 +MODULE_PARM(wavetable, "1-" __MODULE_STRING(NR_DEVICE) "i"); +MODULE_PARM_DESC(wavetable, "if 1 the wavetable synth is enabled"); +#endif + +MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu"); +MODULE_DESCRIPTION("S3 SonicVibes Driver"); +MODULE_LICENSE("GPL"); + + +/* --------------------------------------------------------------------- */ + +static struct initvol { + int mixch; + int vol; +} initvol[] __devinitdata = { + { SOUND_MIXER_WRITE_RECLEV, 0x4040 }, + { SOUND_MIXER_WRITE_LINE1, 0x4040 }, + { SOUND_MIXER_WRITE_CD, 0x4040 }, + { SOUND_MIXER_WRITE_LINE, 0x4040 }, + { SOUND_MIXER_WRITE_MIC, 0x4040 }, + { SOUND_MIXER_WRITE_SYNTH, 0x4040 }, + { SOUND_MIXER_WRITE_LINE2, 0x4040 }, + { SOUND_MIXER_WRITE_VOLUME, 0x4040 }, + { SOUND_MIXER_WRITE_PCM, 0x4040 } +}; + +#define RSRCISIOREGION(dev,num) (pci_resource_start((dev), (num)) != 0 && \ + (pci_resource_flags((dev), (num)) & IORESOURCE_IO)) + +static int __devinit sv_register_gameport(struct sv_state *s, int io_port) +{ + struct gameport *gp; + + if (!request_region(io_port, SV_EXTENT_GAME, "S3 SonicVibes Gameport")) { + printk(KERN_ERR "sv: gameport io ports are in use\n"); + return -EBUSY; + } + + s->gameport = gp = gameport_allocate_port(); + if (!gp) { + printk(KERN_ERR "sv: can not allocate memory for gameport\n"); + release_region(io_port, SV_EXTENT_GAME); + return -ENOMEM; + } + + gameport_set_name(gp, "S3 SonicVibes Gameport"); + gameport_set_phys(gp, "isa%04x/gameport0", io_port); + gp->dev.parent = &s->dev->dev; + gp->io = io_port; + + gameport_register_port(gp); + + return 0; +} + +static int __devinit sv_probe(struct pci_dev *pcidev, const struct pci_device_id *pciid) +{ + static char __devinitdata sv_ddma_name[] = "S3 Inc. SonicVibes DDMA Controller"; + struct sv_state *s; + mm_segment_t fs; + int i, val, ret; + int gpio; + char *ddmaname; + unsigned ddmanamelen; + + if ((ret=pci_enable_device(pcidev))) + return ret; + + if (!RSRCISIOREGION(pcidev, RESOURCE_SB) || + !RSRCISIOREGION(pcidev, RESOURCE_ENH) || + !RSRCISIOREGION(pcidev, RESOURCE_SYNTH) || + !RSRCISIOREGION(pcidev, RESOURCE_MIDI) || + !RSRCISIOREGION(pcidev, RESOURCE_GAME)) + return -ENODEV; + if (pcidev->irq == 0) + return -ENODEV; + if (pci_set_dma_mask(pcidev, 0x00ffffff)) { + printk(KERN_WARNING "sonicvibes: architecture does not support 24bit PCI busmaster DMA\n"); + return -ENODEV; + } + /* try to allocate a DDMA resource if not already available */ + if (!RSRCISIOREGION(pcidev, RESOURCE_DDMA)) { + pcidev->resource[RESOURCE_DDMA].start = 0; + pcidev->resource[RESOURCE_DDMA].end = 2*SV_EXTENT_DMA-1; + pcidev->resource[RESOURCE_DDMA].flags = PCI_BASE_ADDRESS_SPACE_IO | IORESOURCE_IO; + ddmanamelen = strlen(sv_ddma_name)+1; + if (!(ddmaname = kmalloc(ddmanamelen, GFP_KERNEL))) + return -1; + memcpy(ddmaname, sv_ddma_name, ddmanamelen); + pcidev->resource[RESOURCE_DDMA].name = ddmaname; + if (pci_assign_resource(pcidev, RESOURCE_DDMA)) { + pcidev->resource[RESOURCE_DDMA].name = NULL; + kfree(ddmaname); + printk(KERN_ERR "sv: cannot allocate DDMA controller io ports\n"); + return -EBUSY; + } + } + if (!(s = kmalloc(sizeof(struct sv_state), GFP_KERNEL))) { + printk(KERN_WARNING "sv: out of memory\n"); + return -ENOMEM; + } + memset(s, 0, sizeof(struct sv_state)); + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac.wait); + init_waitqueue_head(&s->open_wait); + init_waitqueue_head(&s->midi.iwait); + init_waitqueue_head(&s->midi.owait); + init_MUTEX(&s->open_sem); + spin_lock_init(&s->lock); + s->magic = SV_MAGIC; + s->dev = pcidev; + s->iosb = pci_resource_start(pcidev, RESOURCE_SB); + s->ioenh = pci_resource_start(pcidev, RESOURCE_ENH); + s->iosynth = pci_resource_start(pcidev, RESOURCE_SYNTH); + s->iomidi = pci_resource_start(pcidev, RESOURCE_MIDI); + s->iodmaa = pci_resource_start(pcidev, RESOURCE_DDMA); + s->iodmac = pci_resource_start(pcidev, RESOURCE_DDMA) + SV_EXTENT_DMA; + gpio = pci_resource_start(pcidev, RESOURCE_GAME); + pci_write_config_dword(pcidev, 0x40, s->iodmaa | 9); /* enable and use extended mode */ + pci_write_config_dword(pcidev, 0x48, s->iodmac | 9); /* enable */ + printk(KERN_DEBUG "sv: io ports: %#lx %#lx %#lx %#lx %#x %#x %#x\n", + s->iosb, s->ioenh, s->iosynth, s->iomidi, gpio, s->iodmaa, s->iodmac); + s->irq = pcidev->irq; + + /* hack */ + pci_write_config_dword(pcidev, 0x60, wavetable_mem >> 12); /* wavetable base address */ + + ret = -EBUSY; + if (!request_region(s->ioenh, SV_EXTENT_ENH, "S3 SonicVibes PCM")) { + printk(KERN_ERR "sv: io ports %#lx-%#lx in use\n", s->ioenh, s->ioenh+SV_EXTENT_ENH-1); + goto err_region5; + } + if (!request_region(s->iodmaa, SV_EXTENT_DMA, "S3 SonicVibes DMAA")) { + printk(KERN_ERR "sv: io ports %#x-%#x in use\n", s->iodmaa, s->iodmaa+SV_EXTENT_DMA-1); + goto err_region4; + } + if (!request_region(s->iodmac, SV_EXTENT_DMA, "S3 SonicVibes DMAC")) { + printk(KERN_ERR "sv: io ports %#x-%#x in use\n", s->iodmac, s->iodmac+SV_EXTENT_DMA-1); + goto err_region3; + } + if (!request_region(s->iomidi, SV_EXTENT_MIDI, "S3 SonicVibes Midi")) { + printk(KERN_ERR "sv: io ports %#lx-%#lx in use\n", s->iomidi, s->iomidi+SV_EXTENT_MIDI-1); + goto err_region2; + } + if (!request_region(s->iosynth, SV_EXTENT_SYNTH, "S3 SonicVibes Synth")) { + printk(KERN_ERR "sv: io ports %#lx-%#lx in use\n", s->iosynth, s->iosynth+SV_EXTENT_SYNTH-1); + goto err_region1; + } + + /* initialize codec registers */ + outb(0x80, s->ioenh + SV_CODEC_CONTROL); /* assert reset */ + udelay(50); + outb(0x00, s->ioenh + SV_CODEC_CONTROL); /* deassert reset */ + udelay(50); + outb(SV_CCTRL_INTADRIVE | SV_CCTRL_ENHANCED /*| SV_CCTRL_WAVETABLE */ + | (reverb[devindex] ? SV_CCTRL_REVERB : 0), s->ioenh + SV_CODEC_CONTROL); + inb(s->ioenh + SV_CODEC_STATUS); /* clear ints */ + wrindir(s, SV_CIDRIVECONTROL, 0); /* drive current 16mA */ + wrindir(s, SV_CIENABLE, s->enable = 0); /* disable DMAA and DMAC */ + outb(~(SV_CINTMASK_DMAA | SV_CINTMASK_DMAC), s->ioenh + SV_CODEC_INTMASK); + /* outb(0xff, s->iodmaa + SV_DMA_RESET); */ + /* outb(0xff, s->iodmac + SV_DMA_RESET); */ + inb(s->ioenh + SV_CODEC_STATUS); /* ack interrupts */ + wrindir(s, SV_CIADCCLKSOURCE, 0); /* use pll as ADC clock source */ + wrindir(s, SV_CIANALOGPWRDOWN, 0); /* power up the analog parts of the device */ + wrindir(s, SV_CIDIGITALPWRDOWN, 0); /* power up the digital parts of the device */ + setpll(s, SV_CIADCPLLM, 8000); + wrindir(s, SV_CISRSSPACE, 0x80); /* SRS off */ + wrindir(s, SV_CIPCMSR0, (8000 * 65536 / FULLRATE) & 0xff); + wrindir(s, SV_CIPCMSR1, ((8000 * 65536 / FULLRATE) >> 8) & 0xff); + wrindir(s, SV_CIADCOUTPUT, 0); + /* request irq */ + if ((ret=request_irq(s->irq,sv_interrupt,SA_SHIRQ,"S3 SonicVibes",s))) { + printk(KERN_ERR "sv: irq %u in use\n", s->irq); + goto err_irq; + } + printk(KERN_INFO "sv: found adapter at io %#lx irq %u dmaa %#06x dmac %#06x revision %u\n", + s->ioenh, s->irq, s->iodmaa, s->iodmac, rdindir(s, SV_CIREVISION)); + /* register devices */ + if ((s->dev_audio = register_sound_dsp(&sv_audio_fops, -1)) < 0) { + ret = s->dev_audio; + goto err_dev1; + } + if ((s->dev_mixer = register_sound_mixer(&sv_mixer_fops, -1)) < 0) { + ret = s->dev_mixer; + goto err_dev2; + } + if ((s->dev_midi = register_sound_midi(&sv_midi_fops, -1)) < 0) { + ret = s->dev_midi; + goto err_dev3; + } + if ((s->dev_dmfm = register_sound_special(&sv_dmfm_fops, 15 /* ?? */)) < 0) { + ret = s->dev_dmfm; + goto err_dev4; + } + pci_set_master(pcidev); /* enable bus mastering */ + /* initialize the chips */ + fs = get_fs(); + set_fs(KERNEL_DS); + val = SOUND_MASK_LINE|SOUND_MASK_SYNTH; + mixer_ioctl(s, SOUND_MIXER_WRITE_RECSRC, (unsigned long)&val); + for (i = 0; i < sizeof(initvol)/sizeof(initvol[0]); i++) { + val = initvol[i].vol; + mixer_ioctl(s, initvol[i].mixch, (unsigned long)&val); + } + set_fs(fs); + /* register gameport */ + sv_register_gameport(s, gpio); + /* store it in the driver field */ + pci_set_drvdata(pcidev, s); + /* put it into driver list */ + list_add_tail(&s->devs, &devs); + /* increment devindex */ + if (devindex < NR_DEVICE-1) + devindex++; + return 0; + + err_dev4: + unregister_sound_midi(s->dev_midi); + err_dev3: + unregister_sound_mixer(s->dev_mixer); + err_dev2: + unregister_sound_dsp(s->dev_audio); + err_dev1: + printk(KERN_ERR "sv: cannot register misc device\n"); + free_irq(s->irq, s); + err_irq: + release_region(s->iosynth, SV_EXTENT_SYNTH); + err_region1: + release_region(s->iomidi, SV_EXTENT_MIDI); + err_region2: + release_region(s->iodmac, SV_EXTENT_DMA); + err_region3: + release_region(s->iodmaa, SV_EXTENT_DMA); + err_region4: + release_region(s->ioenh, SV_EXTENT_ENH); + err_region5: + kfree(s); + return ret; +} + +static void __devexit sv_remove(struct pci_dev *dev) +{ + struct sv_state *s = pci_get_drvdata(dev); + + if (!s) + return; + list_del(&s->devs); + outb(~0, s->ioenh + SV_CODEC_INTMASK); /* disable ints */ + synchronize_irq(s->irq); + inb(s->ioenh + SV_CODEC_STATUS); /* ack interrupts */ + wrindir(s, SV_CIENABLE, 0); /* disable DMAA and DMAC */ + /*outb(0, s->iodmaa + SV_DMA_RESET);*/ + /*outb(0, s->iodmac + SV_DMA_RESET);*/ + free_irq(s->irq, s); + if (s->gameport) { + int gpio = s->gameport->io; + gameport_unregister_port(s->gameport); + release_region(gpio, SV_EXTENT_GAME); + } + release_region(s->iodmac, SV_EXTENT_DMA); + release_region(s->iodmaa, SV_EXTENT_DMA); + release_region(s->ioenh, SV_EXTENT_ENH); + release_region(s->iomidi, SV_EXTENT_MIDI); + release_region(s->iosynth, SV_EXTENT_SYNTH); + unregister_sound_dsp(s->dev_audio); + unregister_sound_mixer(s->dev_mixer); + unregister_sound_midi(s->dev_midi); + unregister_sound_special(s->dev_dmfm); + kfree(s); + pci_set_drvdata(dev, NULL); +} + +static struct pci_device_id id_table[] = { + { PCI_VENDOR_ID_S3, PCI_DEVICE_ID_S3_SONICVIBES, PCI_ANY_ID, PCI_ANY_ID, 0, 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, id_table); + +static struct pci_driver sv_driver = { + .name = "sonicvibes", + .id_table = id_table, + .probe = sv_probe, + .remove = __devexit_p(sv_remove), +}; + +static int __init init_sonicvibes(void) +{ + printk(KERN_INFO "sv: version v0.31 time " __TIME__ " " __DATE__ "\n"); +#if 0 + if (!(wavetable_mem = __get_free_pages(GFP_KERNEL, 20-PAGE_SHIFT))) + printk(KERN_INFO "sv: cannot allocate 1MB of contiguous nonpageable memory for wavetable data\n"); +#endif + return pci_module_init(&sv_driver); +} + +static void __exit cleanup_sonicvibes(void) +{ + printk(KERN_INFO "sv: unloading\n"); + pci_unregister_driver(&sv_driver); + if (wavetable_mem) + free_pages(wavetable_mem, 20-PAGE_SHIFT); +} + +module_init(init_sonicvibes); +module_exit(cleanup_sonicvibes); + +/* --------------------------------------------------------------------- */ + +#ifndef MODULE + +/* format is: sonicvibes=[reverb] sonicvibesdmaio=dmaioaddr */ + +static int __init sonicvibes_setup(char *str) +{ + static unsigned __initdata nr_dev = 0; + + if (nr_dev >= NR_DEVICE) + return 0; +#if 0 + if (get_option(&str, &reverb[nr_dev]) == 2) + (void)get_option(&str, &wavetable[nr_dev]); +#else + (void)get_option(&str, &reverb[nr_dev]); +#endif + + nr_dev++; + return 1; +} + +__setup("sonicvibes=", sonicvibes_setup); + +#endif /* MODULE */ diff --git a/sound/oss/sound_calls.h b/sound/oss/sound_calls.h new file mode 100644 index 000000000000..1ae07509664f --- /dev/null +++ b/sound/oss/sound_calls.h @@ -0,0 +1,90 @@ +/* + * 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); +int DMAbuf_open_dma (int dev); +void DMAbuf_close_dma (int dev); +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); +void MIDIbuf_init(void); + + +/* 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 000000000000..9f912b8a2969 --- /dev/null +++ b/sound/oss/sound_config.h @@ -0,0 +1,154 @@ +/* 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 +#include +#include + +#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) + +#if OPEN_READ == FMODE_READ && OPEN_WRITE == FMODE_WRITE + +static inline int translate_mode(struct file *file) +{ + return file->f_mode; +} + +#else + +static inline int translate_mode(struct file *file) +{ + return ((file->f_mode & FMODE_READ) ? OPEN_READ : 0) | + ((file->f_mode & FMODE_WRITE) ? OPEN_WRITE : 0); +} + +#endif + + +#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 + +#endif diff --git a/sound/oss/sound_firmware.h b/sound/oss/sound_firmware.h new file mode 100644 index 000000000000..0a0cbfdfb855 --- /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_syms.c b/sound/oss/sound_syms.c new file mode 100644 index 000000000000..cb7c33fe5b05 --- /dev/null +++ b/sound/oss/sound_syms.c @@ -0,0 +1,50 @@ +/* + * The sound core exports the following symbols to the rest of + * modulespace. + * + * (C) Copyright 1997 Alan Cox, Licensed under the GNU GPL + * + * Thu May 27 1999 Andrew J. Kroll + * left out exported symbol... fixed + */ + +#include +#include "sound_config.h" +#include "sound_calls.h" + +char sound_syms_symbol; + +EXPORT_SYMBOL(mixer_devs); +EXPORT_SYMBOL(audio_devs); +EXPORT_SYMBOL(num_mixers); +EXPORT_SYMBOL(num_audiodevs); + +EXPORT_SYMBOL(midi_devs); +EXPORT_SYMBOL(num_midis); +EXPORT_SYMBOL(synth_devs); + +EXPORT_SYMBOL(sound_timer_devs); + +EXPORT_SYMBOL(sound_install_audiodrv); +EXPORT_SYMBOL(sound_install_mixer); +EXPORT_SYMBOL(sound_alloc_dma); +EXPORT_SYMBOL(sound_free_dma); +EXPORT_SYMBOL(sound_open_dma); +EXPORT_SYMBOL(sound_close_dma); +EXPORT_SYMBOL(sound_alloc_mididev); +EXPORT_SYMBOL(sound_alloc_mixerdev); +EXPORT_SYMBOL(sound_alloc_timerdev); +EXPORT_SYMBOL(sound_alloc_synthdev); +EXPORT_SYMBOL(sound_unload_audiodev); +EXPORT_SYMBOL(sound_unload_mididev); +EXPORT_SYMBOL(sound_unload_mixerdev); +EXPORT_SYMBOL(sound_unload_timerdev); +EXPORT_SYMBOL(sound_unload_synthdev); + +EXPORT_SYMBOL(load_mixer_volumes); + +EXPORT_SYMBOL(conf_printf); +EXPORT_SYMBOL(conf_printf2); + +MODULE_DESCRIPTION("OSS Sound subsystem"); +MODULE_AUTHOR("Hannu Savolainen, et al."); diff --git a/sound/oss/sound_timer.c b/sound/oss/sound_timer.c new file mode 100644 index 000000000000..bc2777dd2ef9 --- /dev/null +++ b/sound/oss/sound_timer.c @@ -0,0 +1,323 @@ +/* + * sound/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 +#include + +#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 spinlock_t 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; +} + +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); +} + +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; +} diff --git a/sound/oss/soundcard.c b/sound/oss/soundcard.c new file mode 100644 index 000000000000..de91c90a0112 --- /dev/null +++ b/sound/oss/soundcard.c @@ -0,0 +1,751 @@ +/* + * linux/drivers/sound/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) with + * fixups by C. Scott Ananian + * 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 + +#include "sound_config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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[1024]; +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_simple *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 (strcmp(name, mixer_vols[i].name) == 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++; + + strcpy(mixer_vols[n].name, name); + + 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; +} + +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_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.. + */ + + lock_kernel(); + + 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); + } + unlock_kernel(); + 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_dentry->d_inode); + int ret = -EINVAL; + + lock_kernel(); + 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; + } + unlock_kernel(); + 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; + } + 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); + } + if (dev && (dev >= num_mixers || mixer_devs[dev] == NULL)) + return -ENXIO; + + if (!try_module_get(mixer_devs[dev]->owner)) + return -ENXIO; + break; + + case SND_DEV_SEQ: + case SND_DEV_SEQ2: + if ((retval = sequencer_open(dev, file)) < 0) + return retval; + break; + + case SND_DEV_MIDIN: + if ((retval = MIDIbuf_open(dev, file)) < 0) + return retval; + break; + + case SND_DEV_DSP: + case SND_DEV_DSP16: + case SND_DEV_AUDIO: + if ((retval = audio_open(dev, file)) < 0) + return retval; + break; + + default: + printk(KERN_ERR "Invalid minor device %d\n", dev); + return -ENXIO; + } + + return 0; +} + +static int sound_release(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + + lock_kernel(); + 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); + } + unlock_kernel(); + + 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 int sound_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int len = 0, dtype; + int dev = iminor(inode); + 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); + + 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: + return sound_mixer_ioctl(audio_devs[dev >> 4]->mixer_dev, + cmd, p); + + default: + return sound_mixer_ioctl(dev >> 4, cmd, p); + } + } + switch (dev & 0x0f) { + case SND_DEV_CTL: + if (cmd == SOUND_MIXER_GETLEVELS) + return get_mixer_levels(p); + if (cmd == SOUND_MIXER_SETLEVELS) + return set_mixer_levels(p); + return sound_mixer_ioctl(dev >> 4, cmd, p); + + case SND_DEV_SEQ: + case SND_DEV_SEQ2: + return sequencer_ioctl(dev, file, cmd, p); + + case SND_DEV_DSP: + case SND_DEV_DSP16: + case SND_DEV_AUDIO: + return audio_ioctl(dev, file, cmd, p); + break; + + case SND_DEV_MIDIN: + return MIDIbuf_ioctl(dev, file, cmd, p); + break; + + } + return -EINVAL; +} + +static unsigned int sound_poll(struct file *file, poll_table * wait) +{ + struct inode *inode = file->f_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_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; + } + lock_kernel(); + 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"); + unlock_kernel(); + return -EINVAL; + } + + if (dmap == NULL) { + printk(KERN_ERR "Sound: mmap() error. dmap == NULL\n"); + unlock_kernel(); + return -EIO; + } + if (dmap->raw_buf == NULL) { + printk(KERN_ERR "Sound: mmap() called when raw_buf == NULL\n"); + unlock_kernel(); + return -EIO; + } + if (dmap->mapping_flags) { + printk(KERN_ERR "Sound: mmap() called twice for the same DMA buffer\n"); + unlock_kernel(); + return -EIO; + } + if (vma->vm_pgoff != 0) { + printk(KERN_ERR "Sound: mmap() offset must be 0.\n"); + unlock_kernel(); + 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)) { + unlock_kernel(); + 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); + unlock_kernel(); + return 0; +} + +struct file_operations oss_sound_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = sound_read, + .write = sound_write, + .poll = sound_poll, + .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; +} + + +/* These device names follow the official Linux device list, + * Documentation/devices.txt. Let us know if there are other + * common names we should support for compatibility. + * Only those devices not created by the generic code in sound_core.c are + * registered here. + */ +static const struct { + unsigned short minor; + char *name; + umode_t mode; + int *num; +} dev_list[] = { /* list of minor devices */ +/* seems to be some confusion here -- this device is not in the device list */ + {SND_DEV_DSP16, "dspW", S_IWUGO | S_IRUSR | S_IRGRP, + &num_audiodevs}, + {SND_DEV_AUDIO, "audio", S_IWUGO | S_IRUSR | S_IRGRP, + &num_audiodevs}, +}; + +static int dmabuf; +static int dmabug; + +module_param(dmabuf, int, 0444); +module_param(dmabug, int, 0444); + +static int __init oss_init(void) +{ + int err; + int i, j; + + /* drag in sound_syms.o */ + { + extern char sound_syms_symbol; + sound_syms_symbol = 0; + } + +#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 < sizeof (dev_list) / sizeof *dev_list; i++) { + devfs_mk_cdev(MKDEV(SOUND_MAJOR, dev_list[i].minor), + S_IFCHR | dev_list[i].mode, + "sound/%s", dev_list[i].name); + class_simple_device_add(sound_class, + MKDEV(SOUND_MAJOR, dev_list[i].minor), + NULL, "%s", dev_list[i].name); + + if (!dev_list[i].num) + continue; + + for (j = 1; j < *dev_list[i].num; j++) { + devfs_mk_cdev(MKDEV(SOUND_MAJOR, + dev_list[i].minor + (j*0x10)), + S_IFCHR | dev_list[i].mode, + "sound/%s%d", dev_list[i].name, j); + class_simple_device_add(sound_class, + MKDEV(SOUND_MAJOR, dev_list[i].minor + (j*0x10)), + NULL, + "%s%d", dev_list[i].name, j); + } + } + + if (sound_nblocks >= 1024) + 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 < sizeof (dev_list) / sizeof *dev_list; i++) { + devfs_remove("sound/%s", dev_list[i].name); + class_simple_device_remove(MKDEV(SOUND_MAJOR, dev_list[i].minor)); + if (!dev_list[i].num) + continue; + for (j = 1; j < *dev_list[i].num; j++) { + devfs_remove("sound/%s%d", dev_list[i].name, j); + class_simple_device_remove(MKDEV(SOUND_MAJOR, dev_list[i].minor + (j*0x10))); + } + } + + 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"); + + +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; +} + +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; +} + +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; +} + +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; +} + +static void do_sequencer_timer(unsigned long dummy) +{ + sequencer_timer(0); +} + + +static struct timer_list seq_timer = + TIMER_INITIALIZER(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 +} + +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 +} diff --git a/sound/oss/soundvers.h b/sound/oss/soundvers.h new file mode 100644 index 000000000000..e9084d2f46a9 --- /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/sscape.c b/sound/oss/sscape.c new file mode 100644 index 000000000000..50ca64629450 --- /dev/null +++ b/sound/oss/sscape.c @@ -0,0 +1,1485 @@ +/* + * sound/sscape.c + * + * Low level driver for Ensoniq SoundScape + * + * + * 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) + * Sergey Smitienko : ensoniq p'n'p support + * Christoph Hellwig : adapted to module_init/module_exit + * Bartlomiej Zolnierkiewicz : added __init to attach_sscape() + * Chris Rankin : Specify that this module owns the coprocessor + * Arnaldo C. de Melo : added missing restore_flags in sscape_pnp_upload_file + */ + +#include +#include + +#include "sound_config.h" +#include "sound_firmware.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coproc.h" + +#include "ad1848.h" +#include "mpu401.h" + +/* + * I/O ports + */ +#define MIDI_DATA 0 +#define MIDI_CTRL 1 +#define HOST_CTRL 2 +#define TX_READY 0x02 +#define RX_READY 0x01 +#define HOST_DATA 3 +#define ODIE_ADDR 4 +#define ODIE_DATA 5 + +/* + * Indirect registers + */ + +#define GA_INTSTAT_REG 0 +#define GA_INTENA_REG 1 +#define GA_DMAA_REG 2 +#define GA_DMAB_REG 3 +#define GA_INTCFG_REG 4 +#define GA_DMACFG_REG 5 +#define GA_CDCFG_REG 6 +#define GA_SMCFGA_REG 7 +#define GA_SMCFGB_REG 8 +#define GA_HMCTL_REG 9 + +/* + * DMA channel identifiers (A and B) + */ + +#define SSCAPE_DMA_A 0 +#define SSCAPE_DMA_B 1 + +#define PORT(name) (devc->base+name) + +/* + * Host commands recognized by the OBP microcode + */ + +#define CMD_GEN_HOST_ACK 0x80 +#define CMD_GEN_MPU_ACK 0x81 +#define CMD_GET_BOARD_TYPE 0x82 +#define CMD_SET_CONTROL 0x88 /* Old firmware only */ +#define CMD_GET_CONTROL 0x89 /* Old firmware only */ +#define CTL_MASTER_VOL 0 +#define CTL_MIC_MODE 2 +#define CTL_SYNTH_VOL 4 +#define CTL_WAVE_VOL 7 +#define CMD_SET_EXTMIDI 0x8a +#define CMD_GET_EXTMIDI 0x8b +#define CMD_SET_MT32 0x8c +#define CMD_GET_MT32 0x8d + +#define CMD_ACK 0x80 + +#define IC_ODIE 1 +#define IC_OPUS 2 + +typedef struct sscape_info +{ + int base, irq, dma; + + int codec, codec_irq; /* required to setup pnp cards*/ + int codec_type; + int ic_type; + char* raw_buf; + unsigned long raw_buf_phys; + int buffsize; /* -------------------------- */ + spinlock_t lock; + int ok; /* Properly detected */ + int failed; + int dma_allocated; + int codec_audiodev; + int opened; + int *osp; + int my_audiodev; +} sscape_info; + +static struct sscape_info adev_info = { + 0 +}; + +static struct sscape_info *devc = &adev_info; +static int sscape_mididev = -1; + +/* Some older cards have assigned interrupt bits differently than new ones */ +static char valid_interrupts_old[] = { + 9, 7, 5, 15 +}; + +static char valid_interrupts_new[] = { + 9, 5, 7, 10 +}; + +static char *valid_interrupts = valid_interrupts_new; + +/* + * See the bottom of the driver. This can be set by spea =0/1. + */ + +#ifdef REVEAL_SPEA +static char old_hardware = 1; +#else +static char old_hardware; +#endif + +static void sleep(unsigned howlong) +{ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(howlong); +} + +static unsigned char sscape_read(struct sscape_info *devc, int reg) +{ + unsigned long flags; + unsigned char val; + + spin_lock_irqsave(&devc->lock,flags); + outb(reg, PORT(ODIE_ADDR)); + val = inb(PORT(ODIE_DATA)); + spin_unlock_irqrestore(&devc->lock,flags); + return val; +} + +static void __sscape_write(int reg, int data) +{ + outb(reg, PORT(ODIE_ADDR)); + outb(data, PORT(ODIE_DATA)); +} + +static void sscape_write(struct sscape_info *devc, int reg, int data) +{ + unsigned long flags; + + spin_lock_irqsave(&devc->lock,flags); + __sscape_write(reg, data); + spin_unlock_irqrestore(&devc->lock,flags); +} + +static unsigned char sscape_pnp_read_codec(sscape_info* devc, unsigned char reg) +{ + unsigned char res; + unsigned long flags; + + spin_lock_irqsave(&devc->lock,flags); + outb( reg, devc -> codec); + res = inb (devc -> codec + 1); + spin_unlock_irqrestore(&devc->lock,flags); + return res; + +} + +static void sscape_pnp_write_codec(sscape_info* devc, unsigned char reg, unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&devc->lock,flags); + outb( reg, devc -> codec); + outb( data, devc -> codec + 1); + spin_unlock_irqrestore(&devc->lock,flags); +} + +static void host_open(struct sscape_info *devc) +{ + outb((0x00), PORT(HOST_CTRL)); /* Put the board to the host mode */ +} + +static void host_close(struct sscape_info *devc) +{ + outb((0x03), PORT(HOST_CTRL)); /* Put the board to the MIDI mode */ +} + +static int host_write(struct sscape_info *devc, unsigned char *data, int count) +{ + unsigned long flags; + int i, timeout_val; + + spin_lock_irqsave(&devc->lock,flags); + /* + * Send the command and data bytes + */ + + for (i = 0; i < count; i++) + { + for (timeout_val = 10000; timeout_val > 0; timeout_val--) + if (inb(PORT(HOST_CTRL)) & TX_READY) + break; + + if (timeout_val <= 0) + { + spin_unlock_irqrestore(&devc->lock,flags); + return 0; + } + outb(data[i], PORT(HOST_DATA)); + } + spin_unlock_irqrestore(&devc->lock,flags); + return 1; +} + +static int host_read(struct sscape_info *devc) +{ + unsigned long flags; + int timeout_val; + unsigned char data; + + spin_lock_irqsave(&devc->lock,flags); + /* + * Read a byte + */ + + for (timeout_val = 10000; timeout_val > 0; timeout_val--) + if (inb(PORT(HOST_CTRL)) & RX_READY) + break; + + if (timeout_val <= 0) + { + spin_unlock_irqrestore(&devc->lock,flags); + return -1; + } + data = inb(PORT(HOST_DATA)); + spin_unlock_irqrestore(&devc->lock,flags); + return data; +} + +#if 0 /* unused */ +static int host_command1(struct sscape_info *devc, int cmd) +{ + unsigned char buf[10]; + buf[0] = (unsigned char) (cmd & 0xff); + return host_write(devc, buf, 1); +} +#endif /* unused */ + + +static int host_command2(struct sscape_info *devc, int cmd, int parm1) +{ + unsigned char buf[10]; + + buf[0] = (unsigned char) (cmd & 0xff); + buf[1] = (unsigned char) (parm1 & 0xff); + + return host_write(devc, buf, 2); +} + +static int host_command3(struct sscape_info *devc, int cmd, int parm1, int parm2) +{ + unsigned char buf[10]; + + buf[0] = (unsigned char) (cmd & 0xff); + buf[1] = (unsigned char) (parm1 & 0xff); + buf[2] = (unsigned char) (parm2 & 0xff); + return host_write(devc, buf, 3); +} + +static void set_mt32(struct sscape_info *devc, int value) +{ + host_open(devc); + host_command2(devc, CMD_SET_MT32, value ? 1 : 0); + if (host_read(devc) != CMD_ACK) + { + /* printk( "SNDSCAPE: Setting MT32 mode failed\n"); */ + } + host_close(devc); +} + +static void set_control(struct sscape_info *devc, int ctrl, int value) +{ + host_open(devc); + host_command3(devc, CMD_SET_CONTROL, ctrl, value); + if (host_read(devc) != CMD_ACK) + { + /* printk( "SNDSCAPE: Setting control (%d) failed\n", ctrl); */ + } + host_close(devc); +} + +static void do_dma(struct sscape_info *devc, int dma_chan, unsigned long buf, int blk_size, int mode) +{ + unsigned char temp; + + if (dma_chan != SSCAPE_DMA_A) + { + printk(KERN_WARNING "soundscape: Tried to use DMA channel != A. Why?\n"); + return; + } + audio_devs[devc->codec_audiodev]->flags &= ~DMA_AUTOMODE; + DMAbuf_start_dma(devc->codec_audiodev, buf, blk_size, mode); + audio_devs[devc->codec_audiodev]->flags |= DMA_AUTOMODE; + + temp = devc->dma << 4; /* Setup DMA channel select bits */ + if (devc->dma <= 3) + temp |= 0x80; /* 8 bit DMA channel */ + + temp |= 1; /* Trigger DMA */ + sscape_write(devc, GA_DMAA_REG, temp); + temp &= 0xfe; /* Clear DMA trigger */ + sscape_write(devc, GA_DMAA_REG, temp); +} + +static int verify_mpu(struct sscape_info *devc) +{ + /* + * The SoundScape board could be in three modes (MPU, 8250 and host). + * If the card is not in the MPU mode, enabling the MPU driver will + * cause infinite loop (the driver believes that there is always some + * received data in the buffer. + * + * Detect this by looking if there are more than 10 received MIDI bytes + * (0x00) in the buffer. + */ + + int i; + + for (i = 0; i < 10; i++) + { + if (inb(devc->base + HOST_CTRL) & 0x80) + return 1; + + if (inb(devc->base) != 0x00) + return 1; + } + printk(KERN_WARNING "SoundScape: The device is not in the MPU-401 mode\n"); + return 0; +} + +static int sscape_coproc_open(void *dev_info, int sub_device) +{ + if (sub_device == COPR_MIDI) + { + set_mt32(devc, 0); + if (!verify_mpu(devc)) + return -EIO; + } + return 0; +} + +static void sscape_coproc_close(void *dev_info, int sub_device) +{ + struct sscape_info *devc = dev_info; + unsigned long flags; + + spin_lock_irqsave(&devc->lock,flags); + if (devc->dma_allocated) + { + __sscape_write(GA_DMAA_REG, 0x20); /* DMA channel disabled */ + devc->dma_allocated = 0; + } + spin_unlock_irqrestore(&devc->lock,flags); + return; +} + +static void sscape_coproc_reset(void *dev_info) +{ +} + +static int sscape_download_boot(struct sscape_info *devc, unsigned char *block, int size, int flag) +{ + unsigned long flags; + unsigned char temp; + volatile int done, timeout_val; + static unsigned char codec_dma_bits; + + if (flag & CPF_FIRST) + { + /* + * First block. Have to allocate DMA and to reset the board + * before continuing. + */ + + spin_lock_irqsave(&devc->lock,flags); + codec_dma_bits = sscape_read(devc, GA_CDCFG_REG); + + if (devc->dma_allocated == 0) + devc->dma_allocated = 1; + + spin_unlock_irqrestore(&devc->lock,flags); + + sscape_write(devc, GA_HMCTL_REG, + (temp = sscape_read(devc, GA_HMCTL_REG)) & 0x3f); /*Reset */ + + for (timeout_val = 10000; timeout_val > 0; timeout_val--) + sscape_read(devc, GA_HMCTL_REG); /* Delay */ + + /* Take board out of reset */ + sscape_write(devc, GA_HMCTL_REG, + (temp = sscape_read(devc, GA_HMCTL_REG)) | 0x80); + } + /* + * Transfer one code block using DMA + */ + if (audio_devs[devc->codec_audiodev]->dmap_out->raw_buf == NULL) + { + printk(KERN_WARNING "soundscape: DMA buffer not available\n"); + return 0; + } + memcpy(audio_devs[devc->codec_audiodev]->dmap_out->raw_buf, block, size); + + spin_lock_irqsave(&devc->lock,flags); + + /******** INTERRUPTS DISABLED NOW ********/ + + do_dma(devc, SSCAPE_DMA_A, + audio_devs[devc->codec_audiodev]->dmap_out->raw_buf_phys, + size, DMA_MODE_WRITE); + + /* + * Wait until transfer completes. + */ + + done = 0; + timeout_val = 30; + while (!done && timeout_val-- > 0) + { + int resid; + + if (HZ / 50) + sleep(HZ / 50); + clear_dma_ff(devc->dma); + if ((resid = get_dma_residue(devc->dma)) == 0) + done = 1; + } + + spin_unlock_irqrestore(&devc->lock,flags); + if (!done) + return 0; + + if (flag & CPF_LAST) + { + /* + * Take the board out of reset + */ + outb((0x00), PORT(HOST_CTRL)); + outb((0x00), PORT(MIDI_CTRL)); + + temp = sscape_read(devc, GA_HMCTL_REG); + temp |= 0x40; + sscape_write(devc, GA_HMCTL_REG, temp); /* Kickstart the board */ + + /* + * Wait until the ODB wakes up + */ + spin_lock_irqsave(&devc->lock,flags); + done = 0; + timeout_val = 5 * HZ; + while (!done && timeout_val-- > 0) + { + unsigned char x; + + sleep(1); + x = inb(PORT(HOST_DATA)); + if (x == 0xff || x == 0xfe) /* OBP startup acknowledge */ + { + DDB(printk("Soundscape: Acknowledge = %x\n", x)); + done = 1; + } + } + sscape_write(devc, GA_CDCFG_REG, codec_dma_bits); + + spin_unlock_irqrestore(&devc->lock,flags); + if (!done) + { + printk(KERN_ERR "soundscape: The OBP didn't respond after code download\n"); + return 0; + } + spin_lock_irqsave(&devc->lock,flags); + done = 0; + timeout_val = 5 * HZ; + while (!done && timeout_val-- > 0) + { + sleep(1); + if (inb(PORT(HOST_DATA)) == 0xfe) /* Host startup acknowledge */ + done = 1; + } + spin_unlock_irqrestore(&devc->lock,flags); + if (!done) + { + printk(KERN_ERR "soundscape: OBP Initialization failed.\n"); + return 0; + } + printk(KERN_INFO "SoundScape board initialized OK\n"); + set_control(devc, CTL_MASTER_VOL, 100); + set_control(devc, CTL_SYNTH_VOL, 100); + +#ifdef SSCAPE_DEBUG3 + /* + * Temporary debugging aid. Print contents of the registers after + * downloading the code. + */ + { + int i; + + for (i = 0; i < 13; i++) + printk("I%d = %02x (new value)\n", i, sscape_read(devc, i)); + } +#endif + + } + return 1; +} + +static int download_boot_block(void *dev_info, copr_buffer * buf) +{ + if (buf->len <= 0 || buf->len > sizeof(buf->data)) + return -EINVAL; + + if (!sscape_download_boot(devc, buf->data, buf->len, buf->flags)) + { + printk(KERN_ERR "soundscape: Unable to load microcode block to the OBP.\n"); + return -EIO; + } + return 0; +} + +static int sscape_coproc_ioctl(void *dev_info, unsigned int cmd, void __user *arg, int local) +{ + copr_buffer *buf; + int err; + + switch (cmd) + { + case SNDCTL_COPR_RESET: + sscape_coproc_reset(dev_info); + return 0; + + case SNDCTL_COPR_LOAD: + buf = (copr_buffer *) 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; + + default: + return -EINVAL; + } +} + +static coproc_operations sscape_coproc_operations = +{ + "SoundScape M68K", + THIS_MODULE, + sscape_coproc_open, + sscape_coproc_close, + sscape_coproc_ioctl, + sscape_coproc_reset, + &adev_info +}; + +static struct resource *sscape_ports; +static int sscape_is_pnp; + +static void __init attach_sscape(struct address_info *hw_config) +{ +#ifndef SSCAPE_REGS + /* + * Config register values for Spea/V7 Media FX and Ensoniq S-2000. + * These values are card + * dependent. If you have another SoundScape based card, you have to + * find the correct values. Do the following: + * - Compile this driver with SSCAPE_DEBUG1 defined. + * - Shut down and power off your machine. + * - Boot with DOS so that the SSINIT.EXE program is run. + * - Warm boot to {Linux|SYSV|BSD} and write down the lines displayed + * when detecting the SoundScape. + * - Modify the following list to use the values printed during boot. + * Undefine the SSCAPE_DEBUG1 + */ +#define SSCAPE_REGS { \ +/* I0 */ 0x00, \ +/* I1 */ 0xf0, /* Note! Ignored. Set always to 0xf0 */ \ +/* I2 */ 0x20, /* Note! Ignored. Set always to 0x20 */ \ +/* I3 */ 0x20, /* Note! Ignored. Set always to 0x20 */ \ +/* I4 */ 0xf5, /* Ignored */ \ +/* I5 */ 0x10, \ +/* I6 */ 0x00, \ +/* I7 */ 0x2e, /* I7 MEM config A. Likely to vary between models */ \ +/* I8 */ 0x00, /* I8 MEM config B. Likely to vary between models */ \ +/* I9 */ 0x40 /* Ignored */ \ + } +#endif + + unsigned long flags; + static unsigned char regs[10] = SSCAPE_REGS; + + int i, irq_bits = 0xff; + + if (old_hardware) + { + valid_interrupts = valid_interrupts_old; + conf_printf("Ensoniq SoundScape (old)", hw_config); + } + else + conf_printf("Ensoniq SoundScape", hw_config); + + for (i = 0; i < 4; i++) + { + if (hw_config->irq == valid_interrupts[i]) + { + irq_bits = i; + break; + } + } + if (hw_config->irq > 15 || (regs[4] = irq_bits == 0xff)) + { + printk(KERN_ERR "Invalid IRQ%d\n", hw_config->irq); + release_region(devc->base, 2); + release_region(devc->base + 2, 6); + if (sscape_is_pnp) + release_region(devc->codec, 2); + return; + } + + if (!sscape_is_pnp) { + + spin_lock_irqsave(&devc->lock,flags); + /* Host interrupt enable */ + sscape_write(devc, 1, 0xf0); /* All interrupts enabled */ + /* DMA A status/trigger register */ + sscape_write(devc, 2, 0x20); /* DMA channel disabled */ + /* DMA B status/trigger register */ + sscape_write(devc, 3, 0x20); /* DMA channel disabled */ + /* Host interrupt config reg */ + sscape_write(devc, 4, 0xf0 | (irq_bits << 2) | irq_bits); + /* Don't destroy CD-ROM DMA config bits (0xc0) */ + sscape_write(devc, 5, (regs[5] & 0x3f) | (sscape_read(devc, 5) & 0xc0)); + /* CD-ROM config (WSS codec actually) */ + sscape_write(devc, 6, regs[6]); + sscape_write(devc, 7, regs[7]); + sscape_write(devc, 8, regs[8]); + /* Master control reg. Don't modify CR-ROM bits. Disable SB emul */ + sscape_write(devc, 9, (sscape_read(devc, 9) & 0xf0) | 0x08); + spin_unlock_irqrestore(&devc->lock,flags); + } +#ifdef SSCAPE_DEBUG2 + /* + * Temporary debugging aid. Print contents of the registers after + * changing them. + */ + { + int i; + + for (i = 0; i < 13; i++) + printk("I%d = %02x (new value)\n", i, sscape_read(devc, i)); + } +#endif + + if (probe_mpu401(hw_config, sscape_ports)) + hw_config->always_detect = 1; + hw_config->name = "SoundScape"; + + hw_config->irq *= -1; /* Negative value signals IRQ sharing */ + attach_mpu401(hw_config, THIS_MODULE); + hw_config->irq *= -1; /* Restore it */ + + if (hw_config->slots[1] != -1) /* The MPU driver installed itself */ + { + sscape_mididev = hw_config->slots[1]; + midi_devs[hw_config->slots[1]]->coproc = &sscape_coproc_operations; + } + sscape_write(devc, GA_INTENA_REG, 0x80); /* Master IRQ enable */ + devc->ok = 1; + devc->failed = 0; +} + +static int detect_ga(sscape_info * devc) +{ + unsigned char save; + + DDB(printk("Entered Soundscape detect_ga(%x)\n", devc->base)); + + /* + * First check that the address register of "ODIE" is + * there and that it has exactly 4 writable bits. + * First 4 bits + */ + + if ((save = inb(PORT(ODIE_ADDR))) & 0xf0) + { + DDB(printk("soundscape: Detect error A\n")); + return 0; + } + outb((0x00), PORT(ODIE_ADDR)); + if (inb(PORT(ODIE_ADDR)) != 0x00) + { + DDB(printk("soundscape: Detect error B\n")); + return 0; + } + outb((0xff), PORT(ODIE_ADDR)); + if (inb(PORT(ODIE_ADDR)) != 0x0f) + { + DDB(printk("soundscape: Detect error C\n")); + return 0; + } + outb((save), PORT(ODIE_ADDR)); + + /* + * Now verify that some indirect registers return zero on some bits. + * This may break the driver with some future revisions of "ODIE" but... + */ + + if (sscape_read(devc, 0) & 0x0c) + { + DDB(printk("soundscape: Detect error D (%x)\n", sscape_read(devc, 0))); + return 0; + } + if (sscape_read(devc, 1) & 0x0f) + { + DDB(printk("soundscape: Detect error E\n")); + return 0; + } + if (sscape_read(devc, 5) & 0x0f) + { + DDB(printk("soundscape: Detect error F\n")); + return 0; + } + return 1; +} + +static int sscape_read_host_ctrl(sscape_info* devc) +{ + return host_read(devc); +} + +static void sscape_write_host_ctrl2(sscape_info *devc, int a, int b) +{ + host_command2(devc, a, b); +} + +static int sscape_alloc_dma(sscape_info *devc) +{ + char *start_addr, *end_addr; + int dma_pagesize; + int sz, size; + struct page *page; + + if (devc->raw_buf != NULL) return 0; /* Already done */ + dma_pagesize = (devc->dma < 4) ? (64 * 1024) : (128 * 1024); + devc->raw_buf = NULL; + devc->buffsize = 8192*4; + if (devc->buffsize > dma_pagesize) devc->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 && devc->buffsize > PAGE_SIZE) { + for (sz = 0, size = PAGE_SIZE; size < devc->buffsize; sz++, size <<= 1); + devc->buffsize = PAGE_SIZE * (1 << sz); + start_addr = (char *) __get_free_pages(GFP_ATOMIC|GFP_DMA, sz); + if (start_addr == NULL) devc->buffsize /= 2; + } + + if (start_addr == NULL) { + printk(KERN_ERR "sscape pnp init error: Couldn't allocate DMA buffer\n"); + return 0; + } else { + /* make some checks */ + end_addr = start_addr + devc->buffsize - 1; + /* 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 "sscape pnp: Got invalid address 0x%lx for %db DMA-buffer\n", (long) start_addr, devc->buffsize); + return 0; + } + } + devc->raw_buf = start_addr; + devc->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 1; +} + +static void sscape_free_dma(sscape_info *devc) +{ + int sz, size; + unsigned long start_addr, end_addr; + struct page *page; + + if (devc->raw_buf == NULL) return; + for (sz = 0, size = PAGE_SIZE; size < devc->buffsize; sz++, size <<= 1); + start_addr = (unsigned long) devc->raw_buf; + end_addr = start_addr + devc->buffsize; + + for (page = virt_to_page(start_addr); page <= virt_to_page(end_addr); page++) + ClearPageReserved(page); + + free_pages((unsigned long) devc->raw_buf, sz); + devc->raw_buf = NULL; +} + +/* Intel version !!!!!!!!! */ + +static int sscape_start_dma(int chan, unsigned long physaddr, int count, int dma_mode) +{ + unsigned long flags; + + 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 sscape_pnp_start_dma(sscape_info* devc, int arg ) +{ + int reg; + if (arg == 0) reg = 2; + else reg = 3; + + sscape_write(devc, reg, sscape_read( devc, reg) | 0x01); + sscape_write(devc, reg, sscape_read( devc, reg) & 0xFE); +} + +static int sscape_pnp_wait_dma (sscape_info* devc, int arg ) +{ + int reg; + unsigned long i; + unsigned char d; + + if (arg == 0) reg = 2; + else reg = 3; + + sleep ( 1 ); + i = 0; + do { + d = sscape_read(devc, reg) & 1; + if ( d == 1) break; + i++; + } while (i < 500000); + d = sscape_read(devc, reg) & 1; + return d; +} + +static int sscape_pnp_alloc_dma(sscape_info* devc) +{ + /* printk(KERN_INFO "sscape: requesting dma\n"); */ + if (request_dma(devc -> dma, "sscape")) return 0; + /* printk(KERN_INFO "sscape: dma channel allocated\n"); */ + if (!sscape_alloc_dma(devc)) { + free_dma(devc -> dma); + return 0; + }; + return 1; +} + +static void sscape_pnp_free_dma(sscape_info* devc) +{ + sscape_free_dma( devc); + free_dma(devc -> dma ); + /* printk(KERN_INFO "sscape: dma released\n"); */ +} + +static int sscape_pnp_upload_file(sscape_info* devc, char* fn) +{ + int done = 0; + int timeout_val; + char* data,*dt; + int len,l; + unsigned long flags; + + sscape_write( devc, 9, sscape_read(devc, 9 ) & 0x3F ); + sscape_write( devc, 2, (devc -> dma << 4) | 0x80 ); + sscape_write( devc, 3, 0x20 ); + sscape_write( devc, 9, sscape_read( devc, 9 ) | 0x80 ); + + len = mod_firmware_load(fn, &data); + if (len == 0) { + printk(KERN_ERR "sscape: file not found: %s\n", fn); + return 0; + } + dt = data; + spin_lock_irqsave(&devc->lock,flags); + while ( len > 0 ) { + if (len > devc -> buffsize) l = devc->buffsize; + else l = len; + len -= l; + memcpy(devc->raw_buf, dt, l); dt += l; + sscape_start_dma(devc->dma, devc->raw_buf_phys, l, 0x48); + sscape_pnp_start_dma ( devc, 0 ); + if (sscape_pnp_wait_dma ( devc, 0 ) == 0) { + spin_unlock_irqrestore(&devc->lock,flags); + return 0; + } + } + + spin_unlock_irqrestore(&devc->lock,flags); + vfree(data); + + outb(0, devc -> base + 2); + outb(0, devc -> base); + + sscape_write ( devc, 9, sscape_read( devc, 9 ) | 0x40); + + timeout_val = 5 * HZ; + while (!done && timeout_val-- > 0) + { + unsigned char x; + sleep(1); + x = inb( devc -> base + 3); + if (x == 0xff || x == 0xfe) /* OBP startup acknowledge */ + { + //printk(KERN_ERR "Soundscape: Acknowledge = %x\n", x); + done = 1; + } + } + timeout_val = 5 * HZ; + done = 0; + while (!done && timeout_val-- > 0) + { + unsigned char x; + sleep(1); + x = inb( devc -> base + 3); + if (x == 0xfe) /* OBP startup acknowledge */ + { + //printk(KERN_ERR "Soundscape: Acknowledge = %x\n", x); + done = 1; + } + } + + if ( !done ) printk(KERN_ERR "soundscape: OBP Initialization failed.\n"); + + sscape_write( devc, 2, devc->ic_type == IC_ODIE ? 0x70 : 0x40); + sscape_write( devc, 3, (devc -> dma << 4) + 0x80); + return 1; +} + +static void __init sscape_pnp_init_hw(sscape_info* devc) +{ + unsigned char midi_irq = 0, sb_irq = 0; + unsigned i; + static char code_file_name[23] = "/sndscape/sndscape.cox"; + + int sscape_sb_enable = 0; + int sscape_joystic_enable = 0x7f; + int sscape_mic_enable = 0; + int sscape_ext_midi = 0; + + if ( !sscape_pnp_alloc_dma(devc) ) { + printk(KERN_ERR "sscape: faild to allocate dma\n"); + return; + } + + for (i = 0; i < 4; i++) { + if ( devc -> irq == valid_interrupts[i] ) + midi_irq = i; + if ( devc -> codec_irq == valid_interrupts[i] ) + sb_irq = i; + } + + sscape_write( devc, 5, 0x50); + sscape_write( devc, 7, 0x2e); + sscape_write( devc, 8, 0x00); + + sscape_write( devc, 2, devc->ic_type == IC_ODIE ? 0x70 : 0x40); + sscape_write( devc, 3, ( devc -> dma << 4) | 0x80); + + if ( sscape_sb_enable ) + sscape_write (devc, 4, 0xF0 | (sb_irq << 2) | midi_irq); + else + sscape_write (devc, 4, 0xF0 | (midi_irq<<2) | midi_irq); + + i = 0x10; //sscape_read(devc, 9) & (devc->ic_type == IC_ODIE ? 0xf0 : 0xc0); + if ( sscape_sb_enable ) + i |= devc->ic_type == IC_ODIE ? 0x05 : 0x07; + if (sscape_joystic_enable) i |= 8; + + sscape_write (devc, 9, i); + sscape_write (devc, 6, 0x80); + sscape_write (devc, 1, 0x80); + + if (devc -> codec_type == 2) { + sscape_pnp_write_codec( devc, 0x0C, 0x50); + sscape_pnp_write_codec( devc, 0x10, sscape_pnp_read_codec( devc, 0x10) & 0x3F); + sscape_pnp_write_codec( devc, 0x11, sscape_pnp_read_codec( devc, 0x11) | 0xC0); + sscape_pnp_write_codec( devc, 29, 0x20); + } + + if (sscape_pnp_upload_file(devc, "/sndscape/scope.cod") == 0 ) { + printk(KERN_ERR "sscape: faild to upload file /sndscape/scope.cod\n"); + sscape_pnp_free_dma(devc); + return; + } + + i = sscape_read_host_ctrl( devc ); + + if ( (i & 0x0F) > 7 ) { + printk(KERN_ERR "sscape: scope.cod faild\n"); + sscape_pnp_free_dma(devc); + return; + } + if ( i & 0x10 ) sscape_write( devc, 7, 0x2F); + code_file_name[21] = (char) ( i & 0x0F) + 0x30; + if (sscape_pnp_upload_file( devc, code_file_name) == 0) { + printk(KERN_ERR "sscape: faild to upload file %s\n", code_file_name); + sscape_pnp_free_dma(devc); + return; + } + + if (devc->ic_type != IC_ODIE) { + sscape_pnp_write_codec( devc, 10, (sscape_pnp_read_codec(devc, 10) & 0x7f) | + ( sscape_mic_enable == 0 ? 0x00 : 0x80) ); + } + sscape_write_host_ctrl2( devc, 0x84, 0x64 ); /* MIDI volume */ + sscape_write_host_ctrl2( devc, 0x86, 0x64 ); /* MIDI volume?? */ + sscape_write_host_ctrl2( devc, 0x8A, sscape_ext_midi); + + sscape_pnp_write_codec ( devc, 6, 0x3f ); //WAV_VOL + sscape_pnp_write_codec ( devc, 7, 0x3f ); //WAV_VOL + sscape_pnp_write_codec ( devc, 2, 0x1F ); //WD_CDXVOLL + sscape_pnp_write_codec ( devc, 3, 0x1F ); //WD_CDXVOLR + + if (devc -> codec_type == 1) { + sscape_pnp_write_codec ( devc, 4, 0x1F ); + sscape_pnp_write_codec ( devc, 5, 0x1F ); + sscape_write_host_ctrl2( devc, 0x88, sscape_mic_enable); + } else { + int t; + sscape_pnp_write_codec ( devc, 0x10, 0x1F << 1); + sscape_pnp_write_codec ( devc, 0x11, 0xC0 | (0x1F << 1)); + + t = sscape_pnp_read_codec( devc, 0x00) & 0xDF; + if ( (sscape_mic_enable == 0)) t |= 0; + else t |= 0x20; + sscape_pnp_write_codec ( devc, 0x00, t); + t = sscape_pnp_read_codec( devc, 0x01) & 0xDF; + if ( (sscape_mic_enable == 0) ) t |= 0; + else t |= 0x20; + sscape_pnp_write_codec ( devc, 0x01, t); + sscape_pnp_write_codec ( devc, 0x40 | 29 , 0x20); + outb(0, devc -> codec); + } + if (devc -> ic_type == IC_OPUS ) { + int i = sscape_read( devc, 9 ); + sscape_write( devc, 9, i | 3 ); + sscape_write( devc, 3, 0x40); + + if (request_region(0x228, 1, "sscape setup junk")) { + outb(0, 0x228); + release_region(0x228,1); + } + sscape_write( devc, 3, (devc -> dma << 4) | 0x80); + sscape_write( devc, 9, i ); + } + + host_close ( devc ); + sscape_pnp_free_dma(devc); +} + +static int __init detect_sscape_pnp(sscape_info* devc) +{ + long i, irq_bits = 0xff; + unsigned int d; + + DDB(printk("Entered detect_sscape_pnp(%x)\n", devc->base)); + + if (!request_region(devc->codec, 2, "sscape codec")) { + printk(KERN_ERR "detect_sscape_pnp: port %x is not free\n", devc->codec); + return 0; + } + + if ((inb(devc->base + 2) & 0x78) != 0) + goto fail; + + d = inb ( devc -> base + 4) & 0xF0; + if (d & 0x80) + goto fail; + + if (d == 0) { + devc->codec_type = 1; + devc->ic_type = IC_ODIE; + } else if ( (d & 0x60) != 0) { + devc->codec_type = 2; + devc->ic_type = IC_OPUS; + } else if ( (d & 0x40) != 0) { /* WTF? */ + devc->codec_type = 2; + devc->ic_type = IC_ODIE; + } else + goto fail; + + sscape_is_pnp = 1; + + outb(0xFA, devc -> base+4); + if ((inb( devc -> base+4) & 0x9F) != 0x0A) + goto fail; + outb(0xFE, devc -> base+4); + if ( (inb(devc -> base+4) & 0x9F) != 0x0E) + goto fail; + if ( (inb(devc -> base+5) & 0x9F) != 0x0E) + goto fail; + + if (devc->codec_type == 2) { + if (devc->codec != devc->base + 8) { + printk("soundscape warning: incorrect codec port specified\n"); + goto fail; + } + d = 0x10 | (sscape_read(devc, 9) & 0xCF); + sscape_write(devc, 9, d); + sscape_write(devc, 6, 0x80); + } else { + //todo: check codec is not base + 8 + } + + d = (sscape_read(devc, 9) & 0x3F) | 0xC0; + sscape_write(devc, 9, d); + + for (i = 0; i < 550000; i++) + if ( !(inb(devc -> codec) & 0x80) ) break; + + d = inb(devc -> codec); + if (d & 0x80) + goto fail; + if ( inb(devc -> codec + 2) == 0xFF) + goto fail; + + sscape_write(devc, 9, sscape_read(devc, 9) & 0x3F ); + + d = inb(devc -> codec) & 0x80; + if ( d == 0) { + printk(KERN_INFO "soundscape: hardware detected\n"); + valid_interrupts = valid_interrupts_new; + } else { + printk(KERN_INFO "soundscape: board looks like media fx\n"); + valid_interrupts = valid_interrupts_old; + old_hardware = 1; + } + + sscape_write( devc, 9, 0xC0 | (sscape_read(devc, 9) & 0x3F) ); + + for (i = 0; i < 550000; i++) + if ( !(inb(devc -> codec) & 0x80)) + break; + + sscape_pnp_init_hw(devc); + + for (i = 0; i < 4; i++) + { + if (devc->codec_irq == valid_interrupts[i]) { + irq_bits = i; + break; + } + } + sscape_write(devc, GA_INTENA_REG, 0x00); + sscape_write(devc, GA_DMACFG_REG, 0x50); + sscape_write(devc, GA_DMAA_REG, 0x70); + sscape_write(devc, GA_DMAB_REG, 0x20); + sscape_write(devc, GA_INTCFG_REG, 0xf0); + sscape_write(devc, GA_CDCFG_REG, 0x89 | (devc->dma << 4) | (irq_bits << 1)); + + sscape_pnp_write_codec( devc, 0, sscape_pnp_read_codec( devc, 0) | 0x20); + sscape_pnp_write_codec( devc, 0, sscape_pnp_read_codec( devc, 1) | 0x20); + + return 1; +fail: + release_region(devc->codec, 2); + return 0; +} + +static int __init probe_sscape(struct address_info *hw_config) +{ + devc->base = hw_config->io_base; + devc->irq = hw_config->irq; + devc->dma = hw_config->dma; + devc->osp = hw_config->osp; + +#ifdef SSCAPE_DEBUG1 + /* + * Temporary debugging aid. Print contents of the registers before + * changing them. + */ + { + int i; + + for (i = 0; i < 13; i++) + printk("I%d = %02x (old value)\n", i, sscape_read(devc, i)); + } +#endif + devc->failed = 1; + + sscape_ports = request_region(devc->base, 2, "mpu401"); + if (!sscape_ports) + return 0; + + if (!request_region(devc->base + 2, 6, "SoundScape")) { + release_region(devc->base, 2); + return 0; + } + + if (!detect_ga(devc)) { + if (detect_sscape_pnp(devc)) + return 1; + release_region(devc->base, 2); + release_region(devc->base + 2, 6); + return 0; + } + + if (old_hardware) /* Check that it's really an old Spea/Reveal card. */ + { + unsigned char tmp; + int cc; + + if (!((tmp = sscape_read(devc, GA_HMCTL_REG)) & 0xc0)) + { + sscape_write(devc, GA_HMCTL_REG, tmp | 0x80); + for (cc = 0; cc < 200000; ++cc) + inb(devc->base + ODIE_ADDR); + } + } + return 1; +} + +static int __init init_ss_ms_sound(struct address_info *hw_config) +{ + int i, irq_bits = 0xff; + int ad_flags = 0; + struct resource *ports; + + if (devc->failed) + { + printk(KERN_ERR "soundscape: Card not detected\n"); + return 0; + } + if (devc->ok == 0) + { + printk(KERN_ERR "soundscape: Invalid initialization order.\n"); + return 0; + } + for (i = 0; i < 4; i++) + { + if (hw_config->irq == valid_interrupts[i]) + { + irq_bits = i; + break; + } + } + if (irq_bits == 0xff) { + printk(KERN_ERR "soundscape: Invalid MSS IRQ%d\n", hw_config->irq); + return 0; + } + + if (old_hardware) + ad_flags = 0x12345677; /* Tell that we may have a CS4248 chip (Spea-V7 Media FX) */ + else if (sscape_is_pnp) + ad_flags = 0x87654321; /* Tell that we have a soundscape pnp with 1845 chip */ + + ports = request_region(hw_config->io_base, 4, "ad1848"); + if (!ports) { + printk(KERN_ERR "soundscape: ports busy\n"); + return 0; + } + + if (!ad1848_detect(ports, &ad_flags, hw_config->osp)) { + release_region(hw_config->io_base, 4); + return 0; + } + + if (!sscape_is_pnp) /*pnp is already setup*/ + { + /* + * Setup the DMA polarity. + */ + sscape_write(devc, GA_DMACFG_REG, 0x50); + + /* + * Take the gate-array off of the DMA channel. + */ + sscape_write(devc, GA_DMAB_REG, 0x20); + + /* + * Init the AD1848 (CD-ROM) config reg. + */ + sscape_write(devc, GA_CDCFG_REG, 0x89 | (hw_config->dma << 4) | (irq_bits << 1)); + } + + if (hw_config->irq == devc->irq) + printk(KERN_WARNING "soundscape: Warning! The WSS mode can't share IRQ with MIDI\n"); + + hw_config->slots[0] = ad1848_init( + sscape_is_pnp ? "SoundScape" : "SoundScape PNP", + ports, + hw_config->irq, + hw_config->dma, + hw_config->dma, + 0, + devc->osp, + THIS_MODULE); + + + if (hw_config->slots[0] != -1) /* The AD1848 driver installed itself */ + { + audio_devs[hw_config->slots[0]]->coproc = &sscape_coproc_operations; + devc->codec_audiodev = hw_config->slots[0]; + devc->my_audiodev = hw_config->slots[0]; + + /* Set proper routings here (what are they) */ + AD1848_REROUTE(SOUND_MIXER_LINE1, SOUND_MIXER_LINE); + } + +#ifdef SSCAPE_DEBUG5 + /* + * Temporary debugging aid. Print contents of the registers + * after the AD1848 device has been initialized. + */ + { + int i; + + for (i = 0; i < 13; i++) + printk("I%d = %02x\n", i, sscape_read(devc, i)); + } +#endif + return 1; +} + +static void __exit unload_sscape(struct address_info *hw_config) +{ + release_region(devc->base + 2, 6); + unload_mpu401(hw_config); + if (sscape_is_pnp) + release_region(devc->codec, 2); +} + +static void __exit unload_ss_ms_sound(struct address_info *hw_config) +{ + ad1848_unload(hw_config->io_base, + hw_config->irq, + devc->dma, + devc->dma, + 0); + sound_unload_audiodev(hw_config->slots[0]); +} + +static struct address_info cfg; +static struct address_info cfg_mpu; + +static int __initdata spea = -1; +static int mss = 0; +static int __initdata dma = -1; +static int __initdata irq = -1; +static int __initdata io = -1; +static int __initdata mpu_irq = -1; +static int __initdata mpu_io = -1; + +module_param(dma, int, 0); +module_param(irq, int, 0); +module_param(io, int, 0); +module_param(spea, int, 0); /* spea=0/1 set the old_hardware */ +module_param(mpu_irq, int, 0); +module_param(mpu_io, int, 0); +module_param(mss, int, 0); + +static int __init init_sscape(void) +{ + printk(KERN_INFO "Soundscape driver Copyright (C) by Hannu Savolainen 1993-1996\n"); + + cfg.irq = irq; + cfg.dma = dma; + cfg.io_base = io; + + cfg_mpu.irq = mpu_irq; + cfg_mpu.io_base = mpu_io; + /* WEH - Try to get right dma channel */ + cfg_mpu.dma = dma; + + devc->codec = cfg.io_base; + devc->codec_irq = cfg.irq; + devc->codec_type = 0; + devc->ic_type = 0; + devc->raw_buf = NULL; + spin_lock_init(&devc->lock); + + if (cfg.dma == -1 || cfg.irq == -1 || cfg.io_base == -1) { + printk(KERN_ERR "DMA, IRQ, and IO port must be specified.\n"); + return -EINVAL; + } + + if (cfg_mpu.irq == -1 && cfg_mpu.io_base != -1) { + printk(KERN_ERR "MPU_IRQ must be specified if MPU_IO is set.\n"); + return -EINVAL; + } + + if(spea != -1) { + old_hardware = spea; + printk(KERN_INFO "Forcing %s hardware support.\n", + spea?"new":"old"); + } + if (probe_sscape(&cfg_mpu) == 0) + return -ENODEV; + + attach_sscape(&cfg_mpu); + + mss = init_ss_ms_sound(&cfg); + + return 0; +} + +static void __exit cleanup_sscape(void) +{ + if (mss) + unload_ss_ms_sound(&cfg); + unload_sscape(&cfg_mpu); +} + +module_init(init_sscape); +module_exit(cleanup_sscape); + +#ifndef MODULE +static int __init setup_sscape(char *str) +{ + /* io, irq, dma, mpu_io, mpu_irq */ + int ints[6]; + + str = get_options(str, ARRAY_SIZE(ints), ints); + + io = ints[1]; + irq = ints[2]; + dma = ints[3]; + mpu_io = ints[4]; + mpu_irq = ints[5]; + + return 1; +} + +__setup("sscape=", setup_sscape); +#endif +MODULE_LICENSE("GPL"); diff --git a/sound/oss/swarm_cs4297a.c b/sound/oss/swarm_cs4297a.c new file mode 100644 index 000000000000..df4d3771fa84 --- /dev/null +++ b/sound/oss/swarm_cs4297a.c @@ -0,0 +1,2742 @@ +/******************************************************************************* +* +* "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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct cs4297a_state; + +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). + +//static unsigned long defaultorder = 3; +//MODULE_PARM(defaultorder, "i"); + +// +// 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_PARM(cs_debuglevel, "i"); +MODULE_PARM(cs_debugmask, "i"); +#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 semaphore open_sem; + struct semaphore open_sem_adc; + struct semaphore open_sem_dac; + mode_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 = kmalloc(dma->ringsz * sizeof(serdma_descr_t), GFP_KERNEL); + if (!dma->descrtab) { + printk(KERN_ERR "cs4297a: kmalloc descrtab failed\n"); + return -1; + } + memset(dma->descrtab, 0, dma->ringsz * sizeof(serdma_descr_t)); + 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 = kmalloc(DMA_BUF_SIZE, GFP_KERNEL); + if (!dma->dma_buf) { + printk(KERN_ERR "cs4297a: kmalloc dma_buf failed\n"); + kfree(dma->descrtab); + return -1; + } + memset(dma->dma_buf, 0, DMA_BUF_SIZE); + 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; idma_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; + int 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, noticable 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")); + + 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")); + 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")); + + 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 inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return mixer_ioctl((struct cs4297a_state *) file->private_data, cmd, + arg); +} + + +// ****************************************************************************************** +// Mixer file operations struct. +// ****************************************************************************************** +static /*const */ struct file_operations cs4297a_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .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 inode *inode, 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: + file->f_flags |= O_NONBLOCK; + 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 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); + down(&s->open_sem_dac); + stop_dac(s); + dealloc_dmabuf(s, &s->dma_dac); + s->open_mode &= ~FMODE_WRITE; + up(&s->open_sem_dac); + wake_up(&s->open_wait_dac); + } + if (file->f_mode & FMODE_READ) { + drain_adc(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem_adc); + stop_adc(s); + dealloc_dmabuf(s, &s->dma_adc); + s->open_mode &= ~FMODE_READ; + up(&s->open_sem_adc); + wake_up(&s->open_wait_adc); + } + return 0; +} + +static int cs4297a_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))) + ; + } + + down(&s->open_sem_dac); + while (s->open_mode & FMODE_WRITE) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem_dac); + return -EBUSY; + } + up(&s->open_sem_dac); + interruptible_sleep_on(&s->open_wait_dac); + + if (signal_pending(current)) { + printk("open - sig pending\n"); + return -ERESTARTSYS; + } + down(&s->open_sem_dac); + } + } + if (file->f_mode & FMODE_READ) { + down(&s->open_sem_adc); + while (s->open_mode & FMODE_READ) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem_adc); + return -EBUSY; + } + up(&s->open_sem_adc); + interruptible_sleep_on(&s->open_wait_adc); + + if (signal_pending(current)) { + printk("open - sig pending\n"); + return -ERESTARTSYS; + } + down(&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; + up(&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; + up(&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); +} + + +// ****************************************************************************************** +// 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, + .ioctl = cs4297a_ioctl, + .mmap = cs4297a_mmap, + .open = cs4297a_open, + .release = cs4297a_release, +}; + +static void cs4297a_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + 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; idma_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 = kmalloc(sizeof(struct cs4297a_state), GFP_KERNEL))) { + CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR + "cs4297a: probe() no memory for state struct.\n")); + return -1; + } + memset(s, 0, sizeof(struct cs4297a_state)); + 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); + init_MUTEX(&s->open_sem_adc); + init_MUTEX(&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 < sizeof(initvol) / sizeof(initvol[0]); 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 000000000000..6afe29b763b7 --- /dev/null +++ b/sound/oss/sys_timer.c @@ -0,0 +1,289 @@ +/* + * sound/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 +#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 struct timer_list def_tmr = TIMER_INITIALIZER(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/trident.c b/sound/oss/trident.c new file mode 100644 index 000000000000..47537f0a5b05 --- /dev/null +++ b/sound/oss/trident.c @@ -0,0 +1,4628 @@ +/* + * OSS driver for Linux 2.[46].x for + * + * Trident 4D-Wave + * SiS 7018 + * ALi 5451 + * Tvia/IGST CyberPro 5050 + * + * Driver: Alan Cox + * + * Built from: + * Low level code: from ALSA + * Framework: Thomas Sailer + * Extended by: Zach Brown + * + * Hacked up by: + * Aaron Holtzman + * Ollie Lho SiS 7018 Audio Core Support + * Ching-Ling Lee ALi 5451 Audio Core Support + * Matt Wu ALi 5451 Audio Core Support + * Peter Wächtler CyberPro5050 support + * Muli Ben-Yehuda + * + * + * 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. + * + * History + * v0.14.10j + * January 3 2004 Eugene Teo + * minor cleanup to use pr_debug instead of TRDBG since it is already + * defined in linux/kernel.h. + * v0.14.10i + * December 29 2003 Muli Ben-Yehuda + * major cleanup for 2.6, fix a few error patch buglets + * with returning without properly cleaning up first, + * get rid of lock_kernel(). + * v0.14.10h + * Sept 10 2002 Pascal Schmidt + * added support for ALi 5451 joystick port + * v0.14.10g + * Sept 05 2002 Alan Cox + * adapt to new pci joystick attachment interface + * v0.14.10f + * July 24 2002 Muli Ben-Yehuda + * patch from Eric Lemar (via Ian Soboroff): in suspend and resume, + * fix wrong cast from pci_dev* to struct trident_card*. + * v0.14.10e + * July 19 2002 Muli Ben-Yehuda + * rewrite the DMA buffer allocation/deallcoation functions, to make it + * modular and fix a bug where we would call free_pages on memory + * obtained with pci_alloc_consistent. Also remove unnecessary #ifdef + * CONFIG_PROC_FS and various other cleanups. + * v0.14.10d + * July 19 2002 Muli Ben-Yehuda + * made several printk(KERN_NOTICE...) into TRDBG(...), to avoid spamming + * my syslog with hundreds of messages. + * v0.14.10c + * July 16 2002 Muli Ben-Yehuda + * Cleaned up Lei Hu's 0.4.10 driver to conform to Documentation/CodingStyle + * and the coding style used in the rest of the file. + * v0.14.10b + * June 23 2002 Muli Ben-Yehuda + * add a missing unlock_set_fmt, remove a superflous lock/unlock pair + * with nothing in between. + * v0.14.10a + * June 21 2002 Muli Ben-Yehuda + * use a debug macro instead of #ifdef CONFIG_DEBUG, trim to 80 columns + * per line, use 'do {} while (0)' in statement macros. + * v0.14.10 + * June 6 2002 Lei Hu + * rewrite the part to read/write registers of audio codec for Ali5451 + * v0.14.9e + * January 2 2002 Vojtech Pavlik added gameport + * support to avoid resource conflict with pcigame.c + * v0.14.9d + * October 8 2001 Arnaldo Carvalho de Melo + * use set_current_state, properly release resources on failure in + * trident_probe, get rid of check_region + * v0.14.9c + * August 10 2001 Peter Wächtler + * added support for Tvia (formerly Integraphics/IGST) CyberPro5050 + * this chip is often found in settop boxes (combined video+audio) + * v0.14.9b + * Switch to static inline not extern inline (gcc 3) + * v0.14.9a + * Aug 6 2001 Alan Cox + * 0.14.9 crashed on rmmod due to a timer/bh left running. Simplified + * the existing logic (the BH doesn't help as ac97 is lock_irqsave) + * and used del_timer_sync to clean up + * Fixed a problem where the ALi change broke my generic card + * v0.14.9 + * Jul 10 2001 Matt Wu + * Add H/W Volume Control + * v0.14.8a + * July 7 2001 Alan Cox + * Moved Matt Wu's ac97 register cache into the card structure + * v0.14.8 + * Apr 30 2001 Matt Wu + * Set EBUF1 and EBUF2 to still mode + * Add dc97/ac97 reset function + * Fix power management: ali_restore_regs + * unreleased + * Mar 09 2001 Matt Wu + * Add cache for ac97 access + * v0.14.7 + * Feb 06 2001 Matt Wu + * Fix ac97 initialization + * Fix bug: an extra tail will be played when playing + * Jan 05 2001 Matt Wu + * Implement multi-channels and S/PDIF in support for ALi 1535+ + * v0.14.6 + * Nov 1 2000 Ching-Ling Lee + * Fix the bug of memory leak when switching 5.1-channels to 2 channels. + * Add lock protection into dynamic changing format of data. + * Oct 18 2000 Ching-Ling Lee + * 5.1-channels support for ALi + * June 28 2000 Ching-Ling Lee + * S/PDIF out/in(playback/record) support for ALi 1535+, using /proc to be selected by user + * Simple Power Management support for ALi + * v0.14.5 May 23 2000 Ollie Lho + * Misc bug fix from the Net + * v0.14.4 May 20 2000 Aaron Holtzman + * Fix kfree'd memory access in release + * Fix race in open while looking for a free virtual channel slot + * remove open_wait wq (which appears to be unused) + * v0.14.3 May 10 2000 Ollie Lho + * fixed a small bug in trident_update_ptr, xmms 1.0.1 no longer uses 100% CPU + * v0.14.2 Mar 29 2000 Ching-Ling Lee + * Add clear to silence advance in trident_update_ptr + * fix invalid data of the end of the sound + * v0.14.1 Mar 24 2000 Ching-Ling Lee + * ALi 5451 support added, playback and recording O.K. + * ALi 5451 originally developed and structured based on sonicvibes, and + * suggested to merge into this file by Alan Cox. + * v0.14 Mar 15 2000 Ollie Lho + * 5.1 channel output support with channel binding. What's the Matrix ? + * v0.13.1 Mar 10 2000 Ollie Lho + * few minor bugs on dual codec support, needs more testing + * v0.13 Mar 03 2000 Ollie Lho + * new pci_* for 2.4 kernel, back ported to 2.2 + * v0.12 Feb 23 2000 Ollie Lho + * Preliminary Recording support + * v0.11.2 Feb 19 2000 Ollie Lho + * removed incomplete full-dulplex support + * v0.11.1 Jan 28 2000 Ollie Lho + * small bug in setting sample rate for 4d-nx (reported by Aaron) + * v0.11 Jan 27 2000 Ollie Lho + * DMA bug, scheduler latency, second try + * v0.10 Jan 24 2000 Ollie Lho + * DMA bug fixed, found kernel scheduling problem + * v0.09 Jan 20 2000 Ollie Lho + * Clean up of channel register access routine (prepare for channel binding) + * v0.08 Jan 14 2000 Ollie Lho + * Isolation of AC97 codec code + * v0.07 Jan 13 2000 Ollie Lho + * Get rid of ugly old low level access routines (e.g. CHRegs.lp****) + * v0.06 Jan 11 2000 Ollie Lho + * Preliminary support for dual (more ?) AC97 codecs + * v0.05 Jan 08 2000 Luca Montecchiani + * adapt to 2.3.x new __setup/__init call + * v0.04 Dec 31 1999 Ollie Lho + * Multiple Open, using Middle Loop Interrupt to smooth playback + * v0.03 Dec 24 1999 Ollie Lho + * mem leak in prog_dmabuf and dealloc_dmabuf removed + * v0.02 Dec 15 1999 Ollie Lho + * SiS 7018 support added, playback O.K. + * v0.01 Alan Cox et. al. + * Initial Release in kernel 2.3.30, does not work + * + * ToDo + * Clean up of low level channel register access code. (done) + * Fix the bug on dma buffer management in update_ptr, read/write, drain_dac (done) + * Dual AC97 codecs support (done) + * Recording support (done) + * Mmap support + * "Channel Binding" ioctl extension (done) + * new pci device driver interface for 2.4 kernel (done) + * + * Lock order (high->low) + * lock - hardware lock + * open_sem - guard opens + * sem - guard dmabuf, write re-entry etc + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_ALPHA_NAUTILUS) || defined(CONFIG_ALPHA_GENERIC) +#include +#endif + +#include "trident.h" + +#define DRIVER_VERSION "0.14.10j-2.6" + +/* magic numbers to protect our data structures */ +#define TRIDENT_CARD_MAGIC 0x5072696E /* "Prin" */ +#define TRIDENT_STATE_MAGIC 0x63657373 /* "cess" */ + +#define TRIDENT_DMA_MASK 0x3fffffff /* DMA buffer mask for pci_alloc_consist */ +#define ALI_DMA_MASK 0x7fffffff /* ALI Tridents have 31-bit DMA. Wow. */ + +#define NR_HW_CH 32 + +/* maximum number of AC97 codecs connected, AC97 2.0 defined 4, but 7018 and 4D-NX only + have 2 SDATA_IN lines (currently) */ +#define NR_AC97 2 + +/* minor number of /dev/swmodem (temporary, experimental) */ +#define SND_DEV_SWMODEM 7 + +static const unsigned ali_multi_channels_5_1[] = { + /*ALI_SURR_LEFT_CHANNEL, ALI_SURR_RIGHT_CHANNEL, */ + ALI_CENTER_CHANNEL, + ALI_LEF_CHANNEL, + ALI_SURR_LEFT_CHANNEL, + ALI_SURR_RIGHT_CHANNEL +}; + +static const unsigned sample_size[] = { 1, 2, 2, 4 }; +static const unsigned sample_shift[] = { 0, 1, 1, 2 }; + +static const char invalid_magic[] = KERN_CRIT "trident: invalid magic value in %s\n"; + +enum { + TRIDENT_4D_DX = 0, + TRIDENT_4D_NX, + SIS_7018, + ALI_5451, + CYBER5050 +}; + +static char *card_names[] = { + "Trident 4DWave DX", + "Trident 4DWave NX", + "SiS 7018 PCI Audio", + "ALi Audio Accelerator", + "Tvia/IGST CyberPro 5050" +}; + +static struct pci_device_id trident_pci_tbl[] = { + {PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_TRIDENT_4DWAVE_DX, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, TRIDENT_4D_DX}, + {PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_TRIDENT_4DWAVE_NX, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, TRIDENT_4D_NX}, + {PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_7018, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, SIS_7018}, + {PCI_VENDOR_ID_ALI, PCI_DEVICE_ID_ALI_5451, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, ALI_5451}, + {PCI_VENDOR_ID_INTERG, PCI_DEVICE_ID_INTERG_5050, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, CYBER5050}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, trident_pci_tbl); + +/* "software" or virtual channel, an instance of opened /dev/dsp */ +struct trident_state { + unsigned int magic; + struct trident_card *card; /* Card info */ + + /* file mode */ + mode_t open_mode; + + /* virtual channel number */ + int virt; + + struct dmabuf { + /* wave sample stuff */ + unsigned int rate; + unsigned char fmt, enable; + + /* hardware channel */ + struct trident_channel *channel; + + /* OSS buffer management stuff */ + void *rawbuf; + dma_addr_t dma_handle; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + + /* our buffer acts like a circular ring */ + unsigned hwptr; /* where dma last started, updated by update_ptr */ + unsigned swptr; /* where driver last clear/filled, updated by read/write */ + int count; /* bytes to be comsumed or been generated by dma machine */ + unsigned total_bytes; /* total bytes dmaed by hardware */ + + unsigned error; /* number of over/underruns */ + /* put process on wait queue when no more space in buffer */ + wait_queue_head_t wait; + + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dmasize; + unsigned fragsamples; + + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned endcleared:1; + unsigned update_flag; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + + } dmabuf; + + /* 5.1 channels */ + struct trident_state *other_states[4]; + int multi_channels_adjust_count; + unsigned chans_num; + unsigned long fmt_flag; + /* Guard against mmap/write/read races */ + struct semaphore sem; + +}; + +/* hardware channels */ +struct trident_channel { + int num; /* channel number */ + u32 lba; /* Loop Begine Address, where dma buffer starts */ + u32 eso; /* End Sample Offset, wehre dma buffer ends */ + /* (in the unit of samples) */ + u32 delta; /* delta value, sample rate / 48k for playback, */ + /* 48k/sample rate for recording */ + u16 attribute; /* control where PCM data go and come */ + u16 fm_vol; + u32 control; /* signed/unsigned, 8/16 bits, mono/stereo */ +}; + +struct trident_pcm_bank_address { + u32 start; + u32 stop; + u32 aint; + u32 aint_en; +}; + +static struct trident_pcm_bank_address bank_a_addrs = { + T4D_START_A, + T4D_STOP_A, + T4D_AINT_A, + T4D_AINTEN_A +}; + +static struct trident_pcm_bank_address bank_b_addrs = { + T4D_START_B, + T4D_STOP_B, + T4D_AINT_B, + T4D_AINTEN_B +}; + +struct trident_pcm_bank { + /* register addresses to control bank operations */ + struct trident_pcm_bank_address *addresses; + /* each bank has 32 channels */ + u32 bitmap; /* channel allocation bitmap */ + struct trident_channel channels[32]; +}; + +struct trident_card { + unsigned int magic; + + /* We keep trident cards in a linked list */ + struct trident_card *next; + + /* single open lock mechanism, only used for recording */ + struct semaphore open_sem; + + /* The trident has a certain amount of cross channel interaction + so we use a single per card lock */ + spinlock_t lock; + + /* PCI device stuff */ + struct pci_dev *pci_dev; + u16 pci_id; + u8 revision; + + /* soundcore stuff */ + int dev_audio; + + /* structures for abstraction of hardware facilities, codecs, */ + /* banks and channels */ + struct ac97_codec *ac97_codec[NR_AC97]; + struct trident_pcm_bank banks[NR_BANKS]; + struct trident_state *states[NR_HW_CH]; + + /* hardware resources */ + unsigned long iobase; + u32 irq; + + /* Function support */ + struct trident_channel *(*alloc_pcm_channel) (struct trident_card *); + struct trident_channel *(*alloc_rec_pcm_channel) (struct trident_card *); + void (*free_pcm_channel) (struct trident_card *, unsigned int chan); + void (*address_interrupt) (struct trident_card *); + + /* Added by Matt Wu 01-05-2001 for spdif in */ + int multi_channel_use_count; + int rec_channel_use_count; + u16 mixer_regs[64][NR_AC97]; /* Made card local by Alan */ + int mixer_regs_ready; + + /* Added for hardware volume control */ + int hwvolctl; + struct timer_list timer; + + /* Game port support */ + struct gameport *gameport; +}; + +enum dmabuf_mode { + DM_PLAYBACK = 0, + DM_RECORD +}; + +/* table to map from CHANNELMASK to channel attribute for SiS 7018 */ +static u16 mask2attr[] = { + PCM_LR, PCM_LR, SURR_LR, CENTER_LFE, + HSET, MIC, MODEM_LINE1, MODEM_LINE2, + I2S_LR, SPDIF_LR +}; + +/* table to map from channel attribute to CHANNELMASK for SiS 7018 */ +static int attr2mask[] = { + DSP_BIND_MODEM1, DSP_BIND_MODEM2, DSP_BIND_FRONT, DSP_BIND_HANDSET, + DSP_BIND_I2S, DSP_BIND_CENTER_LFE, DSP_BIND_SURR, DSP_BIND_SPDIF +}; + +/* Added by Matt Wu 01-05-2001 for spdif in */ +static int ali_close_multi_channels(void); +static void ali_delay(struct trident_card *card, int interval); +static void ali_detect_spdif_rate(struct trident_card *card); + +static void ali_ac97_write(struct ac97_codec *codec, u8 reg, u16 val); +static u16 ali_ac97_read(struct ac97_codec *codec, u8 reg); + +static struct trident_card *devs; + +static void trident_ac97_set(struct ac97_codec *codec, u8 reg, u16 val); +static u16 trident_ac97_get(struct ac97_codec *codec, u8 reg); + +static int trident_open_mixdev(struct inode *inode, struct file *file); +static int trident_ioctl_mixdev(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); + +static void ali_ac97_set(struct trident_card *card, int secondary, u8 reg, u16 val); +static u16 ali_ac97_get(struct trident_card *card, int secondary, u8 reg); +static void ali_set_spdif_out_rate(struct trident_card *card, unsigned int rate); +static void ali_enable_special_channel(struct trident_state *stat); +static struct trident_channel *ali_alloc_rec_pcm_channel(struct trident_card *card); +static struct trident_channel *ali_alloc_pcm_channel(struct trident_card *card); +static void ali_restore_regs(struct trident_card *card); +static void ali_save_regs(struct trident_card *card); +static int trident_suspend(struct pci_dev *dev, pm_message_t unused); +static int trident_resume(struct pci_dev *dev); +static void ali_free_pcm_channel(struct trident_card *card, unsigned int channel); +static int ali_setup_multi_channels(struct trident_card *card, int chan_nums); +static unsigned int ali_get_spdif_in_rate(struct trident_card *card); +static void ali_setup_spdif_in(struct trident_card *card); +static void ali_disable_spdif_in(struct trident_card *card); +static void ali_disable_special_channel(struct trident_card *card, int ch); +static void ali_setup_spdif_out(struct trident_card *card, int flag); +static int ali_write_5_1(struct trident_state *state, + const char __user *buffer, + int cnt_for_multi_channel, unsigned int *copy_count, + unsigned int *state_cnt); +static int ali_allocate_other_states_resources(struct trident_state *state, + int chan_nums); +static void ali_free_other_states_resources(struct trident_state *state); + +/* save registers for ALi Power Management */ +static struct ali_saved_registers { + unsigned long global_regs[ALI_GLOBAL_REGS]; + unsigned long channel_regs[ALI_CHANNELS][ALI_CHANNEL_REGS]; + unsigned mixer_regs[ALI_MIXER_REGS]; +} ali_registers; + +#define seek_offset(dma_ptr, buffer, cnt, offset, copy_count) do { \ + (dma_ptr) += (offset); \ + (buffer) += (offset); \ + (cnt) -= (offset); \ + (copy_count) += (offset); \ +} while (0) + +static inline int lock_set_fmt(struct trident_state* state) +{ + if (test_and_set_bit(0, &state->fmt_flag)) + return -EFAULT; + + return 0; +} + +static inline void unlock_set_fmt(struct trident_state* state) +{ + clear_bit(0, &state->fmt_flag); +} + +static int +trident_enable_loop_interrupts(struct trident_card *card) +{ + u32 global_control; + + global_control = inl(TRID_REG(card, T4D_LFO_GC_CIR)); + + switch (card->pci_id) { + case PCI_DEVICE_ID_SI_7018: + global_control |= (ENDLP_IE | MIDLP_IE | BANK_B_EN); + break; + case PCI_DEVICE_ID_ALI_5451: + case PCI_DEVICE_ID_TRIDENT_4DWAVE_DX: + case PCI_DEVICE_ID_TRIDENT_4DWAVE_NX: + case PCI_DEVICE_ID_INTERG_5050: + global_control |= (ENDLP_IE | MIDLP_IE); + break; + default: + return 0; + } + + outl(global_control, TRID_REG(card, T4D_LFO_GC_CIR)); + + pr_debug("trident: Enable Loop Interrupts, globctl = 0x%08X\n", + inl(TRID_REG(card, T4D_LFO_GC_CIR))); + + return 1; +} + +static int +trident_disable_loop_interrupts(struct trident_card *card) +{ + u32 global_control; + + global_control = inl(TRID_REG(card, T4D_LFO_GC_CIR)); + global_control &= ~(ENDLP_IE | MIDLP_IE); + outl(global_control, TRID_REG(card, T4D_LFO_GC_CIR)); + + pr_debug("trident: Disabled Loop Interrupts, globctl = 0x%08X\n", + global_control); + + return 1; +} + +static void +trident_enable_voice_irq(struct trident_card *card, unsigned int channel) +{ + unsigned int mask = 1 << (channel & 0x1f); + struct trident_pcm_bank *bank = &card->banks[channel >> 5]; + u32 reg, addr = bank->addresses->aint_en; + + reg = inl(TRID_REG(card, addr)); + reg |= mask; + outl(reg, TRID_REG(card, addr)); + +#ifdef DEBUG + reg = inl(TRID_REG(card, addr)); + pr_debug("trident: enabled IRQ on channel %d, %s = 0x%08x(addr:%X)\n", + channel, addr == T4D_AINTEN_B ? "AINTEN_B" : "AINTEN_A", + reg, addr); +#endif /* DEBUG */ +} + +static void +trident_disable_voice_irq(struct trident_card *card, unsigned int channel) +{ + unsigned int mask = 1 << (channel & 0x1f); + struct trident_pcm_bank *bank = &card->banks[channel >> 5]; + u32 reg, addr = bank->addresses->aint_en; + + reg = inl(TRID_REG(card, addr)); + reg &= ~mask; + outl(reg, TRID_REG(card, addr)); + + /* Ack the channel in case the interrupt was set before we disable it. */ + outl(mask, TRID_REG(card, bank->addresses->aint)); + +#ifdef DEBUG + reg = inl(TRID_REG(card, addr)); + pr_debug("trident: disabled IRQ on channel %d, %s = 0x%08x(addr:%X)\n", + channel, addr == T4D_AINTEN_B ? "AINTEN_B" : "AINTEN_A", + reg, addr); +#endif /* DEBUG */ +} + +static void +trident_start_voice(struct trident_card *card, unsigned int channel) +{ + unsigned int mask = 1 << (channel & 0x1f); + struct trident_pcm_bank *bank = &card->banks[channel >> 5]; + u32 addr = bank->addresses->start; + +#ifdef DEBUG + u32 reg; +#endif /* DEBUG */ + + outl(mask, TRID_REG(card, addr)); + +#ifdef DEBUG + reg = inl(TRID_REG(card, addr)); + pr_debug("trident: start voice on channel %d, %s = 0x%08x(addr:%X)\n", + channel, addr == T4D_START_B ? "START_B" : "START_A", + reg, addr); +#endif /* DEBUG */ +} + +static void +trident_stop_voice(struct trident_card *card, unsigned int channel) +{ + unsigned int mask = 1 << (channel & 0x1f); + struct trident_pcm_bank *bank = &card->banks[channel >> 5]; + u32 addr = bank->addresses->stop; + +#ifdef DEBUG + u32 reg; +#endif /* DEBUG */ + + outl(mask, TRID_REG(card, addr)); + +#ifdef DEBUG + reg = inl(TRID_REG(card, addr)); + pr_debug("trident: stop voice on channel %d, %s = 0x%08x(addr:%X)\n", + channel, addr == T4D_STOP_B ? "STOP_B" : "STOP_A", + reg, addr); +#endif /* DEBUG */ +} + +static u32 +trident_get_interrupt_mask(struct trident_card *card, unsigned int channel) +{ + struct trident_pcm_bank *bank = &card->banks[channel]; + u32 addr = bank->addresses->aint; + return inl(TRID_REG(card, addr)); +} + +static int +trident_check_channel_interrupt(struct trident_card *card, unsigned int channel) +{ + unsigned int mask = 1 << (channel & 0x1f); + u32 reg = trident_get_interrupt_mask(card, channel >> 5); + +#ifdef DEBUG + if (reg & mask) + pr_debug("trident: channel %d has interrupt, %s = 0x%08x\n", + channel, reg == T4D_AINT_B ? "AINT_B" : "AINT_A", + reg); +#endif /* DEBUG */ + return (reg & mask) ? 1 : 0; +} + +static void +trident_ack_channel_interrupt(struct trident_card *card, unsigned int channel) +{ + unsigned int mask = 1 << (channel & 0x1f); + struct trident_pcm_bank *bank = &card->banks[channel >> 5]; + u32 reg, addr = bank->addresses->aint; + + reg = inl(TRID_REG(card, addr)); + reg &= mask; + outl(reg, TRID_REG(card, addr)); + +#ifdef DEBUG + reg = inl(TRID_REG(card, T4D_AINT_B)); + pr_debug("trident: Ack channel %d interrupt, AINT_B = 0x%08x\n", + channel, reg); +#endif /* DEBUG */ +} + +static struct trident_channel * +trident_alloc_pcm_channel(struct trident_card *card) +{ + struct trident_pcm_bank *bank; + int idx; + + bank = &card->banks[BANK_B]; + + for (idx = 31; idx >= 0; idx--) { + if (!(bank->bitmap & (1 << idx))) { + struct trident_channel *channel = &bank->channels[idx]; + bank->bitmap |= 1 << idx; + channel->num = idx + 32; + return channel; + } + } + + /* no more free channels available */ + printk(KERN_ERR "trident: no more channels available on Bank B.\n"); + return NULL; +} + +static void +trident_free_pcm_channel(struct trident_card *card, unsigned int channel) +{ + int bank; + unsigned char b; + + if (channel < 31 || channel > 63) + return; + + if (card->pci_id == PCI_DEVICE_ID_TRIDENT_4DWAVE_DX || + card->pci_id == PCI_DEVICE_ID_TRIDENT_4DWAVE_NX) { + b = inb(TRID_REG(card, T4D_REC_CH)); + if ((b & ~0x80) == channel) + outb(0x0, TRID_REG(card, T4D_REC_CH)); + } + + bank = channel >> 5; + channel = channel & 0x1f; + + card->banks[bank].bitmap &= ~(1 << (channel)); +} + +static struct trident_channel * +cyber_alloc_pcm_channel(struct trident_card *card) +{ + struct trident_pcm_bank *bank; + int idx; + + /* The cyberpro 5050 has only 32 voices and one bank */ + /* .. at least they are not documented (if you want to call that + * crap documentation), perhaps broken ? */ + + bank = &card->banks[BANK_A]; + + for (idx = 31; idx >= 0; idx--) { + if (!(bank->bitmap & (1 << idx))) { + struct trident_channel *channel = &bank->channels[idx]; + bank->bitmap |= 1 << idx; + channel->num = idx; + return channel; + } + } + + /* no more free channels available */ + printk(KERN_ERR "cyberpro5050: no more channels available on Bank A.\n"); + return NULL; +} + +static void +cyber_free_pcm_channel(struct trident_card *card, unsigned int channel) +{ + if (channel > 31) + return; + card->banks[BANK_A].bitmap &= ~(1 << (channel)); +} + +static inline void +cyber_outidx(int port, int idx, int data) +{ + outb(idx, port); + outb(data, port + 1); +} + +static inline int +cyber_inidx(int port, int idx) +{ + outb(idx, port); + return inb(port + 1); +} + +static int +cyber_init_ritual(struct trident_card *card) +{ + /* some black magic, taken from SDK samples */ + /* remove this and nothing will work */ + int portDat; + int ret = 0; + unsigned long flags; + + /* + * Keep interrupts off for the configure - we don't want to + * clash with another cyberpro config event + */ + + spin_lock_irqsave(&card->lock, flags); + portDat = cyber_inidx(CYBER_PORT_AUDIO, CYBER_IDX_AUDIO_ENABLE); + /* enable, if it was disabled */ + if ((portDat & CYBER_BMSK_AUENZ) != CYBER_BMSK_AUENZ_ENABLE) { + printk(KERN_INFO "cyberpro5050: enabling audio controller\n"); + cyber_outidx(CYBER_PORT_AUDIO, CYBER_IDX_AUDIO_ENABLE, + portDat | CYBER_BMSK_AUENZ_ENABLE); + /* check again if hardware is enabled now */ + portDat = cyber_inidx(CYBER_PORT_AUDIO, CYBER_IDX_AUDIO_ENABLE); + } + if ((portDat & CYBER_BMSK_AUENZ) != CYBER_BMSK_AUENZ_ENABLE) { + printk(KERN_ERR "cyberpro5050: initAudioAccess: no success\n"); + ret = -1; + } else { + cyber_outidx(CYBER_PORT_AUDIO, CYBER_IDX_IRQ_ENABLE, + CYBER_BMSK_AUDIO_INT_ENABLE); + cyber_outidx(CYBER_PORT_AUDIO, 0xbf, 0x01); + cyber_outidx(CYBER_PORT_AUDIO, 0xba, 0x20); + cyber_outidx(CYBER_PORT_AUDIO, 0xbb, 0x08); + cyber_outidx(CYBER_PORT_AUDIO, 0xbf, 0x02); + cyber_outidx(CYBER_PORT_AUDIO, 0xb3, 0x06); + cyber_outidx(CYBER_PORT_AUDIO, 0xbf, 0x00); + } + spin_unlock_irqrestore(&card->lock, flags); + return ret; +} + +/* called with spin lock held */ + +static int +trident_load_channel_registers(struct trident_card *card, u32 * data, + unsigned int channel) +{ + int i; + + if (channel > 63) + return 0; + + /* select hardware channel to write */ + outb(channel, TRID_REG(card, T4D_LFO_GC_CIR)); + + /* Output the channel registers, but don't write register + three to an ALI chip. */ + for (i = 0; i < CHANNEL_REGS; i++) { + if (i == 3 && card->pci_id == PCI_DEVICE_ID_ALI_5451) + continue; + outl(data[i], TRID_REG(card, CHANNEL_START + 4 * i)); + } + if (card->pci_id == PCI_DEVICE_ID_ALI_5451 || + card->pci_id == PCI_DEVICE_ID_INTERG_5050) { + outl(ALI_EMOD_Still, TRID_REG(card, ALI_EBUF1)); + outl(ALI_EMOD_Still, TRID_REG(card, ALI_EBUF2)); + } + return 1; +} + +/* called with spin lock held */ +static int +trident_write_voice_regs(struct trident_state *state) +{ + unsigned int data[CHANNEL_REGS + 1]; + struct trident_channel *channel; + + channel = state->dmabuf.channel; + + data[1] = channel->lba; + data[4] = channel->control; + + switch (state->card->pci_id) { + case PCI_DEVICE_ID_ALI_5451: + data[0] = 0; /* Current Sample Offset */ + data[2] = (channel->eso << 16) | (channel->delta & 0xffff); + data[3] = 0; + break; + case PCI_DEVICE_ID_SI_7018: + case PCI_DEVICE_ID_INTERG_5050: + data[0] = 0; /* Current Sample Offset */ + data[2] = (channel->eso << 16) | (channel->delta & 0xffff); + data[3] = (channel->attribute << 16) | (channel->fm_vol & 0xffff); + break; + case PCI_DEVICE_ID_TRIDENT_4DWAVE_DX: + data[0] = 0; /* Current Sample Offset */ + data[2] = (channel->eso << 16) | (channel->delta & 0xffff); + data[3] = channel->fm_vol & 0xffff; + break; + case PCI_DEVICE_ID_TRIDENT_4DWAVE_NX: + data[0] = (channel->delta << 24); + data[2] = ((channel->delta << 16) & 0xff000000) | + (channel->eso & 0x00ffffff); + data[3] = channel->fm_vol & 0xffff; + break; + default: + return 0; + } + + return trident_load_channel_registers(state->card, data, channel->num); +} + +static int +compute_rate_play(u32 rate) +{ + int delta; + /* We special case 44100 and 8000 since rounding with the equation + does not give us an accurate enough value. For 11025 and 22050 + the equation gives us the best answer. All other frequencies will + also use the equation. JDW */ + if (rate == 44100) + delta = 0xeb3; + else if (rate == 8000) + delta = 0x2ab; + else if (rate == 48000) + delta = 0x1000; + else + delta = (((rate << 12) + rate) / 48000) & 0x0000ffff; + return delta; +} + +static int +compute_rate_rec(u32 rate) +{ + int delta; + + if (rate == 44100) + delta = 0x116a; + else if (rate == 8000) + delta = 0x6000; + else if (rate == 48000) + delta = 0x1000; + else + delta = ((48000 << 12) / rate) & 0x0000ffff; + + return delta; +} + +/* set playback sample rate */ +static unsigned int +trident_set_dac_rate(struct trident_state *state, unsigned int rate) +{ + struct dmabuf *dmabuf = &state->dmabuf; + + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; + + dmabuf->rate = rate; + dmabuf->channel->delta = compute_rate_play(rate); + + trident_write_voice_regs(state); + + pr_debug("trident: called trident_set_dac_rate : rate = %d\n", rate); + + return rate; +} + +/* set recording sample rate */ +static unsigned int +trident_set_adc_rate(struct trident_state *state, unsigned int rate) +{ + struct dmabuf *dmabuf = &state->dmabuf; + + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; + + dmabuf->rate = rate; + dmabuf->channel->delta = compute_rate_rec(rate); + + trident_write_voice_regs(state); + + pr_debug("trident: called trident_set_adc_rate : rate = %d\n", rate); + + return rate; +} + +/* prepare channel attributes for playback */ +static void +trident_play_setup(struct trident_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct trident_channel *channel = dmabuf->channel; + + channel->lba = dmabuf->dma_handle; + channel->delta = compute_rate_play(dmabuf->rate); + + channel->eso = dmabuf->dmasize >> sample_shift[dmabuf->fmt]; + channel->eso -= 1; + + if (state->card->pci_id != PCI_DEVICE_ID_SI_7018) { + channel->attribute = 0; + if (state->card->pci_id == PCI_DEVICE_ID_ALI_5451) { + if ((channel->num == ALI_SPDIF_IN_CHANNEL) || + (channel->num == ALI_PCM_IN_CHANNEL)) + ali_disable_special_channel(state->card, channel->num); + else if ((inl(TRID_REG(state->card, ALI_GLOBAL_CONTROL)) + & ALI_SPDIF_OUT_CH_ENABLE) + && (channel->num == ALI_SPDIF_OUT_CHANNEL)) { + ali_set_spdif_out_rate(state->card, + state->dmabuf.rate); + state->dmabuf.channel->delta = 0x1000; + } + } + } + + channel->fm_vol = 0x0; + + channel->control = CHANNEL_LOOP; + if (dmabuf->fmt & TRIDENT_FMT_16BIT) { + /* 16-bits */ + channel->control |= CHANNEL_16BITS; + /* signed */ + channel->control |= CHANNEL_SIGNED; + } + if (dmabuf->fmt & TRIDENT_FMT_STEREO) + /* stereo */ + channel->control |= CHANNEL_STEREO; + + pr_debug("trident: trident_play_setup, LBA = 0x%08x, Delta = 0x%08x, " + "ESO = 0x%08x, Control = 0x%08x\n", channel->lba, + channel->delta, channel->eso, channel->control); + + trident_write_voice_regs(state); +} + +/* prepare channel attributes for recording */ +static void +trident_rec_setup(struct trident_state *state) +{ + u16 w; + u8 bval; + + struct trident_card *card = state->card; + struct dmabuf *dmabuf = &state->dmabuf; + struct trident_channel *channel = dmabuf->channel; + unsigned int rate; + + /* Enable AC-97 ADC (capture) */ + switch (card->pci_id) { + case PCI_DEVICE_ID_ALI_5451: + ali_enable_special_channel(state); + break; + case PCI_DEVICE_ID_SI_7018: + /* for 7018, the ac97 is always in playback/record (duplex) mode */ + break; + case PCI_DEVICE_ID_TRIDENT_4DWAVE_DX: + w = inb(TRID_REG(card, DX_ACR2_AC97_COM_STAT)); + outb(w | 0x48, TRID_REG(card, DX_ACR2_AC97_COM_STAT)); + /* enable and set record channel */ + outb(0x80 | channel->num, TRID_REG(card, T4D_REC_CH)); + break; + case PCI_DEVICE_ID_TRIDENT_4DWAVE_NX: + w = inw(TRID_REG(card, T4D_MISCINT)); + outw(w | 0x1000, TRID_REG(card, T4D_MISCINT)); + /* enable and set record channel */ + outb(0x80 | channel->num, TRID_REG(card, T4D_REC_CH)); + break; + case PCI_DEVICE_ID_INTERG_5050: + /* don't know yet, using special channel 22 in GC1(0xd4)? */ + break; + default: + return; + } + + channel->lba = dmabuf->dma_handle; + channel->delta = compute_rate_rec(dmabuf->rate); + if ((card->pci_id == PCI_DEVICE_ID_ALI_5451) && + (channel->num == ALI_SPDIF_IN_CHANNEL)) { + rate = ali_get_spdif_in_rate(card); + if (rate == 0) { + printk(KERN_WARNING "trident: ALi 5451 " + "S/PDIF input setup error!\n"); + rate = 48000; + } + bval = inb(TRID_REG(card, ALI_SPDIF_CTRL)); + if (bval & 0x10) { + outb(bval, TRID_REG(card, ALI_SPDIF_CTRL)); + printk(KERN_WARNING "trident: cleared ALi " + "5451 S/PDIF parity error flag.\n"); + } + + if (rate != 48000) + channel->delta = ((rate << 12) / dmabuf->rate) & 0x0000ffff; + } + + channel->eso = dmabuf->dmasize >> sample_shift[dmabuf->fmt]; + channel->eso -= 1; + + if (state->card->pci_id != PCI_DEVICE_ID_SI_7018) { + channel->attribute = 0; + } + + channel->fm_vol = 0x0; + + channel->control = CHANNEL_LOOP; + if (dmabuf->fmt & TRIDENT_FMT_16BIT) { + /* 16-bits */ + channel->control |= CHANNEL_16BITS; + /* signed */ + channel->control |= CHANNEL_SIGNED; + } + if (dmabuf->fmt & TRIDENT_FMT_STEREO) + /* stereo */ + channel->control |= CHANNEL_STEREO; + + pr_debug("trident: trident_rec_setup, LBA = 0x%08x, Delat = 0x%08x, " + "ESO = 0x%08x, Control = 0x%08x\n", channel->lba, + channel->delta, channel->eso, channel->control); + + trident_write_voice_regs(state); +} + +/* get current playback/recording dma buffer pointer (byte offset from LBA), + called with spinlock held! */ +static inline unsigned +trident_get_dma_addr(struct trident_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + u32 cso; + + if (!dmabuf->enable) + return 0; + + outb(dmabuf->channel->num, TRID_REG(state->card, T4D_LFO_GC_CIR)); + + switch (state->card->pci_id) { + case PCI_DEVICE_ID_ALI_5451: + case PCI_DEVICE_ID_SI_7018: + case PCI_DEVICE_ID_TRIDENT_4DWAVE_DX: + case PCI_DEVICE_ID_INTERG_5050: + /* 16 bits ESO, CSO for 7018 and DX */ + cso = inw(TRID_REG(state->card, CH_DX_CSO_ALPHA_FMS + 2)); + break; + case PCI_DEVICE_ID_TRIDENT_4DWAVE_NX: + /* 24 bits ESO, CSO for NX */ + cso = inl(TRID_REG(state->card, CH_NX_DELTA_CSO)) & 0x00ffffff; + break; + default: + return 0; + } + + pr_debug("trident: trident_get_dma_addr: chip reported channel: %d, " + "cso = 0x%04x\n", dmabuf->channel->num, cso); + + /* ESO and CSO are in units of Samples, convert to byte offset */ + cso <<= sample_shift[dmabuf->fmt]; + + return (cso % dmabuf->dmasize); +} + +/* Stop recording (lock held) */ +static inline void +__stop_adc(struct trident_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned int chan_num = dmabuf->channel->num; + struct trident_card *card = state->card; + + dmabuf->enable &= ~ADC_RUNNING; + trident_stop_voice(card, chan_num); + trident_disable_voice_irq(card, chan_num); +} + +static void +stop_adc(struct trident_state *state) +{ + struct trident_card *card = state->card; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + __stop_adc(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +static void +start_adc(struct trident_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned int chan_num = dmabuf->channel->num; + struct trident_card *card = state->card; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + if ((dmabuf->mapped || + dmabuf->count < (signed) dmabuf->dmasize) && + dmabuf->ready) { + dmabuf->enable |= ADC_RUNNING; + trident_enable_voice_irq(card, chan_num); + trident_start_voice(card, chan_num); + } + spin_unlock_irqrestore(&card->lock, flags); +} + +/* stop playback (lock held) */ +static inline void +__stop_dac(struct trident_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned int chan_num = dmabuf->channel->num; + struct trident_card *card = state->card; + + dmabuf->enable &= ~DAC_RUNNING; + trident_stop_voice(card, chan_num); + if (state->chans_num == 6) { + trident_stop_voice(card, state->other_states[0]-> + dmabuf.channel->num); + trident_stop_voice(card, state->other_states[1]-> + dmabuf.channel->num); + trident_stop_voice(card, state->other_states[2]-> + dmabuf.channel->num); + trident_stop_voice(card, state->other_states[3]-> + dmabuf.channel->num); + } + trident_disable_voice_irq(card, chan_num); +} + +static void +stop_dac(struct trident_state *state) +{ + struct trident_card *card = state->card; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + __stop_dac(state); + spin_unlock_irqrestore(&card->lock, flags); +} + +static void +start_dac(struct trident_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned int chan_num = dmabuf->channel->num; + struct trident_card *card = state->card; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + if ((dmabuf->mapped || dmabuf->count > 0) && dmabuf->ready) { + dmabuf->enable |= DAC_RUNNING; + trident_enable_voice_irq(card, chan_num); + trident_start_voice(card, chan_num); + if (state->chans_num == 6) { + trident_start_voice(card, state->other_states[0]-> + dmabuf.channel->num); + trident_start_voice(card, state->other_states[1]-> + dmabuf.channel->num); + trident_start_voice(card, state->other_states[2]-> + dmabuf.channel->num); + trident_start_voice(card, state->other_states[3]-> + dmabuf.channel->num); + } + } + spin_unlock_irqrestore(&card->lock, flags); +} + +#define DMABUF_DEFAULTORDER (15-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +/* alloc a DMA buffer of with a buffer of this order */ +static int +alloc_dmabuf(struct dmabuf *dmabuf, struct pci_dev *pci_dev, int order) +{ + void *rawbuf = NULL; + struct page *page, *pend; + + if (!(rawbuf = pci_alloc_consistent(pci_dev, PAGE_SIZE << order, + &dmabuf->dma_handle))) + return -ENOMEM; + + pr_debug("trident: allocated %ld (order = %d) bytes at %p\n", + PAGE_SIZE << order, order, rawbuf); + + dmabuf->ready = dmabuf->mapped = 0; + dmabuf->rawbuf = rawbuf; + dmabuf->buforder = order; + + /* now mark the pages as reserved; otherwise */ + /* remap_pfn_range doesn't do what we want */ + pend = virt_to_page(rawbuf + (PAGE_SIZE << order) - 1); + for (page = virt_to_page(rawbuf); page <= pend; page++) + SetPageReserved(page); + + return 0; +} + +/* allocate the main DMA buffer, playback and recording buffer should be */ +/* allocated separately */ +static int +alloc_main_dmabuf(struct trident_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + int order; + int ret = -ENOMEM; + + /* alloc as big a chunk as we can, FIXME: is this necessary ?? */ + for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--) { + if (!(ret = alloc_dmabuf(dmabuf, state->card->pci_dev, order))) + return 0; + /* else try again */ + } + return ret; +} + +/* deallocate a DMA buffer */ +static void +dealloc_dmabuf(struct dmabuf *dmabuf, struct pci_dev *pci_dev) +{ + struct page *page, *pend; + + if (dmabuf->rawbuf) { + /* undo marking the pages as reserved */ + pend = virt_to_page(dmabuf->rawbuf + (PAGE_SIZE << dmabuf->buforder) - 1); + for (page = virt_to_page(dmabuf->rawbuf); page <= pend; page++) + ClearPageReserved(page); + pci_free_consistent(pci_dev, PAGE_SIZE << dmabuf->buforder, + dmabuf->rawbuf, dmabuf->dma_handle); + dmabuf->rawbuf = NULL; + } + dmabuf->mapped = dmabuf->ready = 0; +} + +static int +prog_dmabuf(struct trident_state *state, enum dmabuf_mode rec) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned bytepersec; + struct trident_state *s = state; + unsigned bufsize, dma_nums; + unsigned long flags; + int ret, i, order; + + if ((ret = lock_set_fmt(state)) < 0) + return ret; + + if (state->chans_num == 6) + dma_nums = 5; + else + dma_nums = 1; + + for (i = 0; i < dma_nums; i++) { + if (i > 0) { + s = state->other_states[i - 1]; + dmabuf = &s->dmabuf; + dmabuf->fmt = state->dmabuf.fmt; + dmabuf->rate = state->dmabuf.rate; + } + + spin_lock_irqsave(&s->card->lock, flags); + dmabuf->hwptr = dmabuf->swptr = dmabuf->total_bytes = 0; + dmabuf->count = dmabuf->error = 0; + spin_unlock_irqrestore(&s->card->lock, flags); + + /* allocate DMA buffer if not allocated yet */ + if (!dmabuf->rawbuf) { + if (i == 0) { + if ((ret = alloc_main_dmabuf(state))) { + unlock_set_fmt(state); + return ret; + } + } else { + ret = -ENOMEM; + order = state->dmabuf.buforder - 1; + if (order >= DMABUF_MINORDER) { + ret = alloc_dmabuf(dmabuf, + state->card->pci_dev, + order); + } + if (ret) { + /* release the main DMA buffer */ + dealloc_dmabuf(&state->dmabuf, state->card->pci_dev); + /* release the auxiliary DMA buffers */ + for (i -= 2; i >= 0; i--) + dealloc_dmabuf(&state->other_states[i]->dmabuf, + state->card->pci_dev); + unlock_set_fmt(state); + return ret; + } + } + } + /* FIXME: figure out all this OSS fragment stuff */ + bytepersec = dmabuf->rate << sample_shift[dmabuf->fmt]; + bufsize = PAGE_SIZE << dmabuf->buforder; + if (dmabuf->ossfragshift) { + if ((1000 << dmabuf->ossfragshift) < bytepersec) + dmabuf->fragshift = ld2(bytepersec / 1000); + else + dmabuf->fragshift = dmabuf->ossfragshift; + } else { + /* lets hand out reasonable big ass buffers by default */ + dmabuf->fragshift = (dmabuf->buforder + PAGE_SHIFT - 2); + } + dmabuf->numfrag = bufsize >> dmabuf->fragshift; + while (dmabuf->numfrag < 4 && dmabuf->fragshift > 3) { + dmabuf->fragshift--; + dmabuf->numfrag = bufsize >> dmabuf->fragshift; + } + dmabuf->fragsize = 1 << dmabuf->fragshift; + if (dmabuf->ossmaxfrags >= 4 && dmabuf->ossmaxfrags < dmabuf->numfrag) + dmabuf->numfrag = dmabuf->ossmaxfrags; + dmabuf->fragsamples = dmabuf->fragsize >> sample_shift[dmabuf->fmt]; + dmabuf->dmasize = dmabuf->numfrag << dmabuf->fragshift; + + memset(dmabuf->rawbuf, (dmabuf->fmt & TRIDENT_FMT_16BIT) ? 0 : 0x80, + dmabuf->dmasize); + + spin_lock_irqsave(&s->card->lock, flags); + if (rec == DM_RECORD) + trident_rec_setup(s); + else /* DM_PLAYBACK */ + trident_play_setup(s); + + spin_unlock_irqrestore(&s->card->lock, flags); + + /* set the ready flag for the dma buffer */ + dmabuf->ready = 1; + + pr_debug("trident: prog_dmabuf(%d), sample rate = %d, " + "format = %d, numfrag = %d, fragsize = %d " + "dmasize = %d\n", dmabuf->channel->num, + dmabuf->rate, dmabuf->fmt, dmabuf->numfrag, + dmabuf->fragsize, dmabuf->dmasize); + } + unlock_set_fmt(state); + return 0; +} + + +static inline int prog_dmabuf_record(struct trident_state* state) +{ + return prog_dmabuf(state, DM_RECORD); +} + +static inline int prog_dmabuf_playback(struct trident_state* state) +{ + return prog_dmabuf(state, DM_PLAYBACK); +} + +/* we are doing quantum mechanics here, the buffer can only be empty, half or full filled i.e. + |------------|------------| or |xxxxxxxxxxxx|------------| or |xxxxxxxxxxxx|xxxxxxxxxxxx| + but we almost always get this + |xxxxxx------|------------| or |xxxxxxxxxxxx|xxxxx-------| + so we have to clear the tail space to "silence" + |xxxxxx000000|------------| or |xxxxxxxxxxxx|xxxxxx000000| +*/ +static void +trident_clear_tail(struct trident_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned swptr; + unsigned char silence = (dmabuf->fmt & TRIDENT_FMT_16BIT) ? 0 : 0x80; + unsigned int len; + unsigned long flags; + + spin_lock_irqsave(&state->card->lock, flags); + swptr = dmabuf->swptr; + spin_unlock_irqrestore(&state->card->lock, flags); + + if (swptr == 0 || swptr == dmabuf->dmasize / 2 || + swptr == dmabuf->dmasize) + return; + + if (swptr < dmabuf->dmasize / 2) + len = dmabuf->dmasize / 2 - swptr; + else + len = dmabuf->dmasize - swptr; + + memset(dmabuf->rawbuf + swptr, silence, len); + if (state->card->pci_id != PCI_DEVICE_ID_ALI_5451) { + spin_lock_irqsave(&state->card->lock, flags); + dmabuf->swptr += len; + dmabuf->count += len; + spin_unlock_irqrestore(&state->card->lock, flags); + } + + /* restart the dma machine in case it is halted */ + start_dac(state); +} + +static int +drain_dac(struct trident_state *state, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + unsigned long tmo; + int count; + unsigned long diff = 0; + + if (dmabuf->mapped || !dmabuf->ready) + return 0; + + add_wait_queue(&dmabuf->wait, &wait); + for (;;) { + /* It seems that we have to set the current state to TASK_INTERRUPTIBLE + every time to make the process really go to sleep */ + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&state->card->lock, flags); + count = dmabuf->count; + spin_unlock_irqrestore(&state->card->lock, flags); + + if (count <= 0) + break; + + if (signal_pending(current)) + break; + + if (nonblock) { + remove_wait_queue(&dmabuf->wait, &wait); + set_current_state(TASK_RUNNING); + return -EBUSY; + } + + /* No matter how much data is left in the buffer, we have to wait until + CSO == ESO/2 or CSO == ESO when address engine interrupts */ + if (state->card->pci_id == PCI_DEVICE_ID_ALI_5451 || + state->card->pci_id == PCI_DEVICE_ID_INTERG_5050) { + diff = dmabuf->swptr - trident_get_dma_addr(state) + dmabuf->dmasize; + diff = diff % (dmabuf->dmasize); + tmo = (diff * HZ) / dmabuf->rate; + } else { + tmo = (dmabuf->dmasize * HZ) / dmabuf->rate; + } + tmo >>= sample_shift[dmabuf->fmt]; + if (!schedule_timeout(tmo ? tmo : 1) && tmo) { + break; + } + } + remove_wait_queue(&dmabuf->wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + + return 0; +} + +/* update buffer manangement pointers, especially, */ +/* dmabuf->count and dmabuf->hwptr */ +static void +trident_update_ptr(struct trident_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned hwptr, swptr; + int clear_cnt = 0; + int diff; + unsigned char silence; + unsigned half_dmasize; + + /* update hardware pointer */ + hwptr = trident_get_dma_addr(state); + diff = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize; + dmabuf->hwptr = hwptr; + dmabuf->total_bytes += diff; + + /* error handling and process wake up for ADC */ + if (dmabuf->enable == ADC_RUNNING) { + if (dmabuf->mapped) { + dmabuf->count -= diff; + if (dmabuf->count >= (signed) dmabuf->fragsize) + wake_up(&dmabuf->wait); + } else { + dmabuf->count += diff; + + if (dmabuf->count < 0 || + dmabuf->count > dmabuf->dmasize) { + /* buffer underrun or buffer overrun, */ + /* we have no way to recover it here, just */ + /* stop the machine and let the process */ + /* force hwptr and swptr to sync */ + __stop_adc(state); + dmabuf->error++; + } + if (dmabuf->count < (signed) dmabuf->dmasize / 2) + wake_up(&dmabuf->wait); + } + } + + /* error handling and process wake up for DAC */ + if (dmabuf->enable == DAC_RUNNING) { + if (dmabuf->mapped) { + dmabuf->count += diff; + if (dmabuf->count >= (signed) dmabuf->fragsize) + wake_up(&dmabuf->wait); + } else { + dmabuf->count -= diff; + + if (dmabuf->count < 0 || + dmabuf->count > dmabuf->dmasize) { + /* buffer underrun or buffer overrun, we have no way to recover + it here, just stop the machine and let the process force hwptr + and swptr to sync */ + __stop_dac(state); + dmabuf->error++; + } else if (!dmabuf->endcleared) { + swptr = dmabuf->swptr; + silence = (dmabuf->fmt & TRIDENT_FMT_16BIT ? 0 : 0x80); + if (dmabuf->update_flag & ALI_ADDRESS_INT_UPDATE) { + /* We must clear end data of 1/2 dmabuf if needed. + According to 1/2 algorithm of Address Engine Interrupt, + check the validation of the data of half dmasize. */ + half_dmasize = dmabuf->dmasize / 2; + if ((diff = hwptr - half_dmasize) < 0) + diff = hwptr; + if ((dmabuf->count + diff) < half_dmasize) { + //there is invalid data in the end of half buffer + if ((clear_cnt = half_dmasize - swptr) < 0) + clear_cnt += half_dmasize; + //clear the invalid data + memset(dmabuf->rawbuf + swptr, silence, clear_cnt); + if (state->chans_num == 6) { + clear_cnt = clear_cnt / 2; + swptr = swptr / 2; + memset(state->other_states[0]->dmabuf.rawbuf + swptr, + silence, clear_cnt); + memset(state->other_states[1]->dmabuf.rawbuf + swptr, + silence, clear_cnt); + memset(state->other_states[2]->dmabuf.rawbuf + swptr, + silence, clear_cnt); + memset(state->other_states[3]->dmabuf.rawbuf + swptr, + silence, clear_cnt); + } + dmabuf->endcleared = 1; + } + } else if (dmabuf->count < (signed) dmabuf->fragsize) { + clear_cnt = dmabuf->fragsize; + if ((swptr + clear_cnt) > dmabuf->dmasize) + clear_cnt = dmabuf->dmasize - swptr; + memset(dmabuf->rawbuf + swptr, silence, clear_cnt); + if (state->chans_num == 6) { + clear_cnt = clear_cnt / 2; + swptr = swptr / 2; + memset(state->other_states[0]->dmabuf.rawbuf + swptr, + silence, clear_cnt); + memset(state->other_states[1]->dmabuf.rawbuf + swptr, + silence, clear_cnt); + memset(state->other_states[2]->dmabuf.rawbuf + swptr, + silence, clear_cnt); + memset(state->other_states[3]->dmabuf.rawbuf + swptr, + silence, clear_cnt); + } + dmabuf->endcleared = 1; + } + } + /* trident_update_ptr is called by interrupt handler or by process via + ioctl/poll, we only wake up the waiting process when we have more + than 1/2 buffer free (always true for interrupt handler) */ + if (dmabuf->count < (signed) dmabuf->dmasize / 2) + wake_up(&dmabuf->wait); + } + } + dmabuf->update_flag &= ~ALI_ADDRESS_INT_UPDATE; +} + +static void +trident_address_interrupt(struct trident_card *card) +{ + int i; + struct trident_state *state; + unsigned int channel; + + /* Update the pointers for all channels we are running. */ + /* FIXME: should read interrupt status only once */ + for (i = 0; i < NR_HW_CH; i++) { + channel = 63 - i; + if (trident_check_channel_interrupt(card, channel)) { + trident_ack_channel_interrupt(card, channel); + if ((state = card->states[i]) != NULL) { + trident_update_ptr(state); + } else { + printk(KERN_WARNING "trident: spurious channel " + "irq %d.\n", channel); + trident_stop_voice(card, channel); + trident_disable_voice_irq(card, channel); + } + } + } +} + +static void +ali_hwvol_control(struct trident_card *card, int opt) +{ + u16 dwTemp, volume[2], mute, diff, *pVol[2]; + + dwTemp = ali_ac97_read(card->ac97_codec[0], 0x02); + mute = dwTemp & 0x8000; + volume[0] = dwTemp & 0x001f; + volume[1] = (dwTemp & 0x1f00) >> 8; + if (volume[0] < volume[1]) { + pVol[0] = &volume[0]; + pVol[1] = &volume[1]; + } else { + pVol[1] = &volume[0]; + pVol[0] = &volume[1]; + } + diff = *(pVol[1]) - *(pVol[0]); + + if (opt == 1) { // MUTE + dwTemp ^= 0x8000; + ali_ac97_write(card->ac97_codec[0], + 0x02, dwTemp); + } else if (opt == 2) { // Down + if (mute) + return; + if (*(pVol[1]) < 0x001f) { + (*pVol[1])++; + *(pVol[0]) = *(pVol[1]) - diff; + } + dwTemp &= 0xe0e0; + dwTemp |= (volume[0]) | (volume[1] << 8); + ali_ac97_write(card->ac97_codec[0], 0x02, dwTemp); + card->ac97_codec[0]->mixer_state[0] = ((32 - volume[0]) * 25 / 8) | + (((32 - volume[1]) * 25 / 8) << 8); + } else if (opt == 4) { // Up + if (mute) + return; + if (*(pVol[0]) > 0) { + (*pVol[0])--; + *(pVol[1]) = *(pVol[0]) + diff; + } + dwTemp &= 0xe0e0; + dwTemp |= (volume[0]) | (volume[1] << 8); + ali_ac97_write(card->ac97_codec[0], 0x02, dwTemp); + card->ac97_codec[0]->mixer_state[0] = ((32 - volume[0]) * 25 / 8) | + (((32 - volume[1]) * 25 / 8) << 8); + } else { + /* Nothing needs doing */ + } +} + +/* + * Re-enable reporting of vol change after 0.1 seconds + */ + +static void +ali_timeout(unsigned long ptr) +{ + struct trident_card *card = (struct trident_card *) ptr; + u16 temp = 0; + + /* Enable GPIO IRQ (MISCINT bit 18h) */ + temp = inw(TRID_REG(card, T4D_MISCINT + 2)); + temp |= 0x0004; + outw(temp, TRID_REG(card, T4D_MISCINT + 2)); +} + +/* + * Set up the timer to clear the vol change notification + */ + +static void +ali_set_timer(struct trident_card *card) +{ + /* Add Timer Routine to Enable GPIO IRQ */ + del_timer(&card->timer); /* Never queue twice */ + card->timer.function = ali_timeout; + card->timer.data = (unsigned long) card; + card->timer.expires = jiffies + HZ / 10; + add_timer(&card->timer); +} + +/* + * Process a GPIO event + */ + +static void +ali_queue_task(struct trident_card *card, int opt) +{ + u16 temp; + + /* Disable GPIO IRQ (MISCINT bit 18h) */ + temp = inw(TRID_REG(card, T4D_MISCINT + 2)); + temp &= (u16) (~0x0004); + outw(temp, TRID_REG(card, T4D_MISCINT + 2)); + + /* Adjust the volume */ + ali_hwvol_control(card, opt); + + /* Set the timer for 1/10th sec */ + ali_set_timer(card); +} + +static void +cyber_address_interrupt(struct trident_card *card) +{ + int i, irq_status; + struct trident_state *state; + unsigned int channel; + + /* Update the pointers for all channels we are running. */ + /* FIXED: read interrupt status only once */ + irq_status = inl(TRID_REG(card, T4D_AINT_A)); + + pr_debug("cyber_address_interrupt: irq_status 0x%X\n", irq_status); + + for (i = 0; i < NR_HW_CH; i++) { + channel = 31 - i; + if (irq_status & (1 << channel)) { + /* clear bit by writing a 1, zeroes are ignored */ + outl((1 << channel), TRID_REG(card, T4D_AINT_A)); + + pr_debug("cyber_interrupt: channel %d\n", channel); + + if ((state = card->states[i]) != NULL) { + trident_update_ptr(state); + } else { + printk(KERN_WARNING "cyber5050: spurious " + "channel irq %d.\n", channel); + trident_stop_voice(card, channel); + trident_disable_voice_irq(card, channel); + } + } + } +} + +static irqreturn_t +trident_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct trident_card *card = (struct trident_card *) dev_id; + u32 event; + u32 gpio; + + spin_lock(&card->lock); + event = inl(TRID_REG(card, T4D_MISCINT)); + + pr_debug("trident: trident_interrupt called, MISCINT = 0x%08x\n", + event); + + if (event & ADDRESS_IRQ) { + card->address_interrupt(card); + } + + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { + /* GPIO IRQ (H/W Volume Control) */ + event = inl(TRID_REG(card, T4D_MISCINT)); + if (event & (1 << 25)) { + gpio = inl(TRID_REG(card, ALI_GPIO)); + if (!timer_pending(&card->timer)) + ali_queue_task(card, gpio & 0x07); + } + event = inl(TRID_REG(card, T4D_MISCINT)); + outl(event | (ST_TARGET_REACHED | MIXER_OVERFLOW | MIXER_UNDERFLOW), + TRID_REG(card, T4D_MISCINT)); + spin_unlock(&card->lock); + return IRQ_HANDLED; + } + + /* manually clear interrupt status, bad hardware design, blame T^2 */ + outl((ST_TARGET_REACHED | MIXER_OVERFLOW | MIXER_UNDERFLOW), + TRID_REG(card, T4D_MISCINT)); + spin_unlock(&card->lock); + return IRQ_HANDLED; +} + +/* in this loop, dmabuf.count signifies the amount of data that is waiting */ +/* to be copied to the user's buffer. it is filled by the dma machine and */ +/* drained by this loop. */ +static ssize_t +trident_read(struct file *file, char __user *buffer, size_t count, loff_t * ppos) +{ + struct trident_state *state = (struct trident_state *)file->private_data; + struct dmabuf *dmabuf = &state->dmabuf; + ssize_t ret = 0; + unsigned long flags; + unsigned swptr; + int cnt; + + pr_debug("trident: trident_read called, count = %d\n", count); + + VALIDATE_STATE(state); + + if (dmabuf->mapped) + return -ENXIO; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + + down(&state->sem); + if (!dmabuf->ready && (ret = prog_dmabuf_record(state))) + goto out; + + while (count > 0) { + spin_lock_irqsave(&state->card->lock, flags); + if (dmabuf->count > (signed) dmabuf->dmasize) { + /* buffer overrun, we are recovering from */ + /* sleep_on_timeout, resync hwptr and swptr, */ + /* make process flush the buffer */ + dmabuf->count = dmabuf->dmasize; + dmabuf->swptr = dmabuf->hwptr; + } + swptr = dmabuf->swptr; + cnt = dmabuf->dmasize - swptr; + if (dmabuf->count < cnt) + cnt = dmabuf->count; + spin_unlock_irqrestore(&state->card->lock, flags); + + if (cnt > count) + cnt = count; + if (cnt <= 0) { + unsigned long tmo; + /* buffer is empty, start the dma machine and */ + /* wait for data to be recorded */ + start_adc(state); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + + up(&state->sem); + /* No matter how much space left in the buffer, */ + /* we have to wait until CSO == ESO/2 or CSO == ESO */ + /* when address engine interrupts */ + tmo = (dmabuf->dmasize * HZ) / (dmabuf->rate * 2); + tmo >>= sample_shift[dmabuf->fmt]; + /* There are two situations when sleep_on_timeout returns, one is when + the interrupt is serviced correctly and the process is waked up by + ISR ON TIME. Another is when timeout is expired, which means that + either interrupt is NOT serviced correctly (pending interrupt) or it + is TOO LATE for the process to be scheduled to run (scheduler latency) + which results in a (potential) buffer overrun. And worse, there is + NOTHING we can do to prevent it. */ + if (!interruptible_sleep_on_timeout(&dmabuf->wait, tmo)) { + pr_debug(KERN_ERR "trident: recording schedule timeout, " + "dmasz %u fragsz %u count %i hwptr %u swptr %u\n", + dmabuf->dmasize, dmabuf->fragsize, dmabuf->count, + dmabuf->hwptr, dmabuf->swptr); + + /* a buffer overrun, we delay the recovery until next time the + while loop begin and we REALLY have space to record */ + } + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out; + } + down(&state->sem); + if (dmabuf->mapped) { + if (!ret) + ret = -ENXIO; + goto out; + } + continue; + } + + if (copy_to_user(buffer, dmabuf->rawbuf + swptr, cnt)) { + if (!ret) + ret = -EFAULT; + goto out; + } + + swptr = (swptr + cnt) % dmabuf->dmasize; + + spin_lock_irqsave(&state->card->lock, flags); + dmabuf->swptr = swptr; + dmabuf->count -= cnt; + spin_unlock_irqrestore(&state->card->lock, flags); + + count -= cnt; + buffer += cnt; + ret += cnt; + start_adc(state); + } +out: + up(&state->sem); + return ret; +} + +/* in this loop, dmabuf.count signifies the amount of data that is waiting to be dma to + the soundcard. it is drained by the dma machine and filled by this loop. */ + +static ssize_t +trident_write(struct file *file, const char __user *buffer, size_t count, loff_t * ppos) +{ + struct trident_state *state = (struct trident_state *)file->private_data; + struct dmabuf *dmabuf = &state->dmabuf; + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + unsigned int state_cnt; + unsigned int copy_count; + int lret; /* for lock_set_fmt */ + + pr_debug("trident: trident_write called, count = %d\n", count); + + VALIDATE_STATE(state); + + /* + * Guard against an mmap or ioctl while writing + */ + + down(&state->sem); + + if (dmabuf->mapped) { + ret = -ENXIO; + goto out; + } + if (!dmabuf->ready && (ret = prog_dmabuf_playback(state))) + goto out; + + if (!access_ok(VERIFY_READ, buffer, count)) { + ret = -EFAULT; + goto out; + } + + ret = 0; + + while (count > 0) { + spin_lock_irqsave(&state->card->lock, flags); + if (dmabuf->count < 0) { + /* buffer underrun, we are recovering from */ + /* sleep_on_timeout, resync hwptr and swptr */ + dmabuf->count = 0; + dmabuf->swptr = dmabuf->hwptr; + } + swptr = dmabuf->swptr; + cnt = dmabuf->dmasize - swptr; + if (dmabuf->count + cnt > dmabuf->dmasize) + cnt = dmabuf->dmasize - dmabuf->count; + spin_unlock_irqrestore(&state->card->lock, flags); + + if (cnt > count) + cnt = count; + if (cnt <= 0) { + unsigned long tmo; + /* buffer is full, start the dma machine and */ + /* wait for data to be played */ + start_dac(state); + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + goto out; + } + /* No matter how much data left in the buffer, */ + /* we have to wait until CSO == ESO/2 or CSO == ESO */ + /* when address engine interrupts */ + lock_set_fmt(state); + tmo = (dmabuf->dmasize * HZ) / (dmabuf->rate * 2); + tmo >>= sample_shift[dmabuf->fmt]; + unlock_set_fmt(state); + up(&state->sem); + + /* There are two situations when sleep_on_timeout */ + /* returns, one is when the interrupt is serviced */ + /* correctly and the process is waked up by ISR */ + /* ON TIME. Another is when timeout is expired, which */ + /* means that either interrupt is NOT serviced */ + /* correctly (pending interrupt) or it is TOO LATE */ + /* for the process to be scheduled to run */ + /* (scheduler latency) which results in a (potential) */ + /* buffer underrun. And worse, there is NOTHING we */ + /* can do to prevent it. */ + if (!interruptible_sleep_on_timeout(&dmabuf->wait, tmo)) { + pr_debug(KERN_ERR "trident: playback schedule " + "timeout, dmasz %u fragsz %u count %i " + "hwptr %u swptr %u\n", dmabuf->dmasize, + dmabuf->fragsize, dmabuf->count, + dmabuf->hwptr, dmabuf->swptr); + + /* a buffer underrun, we delay the recovery */ + /* until next time the while loop begin and */ + /* we REALLY have data to play */ + } + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + goto out_nolock; + } + down(&state->sem); + if (dmabuf->mapped) { + if (!ret) + ret = -ENXIO; + goto out; + } + continue; + } + if ((lret = lock_set_fmt(state)) < 0) { + ret = lret; + goto out; + } + + if (state->chans_num == 6) { + copy_count = 0; + state_cnt = 0; + if (ali_write_5_1(state, buffer, cnt, ©_count, + &state_cnt) == -EFAULT) { + if (state_cnt) { + swptr = (swptr + state_cnt) % dmabuf->dmasize; + spin_lock_irqsave(&state->card->lock, flags); + dmabuf->swptr = swptr; + dmabuf->count += state_cnt; + dmabuf->endcleared = 0; + spin_unlock_irqrestore(&state->card->lock, flags); + } + ret += copy_count; + if (!ret) + ret = -EFAULT; + unlock_set_fmt(state); + goto out; + } + } else { + if (copy_from_user(dmabuf->rawbuf + swptr, + buffer, cnt)) { + if (!ret) + ret = -EFAULT; + unlock_set_fmt(state); + goto out; + } + state_cnt = cnt; + } + unlock_set_fmt(state); + + swptr = (swptr + state_cnt) % dmabuf->dmasize; + + spin_lock_irqsave(&state->card->lock, flags); + dmabuf->swptr = swptr; + dmabuf->count += state_cnt; + dmabuf->endcleared = 0; + spin_unlock_irqrestore(&state->card->lock, flags); + + count -= cnt; + buffer += cnt; + ret += cnt; + start_dac(state); + } +out: + up(&state->sem); +out_nolock: + return ret; +} + +/* No kernel lock - we have our own spinlock */ +static unsigned int +trident_poll(struct file *file, struct poll_table_struct *wait) +{ + struct trident_state *state = (struct trident_state *)file->private_data; + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(state); + + /* + * Guard against a parallel poll and write causing multiple + * prog_dmabuf events + */ + + down(&state->sem); + + if (file->f_mode & FMODE_WRITE) { + if (!dmabuf->ready && prog_dmabuf_playback(state)) { + up(&state->sem); + return 0; + } + poll_wait(file, &dmabuf->wait, wait); + } + if (file->f_mode & FMODE_READ) { + if (!dmabuf->ready && prog_dmabuf_record(state)) { + up(&state->sem); + return 0; + } + poll_wait(file, &dmabuf->wait, wait); + } + + up(&state->sem); + + spin_lock_irqsave(&state->card->lock, flags); + trident_update_ptr(state); + if (file->f_mode & FMODE_READ) { + if (dmabuf->count >= (signed) dmabuf->fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (dmabuf->mapped) { + if (dmabuf->count >= (signed) dmabuf->fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed) dmabuf->dmasize >= dmabuf->count + + (signed) dmabuf->fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&state->card->lock, flags); + + return mask; +} + +static int +trident_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct trident_state *state = (struct trident_state *)file->private_data; + struct dmabuf *dmabuf = &state->dmabuf; + int ret = -EINVAL; + unsigned long size; + + VALIDATE_STATE(state); + + /* + * Lock against poll read write or mmap creating buffers. Also lock + * a read or write against an mmap. + */ + + down(&state->sem); + + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf_playback(state)) != 0) + goto out; + } else if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf_record(state)) != 0) + goto out; + } else + goto out; + + ret = -EINVAL; + if (vma->vm_pgoff != 0) + goto out; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << dmabuf->buforder)) + goto out; + ret = -EAGAIN; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(dmabuf->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + goto out; + dmabuf->mapped = 1; + ret = 0; +out: + up(&state->sem); + return ret; +} + +static int +trident_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct trident_state *state = (struct trident_state *)file->private_data; + struct dmabuf *dmabuf = &state->dmabuf; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int val, mapped, ret = 0; + struct trident_card *card = state->card; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + VALIDATE_STATE(state); + + + mapped = ((file->f_mode & (FMODE_WRITE | FMODE_READ)) && dmabuf->mapped); + + pr_debug("trident: trident_ioctl, command = %2d, arg = 0x%08x\n", + _IOC_NR(cmd), arg ? *p : 0); + + switch (cmd) { + case OSS_GETVERSION: + ret = put_user(SOUND_VERSION, p); + break; + + case SNDCTL_DSP_RESET: + /* FIXME: spin_lock ? */ + if (file->f_mode & FMODE_WRITE) { + stop_dac(state); + synchronize_irq(card->irq); + dmabuf->ready = 0; + dmabuf->swptr = dmabuf->hwptr = 0; + dmabuf->count = dmabuf->total_bytes = 0; + } + if (file->f_mode & FMODE_READ) { + stop_adc(state); + synchronize_irq(card->irq); + dmabuf->ready = 0; + dmabuf->swptr = dmabuf->hwptr = 0; + dmabuf->count = dmabuf->total_bytes = 0; + } + break; + + case SNDCTL_DSP_SYNC: + if (file->f_mode & FMODE_WRITE) + ret = drain_dac(state, file->f_flags & O_NONBLOCK); + break; + + case SNDCTL_DSP_SPEED: /* set smaple rate */ + if (get_user(val, p)) { + ret = -EFAULT; + break; + } + if (val >= 0) { + if (file->f_mode & FMODE_WRITE) { + stop_dac(state); + dmabuf->ready = 0; + spin_lock_irqsave(&state->card->lock, flags); + trident_set_dac_rate(state, val); + spin_unlock_irqrestore(&state->card->lock, flags); + } + if (file->f_mode & FMODE_READ) { + stop_adc(state); + dmabuf->ready = 0; + spin_lock_irqsave(&state->card->lock, flags); + trident_set_adc_rate(state, val); + spin_unlock_irqrestore(&state->card->lock, flags); + } + } + ret = put_user(dmabuf->rate, p); + break; + + case SNDCTL_DSP_STEREO: /* set stereo or mono channel */ + if (get_user(val, p)) { + ret = -EFAULT; + break; + } + if ((ret = lock_set_fmt(state)) < 0) + return ret; + + if (file->f_mode & FMODE_WRITE) { + stop_dac(state); + dmabuf->ready = 0; + if (val) + dmabuf->fmt |= TRIDENT_FMT_STEREO; + else + dmabuf->fmt &= ~TRIDENT_FMT_STEREO; + } + if (file->f_mode & FMODE_READ) { + stop_adc(state); + dmabuf->ready = 0; + if (val) + dmabuf->fmt |= TRIDENT_FMT_STEREO; + else + dmabuf->fmt &= ~TRIDENT_FMT_STEREO; + } + unlock_set_fmt(state); + break; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + if ((val = prog_dmabuf_playback(state))) + ret = val; + else + ret = put_user(dmabuf->fragsize, p); + break; + } + if (file->f_mode & FMODE_READ) { + if ((val = prog_dmabuf_record(state))) + ret = val; + else + ret = put_user(dmabuf->fragsize, p); + break; + } + /* neither READ nor WRITE? is this even possible? */ + ret = -EINVAL; + break; + + + case SNDCTL_DSP_GETFMTS: /* Returns a mask of supported sample format */ + ret = put_user(AFMT_S16_LE | AFMT_U16_LE | AFMT_S8 | + AFMT_U8, p); + break; + + case SNDCTL_DSP_SETFMT: /* Select sample format */ + if (get_user(val, p)) { + ret = -EFAULT; + break; + } + if ((ret = lock_set_fmt(state)) < 0) + return ret; + + if (val != AFMT_QUERY) { + if (file->f_mode & FMODE_WRITE) { + stop_dac(state); + dmabuf->ready = 0; + if (val == AFMT_S16_LE) + dmabuf->fmt |= TRIDENT_FMT_16BIT; + else + dmabuf->fmt &= ~TRIDENT_FMT_16BIT; + } + if (file->f_mode & FMODE_READ) { + stop_adc(state); + dmabuf->ready = 0; + if (val == AFMT_S16_LE) + dmabuf->fmt |= TRIDENT_FMT_16BIT; + else + dmabuf->fmt &= ~TRIDENT_FMT_16BIT; + } + } + unlock_set_fmt(state); + ret = put_user((dmabuf->fmt & TRIDENT_FMT_16BIT) ? AFMT_S16_LE : + AFMT_U8, p); + break; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) { + ret = -EFAULT; + break; + } + if (val != 0) { + if ((ret = lock_set_fmt(state)) < 0) + return ret; + + if (file->f_mode & FMODE_WRITE) { + stop_dac(state); + dmabuf->ready = 0; + + //prevent from memory leak + if ((state->chans_num > 2) && (state->chans_num != val)) { + ali_free_other_states_resources(state); + state->chans_num = 1; + } + + if (val >= 2) { + + dmabuf->fmt |= TRIDENT_FMT_STEREO; + if ((val == 6) && (state->card->pci_id == PCI_DEVICE_ID_ALI_5451)) { + if (card->rec_channel_use_count > 0) { + printk(KERN_ERR "trident: Record is " + "working on the card!\n"); + ret = -EBUSY; + unlock_set_fmt(state); + break; + } + + ret = ali_setup_multi_channels(state->card, 6); + if (ret < 0) { + unlock_set_fmt(state); + break; + } + down(&state->card->open_sem); + ret = ali_allocate_other_states_resources(state, 6); + if (ret < 0) { + up(&state->card->open_sem); + unlock_set_fmt(state); + break; + } + state->card->multi_channel_use_count++; + up(&state->card->open_sem); + } else + val = 2; /*yield to 2-channels */ + } else + dmabuf->fmt &= ~TRIDENT_FMT_STEREO; + state->chans_num = val; + } + if (file->f_mode & FMODE_READ) { + stop_adc(state); + dmabuf->ready = 0; + if (val >= 2) { + if (!((file->f_mode & FMODE_WRITE) && + (val == 6))) + val = 2; + dmabuf->fmt |= TRIDENT_FMT_STEREO; + } else + dmabuf->fmt &= ~TRIDENT_FMT_STEREO; + state->chans_num = val; + } + unlock_set_fmt(state); + } + ret = put_user(val, p); + break; + + case SNDCTL_DSP_POST: + /* Cause the working fragment to be output */ + break; + + case SNDCTL_DSP_SUBDIVIDE: + if (dmabuf->subdivision) { + ret = -EINVAL; + break; + } + if (get_user(val, p)) { + ret = -EFAULT; + break; + } + if (val != 1 && val != 2 && val != 4) { + ret = -EINVAL; + break; + } + dmabuf->subdivision = val; + break; + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) { + ret = -EFAULT; + break; + } + + dmabuf->ossfragshift = val & 0xffff; + dmabuf->ossmaxfrags = (val >> 16) & 0xffff; + if (dmabuf->ossfragshift < 4) + dmabuf->ossfragshift = 4; + if (dmabuf->ossfragshift > 15) + dmabuf->ossfragshift = 15; + if (dmabuf->ossmaxfrags < 4) + dmabuf->ossmaxfrags = 4; + + break; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) { + ret = -EINVAL; + break; + } + if (!dmabuf->ready && (val = prog_dmabuf_playback(state)) != 0) { + ret = val; + break; + } + spin_lock_irqsave(&state->card->lock, flags); + trident_update_ptr(state); + abinfo.fragsize = dmabuf->fragsize; + abinfo.bytes = dmabuf->dmasize - dmabuf->count; + abinfo.fragstotal = dmabuf->numfrag; + abinfo.fragments = abinfo.bytes >> dmabuf->fragshift; + spin_unlock_irqrestore(&state->card->lock, flags); + ret = copy_to_user(argp, &abinfo, sizeof (abinfo)) ? + -EFAULT : 0; + break; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) { + ret = -EINVAL; + break; + } + if (!dmabuf->ready && (val = prog_dmabuf_record(state)) != 0) { + ret = val; + break; + } + spin_lock_irqsave(&state->card->lock, flags); + trident_update_ptr(state); + abinfo.fragsize = dmabuf->fragsize; + abinfo.bytes = dmabuf->count; + abinfo.fragstotal = dmabuf->numfrag; + abinfo.fragments = abinfo.bytes >> dmabuf->fragshift; + spin_unlock_irqrestore(&state->card->lock, flags); + ret = copy_to_user(argp, &abinfo, sizeof (abinfo)) ? + -EFAULT : 0; + break; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + break; + + case SNDCTL_DSP_GETCAPS: + ret = put_user(DSP_CAP_REALTIME | DSP_CAP_TRIGGER | + DSP_CAP_MMAP | DSP_CAP_BIND, p); + break; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if ((file->f_mode & FMODE_READ) && dmabuf->enable) + val |= PCM_ENABLE_INPUT; + if ((file->f_mode & FMODE_WRITE) && dmabuf->enable) + val |= PCM_ENABLE_OUTPUT; + ret = put_user(val, p); + break; + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, p)) { + ret = -EFAULT; + break; + } + if (file->f_mode & FMODE_READ) { + if (val & PCM_ENABLE_INPUT) { + if (!dmabuf->ready && + (ret = prog_dmabuf_record(state))) + break; + start_adc(state); + } else + stop_adc(state); + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) { + if (!dmabuf->ready && + (ret = prog_dmabuf_playback(state))) + break; + start_dac(state); + } else + stop_dac(state); + } + break; + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) { + ret = -EINVAL; + break; + } + if (!dmabuf->ready && (val = prog_dmabuf_record(state)) + != 0) { + ret = val; + break; + } + spin_lock_irqsave(&state->card->lock, flags); + trident_update_ptr(state); + cinfo.bytes = dmabuf->total_bytes; + cinfo.blocks = dmabuf->count >> dmabuf->fragshift; + cinfo.ptr = dmabuf->hwptr; + if (dmabuf->mapped) + dmabuf->count &= dmabuf->fragsize - 1; + spin_unlock_irqrestore(&state->card->lock, flags); + ret = copy_to_user(argp, &cinfo, sizeof (cinfo)) ? + -EFAULT : 0; + break; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) { + ret = -EINVAL; + break; + } + if (!dmabuf->ready && (val = prog_dmabuf_playback(state)) + != 0) { + ret = val; + break; + } + + spin_lock_irqsave(&state->card->lock, flags); + trident_update_ptr(state); + cinfo.bytes = dmabuf->total_bytes; + cinfo.blocks = dmabuf->count >> dmabuf->fragshift; + cinfo.ptr = dmabuf->hwptr; + if (dmabuf->mapped) + dmabuf->count &= dmabuf->fragsize - 1; + spin_unlock_irqrestore(&state->card->lock, flags); + ret = copy_to_user(argp, &cinfo, sizeof (cinfo)) ? + -EFAULT : 0; + break; + + case SNDCTL_DSP_SETDUPLEX: + ret = -EINVAL; + break; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) { + ret = -EINVAL; + break; + } + if (!dmabuf->ready && (val = prog_dmabuf_playback(state)) != 0) { + ret = val; + break; + } + spin_lock_irqsave(&state->card->lock, flags); + trident_update_ptr(state); + val = dmabuf->count; + spin_unlock_irqrestore(&state->card->lock, flags); + ret = put_user(val, p); + break; + + case SOUND_PCM_READ_RATE: + ret = put_user(dmabuf->rate, p); + break; + + case SOUND_PCM_READ_CHANNELS: + ret = put_user((dmabuf->fmt & TRIDENT_FMT_STEREO) ? 2 : 1, + p); + break; + + case SOUND_PCM_READ_BITS: + ret = put_user((dmabuf->fmt & TRIDENT_FMT_16BIT) ? AFMT_S16_LE : + AFMT_U8, p); + break; + + case SNDCTL_DSP_GETCHANNELMASK: + ret = put_user(DSP_BIND_FRONT | DSP_BIND_SURR | + DSP_BIND_CENTER_LFE, p); + break; + + case SNDCTL_DSP_BIND_CHANNEL: + if (state->card->pci_id != PCI_DEVICE_ID_SI_7018) { + ret = -EINVAL; + break; + } + + if (get_user(val, p)) { + ret = -EFAULT; + break; + } + if (val == DSP_BIND_QUERY) { + val = dmabuf->channel->attribute | 0x3c00; + val = attr2mask[val >> 8]; + } else { + dmabuf->ready = 0; + if (file->f_mode & FMODE_READ) + dmabuf->channel->attribute = (CHANNEL_REC | + SRC_ENABLE); + if (file->f_mode & FMODE_WRITE) + dmabuf->channel->attribute = (CHANNEL_SPC_PB | + SRC_ENABLE); + dmabuf->channel->attribute |= mask2attr[ffs(val)]; + } + ret = put_user(val, p); + break; + + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + default: + ret = -EINVAL; + break; + + } + return ret; +} + +static int +trident_open(struct inode *inode, struct file *file) +{ + int i = 0; + int minor = iminor(inode); + struct trident_card *card = devs; + struct trident_state *state = NULL; + struct dmabuf *dmabuf = NULL; + + /* Added by Matt Wu 01-05-2001 */ + /* TODO: there's some redundacy here wrt the check below */ + /* for multi_use_count > 0. Should we return -EBUSY or find */ + /* a different card? for now, don't break current behaviour */ + /* -- mulix */ + if (file->f_mode & FMODE_READ) { + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { + if (card->multi_channel_use_count > 0) + return -EBUSY; + } + } + + /* find an available virtual channel (instance of /dev/dsp) */ + while (card != NULL) { + down(&card->open_sem); + if (file->f_mode & FMODE_READ) { + /* Skip opens on cards that are in 6 channel mode */ + if (card->multi_channel_use_count > 0) { + up(&card->open_sem); + card = card->next; + continue; + } + } + for (i = 0; i < NR_HW_CH; i++) { + if (card->states[i] == NULL) { + state = card->states[i] = kmalloc(sizeof(*state), GFP_KERNEL); + if (state == NULL) { + up(&card->open_sem); + return -ENOMEM; + } + memset(state, 0, sizeof(*state)); + init_MUTEX(&state->sem); + dmabuf = &state->dmabuf; + goto found_virt; + } + } + up(&card->open_sem); + card = card->next; + } + /* no more virtual channel avaiable */ + if (!state) { + return -ENODEV; + } + found_virt: + /* found a free virtual channel, allocate hardware channels */ + if (file->f_mode & FMODE_READ) + dmabuf->channel = card->alloc_rec_pcm_channel(card); + else + dmabuf->channel = card->alloc_pcm_channel(card); + + if (dmabuf->channel == NULL) { + kfree(card->states[i]); + card->states[i] = NULL; + return -ENODEV; + } + + /* initialize the virtual channel */ + state->virt = i; + state->card = card; + state->magic = TRIDENT_STATE_MAGIC; + init_waitqueue_head(&dmabuf->wait); + file->private_data = state; + + /* set default sample format. According to OSS Programmer's */ + /* Guide /dev/dsp should be default to unsigned 8-bits, mono, */ + /* with sample rate 8kHz and /dev/dspW will accept 16-bits sample */ + if (file->f_mode & FMODE_WRITE) { + dmabuf->fmt &= ~TRIDENT_FMT_MASK; + if ((minor & 0x0f) == SND_DEV_DSP16) + dmabuf->fmt |= TRIDENT_FMT_16BIT; + dmabuf->ossfragshift = 0; + dmabuf->ossmaxfrags = 0; + dmabuf->subdivision = 0; + if (card->pci_id == PCI_DEVICE_ID_SI_7018) { + /* set default channel attribute to normal playback */ + dmabuf->channel->attribute = CHANNEL_PB; + } + trident_set_dac_rate(state, 8000); + } + + if (file->f_mode & FMODE_READ) { + /* FIXME: Trident 4d can only record in signed 16-bits stereo, */ + /* 48kHz sample, to be dealed with in trident_set_adc_rate() ?? */ + dmabuf->fmt &= ~TRIDENT_FMT_MASK; + if ((minor & 0x0f) == SND_DEV_DSP16) + dmabuf->fmt |= TRIDENT_FMT_16BIT; + dmabuf->ossfragshift = 0; + dmabuf->ossmaxfrags = 0; + dmabuf->subdivision = 0; + if (card->pci_id == PCI_DEVICE_ID_SI_7018) { + /* set default channel attribute to 0x8a80, record from + PCM L/R FIFO and mono = (left + right + 1)/2 */ + dmabuf->channel->attribute = (CHANNEL_REC | PCM_LR | + MONO_MIX); + } + trident_set_adc_rate(state, 8000); + + /* Added by Matt Wu 01-05-2001 */ + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) + card->rec_channel_use_count++; + } + + state->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + up(&card->open_sem); + + pr_debug("trident: open virtual channel %d, hard channel %d\n", + state->virt, dmabuf->channel->num); + + return nonseekable_open(inode, file); +} + +static int +trident_release(struct inode *inode, struct file *file) +{ + struct trident_state *state = (struct trident_state *)file->private_data; + struct trident_card *card; + struct dmabuf *dmabuf; + + VALIDATE_STATE(state); + + card = state->card; + dmabuf = &state->dmabuf; + + if (file->f_mode & FMODE_WRITE) { + trident_clear_tail(state); + drain_dac(state, file->f_flags & O_NONBLOCK); + } + + pr_debug("trident: closing virtual channel %d, hard channel %d\n", + state->virt, dmabuf->channel->num); + + /* stop DMA state machine and free DMA buffers/channels */ + down(&card->open_sem); + + if (file->f_mode & FMODE_WRITE) { + stop_dac(state); + dealloc_dmabuf(&state->dmabuf, state->card->pci_dev); + state->card->free_pcm_channel(state->card, dmabuf->channel->num); + + /* Added by Matt Wu */ + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { + if (state->chans_num > 2) { + if (card->multi_channel_use_count-- < 0) + card->multi_channel_use_count = 0; + if (card->multi_channel_use_count == 0) + ali_close_multi_channels(); + ali_free_other_states_resources(state); + } + } + } + if (file->f_mode & FMODE_READ) { + stop_adc(state); + dealloc_dmabuf(&state->dmabuf, state->card->pci_dev); + state->card->free_pcm_channel(state->card, dmabuf->channel->num); + + /* Added by Matt Wu */ + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { + if (card->rec_channel_use_count-- < 0) + card->rec_channel_use_count = 0; + } + } + + card->states[state->virt] = NULL; + kfree(state); + + /* we're covered by the open_sem */ + up(&card->open_sem); + + return 0; +} + +static /*const */ struct file_operations trident_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = trident_read, + .write = trident_write, + .poll = trident_poll, + .ioctl = trident_ioctl, + .mmap = trident_mmap, + .open = trident_open, + .release = trident_release, +}; + +/* trident specific AC97 functions */ +/* Write AC97 codec registers */ +static void +trident_ac97_set(struct ac97_codec *codec, u8 reg, u16 val) +{ + struct trident_card *card = (struct trident_card *)codec->private_data; + unsigned int address, mask, busy; + unsigned short count = 0xffff; + unsigned long flags; + u32 data; + + data = ((u32) val) << 16; + + switch (card->pci_id) { + default: + case PCI_DEVICE_ID_SI_7018: + address = SI_AC97_WRITE; + mask = SI_AC97_BUSY_WRITE | SI_AC97_AUDIO_BUSY; + if (codec->id) + mask |= SI_AC97_SECONDARY; + busy = SI_AC97_BUSY_WRITE; + break; + case PCI_DEVICE_ID_TRIDENT_4DWAVE_DX: + address = DX_ACR0_AC97_W; + mask = busy = DX_AC97_BUSY_WRITE; + break; + case PCI_DEVICE_ID_TRIDENT_4DWAVE_NX: + address = NX_ACR1_AC97_W; + mask = NX_AC97_BUSY_WRITE; + if (codec->id) + mask |= NX_AC97_WRITE_SECONDARY; + busy = NX_AC97_BUSY_WRITE; + break; + case PCI_DEVICE_ID_INTERG_5050: + address = SI_AC97_WRITE; + mask = busy = SI_AC97_BUSY_WRITE; + if (codec->id) + mask |= SI_AC97_SECONDARY; + break; + } + + spin_lock_irqsave(&card->lock, flags); + do { + if ((inw(TRID_REG(card, address)) & busy) == 0) + break; + } while (count--); + + data |= (mask | (reg & AC97_REG_ADDR)); + + if (count == 0) { + printk(KERN_ERR "trident: AC97 CODEC write timed out.\n"); + spin_unlock_irqrestore(&card->lock, flags); + return; + } + + outl(data, TRID_REG(card, address)); + spin_unlock_irqrestore(&card->lock, flags); +} + +/* Read AC97 codec registers */ +static u16 +trident_ac97_get(struct ac97_codec *codec, u8 reg) +{ + struct trident_card *card = (struct trident_card *)codec->private_data; + unsigned int address, mask, busy; + unsigned short count = 0xffff; + unsigned long flags; + u32 data; + + switch (card->pci_id) { + default: + case PCI_DEVICE_ID_SI_7018: + address = SI_AC97_READ; + mask = SI_AC97_BUSY_READ | SI_AC97_AUDIO_BUSY; + if (codec->id) + mask |= SI_AC97_SECONDARY; + busy = SI_AC97_BUSY_READ; + break; + case PCI_DEVICE_ID_TRIDENT_4DWAVE_DX: + address = DX_ACR1_AC97_R; + mask = busy = DX_AC97_BUSY_READ; + break; + case PCI_DEVICE_ID_TRIDENT_4DWAVE_NX: + if (codec->id) + address = NX_ACR3_AC97_R_SECONDARY; + else + address = NX_ACR2_AC97_R_PRIMARY; + mask = NX_AC97_BUSY_READ; + busy = NX_AC97_BUSY_READ | NX_AC97_BUSY_DATA; + break; + case PCI_DEVICE_ID_INTERG_5050: + address = SI_AC97_READ; + mask = busy = SI_AC97_BUSY_READ; + if (codec->id) + mask |= SI_AC97_SECONDARY; + break; + } + + data = (mask | (reg & AC97_REG_ADDR)); + + spin_lock_irqsave(&card->lock, flags); + outl(data, TRID_REG(card, address)); + do { + data = inl(TRID_REG(card, address)); + if ((data & busy) == 0) + break; + } while (count--); + spin_unlock_irqrestore(&card->lock, flags); + + if (count == 0) { + printk(KERN_ERR "trident: AC97 CODEC read timed out.\n"); + data = 0; + } + return ((u16) (data >> 16)); +} + +/* rewrite ac97 read and write mixer register by hulei for ALI*/ +static int +acquirecodecaccess(struct trident_card *card) +{ + u16 wsemamask = 0x6000; /* bit 14..13 */ + u16 wsemabits; + u16 wcontrol; + int block = 0; + int ncount = 25; + while (1) { + wcontrol = inw(TRID_REG(card, ALI_AC97_WRITE)); + wsemabits = wcontrol & wsemamask; + + if (wsemabits == 0x4000) + return 1; /* 0x4000 is audio ,then success */ + if (ncount-- < 0) + break; + if (wsemabits == 0) { + unlock: + outl(((u32) (wcontrol & 0x1eff) | 0x00004000), + TRID_REG(card, ALI_AC97_WRITE)); + continue; + } + udelay(20); + } + if (!block) { + pr_debug("accesscodecsemaphore: try unlock\n"); + block = 1; + goto unlock; + } + return 0; +} + +static void +releasecodecaccess(struct trident_card *card) +{ + unsigned long wcontrol; + wcontrol = inl(TRID_REG(card, ALI_AC97_WRITE)); + outl((wcontrol & 0xffff1eff), TRID_REG(card, ALI_AC97_WRITE)); +} + +static int +waitforstimertick(struct trident_card *card) +{ + unsigned long chk1, chk2; + unsigned int wcount = 0xffff; + chk1 = inl(TRID_REG(card, ALI_STIMER)); + + while (1) { + chk2 = inl(TRID_REG(card, ALI_STIMER)); + if ((wcount > 0) && chk1 != chk2) + return 1; + if (wcount <= 0) + break; + udelay(50); + } + return 0; +} + +/* Read AC97 codec registers for ALi*/ +static u16 +ali_ac97_get(struct trident_card *card, int secondary, u8 reg) +{ + unsigned int address, mask; + unsigned int ncount; + unsigned long aud_reg; + u32 data; + u16 wcontrol; + unsigned long flags; + + if (!card) + BUG(); + + address = ALI_AC97_READ; + if (card->revision == ALI_5451_V02) { + address = ALI_AC97_WRITE; + } + mask = ALI_AC97_READ_ACTION | ALI_AC97_AUDIO_BUSY; + if (secondary) + mask |= ALI_AC97_SECONDARY; + + spin_lock_irqsave(&card->lock, flags); + + if (!acquirecodecaccess(card)) + printk(KERN_ERR "access codec fail\n"); + + wcontrol = inw(TRID_REG(card, ALI_AC97_WRITE)); + wcontrol &= 0xfe00; + wcontrol |= (0x8000 | reg); + outw(wcontrol, TRID_REG(card, ALI_AC97_WRITE)); + + data = (mask | (reg & AC97_REG_ADDR)); + + if (!waitforstimertick(card)) { + printk(KERN_ERR "ali_ac97_read: BIT_CLOCK is dead\n"); + goto releasecodec; + } + + udelay(20); + + ncount = 10; + + while (1) { + if ((inw(TRID_REG(card, ALI_AC97_WRITE)) & ALI_AC97_BUSY_READ) + != 0) + break; + if (ncount <= 0) + break; + if (ncount-- == 1) { + pr_debug("ali_ac97_read :try clear busy flag\n"); + aud_reg = inl(TRID_REG(card, ALI_AC97_WRITE)); + outl((aud_reg & 0xffff7fff), + TRID_REG(card, ALI_AC97_WRITE)); + } + udelay(10); + } + + data = inl(TRID_REG(card, address)); + + spin_unlock_irqrestore(&card->lock, flags); + + return ((u16) (data >> 16)); + + releasecodec: + releasecodecaccess(card); + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_ERR "ali_ac97_read: AC97 CODEC read timed out.\n"); + return 0; +} + +/* Write AC97 codec registers for hulei*/ +static void +ali_ac97_set(struct trident_card *card, int secondary, u8 reg, u16 val) +{ + unsigned int address, mask; + unsigned int ncount; + u32 data; + u16 wcontrol; + unsigned long flags; + + data = ((u32) val) << 16; + + if (!card) + BUG(); + + address = ALI_AC97_WRITE; + mask = ALI_AC97_WRITE_ACTION | ALI_AC97_AUDIO_BUSY; + if (secondary) + mask |= ALI_AC97_SECONDARY; + if (card->revision == ALI_5451_V02) + mask |= ALI_AC97_WRITE_MIXER_REGISTER; + + spin_lock_irqsave(&card->lock, flags); + if (!acquirecodecaccess(card)) + printk(KERN_ERR "ali_ac97_write: access codec fail\n"); + + wcontrol = inw(TRID_REG(card, ALI_AC97_WRITE)); + wcontrol &= 0xff00; + wcontrol |= (0x8100 | reg); /* bit 8=1: (ali1535 )reserved/ */ + /* ali1535+ write */ + outl((data | wcontrol), TRID_REG(card, ALI_AC97_WRITE)); + + if (!waitforstimertick(card)) { + printk(KERN_ERR "BIT_CLOCK is dead\n"); + goto releasecodec; + } + + ncount = 10; + while (1) { + wcontrol = inw(TRID_REG(card, ALI_AC97_WRITE)); + if (!(wcontrol & 0x8000)) + break; + if (ncount <= 0) + break; + if (ncount-- == 1) { + pr_debug("ali_ac97_set :try clear busy flag!!\n"); + outw(wcontrol & 0x7fff, + TRID_REG(card, ALI_AC97_WRITE)); + } + udelay(10); + } + + releasecodec: + releasecodecaccess(card); + spin_unlock_irqrestore(&card->lock, flags); + return; +} + +static void +ali_enable_special_channel(struct trident_state *stat) +{ + struct trident_card *card = stat->card; + unsigned long s_channels; + + s_channels = inl(TRID_REG(card, ALI_GLOBAL_CONTROL)); + s_channels |= (1 << stat->dmabuf.channel->num); + outl(s_channels, TRID_REG(card, ALI_GLOBAL_CONTROL)); +} + +static u16 +ali_ac97_read(struct ac97_codec *codec, u8 reg) +{ + int id; + u16 data; + struct trident_card *card = NULL; + + /* Added by Matt Wu */ + if (!codec) + BUG(); + + card = (struct trident_card *) codec->private_data; + + if (!card->mixer_regs_ready) + return ali_ac97_get(card, codec->id, reg); + + /* + * FIXME: need to stop this caching some registers + */ + if (codec->id) + id = 1; + else + id = 0; + + data = card->mixer_regs[reg / 2][id]; + return data; +} + +static void +ali_ac97_write(struct ac97_codec *codec, u8 reg, u16 val) +{ + int id; + struct trident_card *card; + + /* Added by Matt Wu */ + if (!codec) + BUG(); + + card = (struct trident_card *) codec->private_data; + + if (!card->mixer_regs_ready) { + ali_ac97_set(card, codec->id, reg, val); + return; + } + + if (codec->id) + id = 1; + else + id = 0; + + card->mixer_regs[reg / 2][id] = val; + ali_ac97_set(card, codec->id, reg, val); +} + +/* +flag: ALI_SPDIF_OUT_TO_SPDIF_OUT + ALI_PCM_TO_SPDIF_OUT +*/ + +static void +ali_setup_spdif_out(struct trident_card *card, int flag) +{ + unsigned long spdif; + unsigned char ch; + + char temp; + struct pci_dev *pci_dev = NULL; + + pci_dev = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, + pci_dev); + if (pci_dev == NULL) + return; + pci_read_config_byte(pci_dev, 0x61, &temp); + temp |= 0x40; + pci_write_config_byte(pci_dev, 0x61, temp); + pci_read_config_byte(pci_dev, 0x7d, &temp); + temp |= 0x01; + pci_write_config_byte(pci_dev, 0x7d, temp); + pci_read_config_byte(pci_dev, 0x7e, &temp); + temp &= (~0x20); + temp |= 0x10; + pci_write_config_byte(pci_dev, 0x7e, temp); + + ch = inb(TRID_REG(card, ALI_SCTRL)); + outb(ch | ALI_SPDIF_OUT_ENABLE, TRID_REG(card, ALI_SCTRL)); + ch = inb(TRID_REG(card, ALI_SPDIF_CTRL)); + outb(ch & ALI_SPDIF_OUT_CH_STATUS, TRID_REG(card, ALI_SPDIF_CTRL)); + + if (flag & ALI_SPDIF_OUT_TO_SPDIF_OUT) { + spdif = inw(TRID_REG(card, ALI_GLOBAL_CONTROL)); + spdif |= ALI_SPDIF_OUT_CH_ENABLE; + spdif &= ALI_SPDIF_OUT_SEL_SPDIF; + outw(spdif, TRID_REG(card, ALI_GLOBAL_CONTROL)); + spdif = inw(TRID_REG(card, ALI_SPDIF_CS)); + if (flag & ALI_SPDIF_OUT_NON_PCM) + spdif |= 0x0002; + else + spdif &= (~0x0002); + outw(spdif, TRID_REG(card, ALI_SPDIF_CS)); + } else { + spdif = inw(TRID_REG(card, ALI_GLOBAL_CONTROL)); + spdif |= ALI_SPDIF_OUT_SEL_PCM; + outw(spdif, TRID_REG(card, ALI_GLOBAL_CONTROL)); + } +} + +static void +ali_disable_special_channel(struct trident_card *card, int ch) +{ + unsigned long sc; + + sc = inl(TRID_REG(card, ALI_GLOBAL_CONTROL)); + sc &= ~(1 << ch); + outl(sc, TRID_REG(card, ALI_GLOBAL_CONTROL)); +} + +static void +ali_disable_spdif_in(struct trident_card *card) +{ + unsigned long spdif; + + spdif = inl(TRID_REG(card, ALI_GLOBAL_CONTROL)); + spdif &= (~ALI_SPDIF_IN_SUPPORT); + outl(spdif, TRID_REG(card, ALI_GLOBAL_CONTROL)); + + ali_disable_special_channel(card, ALI_SPDIF_IN_CHANNEL); +} + +static void +ali_setup_spdif_in(struct trident_card *card) +{ + unsigned long spdif; + + //Set SPDIF IN Supported + spdif = inl(TRID_REG(card, ALI_GLOBAL_CONTROL)); + spdif |= ALI_SPDIF_IN_SUPPORT; + outl(spdif, TRID_REG(card, ALI_GLOBAL_CONTROL)); + + //Set SPDIF IN Rec + spdif = inl(TRID_REG(card, ALI_GLOBAL_CONTROL)); + spdif |= ALI_SPDIF_IN_CH_ENABLE; + outl(spdif, TRID_REG(card, ALI_GLOBAL_CONTROL)); + + spdif = inb(TRID_REG(card, ALI_SPDIF_CTRL)); + spdif |= ALI_SPDIF_IN_CH_STATUS; + outb(spdif, TRID_REG(card, ALI_SPDIF_CTRL)); +/* + spdif = inb(TRID_REG(card, ALI_SPDIF_CTRL)); + spdif |= ALI_SPDIF_IN_FUNC_ENABLE; + outb(spdif, TRID_REG(card, ALI_SPDIF_CTRL)); +*/ +} + +static void +ali_delay(struct trident_card *card, int interval) +{ + unsigned long begintimer, currenttimer; + + begintimer = inl(TRID_REG(card, ALI_STIMER)); + currenttimer = inl(TRID_REG(card, ALI_STIMER)); + + while (currenttimer < begintimer + interval) + currenttimer = inl(TRID_REG(card, ALI_STIMER)); +} + +static void +ali_detect_spdif_rate(struct trident_card *card) +{ + u16 wval = 0; + u16 count = 0; + u8 bval = 0, R1 = 0, R2 = 0; + + bval = inb(TRID_REG(card, ALI_SPDIF_CTRL)); + bval |= 0x02; + outb(bval, TRID_REG(card, ALI_SPDIF_CTRL)); + + bval = inb(TRID_REG(card, ALI_SPDIF_CTRL + 1)); + bval |= 0x1F; + outb(bval, TRID_REG(card, ALI_SPDIF_CTRL + 1)); + + while (((R1 < 0x0B) || (R1 > 0x0E)) && (R1 != 0x12) && + count <= 50000) { + count++; + + ali_delay(card, 6); + + bval = inb(TRID_REG(card, ALI_SPDIF_CTRL + 1)); + R1 = bval & 0x1F; + } + + if (count > 50000) { + printk(KERN_WARNING "trident: Error in " + "ali_detect_spdif_rate!\n"); + return; + } + + count = 0; + + while (count <= 50000) { + count++; + + ali_delay(card, 6); + + bval = inb(TRID_REG(card, ALI_SPDIF_CTRL + 1)); + R2 = bval & 0x1F; + + if (R2 != R1) + R1 = R2; + else + break; + } + + if (count > 50000) { + printk(KERN_WARNING "trident: Error in " + "ali_detect_spdif_rate!\n"); + return; + } + + switch (R2) { + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + wval = inw(TRID_REG(card, ALI_SPDIF_CTRL + 2)); + wval &= 0xE0F0; + wval |= (u16) 0x09 << 8 | (u16) 0x05; + outw(wval, TRID_REG(card, ALI_SPDIF_CTRL + 2)); + + bval = inb(TRID_REG(card, ALI_SPDIF_CS + 3)) & 0xF0; + outb(bval | 0x02, TRID_REG(card, ALI_SPDIF_CS + 3)); + break; + + case 0x12: + wval = inw(TRID_REG(card, ALI_SPDIF_CTRL + 2)); + wval &= 0xE0F0; + wval |= (u16) 0x0E << 8 | (u16) 0x08; + outw(wval, TRID_REG(card, ALI_SPDIF_CTRL + 2)); + + bval = inb(TRID_REG(card, ALI_SPDIF_CS + 3)) & 0xF0; + outb(bval | 0x03, TRID_REG(card, ALI_SPDIF_CS + 3)); + break; + + default: + break; + } + +} + +static unsigned int +ali_get_spdif_in_rate(struct trident_card *card) +{ + u32 dwRate = 0; + u8 bval = 0; + + ali_detect_spdif_rate(card); + + bval = inb(TRID_REG(card, ALI_SPDIF_CTRL)); + bval &= 0x7F; + bval |= 0x40; + outb(bval, TRID_REG(card, ALI_SPDIF_CTRL)); + + bval = inb(TRID_REG(card, ALI_SPDIF_CS + 3)); + bval &= 0x0F; + + switch (bval) { + case 0: + dwRate = 44100; + break; + case 1: + dwRate = 48000; + break; + case 2: + dwRate = 32000; + break; + default: + // Error occurs + break; + } + + return dwRate; + +} + +static int +ali_close_multi_channels(void) +{ + char temp = 0; + struct pci_dev *pci_dev = NULL; + + pci_dev = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, + pci_dev); + if (pci_dev == NULL) + return -1; + pci_read_config_byte(pci_dev, 0x59, &temp); + temp &= ~0x80; + pci_write_config_byte(pci_dev, 0x59, temp); + + pci_dev = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101, + pci_dev); + if (pci_dev == NULL) + return -1; + + pci_read_config_byte(pci_dev, 0xB8, &temp); + temp &= ~0x20; + pci_write_config_byte(pci_dev, 0xB8, temp); + + return 0; +} + +static int +ali_setup_multi_channels(struct trident_card *card, int chan_nums) +{ + unsigned long dwValue; + char temp = 0; + struct pci_dev *pci_dev = NULL; + + pci_dev = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, + pci_dev); + if (pci_dev == NULL) + return -1; + pci_read_config_byte(pci_dev, 0x59, &temp); + temp |= 0x80; + pci_write_config_byte(pci_dev, 0x59, temp); + + pci_dev = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101, + pci_dev); + if (pci_dev == NULL) + return -1; + pci_read_config_byte(pci_dev, (int) 0xB8, &temp); + temp |= 0x20; + pci_write_config_byte(pci_dev, (int) 0xB8, (u8) temp); + if (chan_nums == 6) { + dwValue = inl(TRID_REG(card, ALI_SCTRL)) | 0x000f0000; + outl(dwValue, TRID_REG(card, ALI_SCTRL)); + mdelay(4); + dwValue = inl(TRID_REG(card, ALI_SCTRL)); + if (dwValue & 0x2000000) { + ali_ac97_write(card->ac97_codec[0], 0x02, 8080); + ali_ac97_write(card->ac97_codec[0], 0x36, 0); + ali_ac97_write(card->ac97_codec[0], 0x38, 0); + /* + * On a board with a single codec you won't get the + * surround. On other boards configure it. + */ + if (card->ac97_codec[1] != NULL) { + ali_ac97_write(card->ac97_codec[1], 0x36, 0); + ali_ac97_write(card->ac97_codec[1], 0x38, 0); + ali_ac97_write(card->ac97_codec[1], 0x02, 0x0606); + ali_ac97_write(card->ac97_codec[1], 0x18, 0x0303); + ali_ac97_write(card->ac97_codec[1], 0x74, 0x3); + } + return 1; + } + } + return -EINVAL; +} + +static void +ali_free_pcm_channel(struct trident_card *card, unsigned int channel) +{ + int bank; + + if (channel > 31) + return; + + bank = channel >> 5; + channel = channel & 0x1f; + + card->banks[bank].bitmap &= ~(1 << (channel)); +} + +static int +ali_allocate_other_states_resources(struct trident_state *state, int chan_nums) +{ + struct trident_card *card = state->card; + struct trident_state *s; + int i, state_count = 0; + struct trident_pcm_bank *bank; + struct trident_channel *channel; + unsigned long num; + + bank = &card->banks[BANK_A]; + + if (chan_nums != 6) + return 0; + + for (i = 0; (i < ALI_CHANNELS) && (state_count != 4); i++) { + if (card->states[i]) + continue; + + num = ali_multi_channels_5_1[state_count]; + if (!(bank->bitmap & (1 << num))) { + bank->bitmap |= 1 << num; + channel = &bank->channels[num]; + channel->num = num; + } else { + state_count--; + for (; state_count >= 0; state_count--) { + kfree(state->other_states[state_count]); + num = ali_multi_channels_5_1[state_count]; + ali_free_pcm_channel(card, num); + } + return -EBUSY; + } + s = card->states[i] = kmalloc(sizeof(*state), GFP_KERNEL); + if (!s) { + num = ali_multi_channels_5_1[state_count]; + ali_free_pcm_channel(card, num); + state_count--; + for (; state_count >= 0; state_count--) { + num = ali_multi_channels_5_1[state_count]; + ali_free_pcm_channel(card, num); + kfree(state->other_states[state_count]); + } + return -ENOMEM; + } + memset(s, 0, sizeof(*state)); + + s->dmabuf.channel = channel; + s->dmabuf.ossfragshift = s->dmabuf.ossmaxfrags = + s->dmabuf.subdivision = 0; + init_waitqueue_head(&s->dmabuf.wait); + s->magic = card->magic; + s->card = card; + s->virt = i; + ali_enable_special_channel(s); + state->other_states[state_count++] = s; + } + + if (state_count != 4) { + state_count--; + for (; state_count >= 0; state_count--) { + kfree(state->other_states[state_count]); + num = ali_multi_channels_5_1[state_count]; + ali_free_pcm_channel(card, num); + } + return -EBUSY; + } + return 0; +} + +static void +ali_save_regs(struct trident_card *card) +{ + unsigned long flags; + int i, j; + + spin_lock_irqsave(&card->lock, flags); + + ali_registers.global_regs[0x2c] = inl(TRID_REG(card, T4D_MISCINT)); + //ali_registers.global_regs[0x20] = inl(TRID_REG(card,T4D_START_A)); + ali_registers.global_regs[0x21] = inl(TRID_REG(card, T4D_STOP_A)); + + //disable all IRQ bits + outl(ALI_DISABLE_ALL_IRQ, TRID_REG(card, T4D_MISCINT)); + + for (i = 1; i < ALI_MIXER_REGS; i++) + ali_registers.mixer_regs[i] = ali_ac97_read(card->ac97_codec[0], + i * 2); + + for (i = 0; i < ALI_GLOBAL_REGS; i++) { + if ((i * 4 == T4D_MISCINT) || (i * 4 == T4D_STOP_A)) + continue; + ali_registers.global_regs[i] = inl(TRID_REG(card, i * 4)); + } + + for (i = 0; i < ALI_CHANNELS; i++) { + outb(i, TRID_REG(card, T4D_LFO_GC_CIR)); + for (j = 0; j < ALI_CHANNEL_REGS; j++) + ali_registers.channel_regs[i][j] = inl(TRID_REG(card, + j * 4 + 0xe0)); + } + + //Stop all HW channel + outl(ALI_STOP_ALL_CHANNELS, TRID_REG(card, T4D_STOP_A)); + + spin_unlock_irqrestore(&card->lock, flags); +} + +static void +ali_restore_regs(struct trident_card *card) +{ + unsigned long flags; + int i, j; + + spin_lock_irqsave(&card->lock, flags); + + for (i = 1; i < ALI_MIXER_REGS; i++) + ali_ac97_write(card->ac97_codec[0], i * 2, + ali_registers.mixer_regs[i]); + + for (i = 0; i < ALI_CHANNELS; i++) { + outb(i, TRID_REG(card, T4D_LFO_GC_CIR)); + for (j = 0; j < ALI_CHANNEL_REGS; j++) + outl(ali_registers.channel_regs[i][j], + TRID_REG(card, j * 4 + 0xe0)); + } + + for (i = 0; i < ALI_GLOBAL_REGS; i++) { + if ((i * 4 == T4D_MISCINT) || (i * 4 == T4D_STOP_A) || + (i * 4 == T4D_START_A)) + continue; + outl(ali_registers.global_regs[i], TRID_REG(card, i * 4)); + } + + //start HW channel + outl(ali_registers.global_regs[0x20], TRID_REG(card, T4D_START_A)); + //restore IRQ enable bits + outl(ali_registers.global_regs[0x2c], TRID_REG(card, T4D_MISCINT)); + + spin_unlock_irqrestore(&card->lock, flags); +} + +static int +trident_suspend(struct pci_dev *dev, pm_message_t unused) +{ + struct trident_card *card = pci_get_drvdata(dev); + + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { + ali_save_regs(card); + } + return 0; +} + +static int +trident_resume(struct pci_dev *dev) +{ + struct trident_card *card = pci_get_drvdata(dev); + + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { + ali_restore_regs(card); + } + return 0; +} + +static struct trident_channel * +ali_alloc_pcm_channel(struct trident_card *card) +{ + struct trident_pcm_bank *bank; + int idx; + + bank = &card->banks[BANK_A]; + + if (inl(TRID_REG(card, ALI_GLOBAL_CONTROL)) & + (ALI_SPDIF_OUT_CH_ENABLE)) { + idx = ALI_SPDIF_OUT_CHANNEL; + if (!(bank->bitmap & (1 << idx))) { + struct trident_channel *channel = &bank->channels[idx]; + bank->bitmap |= 1 << idx; + channel->num = idx; + return channel; + } + } + + for (idx = ALI_PCM_OUT_CHANNEL_FIRST; idx <= ALI_PCM_OUT_CHANNEL_LAST; + idx++) { + if (!(bank->bitmap & (1 << idx))) { + struct trident_channel *channel = &bank->channels[idx]; + bank->bitmap |= 1 << idx; + channel->num = idx; + return channel; + } + } + + /* no more free channels avaliable */ +#if 0 + printk(KERN_ERR "ali: no more channels available on Bank A.\n"); +#endif /* 0 */ + return NULL; +} + +static struct trident_channel * +ali_alloc_rec_pcm_channel(struct trident_card *card) +{ + struct trident_pcm_bank *bank; + int idx; + + if (inl(TRID_REG(card, ALI_GLOBAL_CONTROL)) & ALI_SPDIF_IN_SUPPORT) + idx = ALI_SPDIF_IN_CHANNEL; + else + idx = ALI_PCM_IN_CHANNEL; + + bank = &card->banks[BANK_A]; + + if (!(bank->bitmap & (1 << idx))) { + struct trident_channel *channel = &bank->channels[idx]; + bank->bitmap |= 1 << idx; + channel->num = idx; + return channel; + } + + /* no free recordable channels avaliable */ +#if 0 + printk(KERN_ERR "ali: no recordable channels available on Bank A.\n"); +#endif /* 0 */ + return NULL; +} + +static void +ali_set_spdif_out_rate(struct trident_card *card, unsigned int rate) +{ + unsigned char ch_st_sel; + unsigned short status_rate; + + switch (rate) { + case 44100: + status_rate = 0; + break; + case 32000: + status_rate = 0x300; + break; + case 48000: + default: + status_rate = 0x200; + break; + } + + /* select spdif_out */ + ch_st_sel = inb(TRID_REG(card, ALI_SPDIF_CTRL)) & ALI_SPDIF_OUT_CH_STATUS; + + ch_st_sel |= 0x80; /* select right */ + outb(ch_st_sel, TRID_REG(card, ALI_SPDIF_CTRL)); + outb(status_rate | 0x20, TRID_REG(card, ALI_SPDIF_CS + 2)); + + ch_st_sel &= (~0x80); /* select left */ + outb(ch_st_sel, TRID_REG(card, ALI_SPDIF_CTRL)); + outw(status_rate | 0x10, TRID_REG(card, ALI_SPDIF_CS + 2)); +} + +static void +ali_address_interrupt(struct trident_card *card) +{ + int i, channel; + struct trident_state *state; + u32 mask, channel_mask; + + mask = trident_get_interrupt_mask(card, 0); + for (i = 0; i < NR_HW_CH; i++) { + if ((state = card->states[i]) == NULL) + continue; + channel = state->dmabuf.channel->num; + if ((channel_mask = 1 << channel) & mask) { + mask &= ~channel_mask; + trident_ack_channel_interrupt(card, channel); + udelay(100); + state->dmabuf.update_flag |= ALI_ADDRESS_INT_UPDATE; + trident_update_ptr(state); + } + } + if (mask) { + for (i = 0; i < NR_HW_CH; i++) { + if (mask & (1 << i)) { + printk("ali: spurious channel irq %d.\n", i); + trident_ack_channel_interrupt(card, i); + trident_stop_voice(card, i); + trident_disable_voice_irq(card, i); + } + } + } +} + +/* Updating the values of counters of other_states' DMAs without lock +protection is no harm because all DMAs of multi-channels and interrupt +depend on a master state's DMA, and changing the counters of the master +state DMA is protected by a spinlock. +*/ +static int +ali_write_5_1(struct trident_state *state, const char __user *buf, + int cnt_for_multi_channel, unsigned int *copy_count, + unsigned int *state_cnt) +{ + + struct dmabuf *dmabuf = &state->dmabuf; + struct dmabuf *dmabuf_temp; + const char __user *buffer = buf; + unsigned swptr, other_dma_nums, sample_s; + unsigned int i, loop; + + other_dma_nums = 4; + sample_s = sample_size[dmabuf->fmt] >> 1; + swptr = dmabuf->swptr; + + if ((i = state->multi_channels_adjust_count) > 0) { + if (i == 1) { + if (copy_from_user(dmabuf->rawbuf + swptr, + buffer, sample_s)) + return -EFAULT; + seek_offset(swptr, buffer, cnt_for_multi_channel, + sample_s, *copy_count); + i--; + (*state_cnt) += sample_s; + state->multi_channels_adjust_count++; + } else + i = i - (state->chans_num - other_dma_nums); + for (; (i < other_dma_nums) && (cnt_for_multi_channel > 0); i++) { + dmabuf_temp = &state->other_states[i]->dmabuf; + if (copy_from_user(dmabuf_temp->rawbuf + dmabuf_temp->swptr, + buffer, sample_s)) + return -EFAULT; + seek_offset(dmabuf_temp->swptr, buffer, cnt_for_multi_channel, + sample_s, *copy_count); + } + if (cnt_for_multi_channel == 0) + state->multi_channels_adjust_count += i; + } + if (cnt_for_multi_channel > 0) { + loop = cnt_for_multi_channel / (state->chans_num * sample_s); + for (i = 0; i < loop; i++) { + if (copy_from_user(dmabuf->rawbuf + swptr, buffer, + sample_s * 2)) + return -EFAULT; + seek_offset(swptr, buffer, cnt_for_multi_channel, + sample_s * 2, *copy_count); + (*state_cnt) += (sample_s * 2); + + dmabuf_temp = &state->other_states[0]->dmabuf; + if (copy_from_user(dmabuf_temp->rawbuf + dmabuf_temp->swptr, + buffer, sample_s)) + return -EFAULT; + seek_offset(dmabuf_temp->swptr, buffer, cnt_for_multi_channel, + sample_s, *copy_count); + + dmabuf_temp = &state->other_states[1]->dmabuf; + if (copy_from_user(dmabuf_temp->rawbuf + dmabuf_temp->swptr, + buffer, sample_s)) + return -EFAULT; + seek_offset(dmabuf_temp->swptr, buffer, cnt_for_multi_channel, + sample_s, *copy_count); + + dmabuf_temp = &state->other_states[2]->dmabuf; + if (copy_from_user(dmabuf_temp->rawbuf + dmabuf_temp->swptr, + buffer, sample_s)) + return -EFAULT; + seek_offset(dmabuf_temp->swptr, buffer, cnt_for_multi_channel, + sample_s, *copy_count); + + dmabuf_temp = &state->other_states[3]->dmabuf; + if (copy_from_user(dmabuf_temp->rawbuf + dmabuf_temp->swptr, + buffer, sample_s)) + return -EFAULT; + seek_offset(dmabuf_temp->swptr, buffer, cnt_for_multi_channel, + sample_s, *copy_count); + } + + if (cnt_for_multi_channel > 0) { + state->multi_channels_adjust_count = cnt_for_multi_channel / sample_s; + + if (copy_from_user(dmabuf->rawbuf + swptr, buffer, sample_s)) + return -EFAULT; + seek_offset(swptr, buffer, cnt_for_multi_channel, + sample_s, *copy_count); + (*state_cnt) += sample_s; + + if (cnt_for_multi_channel > 0) { + if (copy_from_user(dmabuf->rawbuf + swptr, + buffer, sample_s)) + return -EFAULT; + seek_offset(swptr, buffer, cnt_for_multi_channel, + sample_s, *copy_count); + (*state_cnt) += sample_s; + + if (cnt_for_multi_channel > 0) { + int diff = state->chans_num - other_dma_nums; + loop = state->multi_channels_adjust_count - diff; + for (i = 0; i < loop; i++) { + dmabuf_temp = &state->other_states[i]->dmabuf; + if (copy_from_user(dmabuf_temp->rawbuf + + dmabuf_temp->swptr, + buffer, sample_s)) + return -EFAULT; + seek_offset(dmabuf_temp->swptr, buffer, + cnt_for_multi_channel, + sample_s, *copy_count); + } + } + } + } else + state->multi_channels_adjust_count = 0; + } + for (i = 0; i < other_dma_nums; i++) { + dmabuf_temp = &state->other_states[i]->dmabuf; + dmabuf_temp->swptr = dmabuf_temp->swptr % dmabuf_temp->dmasize; + } + return *state_cnt; +} + +static void +ali_free_other_states_resources(struct trident_state *state) +{ + int i; + struct trident_card *card = state->card; + struct trident_state *s; + unsigned other_states_count; + + other_states_count = state->chans_num - 2; /* except PCM L/R channels */ + for (i = 0; i < other_states_count; i++) { + s = state->other_states[i]; + dealloc_dmabuf(&s->dmabuf, card->pci_dev); + ali_disable_special_channel(s->card, s->dmabuf.channel->num); + state->card->free_pcm_channel(s->card, s->dmabuf.channel->num); + card->states[s->virt] = NULL; + kfree(s); + } +} + +static struct proc_dir_entry *res; + +static int +ali_write_proc(struct file *file, const char __user *buffer, unsigned long count, void *data) +{ + struct trident_card *card = (struct trident_card *) data; + unsigned long flags; + char c; + + if (count < 0) + return -EINVAL; + if (count == 0) + return 0; + if (get_user(c, buffer)) + return -EFAULT; + + spin_lock_irqsave(&card->lock, flags); + switch (c) { + case '0': + ali_setup_spdif_out(card, ALI_PCM_TO_SPDIF_OUT); + ali_disable_special_channel(card, ALI_SPDIF_OUT_CHANNEL); + break; + case '1': + ali_setup_spdif_out(card, ALI_SPDIF_OUT_TO_SPDIF_OUT | + ALI_SPDIF_OUT_PCM); + break; + case '2': + ali_setup_spdif_out(card, ALI_SPDIF_OUT_TO_SPDIF_OUT | + ALI_SPDIF_OUT_NON_PCM); + break; + case '3': + ali_disable_spdif_in(card); //default + break; + case '4': + ali_setup_spdif_in(card); + break; + } + spin_unlock_irqrestore(&card->lock, flags); + + return count; +} + +/* OSS /dev/mixer file operation methods */ +static int +trident_open_mixdev(struct inode *inode, struct file *file) +{ + int i = 0; + int minor = iminor(inode); + struct trident_card *card = devs; + + for (card = devs; card != NULL; card = card->next) + for (i = 0; i < NR_AC97; i++) + if (card->ac97_codec[i] != NULL && + card->ac97_codec[i]->dev_mixer == minor) + goto match; + + if (!card) { + return -ENODEV; + } + match: + file->private_data = card->ac97_codec[i]; + + return nonseekable_open(inode, file); +} + +static int +trident_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct ac97_codec *codec = (struct ac97_codec *) file->private_data; + + return codec->mixer_ioctl(codec, cmd, arg); +} + +static /*const */ struct file_operations trident_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = trident_ioctl_mixdev, + .open = trident_open_mixdev, +}; + +static int +ali_reset_5451(struct trident_card *card) +{ + struct pci_dev *pci_dev = NULL; + unsigned int dwVal; + unsigned short wCount, wReg; + + pci_dev = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, + pci_dev); + if (pci_dev == NULL) + return -1; + + pci_read_config_dword(pci_dev, 0x7c, &dwVal); + pci_write_config_dword(pci_dev, 0x7c, dwVal | 0x08000000); + udelay(5000); + pci_read_config_dword(pci_dev, 0x7c, &dwVal); + pci_write_config_dword(pci_dev, 0x7c, dwVal & 0xf7ffffff); + udelay(5000); + + pci_dev = card->pci_dev; + if (pci_dev == NULL) + return -1; + + pci_read_config_dword(pci_dev, 0x44, &dwVal); + pci_write_config_dword(pci_dev, 0x44, dwVal | 0x000c0000); + udelay(500); + pci_read_config_dword(pci_dev, 0x44, &dwVal); + pci_write_config_dword(pci_dev, 0x44, dwVal & 0xfffbffff); + udelay(5000); + + /* TODO: recognize if we have a PM capable codec and only do this */ + /* if the codec is PM capable */ + wCount = 2000; + while (wCount--) { + wReg = ali_ac97_get(card, 0, AC97_POWER_CONTROL); + if ((wReg & 0x000f) == 0x000f) + return 0; + udelay(5000); + } + /* This is non fatal if you have a non PM capable codec.. */ + return 0; +} + +/* AC97 codec initialisation. */ +static int __devinit +trident_ac97_init(struct trident_card *card) +{ + int num_ac97 = 0; + unsigned long ready_2nd = 0; + struct ac97_codec *codec; + int i = 0; + + /* initialize controller side of AC link, and find out if secondary codes + really exist */ + switch (card->pci_id) { + case PCI_DEVICE_ID_ALI_5451: + if (ali_reset_5451(card)) { + printk(KERN_ERR "trident_ac97_init: error " + "resetting 5451.\n"); + return -1; + } + outl(0x80000001, TRID_REG(card, ALI_GLOBAL_CONTROL)); + outl(0x00000000, TRID_REG(card, T4D_AINTEN_A)); + outl(0xffffffff, TRID_REG(card, T4D_AINT_A)); + outl(0x00000000, TRID_REG(card, T4D_MUSICVOL_WAVEVOL)); + outb(0x10, TRID_REG(card, ALI_MPUR2)); + ready_2nd = inl(TRID_REG(card, ALI_SCTRL)); + ready_2nd &= 0x3fff; + outl(ready_2nd | PCMOUT | 0x8000, TRID_REG(card, ALI_SCTRL)); + ready_2nd = inl(TRID_REG(card, ALI_SCTRL)); + ready_2nd &= SI_AC97_SECONDARY_READY; + if (card->revision < ALI_5451_V02) + ready_2nd = 0; + break; + case PCI_DEVICE_ID_SI_7018: + /* disable AC97 GPIO interrupt */ + outl(0x00, TRID_REG(card, SI_AC97_GPIO)); + /* when power up the AC link is in cold reset mode so stop it */ + outl(PCMOUT | SURROUT | CENTEROUT | LFEOUT | SECONDARY_ID, + TRID_REG(card, SI_SERIAL_INTF_CTRL)); + /* it take a long time to recover from a cold reset */ + /* (especially when you have more than one codec) */ + udelay(2000); + ready_2nd = inl(TRID_REG(card, SI_SERIAL_INTF_CTRL)); + ready_2nd &= SI_AC97_SECONDARY_READY; + break; + case PCI_DEVICE_ID_TRIDENT_4DWAVE_DX: + /* playback on */ + outl(DX_AC97_PLAYBACK, TRID_REG(card, DX_ACR2_AC97_COM_STAT)); + break; + case PCI_DEVICE_ID_TRIDENT_4DWAVE_NX: + /* enable AC97 Output Slot 3,4 (PCM Left/Right Playback) */ + outl(NX_AC97_PCM_OUTPUT, TRID_REG(card, NX_ACR0_AC97_COM_STAT)); + ready_2nd = inl(TRID_REG(card, NX_ACR0_AC97_COM_STAT)); + ready_2nd &= NX_AC97_SECONDARY_READY; + break; + case PCI_DEVICE_ID_INTERG_5050: + /* disable AC97 GPIO interrupt */ + outl(0x00, TRID_REG(card, SI_AC97_GPIO)); + /* when power up, the AC link is in cold reset mode, so stop it */ + outl(PCMOUT | SURROUT | CENTEROUT | LFEOUT, + TRID_REG(card, SI_SERIAL_INTF_CTRL)); + /* it take a long time to recover from a cold reset (especially */ + /* when you have more than one codec) */ + udelay(2000); + ready_2nd = inl(TRID_REG(card, SI_SERIAL_INTF_CTRL)); + ready_2nd &= SI_AC97_SECONDARY_READY; + break; + } + + for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) { + if ((codec = ac97_alloc_codec()) == NULL) + return -ENOMEM; + + /* initialize some basic codec information, other fields */ + /* will be filled in ac97_probe_codec */ + codec->private_data = card; + codec->id = num_ac97; + + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { + codec->codec_read = ali_ac97_read; + codec->codec_write = ali_ac97_write; + } else { + codec->codec_read = trident_ac97_get; + codec->codec_write = trident_ac97_set; + } + + if (ac97_probe_codec(codec) == 0) + break; + + codec->dev_mixer = register_sound_mixer(&trident_mixer_fops, -1); + if (codec->dev_mixer < 0) { + printk(KERN_ERR "trident: couldn't register mixer!\n"); + ac97_release_codec(codec); + break; + } + + card->ac97_codec[num_ac97] = codec; + + /* if there is no secondary codec at all, don't probe any more */ + if (!ready_2nd) + break; + } + + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { + for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) { + if (card->ac97_codec[num_ac97] == NULL) + break; + for (i = 0; i < 64; i++) { + u16 reg = ali_ac97_get(card, num_ac97, i * 2); + card->mixer_regs[i][num_ac97] = reg; + } + } + } + return num_ac97 + 1; +} + +/* Gameport functions for the cards ADC gameport */ + +static unsigned char +trident_game_read(struct gameport *gameport) +{ + struct trident_card *card = gameport->port_data; + return inb(TRID_REG(card, T4D_GAME_LEG)); +} + +static void +trident_game_trigger(struct gameport *gameport) +{ + struct trident_card *card = gameport->port_data; + outb(0xff, TRID_REG(card, T4D_GAME_LEG)); +} + +static int +trident_game_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + struct trident_card *card = gameport->port_data; + int i; + + *buttons = (~inb(TRID_REG(card, T4D_GAME_LEG)) >> 4) & 0xf; + + for (i = 0; i < 4; i++) { + axes[i] = inw(TRID_REG(card, T4D_GAME_AXD) + i * sizeof (u16)); + if (axes[i] == 0xffff) + axes[i] = -1; + } + + return 0; +} + +static int +trident_game_open(struct gameport *gameport, int mode) +{ + struct trident_card *card = gameport->port_data; + + switch (mode) { + case GAMEPORT_MODE_COOKED: + outb(0x80, TRID_REG(card, T4D_GAME_CR)); + msleep(20); + return 0; + case GAMEPORT_MODE_RAW: + outb(0x00, TRID_REG(card, T4D_GAME_CR)); + return 0; + default: + return -1; + } + + return 0; +} + +static int __devinit +trident_register_gameport(struct trident_card *card) +{ + struct gameport *gp; + + card->gameport = gp = gameport_allocate_port(); + if (!gp) { + printk(KERN_ERR "trident: can not allocate memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "Trident 4DWave"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(card->pci_dev)); + gp->read = trident_game_read; + gp->trigger = trident_game_trigger; + gp->cooked_read = trident_game_cooked_read; + gp->open = trident_game_open; + gp->fuzz = 64; + gp->port_data = card; + + gameport_register_port(gp); + + return 0; +} + +/* install the driver, we do not allocate hardware channel nor DMA buffer */ +/* now, they are defered until "ACCESS" time (in prog_dmabuf called by */ +/* open/read/write/ioctl/mmap) */ +static int __devinit +trident_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id) +{ + unsigned long iobase; + struct trident_card *card; + u8 bits; + u8 revision; + int i = 0; + u16 temp; + struct pci_dev *pci_dev_m1533 = NULL; + int rc = -ENODEV; + u64 dma_mask; + + if (pci_enable_device(pci_dev)) + goto out; + + if (pci_dev->device == PCI_DEVICE_ID_ALI_5451) + dma_mask = ALI_DMA_MASK; + else + dma_mask = TRIDENT_DMA_MASK; + if (pci_set_dma_mask(pci_dev, dma_mask)) { + printk(KERN_ERR "trident: architecture does not support" + " %s PCI busmaster DMA\n", + pci_dev->device == PCI_DEVICE_ID_ALI_5451 ? + "32-bit" : "30-bit"); + goto out; + } + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &revision); + + if (pci_id->device == PCI_DEVICE_ID_INTERG_5050) + iobase = pci_resource_start(pci_dev, 1); + else + iobase = pci_resource_start(pci_dev, 0); + + if (!request_region(iobase, 256, card_names[pci_id->driver_data])) { + printk(KERN_ERR "trident: can't allocate I/O space at " + "0x%4.4lx\n", iobase); + goto out; + } + + rc = -ENOMEM; + if ((card = kmalloc(sizeof(*card), GFP_KERNEL)) == NULL) { + printk(KERN_ERR "trident: out of memory\n"); + goto out_release_region; + } + memset(card, 0, sizeof (*card)); + + init_timer(&card->timer); + card->iobase = iobase; + card->pci_dev = pci_dev; + card->pci_id = pci_id->device; + card->revision = revision; + card->irq = pci_dev->irq; + card->next = devs; + card->magic = TRIDENT_CARD_MAGIC; + card->banks[BANK_A].addresses = &bank_a_addrs; + card->banks[BANK_A].bitmap = 0UL; + card->banks[BANK_B].addresses = &bank_b_addrs; + card->banks[BANK_B].bitmap = 0UL; + + init_MUTEX(&card->open_sem); + spin_lock_init(&card->lock); + init_timer(&card->timer); + + devs = card; + + pci_set_master(pci_dev); + + printk(KERN_INFO "trident: %s found at IO 0x%04lx, IRQ %d\n", + card_names[pci_id->driver_data], card->iobase, card->irq); + + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { + /* ALi channel Management */ + card->alloc_pcm_channel = ali_alloc_pcm_channel; + card->alloc_rec_pcm_channel = ali_alloc_rec_pcm_channel; + card->free_pcm_channel = ali_free_pcm_channel; + + card->address_interrupt = ali_address_interrupt; + + /* Added by Matt Wu 01-05-2001 for spdif in */ + card->multi_channel_use_count = 0; + card->rec_channel_use_count = 0; + + /* ALi SPDIF OUT function */ + if (card->revision == ALI_5451_V02) { + ali_setup_spdif_out(card, ALI_PCM_TO_SPDIF_OUT); + res = create_proc_entry("ALi5451", 0, NULL); + if (res) { + res->write_proc = ali_write_proc; + res->data = card; + } + } + + /* Add H/W Volume Control By Matt Wu Jul. 06, 2001 */ + card->hwvolctl = 0; + pci_dev_m1533 = pci_find_device(PCI_VENDOR_ID_AL, + PCI_DEVICE_ID_AL_M1533, + pci_dev_m1533); + rc = -ENODEV; + if (pci_dev_m1533 == NULL) + goto out_proc_fs; + pci_read_config_byte(pci_dev_m1533, 0x63, &bits); + if (bits & (1 << 5)) + card->hwvolctl = 1; + if (card->hwvolctl) { + /* Clear m1533 pci cfg 78h bit 30 to zero, which makes + GPIO11/12/13 work as ACGP_UP/DOWN/MUTE. */ + pci_read_config_byte(pci_dev_m1533, 0x7b, &bits); + bits &= 0xbf; /*clear bit 6 */ + pci_write_config_byte(pci_dev_m1533, 0x7b, bits); + } + } else if (card->pci_id == PCI_DEVICE_ID_INTERG_5050) { + card->alloc_pcm_channel = cyber_alloc_pcm_channel; + card->alloc_rec_pcm_channel = cyber_alloc_pcm_channel; + card->free_pcm_channel = cyber_free_pcm_channel; + card->address_interrupt = cyber_address_interrupt; + cyber_init_ritual(card); + } else { + card->alloc_pcm_channel = trident_alloc_pcm_channel; + card->alloc_rec_pcm_channel = trident_alloc_pcm_channel; + card->free_pcm_channel = trident_free_pcm_channel; + card->address_interrupt = trident_address_interrupt; + } + + /* claim our irq */ + rc = -ENODEV; + if (request_irq(card->irq, &trident_interrupt, SA_SHIRQ, + card_names[pci_id->driver_data], card)) { + printk(KERN_ERR "trident: unable to allocate irq %d\n", + card->irq); + goto out_proc_fs; + } + /* register /dev/dsp */ + if ((card->dev_audio = register_sound_dsp(&trident_audio_fops, -1)) < 0) { + printk(KERN_ERR "trident: couldn't register DSP device!\n"); + goto out_free_irq; + } + card->mixer_regs_ready = 0; + /* initialize AC97 codec and register /dev/mixer */ + if (trident_ac97_init(card) <= 0) { + /* unregister audio devices */ + for (i = 0; i < NR_AC97; i++) { + if (card->ac97_codec[i] != NULL) { + struct ac97_codec* codec = card->ac97_codec[i]; + unregister_sound_mixer(codec->dev_mixer); + ac97_release_codec(codec); + } + } + goto out_unregister_sound_dsp; + } + card->mixer_regs_ready = 1; + outl(0x00, TRID_REG(card, T4D_MUSICVOL_WAVEVOL)); + + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { + /* Add H/W Volume Control By Matt Wu Jul. 06, 2001 */ + if (card->hwvolctl) { + /* Enable GPIO IRQ (MISCINT bit 18h) */ + temp = inw(TRID_REG(card, T4D_MISCINT + 2)); + temp |= 0x0004; + outw(temp, TRID_REG(card, T4D_MISCINT + 2)); + + /* Enable H/W Volume Control GLOVAL CONTROL bit 0 */ + temp = inw(TRID_REG(card, ALI_GLOBAL_CONTROL)); + temp |= 0x0001; + outw(temp, TRID_REG(card, ALI_GLOBAL_CONTROL)); + + } + if (card->revision == ALI_5451_V02) + ali_close_multi_channels(); + /* edited by HMSEO for GT sound */ +#if defined(CONFIG_ALPHA_NAUTILUS) || defined(CONFIG_ALPHA_GENERIC) + { + u16 ac97_data; + extern struct hwrpb_struct *hwrpb; + + if ((hwrpb->sys_type) == 201) { + printk(KERN_INFO "trident: Running on Alpha system " + "type Nautilus\n"); + ac97_data = ali_ac97_get(card, 0, AC97_POWER_CONTROL); + ali_ac97_set(card, 0, AC97_POWER_CONTROL, + ac97_data | ALI_EAPD_POWER_DOWN); + } + } +#endif /* CONFIG_ALPHA_NAUTILUS || CONFIG_ALPHA_GENERIC */ + /* edited by HMSEO for GT sound */ + } + rc = 0; + pci_set_drvdata(pci_dev, card); + + /* Enable Address Engine Interrupts */ + trident_enable_loop_interrupts(card); + + /* Register gameport */ + trident_register_gameport(card); + +out: + return rc; + +out_unregister_sound_dsp: + unregister_sound_dsp(card->dev_audio); +out_free_irq: + free_irq(card->irq, card); +out_proc_fs: + if (res) { + remove_proc_entry("ALi5451", NULL); + res = NULL; + } + kfree(card); + devs = NULL; +out_release_region: + release_region(iobase, 256); + return rc; +} + +static void __devexit +trident_remove(struct pci_dev *pci_dev) +{ + int i; + struct trident_card *card = pci_get_drvdata(pci_dev); + + /* + * Kill running timers before unload. We can't have them + * going off after rmmod! + */ + if (card->hwvolctl) + del_timer_sync(&card->timer); + + /* ALi S/PDIF and Power Management */ + if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { + ali_setup_spdif_out(card, ALI_PCM_TO_SPDIF_OUT); + ali_disable_special_channel(card, ALI_SPDIF_OUT_CHANNEL); + ali_disable_spdif_in(card); + remove_proc_entry("ALi5451", NULL); + } + + /* Unregister gameport */ + if (card->gameport) + gameport_unregister_port(card->gameport); + + /* Kill interrupts, and SP/DIF */ + trident_disable_loop_interrupts(card); + + /* free hardware resources */ + free_irq(card->irq, card); + release_region(card->iobase, 256); + + /* unregister audio devices */ + for (i = 0; i < NR_AC97; i++) + if (card->ac97_codec[i] != NULL) { + unregister_sound_mixer(card->ac97_codec[i]->dev_mixer); + ac97_release_codec(card->ac97_codec[i]); + } + unregister_sound_dsp(card->dev_audio); + + kfree(card); + + pci_set_drvdata(pci_dev, NULL); +} + +MODULE_AUTHOR("Alan Cox, Aaron Holtzman, Ollie Lho, Ching Ling Lee, Muli Ben-Yehuda"); +MODULE_DESCRIPTION("Trident 4DWave/SiS 7018/ALi 5451 and Tvia/IGST CyberPro5050 PCI " + "Audio Driver"); +MODULE_LICENSE("GPL"); + +#define TRIDENT_MODULE_NAME "trident" + +static struct pci_driver trident_pci_driver = { + .name = TRIDENT_MODULE_NAME, + .id_table = trident_pci_tbl, + .probe = trident_probe, + .remove = __devexit_p(trident_remove), + .suspend = trident_suspend, + .resume = trident_resume +}; + +static int __init +trident_init_module(void) +{ + printk(KERN_INFO "Trident 4DWave/SiS 7018/ALi 5451,Tvia CyberPro " + "5050 PCI Audio, version " DRIVER_VERSION ", " __TIME__ " " + __DATE__ "\n"); + + return pci_register_driver(&trident_pci_driver); +} + +static void __exit +trident_cleanup_module(void) +{ + pci_unregister_driver(&trident_pci_driver); +} + +module_init(trident_init_module); +module_exit(trident_cleanup_module); diff --git a/sound/oss/trident.h b/sound/oss/trident.h new file mode 100644 index 000000000000..4713b49fc91d --- /dev/null +++ b/sound/oss/trident.h @@ -0,0 +1,358 @@ +#ifndef __TRID4DWAVE_H +#define __TRID4DWAVE_H + +/* + * audio@tridentmicro.com + * Fri Feb 19 15:55:28 MST 1999 + * Definitions for Trident 4DWave DX/NX chips + * + * + * 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. + * + */ + +/* PCI vendor and device ID */ +#ifndef PCI_VENDOR_ID_TRIDENT +#define PCI_VENDOR_ID_TRIDENT 0x1023 +#endif + +#ifndef PCI_VENDOR_ID_SI +#define PCI_VENDOR_ID_SI 0x1039 +#endif + +#ifndef PCI_VENDOR_ID_ALI +#define PCI_VENDOR_ID_ALI 0x10b9 +#endif + +#ifndef PCI_DEVICE_ID_TRIDENT_4DWAVE_DX +#define PCI_DEVICE_ID_TRIDENT_4DWAVE_DX 0x2000 +#endif + +#ifndef PCI_DEVICE_ID_TRIDENT_4DWAVE_NX +#define PCI_DEVICE_ID_TRIDENT_4DWAVE_NX 0x2001 +#endif + +#ifndef PCI_DEVICE_ID_SI_7018 +#define PCI_DEVICE_ID_SI_7018 0x7018 +#endif + +#ifndef PCI_DEVICE_ID_ALI_5451 +#define PCI_DEVICE_ID_ALI_5451 0x5451 +#endif + +#ifndef PCI_DEVICE_ID_ALI_1533 +#define PCI_DEVICE_ID_ALI_1533 0x1533 +#endif + +#define CHANNEL_REGS 5 +#define CHANNEL_START 0xe0 // The first bytes of the contiguous register space. + +#define BANK_A 0 +#define BANK_B 1 +#define NR_BANKS 2 + +#define TRIDENT_FMT_STEREO 0x01 +#define TRIDENT_FMT_16BIT 0x02 +#define TRIDENT_FMT_MASK 0x03 + +#define DAC_RUNNING 0x01 +#define ADC_RUNNING 0x02 + +/* Register Addresses */ + +/* operational registers common to DX, NX, 7018 */ +enum trident_op_registers { + T4D_GAME_CR = 0x30, T4D_GAME_LEG = 0x31, + T4D_GAME_AXD = 0x34, + T4D_REC_CH = 0x70, + T4D_START_A = 0x80, T4D_STOP_A = 0x84, + T4D_DLY_A = 0x88, T4D_SIGN_CSO_A = 0x8c, + T4D_CSPF_A = 0x90, T4D_CEBC_A = 0x94, + T4D_AINT_A = 0x98, T4D_EINT_A = 0x9c, + T4D_LFO_GC_CIR = 0xa0, T4D_AINTEN_A = 0xa4, + T4D_MUSICVOL_WAVEVOL = 0xa8, T4D_SBDELTA_DELTA_R = 0xac, + T4D_MISCINT = 0xb0, T4D_START_B = 0xb4, + T4D_STOP_B = 0xb8, T4D_CSPF_B = 0xbc, + T4D_SBBL_SBCL = 0xc0, T4D_SBCTRL_SBE2R_SBDD = 0xc4, + T4D_STIMER = 0xc8, T4D_LFO_B_I2S_DELTA = 0xcc, + T4D_AINT_B = 0xd8, T4D_AINTEN_B = 0xdc, + ALI_MPUR2 = 0x22, ALI_GPIO = 0x7c, + ALI_EBUF1 = 0xf4, + ALI_EBUF2 = 0xf8 +}; + +enum ali_op_registers { + ALI_SCTRL = 0x48, + ALI_GLOBAL_CONTROL = 0xd4, + ALI_STIMER = 0xc8, + ALI_SPDIF_CS = 0x70, + ALI_SPDIF_CTRL = 0x74 +}; + +enum ali_registers_number { + ALI_GLOBAL_REGS = 56, + ALI_CHANNEL_REGS = 8, + ALI_MIXER_REGS = 20 +}; + +enum ali_sctrl_control_bit { + ALI_SPDIF_OUT_ENABLE = 0x20 +}; + +enum ali_global_control_bit { + ALI_SPDIF_OUT_SEL_PCM = 0x00000400, + ALI_SPDIF_IN_SUPPORT = 0x00000800, + ALI_SPDIF_OUT_CH_ENABLE = 0x00008000, + ALI_SPDIF_IN_CH_ENABLE = 0x00080000, + ALI_PCM_IN_DISABLE = 0x7fffffff, + ALI_PCM_IN_ENABLE = 0x80000000, + ALI_SPDIF_IN_CH_DISABLE = 0xfff7ffff, + ALI_SPDIF_OUT_CH_DISABLE = 0xffff7fff, + ALI_SPDIF_OUT_SEL_SPDIF = 0xfffffbff + +}; + +enum ali_spdif_control_bit { + ALI_SPDIF_IN_FUNC_ENABLE = 0x02, + ALI_SPDIF_IN_CH_STATUS = 0x40, + ALI_SPDIF_OUT_CH_STATUS = 0xbf + +}; + +enum ali_control_all { + ALI_DISABLE_ALL_IRQ = 0, + ALI_CHANNELS = 32, + ALI_STOP_ALL_CHANNELS = 0xffffffff, + ALI_MULTI_CHANNELS_START_STOP = 0x07800000 +}; + +enum ali_EMOD_control_bit { + ALI_EMOD_DEC = 0x00000000, + ALI_EMOD_INC = 0x10000000, + ALI_EMOD_Delay = 0x20000000, + ALI_EMOD_Still = 0x30000000 +}; + +enum ali_pcm_in_channel_num { + ALI_NORMAL_CHANNEL = 0, + ALI_SPDIF_OUT_CHANNEL = 15, + ALI_SPDIF_IN_CHANNEL = 19, + ALI_LEF_CHANNEL = 23, + ALI_CENTER_CHANNEL = 24, + ALI_SURR_RIGHT_CHANNEL = 25, + ALI_SURR_LEFT_CHANNEL = 26, + ALI_PCM_IN_CHANNEL = 31 +}; + +enum ali_pcm_out_channel_num { + ALI_PCM_OUT_CHANNEL_FIRST = 0, + ALI_PCM_OUT_CHANNEL_LAST = 31 +}; + +enum ali_ac97_power_control_bit { + ALI_EAPD_POWER_DOWN = 0x8000 +}; + +enum ali_update_ptr_flags { + ALI_ADDRESS_INT_UPDATE = 0x01 +}; + +enum ali_revision { + ALI_5451_V02 = 0x02 +}; + +enum ali_spdif_out_control { + ALI_PCM_TO_SPDIF_OUT = 0, + ALI_SPDIF_OUT_TO_SPDIF_OUT = 1, + ALI_SPDIF_OUT_PCM = 0, + ALI_SPDIF_OUT_NON_PCM = 2 +}; + +/* S/PDIF Operational Registers for 4D-NX */ +enum nx_spdif_registers { + NX_SPCTRL_SPCSO = 0x24, NX_SPLBA = 0x28, + NX_SPESO = 0x2c, NX_SPCSTATUS = 0x64 +}; + +/* OP registers to access each hardware channel */ +enum channel_registers { + CH_DX_CSO_ALPHA_FMS = 0xe0, CH_DX_ESO_DELTA = 0xe8, + CH_DX_FMC_RVOL_CVOL = 0xec, + CH_NX_DELTA_CSO = 0xe0, CH_NX_DELTA_ESO = 0xe8, + CH_NX_ALPHA_FMS_FMC_RVOL_CVOL = 0xec, + CH_LBA = 0xe4, + CH_GVSEL_PAN_VOL_CTRL_EC = 0xf0 +}; + +/* registers to read/write/control AC97 codec */ +enum dx_ac97_registers { + DX_ACR0_AC97_W = 0x40, DX_ACR1_AC97_R = 0x44, + DX_ACR2_AC97_COM_STAT = 0x48 +}; + +enum nx_ac97_registers { + NX_ACR0_AC97_COM_STAT = 0x40, NX_ACR1_AC97_W = 0x44, + NX_ACR2_AC97_R_PRIMARY = 0x48, NX_ACR3_AC97_R_SECONDARY = 0x4c +}; + +enum si_ac97_registers { + SI_AC97_WRITE = 0x40, SI_AC97_READ = 0x44, + SI_SERIAL_INTF_CTRL = 0x48, SI_AC97_GPIO = 0x4c +}; + +enum ali_ac97_registers { + ALI_AC97_WRITE = 0x40, ALI_AC97_READ = 0x44 +}; + +/* Bit mask for operational registers */ +#define AC97_REG_ADDR 0x000000ff + +enum ali_ac97_bits { + ALI_AC97_BUSY_WRITE = 0x8000, ALI_AC97_BUSY_READ = 0x8000, + ALI_AC97_WRITE_ACTION = 0x8000, ALI_AC97_READ_ACTION = 0x8000, + ALI_AC97_AUDIO_BUSY = 0x4000, ALI_AC97_SECONDARY = 0x0080, + ALI_AC97_READ_MIXER_REGISTER = 0xfeff, + ALI_AC97_WRITE_MIXER_REGISTER = 0x0100 +}; + +enum sis7018_ac97_bits { + SI_AC97_BUSY_WRITE = 0x8000, SI_AC97_BUSY_READ = 0x8000, + SI_AC97_AUDIO_BUSY = 0x4000, SI_AC97_MODEM_BUSY = 0x2000, + SI_AC97_SECONDARY = 0x0080 +}; + +enum trident_dx_ac97_bits { + DX_AC97_BUSY_WRITE = 0x8000, DX_AC97_BUSY_READ = 0x8000, + DX_AC97_READY = 0x0010, DX_AC97_RECORD = 0x0008, + DX_AC97_PLAYBACK = 0x0002 +}; + +enum trident_nx_ac97_bits { + /* ACR1-3 */ + NX_AC97_BUSY_WRITE = 0x0800, NX_AC97_BUSY_READ = 0x0800, + NX_AC97_BUSY_DATA = 0x0400, NX_AC97_WRITE_SECONDARY = 0x0100, + /* ACR0 */ + NX_AC97_SECONDARY_READY = 0x0040, NX_AC97_SECONDARY_RECORD = 0x0020, + NX_AC97_SURROUND_OUTPUT = 0x0010, + NX_AC97_PRIMARY_READY = 0x0008, NX_AC97_PRIMARY_RECORD = 0x0004, + NX_AC97_PCM_OUTPUT = 0x0002, + NX_AC97_WARM_RESET = 0x0001 +}; + +enum serial_intf_ctrl_bits { + WARM_REST = 0x00000001, COLD_RESET = 0x00000002, + I2S_CLOCK = 0x00000004, PCM_SEC_AC97= 0x00000008, + AC97_DBL_RATE = 0x00000010, SPDIF_EN = 0x00000020, + I2S_OUTPUT_EN = 0x00000040, I2S_INPUT_EN = 0x00000080, + PCMIN = 0x00000100, LINE1IN = 0x00000200, + MICIN = 0x00000400, LINE2IN = 0x00000800, + HEAD_SET_IN = 0x00001000, GPIOIN = 0x00002000, + /* 7018 spec says id = 01 but the demo board routed to 10 + SECONDARY_ID= 0x00004000, */ + SECONDARY_ID= 0x00004000, + PCMOUT = 0x00010000, SURROUT = 0x00020000, + CENTEROUT = 0x00040000, LFEOUT = 0x00080000, + LINE1OUT = 0x00100000, LINE2OUT = 0x00200000, + GPIOOUT = 0x00400000, + SI_AC97_PRIMARY_READY = 0x01000000, + SI_AC97_SECONDARY_READY = 0x02000000, +}; + +enum global_control_bits { + CHANNLE_IDX = 0x0000003f, PB_RESET = 0x00000100, + PAUSE_ENG = 0x00000200, + OVERRUN_IE = 0x00000400, UNDERRUN_IE = 0x00000800, + ENDLP_IE = 0x00001000, MIDLP_IE = 0x00002000, + ETOG_IE = 0x00004000, + EDROP_IE = 0x00008000, BANK_B_EN = 0x00010000 +}; + +enum channel_control_bits { + CHANNEL_LOOP = 0x00001000, CHANNEL_SIGNED = 0x00002000, + CHANNEL_STEREO = 0x00004000, CHANNEL_16BITS = 0x00008000, +}; + +enum channel_attribute { + /* playback/record select */ + CHANNEL_PB = 0x0000, CHANNEL_SPC_PB = 0x4000, + CHANNEL_REC = 0x8000, CHANNEL_REC_PB = 0xc000, + /* playback destination/record source select */ + MODEM_LINE1 = 0x0000, MODEM_LINE2 = 0x0400, + PCM_LR = 0x0800, HSET = 0x0c00, + I2S_LR = 0x1000, CENTER_LFE = 0x1400, + SURR_LR = 0x1800, SPDIF_LR = 0x1c00, + MIC = 0x1400, + /* mist stuff */ + MONO_LEFT = 0x0000, MONO_RIGHT = 0x0100, + MONO_MIX = 0x0200, SRC_ENABLE = 0x0080, +}; + +enum miscint_bits { + PB_UNDERRUN_IRO = 0x00000001, REC_OVERRUN_IRQ = 0x00000002, + SB_IRQ = 0x00000004, MPU401_IRQ = 0x00000008, + OPL3_IRQ = 0x00000010, ADDRESS_IRQ = 0x00000020, + ENVELOPE_IRQ = 0x00000040, ST_IRQ = 0x00000080, + PB_UNDERRUN = 0x00000100, REC_OVERRUN = 0x00000200, + MIXER_UNDERFLOW = 0x00000400, MIXER_OVERFLOW = 0x00000800, + ST_TARGET_REACHED = 0x00008000, PB_24K_MODE = 0x00010000, + ST_IRQ_EN = 0x00800000, ACGPIO_IRQ = 0x01000000 +}; + +#define TRID_REG( trident, x ) ( (trident) -> iobase + (x) ) + +#define CYBER_PORT_AUDIO 0x3CE +#define CYBER_IDX_AUDIO_ENABLE 0x7B +#define CYBER_BMSK_AUDIO_INT_ENABLE 0x09 +#define CYBER_BMSK_AUENZ 0x01 +#define CYBER_BMSK_AUENZ_ENABLE 0x00 +#define CYBER_IDX_IRQ_ENABLE 0x12 + +#define VALIDATE_MAGIC(FOO,MAG) \ +({ \ + if (!(FOO) || (FOO)->magic != MAG) { \ + printk(invalid_magic,__FUNCTION__); \ + return -ENXIO; \ + } \ +}) + +#define VALIDATE_STATE(a) VALIDATE_MAGIC(a,TRIDENT_STATE_MAGIC) +#define VALIDATE_CARD(a) VALIDATE_MAGIC(a,TRIDENT_CARD_MAGIC) + +static inline unsigned ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +#endif /* __TRID4DWAVE_H */ diff --git a/sound/oss/trix.c b/sound/oss/trix.c new file mode 100644 index 000000000000..d1f1f154dcce --- /dev/null +++ b/sound/oss/trix.c @@ -0,0 +1,525 @@ +/* + * sound/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 +#include + +#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 000000000000..858e1fe6c618 --- /dev/null +++ b/sound/oss/tuning.h @@ -0,0 +1,29 @@ +#ifdef SEQUENCER_C + +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 +}; + +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 +}; +#else +extern unsigned short semitone_tuning[24]; +extern unsigned short cent_tuning[100]; +#endif diff --git a/sound/oss/uart401.c b/sound/oss/uart401.c new file mode 100644 index 000000000000..a3d75baf6df8 --- /dev/null +++ b/sound/oss/uart401.c @@ -0,0 +1,481 @@ +/* + * sound/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 +#include +#include +#include +#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, struct pt_regs *dummy) +{ + 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 000000000000..be00cf128651 --- /dev/null +++ b/sound/oss/uart6850.c @@ -0,0 +1,362 @@ +/* + * sound/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 +#include +#include +#include +/* 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 struct timer_list uart6850_timer = + TIMER_INITIALIZER(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, struct pt_regs *dummy) +{ + 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 000000000000..0ff8c0a3bda0 --- /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 000000000000..077b76797665 --- /dev/null +++ b/sound/oss/v_midi.c @@ -0,0 +1,291 @@ +/* + * sound/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 +#include +#include +#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. + */ + + +void (*midi_input_intr) (int dev, unsigned char data); + +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=(struct vmidi_memory *)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 000000000000..1b86cb45c607 --- /dev/null +++ b/sound/oss/v_midi.h @@ -0,0 +1,15 @@ +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/via82cxxx_audio.c b/sound/oss/via82cxxx_audio.c new file mode 100644 index 000000000000..b387e1e52485 --- /dev/null +++ b/sound/oss/via82cxxx_audio.c @@ -0,0 +1,3615 @@ +/* + * Support for VIA 82Cxxx Audio Codecs + * Copyright 1999,2000 Jeff Garzik + * + * Updated to support the VIA 8233/8235 audio subsystem + * Alan Cox (C) Copyright 2002, 2003 Red Hat Inc + * + * Distributed under the GNU GENERAL PUBLIC LICENSE (GPL) Version 2. + * See the "COPYING" file distributed with this software for more info. + * NO WARRANTY + * + * For a list of known bugs (errata) and documentation, + * see via-audio.pdf in Documentation/DocBook. + * If this documentation does not exist, run "make pdfdocs". + */ + + +#define VIA_VERSION "1.9.1-ac4-2.5" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sound_config.h" +#include "dev_table.h" +#include "mpu401.h" + + +#undef VIA_DEBUG /* define to enable debugging output and checks */ +#ifdef VIA_DEBUG +/* note: prints function name for you */ +#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __FUNCTION__ , ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +#undef VIA_NDEBUG /* define to disable lightweight runtime checks */ +#ifdef VIA_NDEBUG +#define assert(expr) +#else +#define assert(expr) \ + if(!(expr)) { \ + printk( "Assertion failed! %s,%s,%s,line=%d\n", \ + #expr,__FILE__,__FUNCTION__,__LINE__); \ + } +#endif + +#define VIA_SUPPORT_MMAP 1 /* buggy, for now... */ + +#define MAX_CARDS 1 + +#define VIA_CARD_NAME "VIA 82Cxxx Audio driver " VIA_VERSION +#define VIA_MODULE_NAME "via82cxxx" +#define PFX VIA_MODULE_NAME ": " + +#define VIA_COUNTER_LIMIT 100000 + +/* size of DMA buffers */ +#define VIA_MAX_BUFFER_DMA_PAGES 32 + +/* buffering default values in ms */ +#define VIA_DEFAULT_FRAG_TIME 20 +#define VIA_DEFAULT_BUFFER_TIME 500 + +/* the hardware has a 256 fragment limit */ +#define VIA_MIN_FRAG_NUMBER 2 +#define VIA_MAX_FRAG_NUMBER 128 + +#define VIA_MAX_FRAG_SIZE PAGE_SIZE +#define VIA_MIN_FRAG_SIZE (VIA_MAX_BUFFER_DMA_PAGES * PAGE_SIZE / VIA_MAX_FRAG_NUMBER) + + +/* 82C686 function 5 (audio codec) PCI configuration registers */ +#define VIA_ACLINK_STATUS 0x40 +#define VIA_ACLINK_CTRL 0x41 +#define VIA_FUNC_ENABLE 0x42 +#define VIA_PNP_CONTROL 0x43 +#define VIA_FM_NMI_CTRL 0x48 + +/* + * controller base 0 (scatter-gather) registers + * + * NOTE: Via datasheet lists first channel as "read" + * channel and second channel as "write" channel. + * I changed the naming of the constants to be more + * clear than I felt the datasheet to be. + */ + +#define VIA_BASE0_PCM_OUT_CHAN 0x00 /* output PCM to user */ +#define VIA_BASE0_PCM_OUT_CHAN_STATUS 0x00 +#define VIA_BASE0_PCM_OUT_CHAN_CTRL 0x01 +#define VIA_BASE0_PCM_OUT_CHAN_TYPE 0x02 + +#define VIA_BASE0_PCM_IN_CHAN 0x10 /* input PCM from user */ +#define VIA_BASE0_PCM_IN_CHAN_STATUS 0x10 +#define VIA_BASE0_PCM_IN_CHAN_CTRL 0x11 +#define VIA_BASE0_PCM_IN_CHAN_TYPE 0x12 + +/* offsets from base */ +#define VIA_PCM_STATUS 0x00 +#define VIA_PCM_CONTROL 0x01 +#define VIA_PCM_TYPE 0x02 +#define VIA_PCM_LEFTVOL 0x02 +#define VIA_PCM_RIGHTVOL 0x03 +#define VIA_PCM_TABLE_ADDR 0x04 +#define VIA_PCM_STOPRATE 0x08 /* 8233+ */ +#define VIA_PCM_BLOCK_COUNT 0x0C + +/* XXX unused DMA channel for FM PCM data */ +#define VIA_BASE0_FM_OUT_CHAN 0x20 +#define VIA_BASE0_FM_OUT_CHAN_STATUS 0x20 +#define VIA_BASE0_FM_OUT_CHAN_CTRL 0x21 +#define VIA_BASE0_FM_OUT_CHAN_TYPE 0x22 + +/* Six channel audio output on 8233 */ +#define VIA_BASE0_MULTI_OUT_CHAN 0x40 +#define VIA_BASE0_MULTI_OUT_CHAN_STATUS 0x40 +#define VIA_BASE0_MULTI_OUT_CHAN_CTRL 0x41 +#define VIA_BASE0_MULTI_OUT_CHAN_TYPE 0x42 + +#define VIA_BASE0_AC97_CTRL 0x80 +#define VIA_BASE0_SGD_STATUS_SHADOW 0x84 +#define VIA_BASE0_GPI_INT_ENABLE 0x8C +#define VIA_INTR_OUT ((1<<0) | (1<<4) | (1<<8)) +#define VIA_INTR_IN ((1<<1) | (1<<5) | (1<<9)) +#define VIA_INTR_FM ((1<<2) | (1<<6) | (1<<10)) +#define VIA_INTR_MASK (VIA_INTR_OUT | VIA_INTR_IN | VIA_INTR_FM) + +/* Newer VIA we need to monitor the low 3 bits of each channel. This + mask covers the channels we don't yet use as well + */ + +#define VIA_NEW_INTR_MASK 0x77077777UL + +/* VIA_BASE0_AUDIO_xxx_CHAN_TYPE bits */ +#define VIA_IRQ_ON_FLAG (1<<0) /* int on each flagged scatter block */ +#define VIA_IRQ_ON_EOL (1<<1) /* int at end of scatter list */ +#define VIA_INT_SEL_PCI_LAST_LINE_READ (0) /* int at PCI read of last line */ +#define VIA_INT_SEL_LAST_SAMPLE_SENT (1<<2) /* int at last sample sent */ +#define VIA_INT_SEL_ONE_LINE_LEFT (1<<3) /* int at less than one line to send */ +#define VIA_PCM_FMT_STEREO (1<<4) /* PCM stereo format (bit clear == mono) */ +#define VIA_PCM_FMT_16BIT (1<<5) /* PCM 16-bit format (bit clear == 8-bit) */ +#define VIA_PCM_REC_FIFO (1<<6) /* PCM Recording FIFO */ +#define VIA_RESTART_SGD_ON_EOL (1<<7) /* restart scatter-gather at EOL */ +#define VIA_PCM_FMT_MASK (VIA_PCM_FMT_STEREO|VIA_PCM_FMT_16BIT) +#define VIA_CHAN_TYPE_MASK (VIA_RESTART_SGD_ON_EOL | \ + VIA_IRQ_ON_FLAG | \ + VIA_IRQ_ON_EOL) +#define VIA_CHAN_TYPE_INT_SELECT (VIA_INT_SEL_LAST_SAMPLE_SENT) + +/* PCI configuration register bits and masks */ +#define VIA_CR40_AC97_READY 0x01 +#define VIA_CR40_AC97_LOW_POWER 0x02 +#define VIA_CR40_SECONDARY_READY 0x04 + +#define VIA_CR41_AC97_ENABLE 0x80 /* enable AC97 codec */ +#define VIA_CR41_AC97_RESET 0x40 /* clear bit to reset AC97 */ +#define VIA_CR41_AC97_WAKEUP 0x20 /* wake up from power-down mode */ +#define VIA_CR41_AC97_SDO 0x10 /* force Serial Data Out (SDO) high */ +#define VIA_CR41_VRA 0x08 /* enable variable sample rate */ +#define VIA_CR41_PCM_ENABLE 0x04 /* AC Link SGD Read Channel PCM Data Output */ +#define VIA_CR41_FM_PCM_ENABLE 0x02 /* AC Link FM Channel PCM Data Out */ +#define VIA_CR41_SB_PCM_ENABLE 0x01 /* AC Link SB PCM Data Output */ +#define VIA_CR41_BOOT_MASK (VIA_CR41_AC97_ENABLE | \ + VIA_CR41_AC97_WAKEUP | \ + VIA_CR41_AC97_SDO) +#define VIA_CR41_RUN_MASK (VIA_CR41_AC97_ENABLE | \ + VIA_CR41_AC97_RESET | \ + VIA_CR41_VRA | \ + VIA_CR41_PCM_ENABLE) + +#define VIA_CR42_SB_ENABLE 0x01 +#define VIA_CR42_MIDI_ENABLE 0x02 +#define VIA_CR42_FM_ENABLE 0x04 +#define VIA_CR42_GAME_ENABLE 0x08 +#define VIA_CR42_MIDI_IRQMASK 0x40 +#define VIA_CR42_MIDI_PNP 0x80 + +#define VIA_CR44_SECOND_CODEC_SUPPORT (1 << 6) +#define VIA_CR44_AC_LINK_ACCESS (1 << 7) + +#define VIA_CR48_FM_TRAP_TO_NMI (1 << 2) + +/* controller base 0 register bitmasks */ +#define VIA_INT_DISABLE_MASK (~(0x01|0x02)) +#define VIA_SGD_STOPPED (1 << 2) +#define VIA_SGD_PAUSED (1 << 6) +#define VIA_SGD_ACTIVE (1 << 7) +#define VIA_SGD_TERMINATE (1 << 6) +#define VIA_SGD_FLAG (1 << 0) +#define VIA_SGD_EOL (1 << 1) +#define VIA_SGD_START (1 << 7) + +#define VIA_CR80_FIRST_CODEC 0 +#define VIA_CR80_SECOND_CODEC (1 << 30) +#define VIA_CR80_FIRST_CODEC_VALID (1 << 25) +#define VIA_CR80_VALID (1 << 25) +#define VIA_CR80_SECOND_CODEC_VALID (1 << 27) +#define VIA_CR80_BUSY (1 << 24) +#define VIA_CR83_BUSY (1) +#define VIA_CR83_FIRST_CODEC_VALID (1 << 1) +#define VIA_CR80_READ (1 << 23) +#define VIA_CR80_WRITE_MODE 0 +#define VIA_CR80_REG_IDX(idx) ((((idx) & 0xFF) >> 1) << 16) + +/* capabilities we announce */ +#ifdef VIA_SUPPORT_MMAP +#define VIA_DSP_CAP (DSP_CAP_REVISION | DSP_CAP_DUPLEX | DSP_CAP_MMAP | \ + DSP_CAP_TRIGGER | DSP_CAP_REALTIME) +#else +#define VIA_DSP_CAP (DSP_CAP_REVISION | DSP_CAP_DUPLEX | \ + DSP_CAP_TRIGGER | DSP_CAP_REALTIME) +#endif + +/* scatter-gather DMA table entry, exactly as passed to hardware */ +struct via_sgd_table { + u32 addr; + u32 count; /* includes additional VIA_xxx bits also */ +}; + +#define VIA_EOL (1 << 31) +#define VIA_FLAG (1 << 30) +#define VIA_STOP (1 << 29) + + +enum via_channel_states { + sgd_stopped = 0, + sgd_in_progress = 1, +}; + + +struct via_buffer_pgtbl { + dma_addr_t handle; + void *cpuaddr; +}; + + +struct via_channel { + atomic_t n_frags; + atomic_t hw_ptr; + wait_queue_head_t wait; + + unsigned int sw_ptr; + unsigned int slop_len; + unsigned int n_irqs; + int bytes; + + unsigned is_active : 1; + unsigned is_record : 1; + unsigned is_mapped : 1; + unsigned is_enabled : 1; + unsigned is_multi: 1; /* 8233 6 channel */ + u8 pcm_fmt; /* VIA_PCM_FMT_xxx */ + u8 channels; /* Channel count */ + + unsigned rate; /* sample rate */ + unsigned int frag_size; + unsigned int frag_number; + + unsigned char intmask; + + volatile struct via_sgd_table *sgtable; + dma_addr_t sgt_handle; + + unsigned int page_number; + struct via_buffer_pgtbl pgtbl[VIA_MAX_BUFFER_DMA_PAGES]; + + long iobase; + + const char *name; +}; + + +/* data stored for each chip */ +struct via_info { + struct pci_dev *pdev; + long baseaddr; + + struct ac97_codec *ac97; + spinlock_t ac97_lock; + spinlock_t lock; + int card_num; /* unique card number, from 0 */ + + int dev_dsp; /* /dev/dsp index from register_sound_dsp() */ + + unsigned rev_h : 1; + unsigned legacy: 1; /* Has legacy ports */ + unsigned intmask: 1; /* Needs int bits */ + unsigned sixchannel: 1; /* 8233/35 with 6 channel support */ + unsigned volume: 1; + + int locked_rate : 1; + + int mixer_vol; /* 8233/35 volume - not yet implemented */ + + struct semaphore syscall_sem; + struct semaphore open_sem; + + /* The 8233/8235 have 4 DX audio channels, two record and + one six channel out. We bind ch_in to DX 1, ch_out to multichannel + and ch_fm to DX 2. DX 3 and REC0/REC1 are unused at the + moment */ + + struct via_channel ch_in; + struct via_channel ch_out; + struct via_channel ch_fm; + +#ifdef CONFIG_MIDI_VIA82CXXX + void *midi_devc; + struct address_info midi_info; +#endif +}; + + +/* number of cards, used for assigning unique numbers to cards */ +static unsigned via_num_cards; + + + +/**************************************************************** + * + * prototypes + * + * + */ + +static int via_init_one (struct pci_dev *dev, const struct pci_device_id *id); +static void __devexit via_remove_one (struct pci_dev *pdev); + +static ssize_t via_dsp_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos); +static ssize_t via_dsp_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos); +static unsigned int via_dsp_poll(struct file *file, struct poll_table_struct *wait); +static int via_dsp_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); +static int via_dsp_open (struct inode *inode, struct file *file); +static int via_dsp_release(struct inode *inode, struct file *file); +static int via_dsp_mmap(struct file *file, struct vm_area_struct *vma); + +static u16 via_ac97_read_reg (struct ac97_codec *codec, u8 reg); +static void via_ac97_write_reg (struct ac97_codec *codec, u8 reg, u16 value); +static u8 via_ac97_wait_idle (struct via_info *card); + +static void via_chan_free (struct via_info *card, struct via_channel *chan); +static void via_chan_clear (struct via_info *card, struct via_channel *chan); +static void via_chan_pcm_fmt (struct via_channel *chan, int reset); +static void via_chan_buffer_free (struct via_info *card, struct via_channel *chan); + + +/**************************************************************** + * + * Various data the driver needs + * + * + */ + + +static struct pci_device_id via_pci_tbl[] = { + { PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686_5, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + { PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8233_5, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1}, + { 0, } +}; +MODULE_DEVICE_TABLE(pci,via_pci_tbl); + + +static struct pci_driver via_driver = { + .name = VIA_MODULE_NAME, + .id_table = via_pci_tbl, + .probe = via_init_one, + .remove = __devexit_p(via_remove_one), +}; + + +/**************************************************************** + * + * Low-level base 0 register read/write helpers + * + * + */ + +/** + * via_chan_stop - Terminate DMA on specified PCM channel + * @iobase: PCI base address for SGD channel registers + * + * Terminate scatter-gather DMA operation for given + * channel (derived from @iobase), if DMA is active. + * + * Note that @iobase is not the PCI base address, + * but the PCI base address plus an offset to + * one of three PCM channels supported by the chip. + * + */ + +static inline void via_chan_stop (long iobase) +{ + if (inb (iobase + VIA_PCM_STATUS) & VIA_SGD_ACTIVE) + outb (VIA_SGD_TERMINATE, iobase + VIA_PCM_CONTROL); +} + + +/** + * via_chan_status_clear - Clear status flags on specified DMA channel + * @iobase: PCI base address for SGD channel registers + * + * Clear any pending status flags for the given + * DMA channel (derived from @iobase), if any + * flags are asserted. + * + * Note that @iobase is not the PCI base address, + * but the PCI base address plus an offset to + * one of three PCM channels supported by the chip. + * + */ + +static inline void via_chan_status_clear (long iobase) +{ + u8 tmp = inb (iobase + VIA_PCM_STATUS); + + if (tmp != 0) + outb (tmp, iobase + VIA_PCM_STATUS); +} + + +/** + * sg_begin - Begin recording or playback on a PCM channel + * @chan: Channel for which DMA operation shall begin + * + * Start scatter-gather DMA for the given channel. + * + */ + +static inline void sg_begin (struct via_channel *chan) +{ + DPRINTK("Start with intmask %d\n", chan->intmask); + DPRINTK("About to start from %d to %d\n", + inl(chan->iobase + VIA_PCM_BLOCK_COUNT), + inb(chan->iobase + VIA_PCM_STOPRATE + 3)); + outb (VIA_SGD_START|chan->intmask, chan->iobase + VIA_PCM_CONTROL); + DPRINTK("Status is now %02X\n", inb(chan->iobase + VIA_PCM_STATUS)); + DPRINTK("Control is now %02X\n", inb(chan->iobase + VIA_PCM_CONTROL)); +} + + +static int sg_active (long iobase) +{ + u8 tmp = inb (iobase + VIA_PCM_STATUS); + if ((tmp & VIA_SGD_STOPPED) || (tmp & VIA_SGD_PAUSED)) { + printk(KERN_WARNING "via82cxxx warning: SG stopped or paused\n"); + return 0; + } + if (tmp & VIA_SGD_ACTIVE) + return 1; + return 0; +} + +static int via_sg_offset(struct via_channel *chan) +{ + return inl (chan->iobase + VIA_PCM_BLOCK_COUNT) & 0x00FFFFFF; +} + +/**************************************************************** + * + * Miscellaneous debris + * + * + */ + + +/** + * via_syscall_down - down the card-specific syscell semaphore + * @card: Private info for specified board + * @nonblock: boolean, non-zero if O_NONBLOCK is set + * + * Encapsulates standard method of acquiring the syscall sem. + * + * Returns negative errno on error, or zero for success. + */ + +static inline int via_syscall_down (struct via_info *card, int nonblock) +{ + /* Thomas Sailer: + * EAGAIN is supposed to be used if IO is pending, + * not if there is contention on some internal + * synchronization primitive which should be + * held only for a short time anyway + */ + nonblock = 0; + + if (nonblock) { + if (down_trylock (&card->syscall_sem)) + return -EAGAIN; + } else { + if (down_interruptible (&card->syscall_sem)) + return -ERESTARTSYS; + } + + return 0; +} + + +/** + * via_stop_everything - Stop all audio operations + * @card: Private info for specified board + * + * Stops all DMA operations and interrupts, and clear + * any pending status bits resulting from those operations. + */ + +static void via_stop_everything (struct via_info *card) +{ + u8 tmp, new_tmp; + + DPRINTK ("ENTER\n"); + + assert (card != NULL); + + /* + * terminate any existing operations on audio read/write channels + */ + via_chan_stop (card->baseaddr + VIA_BASE0_PCM_OUT_CHAN); + via_chan_stop (card->baseaddr + VIA_BASE0_PCM_IN_CHAN); + via_chan_stop (card->baseaddr + VIA_BASE0_FM_OUT_CHAN); + if(card->sixchannel) + via_chan_stop (card->baseaddr + VIA_BASE0_MULTI_OUT_CHAN); + + /* + * clear any existing stops / flags (sanity check mainly) + */ + via_chan_status_clear (card->baseaddr + VIA_BASE0_PCM_OUT_CHAN); + via_chan_status_clear (card->baseaddr + VIA_BASE0_PCM_IN_CHAN); + via_chan_status_clear (card->baseaddr + VIA_BASE0_FM_OUT_CHAN); + if(card->sixchannel) + via_chan_status_clear (card->baseaddr + VIA_BASE0_MULTI_OUT_CHAN); + + /* + * clear any enabled interrupt bits + */ + tmp = inb (card->baseaddr + VIA_BASE0_PCM_OUT_CHAN_TYPE); + new_tmp = tmp & ~(VIA_IRQ_ON_FLAG|VIA_IRQ_ON_EOL|VIA_RESTART_SGD_ON_EOL); + if (tmp != new_tmp) + outb (0, card->baseaddr + VIA_BASE0_PCM_OUT_CHAN_TYPE); + + tmp = inb (card->baseaddr + VIA_BASE0_PCM_IN_CHAN_TYPE); + new_tmp = tmp & ~(VIA_IRQ_ON_FLAG|VIA_IRQ_ON_EOL|VIA_RESTART_SGD_ON_EOL); + if (tmp != new_tmp) + outb (0, card->baseaddr + VIA_BASE0_PCM_IN_CHAN_TYPE); + + tmp = inb (card->baseaddr + VIA_BASE0_FM_OUT_CHAN_TYPE); + new_tmp = tmp & ~(VIA_IRQ_ON_FLAG|VIA_IRQ_ON_EOL|VIA_RESTART_SGD_ON_EOL); + if (tmp != new_tmp) + outb (0, card->baseaddr + VIA_BASE0_FM_OUT_CHAN_TYPE); + + if(card->sixchannel) + { + tmp = inb (card->baseaddr + VIA_BASE0_MULTI_OUT_CHAN_TYPE); + new_tmp = tmp & ~(VIA_IRQ_ON_FLAG|VIA_IRQ_ON_EOL|VIA_RESTART_SGD_ON_EOL); + if (tmp != new_tmp) + outb (0, card->baseaddr + VIA_BASE0_MULTI_OUT_CHAN_TYPE); + } + + udelay(10); + + /* + * clear any existing flags + */ + via_chan_status_clear (card->baseaddr + VIA_BASE0_PCM_OUT_CHAN); + via_chan_status_clear (card->baseaddr + VIA_BASE0_PCM_IN_CHAN); + via_chan_status_clear (card->baseaddr + VIA_BASE0_FM_OUT_CHAN); + + DPRINTK ("EXIT\n"); +} + + +/** + * via_set_rate - Set PCM rate for given channel + * @ac97: Pointer to generic codec info struct + * @chan: Private info for specified channel + * @rate: Desired PCM sample rate, in Khz + * + * Sets the PCM sample rate for a channel. + * + * Values for @rate are clamped to a range of 4000 Khz through 48000 Khz, + * due to hardware constraints. + */ + +static int via_set_rate (struct ac97_codec *ac97, + struct via_channel *chan, unsigned rate) +{ + struct via_info *card = ac97->private_data; + int rate_reg; + u32 dacp; + u32 mast_vol, phone_vol, mono_vol, pcm_vol; + u32 mute_vol = 0x8000; /* The mute volume? -- Seems to work! */ + + DPRINTK ("ENTER, rate = %d\n", rate); + + if (chan->rate == rate) + goto out; + if (card->locked_rate) { + chan->rate = 48000; + goto out; + } + + if (rate > 48000) rate = 48000; + if (rate < 4000) rate = 4000; + + rate_reg = chan->is_record ? AC97_PCM_LR_ADC_RATE : + AC97_PCM_FRONT_DAC_RATE; + + /* Save current state */ + dacp=via_ac97_read_reg(ac97, AC97_POWER_CONTROL); + mast_vol = via_ac97_read_reg(ac97, AC97_MASTER_VOL_STEREO); + mono_vol = via_ac97_read_reg(ac97, AC97_MASTER_VOL_MONO); + phone_vol = via_ac97_read_reg(ac97, AC97_HEADPHONE_VOL); + pcm_vol = via_ac97_read_reg(ac97, AC97_PCMOUT_VOL); + /* Mute - largely reduces popping */ + via_ac97_write_reg(ac97, AC97_MASTER_VOL_STEREO, mute_vol); + via_ac97_write_reg(ac97, AC97_MASTER_VOL_MONO, mute_vol); + via_ac97_write_reg(ac97, AC97_HEADPHONE_VOL, mute_vol); + via_ac97_write_reg(ac97, AC97_PCMOUT_VOL, mute_vol); + /* Power down the DAC */ + via_ac97_write_reg(ac97, AC97_POWER_CONTROL, dacp|0x0200); + + /* Set new rate */ + via_ac97_write_reg (ac97, rate_reg, rate); + + /* Power DAC back up */ + via_ac97_write_reg(ac97, AC97_POWER_CONTROL, dacp); + udelay (200); /* reduces popping */ + + /* Restore volumes */ + via_ac97_write_reg(ac97, AC97_MASTER_VOL_STEREO, mast_vol); + via_ac97_write_reg(ac97, AC97_MASTER_VOL_MONO, mono_vol); + via_ac97_write_reg(ac97, AC97_HEADPHONE_VOL, phone_vol); + via_ac97_write_reg(ac97, AC97_PCMOUT_VOL, pcm_vol); + + /* the hardware might return a value different than what we + * passed to it, so read the rate value back from hardware + * to see what we came up with + */ + chan->rate = via_ac97_read_reg (ac97, rate_reg); + + if (chan->rate == 0) { + card->locked_rate = 1; + chan->rate = 48000; + printk (KERN_WARNING PFX "Codec rate locked at 48Khz\n"); + } + +out: + DPRINTK ("EXIT, returning rate %d Hz\n", chan->rate); + return chan->rate; +} + + +/**************************************************************** + * + * Channel-specific operations + * + * + */ + + +/** + * via_chan_init_defaults - Initialize a struct via_channel + * @card: Private audio chip info + * @chan: Channel to be initialized + * + * Zero @chan, and then set all static defaults for the structure. + */ + +static void via_chan_init_defaults (struct via_info *card, struct via_channel *chan) +{ + memset (chan, 0, sizeof (*chan)); + + if(card->intmask) + chan->intmask = 0x23; /* Turn on the IRQ bits */ + + if (chan == &card->ch_out) { + chan->name = "PCM-OUT"; + if(card->sixchannel) + { + chan->iobase = card->baseaddr + VIA_BASE0_MULTI_OUT_CHAN; + chan->is_multi = 1; + DPRINTK("Using multichannel for pcm out\n"); + } + else + chan->iobase = card->baseaddr + VIA_BASE0_PCM_OUT_CHAN; + } else if (chan == &card->ch_in) { + chan->name = "PCM-IN"; + chan->iobase = card->baseaddr + VIA_BASE0_PCM_IN_CHAN; + chan->is_record = 1; + } else if (chan == &card->ch_fm) { + chan->name = "PCM-OUT-FM"; + chan->iobase = card->baseaddr + VIA_BASE0_FM_OUT_CHAN; + } else { + BUG(); + } + + init_waitqueue_head (&chan->wait); + + chan->pcm_fmt = VIA_PCM_FMT_MASK; + chan->is_enabled = 1; + + chan->frag_number = 0; + chan->frag_size = 0; + atomic_set(&chan->n_frags, 0); + atomic_set (&chan->hw_ptr, 0); +} + +/** + * via_chan_init - Initialize PCM channel + * @card: Private audio chip info + * @chan: Channel to be initialized + * + * Performs some of the preparations necessary to begin + * using a PCM channel. + * + * Currently the preparations consist of + * setting the PCM channel to a known state. + */ + + +static void via_chan_init (struct via_info *card, struct via_channel *chan) +{ + + DPRINTK ("ENTER\n"); + + /* bzero channel structure, and init members to defaults */ + via_chan_init_defaults (card, chan); + + /* stop any existing channel output */ + via_chan_clear (card, chan); + via_chan_status_clear (chan->iobase); + via_chan_pcm_fmt (chan, 1); + + DPRINTK ("EXIT\n"); +} + +/** + * via_chan_buffer_init - Initialize PCM channel buffer + * @card: Private audio chip info + * @chan: Channel to be initialized + * + * Performs some of the preparations necessary to begin + * using a PCM channel. + * + * Currently the preparations include allocating the + * scatter-gather DMA table and buffers, + * and passing the + * address of the DMA table to the hardware. + * + * Note that special care is taken when passing the + * DMA table address to hardware, because it was found + * during driver development that the hardware did not + * always "take" the address. + */ + +static int via_chan_buffer_init (struct via_info *card, struct via_channel *chan) +{ + int page, offset; + int i; + + DPRINTK ("ENTER\n"); + + + chan->intmask = 0; + if(card->intmask) + chan->intmask = 0x23; /* Turn on the IRQ bits */ + + if (chan->sgtable != NULL) { + DPRINTK ("EXIT\n"); + return 0; + } + + /* alloc DMA-able memory for scatter-gather table */ + chan->sgtable = pci_alloc_consistent (card->pdev, + (sizeof (struct via_sgd_table) * chan->frag_number), + &chan->sgt_handle); + if (!chan->sgtable) { + printk (KERN_ERR PFX "DMA table alloc fail, aborting\n"); + DPRINTK ("EXIT\n"); + return -ENOMEM; + } + + memset ((void*)chan->sgtable, 0, + (sizeof (struct via_sgd_table) * chan->frag_number)); + + /* alloc DMA-able memory for scatter-gather buffers */ + + chan->page_number = (chan->frag_number * chan->frag_size) / PAGE_SIZE + + (((chan->frag_number * chan->frag_size) % PAGE_SIZE) ? 1 : 0); + + for (i = 0; i < chan->page_number; i++) { + chan->pgtbl[i].cpuaddr = pci_alloc_consistent (card->pdev, PAGE_SIZE, + &chan->pgtbl[i].handle); + + if (!chan->pgtbl[i].cpuaddr) { + chan->page_number = i; + goto err_out_nomem; + } + +#ifndef VIA_NDEBUG + memset (chan->pgtbl[i].cpuaddr, 0xBC, chan->frag_size); +#endif + +#if 1 + DPRINTK ("dmabuf_pg #%d (h=%lx, v2p=%lx, a=%p)\n", + i, (long)chan->pgtbl[i].handle, + virt_to_phys(chan->pgtbl[i].cpuaddr), + chan->pgtbl[i].cpuaddr); +#endif + } + + for (i = 0; i < chan->frag_number; i++) { + + page = i / (PAGE_SIZE / chan->frag_size); + offset = (i % (PAGE_SIZE / chan->frag_size)) * chan->frag_size; + + chan->sgtable[i].count = cpu_to_le32 (chan->frag_size | VIA_FLAG); + chan->sgtable[i].addr = cpu_to_le32 (chan->pgtbl[page].handle + offset); + +#if 1 + DPRINTK ("dmabuf #%d (32(h)=%lx)\n", + i, + (long)chan->sgtable[i].addr); +#endif + } + + /* overwrite the last buffer information */ + chan->sgtable[chan->frag_number - 1].count = cpu_to_le32 (chan->frag_size | VIA_EOL); + + /* set location of DMA-able scatter-gather info table */ + DPRINTK ("outl (0x%X, 0x%04lX)\n", + chan->sgt_handle, chan->iobase + VIA_PCM_TABLE_ADDR); + + via_ac97_wait_idle (card); + outl (chan->sgt_handle, chan->iobase + VIA_PCM_TABLE_ADDR); + udelay (20); + via_ac97_wait_idle (card); + /* load no rate adaption, stereo 16bit, set up ring slots */ + if(card->sixchannel) + { + if(!chan->is_multi) + { + outl (0xFFFFF | (0x3 << 20) | (chan->frag_number << 24), chan->iobase + VIA_PCM_STOPRATE); + udelay (20); + via_ac97_wait_idle (card); + } + } + + DPRINTK ("inl (0x%lX) = %x\n", + chan->iobase + VIA_PCM_TABLE_ADDR, + inl(chan->iobase + VIA_PCM_TABLE_ADDR)); + + DPRINTK ("EXIT\n"); + return 0; + +err_out_nomem: + printk (KERN_ERR PFX "DMA buffer alloc fail, aborting\n"); + via_chan_buffer_free (card, chan); + DPRINTK ("EXIT\n"); + return -ENOMEM; +} + + +/** + * via_chan_free - Release a PCM channel + * @card: Private audio chip info + * @chan: Channel to be released + * + * Performs all the functions necessary to clean up + * an initialized channel. + * + * Currently these functions include disabled any + * active DMA operations, setting the PCM channel + * back to a known state, and releasing any allocated + * sound buffers. + */ + +static void via_chan_free (struct via_info *card, struct via_channel *chan) +{ + DPRINTK ("ENTER\n"); + + spin_lock_irq (&card->lock); + + /* stop any existing channel output */ + via_chan_status_clear (chan->iobase); + via_chan_stop (chan->iobase); + via_chan_status_clear (chan->iobase); + + spin_unlock_irq (&card->lock); + + synchronize_irq(card->pdev->irq); + + DPRINTK ("EXIT\n"); +} + +static void via_chan_buffer_free (struct via_info *card, struct via_channel *chan) +{ + int i; + + DPRINTK ("ENTER\n"); + + /* zero location of DMA-able scatter-gather info table */ + via_ac97_wait_idle(card); + outl (0, chan->iobase + VIA_PCM_TABLE_ADDR); + + for (i = 0; i < chan->page_number; i++) + if (chan->pgtbl[i].cpuaddr) { + pci_free_consistent (card->pdev, PAGE_SIZE, + chan->pgtbl[i].cpuaddr, + chan->pgtbl[i].handle); + chan->pgtbl[i].cpuaddr = NULL; + chan->pgtbl[i].handle = 0; + } + + chan->page_number = 0; + + if (chan->sgtable) { + pci_free_consistent (card->pdev, + (sizeof (struct via_sgd_table) * chan->frag_number), + (void*)chan->sgtable, chan->sgt_handle); + chan->sgtable = NULL; + } + + DPRINTK ("EXIT\n"); +} + + +/** + * via_chan_pcm_fmt - Update PCM channel settings + * @chan: Channel to be updated + * @reset: Boolean. If non-zero, channel will be reset + * to 8-bit mono mode. + * + * Stores the settings of the current PCM format, + * 8-bit or 16-bit, and mono/stereo, into the + * hardware settings for the specified channel. + * If @reset is non-zero, the channel is reset + * to 8-bit mono mode. Otherwise, the channel + * is set to the values stored in the channel + * information struct @chan. + */ + +static void via_chan_pcm_fmt (struct via_channel *chan, int reset) +{ + DPRINTK ("ENTER, pcm_fmt=0x%02X, reset=%s\n", + chan->pcm_fmt, reset ? "yes" : "no"); + + assert (chan != NULL); + + if (reset) + { + /* reset to 8-bit mono mode */ + chan->pcm_fmt = 0; + chan->channels = 1; + } + + /* enable interrupts on FLAG and EOL */ + chan->pcm_fmt |= VIA_CHAN_TYPE_MASK; + + /* if we are recording, enable recording fifo bit */ + if (chan->is_record) + chan->pcm_fmt |= VIA_PCM_REC_FIFO; + /* set interrupt select bits where applicable (PCM in & out channels) */ + if (!chan->is_record) + chan->pcm_fmt |= VIA_CHAN_TYPE_INT_SELECT; + + DPRINTK("SET FMT - %02x %02x\n", chan->intmask , chan->is_multi); + + if(chan->intmask) + { + u32 m; + + /* + * Channel 0x4 is up to 6 x 16bit and has to be + * programmed differently + */ + + if(chan->is_multi) + { + u8 c = 0; + + /* + * Load the type bit for num channels + * and 8/16bit + */ + + if(chan->pcm_fmt & VIA_PCM_FMT_16BIT) + c = 1 << 7; + if(chan->pcm_fmt & VIA_PCM_FMT_STEREO) + c |= (2<<4); + else + c |= (1<<4); + + outb(c, chan->iobase + VIA_PCM_TYPE); + + /* + * Set the channel steering + * Mono + * Channel 0 to slot 3 + * Channel 0 to slot 4 + * Stereo + * Channel 0 to slot 3 + * Channel 1 to slot 4 + */ + + switch(chan->channels) + { + case 1: + outl(0xFF000000 | (1<<0) | (1<<4) , chan->iobase + VIA_PCM_STOPRATE); + break; + case 2: + outl(0xFF000000 | (1<<0) | (2<<4) , chan->iobase + VIA_PCM_STOPRATE); + break; + case 4: + outl(0xFF000000 | (1<<0) | (2<<4) | (3<<8) | (4<<12), chan->iobase + VIA_PCM_STOPRATE); + break; + case 6: + outl(0xFF000000 | (1<<0) | (2<<4) | (5<<8) | (6<<12) | (3<<16) | (4<<20), chan->iobase + VIA_PCM_STOPRATE); + break; + } + } + else + { + /* + * New style, turn off channel volume + * control, set bits in the right register + */ + outb(0x0, chan->iobase + VIA_PCM_LEFTVOL); + outb(0x0, chan->iobase + VIA_PCM_RIGHTVOL); + + m = inl(chan->iobase + VIA_PCM_STOPRATE); + m &= ~(3<<20); + if(chan->pcm_fmt & VIA_PCM_FMT_STEREO) + m |= (1 << 20); + if(chan->pcm_fmt & VIA_PCM_FMT_16BIT) + m |= (1 << 21); + outl(m, chan->iobase + VIA_PCM_STOPRATE); + } + } + else + outb (chan->pcm_fmt, chan->iobase + VIA_PCM_TYPE); + + + DPRINTK ("EXIT, pcm_fmt = 0x%02X, reg = 0x%02X\n", + chan->pcm_fmt, + inb (chan->iobase + VIA_PCM_TYPE)); +} + + +/** + * via_chan_clear - Stop DMA channel operation, and reset pointers + * @card: the chip to accessed + * @chan: Channel to be cleared + * + * Call via_chan_stop to halt DMA operations, and then resets + * all software pointers which track DMA operation. + */ + +static void via_chan_clear (struct via_info *card, struct via_channel *chan) +{ + DPRINTK ("ENTER\n"); + via_chan_stop (chan->iobase); + via_chan_buffer_free(card, chan); + chan->is_active = 0; + chan->is_mapped = 0; + chan->is_enabled = 1; + chan->slop_len = 0; + chan->sw_ptr = 0; + chan->n_irqs = 0; + atomic_set (&chan->hw_ptr, 0); + DPRINTK ("EXIT\n"); +} + + +/** + * via_chan_set_speed - Set PCM sample rate for given channel + * @card: Private info for specified board + * @chan: Channel whose sample rate will be adjusted + * @val: New sample rate, in Khz + * + * Helper function for the %SNDCTL_DSP_SPEED ioctl. OSS semantics + * demand that all audio operations halt (if they are not already + * halted) when the %SNDCTL_DSP_SPEED is given. + * + * This function halts all audio operations for the given channel + * @chan, and then calls via_set_rate to set the audio hardware + * to the new rate. + */ + +static int via_chan_set_speed (struct via_info *card, + struct via_channel *chan, int val) +{ + DPRINTK ("ENTER, requested rate = %d\n", val); + + via_chan_clear (card, chan); + + val = via_set_rate (card->ac97, chan, val); + + DPRINTK ("EXIT, returning %d\n", val); + return val; +} + + +/** + * via_chan_set_fmt - Set PCM sample size for given channel + * @card: Private info for specified board + * @chan: Channel whose sample size will be adjusted + * @val: New sample size, use the %AFMT_xxx constants + * + * Helper function for the %SNDCTL_DSP_SETFMT ioctl. OSS semantics + * demand that all audio operations halt (if they are not already + * halted) when the %SNDCTL_DSP_SETFMT is given. + * + * This function halts all audio operations for the given channel + * @chan, and then calls via_chan_pcm_fmt to set the audio hardware + * to the new sample size, either 8-bit or 16-bit. + */ + +static int via_chan_set_fmt (struct via_info *card, + struct via_channel *chan, int val) +{ + DPRINTK ("ENTER, val=%s\n", + val == AFMT_U8 ? "AFMT_U8" : + val == AFMT_S16_LE ? "AFMT_S16_LE" : + "unknown"); + + via_chan_clear (card, chan); + + assert (val != AFMT_QUERY); /* this case is handled elsewhere */ + + switch (val) { + case AFMT_S16_LE: + if ((chan->pcm_fmt & VIA_PCM_FMT_16BIT) == 0) { + chan->pcm_fmt |= VIA_PCM_FMT_16BIT; + via_chan_pcm_fmt (chan, 0); + } + break; + + case AFMT_U8: + if (chan->pcm_fmt & VIA_PCM_FMT_16BIT) { + chan->pcm_fmt &= ~VIA_PCM_FMT_16BIT; + via_chan_pcm_fmt (chan, 0); + } + break; + + default: + DPRINTK ("unknown AFMT: 0x%X\n", val); + val = AFMT_S16_LE; + } + + DPRINTK ("EXIT\n"); + return val; +} + + +/** + * via_chan_set_stereo - Enable or disable stereo for a DMA channel + * @card: Private info for specified board + * @chan: Channel whose stereo setting will be adjusted + * @val: New sample size, use the %AFMT_xxx constants + * + * Helper function for the %SNDCTL_DSP_CHANNELS and %SNDCTL_DSP_STEREO ioctls. OSS semantics + * demand that all audio operations halt (if they are not already + * halted) when %SNDCTL_DSP_CHANNELS or SNDCTL_DSP_STEREO is given. + * + * This function halts all audio operations for the given channel + * @chan, and then calls via_chan_pcm_fmt to set the audio hardware + * to enable or disable stereo. + */ + +static int via_chan_set_stereo (struct via_info *card, + struct via_channel *chan, int val) +{ + DPRINTK ("ENTER, channels = %d\n", val); + + via_chan_clear (card, chan); + + switch (val) { + + /* mono */ + case 1: + chan->pcm_fmt &= ~VIA_PCM_FMT_STEREO; + chan->channels = 1; + via_chan_pcm_fmt (chan, 0); + break; + + /* stereo */ + case 2: + chan->pcm_fmt |= VIA_PCM_FMT_STEREO; + chan->channels = 2; + via_chan_pcm_fmt (chan, 0); + break; + + case 4: + case 6: + if(chan->is_multi) + { + chan->pcm_fmt |= VIA_PCM_FMT_STEREO; + chan->channels = val; + break; + } + /* unknown */ + default: + val = -EINVAL; + break; + } + + DPRINTK ("EXIT, returning %d\n", val); + return val; +} + +static int via_chan_set_buffering (struct via_info *card, + struct via_channel *chan, int val) +{ + int shift; + + DPRINTK ("ENTER\n"); + + /* in both cases the buffer cannot be changed */ + if (chan->is_active || chan->is_mapped) { + DPRINTK ("EXIT\n"); + return -EINVAL; + } + + /* called outside SETFRAGMENT */ + /* set defaults or do nothing */ + if (val < 0) { + + if (chan->frag_size && chan->frag_number) + goto out; + + DPRINTK ("\n"); + + chan->frag_size = (VIA_DEFAULT_FRAG_TIME * chan->rate * chan->channels + * ((chan->pcm_fmt & VIA_PCM_FMT_16BIT) ? 2 : 1)) / 1000 - 1; + + shift = 0; + while (chan->frag_size) { + chan->frag_size >>= 1; + shift++; + } + chan->frag_size = 1 << shift; + + chan->frag_number = (VIA_DEFAULT_BUFFER_TIME / VIA_DEFAULT_FRAG_TIME); + + DPRINTK ("setting default values %d %d\n", chan->frag_size, chan->frag_number); + } else { + chan->frag_size = 1 << (val & 0xFFFF); + chan->frag_number = (val >> 16) & 0xFFFF; + + DPRINTK ("using user values %d %d\n", chan->frag_size, chan->frag_number); + } + + /* quake3 wants frag_number to be a power of two */ + shift = 0; + while (chan->frag_number) { + chan->frag_number >>= 1; + shift++; + } + chan->frag_number = 1 << shift; + + if (chan->frag_size > VIA_MAX_FRAG_SIZE) + chan->frag_size = VIA_MAX_FRAG_SIZE; + else if (chan->frag_size < VIA_MIN_FRAG_SIZE) + chan->frag_size = VIA_MIN_FRAG_SIZE; + + if (chan->frag_number < VIA_MIN_FRAG_NUMBER) + chan->frag_number = VIA_MIN_FRAG_NUMBER; + if (chan->frag_number > VIA_MAX_FRAG_NUMBER) + chan->frag_number = VIA_MAX_FRAG_NUMBER; + + if ((chan->frag_number * chan->frag_size) / PAGE_SIZE > VIA_MAX_BUFFER_DMA_PAGES) + chan->frag_number = (VIA_MAX_BUFFER_DMA_PAGES * PAGE_SIZE) / chan->frag_size; + +out: + if (chan->is_record) + atomic_set (&chan->n_frags, 0); + else + atomic_set (&chan->n_frags, chan->frag_number); + + DPRINTK ("EXIT\n"); + + return 0; +} + +#ifdef VIA_CHAN_DUMP_BUFS +/** + * via_chan_dump_bufs - Display DMA table contents + * @chan: Channel whose DMA table will be displayed + * + * Debugging function which displays the contents of the + * scatter-gather DMA table for the given channel @chan. + */ + +static void via_chan_dump_bufs (struct via_channel *chan) +{ + int i; + + for (i = 0; i < chan->frag_number; i++) { + DPRINTK ("#%02d: addr=%x, count=%u, flag=%d, eol=%d\n", + i, chan->sgtable[i].addr, + chan->sgtable[i].count & 0x00FFFFFF, + chan->sgtable[i].count & VIA_FLAG ? 1 : 0, + chan->sgtable[i].count & VIA_EOL ? 1 : 0); + } + DPRINTK ("buf_in_use = %d, nextbuf = %d\n", + atomic_read (&chan->buf_in_use), + atomic_read (&chan->sw_ptr)); +} +#endif /* VIA_CHAN_DUMP_BUFS */ + + +/** + * via_chan_flush_frag - Flush partially-full playback buffer to hardware + * @chan: Channel whose DMA table will be flushed + * + * Flushes partially-full playback buffer to hardware. + */ + +static void via_chan_flush_frag (struct via_channel *chan) +{ + DPRINTK ("ENTER\n"); + + assert (chan->slop_len > 0); + + if (chan->sw_ptr == (chan->frag_number - 1)) + chan->sw_ptr = 0; + else + chan->sw_ptr++; + + chan->slop_len = 0; + + assert (atomic_read (&chan->n_frags) > 0); + atomic_dec (&chan->n_frags); + + DPRINTK ("EXIT\n"); +} + + + +/** + * via_chan_maybe_start - Initiate audio hardware DMA operation + * @chan: Channel whose DMA is to be started + * + * Initiate DMA operation, if the DMA engine for the given + * channel @chan is not already active. + */ + +static inline void via_chan_maybe_start (struct via_channel *chan) +{ + assert (chan->is_active == sg_active(chan->iobase)); + + DPRINTK ("MAYBE START %s\n", chan->name); + if (!chan->is_active && chan->is_enabled) { + chan->is_active = 1; + sg_begin (chan); + DPRINTK ("starting channel %s\n", chan->name); + } +} + + +/**************************************************************** + * + * Interface to ac97-codec module + * + * + */ + +/** + * via_ac97_wait_idle - Wait until AC97 codec is not busy + * @card: Private info for specified board + * + * Sleep until the AC97 codec is no longer busy. + * Returns the final value read from the SGD + * register being polled. + */ + +static u8 via_ac97_wait_idle (struct via_info *card) +{ + u8 tmp8; + int counter = VIA_COUNTER_LIMIT; + + DPRINTK ("ENTER/EXIT\n"); + + assert (card != NULL); + assert (card->pdev != NULL); + + do { + udelay (15); + + tmp8 = inb (card->baseaddr + 0x83); + } while ((tmp8 & VIA_CR83_BUSY) && (counter-- > 0)); + + if (tmp8 & VIA_CR83_BUSY) + printk (KERN_WARNING PFX "timeout waiting on AC97 codec\n"); + return tmp8; +} + + +/** + * via_ac97_read_reg - Read AC97 standard register + * @codec: Pointer to generic AC97 codec info + * @reg: Index of AC97 register to be read + * + * Read the value of a single AC97 codec register, + * as defined by the Intel AC97 specification. + * + * Defines the standard AC97 read-register operation + * required by the kernel's ac97_codec interface. + * + * Returns the 16-bit value stored in the specified + * register. + */ + +static u16 via_ac97_read_reg (struct ac97_codec *codec, u8 reg) +{ + unsigned long data; + struct via_info *card; + int counter; + + DPRINTK ("ENTER\n"); + + assert (codec != NULL); + assert (codec->private_data != NULL); + + card = codec->private_data; + + spin_lock(&card->ac97_lock); + + /* Every time we write to register 80 we cause a transaction. + The only safe way to clear the valid bit is to write it at + the same time as the command */ + data = (reg << 16) | VIA_CR80_READ | VIA_CR80_VALID; + + outl (data, card->baseaddr + VIA_BASE0_AC97_CTRL); + udelay (20); + + for (counter = VIA_COUNTER_LIMIT; counter > 0; counter--) { + udelay (1); + if ((((data = inl(card->baseaddr + VIA_BASE0_AC97_CTRL)) & + (VIA_CR80_VALID|VIA_CR80_BUSY)) == VIA_CR80_VALID)) + goto out; + } + + printk (KERN_WARNING PFX "timeout while reading AC97 codec (0x%lX)\n", data); + goto err_out; + +out: + /* Once the valid bit has become set, we must wait a complete AC97 + frame before the data has settled. */ + udelay(25); + data = (unsigned long) inl (card->baseaddr + VIA_BASE0_AC97_CTRL); + + outb (0x02, card->baseaddr + 0x83); + + if (((data & 0x007F0000) >> 16) == reg) { + DPRINTK ("EXIT, success, data=0x%lx, retval=0x%lx\n", + data, data & 0x0000FFFF); + spin_unlock(&card->ac97_lock); + return data & 0x0000FFFF; + } + + printk (KERN_WARNING "via82cxxx_audio: not our index: reg=0x%x, newreg=0x%lx\n", + reg, ((data & 0x007F0000) >> 16)); + +err_out: + spin_unlock(&card->ac97_lock); + DPRINTK ("EXIT, returning 0\n"); + return 0; +} + + +/** + * via_ac97_write_reg - Write AC97 standard register + * @codec: Pointer to generic AC97 codec info + * @reg: Index of AC97 register to be written + * @value: Value to be written to AC97 register + * + * Write the value of a single AC97 codec register, + * as defined by the Intel AC97 specification. + * + * Defines the standard AC97 write-register operation + * required by the kernel's ac97_codec interface. + */ + +static void via_ac97_write_reg (struct ac97_codec *codec, u8 reg, u16 value) +{ + u32 data; + struct via_info *card; + int counter; + + DPRINTK ("ENTER\n"); + + assert (codec != NULL); + assert (codec->private_data != NULL); + + card = codec->private_data; + + spin_lock(&card->ac97_lock); + + data = (reg << 16) + value; + outl (data, card->baseaddr + VIA_BASE0_AC97_CTRL); + udelay (10); + + for (counter = VIA_COUNTER_LIMIT; counter > 0; counter--) { + if ((inb (card->baseaddr + 0x83) & VIA_CR83_BUSY) == 0) + goto out; + + udelay (15); + } + + printk (KERN_WARNING PFX "timeout after AC97 codec write (0x%X, 0x%X)\n", reg, value); + +out: + spin_unlock(&card->ac97_lock); + DPRINTK ("EXIT\n"); +} + + +static int via_mixer_open (struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct via_info *card; + struct pci_dev *pdev = NULL; + struct pci_driver *drvr; + + DPRINTK ("ENTER\n"); + + while ((pdev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL) { + drvr = pci_dev_driver (pdev); + if (drvr == &via_driver) { + assert (pci_get_drvdata (pdev) != NULL); + + card = pci_get_drvdata (pdev); + if (card->ac97->dev_mixer == minor) + goto match; + } + } + + DPRINTK ("EXIT, returning -ENODEV\n"); + return -ENODEV; + +match: + file->private_data = card->ac97; + + DPRINTK ("EXIT, returning 0\n"); + return nonseekable_open(inode, file); +} + +static int via_mixer_ioctl (struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct ac97_codec *codec = file->private_data; + struct via_info *card; + int nonblock = (file->f_flags & O_NONBLOCK); + int rc; + + DPRINTK ("ENTER\n"); + + assert (codec != NULL); + card = codec->private_data; + assert (card != NULL); + + rc = via_syscall_down (card, nonblock); + if (rc) goto out; + +#if 0 + /* + * Intercept volume control on 8233 and 8235 + */ + if(card->volume) + { + switch(cmd) + { + case SOUND_MIXER_READ_VOLUME: + return card->mixer_vol; + case SOUND_MIXER_WRITE_VOLUME: + { + int v; + if(get_user(v, (int *)arg)) + { + rc = -EFAULT; + goto out; + } + card->mixer_vol = v; + } + } + } +#endif + rc = codec->mixer_ioctl(codec, cmd, arg); + + up (&card->syscall_sem); + +out: + DPRINTK ("EXIT, returning %d\n", rc); + return rc; +} + + +static struct file_operations via_mixer_fops = { + .owner = THIS_MODULE, + .open = via_mixer_open, + .llseek = no_llseek, + .ioctl = via_mixer_ioctl, +}; + + +static int __devinit via_ac97_reset (struct via_info *card) +{ + struct pci_dev *pdev = card->pdev; + u8 tmp8; + u16 tmp16; + + DPRINTK ("ENTER\n"); + + assert (pdev != NULL); + +#ifndef NDEBUG + { + u8 r40,r41,r42,r43,r44,r48; + pci_read_config_byte (card->pdev, 0x40, &r40); + pci_read_config_byte (card->pdev, 0x41, &r41); + pci_read_config_byte (card->pdev, 0x42, &r42); + pci_read_config_byte (card->pdev, 0x43, &r43); + pci_read_config_byte (card->pdev, 0x44, &r44); + pci_read_config_byte (card->pdev, 0x48, &r48); + DPRINTK ("PCI config: %02X %02X %02X %02X %02X %02X\n", + r40,r41,r42,r43,r44,r48); + + spin_lock_irq (&card->lock); + DPRINTK ("regs==%02X %02X %02X %08X %08X %08X %08X\n", + inb (card->baseaddr + 0x00), + inb (card->baseaddr + 0x01), + inb (card->baseaddr + 0x02), + inl (card->baseaddr + 0x04), + inl (card->baseaddr + 0x0C), + inl (card->baseaddr + 0x80), + inl (card->baseaddr + 0x84)); + spin_unlock_irq (&card->lock); + + } +#endif + + /* + * Reset AC97 controller: enable, disable, enable, + * pausing after each command for good luck. Only + * do this if the codec is not ready, because it causes + * loud pops and such due to such a hard codec reset. + */ + pci_read_config_byte (pdev, VIA_ACLINK_STATUS, &tmp8); + if ((tmp8 & VIA_CR40_AC97_READY) == 0) { + pci_write_config_byte (pdev, VIA_ACLINK_CTRL, + VIA_CR41_AC97_ENABLE | + VIA_CR41_AC97_RESET | + VIA_CR41_AC97_WAKEUP); + udelay (100); + + pci_write_config_byte (pdev, VIA_ACLINK_CTRL, 0); + udelay (100); + + pci_write_config_byte (pdev, VIA_ACLINK_CTRL, + VIA_CR41_AC97_ENABLE | + VIA_CR41_PCM_ENABLE | + VIA_CR41_VRA | VIA_CR41_AC97_RESET); + udelay (100); + } + + /* Make sure VRA is enabled, in case we didn't do a + * complete codec reset, above + */ + pci_read_config_byte (pdev, VIA_ACLINK_CTRL, &tmp8); + if (((tmp8 & VIA_CR41_VRA) == 0) || + ((tmp8 & VIA_CR41_AC97_ENABLE) == 0) || + ((tmp8 & VIA_CR41_PCM_ENABLE) == 0) || + ((tmp8 & VIA_CR41_AC97_RESET) == 0)) { + pci_write_config_byte (pdev, VIA_ACLINK_CTRL, + VIA_CR41_AC97_ENABLE | + VIA_CR41_PCM_ENABLE | + VIA_CR41_VRA | VIA_CR41_AC97_RESET); + udelay (100); + } + + if(card->legacy) + { +#if 0 /* this breaks on K7M */ + /* disable legacy stuff */ + pci_write_config_byte (pdev, 0x42, 0x00); + udelay(10); +#endif + + /* route FM trap to IRQ, disable FM trap */ + pci_write_config_byte (pdev, 0x48, 0x05); + udelay(10); + } + + /* disable all codec GPI interrupts */ + outl (0, pci_resource_start (pdev, 0) + 0x8C); + + /* WARNING: this line is magic. Remove this + * and things break. */ + /* enable variable rate */ + tmp16 = via_ac97_read_reg (card->ac97, AC97_EXTENDED_STATUS); + if ((tmp16 & 1) == 0) + via_ac97_write_reg (card->ac97, AC97_EXTENDED_STATUS, tmp16 | 1); + + DPRINTK ("EXIT, returning 0\n"); + return 0; +} + + +static void via_ac97_codec_wait (struct ac97_codec *codec) +{ + assert (codec->private_data != NULL); + via_ac97_wait_idle (codec->private_data); +} + + +static int __devinit via_ac97_init (struct via_info *card) +{ + int rc; + u16 tmp16; + + DPRINTK ("ENTER\n"); + + assert (card != NULL); + + card->ac97 = ac97_alloc_codec(); + if(card->ac97 == NULL) + return -ENOMEM; + + card->ac97->private_data = card; + card->ac97->codec_read = via_ac97_read_reg; + card->ac97->codec_write = via_ac97_write_reg; + card->ac97->codec_wait = via_ac97_codec_wait; + + card->ac97->dev_mixer = register_sound_mixer (&via_mixer_fops, -1); + if (card->ac97->dev_mixer < 0) { + printk (KERN_ERR PFX "unable to register AC97 mixer, aborting\n"); + DPRINTK ("EXIT, returning -EIO\n"); + ac97_release_codec(card->ac97); + return -EIO; + } + + rc = via_ac97_reset (card); + if (rc) { + printk (KERN_ERR PFX "unable to reset AC97 codec, aborting\n"); + goto err_out; + } + + mdelay(10); + + if (ac97_probe_codec (card->ac97) == 0) { + printk (KERN_ERR PFX "unable to probe AC97 codec, aborting\n"); + rc = -EIO; + goto err_out; + } + + /* enable variable rate */ + tmp16 = via_ac97_read_reg (card->ac97, AC97_EXTENDED_STATUS); + via_ac97_write_reg (card->ac97, AC97_EXTENDED_STATUS, tmp16 | 1); + + /* + * If we cannot enable VRA, we have a locked-rate codec. + * We try again to enable VRA before assuming so, however. + */ + tmp16 = via_ac97_read_reg (card->ac97, AC97_EXTENDED_STATUS); + if ((tmp16 & 1) == 0) { + via_ac97_write_reg (card->ac97, AC97_EXTENDED_STATUS, tmp16 | 1); + tmp16 = via_ac97_read_reg (card->ac97, AC97_EXTENDED_STATUS); + if ((tmp16 & 1) == 0) { + card->locked_rate = 1; + printk (KERN_WARNING PFX "Codec rate locked at 48Khz\n"); + } + } + + DPRINTK ("EXIT, returning 0\n"); + return 0; + +err_out: + unregister_sound_mixer (card->ac97->dev_mixer); + DPRINTK ("EXIT, returning %d\n", rc); + ac97_release_codec(card->ac97); + return rc; +} + + +static void via_ac97_cleanup (struct via_info *card) +{ + DPRINTK ("ENTER\n"); + + assert (card != NULL); + assert (card->ac97->dev_mixer >= 0); + + unregister_sound_mixer (card->ac97->dev_mixer); + ac97_release_codec(card->ac97); + + DPRINTK ("EXIT\n"); +} + + + +/**************************************************************** + * + * Interrupt-related code + * + */ + +/** + * via_intr_channel - handle an interrupt for a single channel + * @card: unused + * @chan: handle interrupt for this channel + * + * This is the "meat" of the interrupt handler, + * containing the actions taken each time an interrupt + * occurs. All communication and coordination with + * userspace takes place here. + * + * Locking: inside card->lock + */ + +static void via_intr_channel (struct via_info *card, struct via_channel *chan) +{ + u8 status; + int n; + + /* check pertinent bits of status register for action bits */ + status = inb (chan->iobase) & (VIA_SGD_FLAG | VIA_SGD_EOL | VIA_SGD_STOPPED); + if (!status) + return; + + /* acknowledge any flagged bits ASAP */ + outb (status, chan->iobase); + + if (!chan->sgtable) /* XXX: temporary solution */ + return; + + /* grab current h/w ptr value */ + n = atomic_read (&chan->hw_ptr); + + /* sanity check: make sure our h/w ptr doesn't have a weird value */ + assert (n >= 0); + assert (n < chan->frag_number); + + + /* reset SGD data structure in memory to reflect a full buffer, + * and advance the h/w ptr, wrapping around to zero if needed + */ + if (n == (chan->frag_number - 1)) { + chan->sgtable[n].count = cpu_to_le32(chan->frag_size | VIA_EOL); + atomic_set (&chan->hw_ptr, 0); + } else { + chan->sgtable[n].count = cpu_to_le32(chan->frag_size | VIA_FLAG); + atomic_inc (&chan->hw_ptr); + } + + /* accounting crap for SNDCTL_DSP_GETxPTR */ + chan->n_irqs++; + chan->bytes += chan->frag_size; + /* FIXME - signed overflow is undefined */ + if (chan->bytes < 0) /* handle overflow of 31-bit value */ + chan->bytes = chan->frag_size; + /* all following checks only occur when not in mmap(2) mode */ + if (!chan->is_mapped) + { + /* If we are recording, then n_frags represents the number + * of fragments waiting to be handled by userspace. + * If we are playback, then n_frags represents the number + * of fragments remaining to be filled by userspace. + * We increment here. If we reach max number of fragments, + * this indicates an underrun/overrun. For this case under OSS, + * we stop the record/playback process. + */ + if (atomic_read (&chan->n_frags) < chan->frag_number) + atomic_inc (&chan->n_frags); + assert (atomic_read (&chan->n_frags) <= chan->frag_number); + if (atomic_read (&chan->n_frags) == chan->frag_number) { + chan->is_active = 0; + via_chan_stop (chan->iobase); + } + } + /* wake up anyone listening to see when interrupts occur */ + wake_up_all (&chan->wait); + + DPRINTK ("%s intr, status=0x%02X, hwptr=0x%lX, chan->hw_ptr=%d\n", + chan->name, status, (long) inl (chan->iobase + 0x04), + atomic_read (&chan->hw_ptr)); + + DPRINTK ("%s intr, channel n_frags == %d, missed %d\n", chan->name, + atomic_read (&chan->n_frags), missed); +} + + +static irqreturn_t via_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct via_info *card = dev_id; + u32 status32; + + /* to minimize interrupt sharing costs, we use the SGD status + * shadow register to check the status of all inputs and + * outputs with a single 32-bit bus read. If no interrupt + * conditions are flagged, we exit immediately + */ + status32 = inl (card->baseaddr + VIA_BASE0_SGD_STATUS_SHADOW); + if (!(status32 & VIA_INTR_MASK)) + { +#ifdef CONFIG_MIDI_VIA82CXXX + if (card->midi_devc) + uart401intr(irq, card->midi_devc, regs); +#endif + return IRQ_HANDLED; + } + DPRINTK ("intr, status32 == 0x%08X\n", status32); + + /* synchronize interrupt handling under SMP. this spinlock + * goes away completely on UP + */ + spin_lock (&card->lock); + + if (status32 & VIA_INTR_OUT) + via_intr_channel (card, &card->ch_out); + if (status32 & VIA_INTR_IN) + via_intr_channel (card, &card->ch_in); + if (status32 & VIA_INTR_FM) + via_intr_channel (card, &card->ch_fm); + + spin_unlock (&card->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t via_new_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct via_info *card = dev_id; + u32 status32; + + /* to minimize interrupt sharing costs, we use the SGD status + * shadow register to check the status of all inputs and + * outputs with a single 32-bit bus read. If no interrupt + * conditions are flagged, we exit immediately + */ + status32 = inl (card->baseaddr + VIA_BASE0_SGD_STATUS_SHADOW); + if (!(status32 & VIA_NEW_INTR_MASK)) + return IRQ_NONE; + /* + * goes away completely on UP + */ + spin_lock (&card->lock); + + via_intr_channel (card, &card->ch_out); + via_intr_channel (card, &card->ch_in); + via_intr_channel (card, &card->ch_fm); + + spin_unlock (&card->lock); + return IRQ_HANDLED; +} + + +/** + * via_interrupt_init - Initialize interrupt handling + * @card: Private info for specified board + * + * Obtain and reserve IRQ for using in handling audio events. + * Also, disable any IRQ-generating resources, to make sure + * we don't get interrupts before we want them. + */ + +static int via_interrupt_init (struct via_info *card) +{ + u8 tmp8; + + DPRINTK ("ENTER\n"); + + assert (card != NULL); + assert (card->pdev != NULL); + + /* check for sane IRQ number. can this ever happen? */ + if (card->pdev->irq < 2) { + printk (KERN_ERR PFX "insane IRQ %d, aborting\n", + card->pdev->irq); + DPRINTK ("EXIT, returning -EIO\n"); + return -EIO; + } + + /* VIA requires this is done */ + pci_write_config_byte(card->pdev, PCI_INTERRUPT_LINE, card->pdev->irq); + + if(card->legacy) + { + /* make sure FM irq is not routed to us */ + pci_read_config_byte (card->pdev, VIA_FM_NMI_CTRL, &tmp8); + if ((tmp8 & VIA_CR48_FM_TRAP_TO_NMI) == 0) { + tmp8 |= VIA_CR48_FM_TRAP_TO_NMI; + pci_write_config_byte (card->pdev, VIA_FM_NMI_CTRL, tmp8); + } + if (request_irq (card->pdev->irq, via_interrupt, SA_SHIRQ, VIA_MODULE_NAME, card)) { + printk (KERN_ERR PFX "unable to obtain IRQ %d, aborting\n", + card->pdev->irq); + DPRINTK ("EXIT, returning -EBUSY\n"); + return -EBUSY; + } + } + else + { + if (request_irq (card->pdev->irq, via_new_interrupt, SA_SHIRQ, VIA_MODULE_NAME, card)) { + printk (KERN_ERR PFX "unable to obtain IRQ %d, aborting\n", + card->pdev->irq); + DPRINTK ("EXIT, returning -EBUSY\n"); + return -EBUSY; + } + } + + DPRINTK ("EXIT, returning 0\n"); + return 0; +} + + +/**************************************************************** + * + * OSS DSP device + * + */ + +static struct file_operations via_dsp_fops = { + .owner = THIS_MODULE, + .open = via_dsp_open, + .release = via_dsp_release, + .read = via_dsp_read, + .write = via_dsp_write, + .poll = via_dsp_poll, + .llseek = no_llseek, + .ioctl = via_dsp_ioctl, + .mmap = via_dsp_mmap, +}; + + +static int __devinit via_dsp_init (struct via_info *card) +{ + u8 tmp8; + + DPRINTK ("ENTER\n"); + + assert (card != NULL); + + if(card->legacy) + { + /* turn off legacy features, if not already */ + pci_read_config_byte (card->pdev, VIA_FUNC_ENABLE, &tmp8); + if (tmp8 & (VIA_CR42_SB_ENABLE | VIA_CR42_FM_ENABLE)) { + tmp8 &= ~(VIA_CR42_SB_ENABLE | VIA_CR42_FM_ENABLE); + pci_write_config_byte (card->pdev, VIA_FUNC_ENABLE, tmp8); + } + } + + via_stop_everything (card); + + card->dev_dsp = register_sound_dsp (&via_dsp_fops, -1); + if (card->dev_dsp < 0) { + DPRINTK ("EXIT, returning -ENODEV\n"); + return -ENODEV; + } + DPRINTK ("EXIT, returning 0\n"); + return 0; +} + + +static void via_dsp_cleanup (struct via_info *card) +{ + DPRINTK ("ENTER\n"); + + assert (card != NULL); + assert (card->dev_dsp >= 0); + + via_stop_everything (card); + + unregister_sound_dsp (card->dev_dsp); + + DPRINTK ("EXIT\n"); +} + + +static struct page * via_mm_nopage (struct vm_area_struct * vma, + unsigned long address, int *type) +{ + struct via_info *card = vma->vm_private_data; + struct via_channel *chan = &card->ch_out; + struct page *dmapage; + unsigned long pgoff; + int rd, wr; + + DPRINTK ("ENTER, start %lXh, ofs %lXh, pgoff %ld, addr %lXh\n", + vma->vm_start, + address - vma->vm_start, + (address - vma->vm_start) >> PAGE_SHIFT, + address); + + if (address > vma->vm_end) { + DPRINTK ("EXIT, returning NOPAGE_SIGBUS\n"); + return NOPAGE_SIGBUS; /* Disallow mremap */ + } + if (!card) { + DPRINTK ("EXIT, returning NOPAGE_OOM\n"); + return NOPAGE_OOM; /* Nothing allocated */ + } + + pgoff = vma->vm_pgoff + ((address - vma->vm_start) >> PAGE_SHIFT); + rd = card->ch_in.is_mapped; + wr = card->ch_out.is_mapped; + +#ifndef VIA_NDEBUG + { + unsigned long max_bufs = chan->frag_number; + if (rd && wr) max_bufs *= 2; + /* via_dsp_mmap() should ensure this */ + assert (pgoff < max_bufs); + } +#endif + + /* if full-duplex (read+write) and we have two sets of bufs, + * then the playback buffers come first, sez soundcard.c */ + if (pgoff >= chan->page_number) { + pgoff -= chan->page_number; + chan = &card->ch_in; + } else if (!wr) + chan = &card->ch_in; + + assert ((((unsigned long)chan->pgtbl[pgoff].cpuaddr) % PAGE_SIZE) == 0); + + dmapage = virt_to_page (chan->pgtbl[pgoff].cpuaddr); + DPRINTK ("EXIT, returning page %p for cpuaddr %lXh\n", + dmapage, (unsigned long) chan->pgtbl[pgoff].cpuaddr); + get_page (dmapage); + if (type) + *type = VM_FAULT_MINOR; + return dmapage; +} + + +#ifndef VM_RESERVED +static int via_mm_swapout (struct page *page, struct file *filp) +{ + return 0; +} +#endif /* VM_RESERVED */ + + +static struct vm_operations_struct via_mm_ops = { + .nopage = via_mm_nopage, + +#ifndef VM_RESERVED + .swapout = via_mm_swapout, +#endif +}; + + +static int via_dsp_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct via_info *card; + int nonblock = (file->f_flags & O_NONBLOCK); + int rc = -EINVAL, rd=0, wr=0; + unsigned long max_size, size, start, offset; + + assert (file != NULL); + assert (vma != NULL); + card = file->private_data; + assert (card != NULL); + + DPRINTK ("ENTER, start %lXh, size %ld, pgoff %ld\n", + vma->vm_start, + vma->vm_end - vma->vm_start, + vma->vm_pgoff); + + max_size = 0; + if (vma->vm_flags & VM_READ) { + rd = 1; + via_chan_set_buffering(card, &card->ch_in, -1); + via_chan_buffer_init (card, &card->ch_in); + max_size += card->ch_in.page_number << PAGE_SHIFT; + } + if (vma->vm_flags & VM_WRITE) { + wr = 1; + via_chan_set_buffering(card, &card->ch_out, -1); + via_chan_buffer_init (card, &card->ch_out); + max_size += card->ch_out.page_number << PAGE_SHIFT; + } + + start = vma->vm_start; + offset = (vma->vm_pgoff << PAGE_SHIFT); + size = vma->vm_end - vma->vm_start; + + /* some basic size/offset sanity checks */ + if (size > max_size) + goto out; + if (offset > max_size - size) + goto out; + + rc = via_syscall_down (card, nonblock); + if (rc) goto out; + + vma->vm_ops = &via_mm_ops; + vma->vm_private_data = card; + +#ifdef VM_RESERVED + vma->vm_flags |= VM_RESERVED; +#endif + + if (rd) + card->ch_in.is_mapped = 1; + if (wr) + card->ch_out.is_mapped = 1; + + up (&card->syscall_sem); + rc = 0; + +out: + DPRINTK ("EXIT, returning %d\n", rc); + return rc; +} + + +static ssize_t via_dsp_do_read (struct via_info *card, + char __user *userbuf, size_t count, + int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + const char __user *orig_userbuf = userbuf; + struct via_channel *chan = &card->ch_in; + size_t size; + int n, tmp; + ssize_t ret = 0; + + /* if SGD has not yet been started, start it */ + via_chan_maybe_start (chan); + +handle_one_block: + /* just to be a nice neighbor */ + /* Thomas Sailer: + * But also to ourselves, release semaphore if we do so */ + if (need_resched()) { + up(&card->syscall_sem); + schedule (); + ret = via_syscall_down (card, nonblock); + if (ret) + goto out; + } + + /* grab current channel software pointer. In the case of + * recording, this is pointing to the next buffer that + * will receive data from the audio hardware. + */ + n = chan->sw_ptr; + + /* n_frags represents the number of fragments waiting + * to be copied to userland. sleep until at least + * one buffer has been read from the audio hardware. + */ + add_wait_queue(&chan->wait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + tmp = atomic_read (&chan->n_frags); + assert (tmp >= 0); + assert (tmp <= chan->frag_number); + if (tmp) + break; + if (nonblock || !chan->is_active) { + ret = -EAGAIN; + break; + } + + up(&card->syscall_sem); + + DPRINTK ("Sleeping on block %d\n", n); + schedule(); + + ret = via_syscall_down (card, nonblock); + if (ret) + break; + + if (signal_pending (current)) { + ret = -ERESTARTSYS; + break; + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&chan->wait, &wait); + if (ret) + goto out; + + /* Now that we have a buffer we can read from, send + * as much as sample data possible to userspace. + */ + while ((count > 0) && (chan->slop_len < chan->frag_size)) { + size_t slop_left = chan->frag_size - chan->slop_len; + void *base = chan->pgtbl[n / (PAGE_SIZE / chan->frag_size)].cpuaddr; + unsigned ofs = (n % (PAGE_SIZE / chan->frag_size)) * chan->frag_size; + + size = (count < slop_left) ? count : slop_left; + if (copy_to_user (userbuf, + base + ofs + chan->slop_len, + size)) { + ret = -EFAULT; + goto out; + } + + count -= size; + chan->slop_len += size; + userbuf += size; + } + + /* If we didn't copy the buffer completely to userspace, + * stop now. + */ + if (chan->slop_len < chan->frag_size) + goto out; + + /* + * If we get to this point, we copied one buffer completely + * to userspace, give the buffer back to the hardware. + */ + + /* advance channel software pointer to point to + * the next buffer from which we will copy + */ + if (chan->sw_ptr == (chan->frag_number - 1)) + chan->sw_ptr = 0; + else + chan->sw_ptr++; + + /* mark one less buffer waiting to be processed */ + assert (atomic_read (&chan->n_frags) > 0); + atomic_dec (&chan->n_frags); + + /* we are at a block boundary, there is no fragment data */ + chan->slop_len = 0; + + DPRINTK ("Flushed block %u, sw_ptr now %u, n_frags now %d\n", + n, chan->sw_ptr, atomic_read (&chan->n_frags)); + + DPRINTK ("regs==%02X %02X %02X %08X %08X %08X %08X\n", + inb (card->baseaddr + 0x00), + inb (card->baseaddr + 0x01), + inb (card->baseaddr + 0x02), + inl (card->baseaddr + 0x04), + inl (card->baseaddr + 0x0C), + inl (card->baseaddr + 0x80), + inl (card->baseaddr + 0x84)); + + if (count > 0) + goto handle_one_block; + +out: + return (userbuf != orig_userbuf) ? (userbuf - orig_userbuf) : ret; +} + + +static ssize_t via_dsp_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct via_info *card; + int nonblock = (file->f_flags & O_NONBLOCK); + int rc; + + DPRINTK ("ENTER, file=%p, buffer=%p, count=%u, ppos=%lu\n", + file, buffer, count, ppos ? ((unsigned long)*ppos) : 0); + + assert (file != NULL); + card = file->private_data; + assert (card != NULL); + + rc = via_syscall_down (card, nonblock); + if (rc) goto out; + + if (card->ch_in.is_mapped) { + rc = -ENXIO; + goto out_up; + } + + via_chan_set_buffering(card, &card->ch_in, -1); + rc = via_chan_buffer_init (card, &card->ch_in); + + if (rc) + goto out_up; + + rc = via_dsp_do_read (card, buffer, count, nonblock); + +out_up: + up (&card->syscall_sem); +out: + DPRINTK ("EXIT, returning %ld\n",(long) rc); + return rc; +} + + +static ssize_t via_dsp_do_write (struct via_info *card, + const char __user *userbuf, size_t count, + int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + const char __user *orig_userbuf = userbuf; + struct via_channel *chan = &card->ch_out; + volatile struct via_sgd_table *sgtable = chan->sgtable; + size_t size; + int n, tmp; + ssize_t ret = 0; + +handle_one_block: + /* just to be a nice neighbor */ + /* Thomas Sailer: + * But also to ourselves, release semaphore if we do so */ + if (need_resched()) { + up(&card->syscall_sem); + schedule (); + ret = via_syscall_down (card, nonblock); + if (ret) + goto out; + } + + /* grab current channel fragment pointer. In the case of + * playback, this is pointing to the next fragment that + * should receive data from userland. + */ + n = chan->sw_ptr; + + /* n_frags represents the number of fragments remaining + * to be filled by userspace. Sleep until + * at least one fragment is available for our use. + */ + add_wait_queue(&chan->wait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + tmp = atomic_read (&chan->n_frags); + assert (tmp >= 0); + assert (tmp <= chan->frag_number); + if (tmp) + break; + if (nonblock || !chan->is_active) { + ret = -EAGAIN; + break; + } + + up(&card->syscall_sem); + + DPRINTK ("Sleeping on page %d, tmp==%d, ir==%d\n", n, tmp, chan->is_record); + schedule(); + + ret = via_syscall_down (card, nonblock); + if (ret) + break; + + if (signal_pending (current)) { + ret = -ERESTARTSYS; + break; + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&chan->wait, &wait); + if (ret) + goto out; + + /* Now that we have at least one fragment we can write to, fill the buffer + * as much as possible with data from userspace. + */ + while ((count > 0) && (chan->slop_len < chan->frag_size)) { + size_t slop_left = chan->frag_size - chan->slop_len; + + size = (count < slop_left) ? count : slop_left; + if (copy_from_user (chan->pgtbl[n / (PAGE_SIZE / chan->frag_size)].cpuaddr + (n % (PAGE_SIZE / chan->frag_size)) * chan->frag_size + chan->slop_len, + userbuf, size)) { + ret = -EFAULT; + goto out; + } + + count -= size; + chan->slop_len += size; + userbuf += size; + } + + /* If we didn't fill up the buffer with data, stop now. + * Put a 'stop' marker in the DMA table too, to tell the + * audio hardware to stop if it gets here. + */ + if (chan->slop_len < chan->frag_size) { + sgtable[n].count = cpu_to_le32 (chan->slop_len | VIA_EOL | VIA_STOP); + goto out; + } + + /* + * If we get to this point, we have filled a buffer with + * audio data, flush the buffer to audio hardware. + */ + + /* Record the true size for the audio hardware to notice */ + if (n == (chan->frag_number - 1)) + sgtable[n].count = cpu_to_le32 (chan->frag_size | VIA_EOL); + else + sgtable[n].count = cpu_to_le32 (chan->frag_size | VIA_FLAG); + + /* advance channel software pointer to point to + * the next buffer we will fill with data + */ + if (chan->sw_ptr == (chan->frag_number - 1)) + chan->sw_ptr = 0; + else + chan->sw_ptr++; + + /* mark one less buffer as being available for userspace consumption */ + assert (atomic_read (&chan->n_frags) > 0); + atomic_dec (&chan->n_frags); + + /* we are at a block boundary, there is no fragment data */ + chan->slop_len = 0; + + /* if SGD has not yet been started, start it */ + via_chan_maybe_start (chan); + + DPRINTK ("Flushed block %u, sw_ptr now %u, n_frags now %d\n", + n, chan->sw_ptr, atomic_read (&chan->n_frags)); + + DPRINTK ("regs==S=%02X C=%02X TP=%02X BP=%08X RT=%08X SG=%08X CC=%08X SS=%08X\n", + inb (card->baseaddr + 0x00), + inb (card->baseaddr + 0x01), + inb (card->baseaddr + 0x02), + inl (card->baseaddr + 0x04), + inl (card->baseaddr + 0x08), + inl (card->baseaddr + 0x0C), + inl (card->baseaddr + 0x80), + inl (card->baseaddr + 0x84)); + + if (count > 0) + goto handle_one_block; + +out: + if (userbuf - orig_userbuf) + return userbuf - orig_userbuf; + else + return ret; +} + + +static ssize_t via_dsp_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct via_info *card; + ssize_t rc; + int nonblock = (file->f_flags & O_NONBLOCK); + + DPRINTK ("ENTER, file=%p, buffer=%p, count=%u, ppos=%lu\n", + file, buffer, count, ppos ? ((unsigned long)*ppos) : 0); + + assert (file != NULL); + card = file->private_data; + assert (card != NULL); + + rc = via_syscall_down (card, nonblock); + if (rc) goto out; + + if (card->ch_out.is_mapped) { + rc = -ENXIO; + goto out_up; + } + + via_chan_set_buffering(card, &card->ch_out, -1); + rc = via_chan_buffer_init (card, &card->ch_out); + + if (rc) + goto out_up; + + rc = via_dsp_do_write (card, buffer, count, nonblock); + +out_up: + up (&card->syscall_sem); +out: + DPRINTK ("EXIT, returning %ld\n",(long) rc); + return rc; +} + + +static unsigned int via_dsp_poll(struct file *file, struct poll_table_struct *wait) +{ + struct via_info *card; + struct via_channel *chan; + unsigned int mask = 0; + + DPRINTK ("ENTER\n"); + + assert (file != NULL); + card = file->private_data; + assert (card != NULL); + + if (file->f_mode & FMODE_READ) { + chan = &card->ch_in; + if (sg_active (chan->iobase)) + poll_wait(file, &chan->wait, wait); + if (atomic_read (&chan->n_frags) > 0) + mask |= POLLIN | POLLRDNORM; + } + + if (file->f_mode & FMODE_WRITE) { + chan = &card->ch_out; + if (sg_active (chan->iobase)) + poll_wait(file, &chan->wait, wait); + if (atomic_read (&chan->n_frags) > 0) + mask |= POLLOUT | POLLWRNORM; + } + + DPRINTK ("EXIT, returning %u\n", mask); + return mask; +} + + +/** + * via_dsp_drain_playback - sleep until all playback samples are flushed + * @card: Private info for specified board + * @chan: Channel to drain + * @nonblock: boolean, non-zero if O_NONBLOCK is set + * + * Sleeps until all playback has been flushed to the audio + * hardware. + * + * Locking: inside card->syscall_sem + */ + +static int via_dsp_drain_playback (struct via_info *card, + struct via_channel *chan, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + int ret = 0; + + DPRINTK ("ENTER, nonblock = %d\n", nonblock); + + if (chan->slop_len > 0) + via_chan_flush_frag (chan); + + if (atomic_read (&chan->n_frags) == chan->frag_number) + goto out; + + via_chan_maybe_start (chan); + + add_wait_queue(&chan->wait, &wait); + for (;;) { + DPRINTK ("FRAGS %d FRAGNUM %d\n", atomic_read(&chan->n_frags), chan->frag_number); + __set_current_state(TASK_INTERRUPTIBLE); + if (atomic_read (&chan->n_frags) >= chan->frag_number) + break; + + if (nonblock) { + DPRINTK ("EXIT, returning -EAGAIN\n"); + ret = -EAGAIN; + break; + } + +#ifdef VIA_DEBUG + { + u8 r40,r41,r42,r43,r44,r48; + pci_read_config_byte (card->pdev, 0x40, &r40); + pci_read_config_byte (card->pdev, 0x41, &r41); + pci_read_config_byte (card->pdev, 0x42, &r42); + pci_read_config_byte (card->pdev, 0x43, &r43); + pci_read_config_byte (card->pdev, 0x44, &r44); + pci_read_config_byte (card->pdev, 0x48, &r48); + DPRINTK ("PCI config: %02X %02X %02X %02X %02X %02X\n", + r40,r41,r42,r43,r44,r48); + + DPRINTK ("regs==%02X %02X %02X %08X %08X %08X %08X\n", + inb (card->baseaddr + 0x00), + inb (card->baseaddr + 0x01), + inb (card->baseaddr + 0x02), + inl (card->baseaddr + 0x04), + inl (card->baseaddr + 0x0C), + inl (card->baseaddr + 0x80), + inl (card->baseaddr + 0x84)); + } + + if (!chan->is_active) + printk (KERN_ERR "sleeping but not active\n"); +#endif + + up(&card->syscall_sem); + + DPRINTK ("sleeping, nbufs=%d\n", atomic_read (&chan->n_frags)); + schedule(); + + if ((ret = via_syscall_down (card, nonblock))) + break; + + if (signal_pending (current)) { + DPRINTK ("EXIT, returning -ERESTARTSYS\n"); + ret = -ERESTARTSYS; + break; + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&chan->wait, &wait); + +#ifdef VIA_DEBUG + { + u8 r40,r41,r42,r43,r44,r48; + pci_read_config_byte (card->pdev, 0x40, &r40); + pci_read_config_byte (card->pdev, 0x41, &r41); + pci_read_config_byte (card->pdev, 0x42, &r42); + pci_read_config_byte (card->pdev, 0x43, &r43); + pci_read_config_byte (card->pdev, 0x44, &r44); + pci_read_config_byte (card->pdev, 0x48, &r48); + DPRINTK ("PCI config: %02X %02X %02X %02X %02X %02X\n", + r40,r41,r42,r43,r44,r48); + + DPRINTK ("regs==%02X %02X %02X %08X %08X %08X %08X\n", + inb (card->baseaddr + 0x00), + inb (card->baseaddr + 0x01), + inb (card->baseaddr + 0x02), + inl (card->baseaddr + 0x04), + inl (card->baseaddr + 0x0C), + inl (card->baseaddr + 0x80), + inl (card->baseaddr + 0x84)); + + DPRINTK ("final nbufs=%d\n", atomic_read (&chan->n_frags)); + } +#endif + +out: + DPRINTK ("EXIT, returning %d\n", ret); + return ret; +} + + +/** + * via_dsp_ioctl_space - get information about channel buffering + * @card: Private info for specified board + * @chan: pointer to channel-specific info + * @arg: user buffer for returned information + * + * Handles SNDCTL_DSP_GETISPACE and SNDCTL_DSP_GETOSPACE. + * + * Locking: inside card->syscall_sem + */ + +static int via_dsp_ioctl_space (struct via_info *card, + struct via_channel *chan, + void __user *arg) +{ + audio_buf_info info; + + via_chan_set_buffering(card, chan, -1); + + info.fragstotal = chan->frag_number; + info.fragsize = chan->frag_size; + + /* number of full fragments we can read/write without blocking */ + info.fragments = atomic_read (&chan->n_frags); + + if ((chan->slop_len % chan->frag_size > 0) && (info.fragments > 0)) + info.fragments--; + + /* number of bytes that can be read or written immediately + * without blocking. + */ + info.bytes = (info.fragments * chan->frag_size); + if (chan->slop_len % chan->frag_size > 0) + info.bytes += chan->frag_size - (chan->slop_len % chan->frag_size); + + DPRINTK ("EXIT, returning fragstotal=%d, fragsize=%d, fragments=%d, bytes=%d\n", + info.fragstotal, + info.fragsize, + info.fragments, + info.bytes); + + return copy_to_user (arg, &info, sizeof (info))?-EFAULT:0; +} + + +/** + * via_dsp_ioctl_ptr - get information about hardware buffer ptr + * @card: Private info for specified board + * @chan: pointer to channel-specific info + * @arg: user buffer for returned information + * + * Handles SNDCTL_DSP_GETIPTR and SNDCTL_DSP_GETOPTR. + * + * Locking: inside card->syscall_sem + */ + +static int via_dsp_ioctl_ptr (struct via_info *card, + struct via_channel *chan, + void __user *arg) +{ + count_info info; + + spin_lock_irq (&card->lock); + + info.bytes = chan->bytes; + info.blocks = chan->n_irqs; + chan->n_irqs = 0; + + spin_unlock_irq (&card->lock); + + if (chan->is_active) { + unsigned long extra; + info.ptr = atomic_read (&chan->hw_ptr) * chan->frag_size; + extra = chan->frag_size - via_sg_offset(chan); + info.ptr += extra; + info.bytes += extra; + } else { + info.ptr = 0; + } + + DPRINTK ("EXIT, returning bytes=%d, blocks=%d, ptr=%d\n", + info.bytes, + info.blocks, + info.ptr); + + return copy_to_user (arg, &info, sizeof (info))?-EFAULT:0; +} + + +static int via_dsp_ioctl_trigger (struct via_channel *chan, int val) +{ + int enable, do_something; + + if (chan->is_record) + enable = (val & PCM_ENABLE_INPUT); + else + enable = (val & PCM_ENABLE_OUTPUT); + + if (!chan->is_enabled && enable) { + do_something = 1; + } else if (chan->is_enabled && !enable) { + do_something = -1; + } else { + do_something = 0; + } + + DPRINTK ("enable=%d, do_something=%d\n", + enable, do_something); + + if (chan->is_active && do_something) + return -EINVAL; + + if (do_something == 1) { + chan->is_enabled = 1; + via_chan_maybe_start (chan); + DPRINTK ("Triggering input\n"); + } + + else if (do_something == -1) { + chan->is_enabled = 0; + DPRINTK ("Setup input trigger\n"); + } + + return 0; +} + + +static int via_dsp_ioctl (struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int rc, rd=0, wr=0, val=0; + struct via_info *card; + struct via_channel *chan; + int nonblock = (file->f_flags & O_NONBLOCK); + int __user *ip = (int __user *)arg; + void __user *p = (void __user *)arg; + + assert (file != NULL); + card = file->private_data; + assert (card != NULL); + + if (file->f_mode & FMODE_WRITE) + wr = 1; + if (file->f_mode & FMODE_READ) + rd = 1; + + rc = via_syscall_down (card, nonblock); + if (rc) + return rc; + rc = -EINVAL; + + switch (cmd) { + + /* OSS API version. XXX unverified */ + case OSS_GETVERSION: + DPRINTK ("ioctl OSS_GETVERSION, EXIT, returning SOUND_VERSION\n"); + rc = put_user (SOUND_VERSION, ip); + break; + + /* list of supported PCM data formats */ + case SNDCTL_DSP_GETFMTS: + DPRINTK ("DSP_GETFMTS, EXIT, returning AFMT U8|S16_LE\n"); + rc = put_user (AFMT_U8 | AFMT_S16_LE, ip); + break; + + /* query or set current channel's PCM data format */ + case SNDCTL_DSP_SETFMT: + if (get_user(val, ip)) { + rc = -EFAULT; + break; + } + DPRINTK ("DSP_SETFMT, val==%d\n", val); + if (val != AFMT_QUERY) { + rc = 0; + + if (rd) + rc = via_chan_set_fmt (card, &card->ch_in, val); + + if (rc >= 0 && wr) + rc = via_chan_set_fmt (card, &card->ch_out, val); + + if (rc < 0) + break; + + val = rc; + } else { + if ((rd && (card->ch_in.pcm_fmt & VIA_PCM_FMT_16BIT)) || + (wr && (card->ch_out.pcm_fmt & VIA_PCM_FMT_16BIT))) + val = AFMT_S16_LE; + else + val = AFMT_U8; + } + DPRINTK ("SETFMT EXIT, returning %d\n", val); + rc = put_user (val, ip); + break; + + /* query or set number of channels (1=mono, 2=stereo, 4/6 for multichannel) */ + case SNDCTL_DSP_CHANNELS: + if (get_user(val, ip)) { + rc = -EFAULT; + break; + } + DPRINTK ("DSP_CHANNELS, val==%d\n", val); + if (val != 0) { + rc = 0; + + if (rd) + rc = via_chan_set_stereo (card, &card->ch_in, val); + + if (rc >= 0 && wr) + rc = via_chan_set_stereo (card, &card->ch_out, val); + + if (rc < 0) + break; + + val = rc; + } else { + if (rd) + val = card->ch_in.channels; + else + val = card->ch_out.channels; + } + DPRINTK ("CHANNELS EXIT, returning %d\n", val); + rc = put_user (val, ip); + break; + + /* enable (val is not zero) or disable (val == 0) stereo */ + case SNDCTL_DSP_STEREO: + if (get_user(val, ip)) { + rc = -EFAULT; + break; + } + DPRINTK ("DSP_STEREO, val==%d\n", val); + rc = 0; + + if (rd) + rc = via_chan_set_stereo (card, &card->ch_in, val ? 2 : 1); + if (rc >= 0 && wr) + rc = via_chan_set_stereo (card, &card->ch_out, val ? 2 : 1); + + if (rc < 0) + break; + + val = rc - 1; + + DPRINTK ("STEREO EXIT, returning %d\n", val); + rc = put_user(val, ip); + break; + + /* query or set sampling rate */ + case SNDCTL_DSP_SPEED: + if (get_user(val, ip)) { + rc = -EFAULT; + break; + } + DPRINTK ("DSP_SPEED, val==%d\n", val); + if (val < 0) { + rc = -EINVAL; + break; + } + if (val > 0) { + rc = 0; + + if (rd) + rc = via_chan_set_speed (card, &card->ch_in, val); + if (rc >= 0 && wr) + rc = via_chan_set_speed (card, &card->ch_out, val); + + if (rc < 0) + break; + + val = rc; + } else { + if (rd) + val = card->ch_in.rate; + else if (wr) + val = card->ch_out.rate; + else + val = 0; + } + DPRINTK ("SPEED EXIT, returning %d\n", val); + rc = put_user (val, ip); + break; + + /* wait until all buffers have been played, and then stop device */ + case SNDCTL_DSP_SYNC: + DPRINTK ("DSP_SYNC\n"); + rc = 0; + if (wr) { + DPRINTK ("SYNC EXIT (after calling via_dsp_drain_playback)\n"); + rc = via_dsp_drain_playback (card, &card->ch_out, nonblock); + } + break; + + /* stop recording/playback immediately */ + case SNDCTL_DSP_RESET: + DPRINTK ("DSP_RESET\n"); + if (rd) { + via_chan_clear (card, &card->ch_in); + card->ch_in.frag_number = 0; + card->ch_in.frag_size = 0; + atomic_set(&card->ch_in.n_frags, 0); + } + + if (wr) { + via_chan_clear (card, &card->ch_out); + card->ch_out.frag_number = 0; + card->ch_out.frag_size = 0; + atomic_set(&card->ch_out.n_frags, 0); + } + + rc = 0; + break; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + rc = 0; + break; + + /* obtain bitmask of device capabilities, such as mmap, full duplex, etc. */ + case SNDCTL_DSP_GETCAPS: + DPRINTK ("DSP_GETCAPS\n"); + rc = put_user(VIA_DSP_CAP, ip); + break; + + /* obtain buffer fragment size */ + case SNDCTL_DSP_GETBLKSIZE: + DPRINTK ("DSP_GETBLKSIZE\n"); + + if (rd) { + via_chan_set_buffering(card, &card->ch_in, -1); + rc = put_user(card->ch_in.frag_size, ip); + } else if (wr) { + via_chan_set_buffering(card, &card->ch_out, -1); + rc = put_user(card->ch_out.frag_size, ip); + } + break; + + /* obtain information about input buffering */ + case SNDCTL_DSP_GETISPACE: + DPRINTK ("DSP_GETISPACE\n"); + if (rd) + rc = via_dsp_ioctl_space (card, &card->ch_in, p); + break; + + /* obtain information about output buffering */ + case SNDCTL_DSP_GETOSPACE: + DPRINTK ("DSP_GETOSPACE\n"); + if (wr) + rc = via_dsp_ioctl_space (card, &card->ch_out, p); + break; + + /* obtain information about input hardware pointer */ + case SNDCTL_DSP_GETIPTR: + DPRINTK ("DSP_GETIPTR\n"); + if (rd) + rc = via_dsp_ioctl_ptr (card, &card->ch_in, p); + break; + + /* obtain information about output hardware pointer */ + case SNDCTL_DSP_GETOPTR: + DPRINTK ("DSP_GETOPTR\n"); + if (wr) + rc = via_dsp_ioctl_ptr (card, &card->ch_out, p); + break; + + /* return number of bytes remaining to be played by DMA engine */ + case SNDCTL_DSP_GETODELAY: + { + DPRINTK ("DSP_GETODELAY\n"); + + chan = &card->ch_out; + + if (!wr) + break; + + if (chan->is_active) { + + val = chan->frag_number - atomic_read (&chan->n_frags); + + assert(val >= 0); + + if (val > 0) { + val *= chan->frag_size; + val -= chan->frag_size - via_sg_offset(chan); + } + val += chan->slop_len % chan->frag_size; + } else + val = 0; + + assert (val <= (chan->frag_size * chan->frag_number)); + + DPRINTK ("GETODELAY EXIT, val = %d bytes\n", val); + rc = put_user (val, ip); + break; + } + + /* handle the quick-start of a channel, + * or the notification that a quick-start will + * occur in the future + */ + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, ip)) { + rc = -EFAULT; + break; + } + DPRINTK ("DSP_SETTRIGGER, rd=%d, wr=%d, act=%d/%d, en=%d/%d\n", + rd, wr, card->ch_in.is_active, card->ch_out.is_active, + card->ch_in.is_enabled, card->ch_out.is_enabled); + + rc = 0; + + if (rd) + rc = via_dsp_ioctl_trigger (&card->ch_in, val); + + if (!rc && wr) + rc = via_dsp_ioctl_trigger (&card->ch_out, val); + + break; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if ((file->f_mode & FMODE_READ) && card->ch_in.is_enabled) + val |= PCM_ENABLE_INPUT; + if ((file->f_mode & FMODE_WRITE) && card->ch_out.is_enabled) + val |= PCM_ENABLE_OUTPUT; + rc = put_user(val, ip); + break; + + /* Enable full duplex. Since we do this as soon as we are opened + * with O_RDWR, this is mainly a no-op that always returns success. + */ + case SNDCTL_DSP_SETDUPLEX: + DPRINTK ("DSP_SETDUPLEX\n"); + if (!rd || !wr) + break; + rc = 0; + break; + + /* set fragment size. implemented as a successful no-op for now */ + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, ip)) { + rc = -EFAULT; + break; + } + DPRINTK ("DSP_SETFRAGMENT, val==%d\n", val); + + if (rd) + rc = via_chan_set_buffering(card, &card->ch_in, val); + + if (wr) + rc = via_chan_set_buffering(card, &card->ch_out, val); + + DPRINTK ("SNDCTL_DSP_SETFRAGMENT (fragshift==0x%04X (%d), maxfrags==0x%04X (%d))\n", + val & 0xFFFF, + val & 0xFFFF, + (val >> 16) & 0xFFFF, + (val >> 16) & 0xFFFF); + + rc = 0; + break; + + /* inform device of an upcoming pause in input (or output). */ + case SNDCTL_DSP_POST: + DPRINTK ("DSP_POST\n"); + if (wr) { + if (card->ch_out.slop_len > 0) + via_chan_flush_frag (&card->ch_out); + via_chan_maybe_start (&card->ch_out); + } + + rc = 0; + break; + + /* not implemented */ + default: + DPRINTK ("unhandled ioctl, cmd==%u, arg==%p\n", + cmd, p); + break; + } + + up (&card->syscall_sem); + DPRINTK ("EXIT, returning %d\n", rc); + return rc; +} + + +static int via_dsp_open (struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct via_info *card; + struct pci_dev *pdev = NULL; + struct via_channel *chan; + struct pci_driver *drvr; + int nonblock = (file->f_flags & O_NONBLOCK); + + DPRINTK ("ENTER, minor=%d, file->f_mode=0x%x\n", minor, file->f_mode); + + if (!(file->f_mode & (FMODE_READ | FMODE_WRITE))) { + DPRINTK ("EXIT, returning -EINVAL\n"); + return -EINVAL; + } + + card = NULL; + while ((pdev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL) { + drvr = pci_dev_driver (pdev); + if (drvr == &via_driver) { + assert (pci_get_drvdata (pdev) != NULL); + + card = pci_get_drvdata (pdev); + DPRINTK ("dev_dsp = %d, minor = %d, assn = %d\n", + card->dev_dsp, minor, + (card->dev_dsp ^ minor) & ~0xf); + + if (((card->dev_dsp ^ minor) & ~0xf) == 0) + goto match; + } + } + + DPRINTK ("no matching %s found\n", card ? "minor" : "driver"); + return -ENODEV; + +match: + if (nonblock) { + if (down_trylock (&card->open_sem)) { + DPRINTK ("EXIT, returning -EAGAIN\n"); + return -EAGAIN; + } + } else { + if (down_interruptible (&card->open_sem)) { + DPRINTK ("EXIT, returning -ERESTARTSYS\n"); + return -ERESTARTSYS; + } + } + + file->private_data = card; + DPRINTK ("file->f_mode == 0x%x\n", file->f_mode); + + /* handle input from analog source */ + if (file->f_mode & FMODE_READ) { + chan = &card->ch_in; + + via_chan_init (card, chan); + + /* why is this forced to 16-bit stereo in all drivers? */ + chan->pcm_fmt = VIA_PCM_FMT_16BIT | VIA_PCM_FMT_STEREO; + chan->channels = 2; + + // TO DO - use FIFO: via_capture_fifo(card, 1); + via_chan_pcm_fmt (chan, 0); + via_set_rate (card->ac97, chan, 44100); + } + + /* handle output to analog source */ + if (file->f_mode & FMODE_WRITE) { + chan = &card->ch_out; + + via_chan_init (card, chan); + + if (file->f_mode & FMODE_READ) { + /* if in duplex mode make the recording and playback channels + have the same settings */ + chan->pcm_fmt = VIA_PCM_FMT_16BIT | VIA_PCM_FMT_STEREO; + chan->channels = 2; + via_chan_pcm_fmt (chan, 0); + via_set_rate (card->ac97, chan, 44100); + } else { + if ((minor & 0xf) == SND_DEV_DSP16) { + chan->pcm_fmt = VIA_PCM_FMT_16BIT; + via_chan_pcm_fmt (chan, 0); + via_set_rate (card->ac97, chan, 44100); + } else { + via_chan_pcm_fmt (chan, 1); + via_set_rate (card->ac97, chan, 8000); + } + } + } + + DPRINTK ("EXIT, returning 0\n"); + return nonseekable_open(inode, file); +} + + +static int via_dsp_release(struct inode *inode, struct file *file) +{ + struct via_info *card; + int nonblock = (file->f_flags & O_NONBLOCK); + int rc; + + DPRINTK ("ENTER\n"); + + assert (file != NULL); + card = file->private_data; + assert (card != NULL); + + rc = via_syscall_down (card, nonblock); + if (rc) { + DPRINTK ("EXIT (syscall_down error), rc=%d\n", rc); + return rc; + } + + if (file->f_mode & FMODE_WRITE) { + rc = via_dsp_drain_playback (card, &card->ch_out, nonblock); + if (rc && rc != -ERESTARTSYS) /* Nobody needs to know about ^C */ + printk (KERN_DEBUG "via_audio: ignoring drain playback error %d\n", rc); + + via_chan_free (card, &card->ch_out); + via_chan_buffer_free(card, &card->ch_out); + } + + if (file->f_mode & FMODE_READ) { + via_chan_free (card, &card->ch_in); + via_chan_buffer_free (card, &card->ch_in); + } + + up (&card->syscall_sem); + up (&card->open_sem); + + DPRINTK ("EXIT, returning 0\n"); + return 0; +} + + +/**************************************************************** + * + * Chip setup and kernel registration + * + * + */ + +static int __devinit via_init_one (struct pci_dev *pdev, const struct pci_device_id *id) +{ +#ifdef CONFIG_MIDI_VIA82CXXX + u8 r42; +#endif + int rc; + struct via_info *card; + static int printed_version; + + DPRINTK ("ENTER\n"); + + if (printed_version++ == 0) + printk (KERN_INFO "Via 686a/8233/8235 audio driver " VIA_VERSION "\n"); + + rc = pci_enable_device (pdev); + if (rc) + goto err_out; + + rc = pci_request_regions (pdev, "via82cxxx_audio"); + if (rc) + goto err_out_disable; + + rc = pci_set_dma_mask(pdev, 0xffffffffULL); + if (rc) + goto err_out_res; + rc = pci_set_consistent_dma_mask(pdev, 0xffffffffULL); + if (rc) + goto err_out_res; + + card = kmalloc (sizeof (*card), GFP_KERNEL); + if (!card) { + printk (KERN_ERR PFX "out of memory, aborting\n"); + rc = -ENOMEM; + goto err_out_res; + } + + pci_set_drvdata (pdev, card); + + memset (card, 0, sizeof (*card)); + card->pdev = pdev; + card->baseaddr = pci_resource_start (pdev, 0); + card->card_num = via_num_cards++; + spin_lock_init (&card->lock); + spin_lock_init (&card->ac97_lock); + init_MUTEX (&card->syscall_sem); + init_MUTEX (&card->open_sem); + + /* we must init these now, in case the intr handler needs them */ + via_chan_init_defaults (card, &card->ch_out); + via_chan_init_defaults (card, &card->ch_in); + via_chan_init_defaults (card, &card->ch_fm); + + /* if BAR 2 is present, chip is Rev H or later, + * which means it has a few extra features */ + if (pci_resource_start (pdev, 2) > 0) + card->rev_h = 1; + + /* Overkill for now, but more flexible done right */ + + card->intmask = id->driver_data; + card->legacy = !card->intmask; + card->sixchannel = id->driver_data; + + if(card->sixchannel) + printk(KERN_INFO PFX "Six channel audio available\n"); + if (pdev->irq < 1) { + printk (KERN_ERR PFX "invalid PCI IRQ %d, aborting\n", pdev->irq); + rc = -ENODEV; + goto err_out_kfree; + } + + if (!(pci_resource_flags (pdev, 0) & IORESOURCE_IO)) { + printk (KERN_ERR PFX "unable to locate I/O resources, aborting\n"); + rc = -ENODEV; + goto err_out_kfree; + } + + pci_set_master(pdev); + + /* + * init AC97 mixer and codec + */ + rc = via_ac97_init (card); + if (rc) { + printk (KERN_ERR PFX "AC97 init failed, aborting\n"); + goto err_out_kfree; + } + + /* + * init DSP device + */ + rc = via_dsp_init (card); + if (rc) { + printk (KERN_ERR PFX "DSP device init failed, aborting\n"); + goto err_out_have_mixer; + } + + /* + * init and turn on interrupts, as the last thing we do + */ + rc = via_interrupt_init (card); + if (rc) { + printk (KERN_ERR PFX "interrupt init failed, aborting\n"); + goto err_out_have_dsp; + } + + printk (KERN_INFO PFX "board #%d at 0x%04lX, IRQ %d\n", + card->card_num + 1, card->baseaddr, pdev->irq); + +#ifdef CONFIG_MIDI_VIA82CXXX + /* Disable by default */ + card->midi_info.io_base = 0; + + if(card->legacy) + { + pci_read_config_byte (pdev, 0x42, &r42); + /* Disable MIDI interrupt */ + pci_write_config_byte (pdev, 0x42, r42 | VIA_CR42_MIDI_IRQMASK); + if (r42 & VIA_CR42_MIDI_ENABLE) + { + if (r42 & VIA_CR42_MIDI_PNP) /* Address selected by iobase 2 - not tested */ + card->midi_info.io_base = pci_resource_start (pdev, 2); + else /* Address selected by byte 0x43 */ + { + u8 r43; + pci_read_config_byte (pdev, 0x43, &r43); + card->midi_info.io_base = 0x300 + ((r43 & 0x0c) << 2); + } + + card->midi_info.irq = -pdev->irq; + if (probe_uart401(& card->midi_info, THIS_MODULE)) + { + card->midi_devc=midi_devs[card->midi_info.slots[4]]->devc; + pci_write_config_byte(pdev, 0x42, r42 & ~VIA_CR42_MIDI_IRQMASK); + printk("Enabled Via MIDI\n"); + } + } + } +#endif + + DPRINTK ("EXIT, returning 0\n"); + return 0; + +err_out_have_dsp: + via_dsp_cleanup (card); + +err_out_have_mixer: + via_ac97_cleanup (card); + +err_out_kfree: +#ifndef VIA_NDEBUG + memset (card, 0xAB, sizeof (*card)); /* poison memory */ +#endif + kfree (card); + +err_out_res: + pci_release_regions (pdev); + +err_out_disable: + pci_disable_device (pdev); + +err_out: + pci_set_drvdata (pdev, NULL); + DPRINTK ("EXIT - returning %d\n", rc); + return rc; +} + + +static void __devexit via_remove_one (struct pci_dev *pdev) +{ + struct via_info *card; + + DPRINTK ("ENTER\n"); + + assert (pdev != NULL); + card = pci_get_drvdata (pdev); + assert (card != NULL); + +#ifdef CONFIG_MIDI_VIA82CXXX + if (card->midi_info.io_base) + unload_uart401(&card->midi_info); +#endif + + free_irq (card->pdev->irq, card); + via_dsp_cleanup (card); + via_ac97_cleanup (card); + +#ifndef VIA_NDEBUG + memset (card, 0xAB, sizeof (*card)); /* poison memory */ +#endif + kfree (card); + + pci_set_drvdata (pdev, NULL); + + pci_release_regions (pdev); + pci_disable_device (pdev); + pci_set_power_state (pdev, 3); /* ...zzzzzz */ + + DPRINTK ("EXIT\n"); + return; +} + + +/**************************************************************** + * + * Driver initialization and cleanup + * + * + */ + +static int __init init_via82cxxx_audio(void) +{ + int rc; + + DPRINTK ("ENTER\n"); + + rc = pci_register_driver (&via_driver); + if (rc) { + DPRINTK ("EXIT, returning %d\n", rc); + return rc; + } + + DPRINTK ("EXIT, returning 0\n"); + return 0; +} + + +static void __exit cleanup_via82cxxx_audio(void) +{ + DPRINTK ("ENTER\n"); + + pci_unregister_driver (&via_driver); + + DPRINTK ("EXIT\n"); +} + + +module_init(init_via82cxxx_audio); +module_exit(cleanup_via82cxxx_audio); + +MODULE_AUTHOR("Jeff Garzik"); +MODULE_DESCRIPTION("DSP audio and mixer driver for Via 82Cxxx audio devices"); +MODULE_LICENSE("GPL"); + diff --git a/sound/oss/vidc.c b/sound/oss/vidc.c new file mode 100644 index 000000000000..00fe5cec9dc1 --- /dev/null +++ b/sound/oss/vidc.c @@ -0,0 +1,561 @@ +/* + * linux/drivers/sound/vidc.c + * + * Copyright (C) 1997-2000 by 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. + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 rougly 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->flags & DMA_ACTIVE)) { + unsigned long flags; + + local_irq_save(flags); + + /* prevent recusion */ + adev->flags |= DMA_ACTIVE; + + dma_interrupt = vidc_audio_dma_interrupt; + vidc_sound_dma_irq(0, NULL, 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)); + +#if defined(CONFIG_SOUND_SOFTOSS) || defined(CONFIG_SOUND_SOFTOSS_MODULE) + softoss_dev = adev; +#endif + 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 000000000000..bab7044572d3 --- /dev/null +++ b/sound/oss/vidc.h @@ -0,0 +1,67 @@ +/* + * linux/drivers/sound/vidc.h + * + * 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. + * + * VIDC sound function prototypes + */ + +/* vidc.c */ + +extern int vidc_busy; + +/* 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, struct pt_regs *regs); + +/* + * 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 000000000000..01ccc074cc11 --- /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 +#include +#include +#include + + .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 000000000000..265423054caf --- /dev/null +++ b/sound/oss/vwsnd.c @@ -0,0 +1,3486 @@ +/* + * 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_sema 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_sema 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 + * Added some __init/__exit + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "sound_config.h" + +/*****************************************************************************/ +/* debug stuff */ + +#ifdef VWSND_DEBUG + +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(__FUNCTION__, __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, __FUNCTION__ , ##args)) +#define DBGE(fmt, args...) (DBGX("%s" fmt, __FUNCTION__ , ##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_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) +{ + static void li_destroy(lithium_t *); + + 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; +} + +/* + * 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; + } +} + +/* + * 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 = (!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 = (!LI_CCTL_RESET | + SHIFT_FIELD(size, LI_CCTL_SIZE) | + !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_sema 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_sema and devc->io_sema. + */ + +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 semaphore open_sema; + struct semaphore io_sema; + struct semaphore mix_sema; + mode_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_sema 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, struct pt_regs *regs) +{ + vwsnd_dev_t *devc = (vwsnd_dev_t *) dev_id; + unsigned int status; + + DBGEV("(irq=%d, dev_id=0x%p, regs=0x%p)\n", irq, dev_id, regs); + + 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; + + down(&devc->io_sema); + ret = vwsnd_audio_do_read(file, buffer, count, ppos); + up(&devc->io_sema); + 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; + + down(&devc->io_sema); + ret = vwsnd_audio_do_write(file, buffer, count, ppos); + up(&devc->io_sema); + 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 inode *inode, + 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("(inode=0x%p, file=0x%p, cmd=0x%x, arg=0x%lx)\n", + inode, 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"); + file->f_flags |= O_NONBLOCK; + 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 int vwsnd_audio_ioctl(struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long arg) +{ + vwsnd_dev_t *devc = (vwsnd_dev_t *) file->private_data; + int ret; + + down(&devc->io_sema); + ret = vwsnd_audio_do_ioctl(inode, file, cmd, arg); + up(&devc->io_sema); + 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); + + 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; + return -ENODEV; + } + + down(&devc->open_sema); + while (devc->open_mode & file->f_mode) { + up(&devc->open_sema); + if (file->f_flags & O_NONBLOCK) { + DEC_USE_COUNT; + return -EBUSY; + } + interruptible_sleep_on(&devc->open_wait); + if (signal_pending(current)) { + DEC_USE_COUNT; + return -ERESTARTSYS; + } + down(&devc->open_sema); + } + devc->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + up(&devc->open_sema); + + /* 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. */ + + down(&devc->io_sema); + { + 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; + } + } + up(&devc->io_sema); + + file->private_data = devc; + DBGRV(); + 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; + + lock_kernel(); + down(&devc->io_sema); + { + 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; + } + up(&devc->io_sema); + + down(&devc->open_sema); + { + devc->open_mode &= ~file->f_mode; + } + up(&devc->open_sema); + wake_up(&devc->open_wait); + DEC_USE_COUNT; + DBGR(); + unlock_kernel(); + return err; +} + +static struct file_operations vwsnd_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = vwsnd_audio_read, + .write = vwsnd_audio_write, + .poll = vwsnd_audio_poll, + .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; + for (devc = vwsnd_dev_list; devc; devc = devc->next_dev) + if (devc->mixer_minor == iminor(inode)) + break; + + if (devc == NULL) { + DEC_USE_COUNT; + return -ENODEV; + } + file->private_data = devc; + 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 int vwsnd_mixer_ioctl(struct inode *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); + + down(&devc->mix_sema); + { + 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; + } + up(&devc->mix_sema); + return retval; +} + +static struct file_operations vwsnd_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .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 */ + + init_MUTEX(&devc->open_sema); + init_MUTEX(&devc->io_sema); + init_MUTEX(&devc->mix_sema); + 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 "); +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 000000000000..99d04ad3ca13 --- /dev/null +++ b/sound/oss/waveartist.c @@ -0,0 +1,2035 @@ +/* + * linux/drivers/sound/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 + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sound_config.h" +#include "waveartist.h" + +#ifdef CONFIG_ARM +#include +#include +#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 = timeout_ms * 10 * HZ / 100; + + do { + set_current_state(TASK_INTERRUPTIBLE); + timeout = schedule_timeout(timeout); + } while (timeout); + + return 0; +} + +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, struct pt_regs *regs) +{ + wavnc_info *devc = (wavnc_info *)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 = (wavnc_port_info *)kmalloc(sizeof(wavnc_port_info), GFP_KERNEL); + if (portc == NULL) + goto nomem; + + memset(portc, 0, sizeof(wavnc_port_info)); + + 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 + +#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 + +extern spinlock_t gpio_lock; + +static inline void +vnc_mute_spkr(wavnc_info *devc) +{ + unsigned long flags; + + spin_lock_irqsave(&gpio_lock, flags); + cpld_modify(CPLD_UNMUTE, devc->spkr_mute_state ? 0 : CPLD_UNMUTE); + spin_unlock_irqrestore(&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_PARM(io, "i"); /* IO base */ +MODULE_PARM(irq, "i"); /* IRQ */ +MODULE_PARM(dma, "i"); /* DMA */ +MODULE_PARM(dma2, "i"); /* DMA2 */ +MODULE_LICENSE("GPL"); diff --git a/sound/oss/waveartist.h b/sound/oss/waveartist.h new file mode 100644 index 000000000000..2033fb87b247 --- /dev/null +++ b/sound/oss/waveartist.h @@ -0,0 +1,92 @@ +/* + * linux/drivers/sound/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); diff --git a/sound/oss/wavfront.c b/sound/oss/wavfront.c new file mode 100644 index 000000000000..cce1278dc487 --- /dev/null +++ b/sound/oss/wavfront.c @@ -0,0 +1,3538 @@ +/* -*- linux-c -*- + * + * sound/wavfront.c + * + * A Linux driver for Turtle Beach WaveFront Series (Maui, Tropez, Tropez Plus) + * + * This driver supports the onboard wavetable synthesizer (an ICS2115), + * including patch, sample and program loading and unloading, conversion + * of GUS patches during loading, and full user-level access to all + * WaveFront commands. It tries to provide semi-intelligent patch and + * sample management as well. + * + * It also provides support for the ICS emulation of an MPU-401. Full + * support for the ICS emulation's "virtual MIDI mode" is provided in + * wf_midi.c. + * + * Support is also provided for the Tropez Plus' onboard FX processor, + * a Yamaha YSS225. Currently, code exists to configure the YSS225, + * and there is an interface allowing tweaking of any of its memory + * addresses. However, I have been unable to decipher the logical + * positioning of the configuration info for various effects, so for + * now, you just get the YSS225 in the same state as Turtle Beach's + * "SETUPSND.EXE" utility leaves it. + * + * The boards' DAC/ADC (a Crystal CS4232) is supported by cs4232.[co], + * This chip also controls the configuration of the card: the wavefront + * synth is logical unit 4. + * + * + * Supported devices: + * + * /dev/dsp - using cs4232+ad1848 modules, OSS compatible + * /dev/midiNN and /dev/midiNN+1 - using wf_midi code, OSS compatible + * /dev/synth00 - raw synth interface + * + ********************************************************************** + * + * Copyright (C) by Paul Barton-Davis 1998 + * + * Some portions of this file are taken from work that is + * copyright (C) by Hannu Savolainen 1993-1996 + * + * Although the relevant code here is all new, the handling of + * sample/alias/multi- samples is entirely based on a driver by Matt + * Martin and Rutger Nijlunsing which demonstrated how to get things + * to work correctly. The GUS patch loading code has been almost + * unaltered by me, except to fit formatting and function names in the + * rest of the file. Many thanks to them. + * + * Appreciation and thanks to Hannu Savolainen for his early work on the Maui + * driver, and answering a few questions while this one was developed. + * + * Absolutely NO thanks to Turtle Beach/Voyetra and Yamaha for their + * complete lack of help in developing this driver, and in particular + * for their utter silence in response to questions about undocumented + * aspects of configuring a WaveFront soundcard, particularly the + * effects processor. + * + * $Id: wavfront.c,v 0.7 1998/09/09 15:47:36 pbd Exp $ + * + * This program 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 + * Added some __init and __initdata to entries in yss225.c + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sound_config.h" + +#include + +#define _MIDI_SYNTH_C_ +#define MIDI_SYNTH_NAME "WaveFront MIDI" +#define MIDI_SYNTH_CAPS SYNTH_CAP_INPUT +#include "midi_synth.h" + +/* Compile-time control of the extent to which OSS is supported. + + I consider /dev/sequencer to be an anachronism, but given its + widespread usage by various Linux MIDI software, it seems worth + offering support to it if it's not too painful. Instead of using + /dev/sequencer, I recommend: + + for synth programming and patch loading: /dev/synthNN + for kernel-synchronized MIDI sequencing: the ALSA sequencer + for direct MIDI control: /dev/midiNN + + I have never tried static compilation into the kernel. The #if's + for this are really just notes to myself about what the code is + for. +*/ + +#define OSS_SUPPORT_SEQ 0x1 /* use of /dev/sequencer */ +#define OSS_SUPPORT_STATIC_INSTALL 0x2 /* static compilation into kernel */ + +#define OSS_SUPPORT_LEVEL 0x1 /* just /dev/sequencer for now */ + +#if OSS_SUPPORT_LEVEL & OSS_SUPPORT_SEQ +static int (*midi_load_patch) (int devno, int format, const char __user *addr, + int offs, int count, int pmgr_flag) = NULL; +#endif /* OSS_SUPPORT_SEQ */ + +/* if WF_DEBUG not defined, no run-time debugging messages will + be available via the debug flag setting. Given the current + beta state of the driver, this will remain set until a future + version. +*/ + +#define WF_DEBUG 1 + +#ifdef WF_DEBUG + +/* Thank goodness for gcc's preprocessor ... */ + +#define DPRINT(cond, format, args...) \ + if ((dev.debug & (cond)) == (cond)) { \ + printk (KERN_DEBUG LOGNAME format, ## args); \ + } +#else +#define DPRINT(cond, format, args...) +#endif + +#define LOGNAME "WaveFront: " + +/* bitmasks for WaveFront status port value */ + +#define STAT_RINTR_ENABLED 0x01 +#define STAT_CAN_READ 0x02 +#define STAT_INTR_READ 0x04 +#define STAT_WINTR_ENABLED 0x10 +#define STAT_CAN_WRITE 0x20 +#define STAT_INTR_WRITE 0x40 + +/*** Module-accessible parameters ***************************************/ + +int wf_raw; /* we normally check for "raw state" to firmware + loading. if set, then during driver loading, the + state of the board is ignored, and we reset the + board and load the firmware anyway. + */ + +static int fx_raw = 1; /* if this is zero, we'll leave the FX processor in + whatever state it is when the driver is loaded. + The default is to download the microprogram and + associated coefficients to set it up for "default" + operation, whatever that means. + */ + +static int debug_default; /* you can set this to control debugging + during driver loading. it takes any combination + of the WF_DEBUG_* flags defined in + wavefront.h + */ + +/* XXX this needs to be made firmware and hardware version dependent */ + +static char *ospath = "/etc/sound/wavefront.os"; /* where to find a processed + version of the WaveFront OS + */ + +static int wait_polls = 2000; /* This is a number of tries we poll the + status register before resorting to sleeping. + WaveFront being an ISA card each poll takes + about 1.2us. So before going to + sleep we wait up to 2.4ms in a loop. + */ + +static int sleep_length = HZ/100; /* This says how long we're going to + sleep between polls. + 10ms sounds reasonable for fast response. + */ + +static int sleep_tries = 50; /* Wait for status 0.5 seconds total. */ + +static int reset_time = 2; /* hundreths of a second we wait after a HW reset for + the expected interrupt. + */ + +static int ramcheck_time = 20; /* time in seconds to wait while ROM code + checks on-board RAM. + */ + +static int osrun_time = 10; /* time in seconds we wait for the OS to + start running. + */ + +module_param(wf_raw, int, 0); +module_param(fx_raw, int, 0); +module_param(debug_default, int, 0); +module_param(wait_polls, int, 0); +module_param(sleep_length, int, 0); +module_param(sleep_tries, int, 0); +module_param(ospath, charp, 0); +module_param(reset_time, int, 0); +module_param(ramcheck_time, int, 0); +module_param(osrun_time, int, 0); + +/***************************************************************************/ + +/* Note: because this module doesn't export any symbols, this really isn't + a global variable, even if it looks like one. I was quite confused by + this when I started writing this as a (newer) module -- pbd. +*/ + +struct wf_config { + int devno; /* device number from kernel */ + int irq; /* "you were one, one of the few ..." */ + int base; /* low i/o port address */ + +#define mpu_data_port base +#define mpu_command_port base + 1 /* write semantics */ +#define mpu_status_port base + 1 /* read semantics */ +#define data_port base + 2 +#define status_port base + 3 /* read semantics */ +#define control_port base + 3 /* write semantics */ +#define block_port base + 4 /* 16 bit, writeonly */ +#define last_block_port base + 6 /* 16 bit, writeonly */ + + /* FX ports. These are mapped through the ICS2115 to the YS225. + The ICS2115 takes care of flipping the relevant pins on the + YS225 so that access to each of these ports does the right + thing. Note: these are NOT documented by Turtle Beach. + */ + +#define fx_status base + 8 +#define fx_op base + 8 +#define fx_lcr base + 9 +#define fx_dsp_addr base + 0xa +#define fx_dsp_page base + 0xb +#define fx_dsp_lsb base + 0xc +#define fx_dsp_msb base + 0xd +#define fx_mod_addr base + 0xe +#define fx_mod_data base + 0xf + + volatile int irq_ok; /* set by interrupt handler */ + volatile int irq_cnt; /* ditto */ + int opened; /* flag, holds open(2) mode */ + char debug; /* debugging flags */ + int freemem; /* installed RAM, in bytes */ + + int synth_dev; /* devno for "raw" synth */ + int mididev; /* devno for internal MIDI */ + int ext_mididev; /* devno for external MIDI */ + int fx_mididev; /* devno for FX MIDI interface */ +#if OSS_SUPPORT_LEVEL & OSS_SUPPORT_SEQ + int oss_dev; /* devno for OSS sequencer synth */ +#endif /* OSS_SUPPORT_SEQ */ + + char fw_version[2]; /* major = [0], minor = [1] */ + char hw_version[2]; /* major = [0], minor = [1] */ + char israw; /* needs Motorola microcode */ + char has_fx; /* has FX processor (Tropez+) */ + char prog_status[WF_MAX_PROGRAM]; /* WF_SLOT_* */ + char patch_status[WF_MAX_PATCH]; /* WF_SLOT_* */ + char sample_status[WF_MAX_SAMPLE]; /* WF_ST_* | WF_SLOT_* */ + int samples_used; /* how many */ + char interrupts_on; /* h/w MPU interrupts enabled ? */ + char rom_samples_rdonly; /* can we write on ROM samples */ + wait_queue_head_t interrupt_sleeper; +} dev; + +static DEFINE_SPINLOCK(lock); +static int detect_wffx(void); +static int wffx_ioctl (wavefront_fx_info *); +static int wffx_init (void); + +static int wavefront_delete_sample (int sampnum); +static int wavefront_find_free_sample (void); + +/* From wf_midi.c */ + +extern int virtual_midi_enable (void); +extern int virtual_midi_disable (void); +extern int detect_wf_mpu (int, int); +extern int install_wf_mpu (void); +extern int uninstall_wf_mpu (void); + +typedef struct { + int cmd; + char *action; + unsigned int read_cnt; + unsigned int write_cnt; + int need_ack; +} wavefront_command; + +static struct { + int errno; + const char *errstr; +} wavefront_errors[] = { + { 0x01, "Bad sample number" }, + { 0x02, "Out of sample memory" }, + { 0x03, "Bad patch number" }, + { 0x04, "Error in number of voices" }, + { 0x06, "Sample load already in progress" }, + { 0x0B, "No sample load request pending" }, + { 0x0E, "Bad MIDI channel number" }, + { 0x10, "Download Record Error" }, + { 0x80, "Success" }, + { 0 } +}; + +#define NEEDS_ACK 1 + +static wavefront_command wavefront_commands[] = { + { WFC_SET_SYNTHVOL, "set synthesizer volume", 0, 1, NEEDS_ACK }, + { WFC_GET_SYNTHVOL, "get synthesizer volume", 1, 0, 0}, + { WFC_SET_NVOICES, "set number of voices", 0, 1, NEEDS_ACK }, + { WFC_GET_NVOICES, "get number of voices", 1, 0, 0 }, + { WFC_SET_TUNING, "set synthesizer tuning", 0, 2, NEEDS_ACK }, + { WFC_GET_TUNING, "get synthesizer tuning", 2, 0, 0 }, + { WFC_DISABLE_CHANNEL, "disable synth channel", 0, 1, NEEDS_ACK }, + { WFC_ENABLE_CHANNEL, "enable synth channel", 0, 1, NEEDS_ACK }, + { WFC_GET_CHANNEL_STATUS, "get synth channel status", 3, 0, 0 }, + { WFC_MISYNTH_OFF, "disable midi-in to synth", 0, 0, NEEDS_ACK }, + { WFC_MISYNTH_ON, "enable midi-in to synth", 0, 0, NEEDS_ACK }, + { WFC_VMIDI_ON, "enable virtual midi mode", 0, 0, NEEDS_ACK }, + { WFC_VMIDI_OFF, "disable virtual midi mode", 0, 0, NEEDS_ACK }, + { WFC_MIDI_STATUS, "report midi status", 1, 0, 0 }, + { WFC_FIRMWARE_VERSION, "report firmware version", 2, 0, 0 }, + { WFC_HARDWARE_VERSION, "report hardware version", 2, 0, 0 }, + { WFC_GET_NSAMPLES, "report number of samples", 2, 0, 0 }, + { WFC_INSTOUT_LEVELS, "report instantaneous output levels", 7, 0, 0 }, + { WFC_PEAKOUT_LEVELS, "report peak output levels", 7, 0, 0 }, + { WFC_DOWNLOAD_SAMPLE, "download sample", + 0, WF_SAMPLE_BYTES, NEEDS_ACK }, + { WFC_DOWNLOAD_BLOCK, "download block", 0, 0, NEEDS_ACK}, + { WFC_DOWNLOAD_SAMPLE_HEADER, "download sample header", + 0, WF_SAMPLE_HDR_BYTES, NEEDS_ACK }, + { WFC_UPLOAD_SAMPLE_HEADER, "upload sample header", 13, 2, 0 }, + + /* This command requires a variable number of bytes to be written. + There is a hack in wavefront_cmd() to support this. The actual + count is passed in as the read buffer ptr, cast appropriately. + Ugh. + */ + + { WFC_DOWNLOAD_MULTISAMPLE, "download multisample", 0, 0, NEEDS_ACK }, + + /* This one is a hack as well. We just read the first byte of the + response, don't fetch an ACK, and leave the rest to the + calling function. Ugly, ugly, ugly. + */ + + { WFC_UPLOAD_MULTISAMPLE, "upload multisample", 2, 1, 0 }, + { WFC_DOWNLOAD_SAMPLE_ALIAS, "download sample alias", + 0, WF_ALIAS_BYTES, NEEDS_ACK }, + { WFC_UPLOAD_SAMPLE_ALIAS, "upload sample alias", WF_ALIAS_BYTES, 2, 0}, + { WFC_DELETE_SAMPLE, "delete sample", 0, 2, NEEDS_ACK }, + { WFC_IDENTIFY_SAMPLE_TYPE, "identify sample type", 5, 2, 0 }, + { WFC_UPLOAD_SAMPLE_PARAMS, "upload sample parameters" }, + { WFC_REPORT_FREE_MEMORY, "report free memory", 4, 0, 0 }, + { WFC_DOWNLOAD_PATCH, "download patch", 0, 134, NEEDS_ACK }, + { WFC_UPLOAD_PATCH, "upload patch", 132, 2, 0 }, + { WFC_DOWNLOAD_PROGRAM, "download program", 0, 33, NEEDS_ACK }, + { WFC_UPLOAD_PROGRAM, "upload program", 32, 1, 0 }, + { WFC_DOWNLOAD_EDRUM_PROGRAM, "download enhanced drum program", 0, 9, + NEEDS_ACK}, + { WFC_UPLOAD_EDRUM_PROGRAM, "upload enhanced drum program", 8, 1, 0}, + { WFC_SET_EDRUM_CHANNEL, "set enhanced drum program channel", + 0, 1, NEEDS_ACK }, + { WFC_DISABLE_DRUM_PROGRAM, "disable drum program", 0, 1, NEEDS_ACK }, + { WFC_REPORT_CHANNEL_PROGRAMS, "report channel program numbers", + 32, 0, 0 }, + { WFC_NOOP, "the no-op command", 0, 0, NEEDS_ACK }, + { 0x00 } +}; + +static const char * +wavefront_errorstr (int errnum) + +{ + int i; + + for (i = 0; wavefront_errors[i].errstr; i++) { + if (wavefront_errors[i].errno == errnum) { + return wavefront_errors[i].errstr; + } + } + + return "Unknown WaveFront error"; +} + +static wavefront_command * +wavefront_get_command (int cmd) + +{ + int i; + + for (i = 0; wavefront_commands[i].cmd != 0; i++) { + if (cmd == wavefront_commands[i].cmd) { + return &wavefront_commands[i]; + } + } + + return (wavefront_command *) 0; +} + +static inline int +wavefront_status (void) + +{ + return inb (dev.status_port); +} + +static int +wavefront_wait (int mask) + +{ + int i; + + for (i = 0; i < wait_polls; i++) + if (wavefront_status() & mask) + return 1; + + for (i = 0; i < sleep_tries; i++) { + + if (wavefront_status() & mask) { + set_current_state(TASK_RUNNING); + return 1; + } + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(sleep_length); + if (signal_pending(current)) + break; + } + + set_current_state(TASK_RUNNING); + return 0; +} + +static int +wavefront_read (void) + +{ + if (wavefront_wait (STAT_CAN_READ)) + return inb (dev.data_port); + + DPRINT (WF_DEBUG_DATA, "read timeout.\n"); + + return -1; +} + +static int +wavefront_write (unsigned char data) + +{ + if (wavefront_wait (STAT_CAN_WRITE)) { + outb (data, dev.data_port); + return 0; + } + + DPRINT (WF_DEBUG_DATA, "write timeout.\n"); + + return -1; +} + +static int +wavefront_cmd (int cmd, unsigned char *rbuf, unsigned char *wbuf) + +{ + int ack; + int i; + int c; + wavefront_command *wfcmd; + + if ((wfcmd = wavefront_get_command (cmd)) == (wavefront_command *) 0) { + printk (KERN_WARNING LOGNAME "command 0x%x not supported.\n", + cmd); + return 1; + } + + /* Hack to handle the one variable-size write command. See + wavefront_send_multisample() for the other half of this + gross and ugly strategy. + */ + + if (cmd == WFC_DOWNLOAD_MULTISAMPLE) { + wfcmd->write_cnt = (unsigned int) rbuf; + rbuf = NULL; + } + + DPRINT (WF_DEBUG_CMD, "0x%x [%s] (%d,%d,%d)\n", + cmd, wfcmd->action, wfcmd->read_cnt, + wfcmd->write_cnt, wfcmd->need_ack); + + if (wavefront_write (cmd)) { + DPRINT ((WF_DEBUG_IO|WF_DEBUG_CMD), "cannot request " + "0x%x [%s].\n", + cmd, wfcmd->action); + return 1; + } + + if (wfcmd->write_cnt > 0) { + DPRINT (WF_DEBUG_DATA, "writing %d bytes " + "for 0x%x\n", + wfcmd->write_cnt, cmd); + + for (i = 0; i < wfcmd->write_cnt; i++) { + if (wavefront_write (wbuf[i])) { + DPRINT (WF_DEBUG_IO, "bad write for byte " + "%d of 0x%x [%s].\n", + i, cmd, wfcmd->action); + return 1; + } + + DPRINT (WF_DEBUG_DATA, "write[%d] = 0x%x\n", + i, wbuf[i]); + } + } + + if (wfcmd->read_cnt > 0) { + DPRINT (WF_DEBUG_DATA, "reading %d ints " + "for 0x%x\n", + wfcmd->read_cnt, cmd); + + for (i = 0; i < wfcmd->read_cnt; i++) { + + if ((c = wavefront_read()) == -1) { + DPRINT (WF_DEBUG_IO, "bad read for byte " + "%d of 0x%x [%s].\n", + i, cmd, wfcmd->action); + return 1; + } + + /* Now handle errors. Lots of special cases here */ + + if (c == 0xff) { + if ((c = wavefront_read ()) == -1) { + DPRINT (WF_DEBUG_IO, "bad read for " + "error byte at " + "read byte %d " + "of 0x%x [%s].\n", + i, cmd, + wfcmd->action); + return 1; + } + + /* Can you believe this madness ? */ + + if (c == 1 && + wfcmd->cmd == WFC_IDENTIFY_SAMPLE_TYPE) { + rbuf[0] = WF_ST_EMPTY; + return (0); + + } else if (c == 3 && + wfcmd->cmd == WFC_UPLOAD_PATCH) { + + return 3; + + } else if (c == 1 && + wfcmd->cmd == WFC_UPLOAD_PROGRAM) { + + return 1; + + } else { + + DPRINT (WF_DEBUG_IO, "error %d (%s) " + "during " + "read for byte " + "%d of 0x%x " + "[%s].\n", + c, + wavefront_errorstr (c), + i, cmd, + wfcmd->action); + return 1; + + } + + } else { + rbuf[i] = c; + } + + DPRINT (WF_DEBUG_DATA, "read[%d] = 0x%x\n",i, rbuf[i]); + } + } + + if ((wfcmd->read_cnt == 0 && wfcmd->write_cnt == 0) || wfcmd->need_ack) { + + DPRINT (WF_DEBUG_CMD, "reading ACK for 0x%x\n", cmd); + + /* Some commands need an ACK, but return zero instead + of the standard value. + */ + + if ((ack = wavefront_read()) == 0) { + ack = WF_ACK; + } + + if (ack != WF_ACK) { + if (ack == -1) { + DPRINT (WF_DEBUG_IO, "cannot read ack for " + "0x%x [%s].\n", + cmd, wfcmd->action); + return 1; + + } else { + int err = -1; /* something unknown */ + + if (ack == 0xff) { /* explicit error */ + + if ((err = wavefront_read ()) == -1) { + DPRINT (WF_DEBUG_DATA, + "cannot read err " + "for 0x%x [%s].\n", + cmd, wfcmd->action); + } + } + + DPRINT (WF_DEBUG_IO, "0x%x [%s] " + "failed (0x%x, 0x%x, %s)\n", + cmd, wfcmd->action, ack, err, + wavefront_errorstr (err)); + + return -err; + } + } + + DPRINT (WF_DEBUG_DATA, "ack received " + "for 0x%x [%s]\n", + cmd, wfcmd->action); + } else { + + DPRINT (WF_DEBUG_CMD, "0x%x [%s] does not need " + "ACK (%d,%d,%d)\n", + cmd, wfcmd->action, wfcmd->read_cnt, + wfcmd->write_cnt, wfcmd->need_ack); + } + + return 0; + +} + +/*********************************************************************** +WaveFront: data munging + +Things here are weird. All data written to the board cannot +have its most significant bit set. Any data item with values +potentially > 0x7F (127) must be split across multiple bytes. + +Sometimes, we need to munge numeric values that are represented on +the x86 side as 8-32 bit values. Sometimes, we need to munge data +that is represented on the x86 side as an array of bytes. The most +efficient approach to handling both cases seems to be to use 2 +different functions for munging and 2 for de-munging. This avoids +weird casting and worrying about bit-level offsets. + +**********************************************************************/ + +static +unsigned char * +munge_int32 (unsigned int src, + unsigned char *dst, + unsigned int dst_size) +{ + int i; + + for (i = 0;i < dst_size; i++) { + *dst = src & 0x7F; /* Mask high bit of LSB */ + src = src >> 7; /* Rotate Right 7 bits */ + /* Note: we leave the upper bits in place */ + + dst++; + }; + return dst; +}; + +static int +demunge_int32 (unsigned char* src, int src_size) + +{ + int i; + int outval = 0; + + for (i = src_size - 1; i >= 0; i--) { + outval=(outval<<7)+src[i]; + } + + return outval; +}; + +static +unsigned char * +munge_buf (unsigned char *src, unsigned char *dst, unsigned int dst_size) + +{ + int i; + unsigned int last = dst_size / 2; + + for (i = 0; i < last; i++) { + *dst++ = src[i] & 0x7f; + *dst++ = src[i] >> 7; + } + return dst; +} + +static +unsigned char * +demunge_buf (unsigned char *src, unsigned char *dst, unsigned int src_bytes) + +{ + int i; + unsigned char *end = src + src_bytes; + + end = src + src_bytes; + + /* NOTE: src and dst *CAN* point to the same address */ + + for (i = 0; src != end; i++) { + dst[i] = *src++; + dst[i] |= (*src++)<<7; + } + + return dst; +} + +/*********************************************************************** +WaveFront: sample, patch and program management. +***********************************************************************/ + +static int +wavefront_delete_sample (int sample_num) + +{ + unsigned char wbuf[2]; + int x; + + wbuf[0] = sample_num & 0x7f; + wbuf[1] = sample_num >> 7; + + if ((x = wavefront_cmd (WFC_DELETE_SAMPLE, NULL, wbuf)) == 0) { + dev.sample_status[sample_num] = WF_ST_EMPTY; + } + + return x; +} + +static int +wavefront_get_sample_status (int assume_rom) + +{ + int i; + unsigned char rbuf[32], wbuf[32]; + unsigned int sc_real, sc_alias, sc_multi; + + /* check sample status */ + + if (wavefront_cmd (WFC_GET_NSAMPLES, rbuf, wbuf)) { + printk (KERN_WARNING LOGNAME "cannot request sample count.\n"); + return -1; + } + + sc_real = sc_alias = sc_multi = dev.samples_used = 0; + + for (i = 0; i < WF_MAX_SAMPLE; i++) { + + wbuf[0] = i & 0x7f; + wbuf[1] = i >> 7; + + if (wavefront_cmd (WFC_IDENTIFY_SAMPLE_TYPE, rbuf, wbuf)) { + printk (KERN_WARNING LOGNAME + "cannot identify sample " + "type of slot %d\n", i); + dev.sample_status[i] = WF_ST_EMPTY; + continue; + } + + dev.sample_status[i] = (WF_SLOT_FILLED|rbuf[0]); + + if (assume_rom) { + dev.sample_status[i] |= WF_SLOT_ROM; + } + + switch (rbuf[0] & WF_ST_MASK) { + case WF_ST_SAMPLE: + sc_real++; + break; + case WF_ST_MULTISAMPLE: + sc_multi++; + break; + case WF_ST_ALIAS: + sc_alias++; + break; + case WF_ST_EMPTY: + break; + + default: + printk (KERN_WARNING LOGNAME "unknown sample type for " + "slot %d (0x%x)\n", + i, rbuf[0]); + } + + if (rbuf[0] != WF_ST_EMPTY) { + dev.samples_used++; + } + } + + printk (KERN_INFO LOGNAME + "%d samples used (%d real, %d aliases, %d multi), " + "%d empty\n", dev.samples_used, sc_real, sc_alias, sc_multi, + WF_MAX_SAMPLE - dev.samples_used); + + + return (0); + +} + +static int +wavefront_get_patch_status (void) + +{ + unsigned char patchbuf[WF_PATCH_BYTES]; + unsigned char patchnum[2]; + wavefront_patch *p; + int i, x, cnt, cnt2; + + for (i = 0; i < WF_MAX_PATCH; i++) { + patchnum[0] = i & 0x7f; + patchnum[1] = i >> 7; + + if ((x = wavefront_cmd (WFC_UPLOAD_PATCH, patchbuf, + patchnum)) == 0) { + + dev.patch_status[i] |= WF_SLOT_FILLED; + p = (wavefront_patch *) patchbuf; + dev.sample_status + [p->sample_number|(p->sample_msb<<7)] |= + WF_SLOT_USED; + + } else if (x == 3) { /* Bad patch number */ + dev.patch_status[i] = 0; + } else { + printk (KERN_ERR LOGNAME "upload patch " + "error 0x%x\n", x); + dev.patch_status[i] = 0; + return 1; + } + } + + /* program status has already filled in slot_used bits */ + + for (i = 0, cnt = 0, cnt2 = 0; i < WF_MAX_PATCH; i++) { + if (dev.patch_status[i] & WF_SLOT_FILLED) { + cnt++; + } + if (dev.patch_status[i] & WF_SLOT_USED) { + cnt2++; + } + + } + printk (KERN_INFO LOGNAME + "%d patch slots filled, %d in use\n", cnt, cnt2); + + return (0); +} + +static int +wavefront_get_program_status (void) + +{ + unsigned char progbuf[WF_PROGRAM_BYTES]; + wavefront_program prog; + unsigned char prognum; + int i, x, l, cnt; + + for (i = 0; i < WF_MAX_PROGRAM; i++) { + prognum = i; + + if ((x = wavefront_cmd (WFC_UPLOAD_PROGRAM, progbuf, + &prognum)) == 0) { + + dev.prog_status[i] |= WF_SLOT_USED; + + demunge_buf (progbuf, (unsigned char *) &prog, + WF_PROGRAM_BYTES); + + for (l = 0; l < WF_NUM_LAYERS; l++) { + if (prog.layer[l].mute) { + dev.patch_status + [prog.layer[l].patch_number] |= + WF_SLOT_USED; + } + } + } else if (x == 1) { /* Bad program number */ + dev.prog_status[i] = 0; + } else { + printk (KERN_ERR LOGNAME "upload program " + "error 0x%x\n", x); + dev.prog_status[i] = 0; + } + } + + for (i = 0, cnt = 0; i < WF_MAX_PROGRAM; i++) { + if (dev.prog_status[i]) { + cnt++; + } + } + + printk (KERN_INFO LOGNAME "%d programs slots in use\n", cnt); + + return (0); +} + +static int +wavefront_send_patch (wavefront_patch_info *header) + +{ + unsigned char buf[WF_PATCH_BYTES+2]; + unsigned char *bptr; + + DPRINT (WF_DEBUG_LOAD_PATCH, "downloading patch %d\n", + header->number); + + dev.patch_status[header->number] |= WF_SLOT_FILLED; + + bptr = buf; + bptr = munge_int32 (header->number, buf, 2); + munge_buf ((unsigned char *)&header->hdr.p, bptr, WF_PATCH_BYTES); + + if (wavefront_cmd (WFC_DOWNLOAD_PATCH, NULL, buf)) { + printk (KERN_ERR LOGNAME "download patch failed\n"); + return -(EIO); + } + + return (0); +} + +static int +wavefront_send_program (wavefront_patch_info *header) + +{ + unsigned char buf[WF_PROGRAM_BYTES+1]; + int i; + + DPRINT (WF_DEBUG_LOAD_PATCH, "downloading program %d\n", + header->number); + + dev.prog_status[header->number] = WF_SLOT_USED; + + /* XXX need to zero existing SLOT_USED bit for program_status[i] + where `i' is the program that's being (potentially) overwritten. + */ + + for (i = 0; i < WF_NUM_LAYERS; i++) { + if (header->hdr.pr.layer[i].mute) { + dev.patch_status[header->hdr.pr.layer[i].patch_number] |= + WF_SLOT_USED; + + /* XXX need to mark SLOT_USED for sample used by + patch_number, but this means we have to load it. Ick. + */ + } + } + + buf[0] = header->number; + munge_buf ((unsigned char *)&header->hdr.pr, &buf[1], WF_PROGRAM_BYTES); + + if (wavefront_cmd (WFC_DOWNLOAD_PROGRAM, NULL, buf)) { + printk (KERN_WARNING LOGNAME "download patch failed\n"); + return -(EIO); + } + + return (0); +} + +static int +wavefront_freemem (void) + +{ + char rbuf[8]; + + if (wavefront_cmd (WFC_REPORT_FREE_MEMORY, rbuf, NULL)) { + printk (KERN_WARNING LOGNAME "can't get memory stats.\n"); + return -1; + } else { + return demunge_int32 (rbuf, 4); + } +} + +static int +wavefront_send_sample (wavefront_patch_info *header, + UINT16 __user *dataptr, + int data_is_unsigned) + +{ + /* samples are downloaded via a 16-bit wide i/o port + (you could think of it as 2 adjacent 8-bit wide ports + but its less efficient that way). therefore, all + the blocksizes and so forth listed in the documentation, + and used conventionally to refer to sample sizes, + which are given in 8-bit units (bytes), need to be + divided by 2. + */ + + UINT16 sample_short; + UINT32 length; + UINT16 __user *data_end = NULL; + unsigned int i; + const int max_blksize = 4096/2; + unsigned int written; + unsigned int blocksize; + int dma_ack; + int blocknum; + unsigned char sample_hdr[WF_SAMPLE_HDR_BYTES]; + unsigned char *shptr; + int skip = 0; + int initial_skip = 0; + + DPRINT (WF_DEBUG_LOAD_PATCH, "sample %sdownload for slot %d, " + "type %d, %d bytes from %p\n", + header->size ? "" : "header ", + header->number, header->subkey, + header->size, + header->dataptr); + + if (header->number == WAVEFRONT_FIND_FREE_SAMPLE_SLOT) { + int x; + + if ((x = wavefront_find_free_sample ()) < 0) { + return -ENOMEM; + } + printk (KERN_DEBUG LOGNAME "unspecified sample => %d\n", x); + header->number = x; + } + + if (header->size) { + + /* XXX it's a debatable point whether or not RDONLY semantics + on the ROM samples should cover just the sample data or + the sample header. For now, it only covers the sample data, + so anyone is free at all times to rewrite sample headers. + + My reason for this is that we have the sample headers + available in the WFB file for General MIDI, and so these + can always be reset if needed. The sample data, however, + cannot be recovered without a complete reset and firmware + reload of the ICS2115, which is a very expensive operation. + + So, doing things this way allows us to honor the notion of + "RESETSAMPLES" reasonably cheaply. Note however, that this + is done purely at user level: there is no WFB parser in + this driver, and so a complete reset (back to General MIDI, + or theoretically some other configuration) is the + responsibility of the user level library. + + To try to do this in the kernel would be a little + crazy: we'd need 158K of kernel space just to hold + a copy of the patch/program/sample header data. + */ + + if (dev.rom_samples_rdonly) { + if (dev.sample_status[header->number] & WF_SLOT_ROM) { + printk (KERN_ERR LOGNAME "sample slot %d " + "write protected\n", + header->number); + return -EACCES; + } + } + + wavefront_delete_sample (header->number); + } + + if (header->size) { + dev.freemem = wavefront_freemem (); + + if (dev.freemem < header->size) { + printk (KERN_ERR LOGNAME + "insufficient memory to " + "load %d byte sample.\n", + header->size); + return -ENOMEM; + } + + } + + skip = WF_GET_CHANNEL(&header->hdr.s); + + if (skip > 0 && header->hdr.s.SampleResolution != LINEAR_16BIT) { + printk (KERN_ERR LOGNAME "channel selection only " + "possible on 16-bit samples"); + return -(EINVAL); + } + + switch (skip) { + case 0: + initial_skip = 0; + skip = 1; + break; + case 1: + initial_skip = 0; + skip = 2; + break; + case 2: + initial_skip = 1; + skip = 2; + break; + case 3: + initial_skip = 2; + skip = 3; + break; + case 4: + initial_skip = 3; + skip = 4; + break; + case 5: + initial_skip = 4; + skip = 5; + break; + case 6: + initial_skip = 5; + skip = 6; + break; + } + + DPRINT (WF_DEBUG_LOAD_PATCH, "channel selection: %d => " + "initial skip = %d, skip = %d\n", + WF_GET_CHANNEL (&header->hdr.s), + initial_skip, skip); + + /* Be safe, and zero the "Unused" bits ... */ + + WF_SET_CHANNEL(&header->hdr.s, 0); + + /* adjust size for 16 bit samples by dividing by two. We always + send 16 bits per write, even for 8 bit samples, so the length + is always half the size of the sample data in bytes. + */ + + length = header->size / 2; + + /* the data we're sent has not been munged, and in fact, the + header we have to send isn't just a munged copy either. + so, build the sample header right here. + */ + + shptr = &sample_hdr[0]; + + shptr = munge_int32 (header->number, shptr, 2); + + if (header->size) { + shptr = munge_int32 (length, shptr, 4); + } + + /* Yes, a 4 byte result doesn't contain all of the offset bits, + but the offset only uses 24 bits. + */ + + shptr = munge_int32 (*((UINT32 *) &header->hdr.s.sampleStartOffset), + shptr, 4); + shptr = munge_int32 (*((UINT32 *) &header->hdr.s.loopStartOffset), + shptr, 4); + shptr = munge_int32 (*((UINT32 *) &header->hdr.s.loopEndOffset), + shptr, 4); + shptr = munge_int32 (*((UINT32 *) &header->hdr.s.sampleEndOffset), + shptr, 4); + + /* This one is truly weird. What kind of weirdo decided that in + a system dominated by 16 and 32 bit integers, they would use + a just 12 bits ? + */ + + shptr = munge_int32 (header->hdr.s.FrequencyBias, shptr, 3); + + /* Why is this nybblified, when the MSB is *always* zero ? + Anyway, we can't take address of bitfield, so make a + good-faith guess at where it starts. + */ + + shptr = munge_int32 (*(&header->hdr.s.FrequencyBias+1), + shptr, 2); + + if (wavefront_cmd (header->size ? + WFC_DOWNLOAD_SAMPLE : WFC_DOWNLOAD_SAMPLE_HEADER, + NULL, sample_hdr)) { + printk (KERN_WARNING LOGNAME "sample %sdownload refused.\n", + header->size ? "" : "header "); + return -(EIO); + } + + if (header->size == 0) { + goto sent; /* Sorry. Just had to have one somewhere */ + } + + data_end = dataptr + length; + + /* Do any initial skip over an unused channel's data */ + + dataptr += initial_skip; + + for (written = 0, blocknum = 0; + written < length; written += max_blksize, blocknum++) { + + if ((length - written) > max_blksize) { + blocksize = max_blksize; + } else { + /* round to nearest 16-byte value */ + blocksize = ((length-written+7)&~0x7); + } + + if (wavefront_cmd (WFC_DOWNLOAD_BLOCK, NULL, NULL)) { + printk (KERN_WARNING LOGNAME "download block " + "request refused.\n"); + return -(EIO); + } + + for (i = 0; i < blocksize; i++) { + + if (dataptr < data_end) { + + __get_user (sample_short, dataptr); + dataptr += skip; + + if (data_is_unsigned) { /* GUS ? */ + + if (WF_SAMPLE_IS_8BIT(&header->hdr.s)) { + + /* 8 bit sample + resolution, sign + extend both bytes. + */ + + ((unsigned char*) + &sample_short)[0] += 0x7f; + ((unsigned char*) + &sample_short)[1] += 0x7f; + + } else { + + /* 16 bit sample + resolution, sign + extend the MSB. + */ + + sample_short += 0x7fff; + } + } + + } else { + + /* In padding section of final block: + + Don't fetch unsupplied data from + user space, just continue with + whatever the final value was. + */ + } + + if (i < blocksize - 1) { + outw (sample_short, dev.block_port); + } else { + outw (sample_short, dev.last_block_port); + } + } + + /* Get "DMA page acknowledge", even though its really + nothing to do with DMA at all. + */ + + if ((dma_ack = wavefront_read ()) != WF_DMA_ACK) { + if (dma_ack == -1) { + printk (KERN_ERR LOGNAME "upload sample " + "DMA ack timeout\n"); + return -(EIO); + } else { + printk (KERN_ERR LOGNAME "upload sample " + "DMA ack error 0x%x\n", + dma_ack); + return -(EIO); + } + } + } + + dev.sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_SAMPLE); + + /* Note, label is here because sending the sample header shouldn't + alter the sample_status info at all. + */ + + sent: + return (0); +} + +static int +wavefront_send_alias (wavefront_patch_info *header) + +{ + unsigned char alias_hdr[WF_ALIAS_BYTES]; + + DPRINT (WF_DEBUG_LOAD_PATCH, "download alias, %d is " + "alias for %d\n", + header->number, + header->hdr.a.OriginalSample); + + munge_int32 (header->number, &alias_hdr[0], 2); + munge_int32 (header->hdr.a.OriginalSample, &alias_hdr[2], 2); + munge_int32 (*((unsigned int *)&header->hdr.a.sampleStartOffset), + &alias_hdr[4], 4); + munge_int32 (*((unsigned int *)&header->hdr.a.loopStartOffset), + &alias_hdr[8], 4); + munge_int32 (*((unsigned int *)&header->hdr.a.loopEndOffset), + &alias_hdr[12], 4); + munge_int32 (*((unsigned int *)&header->hdr.a.sampleEndOffset), + &alias_hdr[16], 4); + munge_int32 (header->hdr.a.FrequencyBias, &alias_hdr[20], 3); + munge_int32 (*(&header->hdr.a.FrequencyBias+1), &alias_hdr[23], 2); + + if (wavefront_cmd (WFC_DOWNLOAD_SAMPLE_ALIAS, NULL, alias_hdr)) { + printk (KERN_ERR LOGNAME "download alias failed.\n"); + return -(EIO); + } + + dev.sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_ALIAS); + + return (0); +} + +static int +wavefront_send_multisample (wavefront_patch_info *header) +{ + int i; + int num_samples; + unsigned char msample_hdr[WF_MSAMPLE_BYTES]; + + munge_int32 (header->number, &msample_hdr[0], 2); + + /* You'll recall at this point that the "number of samples" value + in a wavefront_multisample struct is actually the log2 of the + real number of samples. + */ + + num_samples = (1<<(header->hdr.ms.NumberOfSamples&7)); + msample_hdr[2] = (unsigned char) header->hdr.ms.NumberOfSamples; + + DPRINT (WF_DEBUG_LOAD_PATCH, "multi %d with %d=%d samples\n", + header->number, + header->hdr.ms.NumberOfSamples, + num_samples); + + for (i = 0; i < num_samples; i++) { + DPRINT(WF_DEBUG_LOAD_PATCH|WF_DEBUG_DATA, "sample[%d] = %d\n", + i, header->hdr.ms.SampleNumber[i]); + munge_int32 (header->hdr.ms.SampleNumber[i], + &msample_hdr[3+(i*2)], 2); + } + + /* Need a hack here to pass in the number of bytes + to be written to the synth. This is ugly, and perhaps + one day, I'll fix it. + */ + + if (wavefront_cmd (WFC_DOWNLOAD_MULTISAMPLE, + (unsigned char *) ((num_samples*2)+3), + msample_hdr)) { + printk (KERN_ERR LOGNAME "download of multisample failed.\n"); + return -(EIO); + } + + dev.sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_MULTISAMPLE); + + return (0); +} + +static int +wavefront_fetch_multisample (wavefront_patch_info *header) +{ + int i; + unsigned char log_ns[1]; + unsigned char number[2]; + int num_samples; + + munge_int32 (header->number, number, 2); + + if (wavefront_cmd (WFC_UPLOAD_MULTISAMPLE, log_ns, number)) { + printk (KERN_ERR LOGNAME "upload multisample failed.\n"); + return -(EIO); + } + + DPRINT (WF_DEBUG_DATA, "msample %d has %d samples\n", + header->number, log_ns[0]); + + header->hdr.ms.NumberOfSamples = log_ns[0]; + + /* get the number of samples ... */ + + num_samples = (1 << log_ns[0]); + + for (i = 0; i < num_samples; i++) { + s8 d[2]; + + if ((d[0] = wavefront_read ()) == -1) { + printk (KERN_ERR LOGNAME "upload multisample failed " + "during sample loop.\n"); + return -(EIO); + } + + if ((d[1] = wavefront_read ()) == -1) { + printk (KERN_ERR LOGNAME "upload multisample failed " + "during sample loop.\n"); + return -(EIO); + } + + header->hdr.ms.SampleNumber[i] = + demunge_int32 ((unsigned char *) d, 2); + + DPRINT (WF_DEBUG_DATA, "msample sample[%d] = %d\n", + i, header->hdr.ms.SampleNumber[i]); + } + + return (0); +} + + +static int +wavefront_send_drum (wavefront_patch_info *header) + +{ + unsigned char drumbuf[WF_DRUM_BYTES]; + wavefront_drum *drum = &header->hdr.d; + int i; + + DPRINT (WF_DEBUG_LOAD_PATCH, "downloading edrum for MIDI " + "note %d, patch = %d\n", + header->number, drum->PatchNumber); + + drumbuf[0] = header->number & 0x7f; + + for (i = 0; i < 4; i++) { + munge_int32 (((unsigned char *)drum)[i], &drumbuf[1+(i*2)], 2); + } + + if (wavefront_cmd (WFC_DOWNLOAD_EDRUM_PROGRAM, NULL, drumbuf)) { + printk (KERN_ERR LOGNAME "download drum failed.\n"); + return -(EIO); + } + + return (0); +} + +static int +wavefront_find_free_sample (void) + +{ + int i; + + for (i = 0; i < WF_MAX_SAMPLE; i++) { + if (!(dev.sample_status[i] & WF_SLOT_FILLED)) { + return i; + } + } + printk (KERN_WARNING LOGNAME "no free sample slots!\n"); + return -1; +} + +static int +wavefront_find_free_patch (void) + +{ + int i; + + for (i = 0; i < WF_MAX_PATCH; i++) { + if (!(dev.patch_status[i] & WF_SLOT_FILLED)) { + return i; + } + } + printk (KERN_WARNING LOGNAME "no free patch slots!\n"); + return -1; +} + +static int +log2_2048(int n) + +{ + int tbl[]={0, 0, 2048, 3246, 4096, 4755, 5294, 5749, 6143, + 6492, 6803, 7084, 7342, 7578, 7797, 8001, 8192, + 8371, 8540, 8699, 8851, 8995, 9132, 9264, 9390, + 9510, 9626, 9738, 9845, 9949, 10049, 10146}; + int i; + + /* Returns 2048*log2(n) */ + + /* FIXME: this is like doing integer math + on quantum particles (RuN) */ + + i=0; + while(n>=32*256) { + n>>=8; + i+=2048*8; + } + while(n>=32) { + n>>=1; + i+=2048; + } + i+=tbl[n]; + return(i); +} + +static int +wavefront_load_gus_patch (int devno, int format, const char __user *addr, + int offs, int count, int pmgr_flag) +{ + struct patch_info guspatch; + wavefront_patch_info *samp, *pat, *prog; + wavefront_patch *patp; + wavefront_sample *sampp; + wavefront_program *progp; + + int i,base_note; + long sizeof_patch; + int rc = -ENOMEM; + + samp = kmalloc(3 * sizeof(wavefront_patch_info), GFP_KERNEL); + if (!samp) + goto free_fail; + pat = samp + 1; + prog = pat + 1; + + /* Copy in the header of the GUS patch */ + + sizeof_patch = (long) &guspatch.data[0] - (long) &guspatch; + if (copy_from_user(&((char *) &guspatch)[offs], + &(addr)[offs], sizeof_patch - offs)) { + rc = -EFAULT; + goto free_fail; + } + + if ((i = wavefront_find_free_patch ()) == -1) { + rc = -EBUSY; + goto free_fail; + } + pat->number = i; + pat->subkey = WF_ST_PATCH; + patp = &pat->hdr.p; + + if ((i = wavefront_find_free_sample ()) == -1) { + rc = -EBUSY; + goto free_fail; + } + samp->number = i; + samp->subkey = WF_ST_SAMPLE; + samp->size = guspatch.len; + sampp = &samp->hdr.s; + + prog->number = guspatch.instr_no; + progp = &prog->hdr.pr; + + /* Setup the patch structure */ + + patp->amplitude_bias=guspatch.volume; + patp->portamento=0; + patp->sample_number= samp->number & 0xff; + patp->sample_msb= samp->number >> 8; + patp->pitch_bend= /*12*/ 0; + patp->mono=1; + patp->retrigger=1; + patp->nohold=(guspatch.mode & WAVE_SUSTAIN_ON) ? 0:1; + patp->frequency_bias=0; + patp->restart=0; + patp->reuse=0; + patp->reset_lfo=1; + patp->fm_src2=0; + patp->fm_src1=WF_MOD_MOD_WHEEL; + patp->am_src=WF_MOD_PRESSURE; + patp->am_amount=127; + patp->fc1_mod_amount=0; + patp->fc2_mod_amount=0; + patp->fm_amount1=0; + patp->fm_amount2=0; + patp->envelope1.attack_level=127; + patp->envelope1.decay1_level=127; + patp->envelope1.decay2_level=127; + patp->envelope1.sustain_level=127; + patp->envelope1.release_level=0; + patp->envelope2.attack_velocity=127; + patp->envelope2.attack_level=127; + patp->envelope2.decay1_level=127; + patp->envelope2.decay2_level=127; + patp->envelope2.sustain_level=127; + patp->envelope2.release_level=0; + patp->envelope2.attack_velocity=127; + patp->randomizer=0; + + /* Program for this patch */ + + progp->layer[0].patch_number= pat->number; /* XXX is this right ? */ + progp->layer[0].mute=1; + progp->layer[0].pan_or_mod=1; + progp->layer[0].pan=7; + progp->layer[0].mix_level=127 /* guspatch.volume */; + progp->layer[0].split_type=0; + progp->layer[0].split_point=0; + progp->layer[0].play_below=0; + + for (i = 1; i < 4; i++) { + progp->layer[i].mute=0; + } + + /* Sample data */ + + sampp->SampleResolution=((~guspatch.mode & WAVE_16_BITS)<<1); + + for (base_note=0; + note_to_freq (base_note) < guspatch.base_note; + base_note++); + + if ((guspatch.base_note-note_to_freq(base_note)) + >(note_to_freq(base_note)-guspatch.base_note)) + base_note++; + + printk(KERN_DEBUG "ref freq=%d,base note=%d\n", + guspatch.base_freq, + base_note); + + sampp->FrequencyBias = (29550 - log2_2048(guspatch.base_freq) + + base_note*171); + printk(KERN_DEBUG "Freq Bias is %d\n", sampp->FrequencyBias); + sampp->Loop=(guspatch.mode & WAVE_LOOPING) ? 1:0; + sampp->sampleStartOffset.Fraction=0; + sampp->sampleStartOffset.Integer=0; + sampp->loopStartOffset.Fraction=0; + sampp->loopStartOffset.Integer=guspatch.loop_start + >>((guspatch.mode&WAVE_16_BITS) ? 1:0); + sampp->loopEndOffset.Fraction=0; + sampp->loopEndOffset.Integer=guspatch.loop_end + >>((guspatch.mode&WAVE_16_BITS) ? 1:0); + sampp->sampleEndOffset.Fraction=0; + sampp->sampleEndOffset.Integer=guspatch.len >> (guspatch.mode&1); + sampp->Bidirectional=(guspatch.mode&WAVE_BIDIR_LOOP) ? 1:0; + sampp->Reverse=(guspatch.mode&WAVE_LOOP_BACK) ? 1:0; + + /* Now ship it down */ + + wavefront_send_sample (samp, + (unsigned short __user *) &(addr)[sizeof_patch], + (guspatch.mode & WAVE_UNSIGNED) ? 1:0); + wavefront_send_patch (pat); + wavefront_send_program (prog); + + /* Now pan as best we can ... use the slave/internal MIDI device + number if it exists (since it talks to the WaveFront), or the + master otherwise. + */ + + if (dev.mididev > 0) { + midi_synth_controller (dev.mididev, guspatch.instr_no, 10, + ((guspatch.panning << 4) > 127) ? + 127 : (guspatch.panning << 4)); + } + rc = 0; + +free_fail: + kfree(samp); + return rc; +} + +static int +wavefront_load_patch (const char __user *addr) + + +{ + wavefront_patch_info header; + + if (copy_from_user (&header, addr, sizeof(wavefront_patch_info) - + sizeof(wavefront_any))) { + printk (KERN_WARNING LOGNAME "bad address for load patch.\n"); + return -EFAULT; + } + + DPRINT (WF_DEBUG_LOAD_PATCH, "download " + "Sample type: %d " + "Sample number: %d " + "Sample size: %d\n", + header.subkey, + header.number, + header.size); + + switch (header.subkey) { + case WF_ST_SAMPLE: /* sample or sample_header, based on patch->size */ + + if (copy_from_user((unsigned char *) &header.hdr.s, + (unsigned char __user *) header.hdrptr, + sizeof (wavefront_sample))) + return -EFAULT; + + return wavefront_send_sample (&header, header.dataptr, 0); + + case WF_ST_MULTISAMPLE: + + if (copy_from_user(&header.hdr.s, header.hdrptr, + sizeof(wavefront_multisample))) + return -EFAULT; + + return wavefront_send_multisample (&header); + + + case WF_ST_ALIAS: + + if (copy_from_user(&header.hdr.a, header.hdrptr, + sizeof (wavefront_alias))) + return -EFAULT; + + return wavefront_send_alias (&header); + + case WF_ST_DRUM: + if (copy_from_user(&header.hdr.d, header.hdrptr, + sizeof (wavefront_drum))) + return -EFAULT; + + return wavefront_send_drum (&header); + + case WF_ST_PATCH: + if (copy_from_user(&header.hdr.p, header.hdrptr, + sizeof (wavefront_patch))) + return -EFAULT; + + return wavefront_send_patch (&header); + + case WF_ST_PROGRAM: + if (copy_from_user(&header.hdr.pr, header.hdrptr, + sizeof (wavefront_program))) + return -EFAULT; + + return wavefront_send_program (&header); + + default: + printk (KERN_ERR LOGNAME "unknown patch type %d.\n", + header.subkey); + return -(EINVAL); + } + + return 0; +} + +/*********************************************************************** +WaveFront: /dev/sequencer{,2} and other hardware-dependent interfaces +***********************************************************************/ + +static void +process_sample_hdr (UCHAR8 *buf) + +{ + wavefront_sample s; + UCHAR8 *ptr; + + ptr = buf; + + /* The board doesn't send us an exact copy of a "wavefront_sample" + in response to an Upload Sample Header command. Instead, we + have to convert the data format back into our data structure, + just as in the Download Sample command, where we have to do + something very similar in the reverse direction. + */ + + *((UINT32 *) &s.sampleStartOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((UINT32 *) &s.loopStartOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((UINT32 *) &s.loopEndOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((UINT32 *) &s.sampleEndOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((UINT32 *) &s.FrequencyBias) = demunge_int32 (ptr, 3); ptr += 3; + + s.SampleResolution = *ptr & 0x3; + s.Loop = *ptr & 0x8; + s.Bidirectional = *ptr & 0x10; + s.Reverse = *ptr & 0x40; + + /* Now copy it back to where it came from */ + + memcpy (buf, (unsigned char *) &s, sizeof (wavefront_sample)); +} + +static int +wavefront_synth_control (int cmd, wavefront_control *wc) + +{ + unsigned char patchnumbuf[2]; + int i; + + DPRINT (WF_DEBUG_CMD, "synth control with " + "cmd 0x%x\n", wc->cmd); + + /* Pre-handling of or for various commands */ + + switch (wc->cmd) { + case WFC_DISABLE_INTERRUPTS: + printk (KERN_INFO LOGNAME "interrupts disabled.\n"); + outb (0x80|0x20, dev.control_port); + dev.interrupts_on = 0; + return 0; + + case WFC_ENABLE_INTERRUPTS: + printk (KERN_INFO LOGNAME "interrupts enabled.\n"); + outb (0x80|0x40|0x20, dev.control_port); + dev.interrupts_on = 1; + return 0; + + case WFC_INTERRUPT_STATUS: + wc->rbuf[0] = dev.interrupts_on; + return 0; + + case WFC_ROMSAMPLES_RDONLY: + dev.rom_samples_rdonly = wc->wbuf[0]; + wc->status = 0; + return 0; + + case WFC_IDENTIFY_SLOT_TYPE: + i = wc->wbuf[0] | (wc->wbuf[1] << 7); + if (i <0 || i >= WF_MAX_SAMPLE) { + printk (KERN_WARNING LOGNAME "invalid slot ID %d\n", + i); + wc->status = EINVAL; + return 0; + } + wc->rbuf[0] = dev.sample_status[i]; + wc->status = 0; + return 0; + + case WFC_DEBUG_DRIVER: + dev.debug = wc->wbuf[0]; + printk (KERN_INFO LOGNAME "debug = 0x%x\n", dev.debug); + return 0; + + case WFC_FX_IOCTL: + wffx_ioctl ((wavefront_fx_info *) &wc->wbuf[0]); + return 0; + + case WFC_UPLOAD_PATCH: + munge_int32 (*((UINT32 *) wc->wbuf), patchnumbuf, 2); + memcpy (wc->wbuf, patchnumbuf, 2); + break; + + case WFC_UPLOAD_MULTISAMPLE: + /* multisamples have to be handled differently, and + cannot be dealt with properly by wavefront_cmd() alone. + */ + wc->status = wavefront_fetch_multisample + ((wavefront_patch_info *) wc->rbuf); + return 0; + + case WFC_UPLOAD_SAMPLE_ALIAS: + printk (KERN_INFO LOGNAME "support for sample alias upload " + "being considered.\n"); + wc->status = EINVAL; + return -EINVAL; + } + + wc->status = wavefront_cmd (wc->cmd, wc->rbuf, wc->wbuf); + + /* Post-handling of certain commands. + + In particular, if the command was an upload, demunge the data + so that the user-level doesn't have to think about it. + */ + + if (wc->status == 0) { + switch (wc->cmd) { + /* intercept any freemem requests so that we know + we are always current with the user-level view + of things. + */ + + case WFC_REPORT_FREE_MEMORY: + dev.freemem = demunge_int32 (wc->rbuf, 4); + break; + + case WFC_UPLOAD_PATCH: + demunge_buf (wc->rbuf, wc->rbuf, WF_PATCH_BYTES); + break; + + case WFC_UPLOAD_PROGRAM: + demunge_buf (wc->rbuf, wc->rbuf, WF_PROGRAM_BYTES); + break; + + case WFC_UPLOAD_EDRUM_PROGRAM: + demunge_buf (wc->rbuf, wc->rbuf, WF_DRUM_BYTES - 1); + break; + + case WFC_UPLOAD_SAMPLE_HEADER: + process_sample_hdr (wc->rbuf); + break; + + case WFC_UPLOAD_SAMPLE_ALIAS: + printk (KERN_INFO LOGNAME "support for " + "sample aliases still " + "being considered.\n"); + break; + + case WFC_VMIDI_OFF: + if (virtual_midi_disable () < 0) { + return -(EIO); + } + break; + + case WFC_VMIDI_ON: + if (virtual_midi_enable () < 0) { + return -(EIO); + } + break; + } + } + + return 0; +} + + +/***********************************************************************/ +/* WaveFront: Linux file system interface (for access via raw synth) */ +/***********************************************************************/ + +static int +wavefront_open (struct inode *inode, struct file *file) +{ + /* XXX fix me */ + dev.opened = file->f_flags; + return 0; +} + +static int +wavefront_release(struct inode *inode, struct file *file) +{ + lock_kernel(); + dev.opened = 0; + dev.debug = 0; + unlock_kernel(); + return 0; +} + +static int +wavefront_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + wavefront_control wc; + int err; + + switch (cmd) { + + case WFCTL_WFCMD: + if (copy_from_user(&wc, (void __user *) arg, sizeof (wc))) + return -EFAULT; + + if ((err = wavefront_synth_control (cmd, &wc)) == 0) { + if (copy_to_user ((void __user *) arg, &wc, sizeof (wc))) + return -EFAULT; + } + + return err; + + case WFCTL_LOAD_SPP: + return wavefront_load_patch ((const char __user *) arg); + + default: + printk (KERN_WARNING LOGNAME "invalid ioctl %#x\n", cmd); + return -(EINVAL); + + } + return 0; +} + +static /*const*/ struct file_operations wavefront_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = wavefront_ioctl, + .open = wavefront_open, + .release = wavefront_release, +}; + + +/***********************************************************************/ +/* WaveFront: OSS installation and support interface */ +/***********************************************************************/ + +#if OSS_SUPPORT_LEVEL & OSS_SUPPORT_SEQ + +static struct synth_info wavefront_info = +{"Turtle Beach WaveFront", 0, SYNTH_TYPE_SAMPLE, SAMPLE_TYPE_WAVEFRONT, + 0, 32, 0, 0, SYNTH_CAP_INPUT}; + +static int +wavefront_oss_open (int devno, int mode) + +{ + dev.opened = mode; + return 0; +} + +static void +wavefront_oss_close (int devno) + +{ + dev.opened = 0; + dev.debug = 0; + return; +} + +static int +wavefront_oss_ioctl (int devno, unsigned int cmd, void __user * arg) + +{ + wavefront_control wc; + int err; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + if(copy_to_user(arg, &wavefront_info, sizeof (wavefront_info))) + return -EFAULT; + return 0; + + case SNDCTL_SEQ_RESETSAMPLES: +// printk (KERN_WARNING LOGNAME "driver cannot reset samples.\n"); + return 0; /* don't force an error */ + + case SNDCTL_SEQ_PERCMODE: + return 0; /* don't force an error */ + + case SNDCTL_SYNTH_MEMAVL: + if ((dev.freemem = wavefront_freemem ()) < 0) { + printk (KERN_ERR LOGNAME "cannot get memory size\n"); + return -EIO; + } else { + return dev.freemem; + } + break; + + case SNDCTL_SYNTH_CONTROL: + if(copy_from_user (&wc, arg, sizeof (wc))) + err = -EFAULT; + else if ((err = wavefront_synth_control (cmd, &wc)) == 0) { + if(copy_to_user (arg, &wc, sizeof (wc))) + err = -EFAULT; + } + + return err; + + default: + return -(EINVAL); + } +} + +static int +wavefront_oss_load_patch (int devno, int format, const char __user *addr, + int offs, int count, int pmgr_flag) +{ + + if (format == SYSEX_PATCH) { /* Handled by midi_synth.c */ + if (midi_load_patch == NULL) { + printk (KERN_ERR LOGNAME + "SYSEX not loadable: " + "no midi patch loader!\n"); + return -(EINVAL); + } + + return midi_load_patch (devno, format, addr, + offs, count, pmgr_flag); + + } else if (format == GUS_PATCH) { + return wavefront_load_gus_patch (devno, format, + addr, offs, count, pmgr_flag); + + } else if (format != WAVEFRONT_PATCH) { + printk (KERN_ERR LOGNAME "unknown patch format %d\n", format); + return -(EINVAL); + } + + if (count < sizeof (wavefront_patch_info)) { + printk (KERN_ERR LOGNAME "sample header too short\n"); + return -(EINVAL); + } + + /* "addr" points to a user-space wavefront_patch_info */ + + return wavefront_load_patch (addr); +} + +static struct synth_operations wavefront_operations = +{ + .owner = THIS_MODULE, + .id = "WaveFront", + .info = &wavefront_info, + .midi_dev = 0, + .synth_type = SYNTH_TYPE_SAMPLE, + .synth_subtype = SAMPLE_TYPE_WAVEFRONT, + .open = wavefront_oss_open, + .close = wavefront_oss_close, + .ioctl = wavefront_oss_ioctl, + .kill_note = midi_synth_kill_note, + .start_note = midi_synth_start_note, + .set_instr = midi_synth_set_instr, + .reset = midi_synth_reset, + .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 +}; +#endif /* OSS_SUPPORT_SEQ */ + +#if OSS_SUPPORT_LEVEL & OSS_SUPPORT_STATIC_INSTALL + +static void __init attach_wavefront (struct address_info *hw_config) +{ + (void) install_wavefront (); +} + +static int __init probe_wavefront (struct address_info *hw_config) +{ + return !detect_wavefront (hw_config->irq, hw_config->io_base); +} + +static void __exit unload_wavefront (struct address_info *hw_config) +{ + (void) uninstall_wavefront (); +} + +#endif /* OSS_SUPPORT_STATIC_INSTALL */ + +/***********************************************************************/ +/* WaveFront: Linux modular sound kernel installation interface */ +/***********************************************************************/ + +static irqreturn_t +wavefrontintr(int irq, void *dev_id, struct pt_regs *dummy) +{ + struct wf_config *hw = dev_id; + + /* + Some comments on interrupts. I attempted a version of this + driver that used interrupts throughout the code instead of + doing busy and/or sleep-waiting. Alas, it appears that once + the Motorola firmware is downloaded, the card *never* + generates an RX interrupt. These are successfully generated + during firmware loading, and after that wavefront_status() + reports that an interrupt is pending on the card from time + to time, but it never seems to be delivered to this + driver. Note also that wavefront_status() continues to + report that RX interrupts are enabled, suggesting that I + didn't goof up and disable them by mistake. + + Thus, I stepped back to a prior version of + wavefront_wait(), the only place where this really + matters. Its sad, but I've looked through the code to check + on things, and I really feel certain that the Motorola + firmware prevents RX-ready interrupts. + */ + + if ((wavefront_status() & (STAT_INTR_READ|STAT_INTR_WRITE)) == 0) { + return IRQ_NONE; + } + + hw->irq_ok = 1; + hw->irq_cnt++; + wake_up_interruptible (&hw->interrupt_sleeper); + return IRQ_HANDLED; +} + +/* STATUS REGISTER + +0 Host Rx Interrupt Enable (1=Enabled) +1 Host Rx Register Full (1=Full) +2 Host Rx Interrupt Pending (1=Interrupt) +3 Unused +4 Host Tx Interrupt (1=Enabled) +5 Host Tx Register empty (1=Empty) +6 Host Tx Interrupt Pending (1=Interrupt) +7 Unused +*/ + +static int +wavefront_interrupt_bits (int irq) + +{ + int bits; + + switch (irq) { + case 9: + bits = 0x00; + break; + case 5: + bits = 0x08; + break; + case 12: + bits = 0x10; + break; + case 15: + bits = 0x18; + break; + + default: + printk (KERN_WARNING LOGNAME "invalid IRQ %d\n", irq); + bits = -1; + } + + return bits; +} + +static void +wavefront_should_cause_interrupt (int val, int port, int timeout) + +{ + unsigned long flags; + + /* this will not help on SMP - but at least it compiles */ + spin_lock_irqsave(&lock, flags); + dev.irq_ok = 0; + outb (val,port); + interruptible_sleep_on_timeout (&dev.interrupt_sleeper, timeout); + spin_unlock_irqrestore(&lock,flags); +} + +static int __init wavefront_hw_reset (void) +{ + int bits; + int hwv[2]; + unsigned long irq_mask; + short reported_irq; + + /* IRQ already checked in init_module() */ + + bits = wavefront_interrupt_bits (dev.irq); + + printk (KERN_DEBUG LOGNAME "autodetecting WaveFront IRQ\n"); + + irq_mask = probe_irq_on (); + + outb (0x0, dev.control_port); + outb (0x80 | 0x40 | bits, dev.data_port); + wavefront_should_cause_interrupt(0x80|0x40|0x10|0x1, + dev.control_port, + (reset_time*HZ)/100); + + reported_irq = probe_irq_off (irq_mask); + + if (reported_irq != dev.irq) { + if (reported_irq == 0) { + printk (KERN_ERR LOGNAME + "No unassigned interrupts detected " + "after h/w reset\n"); + } else if (reported_irq < 0) { + printk (KERN_ERR LOGNAME + "Multiple unassigned interrupts detected " + "after h/w reset\n"); + } else { + printk (KERN_ERR LOGNAME "autodetected IRQ %d not the " + "value provided (%d)\n", reported_irq, + dev.irq); + } + dev.irq = -1; + return 1; + } else { + printk (KERN_INFO LOGNAME "autodetected IRQ at %d\n", + reported_irq); + } + + if (request_irq (dev.irq, wavefrontintr, + SA_INTERRUPT|SA_SHIRQ, + "wavefront synth", &dev) < 0) { + printk (KERN_WARNING LOGNAME "IRQ %d not available!\n", + dev.irq); + return 1; + } + + /* try reset of port */ + + outb (0x0, dev.control_port); + + /* At this point, the board is in reset, and the H/W initialization + register is accessed at the same address as the data port. + + Bit 7 - Enable IRQ Driver + 0 - Tri-state the Wave-Board drivers for the PC Bus IRQs + 1 - Enable IRQ selected by bits 5:3 to be driven onto the PC Bus. + + Bit 6 - MIDI Interface Select + + 0 - Use the MIDI Input from the 26-pin WaveBlaster + compatible header as the serial MIDI source + 1 - Use the MIDI Input from the 9-pin D connector as the + serial MIDI source. + + Bits 5:3 - IRQ Selection + 0 0 0 - IRQ 2/9 + 0 0 1 - IRQ 5 + 0 1 0 - IRQ 12 + 0 1 1 - IRQ 15 + 1 0 0 - Reserved + 1 0 1 - Reserved + 1 1 0 - Reserved + 1 1 1 - Reserved + + Bits 2:1 - Reserved + Bit 0 - Disable Boot ROM + 0 - memory accesses to 03FC30-03FFFFH utilize the internal Boot ROM + 1 - memory accesses to 03FC30-03FFFFH are directed to external + storage. + + */ + + /* configure hardware: IRQ, enable interrupts, + plus external 9-pin MIDI interface selected + */ + + outb (0x80 | 0x40 | bits, dev.data_port); + + /* CONTROL REGISTER + + 0 Host Rx Interrupt Enable (1=Enabled) 0x1 + 1 Unused 0x2 + 2 Unused 0x4 + 3 Unused 0x8 + 4 Host Tx Interrupt Enable 0x10 + 5 Mute (0=Mute; 1=Play) 0x20 + 6 Master Interrupt Enable (1=Enabled) 0x40 + 7 Master Reset (0=Reset; 1=Run) 0x80 + + Take us out of reset, mute output, master + TX + RX interrupts on. + + We'll get an interrupt presumably to tell us that the TX + register is clear. + */ + + wavefront_should_cause_interrupt(0x80|0x40|0x10|0x1, + dev.control_port, + (reset_time*HZ)/100); + + /* Note: data port is now the data port, not the h/w initialization + port. + */ + + if (!dev.irq_ok) { + printk (KERN_WARNING LOGNAME + "intr not received after h/w un-reset.\n"); + goto gone_bad; + } + + dev.interrupts_on = 1; + + /* Note: data port is now the data port, not the h/w initialization + port. + + At this point, only "HW VERSION" or "DOWNLOAD OS" commands + will work. So, issue one of them, and wait for TX + interrupt. This can take a *long* time after a cold boot, + while the ISC ROM does its RAM test. The SDK says up to 4 + seconds - with 12MB of RAM on a Tropez+, it takes a lot + longer than that (~16secs). Note that the card understands + the difference between a warm and a cold boot, so + subsequent ISC2115 reboots (say, caused by module + reloading) will get through this much faster. + + XXX Interesting question: why is no RX interrupt received first ? + */ + + wavefront_should_cause_interrupt(WFC_HARDWARE_VERSION, + dev.data_port, ramcheck_time*HZ); + + if (!dev.irq_ok) { + printk (KERN_WARNING LOGNAME + "post-RAM-check interrupt not received.\n"); + goto gone_bad; + } + + if (!wavefront_wait (STAT_CAN_READ)) { + printk (KERN_WARNING LOGNAME + "no response to HW version cmd.\n"); + goto gone_bad; + } + + if ((hwv[0] = wavefront_read ()) == -1) { + printk (KERN_WARNING LOGNAME + "board not responding correctly.\n"); + goto gone_bad; + } + + if (hwv[0] == 0xFF) { /* NAK */ + + /* Board's RAM test failed. Try to read error code, + and tell us about it either way. + */ + + if ((hwv[0] = wavefront_read ()) == -1) { + printk (KERN_WARNING LOGNAME "on-board RAM test failed " + "(bad error code).\n"); + } else { + printk (KERN_WARNING LOGNAME "on-board RAM test failed " + "(error code: 0x%x).\n", + hwv[0]); + } + goto gone_bad; + } + + /* We're OK, just get the next byte of the HW version response */ + + if ((hwv[1] = wavefront_read ()) == -1) { + printk (KERN_WARNING LOGNAME "incorrect h/w response.\n"); + goto gone_bad; + } + + printk (KERN_INFO LOGNAME "hardware version %d.%d\n", + hwv[0], hwv[1]); + + return 0; + + + gone_bad: + if (dev.irq >= 0) { + free_irq (dev.irq, &dev); + dev.irq = -1; + } + return (1); +} + +static int __init detect_wavefront (int irq, int io_base) +{ + unsigned char rbuf[4], wbuf[4]; + + /* TB docs say the device takes up 8 ports, but we know that + if there is an FX device present (i.e. a Tropez+) it really + consumes 16. + */ + + if (check_region (io_base, 16)) { + printk (KERN_ERR LOGNAME "IO address range 0x%x - 0x%x " + "already in use - ignored\n", dev.base, + dev.base+15); + return -1; + } + + dev.irq = irq; + dev.base = io_base; + dev.israw = 0; + dev.debug = debug_default; + dev.interrupts_on = 0; + dev.irq_cnt = 0; + dev.rom_samples_rdonly = 1; /* XXX default lock on ROM sample slots */ + + if (wavefront_cmd (WFC_FIRMWARE_VERSION, rbuf, wbuf) == 0) { + + dev.fw_version[0] = rbuf[0]; + dev.fw_version[1] = rbuf[1]; + printk (KERN_INFO LOGNAME + "firmware %d.%d already loaded.\n", + rbuf[0], rbuf[1]); + + /* check that a command actually works */ + + if (wavefront_cmd (WFC_HARDWARE_VERSION, + rbuf, wbuf) == 0) { + dev.hw_version[0] = rbuf[0]; + dev.hw_version[1] = rbuf[1]; + } else { + printk (KERN_WARNING LOGNAME "not raw, but no " + "hardware version!\n"); + return 0; + } + + if (!wf_raw) { + return 1; + } else { + printk (KERN_INFO LOGNAME + "reloading firmware anyway.\n"); + dev.israw = 1; + } + + } else { + + dev.israw = 1; + printk (KERN_INFO LOGNAME + "no response to firmware probe, assume raw.\n"); + + } + + init_waitqueue_head (&dev.interrupt_sleeper); + + if (wavefront_hw_reset ()) { + printk (KERN_WARNING LOGNAME "hardware reset failed\n"); + return 0; + } + + /* Check for FX device, present only on Tropez+ */ + + dev.has_fx = (detect_wffx () == 0); + + return 1; +} + +#include "os.h" +#include +#include +#include +#include + + +static int +wavefront_download_firmware (char *path) + +{ + unsigned char section[WF_SECTION_MAX]; + char section_length; /* yes, just a char; max value is WF_SECTION_MAX */ + int section_cnt_downloaded = 0; + int fd; + int c; + int i; + mm_segment_t fs; + + /* This tries to be a bit cleverer than the stuff Alan Cox did for + the generic sound firmware, in that it actually knows + something about the structure of the Motorola firmware. In + particular, it uses a version that has been stripped of the + 20K of useless header information, and had section lengths + added, making it possible to load the entire OS without any + [kv]malloc() activity, since the longest entity we ever read is + 42 bytes (well, WF_SECTION_MAX) long. + */ + + fs = get_fs(); + set_fs (get_ds()); + + if ((fd = sys_open (path, 0, 0)) < 0) { + printk (KERN_WARNING LOGNAME "Unable to load \"%s\".\n", + path); + return 1; + } + + while (1) { + int x; + + if ((x = sys_read (fd, §ion_length, sizeof (section_length))) != + sizeof (section_length)) { + printk (KERN_ERR LOGNAME "firmware read error.\n"); + goto failure; + } + + if (section_length == 0) { + break; + } + + if (sys_read (fd, section, section_length) != section_length) { + printk (KERN_ERR LOGNAME "firmware section " + "read error.\n"); + goto failure; + } + + /* Send command */ + + if (wavefront_write (WFC_DOWNLOAD_OS)) { + goto failure; + } + + for (i = 0; i < section_length; i++) { + if (wavefront_write (section[i])) { + goto failure; + } + } + + /* get ACK */ + + if (wavefront_wait (STAT_CAN_READ)) { + + if ((c = inb (dev.data_port)) != WF_ACK) { + + printk (KERN_ERR LOGNAME "download " + "of section #%d not " + "acknowledged, ack = 0x%x\n", + section_cnt_downloaded + 1, c); + goto failure; + + } + + } else { + printk (KERN_ERR LOGNAME "time out for firmware ACK.\n"); + goto failure; + } + + } + + sys_close (fd); + set_fs (fs); + return 0; + + failure: + sys_close (fd); + set_fs (fs); + printk (KERN_ERR "\nWaveFront: firmware download failed!!!\n"); + return 1; +} + +static int __init wavefront_config_midi (void) +{ + unsigned char rbuf[4], wbuf[4]; + + if (detect_wf_mpu (dev.irq, dev.base) < 0) { + printk (KERN_WARNING LOGNAME + "could not find working MIDI device\n"); + return -1; + } + + if ((dev.mididev = install_wf_mpu ()) < 0) { + printk (KERN_WARNING LOGNAME + "MIDI interfaces not configured\n"); + return -1; + } + + /* Route external MIDI to WaveFront synth (by default) */ + + if (wavefront_cmd (WFC_MISYNTH_ON, rbuf, wbuf)) { + printk (KERN_WARNING LOGNAME + "cannot enable MIDI-IN to synth routing.\n"); + /* XXX error ? */ + } + + +#if OSS_SUPPORT_LEVEL & OSS_SUPPORT_SEQ + /* Get the regular MIDI patch loading function, so we can + use it if we ever get handed a SYSEX patch. This is + unlikely, because its so damn slow, but we may as well + leave this functionality from maui.c behind, since it + could be useful for sequencer applications that can + only use MIDI to do patch loading. + */ + + if (midi_devs[dev.mididev]->converter != NULL) { + midi_load_patch = midi_devs[dev.mididev]->converter->load_patch; + midi_devs[dev.mididev]->converter->load_patch = + &wavefront_oss_load_patch; + } + +#endif /* OSS_SUPPORT_SEQ */ + + /* Turn on Virtual MIDI, but first *always* turn it off, + since otherwise consectutive reloads of the driver will + never cause the hardware to generate the initial "internal" or + "external" source bytes in the MIDI data stream. This + is pretty important, since the internal hardware generally will + be used to generate none or very little MIDI output, and + thus the only source of MIDI data is actually external. Without + the switch bytes, the driver will think it all comes from + the internal interface. Duh. + */ + + if (wavefront_cmd (WFC_VMIDI_OFF, rbuf, wbuf)) { + printk (KERN_WARNING LOGNAME + "virtual MIDI mode not disabled\n"); + return 0; /* We're OK, but missing the external MIDI dev */ + } + + if ((dev.ext_mididev = virtual_midi_enable ()) < 0) { + printk (KERN_WARNING LOGNAME "no virtual MIDI access.\n"); + } else { + if (wavefront_cmd (WFC_VMIDI_ON, rbuf, wbuf)) { + printk (KERN_WARNING LOGNAME + "cannot enable virtual MIDI mode.\n"); + virtual_midi_disable (); + } + } + + return 0; +} + +static int __init wavefront_do_reset (int atboot) +{ + char voices[1]; + + if (!atboot && wavefront_hw_reset ()) { + printk (KERN_WARNING LOGNAME "hw reset failed.\n"); + goto gone_bad; + } + + if (dev.israw) { + if (wavefront_download_firmware (ospath)) { + goto gone_bad; + } + + dev.israw = 0; + + /* Wait for the OS to get running. The protocol for + this is non-obvious, and was determined by + using port-IO tracing in DOSemu and some + experimentation here. + + Rather than using timed waits, use interrupts creatively. + */ + + wavefront_should_cause_interrupt (WFC_NOOP, + dev.data_port, + (osrun_time*HZ)); + + if (!dev.irq_ok) { + printk (KERN_WARNING LOGNAME + "no post-OS interrupt.\n"); + goto gone_bad; + } + + /* Now, do it again ! */ + + wavefront_should_cause_interrupt (WFC_NOOP, + dev.data_port, (10*HZ)); + + if (!dev.irq_ok) { + printk (KERN_WARNING LOGNAME + "no post-OS interrupt(2).\n"); + goto gone_bad; + } + + /* OK, no (RX/TX) interrupts any more, but leave mute + in effect. + */ + + outb (0x80|0x40, dev.control_port); + + /* No need for the IRQ anymore */ + + free_irq (dev.irq, &dev); + + } + + if (dev.has_fx && fx_raw) { + wffx_init (); + } + + /* SETUPSND.EXE asks for sample memory config here, but since i + have no idea how to interpret the result, we'll forget + about it. + */ + + if ((dev.freemem = wavefront_freemem ()) < 0) { + goto gone_bad; + } + + printk (KERN_INFO LOGNAME "available DRAM %dk\n", dev.freemem / 1024); + + if (wavefront_write (0xf0) || + wavefront_write (1) || + (wavefront_read () < 0)) { + dev.debug = 0; + printk (KERN_WARNING LOGNAME "MPU emulation mode not set.\n"); + goto gone_bad; + } + + voices[0] = 32; + + if (wavefront_cmd (WFC_SET_NVOICES, NULL, voices)) { + printk (KERN_WARNING LOGNAME + "cannot set number of voices to 32.\n"); + goto gone_bad; + } + + + return 0; + + gone_bad: + /* reset that sucker so that it doesn't bother us. */ + + outb (0x0, dev.control_port); + dev.interrupts_on = 0; + if (dev.irq >= 0) { + free_irq (dev.irq, &dev); + } + return 1; +} + +static int __init wavefront_init (int atboot) +{ + int samples_are_from_rom; + + if (dev.israw) { + samples_are_from_rom = 1; + } else { + /* XXX is this always true ? */ + samples_are_from_rom = 0; + } + + if (dev.israw || fx_raw) { + if (wavefront_do_reset (atboot)) { + return -1; + } + } + + wavefront_get_sample_status (samples_are_from_rom); + wavefront_get_program_status (); + wavefront_get_patch_status (); + + /* Start normal operation: unreset, master interrupt enabled, no mute + */ + + outb (0x80|0x40|0x20, dev.control_port); + + return (0); +} + +static int __init install_wavefront (void) + +{ + if ((dev.synth_dev = register_sound_synth (&wavefront_fops, -1)) < 0) { + printk (KERN_ERR LOGNAME "cannot register raw synth\n"); + return -1; + } + +#if OSS_SUPPORT_LEVEL & OSS_SUPPORT_SEQ + if ((dev.oss_dev = sound_alloc_synthdev()) == -1) { + printk (KERN_ERR LOGNAME "Too many sequencers\n"); + return -1; + } else { + synth_devs[dev.oss_dev] = &wavefront_operations; + } +#endif /* OSS_SUPPORT_SEQ */ + + if (wavefront_init (1) < 0) { + printk (KERN_WARNING LOGNAME "initialization failed.\n"); + +#if OSS_SUPPORT_LEVEL & OSS_SUPPORT_SEQ + sound_unload_synthdev (dev.oss_dev); +#endif /* OSS_SUPPORT_SEQ */ + + return -1; + } + + request_region (dev.base+2, 6, "wavefront synth"); + + if (dev.has_fx) { + request_region (dev.base+8, 8, "wavefront fx"); + } + + if (wavefront_config_midi ()) { + printk (KERN_WARNING LOGNAME "could not initialize MIDI.\n"); + } + + return dev.oss_dev; +} + +static void __exit uninstall_wavefront (void) +{ + /* the first two i/o addresses are freed by the wf_mpu code */ + release_region (dev.base+2, 6); + + if (dev.has_fx) { + release_region (dev.base+8, 8); + } + + unregister_sound_synth (dev.synth_dev); + +#if OSS_SUPPORT_LEVEL & OSS_SUPPORT_SEQ + sound_unload_synthdev (dev.oss_dev); +#endif /* OSS_SUPPORT_SEQ */ + uninstall_wf_mpu (); +} + +/***********************************************************************/ +/* WaveFront FX control */ +/***********************************************************************/ + +#include "yss225.h" + +/* Control bits for the Load Control Register + */ + +#define FX_LSB_TRANSFER 0x01 /* transfer after DSP LSB byte written */ +#define FX_MSB_TRANSFER 0x02 /* transfer after DSP MSB byte written */ +#define FX_AUTO_INCR 0x04 /* auto-increment DSP address after transfer */ + +static int +wffx_idle (void) + +{ + int i; + unsigned int x = 0x80; + + for (i = 0; i < 1000; i++) { + x = inb (dev.fx_status); + if ((x & 0x80) == 0) { + break; + } + } + + if (x & 0x80) { + printk (KERN_ERR LOGNAME "FX device never idle.\n"); + return 0; + } + + return (1); +} + +int __init detect_wffx (void) +{ + /* This is a crude check, but its the best one I have for now. + Certainly on the Maui and the Tropez, wffx_idle() will + report "never idle", which suggests that this test should + work OK. + */ + + if (inb (dev.fx_status) & 0x80) { + printk (KERN_INFO LOGNAME "Hmm, probably a Maui or Tropez.\n"); + return -1; + } + + return 0; +} + +void +wffx_mute (int onoff) + +{ + if (!wffx_idle()) { + return; + } + + outb (onoff ? 0x02 : 0x00, dev.fx_op); +} + +static int +wffx_memset (int page, + int addr, int cnt, unsigned short *data) +{ + if (page < 0 || page > 7) { + printk (KERN_ERR LOGNAME "FX memset: " + "page must be >= 0 and <= 7\n"); + return -(EINVAL); + } + + if (addr < 0 || addr > 0x7f) { + printk (KERN_ERR LOGNAME "FX memset: " + "addr must be >= 0 and <= 7f\n"); + return -(EINVAL); + } + + if (cnt == 1) { + + outb (FX_LSB_TRANSFER, dev.fx_lcr); + outb (page, dev.fx_dsp_page); + outb (addr, dev.fx_dsp_addr); + outb ((data[0] >> 8), dev.fx_dsp_msb); + outb ((data[0] & 0xff), dev.fx_dsp_lsb); + + printk (KERN_INFO LOGNAME "FX: addr %d:%x set to 0x%x\n", + page, addr, data[0]); + + } else { + int i; + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (page, dev.fx_dsp_page); + outb (addr, dev.fx_dsp_addr); + + for (i = 0; i < cnt; i++) { + outb ((data[i] >> 8), dev.fx_dsp_msb); + outb ((data[i] & 0xff), dev.fx_dsp_lsb); + if (!wffx_idle ()) { + break; + } + } + + if (i != cnt) { + printk (KERN_WARNING LOGNAME + "FX memset " + "(0x%x, 0x%x, %p, %d) incomplete\n", + page, addr, data, cnt); + return -(EIO); + } + } + + return 0; +} + +static int +wffx_ioctl (wavefront_fx_info *r) + +{ + unsigned short page_data[256]; + unsigned short *pd; + + switch (r->request) { + case WFFX_MUTE: + wffx_mute (r->data[0]); + return 0; + + case WFFX_MEMSET: + + if (r->data[2] <= 0) { + printk (KERN_ERR LOGNAME "cannot write " + "<= 0 bytes to FX\n"); + return -(EINVAL); + } else if (r->data[2] == 1) { + pd = (unsigned short *) &r->data[3]; + } else { + if (r->data[2] > sizeof (page_data)) { + printk (KERN_ERR LOGNAME "cannot write " + "> 255 bytes to FX\n"); + return -(EINVAL); + } + if (copy_from_user(page_data, + (unsigned char __user *)r->data[3], + r->data[2])) + return -EFAULT; + pd = page_data; + } + + return wffx_memset (r->data[0], /* page */ + r->data[1], /* addr */ + r->data[2], /* cnt */ + pd); + + default: + printk (KERN_WARNING LOGNAME + "FX: ioctl %d not yet supported\n", + r->request); + return -(EINVAL); + } +} + +/* YSS225 initialization. + + This code was developed using DOSEMU. The Turtle Beach SETUPSND + utility was run with I/O tracing in DOSEMU enabled, and a reconstruction + of the port I/O done, using the Yamaha faxback document as a guide + to add more logic to the code. Its really pretty weird. + + There was an alternative approach of just dumping the whole I/O + sequence as a series of port/value pairs and a simple loop + that output it. However, I hope that eventually I'll get more + control over what this code does, and so I tried to stick with + a somewhat "algorithmic" approach. +*/ + +static int __init wffx_init (void) +{ + int i; + int j; + + /* Set all bits for all channels on the MOD unit to zero */ + /* XXX But why do this twice ? */ + + for (j = 0; j < 2; j++) { + for (i = 0x10; i <= 0xff; i++) { + + if (!wffx_idle ()) { + return (-1); + } + + outb (i, dev.fx_mod_addr); + outb (0x0, dev.fx_mod_data); + } + } + + if (!wffx_idle()) return (-1); + outb (0x02, dev.fx_op); /* mute on */ + + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x44, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x42, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x43, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x7c, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x7e, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x46, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x49, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x47, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x4a, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + + /* either because of stupidity by TB's programmers, or because it + actually does something, rezero the MOD page. + */ + for (i = 0x10; i <= 0xff; i++) { + + if (!wffx_idle ()) { + return (-1); + } + + outb (i, dev.fx_mod_addr); + outb (0x0, dev.fx_mod_data); + } + /* load page zero */ + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x00, dev.fx_dsp_page); + outb (0x00, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_zero); i += 2) { + outb (page_zero[i], dev.fx_dsp_msb); + outb (page_zero[i+1], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + /* Now load page one */ + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x01, dev.fx_dsp_page); + outb (0x00, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_one); i += 2) { + outb (page_one[i], dev.fx_dsp_msb); + outb (page_one[i+1], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x02, dev.fx_dsp_page); + outb (0x00, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_two); i++) { + outb (page_two[i], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x03, dev.fx_dsp_page); + outb (0x00, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_three); i++) { + outb (page_three[i], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x04, dev.fx_dsp_page); + outb (0x00, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_four); i++) { + outb (page_four[i], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + /* Load memory area (page six) */ + + outb (FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x06, dev.fx_dsp_page); + + for (i = 0; i < sizeof (page_six); i += 3) { + outb (page_six[i], dev.fx_dsp_addr); + outb (page_six[i+1], dev.fx_dsp_msb); + outb (page_six[i+2], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x07, dev.fx_dsp_page); + outb (0x00, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_seven); i += 2) { + outb (page_seven[i], dev.fx_dsp_msb); + outb (page_seven[i+1], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + /* Now setup the MOD area. We do this algorithmically in order to + save a little data space. It could be done in the same fashion + as the "pages". + */ + + for (i = 0x00; i <= 0x0f; i++) { + outb (0x01, dev.fx_mod_addr); + outb (i, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + outb (0x02, dev.fx_mod_addr); + outb (0x00, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + for (i = 0xb0; i <= 0xbf; i++) { + outb (i, dev.fx_mod_addr); + outb (0x20, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + for (i = 0xf0; i <= 0xff; i++) { + outb (i, dev.fx_mod_addr); + outb (0x20, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + for (i = 0x10; i <= 0x1d; i++) { + outb (i, dev.fx_mod_addr); + outb (0xff, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + outb (0x1e, dev.fx_mod_addr); + outb (0x40, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + + for (i = 0x1f; i <= 0x2d; i++) { + outb (i, dev.fx_mod_addr); + outb (0xff, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + outb (0x2e, dev.fx_mod_addr); + outb (0x00, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + + for (i = 0x2f; i <= 0x3e; i++) { + outb (i, dev.fx_mod_addr); + outb (0x00, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + outb (0x3f, dev.fx_mod_addr); + outb (0x20, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + + for (i = 0x40; i <= 0x4d; i++) { + outb (i, dev.fx_mod_addr); + outb (0x00, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + outb (0x4e, dev.fx_mod_addr); + outb (0x0e, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + outb (0x4f, dev.fx_mod_addr); + outb (0x0e, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + + + for (i = 0x50; i <= 0x6b; i++) { + outb (i, dev.fx_mod_addr); + outb (0x00, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + outb (0x6c, dev.fx_mod_addr); + outb (0x40, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + + outb (0x6d, dev.fx_mod_addr); + outb (0x00, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + + outb (0x6e, dev.fx_mod_addr); + outb (0x40, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + + outb (0x6f, dev.fx_mod_addr); + outb (0x40, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + + for (i = 0x70; i <= 0x7f; i++) { + outb (i, dev.fx_mod_addr); + outb (0xc0, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + for (i = 0x80; i <= 0xaf; i++) { + outb (i, dev.fx_mod_addr); + outb (0x00, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + for (i = 0xc0; i <= 0xdd; i++) { + outb (i, dev.fx_mod_addr); + outb (0x00, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + outb (0xde, dev.fx_mod_addr); + outb (0x10, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + outb (0xdf, dev.fx_mod_addr); + outb (0x10, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + + for (i = 0xe0; i <= 0xef; i++) { + outb (i, dev.fx_mod_addr); + outb (0x00, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + for (i = 0x00; i <= 0x0f; i++) { + outb (0x01, dev.fx_mod_addr); + outb (i, dev.fx_mod_data); + outb (0x02, dev.fx_mod_addr); + outb (0x01, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + outb (0x02, dev.fx_op); /* mute on */ + + /* Now set the coefficients and so forth for the programs above */ + + for (i = 0; i < sizeof (coefficients); i += 4) { + outb (coefficients[i], dev.fx_dsp_page); + outb (coefficients[i+1], dev.fx_dsp_addr); + outb (coefficients[i+2], dev.fx_dsp_msb); + outb (coefficients[i+3], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + /* Some settings (?) that are too small to bundle into loops */ + + if (!wffx_idle()) return (-1); + outb (0x1e, dev.fx_mod_addr); + outb (0x14, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + outb (0xde, dev.fx_mod_addr); + outb (0x20, dev.fx_mod_data); + if (!wffx_idle()) return (-1); + outb (0xdf, dev.fx_mod_addr); + outb (0x20, dev.fx_mod_data); + + /* some more coefficients */ + + if (!wffx_idle()) return (-1); + outb (0x06, dev.fx_dsp_page); + outb (0x78, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x40, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x03, dev.fx_dsp_addr); + outb (0x0f, dev.fx_dsp_msb); + outb (0xff, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x0b, dev.fx_dsp_addr); + outb (0x0f, dev.fx_dsp_msb); + outb (0xff, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x02, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x0a, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x46, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + outb (0x07, dev.fx_dsp_page); + outb (0x49, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + + /* Now, for some strange reason, lets reload every page + and all the coefficients over again. I have *NO* idea + why this is done. I do know that no sound is produced + is this phase is omitted. + */ + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x00, dev.fx_dsp_page); + outb (0x10, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_zero_v2); i += 2) { + outb (page_zero_v2[i], dev.fx_dsp_msb); + outb (page_zero_v2[i+1], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x01, dev.fx_dsp_page); + outb (0x10, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_one_v2); i += 2) { + outb (page_one_v2[i], dev.fx_dsp_msb); + outb (page_one_v2[i+1], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + if (!wffx_idle()) return (-1); + if (!wffx_idle()) return (-1); + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x02, dev.fx_dsp_page); + outb (0x10, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_two_v2); i++) { + outb (page_two_v2[i], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x03, dev.fx_dsp_page); + outb (0x10, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_three_v2); i++) { + outb (page_three_v2[i], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x04, dev.fx_dsp_page); + outb (0x10, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_four_v2); i++) { + outb (page_four_v2[i], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + outb (FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x06, dev.fx_dsp_page); + + /* Page six v.2 is algorithmic */ + + for (i = 0x10; i <= 0x3e; i += 2) { + outb (i, dev.fx_dsp_addr); + outb (0x00, dev.fx_dsp_msb); + outb (0x00, dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev.fx_lcr); + outb (0x07, dev.fx_dsp_page); + outb (0x10, dev.fx_dsp_addr); + + for (i = 0; i < sizeof (page_seven_v2); i += 2) { + outb (page_seven_v2[i], dev.fx_dsp_msb); + outb (page_seven_v2[i+1], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + for (i = 0x00; i < sizeof(mod_v2); i += 2) { + outb (mod_v2[i], dev.fx_mod_addr); + outb (mod_v2[i+1], dev.fx_mod_data); + if (!wffx_idle()) return (-1); + } + + for (i = 0; i < sizeof (coefficients2); i += 4) { + outb (coefficients2[i], dev.fx_dsp_page); + outb (coefficients2[i+1], dev.fx_dsp_addr); + outb (coefficients2[i+2], dev.fx_dsp_msb); + outb (coefficients2[i+3], dev.fx_dsp_lsb); + if (!wffx_idle()) return (-1); + } + + for (i = 0; i < sizeof (coefficients3); i += 2) { + int x; + + outb (0x07, dev.fx_dsp_page); + x = (i % 4) ? 0x4e : 0x4c; + outb (x, dev.fx_dsp_addr); + outb (coefficients3[i], dev.fx_dsp_msb); + outb (coefficients3[i+1], dev.fx_dsp_lsb); + } + + outb (0x00, dev.fx_op); /* mute off */ + if (!wffx_idle()) return (-1); + + return (0); +} + +static int io = -1; +static int irq = -1; + +MODULE_AUTHOR ("Paul Barton-Davis "); +MODULE_DESCRIPTION ("Turtle Beach WaveFront Linux Driver"); +MODULE_LICENSE("GPL"); +module_param (io, int, 0); +module_param (irq, int, 0); + +static int __init init_wavfront (void) +{ + printk ("Turtle Beach WaveFront Driver\n" + "Copyright (C) by Hannu Solvainen, " + "Paul Barton-Davis 1993-1998.\n"); + + /* XXX t'would be lovely to ask the CS4232 for these values, eh ? */ + + if (io == -1 || irq == -1) { + printk (KERN_INFO LOGNAME "irq and io options must be set.\n"); + return -EINVAL; + } + + if (wavefront_interrupt_bits (irq) < 0) { + printk (KERN_INFO LOGNAME + "IRQ must be 9, 5, 12 or 15 (not %d)\n", irq); + return -ENODEV; + } + + if (detect_wavefront (irq, io) < 0) { + return -ENODEV; + } + + if (install_wavefront () < 0) { + return -EIO; + } + + return 0; +} + +static void __exit cleanup_wavfront (void) +{ + uninstall_wavefront (); +} + +module_init(init_wavfront); +module_exit(cleanup_wavfront); diff --git a/sound/oss/wf_midi.c b/sound/oss/wf_midi.c new file mode 100644 index 000000000000..7b167b74375b --- /dev/null +++ b/sound/oss/wf_midi.c @@ -0,0 +1,880 @@ +/* + * sound/wf_midi.c + * + * The low level driver for the WaveFront ICS2115 MIDI interface(s) + * Note that there is also an MPU-401 emulation (actually, a UART-401 + * emulation) on the CS4232 on the Tropez Plus. This code has nothing + * to do with that interface at all. + * + * The interface is essentially just a UART-401, but is has the + * interesting property of supporting what Turtle Beach called + * "Virtual MIDI" mode. In this mode, there are effectively *two* + * MIDI buses accessible via the interface, one that is routed + * solely to/from the external WaveFront synthesizer and the other + * corresponding to the pin/socket connector used to link external + * MIDI devices to the board. + * + * This driver fully supports this mode, allowing two distinct + * midi devices (/dev/midiNN and /dev/midiNN+1) to be used + * completely independently, giving 32 channels of MIDI routing, + * 16 to the WaveFront synth and 16 to the external MIDI bus. + * + * Switching between the two is accomplished externally by the driver + * using the two otherwise unused MIDI bytes. See the code for more details. + * + * NOTE: VIRTUAL MIDI MODE IS ON BY DEFAULT (see wavefront.c) + * + * The main reason to turn off Virtual MIDI mode is when you want to + * tightly couple the WaveFront synth with an external MIDI + * device. You won't be able to distinguish the source of any MIDI + * data except via SysEx ID, but thats probably OK, since for the most + * part, the WaveFront won't be sending any MIDI data at all. + * + * The main reason to turn on Virtual MIDI Mode is to provide two + * completely independent 16-channel MIDI buses, one to the + * WaveFront and one to any external MIDI devices. Given the 32 + * voice nature of the WaveFront, its pretty easy to find a use + * for all 16 channels driving just that synth. + * + */ + +/* + * Copyright (C) by Paul Barton-Davis 1998 + * Some portions of this file are derived from work that is: + * + * 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. + */ + +#include +#include +#include +#include "sound_config.h" + +#include + +#ifdef MODULE + +struct wf_mpu_config { + int base; +#define DATAPORT(d) (d)->base +#define COMDPORT(d) (d)->base+1 +#define STATPORT(d) (d)->base+1 + + int irq; + int opened; + int devno; + int synthno; + int mode; +#define MODE_MIDI 1 +#define MODE_SYNTH 2 + + void (*inputintr) (int dev, unsigned char data); + char isvirtual; /* do virtual I/O stuff */ +}; + +static struct wf_mpu_config devs[2]; +static struct wf_mpu_config *phys_dev = &devs[0]; +static struct wf_mpu_config *virt_dev = &devs[1]; + +static void start_uart_mode (void); +static DEFINE_SPINLOCK(lock); + +#define OUTPUT_READY 0x40 +#define INPUT_AVAIL 0x80 +#define MPU_ACK 0xFE +#define UART_MODE_ON 0x3F + +static inline int wf_mpu_status (void) +{ + return inb (STATPORT (phys_dev)); +} + +static inline int input_avail (void) +{ + return !(wf_mpu_status() & INPUT_AVAIL); +} + +static inline int output_ready (void) +{ + return !(wf_mpu_status() & OUTPUT_READY); +} + +static inline int read_data (void) +{ + return inb (DATAPORT (phys_dev)); +} + +static inline void write_data (unsigned char byte) +{ + outb (byte, DATAPORT (phys_dev)); +} + +/* + * States for the input scanner (should be in dev_table.h) + */ + +#define MST_SYSMSG 100 /* System message (sysx etc). */ +#define MST_MTC 102 /* Midi Time Code (MTC) qframe msg */ +#define MST_SONGSEL 103 /* Song select */ +#define MST_SONGPOS 104 /* Song position pointer */ +#define MST_TIMED 105 /* Leading timing byte rcvd */ + +/* buffer space check for input scanner */ + +#define BUFTEST(mi) if (mi->m_ptr >= MI_MAX || mi->m_ptr < 0) \ +{printk(KERN_ERR "WF-MPU: Invalid buffer pointer %d/%d, s=%d\n", \ + mi->m_ptr, mi->m_left, mi->m_state);mi->m_ptr--;} + +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 */ +}; + +static int +wf_mpu_input_scanner (int devno, int synthdev, unsigned char midic) + +{ + struct midi_input_info *mi = &midi_devs[devno]->in_info; + + switch (mi->m_state) { + case MST_INIT: + switch (midic) { + case 0xf8: + /* Timer overflow */ + break; + + case 0xfc: + break; + + case 0xfd: + /* XXX do something useful with this. If there is + an external MIDI timer (e.g. a hardware sequencer, + a useful timer can be derived ... + + For now, no timer support. + */ + break; + + case 0xfe: + return MPU_ACK; + break; + + case 0xf0: + case 0xf1: + case 0xf2: + case 0xf3: + case 0xf4: + case 0xf5: + case 0xf6: + case 0xf7: + break; + + case 0xf9: + break; + + case 0xff: + mi->m_state = MST_SYSMSG; + break; + + default: + if (midic <= 0xef) { + mi->m_state = MST_TIMED; + } + else + printk (KERN_ERR " ", + midic); + } + break; + + case MST_TIMED: + { + int msg = ((int) (midic & 0xf0) >> 4); + + mi->m_state = MST_DATA; + + if (msg < 8) { /* Data byte */ + + msg = ((int) (mi->m_prev_status & 0xf0) >> 4); + msg -= 8; + mi->m_left = len_tab[msg] - 1; + + mi->m_ptr = 2; + mi->m_buf[0] = mi->m_prev_status; + mi->m_buf[1] = midic; + + if (mi->m_left <= 0) { + mi->m_state = MST_INIT; + do_midi_msg (synthdev, mi->m_buf, mi->m_ptr); + mi->m_ptr = 0; + } + } else if (msg == 0xf) { /* MPU MARK */ + + mi->m_state = MST_INIT; + + switch (midic) { + case 0xf8: + break; + + case 0xf9: + break; + + case 0xfc: + break; + + default: + break; + } + } else { + mi->m_prev_status = midic; + msg -= 8; + mi->m_left = len_tab[msg]; + + mi->m_ptr = 1; + mi->m_buf[0] = midic; + + if (mi->m_left <= 0) { + mi->m_state = MST_INIT; + do_midi_msg (synthdev, mi->m_buf, mi->m_ptr); + mi->m_ptr = 0; + } + } + } + break; + + case MST_SYSMSG: + switch (midic) { + case 0xf0: + mi->m_state = MST_SYSEX; + break; + + case 0xf1: + mi->m_state = MST_MTC; + break; + + case 0xf2: + mi->m_state = MST_SONGPOS; + mi->m_ptr = 0; + break; + + case 0xf3: + mi->m_state = MST_SONGSEL; + break; + + case 0xf6: + mi->m_state = MST_INIT; + + /* + * Real time messages + */ + case 0xf8: + /* midi clock */ + mi->m_state = MST_INIT; + /* XXX need ext MIDI timer support */ + break; + + case 0xfA: + mi->m_state = MST_INIT; + /* XXX need ext MIDI timer support */ + break; + + case 0xFB: + mi->m_state = MST_INIT; + /* XXX need ext MIDI timer support */ + break; + + case 0xFC: + mi->m_state = MST_INIT; + /* XXX need ext MIDI timer support */ + break; + + case 0xFE: + /* active sensing */ + mi->m_state = MST_INIT; + break; + + case 0xff: + mi->m_state = MST_INIT; + break; + + default: + printk (KERN_ERR "unknown MIDI sysmsg %0x\n", midic); + mi->m_state = MST_INIT; + } + break; + + case MST_MTC: + mi->m_state = MST_INIT; + break; + + case MST_SYSEX: + if (midic == 0xf7) { + mi->m_state = MST_INIT; + } else { + /* XXX fix me */ + } + break; + + case MST_SONGPOS: + BUFTEST (mi); + mi->m_buf[mi->m_ptr++] = midic; + if (mi->m_ptr == 2) { + mi->m_state = MST_INIT; + mi->m_ptr = 0; + /* XXX need ext MIDI timer support */ + } + break; + + case MST_DATA: + BUFTEST (mi); + mi->m_buf[mi->m_ptr++] = midic; + if ((--mi->m_left) <= 0) { + mi->m_state = MST_INIT; + do_midi_msg (synthdev, mi->m_buf, mi->m_ptr); + mi->m_ptr = 0; + } + break; + + default: + printk (KERN_ERR "Bad state %d ", mi->m_state); + mi->m_state = MST_INIT; + } + + return 1; +} + +static irqreturn_t +wf_mpuintr(int irq, void *dev_id, struct pt_regs *dummy) + +{ + struct wf_mpu_config *physical_dev = dev_id; + static struct wf_mpu_config *input_dev; + struct midi_input_info *mi = &midi_devs[physical_dev->devno]->in_info; + int n; + + if (!input_avail()) { /* not for us */ + return IRQ_NONE; + } + + if (mi->m_busy) + return IRQ_HANDLED; + spin_lock(&lock); + mi->m_busy = 1; + + if (!input_dev) { + input_dev = physical_dev; + } + + n = 50; /* XXX why ? */ + + do { + unsigned char c = read_data (); + + if (phys_dev->isvirtual) { + + if (c == WF_EXTERNAL_SWITCH) { + input_dev = virt_dev; + continue; + } else if (c == WF_INTERNAL_SWITCH) { + input_dev = phys_dev; + continue; + } /* else just leave it as it is */ + + } else { + input_dev = phys_dev; + } + + if (input_dev->mode == MODE_SYNTH) { + + wf_mpu_input_scanner (input_dev->devno, + input_dev->synthno, c); + + } else if (input_dev->opened & OPEN_READ) { + + if (input_dev->inputintr) { + input_dev->inputintr (input_dev->devno, c); + } + } + + } while (input_avail() && n-- > 0); + + mi->m_busy = 0; + spin_unlock(&lock); + return IRQ_HANDLED; +} + +static int +wf_mpu_open (int dev, int mode, + void (*input) (int dev, unsigned char data), + void (*output) (int dev) + ) +{ + struct wf_mpu_config *devc; + + if (dev < 0 || dev >= num_midis || midi_devs[dev]==NULL) + return -(ENXIO); + + if (phys_dev->devno == dev) { + devc = phys_dev; + } else if (phys_dev->isvirtual && virt_dev->devno == dev) { + devc = virt_dev; + } else { + printk (KERN_ERR "WF-MPU: unknown device number %d\n", dev); + return -(EINVAL); + } + + if (devc->opened) { + return -(EBUSY); + } + + devc->mode = MODE_MIDI; + devc->opened = mode; + devc->synthno = 0; + + devc->inputintr = input; + return 0; +} + +static void +wf_mpu_close (int dev) +{ + struct wf_mpu_config *devc; + + if (dev < 0 || dev >= num_midis || midi_devs[dev]==NULL) + return; + + if (phys_dev->devno == dev) { + devc = phys_dev; + } else if (phys_dev->isvirtual && virt_dev->devno == dev) { + devc = virt_dev; + } else { + printk (KERN_ERR "WF-MPU: unknown device number %d\n", dev); + return; + } + + devc->mode = 0; + devc->inputintr = NULL; + devc->opened = 0; +} + +static int +wf_mpu_out (int dev, unsigned char midi_byte) +{ + int timeout; + unsigned long flags; + static int lastoutdev = -1; + unsigned char switchch; + + if (phys_dev->isvirtual && lastoutdev != dev) { + + if (dev == phys_dev->devno) { + switchch = WF_INTERNAL_SWITCH; + } else if (dev == virt_dev->devno) { + switchch = WF_EXTERNAL_SWITCH; + } else { + printk (KERN_ERR "WF-MPU: bad device number %d", dev); + return (0); + } + + /* XXX fix me */ + + for (timeout = 30000; timeout > 0 && !output_ready (); + timeout--); + + spin_lock_irqsave(&lock,flags); + + if (!output_ready ()) { + printk (KERN_WARNING "WF-MPU: Send switch " + "byte timeout\n"); + spin_unlock_irqrestore(&lock,flags); + return 0; + } + + write_data (switchch); + spin_unlock_irqrestore(&lock,flags); + } + + lastoutdev = dev; + + /* + * Sometimes it takes about 30000 loops before the output becomes ready + * (After reset). Normally it takes just about 10 loops. + */ + + /* XXX fix me */ + + for (timeout = 30000; timeout > 0 && !output_ready (); timeout--); + + spin_lock_irqsave(&lock,flags); + if (!output_ready ()) { + spin_unlock_irqrestore(&lock,flags); + printk (KERN_WARNING "WF-MPU: Send data timeout\n"); + return 0; + } + + write_data (midi_byte); + spin_unlock_irqrestore(&lock,flags); + + return 1; +} + +static inline int wf_mpu_start_read (int dev) { + return 0; +} + +static inline int wf_mpu_end_read (int dev) { + return 0; +} + +static int wf_mpu_ioctl (int dev, unsigned cmd, void __user *arg) +{ + printk (KERN_WARNING + "WF-MPU: Intelligent mode not supported by hardware.\n"); + return -(EINVAL); +} + +static int wf_mpu_buffer_status (int dev) +{ + return 0; +} + +static struct synth_operations wf_mpu_synth_operations[2]; +static struct midi_operations wf_mpu_midi_operations[2]; + +static struct midi_operations wf_mpu_midi_proto = +{ + .owner = THIS_MODULE, + .info = {"WF-MPU MIDI", 0, MIDI_CAP_MPU401, SNDCARD_MPU401}, + .in_info = {0}, /* in_info */ + .open = wf_mpu_open, + .close = wf_mpu_close, + .ioctl = wf_mpu_ioctl, + .outputc = wf_mpu_out, + .start_read = wf_mpu_start_read, + .end_read = wf_mpu_end_read, + .buffer_status = wf_mpu_buffer_status, +}; + +static struct synth_info wf_mpu_synth_info_proto = +{"WaveFront MPU-401 interface", 0, + SYNTH_TYPE_MIDI, MIDI_TYPE_MPU401, 0, 128, 0, 128, SYNTH_CAP_INPUT}; + +static struct synth_info wf_mpu_synth_info[2]; + +static int +wf_mpu_synth_ioctl (int dev, unsigned int cmd, void __user *arg) +{ + int midi_dev; + int index; + + midi_dev = synth_devs[dev]->midi_dev; + + if (midi_dev < 0 || midi_dev > num_midis || midi_devs[midi_dev]==NULL) + return -(ENXIO); + + if (midi_dev == phys_dev->devno) { + index = 0; + } else if (phys_dev->isvirtual && midi_dev == virt_dev->devno) { + index = 1; + } else { + return -(EINVAL); + } + + switch (cmd) { + + case SNDCTL_SYNTH_INFO: + if (copy_to_user(arg, + &wf_mpu_synth_info[index], + sizeof (struct synth_info))) + return -EFAULT; + return 0; + + case SNDCTL_SYNTH_MEMAVL: + return 0x7fffffff; + + default: + return -EINVAL; + } +} + +static int +wf_mpu_synth_open (int dev, int mode) +{ + int midi_dev; + struct wf_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); + } + + if (phys_dev->devno == midi_dev) { + devc = phys_dev; + } else if (phys_dev->isvirtual && virt_dev->devno == midi_dev) { + devc = virt_dev; + } else { + printk (KERN_ERR "WF-MPU: unknown device number %d\n", dev); + return -(EINVAL); + } + + if (devc->opened) { + return -(EBUSY); + } + + devc->mode = MODE_SYNTH; + devc->synthno = dev; + devc->opened = mode; + devc->inputintr = NULL; + return 0; +} + +static void +wf_mpu_synth_close (int dev) +{ + int midi_dev; + struct wf_mpu_config *devc; + + midi_dev = synth_devs[dev]->midi_dev; + + if (phys_dev->devno == midi_dev) { + devc = phys_dev; + } else if (phys_dev->isvirtual && virt_dev->devno == midi_dev) { + devc = virt_dev; + } else { + printk (KERN_ERR "WF-MPU: unknown device number %d\n", dev); + return; + } + + devc->inputintr = NULL; + devc->opened = 0; + devc->mode = 0; +} + +#define _MIDI_SYNTH_C_ +#define MIDI_SYNTH_NAME "WaveFront (MIDI)" +#define MIDI_SYNTH_CAPS SYNTH_CAP_INPUT +#include "midi_synth.h" + +static struct synth_operations wf_mpu_synth_proto = +{ + .owner = THIS_MODULE, + .id = "WaveFront (ICS2115)", + .info = NULL, /* info field, filled in during configuration */ + .midi_dev = 0, /* MIDI dev XXX should this be -1 ? */ + .synth_type = SYNTH_TYPE_MIDI, + .synth_subtype = SAMPLE_TYPE_WAVEFRONT, + .open = wf_mpu_synth_open, + .close = wf_mpu_synth_close, + .ioctl = wf_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 int +config_wf_mpu (struct wf_mpu_config *dev) + +{ + int is_external; + char *name; + int index; + + if (dev == phys_dev) { + name = "WaveFront internal MIDI"; + is_external = 0; + index = 0; + memcpy ((char *) &wf_mpu_synth_operations[index], + (char *) &wf_mpu_synth_proto, + sizeof (struct synth_operations)); + } else { + name = "WaveFront external MIDI"; + is_external = 1; + index = 1; + /* no synth operations for an external MIDI interface */ + } + + memcpy ((char *) &wf_mpu_synth_info[dev->devno], + (char *) &wf_mpu_synth_info_proto, + sizeof (struct synth_info)); + + strcpy (wf_mpu_synth_info[index].name, name); + + wf_mpu_synth_operations[index].midi_dev = dev->devno; + wf_mpu_synth_operations[index].info = &wf_mpu_synth_info[index]; + + memcpy ((char *) &wf_mpu_midi_operations[index], + (char *) &wf_mpu_midi_proto, + sizeof (struct midi_operations)); + + if (is_external) { + wf_mpu_midi_operations[index].converter = NULL; + } else { + wf_mpu_midi_operations[index].converter = + &wf_mpu_synth_operations[index]; + } + + strcpy (wf_mpu_midi_operations[index].info.name, name); + + midi_devs[dev->devno] = &wf_mpu_midi_operations[index]; + midi_devs[dev->devno]->in_info.m_busy = 0; + midi_devs[dev->devno]->in_info.m_state = MST_INIT; + midi_devs[dev->devno]->in_info.m_ptr = 0; + midi_devs[dev->devno]->in_info.m_left = 0; + midi_devs[dev->devno]->in_info.m_prev_status = 0; + + devs[index].opened = 0; + devs[index].mode = 0; + + return (0); +} + +int virtual_midi_enable (void) + +{ + if ((virt_dev->devno < 0) && + (virt_dev->devno = sound_alloc_mididev()) == -1) { + printk (KERN_ERR + "WF-MPU: too many midi devices detected\n"); + return -1; + } + + config_wf_mpu (virt_dev); + + phys_dev->isvirtual = 1; + return virt_dev->devno; +} + +int +virtual_midi_disable (void) + +{ + unsigned long flags; + + spin_lock_irqsave(&lock,flags); + + wf_mpu_close (virt_dev->devno); + /* no synth on virt_dev, so no need to call wf_mpu_synth_close() */ + phys_dev->isvirtual = 0; + + spin_unlock_irqrestore(&lock,flags); + + return 0; +} + +int __init detect_wf_mpu (int irq, int io_base) +{ + if (!request_region(io_base, 2, "wavefront midi")) { + printk (KERN_WARNING "WF-MPU: I/O port %x already in use.\n", + io_base); + return -1; + } + + phys_dev->base = io_base; + phys_dev->irq = irq; + phys_dev->devno = -1; + virt_dev->devno = -1; + + return 0; +} + +int __init install_wf_mpu (void) +{ + if ((phys_dev->devno = sound_alloc_mididev()) < 0){ + + printk (KERN_ERR "WF-MPU: Too many MIDI devices detected.\n"); + release_region(phys_dev->base, 2); + return -1; + } + + phys_dev->isvirtual = 0; + + if (config_wf_mpu (phys_dev)) { + + printk (KERN_WARNING + "WF-MPU: configuration for MIDI device %d failed\n", + phys_dev->devno); + sound_unload_mididev (phys_dev->devno); + + } + + /* OK, now we're configured to handle an interrupt ... */ + + if (request_irq (phys_dev->irq, wf_mpuintr, SA_INTERRUPT|SA_SHIRQ, + "wavefront midi", phys_dev) < 0) { + + printk (KERN_ERR "WF-MPU: Failed to allocate IRQ%d\n", + phys_dev->irq); + return -1; + + } + + /* This being a WaveFront (ICS-2115) emulated MPU-401, we have + to switch it into UART (dumb) mode, because otherwise, it + won't do anything at all. + */ + + start_uart_mode (); + + return phys_dev->devno; +} + +void +uninstall_wf_mpu (void) + +{ + release_region (phys_dev->base, 2); + free_irq (phys_dev->irq, phys_dev); + sound_unload_mididev (phys_dev->devno); + + if (virt_dev->devno >= 0) { + sound_unload_mididev (virt_dev->devno); + } +} + +static void +start_uart_mode (void) + +{ + int ok, i; + unsigned long flags; + + spin_lock_irqsave(&lock,flags); + + /* XXX fix me */ + + for (i = 0; i < 30000 && !output_ready (); i++); + + outb (UART_MODE_ON, COMDPORT(phys_dev)); + + for (ok = 0, i = 50000; i > 0 && !ok; i--) { + if (input_avail ()) { + if (read_data () == MPU_ACK) { + ok = 1; + } + } + } + + spin_unlock_irqrestore(&lock,flags); +} +#endif diff --git a/sound/oss/ymfpci.c b/sound/oss/ymfpci.c new file mode 100644 index 000000000000..05203ad523f7 --- /dev/null +++ b/sound/oss/ymfpci.c @@ -0,0 +1,2691 @@ +/* + * Copyright 1999 Jaroslav Kysela + * Copyright 2000 Alan Cox + * Copyright 2001 Kai Germaschewski + * Copyright 2002 Pete Zaitcev + * + * Yamaha YMF7xx driver. + * + * This code is a result of high-speed collision + * between ymfpci.c of ALSA and cs46xx.c of Linux. + * -- Pete Zaitcev ; 2000/09/18 + * + * 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. + * + * TODO: + * - Use P44Slot for 44.1 playback (beware of idle buzzing in P44Slot). + * - 96KHz playback for DVD - use pitch of 2.0. + * - Retain DMA buffer on close, do not wait the end of frame. + * - Resolve XXX tagged questions. + * - Cannot play 5133Hz. + * - 2001/01/07 Consider if we can remove voice_lock, like so: + * : Allocate/deallocate voices in open/close under semafore. + * : We access voices in interrupt, that only for pcms that open. + * voice_lock around playback_prepare closes interrupts for insane duration. + * - Revisit the way voice_alloc is done - too confusing, overcomplicated. + * Should support various channel types, however. + * - Remove prog_dmabuf from read/write, leave it in open. + * - 2001/01/07 Replace the OPL3 part of CONFIG_SOUND_YMFPCI_LEGACY code with + * native synthesizer through a playback slot. + * - 2001/11/29 ac97_save_state + * Talk to Kai to remove ac97_save_state before it's too late! + * - Second AC97 + * - Restore S/PDIF - Toshibas have it. + * + * Kai used pci_alloc_consistent for DMA buffer, which sounds a little + * unconventional. However, given how small our fragments can be, + * a little uncached access is perhaps better than endless flushing. + * On i386 and other I/O-coherent architectures pci_alloc_consistent + * is entirely harmless. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef CONFIG_SOUND_YMFPCI_LEGACY +# include "sound_config.h" +# include "mpu401.h" +#endif +#include "ymfpci.h" + +/* + * I do not believe in debug levels as I never can guess what + * part of the code is going to be problematic in the future. + * Don't forget to run your klogd with -c 8. + * + * Example (do not remove): + * #define YMFDBG(fmt, arg...) do{ printk(KERN_DEBUG fmt, ##arg); }while(0) + */ +#define YMFDBGW(fmt, arg...) /* */ /* write counts */ +#define YMFDBGI(fmt, arg...) /* */ /* interrupts */ +#define YMFDBGX(fmt, arg...) /* */ /* ioctl */ + +static int ymf_playback_trigger(ymfpci_t *unit, struct ymf_pcm *ypcm, int cmd); +static void ymf_capture_trigger(ymfpci_t *unit, struct ymf_pcm *ypcm, int cmd); +static void ymfpci_voice_free(ymfpci_t *unit, ymfpci_voice_t *pvoice); +static int ymf_capture_alloc(struct ymf_unit *unit, int *pbank); +static int ymf_playback_prepare(struct ymf_state *state); +static int ymf_capture_prepare(struct ymf_state *state); +static struct ymf_state *ymf_state_alloc(ymfpci_t *unit); + +static void ymfpci_aclink_reset(struct pci_dev * pci); +static void ymfpci_disable_dsp(ymfpci_t *unit); +static void ymfpci_download_image(ymfpci_t *codec); +static void ymf_memload(ymfpci_t *unit); + +static DEFINE_SPINLOCK(ymf_devs_lock); +static LIST_HEAD(ymf_devs); + +/* + * constants + */ + +static struct pci_device_id ymf_id_tbl[] = { +#define DEV(v, d, data) \ + { PCI_VENDOR_ID_##v, PCI_DEVICE_ID_##v##_##d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long)data } + DEV (YAMAHA, 724, "YMF724"), + DEV (YAMAHA, 724F, "YMF724F"), + DEV (YAMAHA, 740, "YMF740"), + DEV (YAMAHA, 740C, "YMF740C"), + DEV (YAMAHA, 744, "YMF744"), + DEV (YAMAHA, 754, "YMF754"), +#undef DEV + { } +}; +MODULE_DEVICE_TABLE(pci, ymf_id_tbl); + +/* + * common I/O routines + */ + +static inline void ymfpci_writeb(ymfpci_t *codec, u32 offset, u8 val) +{ + writeb(val, codec->reg_area_virt + offset); +} + +static inline u16 ymfpci_readw(ymfpci_t *codec, u32 offset) +{ + return readw(codec->reg_area_virt + offset); +} + +static inline void ymfpci_writew(ymfpci_t *codec, u32 offset, u16 val) +{ + writew(val, codec->reg_area_virt + offset); +} + +static inline u32 ymfpci_readl(ymfpci_t *codec, u32 offset) +{ + return readl(codec->reg_area_virt + offset); +} + +static inline void ymfpci_writel(ymfpci_t *codec, u32 offset, u32 val) +{ + writel(val, codec->reg_area_virt + offset); +} + +static int ymfpci_codec_ready(ymfpci_t *codec, int secondary, int sched) +{ + signed long end_time; + u32 reg = secondary ? YDSXGR_SECSTATUSADR : YDSXGR_PRISTATUSADR; + + end_time = jiffies + 3 * (HZ / 4); + do { + if ((ymfpci_readw(codec, reg) & 0x8000) == 0) + return 0; + if (sched) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } + } while (end_time - (signed long)jiffies >= 0); + printk(KERN_ERR "ymfpci_codec_ready: codec %i is not ready [0x%x]\n", + secondary, ymfpci_readw(codec, reg)); + return -EBUSY; +} + +static void ymfpci_codec_write(struct ac97_codec *dev, u8 reg, u16 val) +{ + ymfpci_t *codec = dev->private_data; + u32 cmd; + + spin_lock(&codec->ac97_lock); + /* XXX Do make use of dev->id */ + ymfpci_codec_ready(codec, 0, 0); + cmd = ((YDSXG_AC97WRITECMD | reg) << 16) | val; + ymfpci_writel(codec, YDSXGR_AC97CMDDATA, cmd); + spin_unlock(&codec->ac97_lock); +} + +static u16 _ymfpci_codec_read(ymfpci_t *unit, u8 reg) +{ + int i; + + if (ymfpci_codec_ready(unit, 0, 0)) + return ~0; + ymfpci_writew(unit, YDSXGR_AC97CMDADR, YDSXG_AC97READCMD | reg); + if (ymfpci_codec_ready(unit, 0, 0)) + return ~0; + if (unit->pci->device == PCI_DEVICE_ID_YAMAHA_744 && unit->rev < 2) { + for (i = 0; i < 600; i++) + ymfpci_readw(unit, YDSXGR_PRISTATUSDATA); + } + return ymfpci_readw(unit, YDSXGR_PRISTATUSDATA); +} + +static u16 ymfpci_codec_read(struct ac97_codec *dev, u8 reg) +{ + ymfpci_t *unit = dev->private_data; + u16 ret; + + spin_lock(&unit->ac97_lock); + ret = _ymfpci_codec_read(unit, reg); + spin_unlock(&unit->ac97_lock); + + return ret; +} + +/* + * Misc routines + */ + +/* + * Calculate the actual sampling rate relatetively to the base clock (48kHz). + */ +static u32 ymfpci_calc_delta(u32 rate) +{ + switch (rate) { + case 8000: return 0x02aaab00; + case 11025: return 0x03accd00; + case 16000: return 0x05555500; + case 22050: return 0x07599a00; + case 32000: return 0x0aaaab00; + case 44100: return 0x0eb33300; + default: return ((rate << 16) / 48000) << 12; + } +} + +static u32 def_rate[8] = { + 100, 2000, 8000, 11025, 16000, 22050, 32000, 48000 +}; + +static u32 ymfpci_calc_lpfK(u32 rate) +{ + u32 i; + static u32 val[8] = { + 0x00570000, 0x06AA0000, 0x18B20000, 0x20930000, + 0x2B9A0000, 0x35A10000, 0x3EAA0000, 0x40000000 + }; + + if (rate == 44100) + return 0x40000000; /* FIXME: What's the right value? */ + for (i = 0; i < 8; i++) + if (rate <= def_rate[i]) + return val[i]; + return val[0]; +} + +static u32 ymfpci_calc_lpfQ(u32 rate) +{ + u32 i; + static u32 val[8] = { + 0x35280000, 0x34A70000, 0x32020000, 0x31770000, + 0x31390000, 0x31C90000, 0x33D00000, 0x40000000 + }; + + if (rate == 44100) + return 0x370A0000; + for (i = 0; i < 8; i++) + if (rate <= def_rate[i]) + return val[i]; + return val[0]; +} + +static u32 ymf_calc_lend(u32 rate) +{ + return (rate * YMF_SAMPF) / 48000; +} + +/* + * We ever allow only a few formats, but let's be generic, for smaller surprise. + */ +static int ymf_pcm_format_width(int format) +{ + static int mask16 = AFMT_S16_LE|AFMT_S16_BE|AFMT_U16_LE|AFMT_U16_BE; + + if ((format & (format-1)) != 0) { + printk(KERN_ERR "ymfpci: format 0x%x is not a power of 2\n", format); + return 8; + } + + if (format == AFMT_IMA_ADPCM) return 4; + if ((format & mask16) != 0) return 16; + return 8; +} + +static void ymf_pcm_update_shift(struct ymf_pcm_format *f) +{ + f->shift = 0; + if (f->voices == 2) + f->shift++; + if (ymf_pcm_format_width(f->format) == 16) + f->shift++; +} + +/* Are you sure 32K is not too much? See if mpg123 skips on loaded systems. */ +#define DMABUF_DEFAULTORDER (15-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +/* + * Allocate DMA buffer + */ +static int alloc_dmabuf(ymfpci_t *unit, struct ymf_dmabuf *dmabuf) +{ + void *rawbuf = NULL; + dma_addr_t dma_addr; + int order; + struct page *map, *mapend; + + /* alloc as big a chunk as we can */ + for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--) { + rawbuf = pci_alloc_consistent(unit->pci, PAGE_SIZE << order, &dma_addr); + if (rawbuf) + break; + } + if (!rawbuf) + return -ENOMEM; + +#if 0 + printk(KERN_DEBUG "ymfpci: allocated %ld (order = %d) bytes at %p\n", + PAGE_SIZE << order, order, rawbuf); +#endif + + dmabuf->ready = dmabuf->mapped = 0; + dmabuf->rawbuf = rawbuf; + dmabuf->dma_addr = dma_addr; + dmabuf->buforder = order; + + /* now mark the pages as reserved; otherwise remap_pfn_range doesn't do what we want */ + mapend = virt_to_page(rawbuf + (PAGE_SIZE << order) - 1); + for (map = virt_to_page(rawbuf); map <= mapend; map++) + set_bit(PG_reserved, &map->flags); + + return 0; +} + +/* + * Free DMA buffer + */ +static void dealloc_dmabuf(ymfpci_t *unit, struct ymf_dmabuf *dmabuf) +{ + struct page *map, *mapend; + + if (dmabuf->rawbuf) { + /* undo marking the pages as reserved */ + mapend = virt_to_page(dmabuf->rawbuf + (PAGE_SIZE << dmabuf->buforder) - 1); + for (map = virt_to_page(dmabuf->rawbuf); map <= mapend; map++) + clear_bit(PG_reserved, &map->flags); + + pci_free_consistent(unit->pci, PAGE_SIZE << dmabuf->buforder, + dmabuf->rawbuf, dmabuf->dma_addr); + } + dmabuf->rawbuf = NULL; + dmabuf->mapped = dmabuf->ready = 0; +} + +static int prog_dmabuf(struct ymf_state *state, int rec) +{ + struct ymf_dmabuf *dmabuf; + int w_16; + unsigned bufsize; + unsigned long flags; + int redzone, redfrags; + int ret; + + w_16 = ymf_pcm_format_width(state->format.format) == 16; + dmabuf = rec ? &state->rpcm.dmabuf : &state->wpcm.dmabuf; + + spin_lock_irqsave(&state->unit->reg_lock, flags); + dmabuf->hwptr = dmabuf->swptr = 0; + dmabuf->total_bytes = 0; + dmabuf->count = 0; + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + + /* allocate DMA buffer if not allocated yet */ + if (!dmabuf->rawbuf) + if ((ret = alloc_dmabuf(state->unit, dmabuf))) + return ret; + + /* + * Create fake fragment sizes and numbers for OSS ioctls. + * Import what Doom might have set with SNDCTL_DSP_SETFRAGMENT. + */ + bufsize = PAGE_SIZE << dmabuf->buforder; + /* By default we give 4 big buffers. */ + dmabuf->fragshift = (dmabuf->buforder + PAGE_SHIFT - 2); + if (dmabuf->ossfragshift > 3 && + dmabuf->ossfragshift < dmabuf->fragshift) { + /* If OSS set smaller fragments, give more smaller buffers. */ + dmabuf->fragshift = dmabuf->ossfragshift; + } + dmabuf->fragsize = 1 << dmabuf->fragshift; + + dmabuf->numfrag = bufsize >> dmabuf->fragshift; + dmabuf->dmasize = dmabuf->numfrag << dmabuf->fragshift; + + if (dmabuf->ossmaxfrags >= 2) { + redzone = ymf_calc_lend(state->format.rate); + redzone <<= state->format.shift; + redzone *= 3; + redfrags = (redzone + dmabuf->fragsize-1) >> dmabuf->fragshift; + + if (dmabuf->ossmaxfrags + redfrags < dmabuf->numfrag) { + dmabuf->numfrag = dmabuf->ossmaxfrags + redfrags; + dmabuf->dmasize = dmabuf->numfrag << dmabuf->fragshift; + } + } + + memset(dmabuf->rawbuf, w_16 ? 0 : 0x80, dmabuf->dmasize); + + /* + * Now set up the ring + */ + + /* XXX ret = rec? cap_pre(): pbk_pre(); */ + spin_lock_irqsave(&state->unit->voice_lock, flags); + if (rec) { + if ((ret = ymf_capture_prepare(state)) != 0) { + spin_unlock_irqrestore(&state->unit->voice_lock, flags); + return ret; + } + } else { + if ((ret = ymf_playback_prepare(state)) != 0) { + spin_unlock_irqrestore(&state->unit->voice_lock, flags); + return ret; + } + } + spin_unlock_irqrestore(&state->unit->voice_lock, flags); + + /* set the ready flag for the dma buffer (this comment is not stupid) */ + dmabuf->ready = 1; + +#if 0 + printk(KERN_DEBUG "prog_dmabuf: rate %d format 0x%x," + " numfrag %d fragsize %d dmasize %d\n", + state->format.rate, state->format.format, dmabuf->numfrag, + dmabuf->fragsize, dmabuf->dmasize); +#endif + + return 0; +} + +static void ymf_start_dac(struct ymf_state *state) +{ + ymf_playback_trigger(state->unit, &state->wpcm, 1); +} + +// static void ymf_start_adc(struct ymf_state *state) +// { +// ymf_capture_trigger(state->unit, &state->rpcm, 1); +// } + +/* + * Wait until output is drained. + * This does not kill the hardware for the sake of ioctls. + */ +static void ymf_wait_dac(struct ymf_state *state) +{ + struct ymf_unit *unit = state->unit; + struct ymf_pcm *ypcm = &state->wpcm; + DECLARE_WAITQUEUE(waita, current); + unsigned long flags; + + add_wait_queue(&ypcm->dmabuf.wait, &waita); + + spin_lock_irqsave(&unit->reg_lock, flags); + if (ypcm->dmabuf.count != 0 && !ypcm->running) { + ymf_playback_trigger(unit, ypcm, 1); + } + +#if 0 + if (file->f_flags & O_NONBLOCK) { + /* + * XXX Our mistake is to attach DMA buffer to state + * rather than to some per-device structure. + * Cannot skip waiting, can only make it shorter. + */ + } +#endif + + set_current_state(TASK_UNINTERRUPTIBLE); + while (ypcm->running) { + spin_unlock_irqrestore(&unit->reg_lock, flags); + schedule(); + spin_lock_irqsave(&unit->reg_lock, flags); + set_current_state(TASK_UNINTERRUPTIBLE); + } + spin_unlock_irqrestore(&unit->reg_lock, flags); + + set_current_state(TASK_RUNNING); + remove_wait_queue(&ypcm->dmabuf.wait, &waita); + + /* + * This function may take up to 4 seconds to reach this point + * (32K circular buffer, 8000 Hz). User notices. + */ +} + +/* Can just stop, without wait. Or can we? */ +static void ymf_stop_adc(struct ymf_state *state) +{ + struct ymf_unit *unit = state->unit; + unsigned long flags; + + spin_lock_irqsave(&unit->reg_lock, flags); + ymf_capture_trigger(unit, &state->rpcm, 0); + spin_unlock_irqrestore(&unit->reg_lock, flags); +} + +/* + * Hardware start management + */ + +static void ymfpci_hw_start(ymfpci_t *unit) +{ + unsigned long flags; + + spin_lock_irqsave(&unit->reg_lock, flags); + if (unit->start_count++ == 0) { + ymfpci_writel(unit, YDSXGR_MODE, + ymfpci_readl(unit, YDSXGR_MODE) | 3); + unit->active_bank = ymfpci_readl(unit, YDSXGR_CTRLSELECT) & 1; + } + spin_unlock_irqrestore(&unit->reg_lock, flags); +} + +static void ymfpci_hw_stop(ymfpci_t *unit) +{ + unsigned long flags; + long timeout = 1000; + + spin_lock_irqsave(&unit->reg_lock, flags); + if (--unit->start_count == 0) { + ymfpci_writel(unit, YDSXGR_MODE, + ymfpci_readl(unit, YDSXGR_MODE) & ~3); + while (timeout-- > 0) { + if ((ymfpci_readl(unit, YDSXGR_STATUS) & 2) == 0) + break; + } + } + spin_unlock_irqrestore(&unit->reg_lock, flags); +} + +/* + * Playback voice management + */ + +static int voice_alloc(ymfpci_t *codec, ymfpci_voice_type_t type, int pair, ymfpci_voice_t *rvoice[]) +{ + ymfpci_voice_t *voice, *voice2; + int idx; + + for (idx = 0; idx < YDSXG_PLAYBACK_VOICES; idx += pair ? 2 : 1) { + voice = &codec->voices[idx]; + voice2 = pair ? &codec->voices[idx+1] : NULL; + if (voice->use || (voice2 && voice2->use)) + continue; + voice->use = 1; + if (voice2) + voice2->use = 1; + switch (type) { + case YMFPCI_PCM: + voice->pcm = 1; + if (voice2) + voice2->pcm = 1; + break; + case YMFPCI_SYNTH: + voice->synth = 1; + break; + case YMFPCI_MIDI: + voice->midi = 1; + break; + } + ymfpci_hw_start(codec); + rvoice[0] = voice; + if (voice2) { + ymfpci_hw_start(codec); + rvoice[1] = voice2; + } + return 0; + } + return -EBUSY; /* Your audio channel is open by someone else. */ +} + +static void ymfpci_voice_free(ymfpci_t *unit, ymfpci_voice_t *pvoice) +{ + ymfpci_hw_stop(unit); + pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0; + pvoice->ypcm = NULL; +} + +/* + */ + +static void ymf_pcm_interrupt(ymfpci_t *codec, ymfpci_voice_t *voice) +{ + struct ymf_pcm *ypcm; + int redzone; + int pos, delta, swptr; + int played, distance; + struct ymf_state *state; + struct ymf_dmabuf *dmabuf; + char silence; + + if ((ypcm = voice->ypcm) == NULL) { + return; + } + if ((state = ypcm->state) == NULL) { + ypcm->running = 0; // lock it + return; + } + dmabuf = &ypcm->dmabuf; + spin_lock(&codec->reg_lock); + if (ypcm->running) { + YMFDBGI("ymfpci: %d, intr bank %d count %d start 0x%x:%x\n", + voice->number, codec->active_bank, dmabuf->count, + le32_to_cpu(voice->bank[0].start), + le32_to_cpu(voice->bank[1].start)); + silence = (ymf_pcm_format_width(state->format.format) == 16) ? + 0 : 0x80; + /* We need actual left-hand-side redzone size here. */ + redzone = ymf_calc_lend(state->format.rate); + redzone <<= (state->format.shift + 1); + swptr = dmabuf->swptr; + + pos = le32_to_cpu(voice->bank[codec->active_bank].start); + pos <<= state->format.shift; + if (pos < 0 || pos >= dmabuf->dmasize) { /* ucode bug */ + printk(KERN_ERR "ymfpci%d: runaway voice %d: hwptr %d=>%d dmasize %d\n", + codec->dev_audio, voice->number, + dmabuf->hwptr, pos, dmabuf->dmasize); + pos = 0; + } + if (pos < dmabuf->hwptr) { + delta = dmabuf->dmasize - dmabuf->hwptr; + memset(dmabuf->rawbuf + dmabuf->hwptr, silence, delta); + delta += pos; + memset(dmabuf->rawbuf, silence, pos); + } else { + delta = pos - dmabuf->hwptr; + memset(dmabuf->rawbuf + dmabuf->hwptr, silence, delta); + } + dmabuf->hwptr = pos; + + if (dmabuf->count == 0) { + printk(KERN_ERR "ymfpci%d: %d: strain: hwptr %d\n", + codec->dev_audio, voice->number, dmabuf->hwptr); + ymf_playback_trigger(codec, ypcm, 0); + } + + if (swptr <= pos) { + distance = pos - swptr; + } else { + distance = dmabuf->dmasize - (swptr - pos); + } + if (distance < redzone) { + /* + * hwptr inside redzone => DMA ran out of samples. + */ + if (delta < dmabuf->count) { + /* + * Lost interrupt or other screwage. + */ + printk(KERN_ERR "ymfpci%d: %d: lost: delta %d" + " hwptr %d swptr %d distance %d count %d\n", + codec->dev_audio, voice->number, delta, + dmabuf->hwptr, swptr, distance, dmabuf->count); + } else { + /* + * Normal end of DMA. + */ + YMFDBGI("ymfpci%d: %d: done: delta %d" + " hwptr %d swptr %d distance %d count %d\n", + codec->dev_audio, voice->number, delta, + dmabuf->hwptr, swptr, distance, dmabuf->count); + } + played = dmabuf->count; + if (ypcm->running) { + ymf_playback_trigger(codec, ypcm, 0); + } + } else { + /* + * hwptr is chipping away towards a remote swptr. + * Calculate other distance and apply it to count. + */ + if (swptr >= pos) { + distance = swptr - pos; + } else { + distance = dmabuf->dmasize - (pos - swptr); + } + if (distance < dmabuf->count) { + played = dmabuf->count - distance; + } else { + played = 0; + } + } + + dmabuf->total_bytes += played; + dmabuf->count -= played; + if (dmabuf->count < dmabuf->dmasize / 2) { + wake_up(&dmabuf->wait); + } + } + spin_unlock(&codec->reg_lock); +} + +static void ymf_cap_interrupt(ymfpci_t *unit, struct ymf_capture *cap) +{ + struct ymf_pcm *ypcm; + int redzone; + struct ymf_state *state; + struct ymf_dmabuf *dmabuf; + int pos, delta; + int cnt; + + if ((ypcm = cap->ypcm) == NULL) { + return; + } + if ((state = ypcm->state) == NULL) { + ypcm->running = 0; // lock it + return; + } + dmabuf = &ypcm->dmabuf; + spin_lock(&unit->reg_lock); + if (ypcm->running) { + redzone = ymf_calc_lend(state->format.rate); + redzone <<= (state->format.shift + 1); + + pos = le32_to_cpu(cap->bank[unit->active_bank].start); + // pos <<= state->format.shift; + if (pos < 0 || pos >= dmabuf->dmasize) { /* ucode bug */ + printk(KERN_ERR "ymfpci%d: runaway capture %d: hwptr %d=>%d dmasize %d\n", + unit->dev_audio, ypcm->capture_bank_number, + dmabuf->hwptr, pos, dmabuf->dmasize); + pos = 0; + } + if (pos < dmabuf->hwptr) { + delta = dmabuf->dmasize - dmabuf->hwptr; + delta += pos; + } else { + delta = pos - dmabuf->hwptr; + } + dmabuf->hwptr = pos; + + cnt = dmabuf->count; + cnt += delta; + if (cnt + redzone > dmabuf->dmasize) { + /* Overflow - bump swptr */ + dmabuf->count = dmabuf->dmasize - redzone; + dmabuf->swptr = dmabuf->hwptr + redzone; + if (dmabuf->swptr >= dmabuf->dmasize) { + dmabuf->swptr -= dmabuf->dmasize; + } + } else { + dmabuf->count = cnt; + } + + dmabuf->total_bytes += delta; + if (dmabuf->count) { /* && is_sleeping XXX */ + wake_up(&dmabuf->wait); + } + } + spin_unlock(&unit->reg_lock); +} + +static int ymf_playback_trigger(ymfpci_t *codec, struct ymf_pcm *ypcm, int cmd) +{ + + if (ypcm->voices[0] == NULL) { + return -EINVAL; + } + if (cmd != 0) { + codec->ctrl_playback[ypcm->voices[0]->number + 1] = + cpu_to_le32(ypcm->voices[0]->bank_ba); + if (ypcm->voices[1] != NULL) + codec->ctrl_playback[ypcm->voices[1]->number + 1] = + cpu_to_le32(ypcm->voices[1]->bank_ba); + ypcm->running = 1; + } else { + codec->ctrl_playback[ypcm->voices[0]->number + 1] = 0; + if (ypcm->voices[1] != NULL) + codec->ctrl_playback[ypcm->voices[1]->number + 1] = 0; + ypcm->running = 0; + } + return 0; +} + +static void ymf_capture_trigger(ymfpci_t *codec, struct ymf_pcm *ypcm, int cmd) +{ + u32 tmp; + + if (cmd != 0) { + tmp = ymfpci_readl(codec, YDSXGR_MAPOFREC) | (1 << ypcm->capture_bank_number); + ymfpci_writel(codec, YDSXGR_MAPOFREC, tmp); + ypcm->running = 1; + } else { + tmp = ymfpci_readl(codec, YDSXGR_MAPOFREC) & ~(1 << ypcm->capture_bank_number); + ymfpci_writel(codec, YDSXGR_MAPOFREC, tmp); + ypcm->running = 0; + } +} + +static int ymfpci_pcm_voice_alloc(struct ymf_pcm *ypcm, int voices) +{ + struct ymf_unit *unit; + int err; + + unit = ypcm->state->unit; + if (ypcm->voices[1] != NULL && voices < 2) { + ymfpci_voice_free(unit, ypcm->voices[1]); + ypcm->voices[1] = NULL; + } + if (voices == 1 && ypcm->voices[0] != NULL) + return 0; /* already allocated */ + if (voices == 2 && ypcm->voices[0] != NULL && ypcm->voices[1] != NULL) + return 0; /* already allocated */ + if (voices > 1) { + if (ypcm->voices[0] != NULL && ypcm->voices[1] == NULL) { + ymfpci_voice_free(unit, ypcm->voices[0]); + ypcm->voices[0] = NULL; + } + if ((err = voice_alloc(unit, YMFPCI_PCM, 1, ypcm->voices)) < 0) + return err; + ypcm->voices[0]->ypcm = ypcm; + ypcm->voices[1]->ypcm = ypcm; + } else { + if ((err = voice_alloc(unit, YMFPCI_PCM, 0, ypcm->voices)) < 0) + return err; + ypcm->voices[0]->ypcm = ypcm; + } + return 0; +} + +static void ymf_pcm_init_voice(ymfpci_voice_t *voice, int stereo, + int rate, int w_16, unsigned long addr, unsigned int end, int spdif) +{ + u32 format; + u32 delta = ymfpci_calc_delta(rate); + u32 lpfQ = ymfpci_calc_lpfQ(rate); + u32 lpfK = ymfpci_calc_lpfK(rate); + ymfpci_playback_bank_t *bank; + int nbank; + + /* + * The gain is a floating point number. According to the manual, + * bit 31 indicates a sign bit, bit 30 indicates an integer part, + * and bits [29:15] indicate a decimal fraction part. Thus, + * for a gain of 1.0 the constant of 0x40000000 is loaded. + */ + unsigned default_gain = cpu_to_le32(0x40000000); + + format = (stereo ? 0x00010000 : 0) | (w_16 ? 0 : 0x80000000); + if (stereo) + end >>= 1; + if (w_16) + end >>= 1; + for (nbank = 0; nbank < 2; nbank++) { + bank = &voice->bank[nbank]; + bank->format = cpu_to_le32(format); + bank->loop_default = 0; /* 0-loops forever, otherwise count */ + bank->base = cpu_to_le32(addr); + bank->loop_start = 0; + bank->loop_end = cpu_to_le32(end); + bank->loop_frac = 0; + bank->eg_gain_end = default_gain; + bank->lpfQ = cpu_to_le32(lpfQ); + bank->status = 0; + bank->num_of_frames = 0; + bank->loop_count = 0; + bank->start = 0; + bank->start_frac = 0; + bank->delta = + bank->delta_end = cpu_to_le32(delta); + bank->lpfK = + bank->lpfK_end = cpu_to_le32(lpfK); + bank->eg_gain = default_gain; + bank->lpfD1 = + bank->lpfD2 = 0; + + bank->left_gain = + bank->right_gain = + bank->left_gain_end = + bank->right_gain_end = + bank->eff1_gain = + bank->eff2_gain = + bank->eff3_gain = + bank->eff1_gain_end = + bank->eff2_gain_end = + bank->eff3_gain_end = 0; + + if (!stereo) { + if (!spdif) { + bank->left_gain = + bank->right_gain = + bank->left_gain_end = + bank->right_gain_end = default_gain; + } else { + bank->eff2_gain = + bank->eff2_gain_end = + bank->eff3_gain = + bank->eff3_gain_end = default_gain; + } + } else { + if (!spdif) { + if ((voice->number & 1) == 0) { + bank->left_gain = + bank->left_gain_end = default_gain; + } else { + bank->format |= cpu_to_le32(1); + bank->right_gain = + bank->right_gain_end = default_gain; + } + } else { + if ((voice->number & 1) == 0) { + bank->eff2_gain = + bank->eff2_gain_end = default_gain; + } else { + bank->format |= cpu_to_le32(1); + bank->eff3_gain = + bank->eff3_gain_end = default_gain; + } + } + } + } +} + +/* + * XXX Capture channel allocation is entirely fake at the moment. + * We use only one channel and mark it busy as required. + */ +static int ymf_capture_alloc(struct ymf_unit *unit, int *pbank) +{ + struct ymf_capture *cap; + int cbank; + + cbank = 1; /* Only ADC slot is used for now. */ + cap = &unit->capture[cbank]; + if (cap->use) + return -EBUSY; + cap->use = 1; + *pbank = cbank; + return 0; +} + +static int ymf_playback_prepare(struct ymf_state *state) +{ + struct ymf_pcm *ypcm = &state->wpcm; + int err, nvoice; + + if ((err = ymfpci_pcm_voice_alloc(ypcm, state->format.voices)) < 0) { + /* Somebody started 32 mpg123's in parallel? */ + printk(KERN_INFO "ymfpci%d: cannot allocate voice\n", + state->unit->dev_audio); + return err; + } + + for (nvoice = 0; nvoice < state->format.voices; nvoice++) { + ymf_pcm_init_voice(ypcm->voices[nvoice], + state->format.voices == 2, state->format.rate, + ymf_pcm_format_width(state->format.format) == 16, + ypcm->dmabuf.dma_addr, ypcm->dmabuf.dmasize, + ypcm->spdif); + } + return 0; +} + +static int ymf_capture_prepare(struct ymf_state *state) +{ + ymfpci_t *unit = state->unit; + struct ymf_pcm *ypcm = &state->rpcm; + ymfpci_capture_bank_t * bank; + /* XXX This is confusing, gotta rename one of them banks... */ + int nbank; /* flip-flop bank */ + int cbank; /* input [super-]bank */ + struct ymf_capture *cap; + u32 rate, format; + + if (ypcm->capture_bank_number == -1) { + if (ymf_capture_alloc(unit, &cbank) != 0) + return -EBUSY; + + ypcm->capture_bank_number = cbank; + + cap = &unit->capture[cbank]; + cap->bank = unit->bank_capture[cbank][0]; + cap->ypcm = ypcm; + ymfpci_hw_start(unit); + } + + // ypcm->frag_size = snd_pcm_lib_transfer_fragment(substream); + // frag_size is replaced with nonfragged byte-aligned rolling buffer + rate = ((48000 * 4096) / state->format.rate) - 1; + format = 0; + if (state->format.voices == 2) + format |= 2; + if (ymf_pcm_format_width(state->format.format) == 8) + format |= 1; + switch (ypcm->capture_bank_number) { + case 0: + ymfpci_writel(unit, YDSXGR_RECFORMAT, format); + ymfpci_writel(unit, YDSXGR_RECSLOTSR, rate); + break; + case 1: + ymfpci_writel(unit, YDSXGR_ADCFORMAT, format); + ymfpci_writel(unit, YDSXGR_ADCSLOTSR, rate); + break; + } + for (nbank = 0; nbank < 2; nbank++) { + bank = unit->bank_capture[ypcm->capture_bank_number][nbank]; + bank->base = cpu_to_le32(ypcm->dmabuf.dma_addr); + // bank->loop_end = ypcm->dmabuf.dmasize >> state->format.shift; + bank->loop_end = cpu_to_le32(ypcm->dmabuf.dmasize); + bank->start = 0; + bank->num_of_loops = 0; + } +#if 0 /* s/pdif */ + if (state->digital.dig_valid) + /*state->digital.type == SND_PCM_DIG_AES_IEC958*/ + ymfpci_writew(codec, YDSXGR_SPDIFOUTSTATUS, + state->digital.dig_status[0] | (state->digital.dig_status[1] << 8)); +#endif + return 0; +} + +static irqreturn_t ymf_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + ymfpci_t *codec = dev_id; + u32 status, nvoice, mode; + struct ymf_voice *voice; + struct ymf_capture *cap; + + status = ymfpci_readl(codec, YDSXGR_STATUS); + if (status & 0x80000000) { + codec->active_bank = ymfpci_readl(codec, YDSXGR_CTRLSELECT) & 1; + spin_lock(&codec->voice_lock); + for (nvoice = 0; nvoice < YDSXG_PLAYBACK_VOICES; nvoice++) { + voice = &codec->voices[nvoice]; + if (voice->use) + ymf_pcm_interrupt(codec, voice); + } + for (nvoice = 0; nvoice < YDSXG_CAPTURE_VOICES; nvoice++) { + cap = &codec->capture[nvoice]; + if (cap->use) + ymf_cap_interrupt(codec, cap); + } + spin_unlock(&codec->voice_lock); + spin_lock(&codec->reg_lock); + ymfpci_writel(codec, YDSXGR_STATUS, 0x80000000); + mode = ymfpci_readl(codec, YDSXGR_MODE) | 2; + ymfpci_writel(codec, YDSXGR_MODE, mode); + spin_unlock(&codec->reg_lock); + } + + status = ymfpci_readl(codec, YDSXGR_INTFLAG); + if (status & 1) { + /* timer handler */ + ymfpci_writel(codec, YDSXGR_INTFLAG, ~0); + } + return IRQ_HANDLED; +} + +static void ymf_pcm_free_substream(struct ymf_pcm *ypcm) +{ + unsigned long flags; + struct ymf_unit *unit; + + unit = ypcm->state->unit; + + if (ypcm->type == PLAYBACK_VOICE) { + spin_lock_irqsave(&unit->voice_lock, flags); + if (ypcm->voices[1]) + ymfpci_voice_free(unit, ypcm->voices[1]); + if (ypcm->voices[0]) + ymfpci_voice_free(unit, ypcm->voices[0]); + spin_unlock_irqrestore(&unit->voice_lock, flags); + } else { + if (ypcm->capture_bank_number != -1) { + unit->capture[ypcm->capture_bank_number].use = 0; + ypcm->capture_bank_number = -1; + ymfpci_hw_stop(unit); + } + } +} + +static struct ymf_state *ymf_state_alloc(ymfpci_t *unit) +{ + struct ymf_pcm *ypcm; + struct ymf_state *state; + + if ((state = kmalloc(sizeof(struct ymf_state), GFP_KERNEL)) == NULL) { + goto out0; + } + memset(state, 0, sizeof(struct ymf_state)); + + ypcm = &state->wpcm; + ypcm->state = state; + ypcm->type = PLAYBACK_VOICE; + ypcm->capture_bank_number = -1; + init_waitqueue_head(&ypcm->dmabuf.wait); + + ypcm = &state->rpcm; + ypcm->state = state; + ypcm->type = CAPTURE_AC97; + ypcm->capture_bank_number = -1; + init_waitqueue_head(&ypcm->dmabuf.wait); + + state->unit = unit; + + state->format.format = AFMT_U8; + state->format.rate = 8000; + state->format.voices = 1; + ymf_pcm_update_shift(&state->format); + + return state; + +out0: + return NULL; +} + +/* AES/IEC958 channel status bits */ +#define SND_PCM_AES0_PROFESSIONAL (1<<0) /* 0 = consumer, 1 = professional */ +#define SND_PCM_AES0_NONAUDIO (1<<1) /* 0 = audio, 1 = non-audio */ +#define SND_PCM_AES0_PRO_EMPHASIS (7<<2) /* mask - emphasis */ +#define SND_PCM_AES0_PRO_EMPHASIS_NOTID (0<<2) /* emphasis not indicated */ +#define SND_PCM_AES0_PRO_EMPHASIS_NONE (1<<2) /* none emphasis */ +#define SND_PCM_AES0_PRO_EMPHASIS_5015 (3<<2) /* 50/15us emphasis */ +#define SND_PCM_AES0_PRO_EMPHASIS_CCITT (7<<2) /* CCITT J.17 emphasis */ +#define SND_PCM_AES0_PRO_FREQ_UNLOCKED (1<<5) /* source sample frequency: 0 = locked, 1 = unlocked */ +#define SND_PCM_AES0_PRO_FS (3<<6) /* mask - sample frequency */ +#define SND_PCM_AES0_PRO_FS_NOTID (0<<6) /* fs not indicated */ +#define SND_PCM_AES0_PRO_FS_44100 (1<<6) /* 44.1kHz */ +#define SND_PCM_AES0_PRO_FS_48000 (2<<6) /* 48kHz */ +#define SND_PCM_AES0_PRO_FS_32000 (3<<6) /* 32kHz */ +#define SND_PCM_AES0_CON_NOT_COPYRIGHT (1<<2) /* 0 = copyright, 1 = not copyright */ +#define SND_PCM_AES0_CON_EMPHASIS (7<<3) /* mask - emphasis */ +#define SND_PCM_AES0_CON_EMPHASIS_NONE (0<<3) /* none emphasis */ +#define SND_PCM_AES0_CON_EMPHASIS_5015 (1<<3) /* 50/15us emphasis */ +#define SND_PCM_AES0_CON_MODE (3<<6) /* mask - mode */ +#define SND_PCM_AES1_PRO_MODE (15<<0) /* mask - channel mode */ +#define SND_PCM_AES1_PRO_MODE_NOTID (0<<0) /* not indicated */ +#define SND_PCM_AES1_PRO_MODE_STEREOPHONIC (2<<0) /* stereophonic - ch A is left */ +#define SND_PCM_AES1_PRO_MODE_SINGLE (4<<0) /* single channel */ +#define SND_PCM_AES1_PRO_MODE_TWO (8<<0) /* two channels */ +#define SND_PCM_AES1_PRO_MODE_PRIMARY (12<<0) /* primary/secondary */ +#define SND_PCM_AES1_PRO_MODE_BYTE3 (15<<0) /* vector to byte 3 */ +#define SND_PCM_AES1_PRO_USERBITS (15<<4) /* mask - user bits */ +#define SND_PCM_AES1_PRO_USERBITS_NOTID (0<<4) /* not indicated */ +#define SND_PCM_AES1_PRO_USERBITS_192 (8<<4) /* 192-bit structure */ +#define SND_PCM_AES1_PRO_USERBITS_UDEF (12<<4) /* user defined application */ +#define SND_PCM_AES1_CON_CATEGORY 0x7f +#define SND_PCM_AES1_CON_GENERAL 0x00 +#define SND_PCM_AES1_CON_EXPERIMENTAL 0x40 +#define SND_PCM_AES1_CON_SOLIDMEM_MASK 0x0f +#define SND_PCM_AES1_CON_SOLIDMEM_ID 0x08 +#define SND_PCM_AES1_CON_BROADCAST1_MASK 0x07 +#define SND_PCM_AES1_CON_BROADCAST1_ID 0x04 +#define SND_PCM_AES1_CON_DIGDIGCONV_MASK 0x07 +#define SND_PCM_AES1_CON_DIGDIGCONV_ID 0x02 +#define SND_PCM_AES1_CON_ADC_COPYRIGHT_MASK 0x1f +#define SND_PCM_AES1_CON_ADC_COPYRIGHT_ID 0x06 +#define SND_PCM_AES1_CON_ADC_MASK 0x1f +#define SND_PCM_AES1_CON_ADC_ID 0x16 +#define SND_PCM_AES1_CON_BROADCAST2_MASK 0x0f +#define SND_PCM_AES1_CON_BROADCAST2_ID 0x0e +#define SND_PCM_AES1_CON_LASEROPT_MASK 0x07 +#define SND_PCM_AES1_CON_LASEROPT_ID 0x01 +#define SND_PCM_AES1_CON_MUSICAL_MASK 0x07 +#define SND_PCM_AES1_CON_MUSICAL_ID 0x05 +#define SND_PCM_AES1_CON_MAGNETIC_MASK 0x07 +#define SND_PCM_AES1_CON_MAGNETIC_ID 0x03 +#define SND_PCM_AES1_CON_IEC908_CD (SND_PCM_AES1_CON_LASEROPT_ID|0x00) +#define SND_PCM_AES1_CON_NON_IEC908_CD (SND_PCM_AES1_CON_LASEROPT_ID|0x08) +#define SND_PCM_AES1_CON_PCM_CODER (SND_PCM_AES1_CON_DIGDIGCONV_ID|0x00) +#define SND_PCM_AES1_CON_SAMPLER (SND_PCM_AES1_CON_DIGDIGCONV_ID|0x20) +#define SND_PCM_AES1_CON_MIXER (SND_PCM_AES1_CON_DIGDIGCONV_ID|0x10) +#define SND_PCM_AES1_CON_RATE_CONVERTER (SND_PCM_AES1_CON_DIGDIGCONV_ID|0x18) +#define SND_PCM_AES1_CON_SYNTHESIZER (SND_PCM_AES1_CON_MUSICAL_ID|0x00) +#define SND_PCM_AES1_CON_MICROPHONE (SND_PCM_AES1_CON_MUSICAL_ID|0x08) +#define SND_PCM_AES1_CON_DAT (SND_PCM_AES1_CON_MAGNETIC_ID|0x00) +#define SND_PCM_AES1_CON_VCR (SND_PCM_AES1_CON_MAGNETIC_ID|0x08) +#define SND_PCM_AES1_CON_ORIGINAL (1<<7) /* this bits depends on the category code */ +#define SND_PCM_AES2_PRO_SBITS (7<<0) /* mask - sample bits */ +#define SND_PCM_AES2_PRO_SBITS_20 (2<<0) /* 20-bit - coordination */ +#define SND_PCM_AES2_PRO_SBITS_24 (4<<0) /* 24-bit - main audio */ +#define SND_PCM_AES2_PRO_SBITS_UDEF (6<<0) /* user defined application */ +#define SND_PCM_AES2_PRO_WORDLEN (7<<3) /* mask - source word length */ +#define SND_PCM_AES2_PRO_WORDLEN_NOTID (0<<3) /* not indicated */ +#define SND_PCM_AES2_PRO_WORDLEN_22_18 (2<<3) /* 22-bit or 18-bit */ +#define SND_PCM_AES2_PRO_WORDLEN_23_19 (4<<3) /* 23-bit or 19-bit */ +#define SND_PCM_AES2_PRO_WORDLEN_24_20 (5<<3) /* 24-bit or 20-bit */ +#define SND_PCM_AES2_PRO_WORDLEN_20_16 (6<<3) /* 20-bit or 16-bit */ +#define SND_PCM_AES2_CON_SOURCE (15<<0) /* mask - source number */ +#define SND_PCM_AES2_CON_SOURCE_UNSPEC (0<<0) /* unspecified */ +#define SND_PCM_AES2_CON_CHANNEL (15<<4) /* mask - channel number */ +#define SND_PCM_AES2_CON_CHANNEL_UNSPEC (0<<4) /* unspecified */ +#define SND_PCM_AES3_CON_FS (15<<0) /* mask - sample frequency */ +#define SND_PCM_AES3_CON_FS_44100 (0<<0) /* 44.1kHz */ +#define SND_PCM_AES3_CON_FS_48000 (2<<0) /* 48kHz */ +#define SND_PCM_AES3_CON_FS_32000 (3<<0) /* 32kHz */ +#define SND_PCM_AES3_CON_CLOCK (3<<4) /* mask - clock accuracy */ +#define SND_PCM_AES3_CON_CLOCK_1000PPM (0<<4) /* 1000 ppm */ +#define SND_PCM_AES3_CON_CLOCK_50PPM (1<<4) /* 50 ppm */ +#define SND_PCM_AES3_CON_CLOCK_VARIABLE (2<<4) /* variable pitch */ + +/* + * User interface + */ + +/* + * in this loop, dmabuf.count signifies the amount of data that is + * waiting to be copied to the user's buffer. it is filled by the dma + * machine and drained by this loop. + */ +static ssize_t +ymf_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct ymf_state *state = (struct ymf_state *)file->private_data; + struct ymf_dmabuf *dmabuf = &state->rpcm.dmabuf; + struct ymf_unit *unit = state->unit; + DECLARE_WAITQUEUE(waita, current); + ssize_t ret; + unsigned long flags; + unsigned int swptr; + int cnt; /* This many to go in this revolution */ + + if (dmabuf->mapped) + return -ENXIO; + if (!dmabuf->ready && (ret = prog_dmabuf(state, 1))) + return ret; + ret = 0; + + add_wait_queue(&dmabuf->wait, &waita); + set_current_state(TASK_INTERRUPTIBLE); + while (count > 0) { + spin_lock_irqsave(&unit->reg_lock, flags); + if (unit->suspended) { + spin_unlock_irqrestore(&unit->reg_lock, flags); + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + if (!ret) ret = -EAGAIN; + break; + } + continue; + } + swptr = dmabuf->swptr; + cnt = dmabuf->dmasize - swptr; + if (dmabuf->count < cnt) + cnt = dmabuf->count; + spin_unlock_irqrestore(&unit->reg_lock, flags); + + if (cnt > count) + cnt = count; + if (cnt <= 0) { + unsigned long tmo; + /* buffer is empty, start the dma machine and wait for data to be + recorded */ + spin_lock_irqsave(&state->unit->reg_lock, flags); + if (!state->rpcm.running) { + ymf_capture_trigger(state->unit, &state->rpcm, 1); + } + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + if (file->f_flags & O_NONBLOCK) { + if (!ret) ret = -EAGAIN; + break; + } + /* This isnt strictly right for the 810 but it'll do */ + tmo = (dmabuf->dmasize * HZ) / (state->format.rate * 2); + tmo >>= state->format.shift; + /* There are two situations when sleep_on_timeout returns, one is when + the interrupt is serviced correctly and the process is waked up by + ISR ON TIME. Another is when timeout is expired, which means that + either interrupt is NOT serviced correctly (pending interrupt) or it + is TOO LATE for the process to be scheduled to run (scheduler latency) + which results in a (potential) buffer overrun. And worse, there is + NOTHING we can do to prevent it. */ + tmo = schedule_timeout(tmo); + spin_lock_irqsave(&state->unit->reg_lock, flags); + set_current_state(TASK_INTERRUPTIBLE); + if (tmo == 0 && dmabuf->count == 0) { + printk(KERN_ERR "ymfpci%d: recording schedule timeout, " + "dmasz %u fragsz %u count %i hwptr %u swptr %u\n", + state->unit->dev_audio, + dmabuf->dmasize, dmabuf->fragsize, dmabuf->count, + dmabuf->hwptr, dmabuf->swptr); + } + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + if (signal_pending(current)) { + if (!ret) ret = -ERESTARTSYS; + break; + } + continue; + } + + if (copy_to_user(buffer, dmabuf->rawbuf + swptr, cnt)) { + if (!ret) ret = -EFAULT; + break; + } + + swptr = (swptr + cnt) % dmabuf->dmasize; + + spin_lock_irqsave(&unit->reg_lock, flags); + if (unit->suspended) { + spin_unlock_irqrestore(&unit->reg_lock, flags); + continue; + } + + dmabuf->swptr = swptr; + dmabuf->count -= cnt; + // spin_unlock_irqrestore(&unit->reg_lock, flags); + + count -= cnt; + buffer += cnt; + ret += cnt; + // spin_lock_irqsave(&unit->reg_lock, flags); + if (!state->rpcm.running) { + ymf_capture_trigger(unit, &state->rpcm, 1); + } + spin_unlock_irqrestore(&unit->reg_lock, flags); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&dmabuf->wait, &waita); + + return ret; +} + +static ssize_t +ymf_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct ymf_state *state = (struct ymf_state *)file->private_data; + struct ymf_dmabuf *dmabuf = &state->wpcm.dmabuf; + struct ymf_unit *unit = state->unit; + DECLARE_WAITQUEUE(waita, current); + ssize_t ret; + unsigned long flags; + unsigned int swptr; + int cnt; /* This many to go in this revolution */ + int redzone; + int delay; + + YMFDBGW("ymf_write: count %d\n", count); + + if (dmabuf->mapped) + return -ENXIO; + if (!dmabuf->ready && (ret = prog_dmabuf(state, 0))) + return ret; + ret = 0; + + /* + * Alan's cs46xx works without a red zone - marvel of ingenuity. + * We are not so brilliant... Red zone does two things: + * 1. allows for safe start after a pause as we have no way + * to know what the actual, relentlessly advancing, hwptr is. + * 2. makes computations in ymf_pcm_interrupt simpler. + */ + redzone = ymf_calc_lend(state->format.rate) << state->format.shift; + redzone *= 3; /* 2 redzone + 1 possible uncertainty reserve. */ + + add_wait_queue(&dmabuf->wait, &waita); + set_current_state(TASK_INTERRUPTIBLE); + while (count > 0) { + spin_lock_irqsave(&unit->reg_lock, flags); + if (unit->suspended) { + spin_unlock_irqrestore(&unit->reg_lock, flags); + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + if (!ret) ret = -EAGAIN; + break; + } + continue; + } + if (dmabuf->count < 0) { + printk(KERN_ERR + "ymf_write: count %d, was legal in cs46xx\n", + dmabuf->count); + dmabuf->count = 0; + } + if (dmabuf->count == 0) { + swptr = dmabuf->hwptr; + if (state->wpcm.running) { + /* + * Add uncertainty reserve. + */ + cnt = ymf_calc_lend(state->format.rate); + cnt <<= state->format.shift; + if ((swptr += cnt) >= dmabuf->dmasize) { + swptr -= dmabuf->dmasize; + } + } + dmabuf->swptr = swptr; + } else { + /* + * XXX This is not right if dmabuf->count is small - + * about 2*x frame size or less. We cannot count on + * on appending and not causing an artefact. + * Should use a variation of the count==0 case above. + */ + swptr = dmabuf->swptr; + } + cnt = dmabuf->dmasize - swptr; + if (dmabuf->count + cnt > dmabuf->dmasize - redzone) + cnt = (dmabuf->dmasize - redzone) - dmabuf->count; + spin_unlock_irqrestore(&unit->reg_lock, flags); + + if (cnt > count) + cnt = count; + if (cnt <= 0) { + YMFDBGW("ymf_write: full, count %d swptr %d\n", + dmabuf->count, dmabuf->swptr); + /* + * buffer is full, start the dma machine and + * wait for data to be played + */ + spin_lock_irqsave(&unit->reg_lock, flags); + if (!state->wpcm.running) { + ymf_playback_trigger(unit, &state->wpcm, 1); + } + spin_unlock_irqrestore(&unit->reg_lock, flags); + if (file->f_flags & O_NONBLOCK) { + if (!ret) ret = -EAGAIN; + break; + } + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + if (!ret) ret = -ERESTARTSYS; + break; + } + continue; + } + if (copy_from_user(dmabuf->rawbuf + swptr, buffer, cnt)) { + if (!ret) ret = -EFAULT; + break; + } + + if ((swptr += cnt) >= dmabuf->dmasize) { + swptr -= dmabuf->dmasize; + } + + spin_lock_irqsave(&unit->reg_lock, flags); + if (unit->suspended) { + spin_unlock_irqrestore(&unit->reg_lock, flags); + continue; + } + dmabuf->swptr = swptr; + dmabuf->count += cnt; + + /* + * Start here is a bad idea - may cause startup click + * in /bin/play when dmabuf is not full yet. + * However, some broken applications do not make + * any use of SNDCTL_DSP_SYNC (Doom is the worst). + * One frame is about 5.3ms, Doom write size is 46ms. + */ + delay = state->format.rate / 20; /* 50ms */ + delay <<= state->format.shift; + if (dmabuf->count >= delay && !state->wpcm.running) { + ymf_playback_trigger(unit, &state->wpcm, 1); + } + + spin_unlock_irqrestore(&unit->reg_lock, flags); + + count -= cnt; + buffer += cnt; + ret += cnt; + } + + set_current_state(TASK_RUNNING); + remove_wait_queue(&dmabuf->wait, &waita); + + YMFDBGW("ymf_write: ret %d dmabuf.count %d\n", ret, dmabuf->count); + return ret; +} + +static unsigned int ymf_poll(struct file *file, struct poll_table_struct *wait) +{ + struct ymf_state *state = (struct ymf_state *)file->private_data; + struct ymf_dmabuf *dmabuf; + int redzone; + unsigned long flags; + unsigned int mask = 0; + + if (file->f_mode & FMODE_WRITE) + poll_wait(file, &state->wpcm.dmabuf.wait, wait); + if (file->f_mode & FMODE_READ) + poll_wait(file, &state->rpcm.dmabuf.wait, wait); + + spin_lock_irqsave(&state->unit->reg_lock, flags); + if (file->f_mode & FMODE_READ) { + dmabuf = &state->rpcm.dmabuf; + if (dmabuf->count >= (signed)dmabuf->fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + redzone = ymf_calc_lend(state->format.rate); + redzone <<= state->format.shift; + redzone *= 3; + + dmabuf = &state->wpcm.dmabuf; + if (dmabuf->mapped) { + if (dmabuf->count >= (signed)dmabuf->fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + /* + * Don't select unless a full fragment is available. + * Otherwise artsd does GETOSPACE, sees 0, and loops. + */ + if (dmabuf->count + redzone + dmabuf->fragsize + <= dmabuf->dmasize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + + return mask; +} + +static int ymf_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct ymf_state *state = (struct ymf_state *)file->private_data; + struct ymf_dmabuf *dmabuf = &state->wpcm.dmabuf; + int ret; + unsigned long size; + + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf(state, 0)) != 0) + return ret; + } else if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf(state, 1)) != 0) + return ret; + } else + return -EINVAL; + + if (vma->vm_pgoff != 0) + return -EINVAL; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << dmabuf->buforder)) + return -EINVAL; + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(dmabuf->rawbuf) >> PAGE_SHIFT, + size, vma->vm_page_prot)) + return -EAGAIN; + dmabuf->mapped = 1; + +/* P3 */ printk(KERN_INFO "ymfpci: using memory mapped sound, untested!\n"); + return 0; +} + +static int ymf_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct ymf_state *state = (struct ymf_state *)file->private_data; + struct ymf_dmabuf *dmabuf; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int redzone; + int val; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + switch (cmd) { + case OSS_GETVERSION: + YMFDBGX("ymf_ioctl: cmd 0x%x(GETVER) arg 0x%lx\n", cmd, arg); + return put_user(SOUND_VERSION, p); + + case SNDCTL_DSP_RESET: + YMFDBGX("ymf_ioctl: cmd 0x%x(RESET)\n", cmd); + if (file->f_mode & FMODE_WRITE) { + ymf_wait_dac(state); + dmabuf = &state->wpcm.dmabuf; + spin_lock_irqsave(&state->unit->reg_lock, flags); + dmabuf->ready = 0; + dmabuf->swptr = dmabuf->hwptr; + dmabuf->count = dmabuf->total_bytes = 0; + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + } + if (file->f_mode & FMODE_READ) { + ymf_stop_adc(state); + dmabuf = &state->rpcm.dmabuf; + spin_lock_irqsave(&state->unit->reg_lock, flags); + dmabuf->ready = 0; + dmabuf->swptr = dmabuf->hwptr; + dmabuf->count = dmabuf->total_bytes = 0; + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + } + return 0; + + case SNDCTL_DSP_SYNC: + YMFDBGX("ymf_ioctl: cmd 0x%x(SYNC)\n", cmd); + if (file->f_mode & FMODE_WRITE) { + dmabuf = &state->wpcm.dmabuf; + if (file->f_flags & O_NONBLOCK) { + spin_lock_irqsave(&state->unit->reg_lock, flags); + if (dmabuf->count != 0 && !state->wpcm.running) { + ymf_start_dac(state); + } + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + } else { + ymf_wait_dac(state); + } + } + /* XXX What does this do for reading? dmabuf->count=0; ? */ + return 0; + + case SNDCTL_DSP_SPEED: /* set smaple rate */ + if (get_user(val, p)) + return -EFAULT; + YMFDBGX("ymf_ioctl: cmd 0x%x(SPEED) sp %d\n", cmd, val); + if (val >= 8000 && val <= 48000) { + if (file->f_mode & FMODE_WRITE) { + ymf_wait_dac(state); + dmabuf = &state->wpcm.dmabuf; + spin_lock_irqsave(&state->unit->reg_lock, flags); + dmabuf->ready = 0; + state->format.rate = val; + ymf_pcm_update_shift(&state->format); + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + } + if (file->f_mode & FMODE_READ) { + ymf_stop_adc(state); + dmabuf = &state->rpcm.dmabuf; + spin_lock_irqsave(&state->unit->reg_lock, flags); + dmabuf->ready = 0; + state->format.rate = val; + ymf_pcm_update_shift(&state->format); + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + } + } + return put_user(state->format.rate, p); + + /* + * OSS manual does not mention SNDCTL_DSP_STEREO at all. + * All channels are mono and if you want stereo, you + * play into two channels with SNDCTL_DSP_CHANNELS. + * However, mpg123 calls it. I wonder, why Michael Hipp used it. + */ + case SNDCTL_DSP_STEREO: /* set stereo or mono channel */ + if (get_user(val, p)) + return -EFAULT; + YMFDBGX("ymf_ioctl: cmd 0x%x(STEREO) st %d\n", cmd, val); + if (file->f_mode & FMODE_WRITE) { + ymf_wait_dac(state); + dmabuf = &state->wpcm.dmabuf; + spin_lock_irqsave(&state->unit->reg_lock, flags); + dmabuf->ready = 0; + state->format.voices = val ? 2 : 1; + ymf_pcm_update_shift(&state->format); + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + } + if (file->f_mode & FMODE_READ) { + ymf_stop_adc(state); + dmabuf = &state->rpcm.dmabuf; + spin_lock_irqsave(&state->unit->reg_lock, flags); + dmabuf->ready = 0; + state->format.voices = val ? 2 : 1; + ymf_pcm_update_shift(&state->format); + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + } + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + YMFDBGX("ymf_ioctl: cmd 0x%x(GETBLK)\n", cmd); + if (file->f_mode & FMODE_WRITE) { + if ((val = prog_dmabuf(state, 0))) + return val; + val = state->wpcm.dmabuf.fragsize; + YMFDBGX("ymf_ioctl: GETBLK w %d\n", val); + return put_user(val, p); + } + if (file->f_mode & FMODE_READ) { + if ((val = prog_dmabuf(state, 1))) + return val; + val = state->rpcm.dmabuf.fragsize; + YMFDBGX("ymf_ioctl: GETBLK r %d\n", val); + return put_user(val, p); + } + return -EINVAL; + + case SNDCTL_DSP_GETFMTS: /* Returns a mask of supported sample format*/ + YMFDBGX("ymf_ioctl: cmd 0x%x(GETFMTS)\n", cmd); + return put_user(AFMT_S16_LE|AFMT_U8, p); + + case SNDCTL_DSP_SETFMT: /* Select sample format */ + if (get_user(val, p)) + return -EFAULT; + YMFDBGX("ymf_ioctl: cmd 0x%x(SETFMT) fmt %d\n", cmd, val); + if (val == AFMT_S16_LE || val == AFMT_U8) { + if (file->f_mode & FMODE_WRITE) { + ymf_wait_dac(state); + dmabuf = &state->wpcm.dmabuf; + spin_lock_irqsave(&state->unit->reg_lock, flags); + dmabuf->ready = 0; + state->format.format = val; + ymf_pcm_update_shift(&state->format); + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + } + if (file->f_mode & FMODE_READ) { + ymf_stop_adc(state); + dmabuf = &state->rpcm.dmabuf; + spin_lock_irqsave(&state->unit->reg_lock, flags); + dmabuf->ready = 0; + state->format.format = val; + ymf_pcm_update_shift(&state->format); + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + } + } + return put_user(state->format.format, p); + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + YMFDBGX("ymf_ioctl: cmd 0x%x(CHAN) ch %d\n", cmd, val); + if (val != 0) { + if (file->f_mode & FMODE_WRITE) { + ymf_wait_dac(state); + if (val == 1 || val == 2) { + spin_lock_irqsave(&state->unit->reg_lock, flags); + dmabuf = &state->wpcm.dmabuf; + dmabuf->ready = 0; + state->format.voices = val; + ymf_pcm_update_shift(&state->format); + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + } + } + if (file->f_mode & FMODE_READ) { + ymf_stop_adc(state); + if (val == 1 || val == 2) { + spin_lock_irqsave(&state->unit->reg_lock, flags); + dmabuf = &state->rpcm.dmabuf; + dmabuf->ready = 0; + state->format.voices = val; + ymf_pcm_update_shift(&state->format); + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + } + } + } + return put_user(state->format.voices, p); + + case SNDCTL_DSP_POST: + YMFDBGX("ymf_ioctl: cmd 0x%x(POST)\n", cmd); + /* + * Quoting OSS PG: + * The ioctl SNDCTL_DSP_POST is a lightweight version of + * SNDCTL_DSP_SYNC. It just tells to the driver that there + * is likely to be a pause in the output. This makes it + * possible for the device to handle the pause more + * intelligently. This ioctl doesn't block the application. + * + * The paragraph above is a clumsy way to say "flush ioctl". + * This ioctl is used by mpg123. + */ + spin_lock_irqsave(&state->unit->reg_lock, flags); + if (state->wpcm.dmabuf.count != 0 && !state->wpcm.running) { + ymf_start_dac(state); + } + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + return 0; + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + YMFDBGX("ymf_ioctl: cmd 0x%x(SETFRAG) fr 0x%04x:%04x(%d:%d)\n", + cmd, + (val >> 16) & 0xFFFF, val & 0xFFFF, + (val >> 16) & 0xFFFF, val & 0xFFFF); + dmabuf = &state->wpcm.dmabuf; + dmabuf->ossfragshift = val & 0xffff; + dmabuf->ossmaxfrags = (val >> 16) & 0xffff; + if (dmabuf->ossfragshift < 4) + dmabuf->ossfragshift = 4; + if (dmabuf->ossfragshift > 15) + dmabuf->ossfragshift = 15; + return 0; + + case SNDCTL_DSP_GETOSPACE: + YMFDBGX("ymf_ioctl: cmd 0x%x(GETOSPACE)\n", cmd); + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + dmabuf = &state->wpcm.dmabuf; + if (!dmabuf->ready && (val = prog_dmabuf(state, 0)) != 0) + return val; + redzone = ymf_calc_lend(state->format.rate); + redzone <<= state->format.shift; + redzone *= 3; + spin_lock_irqsave(&state->unit->reg_lock, flags); + abinfo.fragsize = dmabuf->fragsize; + abinfo.bytes = dmabuf->dmasize - dmabuf->count - redzone; + abinfo.fragstotal = dmabuf->numfrag; + abinfo.fragments = abinfo.bytes >> dmabuf->fragshift; + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + YMFDBGX("ymf_ioctl: cmd 0x%x(GETISPACE)\n", cmd); + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + dmabuf = &state->rpcm.dmabuf; + if (!dmabuf->ready && (val = prog_dmabuf(state, 1)) != 0) + return val; + spin_lock_irqsave(&state->unit->reg_lock, flags); + abinfo.fragsize = dmabuf->fragsize; + abinfo.bytes = dmabuf->count; + abinfo.fragstotal = dmabuf->numfrag; + abinfo.fragments = abinfo.bytes >> dmabuf->fragshift; + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + return copy_to_user(argp, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + YMFDBGX("ymf_ioctl: cmd 0x%x(NONBLOCK)\n", cmd); + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETCAPS: + YMFDBGX("ymf_ioctl: cmd 0x%x(GETCAPS)\n", cmd); + /* return put_user(DSP_CAP_REALTIME|DSP_CAP_TRIGGER|DSP_CAP_MMAP, + p); */ + return put_user(0, p); + + case SNDCTL_DSP_GETIPTR: + YMFDBGX("ymf_ioctl: cmd 0x%x(GETIPTR)\n", cmd); + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + dmabuf = &state->rpcm.dmabuf; + spin_lock_irqsave(&state->unit->reg_lock, flags); + cinfo.bytes = dmabuf->total_bytes; + cinfo.blocks = dmabuf->count >> dmabuf->fragshift; + cinfo.ptr = dmabuf->hwptr; + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + YMFDBGX("ymf_ioctl: GETIPTR ptr %d bytes %d\n", + cinfo.ptr, cinfo.bytes); + return copy_to_user(argp, &cinfo, sizeof(cinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETOPTR: + YMFDBGX("ymf_ioctl: cmd 0x%x(GETOPTR)\n", cmd); + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + dmabuf = &state->wpcm.dmabuf; + spin_lock_irqsave(&state->unit->reg_lock, flags); + cinfo.bytes = dmabuf->total_bytes; + cinfo.blocks = dmabuf->count >> dmabuf->fragshift; + cinfo.ptr = dmabuf->hwptr; + spin_unlock_irqrestore(&state->unit->reg_lock, flags); + YMFDBGX("ymf_ioctl: GETOPTR ptr %d bytes %d\n", + cinfo.ptr, cinfo.bytes); + return copy_to_user(argp, &cinfo, sizeof(cinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_SETDUPLEX: + YMFDBGX("ymf_ioctl: cmd 0x%x(SETDUPLEX)\n", cmd); + return 0; /* Always duplex */ + + case SOUND_PCM_READ_RATE: + YMFDBGX("ymf_ioctl: cmd 0x%x(READ_RATE)\n", cmd); + return put_user(state->format.rate, p); + + case SOUND_PCM_READ_CHANNELS: + YMFDBGX("ymf_ioctl: cmd 0x%x(READ_CH)\n", cmd); + return put_user(state->format.voices, p); + + case SOUND_PCM_READ_BITS: + YMFDBGX("ymf_ioctl: cmd 0x%x(READ_BITS)\n", cmd); + return put_user(AFMT_S16_LE, p); + + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + YMFDBGX("ymf_ioctl: cmd 0x%x unsupported\n", cmd); + return -ENOTTY; + + default: + /* + * Some programs mix up audio devices and ioctls + * or perhaps they expect "universal" ioctls, + * for instance we get SNDCTL_TMR_CONTINUE here. + * (mpg123 -g 100 ends here too - to be fixed.) + */ + YMFDBGX("ymf_ioctl: cmd 0x%x unknown\n", cmd); + break; + } + return -ENOTTY; +} + +/* + * open(2) + * We use upper part of the minor to distinguish between soundcards. + * Channels are opened with a clone open. + */ +static int ymf_open(struct inode *inode, struct file *file) +{ + struct list_head *list; + ymfpci_t *unit = NULL; + int minor; + struct ymf_state *state; + int err; + + minor = iminor(inode); + if ((minor & 0x0F) == 3) { /* /dev/dspN */ + ; + } else { + return -ENXIO; + } + + unit = NULL; /* gcc warns */ + spin_lock(&ymf_devs_lock); + list_for_each(list, &ymf_devs) { + unit = list_entry(list, ymfpci_t, ymf_devs); + if (((unit->dev_audio ^ minor) & ~0x0F) == 0) + break; + } + spin_unlock(&ymf_devs_lock); + if (unit == NULL) + return -ENODEV; + + down(&unit->open_sem); + + if ((state = ymf_state_alloc(unit)) == NULL) { + up(&unit->open_sem); + return -ENOMEM; + } + list_add_tail(&state->chain, &unit->states); + + file->private_data = state; + + /* + * ymf_read and ymf_write that we borrowed from cs46xx + * allocate buffers with prog_dmabuf(). We call prog_dmabuf + * here so that in case of DMA memory exhaustion open + * fails rather than write. + * + * XXX prog_dmabuf allocates voice. Should allocate explicitly, above. + */ + if (file->f_mode & FMODE_WRITE) { + if (!state->wpcm.dmabuf.ready) { + if ((err = prog_dmabuf(state, 0)) != 0) { + goto out_nodma; + } + } + } + if (file->f_mode & FMODE_READ) { + if (!state->rpcm.dmabuf.ready) { + if ((err = prog_dmabuf(state, 1)) != 0) { + goto out_nodma; + } + } + } + +#if 0 /* test if interrupts work */ + ymfpci_writew(unit, YDSXGR_TIMERCOUNT, 0xfffe); /* ~ 680ms */ + ymfpci_writeb(unit, YDSXGR_TIMERCTRL, + (YDSXGR_TIMERCTRL_TEN|YDSXGR_TIMERCTRL_TIEN)); +#endif + up(&unit->open_sem); + + return nonseekable_open(inode, file); + +out_nodma: + /* + * XXX Broken custom: "goto out_xxx" in other place is + * a nestable exception, but here it is not nestable due to semaphore. + * XXX Doubtful technique of self-describing objects.... + */ + dealloc_dmabuf(unit, &state->wpcm.dmabuf); + dealloc_dmabuf(unit, &state->rpcm.dmabuf); + ymf_pcm_free_substream(&state->wpcm); + ymf_pcm_free_substream(&state->rpcm); + + list_del(&state->chain); + kfree(state); + + up(&unit->open_sem); + return err; +} + +static int ymf_release(struct inode *inode, struct file *file) +{ + struct ymf_state *state = (struct ymf_state *)file->private_data; + ymfpci_t *unit = state->unit; + +#if 0 /* test if interrupts work */ + ymfpci_writeb(unit, YDSXGR_TIMERCTRL, 0); +#endif + + down(&unit->open_sem); + + /* + * XXX Solve the case of O_NONBLOCK close - don't deallocate here. + * Deallocate when unloading the driver and we can wait. + */ + ymf_wait_dac(state); + ymf_stop_adc(state); /* fortunately, it's immediate */ + dealloc_dmabuf(unit, &state->wpcm.dmabuf); + dealloc_dmabuf(unit, &state->rpcm.dmabuf); + ymf_pcm_free_substream(&state->wpcm); + ymf_pcm_free_substream(&state->rpcm); + + list_del(&state->chain); + file->private_data = NULL; /* Can you tell I programmed Solaris */ + kfree(state); + + up(&unit->open_sem); + + return 0; +} + +/* + * Mixer operations are based on cs46xx. + */ +static int ymf_open_mixdev(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct list_head *list; + ymfpci_t *unit; + int i; + + spin_lock(&ymf_devs_lock); + list_for_each(list, &ymf_devs) { + unit = list_entry(list, ymfpci_t, ymf_devs); + for (i = 0; i < NR_AC97; i++) { + if (unit->ac97_codec[i] != NULL && + unit->ac97_codec[i]->dev_mixer == minor) { + spin_unlock(&ymf_devs_lock); + goto match; + } + } + } + spin_unlock(&ymf_devs_lock); + return -ENODEV; + + match: + file->private_data = unit->ac97_codec[i]; + + return nonseekable_open(inode, file); +} + +static int ymf_ioctl_mixdev(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct ac97_codec *codec = (struct ac97_codec *)file->private_data; + + return codec->mixer_ioctl(codec, cmd, arg); +} + +static int ymf_release_mixdev(struct inode *inode, struct file *file) +{ + return 0; +} + +static /*const*/ struct file_operations ymf_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = ymf_read, + .write = ymf_write, + .poll = ymf_poll, + .ioctl = ymf_ioctl, + .mmap = ymf_mmap, + .open = ymf_open, + .release = ymf_release, +}; + +static /*const*/ struct file_operations ymf_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = ymf_ioctl_mixdev, + .open = ymf_open_mixdev, + .release = ymf_release_mixdev, +}; + +/* + */ + +static int ymf_suspend(struct pci_dev *pcidev, pm_message_t unused) +{ + struct ymf_unit *unit = pci_get_drvdata(pcidev); + unsigned long flags; + struct ymf_dmabuf *dmabuf; + struct list_head *p; + struct ymf_state *state; + struct ac97_codec *codec; + int i; + + spin_lock_irqsave(&unit->reg_lock, flags); + + unit->suspended = 1; + + for (i = 0; i < NR_AC97; i++) { + if ((codec = unit->ac97_codec[i]) != NULL) + ac97_save_state(codec); + } + + list_for_each(p, &unit->states) { + state = list_entry(p, struct ymf_state, chain); + + dmabuf = &state->wpcm.dmabuf; + dmabuf->hwptr = dmabuf->swptr = 0; + dmabuf->total_bytes = 0; + dmabuf->count = 0; + + dmabuf = &state->rpcm.dmabuf; + dmabuf->hwptr = dmabuf->swptr = 0; + dmabuf->total_bytes = 0; + dmabuf->count = 0; + } + + ymfpci_writel(unit, YDSXGR_NATIVEDACOUTVOL, 0); + ymfpci_disable_dsp(unit); + + spin_unlock_irqrestore(&unit->reg_lock, flags); + + return 0; +} + +static int ymf_resume(struct pci_dev *pcidev) +{ + struct ymf_unit *unit = pci_get_drvdata(pcidev); + unsigned long flags; + struct list_head *p; + struct ymf_state *state; + struct ac97_codec *codec; + int i; + + ymfpci_aclink_reset(unit->pci); + ymfpci_codec_ready(unit, 0, 1); /* prints diag if not ready. */ + +#ifdef CONFIG_SOUND_YMFPCI_LEGACY + /* XXX At this time the legacy registers are probably deprogrammed. */ +#endif + + ymfpci_download_image(unit); + + ymf_memload(unit); + + spin_lock_irqsave(&unit->reg_lock, flags); + + if (unit->start_count) { + ymfpci_writel(unit, YDSXGR_MODE, 3); + unit->active_bank = ymfpci_readl(unit, YDSXGR_CTRLSELECT) & 1; + } + + for (i = 0; i < NR_AC97; i++) { + if ((codec = unit->ac97_codec[i]) != NULL) + ac97_restore_state(codec); + } + + unit->suspended = 0; + list_for_each(p, &unit->states) { + state = list_entry(p, struct ymf_state, chain); + wake_up(&state->wpcm.dmabuf.wait); + wake_up(&state->rpcm.dmabuf.wait); + } + + spin_unlock_irqrestore(&unit->reg_lock, flags); + return 0; +} + +/* + * initialization routines + */ + +#ifdef CONFIG_SOUND_YMFPCI_LEGACY + +static int ymfpci_setup_legacy(ymfpci_t *unit, struct pci_dev *pcidev) +{ + int v; + int mpuio = -1, oplio = -1; + + switch (unit->iomidi) { + case 0x330: + mpuio = 0; + break; + case 0x300: + mpuio = 1; + break; + case 0x332: + mpuio = 2; + break; + case 0x334: + mpuio = 3; + break; + default: ; + } + + switch (unit->iosynth) { + case 0x388: + oplio = 0; + break; + case 0x398: + oplio = 1; + break; + case 0x3a0: + oplio = 2; + break; + case 0x3a8: + oplio = 3; + break; + default: ; + } + + if (mpuio >= 0 || oplio >= 0) { + /* 0x0020: 1 - 10 bits of I/O address decoded, 0 - 16 bits. */ + v = 0x001e; + pci_write_config_word(pcidev, PCIR_LEGCTRL, v); + + switch (pcidev->device) { + case PCI_DEVICE_ID_YAMAHA_724: + case PCI_DEVICE_ID_YAMAHA_740: + case PCI_DEVICE_ID_YAMAHA_724F: + case PCI_DEVICE_ID_YAMAHA_740C: + v = 0x8800; + if (mpuio >= 0) { v |= mpuio<<4; } + if (oplio >= 0) { v |= oplio; } + pci_write_config_word(pcidev, PCIR_ELEGCTRL, v); + break; + + case PCI_DEVICE_ID_YAMAHA_744: + case PCI_DEVICE_ID_YAMAHA_754: + v = 0x8800; + pci_write_config_word(pcidev, PCIR_ELEGCTRL, v); + if (oplio >= 0) { + pci_write_config_word(pcidev, PCIR_OPLADR, unit->iosynth); + } + if (mpuio >= 0) { + pci_write_config_word(pcidev, PCIR_MPUADR, unit->iomidi); + } + break; + + default: + printk(KERN_ERR "ymfpci: Unknown device ID: 0x%x\n", + pcidev->device); + return -EINVAL; + } + } + + return 0; +} +#endif /* CONFIG_SOUND_YMFPCI_LEGACY */ + +static void ymfpci_aclink_reset(struct pci_dev * pci) +{ + u8 cmd; + + /* + * In the 744, 754 only 0x01 exists, 0x02 is undefined. + * It does not seem to hurt to trip both regardless of revision. + */ + pci_read_config_byte(pci, PCIR_DSXGCTRL, &cmd); + pci_write_config_byte(pci, PCIR_DSXGCTRL, cmd & 0xfc); + pci_write_config_byte(pci, PCIR_DSXGCTRL, cmd | 0x03); + pci_write_config_byte(pci, PCIR_DSXGCTRL, cmd & 0xfc); + + pci_write_config_word(pci, PCIR_DSXPWRCTRL1, 0); + pci_write_config_word(pci, PCIR_DSXPWRCTRL2, 0); +} + +static void ymfpci_enable_dsp(ymfpci_t *codec) +{ + ymfpci_writel(codec, YDSXGR_CONFIG, 0x00000001); +} + +static void ymfpci_disable_dsp(ymfpci_t *codec) +{ + u32 val; + int timeout = 1000; + + val = ymfpci_readl(codec, YDSXGR_CONFIG); + if (val) + ymfpci_writel(codec, YDSXGR_CONFIG, 0x00000000); + while (timeout-- > 0) { + val = ymfpci_readl(codec, YDSXGR_STATUS); + if ((val & 0x00000002) == 0) + break; + } +} + +#include "ymfpci_image.h" + +static void ymfpci_download_image(ymfpci_t *codec) +{ + int i, ver_1e; + u16 ctrl; + + ymfpci_writel(codec, YDSXGR_NATIVEDACOUTVOL, 0x00000000); + ymfpci_disable_dsp(codec); + ymfpci_writel(codec, YDSXGR_MODE, 0x00010000); + ymfpci_writel(codec, YDSXGR_MODE, 0x00000000); + ymfpci_writel(codec, YDSXGR_MAPOFREC, 0x00000000); + ymfpci_writel(codec, YDSXGR_MAPOFEFFECT, 0x00000000); + ymfpci_writel(codec, YDSXGR_PLAYCTRLBASE, 0x00000000); + ymfpci_writel(codec, YDSXGR_RECCTRLBASE, 0x00000000); + ymfpci_writel(codec, YDSXGR_EFFCTRLBASE, 0x00000000); + ctrl = ymfpci_readw(codec, YDSXGR_GLOBALCTRL); + ymfpci_writew(codec, YDSXGR_GLOBALCTRL, ctrl & ~0x0007); + + /* setup DSP instruction code */ + for (i = 0; i < YDSXG_DSPLENGTH / 4; i++) + ymfpci_writel(codec, YDSXGR_DSPINSTRAM + (i << 2), DspInst[i]); + + switch (codec->pci->device) { + case PCI_DEVICE_ID_YAMAHA_724F: + case PCI_DEVICE_ID_YAMAHA_740C: + case PCI_DEVICE_ID_YAMAHA_744: + case PCI_DEVICE_ID_YAMAHA_754: + ver_1e = 1; + break; + default: + ver_1e = 0; + } + + if (ver_1e) { + /* setup control instruction code */ + for (i = 0; i < YDSXG_CTRLLENGTH / 4; i++) + ymfpci_writel(codec, YDSXGR_CTRLINSTRAM + (i << 2), CntrlInst1E[i]); + } else { + for (i = 0; i < YDSXG_CTRLLENGTH / 4; i++) + ymfpci_writel(codec, YDSXGR_CTRLINSTRAM + (i << 2), CntrlInst[i]); + } + + ymfpci_enable_dsp(codec); + + /* 0.02s sounds not too bad, we may do schedule_timeout() later. */ + mdelay(20); /* seems we need some delay after downloading image.. */ +} + +static int ymfpci_memalloc(ymfpci_t *codec) +{ + unsigned int playback_ctrl_size; + unsigned int bank_size_playback; + unsigned int bank_size_capture; + unsigned int bank_size_effect; + unsigned int size; + unsigned int off; + char *ptr; + dma_addr_t pba; + int voice, bank; + + playback_ctrl_size = 4 + 4 * YDSXG_PLAYBACK_VOICES; + bank_size_playback = ymfpci_readl(codec, YDSXGR_PLAYCTRLSIZE) << 2; + bank_size_capture = ymfpci_readl(codec, YDSXGR_RECCTRLSIZE) << 2; + bank_size_effect = ymfpci_readl(codec, YDSXGR_EFFCTRLSIZE) << 2; + codec->work_size = YDSXG_DEFAULT_WORK_SIZE; + + size = ((playback_ctrl_size + 0x00ff) & ~0x00ff) + + ((bank_size_playback * 2 * YDSXG_PLAYBACK_VOICES + 0xff) & ~0xff) + + ((bank_size_capture * 2 * YDSXG_CAPTURE_VOICES + 0xff) & ~0xff) + + ((bank_size_effect * 2 * YDSXG_EFFECT_VOICES + 0xff) & ~0xff) + + codec->work_size; + + ptr = pci_alloc_consistent(codec->pci, size + 0xff, &pba); + if (ptr == NULL) + return -ENOMEM; + codec->dma_area_va = ptr; + codec->dma_area_ba = pba; + codec->dma_area_size = size + 0xff; + + off = (unsigned long)ptr & 0xff; + if (off) { + ptr += 0x100 - off; + pba += 0x100 - off; + } + + /* + * Hardware requires only ptr[playback_ctrl_size] zeroed, + * but in our judgement it is a wrong kind of savings, so clear it all. + */ + memset(ptr, 0, size); + + codec->ctrl_playback = (u32 *)ptr; + codec->ctrl_playback_ba = pba; + codec->ctrl_playback[0] = cpu_to_le32(YDSXG_PLAYBACK_VOICES); + ptr += (playback_ctrl_size + 0x00ff) & ~0x00ff; + pba += (playback_ctrl_size + 0x00ff) & ~0x00ff; + + off = 0; + for (voice = 0; voice < YDSXG_PLAYBACK_VOICES; voice++) { + codec->voices[voice].number = voice; + codec->voices[voice].bank = + (ymfpci_playback_bank_t *) (ptr + off); + codec->voices[voice].bank_ba = pba + off; + off += 2 * bank_size_playback; /* 2 banks */ + } + off = (off + 0xff) & ~0xff; + ptr += off; + pba += off; + + off = 0; + codec->bank_base_capture = pba; + for (voice = 0; voice < YDSXG_CAPTURE_VOICES; voice++) + for (bank = 0; bank < 2; bank++) { + codec->bank_capture[voice][bank] = + (ymfpci_capture_bank_t *) (ptr + off); + off += bank_size_capture; + } + off = (off + 0xff) & ~0xff; + ptr += off; + pba += off; + + off = 0; + codec->bank_base_effect = pba; + for (voice = 0; voice < YDSXG_EFFECT_VOICES; voice++) + for (bank = 0; bank < 2; bank++) { + codec->bank_effect[voice][bank] = + (ymfpci_effect_bank_t *) (ptr + off); + off += bank_size_effect; + } + off = (off + 0xff) & ~0xff; + ptr += off; + pba += off; + + codec->work_base = pba; + + return 0; +} + +static void ymfpci_memfree(ymfpci_t *codec) +{ + ymfpci_writel(codec, YDSXGR_PLAYCTRLBASE, 0); + ymfpci_writel(codec, YDSXGR_RECCTRLBASE, 0); + ymfpci_writel(codec, YDSXGR_EFFCTRLBASE, 0); + ymfpci_writel(codec, YDSXGR_WORKBASE, 0); + ymfpci_writel(codec, YDSXGR_WORKSIZE, 0); + pci_free_consistent(codec->pci, + codec->dma_area_size, codec->dma_area_va, codec->dma_area_ba); +} + +static void ymf_memload(ymfpci_t *unit) +{ + + ymfpci_writel(unit, YDSXGR_PLAYCTRLBASE, unit->ctrl_playback_ba); + ymfpci_writel(unit, YDSXGR_RECCTRLBASE, unit->bank_base_capture); + ymfpci_writel(unit, YDSXGR_EFFCTRLBASE, unit->bank_base_effect); + ymfpci_writel(unit, YDSXGR_WORKBASE, unit->work_base); + ymfpci_writel(unit, YDSXGR_WORKSIZE, unit->work_size >> 2); + + /* S/PDIF output initialization */ + ymfpci_writew(unit, YDSXGR_SPDIFOUTCTRL, 0); + ymfpci_writew(unit, YDSXGR_SPDIFOUTSTATUS, + SND_PCM_AES0_CON_EMPHASIS_NONE | + (SND_PCM_AES1_CON_ORIGINAL << 8) | + (SND_PCM_AES1_CON_PCM_CODER << 8)); + + /* S/PDIF input initialization */ + ymfpci_writew(unit, YDSXGR_SPDIFINCTRL, 0); + + /* move this volume setup to mixer */ + ymfpci_writel(unit, YDSXGR_NATIVEDACOUTVOL, 0x3fff3fff); + ymfpci_writel(unit, YDSXGR_BUF441OUTVOL, 0); + ymfpci_writel(unit, YDSXGR_NATIVEADCINVOL, 0x3fff3fff); + ymfpci_writel(unit, YDSXGR_NATIVEDACINVOL, 0x3fff3fff); +} + +static int ymf_ac97_init(ymfpci_t *unit, int num_ac97) +{ + struct ac97_codec *codec; + u16 eid; + + if ((codec = ac97_alloc_codec()) == NULL) + return -ENOMEM; + + /* initialize some basic codec information, other fields will be filled + in ac97_probe_codec */ + codec->private_data = unit; + codec->id = num_ac97; + + codec->codec_read = ymfpci_codec_read; + codec->codec_write = ymfpci_codec_write; + + if (ac97_probe_codec(codec) == 0) { + printk(KERN_ERR "ymfpci: ac97_probe_codec failed\n"); + goto out_kfree; + } + + eid = ymfpci_codec_read(codec, AC97_EXTENDED_ID); + if (eid==0xFFFF) { + printk(KERN_WARNING "ymfpci: no codec attached ?\n"); + goto out_kfree; + } + + unit->ac97_features = eid; + + if ((codec->dev_mixer = register_sound_mixer(&ymf_mixer_fops, -1)) < 0) { + printk(KERN_ERR "ymfpci: couldn't register mixer!\n"); + goto out_kfree; + } + + unit->ac97_codec[num_ac97] = codec; + + return 0; + out_kfree: + ac97_release_codec(codec); + return -ENODEV; +} + +#ifdef CONFIG_SOUND_YMFPCI_LEGACY +# ifdef MODULE +static int mpu_io; +static int synth_io; +module_param(mpu_io, int, 0); +module_param(synth_io, int, 0); +# else +static int mpu_io = 0x330; +static int synth_io = 0x388; +# endif +static int assigned; +#endif /* CONFIG_SOUND_YMFPCI_LEGACY */ + +static int __devinit ymf_probe_one(struct pci_dev *pcidev, const struct pci_device_id *ent) +{ + u16 ctrl; + unsigned long base; + ymfpci_t *codec; + + int err; + + if ((err = pci_enable_device(pcidev)) != 0) { + printk(KERN_ERR "ymfpci: pci_enable_device failed\n"); + return err; + } + base = pci_resource_start(pcidev, 0); + + if ((codec = kmalloc(sizeof(ymfpci_t), GFP_KERNEL)) == NULL) { + printk(KERN_ERR "ymfpci: no core\n"); + return -ENOMEM; + } + memset(codec, 0, sizeof(*codec)); + + spin_lock_init(&codec->reg_lock); + spin_lock_init(&codec->voice_lock); + spin_lock_init(&codec->ac97_lock); + init_MUTEX(&codec->open_sem); + INIT_LIST_HEAD(&codec->states); + codec->pci = pcidev; + + pci_read_config_byte(pcidev, PCI_REVISION_ID, &codec->rev); + + if (request_mem_region(base, 0x8000, "ymfpci") == NULL) { + printk(KERN_ERR "ymfpci: unable to request mem region\n"); + goto out_free; + } + + if ((codec->reg_area_virt = ioremap(base, 0x8000)) == NULL) { + printk(KERN_ERR "ymfpci: unable to map registers\n"); + goto out_release_region; + } + + pci_set_master(pcidev); + + printk(KERN_INFO "ymfpci: %s at 0x%lx IRQ %d\n", + (char *)ent->driver_data, base, pcidev->irq); + + ymfpci_aclink_reset(pcidev); + if (ymfpci_codec_ready(codec, 0, 1) < 0) + goto out_unmap; + +#ifdef CONFIG_SOUND_YMFPCI_LEGACY + if (assigned == 0) { + codec->iomidi = mpu_io; + codec->iosynth = synth_io; + if (ymfpci_setup_legacy(codec, pcidev) < 0) + goto out_unmap; + assigned = 1; + } +#endif + + ymfpci_download_image(codec); + + if (ymfpci_memalloc(codec) < 0) + goto out_disable_dsp; + ymf_memload(codec); + + if (request_irq(pcidev->irq, ymf_interrupt, SA_SHIRQ, "ymfpci", codec) != 0) { + printk(KERN_ERR "ymfpci: unable to request IRQ %d\n", + pcidev->irq); + goto out_memfree; + } + + /* register /dev/dsp */ + if ((codec->dev_audio = register_sound_dsp(&ymf_fops, -1)) < 0) { + printk(KERN_ERR "ymfpci: unable to register dsp\n"); + goto out_free_irq; + } + + /* + * Poke just the primary for the moment. + */ + if ((err = ymf_ac97_init(codec, 0)) != 0) + goto out_unregister_sound_dsp; + +#ifdef CONFIG_SOUND_YMFPCI_LEGACY + codec->opl3_data.name = "ymfpci"; + codec->mpu_data.name = "ymfpci"; + + codec->opl3_data.io_base = codec->iosynth; + codec->opl3_data.irq = -1; + + codec->mpu_data.io_base = codec->iomidi; + codec->mpu_data.irq = -1; /* May be different from our PCI IRQ. */ + + if (codec->iomidi) { + if (!probe_uart401(&codec->mpu_data, THIS_MODULE)) { + codec->iomidi = 0; /* XXX kludge */ + } + } +#endif /* CONFIG_SOUND_YMFPCI_LEGACY */ + + /* put it into driver list */ + spin_lock(&ymf_devs_lock); + list_add_tail(&codec->ymf_devs, &ymf_devs); + spin_unlock(&ymf_devs_lock); + pci_set_drvdata(pcidev, codec); + + return 0; + + out_unregister_sound_dsp: + unregister_sound_dsp(codec->dev_audio); + out_free_irq: + free_irq(pcidev->irq, codec); + out_memfree: + ymfpci_memfree(codec); + out_disable_dsp: + ymfpci_disable_dsp(codec); + ctrl = ymfpci_readw(codec, YDSXGR_GLOBALCTRL); + ymfpci_writew(codec, YDSXGR_GLOBALCTRL, ctrl & ~0x0007); + ymfpci_writel(codec, YDSXGR_STATUS, ~0); + out_unmap: + iounmap(codec->reg_area_virt); + out_release_region: + release_mem_region(pci_resource_start(pcidev, 0), 0x8000); + out_free: + if (codec->ac97_codec[0]) + ac97_release_codec(codec->ac97_codec[0]); + return -ENODEV; +} + +static void __devexit ymf_remove_one(struct pci_dev *pcidev) +{ + __u16 ctrl; + ymfpci_t *codec = pci_get_drvdata(pcidev); + + /* remove from list of devices */ + spin_lock(&ymf_devs_lock); + list_del(&codec->ymf_devs); + spin_unlock(&ymf_devs_lock); + + unregister_sound_mixer(codec->ac97_codec[0]->dev_mixer); + ac97_release_codec(codec->ac97_codec[0]); + unregister_sound_dsp(codec->dev_audio); + free_irq(pcidev->irq, codec); + ymfpci_memfree(codec); + ymfpci_writel(codec, YDSXGR_STATUS, ~0); + ymfpci_disable_dsp(codec); + ctrl = ymfpci_readw(codec, YDSXGR_GLOBALCTRL); + ymfpci_writew(codec, YDSXGR_GLOBALCTRL, ctrl & ~0x0007); + iounmap(codec->reg_area_virt); + release_mem_region(pci_resource_start(pcidev, 0), 0x8000); +#ifdef CONFIG_SOUND_YMFPCI_LEGACY + if (codec->iomidi) { + unload_uart401(&codec->mpu_data); + } +#endif /* CONFIG_SOUND_YMFPCI_LEGACY */ +} + +MODULE_AUTHOR("Jaroslav Kysela"); +MODULE_DESCRIPTION("Yamaha YMF7xx PCI Audio"); +MODULE_LICENSE("GPL"); + +static struct pci_driver ymfpci_driver = { + .name = "ymfpci", + .id_table = ymf_id_tbl, + .probe = ymf_probe_one, + .remove = __devexit_p(ymf_remove_one), + .suspend = ymf_suspend, + .resume = ymf_resume +}; + +static int __init ymf_init_module(void) +{ + return pci_module_init(&ymfpci_driver); +} + +static void __exit ymf_cleanup_module (void) +{ + pci_unregister_driver(&ymfpci_driver); +} + +module_init(ymf_init_module); +module_exit(ymf_cleanup_module); diff --git a/sound/oss/ymfpci.h b/sound/oss/ymfpci.h new file mode 100644 index 000000000000..f810a100c641 --- /dev/null +++ b/sound/oss/ymfpci.h @@ -0,0 +1,360 @@ +#ifndef __YMFPCI_H +#define __YMFPCI_H + +/* + * Copyright (c) by Jaroslav Kysela + * Definitions for Yahama YMF724/740/744/754 chips + * + * + * 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 + +/* + * Direct registers + */ + +/* #define YMFREG(codec, reg) (codec->port + YDSXGR_##reg) */ + +#define YDSXGR_INTFLAG 0x0004 +#define YDSXGR_ACTIVITY 0x0006 +#define YDSXGR_GLOBALCTRL 0x0008 +#define YDSXGR_ZVCTRL 0x000A +#define YDSXGR_TIMERCTRL 0x0010 +#define YDSXGR_TIMERCTRL_TEN 0x0001 +#define YDSXGR_TIMERCTRL_TIEN 0x0002 +#define YDSXGR_TIMERCOUNT 0x0012 +#define YDSXGR_SPDIFOUTCTRL 0x0018 +#define YDSXGR_SPDIFOUTSTATUS 0x001C +#define YDSXGR_EEPROMCTRL 0x0020 +#define YDSXGR_SPDIFINCTRL 0x0034 +#define YDSXGR_SPDIFINSTATUS 0x0038 +#define YDSXGR_DSPPROGRAMDL 0x0048 +#define YDSXGR_DLCNTRL 0x004C +#define YDSXGR_GPIOININTFLAG 0x0050 +#define YDSXGR_GPIOININTENABLE 0x0052 +#define YDSXGR_GPIOINSTATUS 0x0054 +#define YDSXGR_GPIOOUTCTRL 0x0056 +#define YDSXGR_GPIOFUNCENABLE 0x0058 +#define YDSXGR_GPIOTYPECONFIG 0x005A +#define YDSXGR_AC97CMDDATA 0x0060 +#define YDSXGR_AC97CMDADR 0x0062 +#define YDSXGR_PRISTATUSDATA 0x0064 +#define YDSXGR_PRISTATUSADR 0x0066 +#define YDSXGR_SECSTATUSDATA 0x0068 +#define YDSXGR_SECSTATUSADR 0x006A +#define YDSXGR_SECCONFIG 0x0070 +#define YDSXGR_LEGACYOUTVOL 0x0080 +#define YDSXGR_LEGACYOUTVOLL 0x0080 +#define YDSXGR_LEGACYOUTVOLR 0x0082 +#define YDSXGR_NATIVEDACOUTVOL 0x0084 +#define YDSXGR_NATIVEDACOUTVOLL 0x0084 +#define YDSXGR_NATIVEDACOUTVOLR 0x0086 +#define YDSXGR_SPDIFOUTVOL 0x0088 +#define YDSXGR_SPDIFOUTVOLL 0x0088 +#define YDSXGR_SPDIFOUTVOLR 0x008A +#define YDSXGR_AC3OUTVOL 0x008C +#define YDSXGR_AC3OUTVOLL 0x008C +#define YDSXGR_AC3OUTVOLR 0x008E +#define YDSXGR_PRIADCOUTVOL 0x0090 +#define YDSXGR_PRIADCOUTVOLL 0x0090 +#define YDSXGR_PRIADCOUTVOLR 0x0092 +#define YDSXGR_LEGACYLOOPVOL 0x0094 +#define YDSXGR_LEGACYLOOPVOLL 0x0094 +#define YDSXGR_LEGACYLOOPVOLR 0x0096 +#define YDSXGR_NATIVEDACLOOPVOL 0x0098 +#define YDSXGR_NATIVEDACLOOPVOLL 0x0098 +#define YDSXGR_NATIVEDACLOOPVOLR 0x009A +#define YDSXGR_SPDIFLOOPVOL 0x009C +#define YDSXGR_SPDIFLOOPVOLL 0x009E +#define YDSXGR_SPDIFLOOPVOLR 0x009E +#define YDSXGR_AC3LOOPVOL 0x00A0 +#define YDSXGR_AC3LOOPVOLL 0x00A0 +#define YDSXGR_AC3LOOPVOLR 0x00A2 +#define YDSXGR_PRIADCLOOPVOL 0x00A4 +#define YDSXGR_PRIADCLOOPVOLL 0x00A4 +#define YDSXGR_PRIADCLOOPVOLR 0x00A6 +#define YDSXGR_NATIVEADCINVOL 0x00A8 +#define YDSXGR_NATIVEADCINVOLL 0x00A8 +#define YDSXGR_NATIVEADCINVOLR 0x00AA +#define YDSXGR_NATIVEDACINVOL 0x00AC +#define YDSXGR_NATIVEDACINVOLL 0x00AC +#define YDSXGR_NATIVEDACINVOLR 0x00AE +#define YDSXGR_BUF441OUTVOL 0x00B0 +#define YDSXGR_BUF441OUTVOLL 0x00B0 +#define YDSXGR_BUF441OUTVOLR 0x00B2 +#define YDSXGR_BUF441LOOPVOL 0x00B4 +#define YDSXGR_BUF441LOOPVOLL 0x00B4 +#define YDSXGR_BUF441LOOPVOLR 0x00B6 +#define YDSXGR_SPDIFOUTVOL2 0x00B8 +#define YDSXGR_SPDIFOUTVOL2L 0x00B8 +#define YDSXGR_SPDIFOUTVOL2R 0x00BA +#define YDSXGR_SPDIFLOOPVOL2 0x00BC +#define YDSXGR_SPDIFLOOPVOL2L 0x00BC +#define YDSXGR_SPDIFLOOPVOL2R 0x00BE +#define YDSXGR_ADCSLOTSR 0x00C0 +#define YDSXGR_RECSLOTSR 0x00C4 +#define YDSXGR_ADCFORMAT 0x00C8 +#define YDSXGR_RECFORMAT 0x00CC +#define YDSXGR_P44SLOTSR 0x00D0 +#define YDSXGR_STATUS 0x0100 +#define YDSXGR_CTRLSELECT 0x0104 +#define YDSXGR_MODE 0x0108 +#define YDSXGR_SAMPLECOUNT 0x010C +#define YDSXGR_NUMOFSAMPLES 0x0110 +#define YDSXGR_CONFIG 0x0114 +#define YDSXGR_PLAYCTRLSIZE 0x0140 +#define YDSXGR_RECCTRLSIZE 0x0144 +#define YDSXGR_EFFCTRLSIZE 0x0148 +#define YDSXGR_WORKSIZE 0x014C +#define YDSXGR_MAPOFREC 0x0150 +#define YDSXGR_MAPOFEFFECT 0x0154 +#define YDSXGR_PLAYCTRLBASE 0x0158 +#define YDSXGR_RECCTRLBASE 0x015C +#define YDSXGR_EFFCTRLBASE 0x0160 +#define YDSXGR_WORKBASE 0x0164 +#define YDSXGR_DSPINSTRAM 0x1000 +#define YDSXGR_CTRLINSTRAM 0x4000 + +#define YDSXG_AC97READCMD 0x8000 +#define YDSXG_AC97WRITECMD 0x0000 + +#define PCIR_LEGCTRL 0x40 +#define PCIR_ELEGCTRL 0x42 +#define PCIR_DSXGCTRL 0x48 +#define PCIR_DSXPWRCTRL1 0x4a +#define PCIR_DSXPWRCTRL2 0x4e +#define PCIR_OPLADR 0x60 +#define PCIR_SBADR 0x62 +#define PCIR_MPUADR 0x64 + +#define YDSXG_DSPLENGTH 0x0080 +#define YDSXG_CTRLLENGTH 0x3000 + +#define YDSXG_DEFAULT_WORK_SIZE 0x0400 + +#define YDSXG_PLAYBACK_VOICES 64 +#define YDSXG_CAPTURE_VOICES 2 +#define YDSXG_EFFECT_VOICES 5 + +/* maxinum number of AC97 codecs connected, AC97 2.0 defined 4 */ +#define NR_AC97 2 + +#define YMF_SAMPF 256 /* Samples per frame @48000 */ + +/* + * The slot/voice control bank (2 of these per voice) + */ + +typedef struct stru_ymfpci_playback_bank { + u32 format; + u32 loop_default; + u32 base; /* 32-bit address */ + u32 loop_start; /* 32-bit offset */ + u32 loop_end; /* 32-bit offset */ + u32 loop_frac; /* 8-bit fraction - loop_start */ + u32 delta_end; /* pitch delta end */ + u32 lpfK_end; + u32 eg_gain_end; + u32 left_gain_end; + u32 right_gain_end; + u32 eff1_gain_end; + u32 eff2_gain_end; + u32 eff3_gain_end; + u32 lpfQ; + u32 status; /* P3: Always 0 for some reason. */ + u32 num_of_frames; + u32 loop_count; + u32 start; /* P3: J. reads this to know where chip is. */ + u32 start_frac; + u32 delta; + u32 lpfK; + u32 eg_gain; + u32 left_gain; + u32 right_gain; + u32 eff1_gain; + u32 eff2_gain; + u32 eff3_gain; + u32 lpfD1; + u32 lpfD2; +} ymfpci_playback_bank_t; + +typedef struct stru_ymfpci_capture_bank { + u32 base; /* 32-bit address (aligned at 4) */ + u32 loop_end; /* size in BYTES (aligned at 4) */ + u32 start; /* 32-bit offset */ + u32 num_of_loops; /* counter */ +} ymfpci_capture_bank_t; + +typedef struct stru_ymfpci_effect_bank { + u32 base; /* 32-bit address */ + u32 loop_end; /* 32-bit offset */ + u32 start; /* 32-bit offset */ + u32 temp; +} ymfpci_effect_bank_t; + +typedef struct ymf_voice ymfpci_voice_t; +/* + * Throughout the code Yaroslav names YMF unit pointer "codec" + * even though it does not correspond to any codec. Must be historic. + * We replace it with "unit" over time. + * AC97 parts use "codec" to denote a codec, naturally. + */ +typedef struct ymf_unit ymfpci_t; + +typedef enum { + YMFPCI_PCM, + YMFPCI_SYNTH, + YMFPCI_MIDI +} ymfpci_voice_type_t; + +struct ymf_voice { + // ymfpci_t *codec; + int number; + char use, pcm, synth, midi; // bool + ymfpci_playback_bank_t *bank; + struct ymf_pcm *ypcm; + dma_addr_t bank_ba; +}; + +struct ymf_capture { + // struct ymf_unit *unit; + int use; + ymfpci_capture_bank_t *bank; + struct ymf_pcm *ypcm; +}; + +struct ymf_unit { + u8 rev; /* PCI revision */ + void __iomem *reg_area_virt; + void *dma_area_va; + dma_addr_t dma_area_ba; + unsigned int dma_area_size; + + dma_addr_t bank_base_capture; + dma_addr_t bank_base_effect; + dma_addr_t work_base; + unsigned int work_size; + + u32 *ctrl_playback; + dma_addr_t ctrl_playback_ba; + ymfpci_playback_bank_t *bank_playback[YDSXG_PLAYBACK_VOICES][2]; + ymfpci_capture_bank_t *bank_capture[YDSXG_CAPTURE_VOICES][2]; + ymfpci_effect_bank_t *bank_effect[YDSXG_EFFECT_VOICES][2]; + + int start_count; + int suspended; + + u32 active_bank; + struct ymf_voice voices[YDSXG_PLAYBACK_VOICES]; + struct ymf_capture capture[YDSXG_CAPTURE_VOICES]; + + struct ac97_codec *ac97_codec[NR_AC97]; + u16 ac97_features; + + struct pci_dev *pci; + +#ifdef CONFIG_SOUND_YMFPCI_LEGACY + /* legacy hardware resources */ + unsigned int iosynth, iomidi; + struct address_info opl3_data, mpu_data; +#endif + + spinlock_t reg_lock; + spinlock_t voice_lock; + spinlock_t ac97_lock; + + /* soundcore stuff */ + int dev_audio; + struct semaphore open_sem; + + struct list_head ymf_devs; + struct list_head states; /* List of states for this unit */ +}; + +struct ymf_dmabuf { + dma_addr_t dma_addr; + void *rawbuf; + unsigned buforder; + + /* OSS buffer management stuff */ + unsigned numfrag; + unsigned fragshift; + + /* our buffer acts like a circular ring */ + unsigned hwptr; /* where dma last started */ + unsigned swptr; /* where driver last clear/filled */ + int count; /* fill count */ + unsigned total_bytes; /* total bytes dmaed by hardware */ + + wait_queue_head_t wait; /* put process on wait queue when no more space in buffer */ + + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dmasize; /* Total rawbuf[] size */ + + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; +}; + +struct ymf_pcm_format { + int format; /* OSS format */ + int rate; /* rate in Hz */ + int voices; /* number of voices */ + int shift; /* redundant, computed from the above */ +}; + +typedef enum { + PLAYBACK_VOICE, + CAPTURE_REC, + CAPTURE_AC97, + EFFECT_DRY_LEFT, + EFFECT_DRY_RIGHT, + EFFECT_EFF1, + EFFECT_EFF2, + EFFECT_EFF3 +} ymfpci_pcm_type_t; + +/* This is variant record, but we hate unions. Little waste on pointers []. */ +struct ymf_pcm { + ymfpci_pcm_type_t type; + struct ymf_state *state; + + ymfpci_voice_t *voices[2]; + int capture_bank_number; + + struct ymf_dmabuf dmabuf; + int running; + int spdif; +}; + +/* + * "Software" or virtual channel, an instance of opened /dev/dsp. + * It may have two physical channels (pcms) for duplex operations. + */ + +struct ymf_state { + struct list_head chain; + struct ymf_unit *unit; /* backpointer */ + struct ymf_pcm rpcm, wpcm; + struct ymf_pcm_format format; +}; + +#endif /* __YMFPCI_H */ diff --git a/sound/oss/ymfpci_image.h b/sound/oss/ymfpci_image.h new file mode 100644 index 000000000000..112f2fff6c8e --- /dev/null +++ b/sound/oss/ymfpci_image.h @@ -0,0 +1,1565 @@ +#ifndef _HWMCODE_ +#define _HWMCODE_ + +static u32 DspInst[YDSXG_DSPLENGTH / 4] = { + 0x00000081, 0x000001a4, 0x0000000a, 0x0000002f, + 0x00080253, 0x01800317, 0x0000407b, 0x0000843f, + 0x0001483c, 0x0001943c, 0x0005d83c, 0x00001c3c, + 0x0000c07b, 0x00050c3f, 0x0121503c, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + +static u32 CntrlInst[YDSXG_CTRLLENGTH / 4] = { + 0x000007, 0x240007, 0x0C0007, 0x1C0007, + 0x060007, 0x700002, 0x000020, 0x030040, + 0x007104, 0x004286, 0x030040, 0x000F0D, + 0x000810, 0x20043A, 0x000282, 0x00020D, + 0x000810, 0x20043A, 0x001282, 0x200E82, + 0x001A82, 0x032D0D, 0x000810, 0x10043A, + 0x02D38D, 0x000810, 0x18043A, 0x00010D, + 0x020015, 0x0000FD, 0x000020, 0x038860, + 0x039060, 0x038060, 0x038040, 0x038040, + 0x038040, 0x018040, 0x000A7D, 0x038040, + 0x038040, 0x018040, 0x200402, 0x000882, + 0x08001A, 0x000904, 0x015986, 0x000007, + 0x260007, 0x000007, 0x000007, 0x018A06, + 0x000007, 0x030C8D, 0x000810, 0x18043A, + 0x260007, 0x00087D, 0x018042, 0x00160A, + 0x04A206, 0x000007, 0x00218D, 0x000810, + 0x08043A, 0x21C206, 0x000007, 0x0007FD, + 0x018042, 0x08000A, 0x000904, 0x029386, + 0x000195, 0x090D04, 0x000007, 0x000820, + 0x0000F5, 0x000B7D, 0x01F060, 0x0000FD, + 0x032206, 0x018040, 0x000A7D, 0x038042, + 0x13804A, 0x18000A, 0x001820, 0x059060, + 0x058860, 0x018040, 0x0000FD, 0x018042, + 0x70000A, 0x000115, 0x071144, 0x032386, + 0x030000, 0x007020, 0x034A06, 0x018040, + 0x00348D, 0x000810, 0x08043A, 0x21EA06, + 0x000007, 0x02D38D, 0x000810, 0x18043A, + 0x018206, 0x000007, 0x240007, 0x000F8D, + 0x000810, 0x00163A, 0x002402, 0x005C02, + 0x0028FD, 0x000020, 0x018040, 0x08000D, + 0x000815, 0x510984, 0x000007, 0x00004D, + 0x000E5D, 0x000E02, 0x00418D, 0x000810, + 0x08043A, 0x2C8A06, 0x000007, 0x00008D, + 0x000924, 0x000F02, 0x00458D, 0x000810, + 0x08043A, 0x2C8A06, 0x000007, 0x00387D, + 0x018042, 0x08000A, 0x001015, 0x010984, + 0x018386, 0x000007, 0x01AA06, 0x000007, + 0x0008FD, 0x018042, 0x18000A, 0x001904, + 0x218086, 0x280007, 0x001810, 0x28043A, + 0x280C02, 0x00000D, 0x000810, 0x28143A, + 0x08808D, 0x000820, 0x0002FD, 0x018040, + 0x200007, 0x00020D, 0x189904, 0x000007, + 0x00402D, 0x0000BD, 0x0002FD, 0x018042, + 0x08000A, 0x000904, 0x055A86, 0x000007, + 0x000100, 0x000A20, 0x00047D, 0x018040, + 0x018042, 0x20000A, 0x003015, 0x012144, + 0x034986, 0x000007, 0x002104, 0x034986, + 0x000007, 0x000F8D, 0x000810, 0x280C3A, + 0x023944, 0x06C986, 0x000007, 0x001810, + 0x28043A, 0x08810D, 0x000820, 0x0002FD, + 0x018040, 0x200007, 0x002810, 0x78003A, + 0x00688D, 0x000810, 0x08043A, 0x288A06, + 0x000007, 0x00400D, 0x001015, 0x189904, + 0x292904, 0x393904, 0x000007, 0x060206, + 0x000007, 0x0004F5, 0x00007D, 0x000020, + 0x00008D, 0x010860, 0x018040, 0x00047D, + 0x038042, 0x21804A, 0x18000A, 0x021944, + 0x215886, 0x000007, 0x004075, 0x71F104, + 0x000007, 0x010042, 0x28000A, 0x002904, + 0x212086, 0x000007, 0x003C0D, 0x30A904, + 0x000007, 0x00077D, 0x018042, 0x08000A, + 0x000904, 0x07DA86, 0x00057D, 0x002820, + 0x03B060, 0x07F206, 0x018040, 0x003020, + 0x03A860, 0x018040, 0x0002FD, 0x018042, + 0x08000A, 0x000904, 0x07FA86, 0x000007, + 0x00057D, 0x018042, 0x28040A, 0x000E8D, + 0x000810, 0x280C3A, 0x00000D, 0x000810, + 0x28143A, 0x09000D, 0x000820, 0x0002FD, + 0x018040, 0x200007, 0x003DFD, 0x000020, + 0x018040, 0x00107D, 0x008D8D, 0x000810, + 0x08043A, 0x288A06, 0x000007, 0x000815, + 0x08001A, 0x010984, 0x095186, 0x00137D, + 0x200500, 0x280F20, 0x338F60, 0x3B8F60, + 0x438F60, 0x4B8F60, 0x538F60, 0x5B8F60, + 0x038A60, 0x018040, 0x007FBD, 0x383DC4, + 0x000007, 0x001A7D, 0x001375, 0x018042, + 0x09004A, 0x10000A, 0x0B8D04, 0x139504, + 0x000007, 0x000820, 0x019060, 0x001104, + 0x212086, 0x010040, 0x0017FD, 0x018042, + 0x08000A, 0x000904, 0x212286, 0x000007, + 0x00197D, 0x038042, 0x09804A, 0x10000A, + 0x000924, 0x001664, 0x0011FD, 0x038042, + 0x2B804A, 0x19804A, 0x00008D, 0x218944, + 0x000007, 0x002244, 0x0AE186, 0x000007, + 0x001A64, 0x002A24, 0x00197D, 0x080102, + 0x100122, 0x000820, 0x039060, 0x018040, + 0x003DFD, 0x00008D, 0x000820, 0x018040, + 0x001375, 0x001A7D, 0x010042, 0x09804A, + 0x10000A, 0x00021D, 0x0189E4, 0x2992E4, + 0x309144, 0x000007, 0x00060D, 0x000A15, + 0x000C1D, 0x001025, 0x00A9E4, 0x012BE4, + 0x000464, 0x01B3E4, 0x0232E4, 0x000464, + 0x000464, 0x000464, 0x000464, 0x00040D, + 0x08B1C4, 0x000007, 0x000820, 0x000BF5, + 0x030040, 0x00197D, 0x038042, 0x09804A, + 0x000A24, 0x08000A, 0x080E64, 0x000007, + 0x100122, 0x000820, 0x031060, 0x010040, + 0x0064AC, 0x00027D, 0x000020, 0x018040, + 0x00107D, 0x018042, 0x0011FD, 0x3B804A, + 0x09804A, 0x20000A, 0x000095, 0x1A1144, + 0x00A144, 0x0D2086, 0x00040D, 0x00B984, + 0x0D2186, 0x0018FD, 0x018042, 0x0010FD, + 0x09804A, 0x28000A, 0x000095, 0x010924, + 0x002A64, 0x0D1186, 0x000007, 0x002904, + 0x0D2286, 0x000007, 0x0D2A06, 0x080002, + 0x00008D, 0x00387D, 0x000820, 0x018040, + 0x00127D, 0x018042, 0x10000A, 0x003904, + 0x0DD186, 0x00080D, 0x7FFFB5, 0x00B984, + 0x0DA186, 0x000025, 0x0E7A06, 0x00002D, + 0x000015, 0x00082D, 0x02C78D, 0x000820, + 0x0EC206, 0x00000D, 0x7F8035, 0x00B984, + 0x0E7186, 0x400025, 0x00008D, 0x110944, + 0x000007, 0x00018D, 0x109504, 0x000007, + 0x009164, 0x000424, 0x000424, 0x000424, + 0x100102, 0x280002, 0x02C68D, 0x000820, + 0x0EC206, 0x00018D, 0x00042D, 0x00008D, + 0x109504, 0x000007, 0x00020D, 0x109184, + 0x000007, 0x02C70D, 0x000820, 0x00008D, + 0x0038FD, 0x018040, 0x003BFD, 0x001020, + 0x03A860, 0x000815, 0x313184, 0x212184, + 0x000007, 0x03B060, 0x03A060, 0x018040, + 0x0022FD, 0x000095, 0x010924, 0x000424, + 0x000424, 0x001264, 0x100102, 0x000820, + 0x039060, 0x018040, 0x001924, 0x00FB8D, + 0x00397D, 0x000820, 0x058040, 0x038042, + 0x09844A, 0x000606, 0x08040A, 0x000424, + 0x000424, 0x00117D, 0x018042, 0x08000A, + 0x000A24, 0x280502, 0x280C02, 0x09800D, + 0x000820, 0x0002FD, 0x018040, 0x200007, + 0x0022FD, 0x018042, 0x08000A, 0x000095, + 0x280DC4, 0x011924, 0x00197D, 0x018042, + 0x0011FD, 0x09804A, 0x10000A, 0x0000B5, + 0x113144, 0x0A8D04, 0x000007, 0x080A44, + 0x129504, 0x000007, 0x0023FD, 0x001020, + 0x038040, 0x101244, 0x000007, 0x000820, + 0x039060, 0x018040, 0x0002FD, 0x018042, + 0x08000A, 0x000904, 0x10FA86, 0x000007, + 0x003BFD, 0x000100, 0x000A10, 0x0B807A, + 0x13804A, 0x090984, 0x000007, 0x000095, + 0x013D04, 0x118086, 0x10000A, 0x100002, + 0x090984, 0x000007, 0x038042, 0x11804A, + 0x090D04, 0x000007, 0x10000A, 0x090D84, + 0x000007, 0x00257D, 0x000820, 0x018040, + 0x00010D, 0x000810, 0x28143A, 0x00127D, + 0x018042, 0x20000A, 0x00197D, 0x018042, + 0x00117D, 0x31804A, 0x10000A, 0x003124, + 0x01280D, 0x00397D, 0x000820, 0x058040, + 0x038042, 0x09844A, 0x000606, 0x08040A, + 0x300102, 0x003124, 0x000424, 0x000424, + 0x001224, 0x280502, 0x001A4C, 0x130186, + 0x700002, 0x00002D, 0x030000, 0x00387D, + 0x018042, 0x10000A, 0x132A06, 0x002124, + 0x0000AD, 0x100002, 0x00010D, 0x000924, + 0x006B24, 0x01368D, 0x00397D, 0x000820, + 0x058040, 0x038042, 0x09844A, 0x000606, + 0x08040A, 0x003264, 0x00008D, 0x000A24, + 0x001020, 0x00227D, 0x018040, 0x013C0D, + 0x000810, 0x08043A, 0x29D206, 0x000007, + 0x002820, 0x00207D, 0x018040, 0x00117D, + 0x038042, 0x13804A, 0x33800A, 0x00387D, + 0x018042, 0x08000A, 0x000904, 0x163A86, + 0x000007, 0x00008D, 0x030964, 0x01478D, + 0x00397D, 0x000820, 0x058040, 0x038042, + 0x09844A, 0x000606, 0x08040A, 0x380102, + 0x000424, 0x000424, 0x001224, 0x0002FD, + 0x018042, 0x08000A, 0x000904, 0x14A286, + 0x000007, 0x280502, 0x001A4C, 0x163986, + 0x000007, 0x032164, 0x00632C, 0x003DFD, + 0x018042, 0x08000A, 0x000095, 0x090904, + 0x000007, 0x000820, 0x001A4C, 0x156186, + 0x018040, 0x030000, 0x157A06, 0x002124, + 0x00010D, 0x000924, 0x006B24, 0x015B8D, + 0x00397D, 0x000820, 0x058040, 0x038042, + 0x09844A, 0x000606, 0x08040A, 0x003A64, + 0x000095, 0x001224, 0x0002FD, 0x018042, + 0x08000A, 0x000904, 0x15DA86, 0x000007, + 0x01628D, 0x000810, 0x08043A, 0x29D206, + 0x000007, 0x14D206, 0x000007, 0x007020, + 0x08010A, 0x10012A, 0x0020FD, 0x038860, + 0x039060, 0x018040, 0x00227D, 0x018042, + 0x003DFD, 0x08000A, 0x31844A, 0x000904, + 0x16D886, 0x18008B, 0x00008D, 0x189904, + 0x00312C, 0x17AA06, 0x000007, 0x00324C, + 0x173386, 0x000007, 0x001904, 0x173086, + 0x000007, 0x000095, 0x199144, 0x00222C, + 0x003124, 0x00636C, 0x000E3D, 0x001375, + 0x000BFD, 0x010042, 0x09804A, 0x10000A, + 0x038AEC, 0x0393EC, 0x00224C, 0x17A986, + 0x000007, 0x00008D, 0x189904, 0x00226C, + 0x00322C, 0x30050A, 0x301DAB, 0x002083, + 0x0018FD, 0x018042, 0x08000A, 0x018924, + 0x300502, 0x001083, 0x001875, 0x010042, + 0x10000A, 0x00008D, 0x010924, 0x001375, + 0x330542, 0x330CCB, 0x332CCB, 0x3334CB, + 0x333CCB, 0x3344CB, 0x334CCB, 0x3354CB, + 0x305C8B, 0x006083, 0x0002F5, 0x010042, + 0x08000A, 0x000904, 0x187A86, 0x000007, + 0x001E2D, 0x0005FD, 0x018042, 0x08000A, + 0x028924, 0x280502, 0x00060D, 0x000810, + 0x280C3A, 0x00008D, 0x000810, 0x28143A, + 0x0A808D, 0x000820, 0x0002F5, 0x010040, + 0x220007, 0x001275, 0x030042, 0x21004A, + 0x00008D, 0x1A0944, 0x000007, 0x01980D, + 0x000810, 0x08043A, 0x2B2206, 0x000007, + 0x0001F5, 0x030042, 0x0D004A, 0x10000A, + 0x089144, 0x000007, 0x000820, 0x010040, + 0x0025F5, 0x0A3144, 0x000007, 0x000820, + 0x032860, 0x030040, 0x00217D, 0x038042, + 0x0B804A, 0x10000A, 0x000820, 0x031060, + 0x030040, 0x00008D, 0x000124, 0x00012C, + 0x000E64, 0x001A64, 0x00636C, 0x08010A, + 0x10012A, 0x000820, 0x031060, 0x030040, + 0x0020FD, 0x018042, 0x08000A, 0x00227D, + 0x018042, 0x10000A, 0x000820, 0x031060, + 0x030040, 0x00197D, 0x018042, 0x08000A, + 0x0022FD, 0x038042, 0x10000A, 0x000820, + 0x031060, 0x030040, 0x090D04, 0x000007, + 0x000820, 0x030040, 0x038042, 0x0B804A, + 0x10000A, 0x000820, 0x031060, 0x030040, + 0x038042, 0x13804A, 0x19804A, 0x110D04, + 0x198D04, 0x000007, 0x08000A, 0x001020, + 0x031860, 0x030860, 0x030040, 0x00008D, + 0x0B0944, 0x000007, 0x000820, 0x010040, + 0x0005F5, 0x030042, 0x08000A, 0x000820, + 0x010040, 0x0000F5, 0x010042, 0x08000A, + 0x000904, 0x1C6086, 0x001E75, 0x030042, + 0x01044A, 0x000C0A, 0x1C7206, 0x000007, + 0x000402, 0x000C02, 0x00177D, 0x001AF5, + 0x018042, 0x03144A, 0x031C4A, 0x03244A, + 0x032C4A, 0x03344A, 0x033C4A, 0x03444A, + 0x004C0A, 0x00043D, 0x0013F5, 0x001AFD, + 0x030042, 0x0B004A, 0x1B804A, 0x13804A, + 0x20000A, 0x089144, 0x19A144, 0x0389E4, + 0x0399EC, 0x005502, 0x005D0A, 0x030042, + 0x0B004A, 0x1B804A, 0x13804A, 0x20000A, + 0x089144, 0x19A144, 0x0389E4, 0x0399EC, + 0x006502, 0x006D0A, 0x030042, 0x0B004A, + 0x19004A, 0x2B804A, 0x13804A, 0x21804A, + 0x30000A, 0x089144, 0x19A144, 0x2AB144, + 0x0389E4, 0x0399EC, 0x007502, 0x007D0A, + 0x03A9E4, 0x000702, 0x00107D, 0x000415, + 0x018042, 0x08000A, 0x0109E4, 0x000F02, + 0x002AF5, 0x0019FD, 0x010042, 0x09804A, + 0x10000A, 0x000934, 0x001674, 0x0029F5, + 0x010042, 0x10000A, 0x00917C, 0x002075, + 0x010042, 0x08000A, 0x000904, 0x1ED286, + 0x0026F5, 0x0027F5, 0x030042, 0x09004A, + 0x10000A, 0x000A3C, 0x00167C, 0x001A75, + 0x000BFD, 0x010042, 0x51804A, 0x48000A, + 0x160007, 0x001075, 0x010042, 0x282C0A, + 0x281D12, 0x282512, 0x001F32, 0x1E0007, + 0x0E0007, 0x001975, 0x010042, 0x002DF5, + 0x0D004A, 0x10000A, 0x009144, 0x1FB286, + 0x010042, 0x28340A, 0x000E5D, 0x00008D, + 0x000375, 0x000820, 0x010040, 0x05D2F4, + 0x54D104, 0x00735C, 0x205386, 0x000007, + 0x0C0007, 0x080007, 0x0A0007, 0x02040D, + 0x000810, 0x08043A, 0x332206, 0x000007, + 0x205A06, 0x000007, 0x080007, 0x002275, + 0x010042, 0x20000A, 0x002104, 0x212086, + 0x001E2D, 0x0002F5, 0x010042, 0x08000A, + 0x000904, 0x209286, 0x000007, 0x002010, + 0x30043A, 0x00057D, 0x0180C3, 0x08000A, + 0x028924, 0x280502, 0x280C02, 0x0A810D, + 0x000820, 0x0002F5, 0x010040, 0x220007, + 0x0004FD, 0x018042, 0x70000A, 0x030000, + 0x007020, 0x06FA06, 0x018040, 0x02180D, + 0x000810, 0x08043A, 0x2B2206, 0x000007, + 0x0002FD, 0x018042, 0x08000A, 0x000904, + 0x218A86, 0x000007, 0x01F206, 0x000007, + 0x000875, 0x0009FD, 0x00010D, 0x220A06, + 0x000295, 0x000B75, 0x00097D, 0x00000D, + 0x000515, 0x010042, 0x18000A, 0x001904, + 0x287886, 0x0006F5, 0x001020, 0x010040, + 0x0004F5, 0x000820, 0x010040, 0x000775, + 0x010042, 0x09804A, 0x10000A, 0x001124, + 0x000904, 0x22BA86, 0x000815, 0x080102, + 0x101204, 0x22DA06, 0x000575, 0x081204, + 0x000007, 0x100102, 0x000575, 0x000425, + 0x021124, 0x100102, 0x000820, 0x031060, + 0x010040, 0x001924, 0x287886, 0x00008D, + 0x000464, 0x009D04, 0x278886, 0x180102, + 0x000575, 0x010042, 0x28040A, 0x00018D, + 0x000924, 0x280D02, 0x00000D, 0x000924, + 0x281502, 0x10000D, 0x000820, 0x0002F5, + 0x010040, 0x200007, 0x001175, 0x0002FD, + 0x018042, 0x08000A, 0x000904, 0x23C286, + 0x000007, 0x000100, 0x080B20, 0x130B60, + 0x1B0B60, 0x030A60, 0x010040, 0x050042, + 0x3D004A, 0x35004A, 0x2D004A, 0x20000A, + 0x0006F5, 0x010042, 0x28140A, 0x0004F5, + 0x010042, 0x08000A, 0x000315, 0x010D04, + 0x24CA86, 0x004015, 0x000095, 0x010D04, + 0x24B886, 0x100022, 0x10002A, 0x24E206, + 0x000007, 0x333104, 0x2AA904, 0x000007, + 0x032124, 0x280502, 0x001124, 0x000424, + 0x000424, 0x003224, 0x00292C, 0x00636C, + 0x25F386, 0x000007, 0x02B164, 0x000464, + 0x000464, 0x00008D, 0x000A64, 0x280D02, + 0x10008D, 0x000820, 0x0002F5, 0x010040, + 0x220007, 0x00008D, 0x38B904, 0x000007, + 0x03296C, 0x30010A, 0x0002F5, 0x010042, + 0x08000A, 0x000904, 0x25BA86, 0x000007, + 0x02312C, 0x28050A, 0x00008D, 0x01096C, + 0x280D0A, 0x10010D, 0x000820, 0x0002F5, + 0x010040, 0x220007, 0x001124, 0x000424, + 0x000424, 0x003224, 0x300102, 0x032944, + 0x267A86, 0x000007, 0x300002, 0x0004F5, + 0x010042, 0x08000A, 0x000315, 0x010D04, + 0x26C086, 0x003124, 0x000464, 0x300102, + 0x0002F5, 0x010042, 0x08000A, 0x000904, + 0x26CA86, 0x000007, 0x003124, 0x300502, + 0x003924, 0x300583, 0x000883, 0x0005F5, + 0x010042, 0x28040A, 0x00008D, 0x008124, + 0x280D02, 0x00008D, 0x008124, 0x281502, + 0x10018D, 0x000820, 0x0002F5, 0x010040, + 0x220007, 0x001025, 0x000575, 0x030042, + 0x09004A, 0x10000A, 0x0A0904, 0x121104, + 0x000007, 0x001020, 0x050860, 0x050040, + 0x0006FD, 0x018042, 0x09004A, 0x10000A, + 0x0000A5, 0x0A0904, 0x121104, 0x000007, + 0x000820, 0x019060, 0x010040, 0x0002F5, + 0x010042, 0x08000A, 0x000904, 0x284286, + 0x000007, 0x230A06, 0x000007, 0x000606, + 0x000007, 0x0002F5, 0x010042, 0x08000A, + 0x000904, 0x289286, 0x000007, 0x000100, + 0x080B20, 0x138B60, 0x1B8B60, 0x238B60, + 0x2B8B60, 0x338B60, 0x3B8B60, 0x438B60, + 0x4B8B60, 0x538B60, 0x5B8B60, 0x638B60, + 0x6B8B60, 0x738B60, 0x7B8B60, 0x038F60, + 0x0B8F60, 0x138F60, 0x1B8F60, 0x238F60, + 0x2B8F60, 0x338F60, 0x3B8F60, 0x438F60, + 0x4B8F60, 0x538F60, 0x5B8F60, 0x638F60, + 0x6B8F60, 0x738F60, 0x7B8F60, 0x038A60, + 0x000606, 0x018040, 0x00008D, 0x000A64, + 0x280D02, 0x000A24, 0x00027D, 0x018042, + 0x10000A, 0x001224, 0x0003FD, 0x018042, + 0x08000A, 0x000904, 0x2A8286, 0x000007, + 0x00018D, 0x000A24, 0x000464, 0x000464, + 0x080102, 0x000924, 0x000424, 0x000424, + 0x100102, 0x02000D, 0x009144, 0x2AD986, + 0x000007, 0x0001FD, 0x018042, 0x08000A, + 0x000A44, 0x2ABB86, 0x018042, 0x0A000D, + 0x000820, 0x0002FD, 0x018040, 0x200007, + 0x00027D, 0x001020, 0x000606, 0x018040, + 0x0002F5, 0x010042, 0x08000A, 0x000904, + 0x2B2A86, 0x000007, 0x00037D, 0x018042, + 0x08000A, 0x000904, 0x2B5A86, 0x000007, + 0x000075, 0x002E7D, 0x010042, 0x0B804A, + 0x000020, 0x000904, 0x000686, 0x010040, + 0x31844A, 0x30048B, 0x000883, 0x00008D, + 0x000810, 0x28143A, 0x00008D, 0x000810, + 0x280C3A, 0x000675, 0x010042, 0x08000A, + 0x003815, 0x010924, 0x280502, 0x0B000D, + 0x000820, 0x0002F5, 0x010040, 0x000606, + 0x220007, 0x000464, 0x000464, 0x000606, + 0x000007, 0x000134, 0x007F8D, 0x00093C, + 0x281D12, 0x282512, 0x001F32, 0x0E0007, + 0x00010D, 0x00037D, 0x000820, 0x018040, + 0x05D2F4, 0x000007, 0x080007, 0x00037D, + 0x018042, 0x08000A, 0x000904, 0x2D0286, + 0x000007, 0x000606, 0x000007, 0x000007, + 0x000012, 0x100007, 0x320007, 0x600007, + 0x100080, 0x48001A, 0x004904, 0x2D6186, + 0x000007, 0x001210, 0x58003A, 0x000145, + 0x5C5D04, 0x000007, 0x000080, 0x48001A, + 0x004904, 0x2DB186, 0x000007, 0x001210, + 0x50003A, 0x005904, 0x2E0886, 0x000045, + 0x0000C5, 0x7FFFF5, 0x7FFF7D, 0x07D524, + 0x004224, 0x500102, 0x200502, 0x000082, + 0x40001A, 0x004104, 0x2E3986, 0x000007, + 0x003865, 0x40001A, 0x004020, 0x00104D, + 0x04C184, 0x301B86, 0x000040, 0x040007, + 0x000165, 0x000145, 0x004020, 0x000040, + 0x000765, 0x080080, 0x40001A, 0x004104, + 0x2EC986, 0x000007, 0x001210, 0x40003A, + 0x004104, 0x2F2286, 0x00004D, 0x0000CD, + 0x004810, 0x20043A, 0x000882, 0x40001A, + 0x004104, 0x2F3186, 0x000007, 0x004820, + 0x005904, 0x300886, 0x000040, 0x0007E5, + 0x200480, 0x2816A0, 0x3216E0, 0x3A16E0, + 0x4216E0, 0x021260, 0x000040, 0x000032, + 0x400075, 0x00007D, 0x07D574, 0x200512, + 0x000082, 0x40001A, 0x004104, 0x2FE186, + 0x000007, 0x037206, 0x640007, 0x060007, + 0x0000E5, 0x000020, 0x000040, 0x000A65, + 0x000020, 0x020040, 0x020040, 0x000040, + 0x000165, 0x000042, 0x70000A, 0x007104, + 0x30A286, 0x000007, 0x018206, 0x640007, + 0x050000, 0x007020, 0x000040, 0x037206, + 0x640007, 0x000007, 0x00306D, 0x028860, + 0x029060, 0x08000A, 0x028860, 0x008040, + 0x100012, 0x00100D, 0x009184, 0x314186, + 0x000E0D, 0x009184, 0x325186, 0x000007, + 0x300007, 0x001020, 0x003B6D, 0x008040, + 0x000080, 0x08001A, 0x000904, 0x316186, + 0x000007, 0x001220, 0x000DED, 0x008040, + 0x008042, 0x10000A, 0x40000D, 0x109544, + 0x000007, 0x001020, 0x000DED, 0x008040, + 0x008042, 0x20040A, 0x000082, 0x08001A, + 0x000904, 0x31F186, 0x000007, 0x003B6D, + 0x008042, 0x08000A, 0x000E15, 0x010984, + 0x329B86, 0x600007, 0x08001A, 0x000C15, + 0x010984, 0x328386, 0x000020, 0x1A0007, + 0x0002ED, 0x008040, 0x620007, 0x00306D, + 0x028042, 0x0A804A, 0x000820, 0x0A804A, + 0x000606, 0x10804A, 0x000007, 0x282512, + 0x001F32, 0x05D2F4, 0x54D104, 0x00735C, + 0x000786, 0x000007, 0x0C0007, 0x0A0007, + 0x1C0007, 0x003465, 0x020040, 0x004820, + 0x025060, 0x40000A, 0x024060, 0x000040, + 0x454944, 0x000007, 0x004020, 0x003AE5, + 0x000040, 0x0028E5, 0x000042, 0x48000A, + 0x004904, 0x386886, 0x002C65, 0x000042, + 0x40000A, 0x0000D5, 0x454104, 0x000007, + 0x000655, 0x054504, 0x34F286, 0x0001D5, + 0x054504, 0x34F086, 0x002B65, 0x000042, + 0x003AE5, 0x50004A, 0x40000A, 0x45C3D4, + 0x000007, 0x454504, 0x000007, 0x0000CD, + 0x444944, 0x000007, 0x454504, 0x000007, + 0x00014D, 0x554944, 0x000007, 0x045144, + 0x34E986, 0x002C65, 0x000042, 0x48000A, + 0x4CD104, 0x000007, 0x04C144, 0x34F386, + 0x000007, 0x160007, 0x002CE5, 0x040042, + 0x40000A, 0x004020, 0x000040, 0x002965, + 0x000042, 0x40000A, 0x004104, 0x356086, + 0x000007, 0x002402, 0x36A206, 0x005C02, + 0x0025E5, 0x000042, 0x40000A, 0x004274, + 0x002AE5, 0x000042, 0x40000A, 0x004274, + 0x500112, 0x0029E5, 0x000042, 0x40000A, + 0x004234, 0x454104, 0x000007, 0x004020, + 0x000040, 0x003EE5, 0x000020, 0x000040, + 0x002DE5, 0x400152, 0x50000A, 0x045144, + 0x364A86, 0x0000C5, 0x003EE5, 0x004020, + 0x000040, 0x002BE5, 0x000042, 0x40000A, + 0x404254, 0x000007, 0x002AE5, 0x004020, + 0x000040, 0x500132, 0x040134, 0x005674, + 0x0029E5, 0x020042, 0x42000A, 0x000042, + 0x50000A, 0x05417C, 0x0028E5, 0x000042, + 0x48000A, 0x0000C5, 0x4CC144, 0x371086, + 0x0026E5, 0x0027E5, 0x020042, 0x40004A, + 0x50000A, 0x00423C, 0x00567C, 0x0028E5, + 0x004820, 0x000040, 0x281D12, 0x282512, + 0x001F72, 0x002965, 0x000042, 0x40000A, + 0x004104, 0x37AA86, 0x0E0007, 0x160007, + 0x1E0007, 0x003EE5, 0x000042, 0x40000A, + 0x004104, 0x37E886, 0x002D65, 0x000042, + 0x28340A, 0x003465, 0x020042, 0x42004A, + 0x004020, 0x4A004A, 0x50004A, 0x05D2F4, + 0x54D104, 0x00735C, 0x385186, 0x000007, + 0x000606, 0x080007, 0x0C0007, 0x080007, + 0x0A0007, 0x0001E5, 0x020045, 0x004020, + 0x000060, 0x000365, 0x000040, 0x002E65, + 0x001A20, 0x0A1A60, 0x000040, 0x003465, + 0x020042, 0x42004A, 0x004020, 0x4A004A, + 0x000606, 0x50004A, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000 +}; + +// -------------------------------------------- +// DS-1E Controller InstructionRAM Code +// 1999/06/21 +// Buf441 slot is Enabled. +// -------------------------------------------- +// 04/09 creat +// 04/12 stop nise fix +// 06/21 WorkingOff timming +static u32 CntrlInst1E[YDSXG_CTRLLENGTH / 4] = { + 0x000007, 0x240007, 0x0C0007, 0x1C0007, + 0x060007, 0x700002, 0x000020, 0x030040, + 0x007104, 0x004286, 0x030040, 0x000F0D, + 0x000810, 0x20043A, 0x000282, 0x00020D, + 0x000810, 0x20043A, 0x001282, 0x200E82, + 0x00800D, 0x000810, 0x20043A, 0x001A82, + 0x03460D, 0x000810, 0x10043A, 0x02EC0D, + 0x000810, 0x18043A, 0x00010D, 0x020015, + 0x0000FD, 0x000020, 0x038860, 0x039060, + 0x038060, 0x038040, 0x038040, 0x038040, + 0x018040, 0x000A7D, 0x038040, 0x038040, + 0x018040, 0x200402, 0x000882, 0x08001A, + 0x000904, 0x017186, 0x000007, 0x260007, + 0x400007, 0x000007, 0x03258D, 0x000810, + 0x18043A, 0x260007, 0x284402, 0x00087D, + 0x018042, 0x00160A, 0x05A206, 0x000007, + 0x440007, 0x00230D, 0x000810, 0x08043A, + 0x22FA06, 0x000007, 0x0007FD, 0x018042, + 0x08000A, 0x000904, 0x02AB86, 0x000195, + 0x090D04, 0x000007, 0x000820, 0x0000F5, + 0x000B7D, 0x01F060, 0x0000FD, 0x033A06, + 0x018040, 0x000A7D, 0x038042, 0x13804A, + 0x18000A, 0x001820, 0x059060, 0x058860, + 0x018040, 0x0000FD, 0x018042, 0x70000A, + 0x000115, 0x071144, 0x033B86, 0x030000, + 0x007020, 0x036206, 0x018040, 0x00360D, + 0x000810, 0x08043A, 0x232206, 0x000007, + 0x02EC0D, 0x000810, 0x18043A, 0x019A06, + 0x000007, 0x240007, 0x000F8D, 0x000810, + 0x00163A, 0x002402, 0x005C02, 0x0028FD, + 0x000020, 0x018040, 0x08000D, 0x000815, + 0x510984, 0x000007, 0x00004D, 0x000E5D, + 0x000E02, 0x00430D, 0x000810, 0x08043A, + 0x2E1206, 0x000007, 0x00008D, 0x000924, + 0x000F02, 0x00470D, 0x000810, 0x08043A, + 0x2E1206, 0x000007, 0x480480, 0x001210, + 0x28043A, 0x00778D, 0x000810, 0x280C3A, + 0x00068D, 0x000810, 0x28143A, 0x284402, + 0x03258D, 0x000810, 0x18043A, 0x07FF8D, + 0x000820, 0x0002FD, 0x018040, 0x260007, + 0x200007, 0x0002FD, 0x018042, 0x08000A, + 0x000904, 0x051286, 0x000007, 0x240007, + 0x02EC0D, 0x000810, 0x18043A, 0x00387D, + 0x018042, 0x08000A, 0x001015, 0x010984, + 0x019B86, 0x000007, 0x01B206, 0x000007, + 0x0008FD, 0x018042, 0x18000A, 0x001904, + 0x22B886, 0x280007, 0x001810, 0x28043A, + 0x280C02, 0x00000D, 0x000810, 0x28143A, + 0x08808D, 0x000820, 0x0002FD, 0x018040, + 0x200007, 0x00020D, 0x189904, 0x000007, + 0x00402D, 0x0000BD, 0x0002FD, 0x018042, + 0x08000A, 0x000904, 0x065A86, 0x000007, + 0x000100, 0x000A20, 0x00047D, 0x018040, + 0x018042, 0x20000A, 0x003015, 0x012144, + 0x036186, 0x000007, 0x002104, 0x036186, + 0x000007, 0x000F8D, 0x000810, 0x280C3A, + 0x023944, 0x07C986, 0x000007, 0x001810, + 0x28043A, 0x08810D, 0x000820, 0x0002FD, + 0x018040, 0x200007, 0x002810, 0x78003A, + 0x00788D, 0x000810, 0x08043A, 0x2A1206, + 0x000007, 0x00400D, 0x001015, 0x189904, + 0x292904, 0x393904, 0x000007, 0x070206, + 0x000007, 0x0004F5, 0x00007D, 0x000020, + 0x00008D, 0x010860, 0x018040, 0x00047D, + 0x038042, 0x21804A, 0x18000A, 0x021944, + 0x229086, 0x000007, 0x004075, 0x71F104, + 0x000007, 0x010042, 0x28000A, 0x002904, + 0x225886, 0x000007, 0x003C0D, 0x30A904, + 0x000007, 0x00077D, 0x018042, 0x08000A, + 0x000904, 0x08DA86, 0x00057D, 0x002820, + 0x03B060, 0x08F206, 0x018040, 0x003020, + 0x03A860, 0x018040, 0x0002FD, 0x018042, + 0x08000A, 0x000904, 0x08FA86, 0x000007, + 0x00057D, 0x018042, 0x28040A, 0x000E8D, + 0x000810, 0x280C3A, 0x00000D, 0x000810, + 0x28143A, 0x09000D, 0x000820, 0x0002FD, + 0x018040, 0x200007, 0x003DFD, 0x000020, + 0x018040, 0x00107D, 0x009D8D, 0x000810, + 0x08043A, 0x2A1206, 0x000007, 0x000815, + 0x08001A, 0x010984, 0x0A5186, 0x00137D, + 0x200500, 0x280F20, 0x338F60, 0x3B8F60, + 0x438F60, 0x4B8F60, 0x538F60, 0x5B8F60, + 0x038A60, 0x018040, 0x00107D, 0x018042, + 0x08000A, 0x000215, 0x010984, 0x3A8186, + 0x000007, 0x007FBD, 0x383DC4, 0x000007, + 0x001A7D, 0x001375, 0x018042, 0x09004A, + 0x10000A, 0x0B8D04, 0x139504, 0x000007, + 0x000820, 0x019060, 0x001104, 0x225886, + 0x010040, 0x0017FD, 0x018042, 0x08000A, + 0x000904, 0x225A86, 0x000007, 0x00197D, + 0x038042, 0x09804A, 0x10000A, 0x000924, + 0x001664, 0x0011FD, 0x038042, 0x2B804A, + 0x19804A, 0x00008D, 0x218944, 0x000007, + 0x002244, 0x0C1986, 0x000007, 0x001A64, + 0x002A24, 0x00197D, 0x080102, 0x100122, + 0x000820, 0x039060, 0x018040, 0x003DFD, + 0x00008D, 0x000820, 0x018040, 0x001375, + 0x001A7D, 0x010042, 0x09804A, 0x10000A, + 0x00021D, 0x0189E4, 0x2992E4, 0x309144, + 0x000007, 0x00060D, 0x000A15, 0x000C1D, + 0x001025, 0x00A9E4, 0x012BE4, 0x000464, + 0x01B3E4, 0x0232E4, 0x000464, 0x000464, + 0x000464, 0x000464, 0x00040D, 0x08B1C4, + 0x000007, 0x000820, 0x000BF5, 0x030040, + 0x00197D, 0x038042, 0x09804A, 0x000A24, + 0x08000A, 0x080E64, 0x000007, 0x100122, + 0x000820, 0x031060, 0x010040, 0x0064AC, + 0x00027D, 0x000020, 0x018040, 0x00107D, + 0x018042, 0x0011FD, 0x3B804A, 0x09804A, + 0x20000A, 0x000095, 0x1A1144, 0x00A144, + 0x0E5886, 0x00040D, 0x00B984, 0x0E5986, + 0x0018FD, 0x018042, 0x0010FD, 0x09804A, + 0x28000A, 0x000095, 0x010924, 0x002A64, + 0x0E4986, 0x000007, 0x002904, 0x0E5A86, + 0x000007, 0x0E6206, 0x080002, 0x00008D, + 0x00387D, 0x000820, 0x018040, 0x00127D, + 0x018042, 0x10000A, 0x003904, 0x0F0986, + 0x00080D, 0x7FFFB5, 0x00B984, 0x0ED986, + 0x000025, 0x0FB206, 0x00002D, 0x000015, + 0x00082D, 0x02E00D, 0x000820, 0x0FFA06, + 0x00000D, 0x7F8035, 0x00B984, 0x0FA986, + 0x400025, 0x00008D, 0x110944, 0x000007, + 0x00018D, 0x109504, 0x000007, 0x009164, + 0x000424, 0x000424, 0x000424, 0x100102, + 0x280002, 0x02DF0D, 0x000820, 0x0FFA06, + 0x00018D, 0x00042D, 0x00008D, 0x109504, + 0x000007, 0x00020D, 0x109184, 0x000007, + 0x02DF8D, 0x000820, 0x00008D, 0x0038FD, + 0x018040, 0x003BFD, 0x001020, 0x03A860, + 0x000815, 0x313184, 0x212184, 0x000007, + 0x03B060, 0x03A060, 0x018040, 0x0022FD, + 0x000095, 0x010924, 0x000424, 0x000424, + 0x001264, 0x100102, 0x000820, 0x039060, + 0x018040, 0x001924, 0x010F0D, 0x00397D, + 0x000820, 0x058040, 0x038042, 0x09844A, + 0x000606, 0x08040A, 0x000424, 0x000424, + 0x00117D, 0x018042, 0x08000A, 0x000A24, + 0x280502, 0x280C02, 0x09800D, 0x000820, + 0x0002FD, 0x018040, 0x200007, 0x0022FD, + 0x018042, 0x08000A, 0x000095, 0x280DC4, + 0x011924, 0x00197D, 0x018042, 0x0011FD, + 0x09804A, 0x10000A, 0x0000B5, 0x113144, + 0x0A8D04, 0x000007, 0x080A44, 0x129504, + 0x000007, 0x0023FD, 0x001020, 0x038040, + 0x101244, 0x000007, 0x000820, 0x039060, + 0x018040, 0x0002FD, 0x018042, 0x08000A, + 0x000904, 0x123286, 0x000007, 0x003BFD, + 0x000100, 0x000A10, 0x0B807A, 0x13804A, + 0x090984, 0x000007, 0x000095, 0x013D04, + 0x12B886, 0x10000A, 0x100002, 0x090984, + 0x000007, 0x038042, 0x11804A, 0x090D04, + 0x000007, 0x10000A, 0x090D84, 0x000007, + 0x00257D, 0x000820, 0x018040, 0x00010D, + 0x000810, 0x28143A, 0x00127D, 0x018042, + 0x20000A, 0x00197D, 0x018042, 0x00117D, + 0x31804A, 0x10000A, 0x003124, 0x013B8D, + 0x00397D, 0x000820, 0x058040, 0x038042, + 0x09844A, 0x000606, 0x08040A, 0x300102, + 0x003124, 0x000424, 0x000424, 0x001224, + 0x280502, 0x001A4C, 0x143986, 0x700002, + 0x00002D, 0x030000, 0x00387D, 0x018042, + 0x10000A, 0x146206, 0x002124, 0x0000AD, + 0x100002, 0x00010D, 0x000924, 0x006B24, + 0x014A0D, 0x00397D, 0x000820, 0x058040, + 0x038042, 0x09844A, 0x000606, 0x08040A, + 0x003264, 0x00008D, 0x000A24, 0x001020, + 0x00227D, 0x018040, 0x014F8D, 0x000810, + 0x08043A, 0x2B5A06, 0x000007, 0x002820, + 0x00207D, 0x018040, 0x00117D, 0x038042, + 0x13804A, 0x33800A, 0x00387D, 0x018042, + 0x08000A, 0x000904, 0x177286, 0x000007, + 0x00008D, 0x030964, 0x015B0D, 0x00397D, + 0x000820, 0x058040, 0x038042, 0x09844A, + 0x000606, 0x08040A, 0x380102, 0x000424, + 0x000424, 0x001224, 0x0002FD, 0x018042, + 0x08000A, 0x000904, 0x15DA86, 0x000007, + 0x280502, 0x001A4C, 0x177186, 0x000007, + 0x032164, 0x00632C, 0x003DFD, 0x018042, + 0x08000A, 0x000095, 0x090904, 0x000007, + 0x000820, 0x001A4C, 0x169986, 0x018040, + 0x030000, 0x16B206, 0x002124, 0x00010D, + 0x000924, 0x006B24, 0x016F0D, 0x00397D, + 0x000820, 0x058040, 0x038042, 0x09844A, + 0x000606, 0x08040A, 0x003A64, 0x000095, + 0x001224, 0x0002FD, 0x018042, 0x08000A, + 0x000904, 0x171286, 0x000007, 0x01760D, + 0x000810, 0x08043A, 0x2B5A06, 0x000007, + 0x160A06, 0x000007, 0x007020, 0x08010A, + 0x10012A, 0x0020FD, 0x038860, 0x039060, + 0x018040, 0x00227D, 0x018042, 0x003DFD, + 0x08000A, 0x31844A, 0x000904, 0x181086, + 0x18008B, 0x00008D, 0x189904, 0x00312C, + 0x18E206, 0x000007, 0x00324C, 0x186B86, + 0x000007, 0x001904, 0x186886, 0x000007, + 0x000095, 0x199144, 0x00222C, 0x003124, + 0x00636C, 0x000E3D, 0x001375, 0x000BFD, + 0x010042, 0x09804A, 0x10000A, 0x038AEC, + 0x0393EC, 0x00224C, 0x18E186, 0x000007, + 0x00008D, 0x189904, 0x00226C, 0x00322C, + 0x30050A, 0x301DAB, 0x002083, 0x0018FD, + 0x018042, 0x08000A, 0x018924, 0x300502, + 0x001083, 0x001875, 0x010042, 0x10000A, + 0x00008D, 0x010924, 0x001375, 0x330542, + 0x330CCB, 0x332CCB, 0x3334CB, 0x333CCB, + 0x3344CB, 0x334CCB, 0x3354CB, 0x305C8B, + 0x006083, 0x0002F5, 0x010042, 0x08000A, + 0x000904, 0x19B286, 0x000007, 0x001E2D, + 0x0005FD, 0x018042, 0x08000A, 0x028924, + 0x280502, 0x00060D, 0x000810, 0x280C3A, + 0x00008D, 0x000810, 0x28143A, 0x0A808D, + 0x000820, 0x0002F5, 0x010040, 0x220007, + 0x001275, 0x030042, 0x21004A, 0x00008D, + 0x1A0944, 0x000007, 0x01AB8D, 0x000810, + 0x08043A, 0x2CAA06, 0x000007, 0x0001F5, + 0x030042, 0x0D004A, 0x10000A, 0x089144, + 0x000007, 0x000820, 0x010040, 0x0025F5, + 0x0A3144, 0x000007, 0x000820, 0x032860, + 0x030040, 0x00217D, 0x038042, 0x0B804A, + 0x10000A, 0x000820, 0x031060, 0x030040, + 0x00008D, 0x000124, 0x00012C, 0x000E64, + 0x001A64, 0x00636C, 0x08010A, 0x10012A, + 0x000820, 0x031060, 0x030040, 0x0020FD, + 0x018042, 0x08000A, 0x00227D, 0x018042, + 0x10000A, 0x000820, 0x031060, 0x030040, + 0x00197D, 0x018042, 0x08000A, 0x0022FD, + 0x038042, 0x10000A, 0x000820, 0x031060, + 0x030040, 0x090D04, 0x000007, 0x000820, + 0x030040, 0x038042, 0x0B804A, 0x10000A, + 0x000820, 0x031060, 0x030040, 0x038042, + 0x13804A, 0x19804A, 0x110D04, 0x198D04, + 0x000007, 0x08000A, 0x001020, 0x031860, + 0x030860, 0x030040, 0x00008D, 0x0B0944, + 0x000007, 0x000820, 0x010040, 0x0005F5, + 0x030042, 0x08000A, 0x000820, 0x010040, + 0x0000F5, 0x010042, 0x08000A, 0x000904, + 0x1D9886, 0x001E75, 0x030042, 0x01044A, + 0x000C0A, 0x1DAA06, 0x000007, 0x000402, + 0x000C02, 0x00177D, 0x001AF5, 0x018042, + 0x03144A, 0x031C4A, 0x03244A, 0x032C4A, + 0x03344A, 0x033C4A, 0x03444A, 0x004C0A, + 0x00043D, 0x0013F5, 0x001AFD, 0x030042, + 0x0B004A, 0x1B804A, 0x13804A, 0x20000A, + 0x089144, 0x19A144, 0x0389E4, 0x0399EC, + 0x005502, 0x005D0A, 0x030042, 0x0B004A, + 0x1B804A, 0x13804A, 0x20000A, 0x089144, + 0x19A144, 0x0389E4, 0x0399EC, 0x006502, + 0x006D0A, 0x030042, 0x0B004A, 0x19004A, + 0x2B804A, 0x13804A, 0x21804A, 0x30000A, + 0x089144, 0x19A144, 0x2AB144, 0x0389E4, + 0x0399EC, 0x007502, 0x007D0A, 0x03A9E4, + 0x000702, 0x00107D, 0x000415, 0x018042, + 0x08000A, 0x0109E4, 0x000F02, 0x002AF5, + 0x0019FD, 0x010042, 0x09804A, 0x10000A, + 0x000934, 0x001674, 0x0029F5, 0x010042, + 0x10000A, 0x00917C, 0x002075, 0x010042, + 0x08000A, 0x000904, 0x200A86, 0x0026F5, + 0x0027F5, 0x030042, 0x09004A, 0x10000A, + 0x000A3C, 0x00167C, 0x001A75, 0x000BFD, + 0x010042, 0x51804A, 0x48000A, 0x160007, + 0x001075, 0x010042, 0x282C0A, 0x281D12, + 0x282512, 0x001F32, 0x1E0007, 0x0E0007, + 0x001975, 0x010042, 0x002DF5, 0x0D004A, + 0x10000A, 0x009144, 0x20EA86, 0x010042, + 0x28340A, 0x000E5D, 0x00008D, 0x000375, + 0x000820, 0x010040, 0x05D2F4, 0x54D104, + 0x00735C, 0x218B86, 0x000007, 0x0C0007, + 0x080007, 0x0A0007, 0x02178D, 0x000810, + 0x08043A, 0x34B206, 0x000007, 0x219206, + 0x000007, 0x080007, 0x002275, 0x010042, + 0x20000A, 0x002104, 0x225886, 0x001E2D, + 0x0002F5, 0x010042, 0x08000A, 0x000904, + 0x21CA86, 0x000007, 0x002010, 0x30043A, + 0x00057D, 0x0180C3, 0x08000A, 0x028924, + 0x280502, 0x280C02, 0x0A810D, 0x000820, + 0x0002F5, 0x010040, 0x220007, 0x0004FD, + 0x018042, 0x70000A, 0x030000, 0x007020, + 0x07FA06, 0x018040, 0x022B8D, 0x000810, + 0x08043A, 0x2CAA06, 0x000007, 0x0002FD, + 0x018042, 0x08000A, 0x000904, 0x22C286, + 0x000007, 0x020206, 0x000007, 0x000875, + 0x0009FD, 0x00010D, 0x234206, 0x000295, + 0x000B75, 0x00097D, 0x00000D, 0x000515, + 0x010042, 0x18000A, 0x001904, 0x2A0086, + 0x0006F5, 0x001020, 0x010040, 0x0004F5, + 0x000820, 0x010040, 0x000775, 0x010042, + 0x09804A, 0x10000A, 0x001124, 0x000904, + 0x23F286, 0x000815, 0x080102, 0x101204, + 0x241206, 0x000575, 0x081204, 0x000007, + 0x100102, 0x000575, 0x000425, 0x021124, + 0x100102, 0x000820, 0x031060, 0x010040, + 0x001924, 0x2A0086, 0x00008D, 0x000464, + 0x009D04, 0x291086, 0x180102, 0x000575, + 0x010042, 0x28040A, 0x00018D, 0x000924, + 0x280D02, 0x00000D, 0x000924, 0x281502, + 0x10000D, 0x000820, 0x0002F5, 0x010040, + 0x200007, 0x001175, 0x0002FD, 0x018042, + 0x08000A, 0x000904, 0x24FA86, 0x000007, + 0x000100, 0x080B20, 0x130B60, 0x1B0B60, + 0x030A60, 0x010040, 0x050042, 0x3D004A, + 0x35004A, 0x2D004A, 0x20000A, 0x0006F5, + 0x010042, 0x28140A, 0x0004F5, 0x010042, + 0x08000A, 0x000315, 0x010D04, 0x260286, + 0x004015, 0x000095, 0x010D04, 0x25F086, + 0x100022, 0x10002A, 0x261A06, 0x000007, + 0x333104, 0x2AA904, 0x000007, 0x032124, + 0x280502, 0x284402, 0x001124, 0x400102, + 0x000424, 0x000424, 0x003224, 0x00292C, + 0x00636C, 0x277386, 0x000007, 0x02B164, + 0x000464, 0x000464, 0x00008D, 0x000A64, + 0x280D02, 0x10008D, 0x000820, 0x0002F5, + 0x010040, 0x220007, 0x00008D, 0x38B904, + 0x000007, 0x03296C, 0x30010A, 0x0002F5, + 0x010042, 0x08000A, 0x000904, 0x270286, + 0x000007, 0x00212C, 0x28050A, 0x00316C, + 0x00046C, 0x00046C, 0x28450A, 0x001124, + 0x006B64, 0x100102, 0x00008D, 0x01096C, + 0x280D0A, 0x10010D, 0x000820, 0x0002F5, + 0x010040, 0x220007, 0x004124, 0x000424, + 0x000424, 0x003224, 0x300102, 0x032944, + 0x27FA86, 0x000007, 0x300002, 0x0004F5, + 0x010042, 0x08000A, 0x000315, 0x010D04, + 0x284086, 0x003124, 0x000464, 0x300102, + 0x0002F5, 0x010042, 0x08000A, 0x000904, + 0x284A86, 0x000007, 0x284402, 0x003124, + 0x300502, 0x003924, 0x300583, 0x000883, + 0x0005F5, 0x010042, 0x28040A, 0x00008D, + 0x008124, 0x280D02, 0x00008D, 0x008124, + 0x281502, 0x10018D, 0x000820, 0x0002F5, + 0x010040, 0x220007, 0x001025, 0x000575, + 0x030042, 0x09004A, 0x10000A, 0x0A0904, + 0x121104, 0x000007, 0x001020, 0x050860, + 0x050040, 0x0006FD, 0x018042, 0x09004A, + 0x10000A, 0x0000A5, 0x0A0904, 0x121104, + 0x000007, 0x000820, 0x019060, 0x010040, + 0x0002F5, 0x010042, 0x08000A, 0x000904, + 0x29CA86, 0x000007, 0x244206, 0x000007, + 0x000606, 0x000007, 0x0002F5, 0x010042, + 0x08000A, 0x000904, 0x2A1A86, 0x000007, + 0x000100, 0x080B20, 0x138B60, 0x1B8B60, + 0x238B60, 0x2B8B60, 0x338B60, 0x3B8B60, + 0x438B60, 0x4B8B60, 0x538B60, 0x5B8B60, + 0x638B60, 0x6B8B60, 0x738B60, 0x7B8B60, + 0x038F60, 0x0B8F60, 0x138F60, 0x1B8F60, + 0x238F60, 0x2B8F60, 0x338F60, 0x3B8F60, + 0x438F60, 0x4B8F60, 0x538F60, 0x5B8F60, + 0x638F60, 0x6B8F60, 0x738F60, 0x7B8F60, + 0x038A60, 0x000606, 0x018040, 0x00008D, + 0x000A64, 0x280D02, 0x000A24, 0x00027D, + 0x018042, 0x10000A, 0x001224, 0x0003FD, + 0x018042, 0x08000A, 0x000904, 0x2C0A86, + 0x000007, 0x00018D, 0x000A24, 0x000464, + 0x000464, 0x080102, 0x000924, 0x000424, + 0x000424, 0x100102, 0x02000D, 0x009144, + 0x2C6186, 0x000007, 0x0001FD, 0x018042, + 0x08000A, 0x000A44, 0x2C4386, 0x018042, + 0x0A000D, 0x000820, 0x0002FD, 0x018040, + 0x200007, 0x00027D, 0x001020, 0x000606, + 0x018040, 0x0002F5, 0x010042, 0x08000A, + 0x000904, 0x2CB286, 0x000007, 0x00037D, + 0x018042, 0x08000A, 0x000904, 0x2CE286, + 0x000007, 0x000075, 0x002E7D, 0x010042, + 0x0B804A, 0x000020, 0x000904, 0x000686, + 0x010040, 0x31844A, 0x30048B, 0x000883, + 0x00008D, 0x000810, 0x28143A, 0x00008D, + 0x000810, 0x280C3A, 0x000675, 0x010042, + 0x08000A, 0x003815, 0x010924, 0x280502, + 0x0B000D, 0x000820, 0x0002F5, 0x010040, + 0x000606, 0x220007, 0x000464, 0x000464, + 0x000606, 0x000007, 0x000134, 0x007F8D, + 0x00093C, 0x281D12, 0x282512, 0x001F32, + 0x0E0007, 0x00010D, 0x00037D, 0x000820, + 0x018040, 0x05D2F4, 0x000007, 0x080007, + 0x00037D, 0x018042, 0x08000A, 0x000904, + 0x2E8A86, 0x000007, 0x000606, 0x000007, + 0x000007, 0x000012, 0x100007, 0x320007, + 0x600007, 0x460007, 0x100080, 0x48001A, + 0x004904, 0x2EF186, 0x000007, 0x001210, + 0x58003A, 0x000145, 0x5C5D04, 0x000007, + 0x000080, 0x48001A, 0x004904, 0x2F4186, + 0x000007, 0x001210, 0x50003A, 0x005904, + 0x2F9886, 0x000045, 0x0000C5, 0x7FFFF5, + 0x7FFF7D, 0x07D524, 0x004224, 0x500102, + 0x200502, 0x000082, 0x40001A, 0x004104, + 0x2FC986, 0x000007, 0x003865, 0x40001A, + 0x004020, 0x00104D, 0x04C184, 0x31AB86, + 0x000040, 0x040007, 0x000165, 0x000145, + 0x004020, 0x000040, 0x000765, 0x080080, + 0x40001A, 0x004104, 0x305986, 0x000007, + 0x001210, 0x40003A, 0x004104, 0x30B286, + 0x00004D, 0x0000CD, 0x004810, 0x20043A, + 0x000882, 0x40001A, 0x004104, 0x30C186, + 0x000007, 0x004820, 0x005904, 0x319886, + 0x000040, 0x0007E5, 0x200480, 0x2816A0, + 0x3216E0, 0x3A16E0, 0x4216E0, 0x021260, + 0x000040, 0x000032, 0x400075, 0x00007D, + 0x07D574, 0x200512, 0x000082, 0x40001A, + 0x004104, 0x317186, 0x000007, 0x038A06, + 0x640007, 0x0000E5, 0x000020, 0x000040, + 0x000A65, 0x000020, 0x020040, 0x020040, + 0x000040, 0x000165, 0x000042, 0x70000A, + 0x007104, 0x323286, 0x000007, 0x060007, + 0x019A06, 0x640007, 0x050000, 0x007020, + 0x000040, 0x038A06, 0x640007, 0x000007, + 0x00306D, 0x028860, 0x029060, 0x08000A, + 0x028860, 0x008040, 0x100012, 0x00100D, + 0x009184, 0x32D186, 0x000E0D, 0x009184, + 0x33E186, 0x000007, 0x300007, 0x001020, + 0x003B6D, 0x008040, 0x000080, 0x08001A, + 0x000904, 0x32F186, 0x000007, 0x001220, + 0x000DED, 0x008040, 0x008042, 0x10000A, + 0x40000D, 0x109544, 0x000007, 0x001020, + 0x000DED, 0x008040, 0x008042, 0x20040A, + 0x000082, 0x08001A, 0x000904, 0x338186, + 0x000007, 0x003B6D, 0x008042, 0x08000A, + 0x000E15, 0x010984, 0x342B86, 0x600007, + 0x08001A, 0x000C15, 0x010984, 0x341386, + 0x000020, 0x1A0007, 0x0002ED, 0x008040, + 0x620007, 0x00306D, 0x028042, 0x0A804A, + 0x000820, 0x0A804A, 0x000606, 0x10804A, + 0x000007, 0x282512, 0x001F32, 0x05D2F4, + 0x54D104, 0x00735C, 0x000786, 0x000007, + 0x0C0007, 0x0A0007, 0x1C0007, 0x003465, + 0x020040, 0x004820, 0x025060, 0x40000A, + 0x024060, 0x000040, 0x454944, 0x000007, + 0x004020, 0x003AE5, 0x000040, 0x0028E5, + 0x000042, 0x48000A, 0x004904, 0x39F886, + 0x002C65, 0x000042, 0x40000A, 0x0000D5, + 0x454104, 0x000007, 0x000655, 0x054504, + 0x368286, 0x0001D5, 0x054504, 0x368086, + 0x002B65, 0x000042, 0x003AE5, 0x50004A, + 0x40000A, 0x45C3D4, 0x000007, 0x454504, + 0x000007, 0x0000CD, 0x444944, 0x000007, + 0x454504, 0x000007, 0x00014D, 0x554944, + 0x000007, 0x045144, 0x367986, 0x002C65, + 0x000042, 0x48000A, 0x4CD104, 0x000007, + 0x04C144, 0x368386, 0x000007, 0x160007, + 0x002CE5, 0x040042, 0x40000A, 0x004020, + 0x000040, 0x002965, 0x000042, 0x40000A, + 0x004104, 0x36F086, 0x000007, 0x002402, + 0x383206, 0x005C02, 0x0025E5, 0x000042, + 0x40000A, 0x004274, 0x002AE5, 0x000042, + 0x40000A, 0x004274, 0x500112, 0x0029E5, + 0x000042, 0x40000A, 0x004234, 0x454104, + 0x000007, 0x004020, 0x000040, 0x003EE5, + 0x000020, 0x000040, 0x002DE5, 0x400152, + 0x50000A, 0x045144, 0x37DA86, 0x0000C5, + 0x003EE5, 0x004020, 0x000040, 0x002BE5, + 0x000042, 0x40000A, 0x404254, 0x000007, + 0x002AE5, 0x004020, 0x000040, 0x500132, + 0x040134, 0x005674, 0x0029E5, 0x020042, + 0x42000A, 0x000042, 0x50000A, 0x05417C, + 0x0028E5, 0x000042, 0x48000A, 0x0000C5, + 0x4CC144, 0x38A086, 0x0026E5, 0x0027E5, + 0x020042, 0x40004A, 0x50000A, 0x00423C, + 0x00567C, 0x0028E5, 0x004820, 0x000040, + 0x281D12, 0x282512, 0x001F72, 0x002965, + 0x000042, 0x40000A, 0x004104, 0x393A86, + 0x0E0007, 0x160007, 0x1E0007, 0x003EE5, + 0x000042, 0x40000A, 0x004104, 0x397886, + 0x002D65, 0x000042, 0x28340A, 0x003465, + 0x020042, 0x42004A, 0x004020, 0x4A004A, + 0x50004A, 0x05D2F4, 0x54D104, 0x00735C, + 0x39E186, 0x000007, 0x000606, 0x080007, + 0x0C0007, 0x080007, 0x0A0007, 0x0001E5, + 0x020045, 0x004020, 0x000060, 0x000365, + 0x000040, 0x002E65, 0x001A20, 0x0A1A60, + 0x000040, 0x003465, 0x020042, 0x42004A, + 0x004020, 0x4A004A, 0x000606, 0x50004A, + 0x0017FD, 0x018042, 0x08000A, 0x000904, + 0x225A86, 0x000007, 0x00107D, 0x018042, + 0x0011FD, 0x33804A, 0x19804A, 0x20000A, + 0x000095, 0x2A1144, 0x01A144, 0x3B9086, + 0x00040D, 0x00B184, 0x3B9186, 0x0018FD, + 0x018042, 0x0010FD, 0x09804A, 0x38000A, + 0x000095, 0x010924, 0x003A64, 0x3B8186, + 0x000007, 0x003904, 0x3B9286, 0x000007, + 0x3B9A06, 0x00000D, 0x00008D, 0x000820, + 0x00387D, 0x018040, 0x700002, 0x00117D, + 0x018042, 0x00197D, 0x29804A, 0x30000A, + 0x380002, 0x003124, 0x000424, 0x000424, + 0x002A24, 0x280502, 0x00068D, 0x000810, + 0x28143A, 0x00750D, 0x00B124, 0x002264, + 0x3D0386, 0x284402, 0x000810, 0x280C3A, + 0x0B800D, 0x000820, 0x0002FD, 0x018040, + 0x200007, 0x00758D, 0x00B124, 0x100102, + 0x012144, 0x3E4986, 0x001810, 0x10003A, + 0x00387D, 0x018042, 0x08000A, 0x000904, + 0x3E4886, 0x030000, 0x3E4A06, 0x0000BD, + 0x00008D, 0x023164, 0x000A64, 0x280D02, + 0x0B808D, 0x000820, 0x0002FD, 0x018040, + 0x200007, 0x00387D, 0x018042, 0x08000A, + 0x000904, 0x3E3286, 0x030000, 0x0002FD, + 0x018042, 0x08000A, 0x000904, 0x3D8286, + 0x000007, 0x002810, 0x28043A, 0x00750D, + 0x030924, 0x002264, 0x280D02, 0x02316C, + 0x28450A, 0x0B810D, 0x000820, 0x0002FD, + 0x018040, 0x200007, 0x00008D, 0x000A24, + 0x3E4A06, 0x100102, 0x001810, 0x10003A, + 0x0000BD, 0x003810, 0x30043A, 0x00187D, + 0x018042, 0x0018FD, 0x09804A, 0x20000A, + 0x0000AD, 0x028924, 0x07212C, 0x001010, + 0x300583, 0x300D8B, 0x3014BB, 0x301C83, + 0x002083, 0x00137D, 0x038042, 0x33844A, + 0x33ACCB, 0x33B4CB, 0x33BCCB, 0x33C4CB, + 0x33CCCB, 0x33D4CB, 0x305C8B, 0x006083, + 0x001E0D, 0x0005FD, 0x018042, 0x20000A, + 0x020924, 0x00068D, 0x00A96C, 0x00009D, + 0x0002FD, 0x018042, 0x08000A, 0x000904, + 0x3F6A86, 0x000007, 0x280502, 0x280D0A, + 0x284402, 0x001810, 0x28143A, 0x0C008D, + 0x000820, 0x0002FD, 0x018040, 0x220007, + 0x003904, 0x225886, 0x001E0D, 0x00057D, + 0x018042, 0x20000A, 0x020924, 0x0000A5, + 0x0002FD, 0x018042, 0x08000A, 0x000904, + 0x402A86, 0x000007, 0x280502, 0x280C02, + 0x002010, 0x28143A, 0x0C010D, 0x000820, + 0x0002FD, 0x018040, 0x225A06, 0x220007, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000 +}; + +#endif //_HWMCODE_ diff --git a/sound/oss/yss225.c b/sound/oss/yss225.c new file mode 100644 index 000000000000..e700400576d8 --- /dev/null +++ b/sound/oss/yss225.c @@ -0,0 +1,319 @@ +#include + +unsigned char page_zero[] __initdata = { +0x01, 0x7c, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf5, 0x00, +0x11, 0x00, 0x20, 0x00, 0x32, 0x00, 0x40, 0x00, 0x13, 0x00, 0x00, +0x00, 0x14, 0x02, 0x76, 0x00, 0x60, 0x00, 0x80, 0x02, 0x00, 0x00, +0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x19, +0x01, 0x1a, 0x01, 0x20, 0x01, 0x40, 0x01, 0x17, 0x00, 0x00, 0x01, +0x80, 0x01, 0x20, 0x00, 0x10, 0x01, 0xa0, 0x03, 0xd1, 0x00, 0x00, +0x01, 0xf2, 0x02, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0xf4, 0x02, +0xe0, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, +0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x00, 0x00, 0x00, +0x40, 0x00, 0x00, 0x00, 0x71, 0x02, 0x00, 0x00, 0x60, 0x00, 0x00, +0x00, 0x92, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xb3, 0x02, +0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x40, +0x00, 0x80, 0x00, 0xf5, 0x00, 0x20, 0x00, 0x70, 0x00, 0xa0, 0x02, +0x11, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, +0x02, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x17, 0x00, 0x1b, 0x00, +0x1d, 0x02, 0xdf +}; + +unsigned char page_one[] __initdata = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x19, 0x00, +0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xd8, 0x00, 0x00, +0x02, 0x20, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, +0xc0, 0x01, 0xfa, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x02, 0x60, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x02, 0x80, 0x00, +0x00, 0x02, 0xfb, 0x02, 0xa0, 0x00, 0x00, 0x00, 0x1b, 0x02, 0xd7, +0x00, 0x00, 0x02, 0xf7, 0x03, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, +0x1c, 0x03, 0x3c, 0x00, 0x00, 0x03, 0x3f, 0x00, 0x00, 0x03, 0xc0, +0x00, 0x00, 0x03, 0xdf, 0x00, 0x00, 0x00, 0x00, 0x03, 0x5d, 0x00, +0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0x7d, 0x00, 0x00, 0x03, 0xc0, +0x00, 0x00, 0x03, 0x9e, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, +0xbe, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, +0xdb, 0x00, 0x00, 0x02, 0xdb, 0x00, 0x00, 0x02, 0xe0, 0x00, 0x00, +0x02, 0xfb, 0x00, 0x00, 0x02, 0xc0, 0x02, 0x40, 0x02, 0xfb, 0x02, +0x60, 0x00, 0x1b +}; + +unsigned char page_two[] __initdata = { +0xc4, 0x00, 0x44, 0x07, 0x44, 0x00, 0x40, 0x25, 0x01, 0x06, 0xc4, +0x07, 0x40, 0x25, 0x01, 0x00, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x07, +0x05, 0x05, 0x05, 0x04, 0x07, 0x05, 0x04, 0x07, 0x05, 0x44, 0x46, +0x44, 0x46, 0x46, 0x07, 0x05, 0x44, 0x46, 0x05, 0x46, 0x05, 0x46, +0x05, 0x46, 0x05, 0x44, 0x46, 0x05, 0x07, 0x44, 0x46, 0x05, 0x07, +0x44, 0x46, 0x05, 0x07, 0x44, 0x46, 0x05, 0x07, 0x44, 0x05, 0x05, +0x05, 0x44, 0x05, 0x05, 0x05, 0x46, 0x05, 0x46, 0x05, 0x46, 0x05, +0x46, 0x05, 0x46, 0x07, 0x46, 0x07, 0x44 +}; + +unsigned char page_three[] __initdata = { +0x07, 0x40, 0x00, 0x00, 0x00, 0x47, 0x00, 0x40, 0x00, 0x40, 0x06, +0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, +0xc0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, +0x60, 0x00, 0x70, 0x00, 0x40, 0x00, 0x40, 0x00, 0x42, 0x00, 0x40, +0x00, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, +0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, +0x00, 0x42, 0x00, 0x40, 0x00, 0x42, 0x00, 0x02, 0x00, 0x02, 0x00, +0x02, 0x00, 0x42, 0x00, 0xc0, 0x00, 0x40 +}; + +unsigned char page_four[] __initdata = { +0x63, 0x03, 0x26, 0x02, 0x2c, 0x00, 0x24, 0x00, 0x2e, 0x02, 0x02, +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, +0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x60, 0x00, +0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x60, +0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, +0x20, 0x00, 0x22, 0x02, 0x22, 0x02, 0x20, 0x00, 0x60, 0x00, 0x22, +0x02, 0x62, 0x02, 0x20, 0x01, 0x21, 0x01 +}; + +unsigned char page_six[] __initdata = { +0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04, 0x00, 0x00, 0x06, 0x00, +0x00, 0x08, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0e, +0x00, 0x00, 0x10, 0x00, 0x00, 0x12, 0x00, 0x00, 0x14, 0x00, 0x00, +0x16, 0x00, 0x00, 0x18, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x1c, 0x00, +0x00, 0x1e, 0x00, 0x00, 0x20, 0x00, 0x00, 0x22, 0x00, 0x00, 0x24, +0x00, 0x00, 0x26, 0x00, 0x00, 0x28, 0x00, 0x00, 0x2a, 0x00, 0x00, +0x2c, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x30, 0x00, 0x00, 0x32, 0x00, +0x00, 0x34, 0x00, 0x00, 0x36, 0x00, 0x00, 0x38, 0x00, 0x00, 0x3a, +0x00, 0x00, 0x3c, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x40, 0x00, 0x00, +0x42, 0x03, 0x00, 0x44, 0x01, 0x00, 0x46, 0x0a, 0x21, 0x48, 0x0d, +0x23, 0x4a, 0x23, 0x1b, 0x4c, 0x37, 0x8f, 0x4e, 0x45, 0x77, 0x50, +0x52, 0xe2, 0x52, 0x1c, 0x92, 0x54, 0x1c, 0x52, 0x56, 0x07, 0x00, +0x58, 0x2f, 0xc6, 0x5a, 0x0b, 0x00, 0x5c, 0x30, 0x06, 0x5e, 0x17, +0x00, 0x60, 0x3d, 0xda, 0x62, 0x29, 0x00, 0x64, 0x3e, 0x41, 0x66, +0x39, 0x00, 0x68, 0x4c, 0x48, 0x6a, 0x49, 0x00, 0x6c, 0x4c, 0x6c, +0x6e, 0x11, 0xd2, 0x70, 0x16, 0x0c, 0x72, 0x00, 0x00, 0x74, 0x00, +0x80, 0x76, 0x0f, 0x00, 0x78, 0x00, 0x80, 0x7a, 0x13, 0x00, 0x7c, +0x80, 0x00, 0x7e, 0x80, 0x80 +}; + +unsigned char page_seven[] __initdata = { +0x0f, 0xff, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, +0x00, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, +0x08, 0x00, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x0f, +0xff, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, +0x0f, 0xff, 0x0f, 0xff, 0x02, 0xe9, 0x06, 0x8c, 0x06, 0x8c, 0x0f, +0xff, 0x1a, 0x75, 0x0d, 0x8b, 0x04, 0xe9, 0x0b, 0x16, 0x1a, 0x38, +0x0d, 0xc8, 0x04, 0x6f, 0x0b, 0x91, 0x0f, 0xff, 0x06, 0x40, 0x06, +0x40, 0x02, 0x8f, 0x0f, 0xff, 0x06, 0x62, 0x06, 0x62, 0x02, 0x7b, +0x0f, 0xff, 0x06, 0x97, 0x06, 0x97, 0x02, 0x52, 0x0f, 0xff, 0x06, +0xf6, 0x06, 0xf6, 0x02, 0x19, 0x05, 0x55, 0x05, 0x55, 0x05, 0x55, +0x05, 0x55, 0x05, 0x55, 0x05, 0x55, 0x05, 0x55, 0x05, 0x55, 0x14, +0xda, 0x0d, 0x93, 0x04, 0xda, 0x05, 0x93, 0x14, 0xda, 0x0d, 0x93, +0x04, 0xda, 0x05, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x02, 0x00 +}; + +unsigned char page_zero_v2[] __initdata = { +0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +unsigned char page_one_v2[] __initdata = { +0x01, 0xc0, 0x01, 0xfa, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +unsigned char page_two_v2[] __initdata = { +0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00 +}; +unsigned char page_three_v2[] __initdata = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00 +}; +unsigned char page_four_v2[] __initdata = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00 +}; + +unsigned char page_seven_v2[] __initdata = { +0x0f, 0xff, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +unsigned char mod_v2[] __initdata = { +0x01, 0x00, 0x02, 0x00, 0x01, 0x01, 0x02, 0x00, 0x01, 0x02, 0x02, +0x00, 0x01, 0x03, 0x02, 0x00, 0x01, 0x04, 0x02, 0x00, 0x01, 0x05, +0x02, 0x00, 0x01, 0x06, 0x02, 0x00, 0x01, 0x07, 0x02, 0x00, 0xb0, +0x20, 0xb1, 0x20, 0xb2, 0x20, 0xb3, 0x20, 0xb4, 0x20, 0xb5, 0x20, +0xb6, 0x20, 0xb7, 0x20, 0xf0, 0x20, 0xf1, 0x20, 0xf2, 0x20, 0xf3, +0x20, 0xf4, 0x20, 0xf5, 0x20, 0xf6, 0x20, 0xf7, 0x20, 0x10, 0xff, +0x11, 0xff, 0x12, 0xff, 0x13, 0xff, 0x14, 0xff, 0x15, 0xff, 0x16, +0xff, 0x17, 0xff, 0x20, 0xff, 0x21, 0xff, 0x22, 0xff, 0x23, 0xff, +0x24, 0xff, 0x25, 0xff, 0x26, 0xff, 0x27, 0xff, 0x30, 0x00, 0x31, +0x00, 0x32, 0x00, 0x33, 0x00, 0x34, 0x00, 0x35, 0x00, 0x36, 0x00, +0x37, 0x00, 0x40, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x44, +0x00, 0x45, 0x00, 0x46, 0x00, 0x47, 0x00, 0x50, 0x00, 0x51, 0x00, +0x52, 0x00, 0x53, 0x00, 0x54, 0x00, 0x55, 0x00, 0x56, 0x00, 0x57, +0x00, 0x60, 0x00, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00, 0x64, 0x00, +0x65, 0x00, 0x66, 0x00, 0x67, 0x00, 0x70, 0xc0, 0x71, 0xc0, 0x72, +0xc0, 0x73, 0xc0, 0x74, 0xc0, 0x75, 0xc0, 0x76, 0xc0, 0x77, 0xc0, +0x80, 0x00, 0x81, 0x00, 0x82, 0x00, 0x83, 0x00, 0x84, 0x00, 0x85, +0x00, 0x86, 0x00, 0x87, 0x00, 0x90, 0x00, 0x91, 0x00, 0x92, 0x00, +0x93, 0x00, 0x94, 0x00, 0x95, 0x00, 0x96, 0x00, 0x97, 0x00, 0xa0, +0x00, 0xa1, 0x00, 0xa2, 0x00, 0xa3, 0x00, 0xa4, 0x00, 0xa5, 0x00, +0xa6, 0x00, 0xa7, 0x00, 0xc0, 0x00, 0xc1, 0x00, 0xc2, 0x00, 0xc3, +0x00, 0xc4, 0x00, 0xc5, 0x00, 0xc6, 0x00, 0xc7, 0x00, 0xd0, 0x00, +0xd1, 0x00, 0xd2, 0x00, 0xd3, 0x00, 0xd4, 0x00, 0xd5, 0x00, 0xd6, +0x00, 0xd7, 0x00, 0xe0, 0x00, 0xe1, 0x00, 0xe2, 0x00, 0xe3, 0x00, +0xe4, 0x00, 0xe5, 0x00, 0xe6, 0x00, 0xe7, 0x00, 0x01, 0x00, 0x02, +0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x03, +0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x05, 0x02, 0x01, 0x01, +0x06, 0x02, 0x01, 0x01, 0x07, 0x02, 0x01 +}; +unsigned char coefficients[] __initdata = { +0x07, 0x46, 0x00, 0x00, 0x07, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x03, +0x11, 0x00, 0x4d, 0x01, 0x32, 0x07, 0x46, 0x00, 0x00, 0x07, 0x49, +0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x07, 0x41, 0x00, 0x00, 0x01, +0x40, 0x02, 0x40, 0x01, 0x41, 0x02, 0x60, 0x07, 0x40, 0x00, 0x00, +0x07, 0x41, 0x00, 0x00, 0x07, 0x47, 0x00, 0x00, 0x07, 0x4a, 0x00, +0x00, 0x00, 0x47, 0x01, 0x00, 0x00, 0x4a, 0x01, 0x20, 0x07, 0x47, +0x00, 0x00, 0x07, 0x4a, 0x00, 0x00, 0x07, 0x7c, 0x00, 0x00, 0x07, +0x7e, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1c, 0x07, 0x7c, 0x00, 0x00, +0x07, 0x7e, 0x00, 0x00, 0x07, 0x44, 0x00, 0x00, 0x00, 0x44, 0x01, +0x00, 0x07, 0x44, 0x00, 0x00, 0x07, 0x42, 0x00, 0x00, 0x07, 0x43, +0x00, 0x00, 0x00, 0x42, 0x01, 0x1a, 0x00, 0x43, 0x01, 0x20, 0x07, +0x42, 0x00, 0x00, 0x07, 0x43, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00, +0x07, 0x41, 0x00, 0x00, 0x01, 0x40, 0x02, 0x40, 0x01, 0x41, 0x02, +0x60, 0x07, 0x40, 0x00, 0x00, 0x07, 0x41, 0x00, 0x00, 0x07, 0x44, +0x0f, 0xff, 0x07, 0x42, 0x00, 0x00, 0x07, 0x43, 0x00, 0x00, 0x07, +0x40, 0x00, 0x00, 0x07, 0x41, 0x00, 0x00, 0x07, 0x51, 0x06, 0x40, +0x07, 0x50, 0x06, 0x40, 0x07, 0x4f, 0x03, 0x81, 0x07, 0x53, 0x1a, +0x76, 0x07, 0x54, 0x0d, 0x8b, 0x07, 0x55, 0x04, 0xe9, 0x07, 0x56, +0x0b, 0x17, 0x07, 0x57, 0x1a, 0x38, 0x07, 0x58, 0x0d, 0xc9, 0x07, +0x59, 0x04, 0x6f, 0x07, 0x5a, 0x0b, 0x91, 0x07, 0x73, 0x14, 0xda, +0x07, 0x74, 0x0d, 0x93, 0x07, 0x75, 0x04, 0xd9, 0x07, 0x76, 0x05, +0x93, 0x07, 0x77, 0x14, 0xda, 0x07, 0x78, 0x0d, 0x93, 0x07, 0x79, +0x04, 0xd9, 0x07, 0x7a, 0x05, 0x93, 0x07, 0x5e, 0x03, 0x68, 0x07, +0x5c, 0x04, 0x31, 0x07, 0x5d, 0x04, 0x31, 0x07, 0x62, 0x03, 0x52, +0x07, 0x60, 0x04, 0x76, 0x07, 0x61, 0x04, 0x76, 0x07, 0x66, 0x03, +0x2e, 0x07, 0x64, 0x04, 0xda, 0x07, 0x65, 0x04, 0xda, 0x07, 0x6a, +0x02, 0xf6, 0x07, 0x68, 0x05, 0x62, 0x07, 0x69, 0x05, 0x62, 0x06, +0x46, 0x0a, 0x22, 0x06, 0x48, 0x0d, 0x24, 0x06, 0x6e, 0x11, 0xd3, +0x06, 0x70, 0x15, 0xcb, 0x06, 0x52, 0x20, 0x93, 0x06, 0x54, 0x20, +0x54, 0x06, 0x4a, 0x27, 0x1d, 0x06, 0x58, 0x2f, 0xc8, 0x06, 0x5c, +0x30, 0x07, 0x06, 0x4c, 0x37, 0x90, 0x06, 0x60, 0x3d, 0xdb, 0x06, +0x64, 0x3e, 0x42, 0x06, 0x4e, 0x45, 0x78, 0x06, 0x68, 0x4c, 0x48, +0x06, 0x6c, 0x4c, 0x6c, 0x06, 0x50, 0x52, 0xe2, 0x06, 0x42, 0x02, +0xba +}; +unsigned char coefficients2[] __initdata = { +0x07, 0x46, 0x00, 0x00, 0x07, 0x49, 0x00, 0x00, 0x07, 0x45, 0x0f, +0xff, 0x07, 0x48, 0x0f, 0xff, 0x07, 0x7b, 0x04, 0xcc, 0x07, 0x7d, +0x04, 0xcc, 0x07, 0x7c, 0x00, 0x00, 0x07, 0x7e, 0x00, 0x00, 0x07, +0x46, 0x00, 0x00, 0x07, 0x49, 0x00, 0x00, 0x07, 0x47, 0x00, 0x00, +0x07, 0x4a, 0x00, 0x00, 0x07, 0x4c, 0x00, 0x00, 0x07, 0x4e, 0x00, 0x00 +}; +unsigned char coefficients3[] __initdata = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x28, 0x00, 0x51, 0x00, +0x51, 0x00, 0x7a, 0x00, 0x7a, 0x00, 0xa3, 0x00, 0xa3, 0x00, 0xcc, +0x00, 0xcc, 0x00, 0xf5, 0x00, 0xf5, 0x01, 0x1e, 0x01, 0x1e, 0x01, +0x47, 0x01, 0x47, 0x01, 0x70, 0x01, 0x70, 0x01, 0x99, 0x01, 0x99, +0x01, 0xc2, 0x01, 0xc2, 0x01, 0xeb, 0x01, 0xeb, 0x02, 0x14, 0x02, +0x14, 0x02, 0x3d, 0x02, 0x3d, 0x02, 0x66, 0x02, 0x66, 0x02, 0x8f, +0x02, 0x8f, 0x02, 0xb8, 0x02, 0xb8, 0x02, 0xe1, 0x02, 0xe1, 0x03, +0x0a, 0x03, 0x0a, 0x03, 0x33, 0x03, 0x33, 0x03, 0x5c, 0x03, 0x5c, +0x03, 0x85, 0x03, 0x85, 0x03, 0xae, 0x03, 0xae, 0x03, 0xd7, 0x03, +0xd7, 0x04, 0x00, 0x04, 0x00, 0x04, 0x28, 0x04, 0x28, 0x04, 0x51, +0x04, 0x51, 0x04, 0x7a, 0x04, 0x7a, 0x04, 0xa3, 0x04, 0xa3, 0x04, +0xcc, 0x04, 0xcc, 0x04, 0xf5, 0x04, 0xf5, 0x05, 0x1e, 0x05, 0x1e, +0x05, 0x47, 0x05, 0x47, 0x05, 0x70, 0x05, 0x70, 0x05, 0x99, 0x05, +0x99, 0x05, 0xc2, 0x05, 0xc2, 0x05, 0xeb, 0x05, 0xeb, 0x06, 0x14, +0x06, 0x14, 0x06, 0x3d, 0x06, 0x3d, 0x06, 0x66, 0x06, 0x66, 0x06, +0x8f, 0x06, 0x8f, 0x06, 0xb8, 0x06, 0xb8, 0x06, 0xe1, 0x06, 0xe1, +0x07, 0x0a, 0x07, 0x0a, 0x07, 0x33, 0x07, 0x33, 0x07, 0x5c, 0x07, +0x5c, 0x07, 0x85, 0x07, 0x85, 0x07, 0xae, 0x07, 0xae, 0x07, 0xd7, +0x07, 0xd7, 0x08, 0x00, 0x08, 0x00, 0x08, 0x28, 0x08, 0x28, 0x08, +0x51, 0x08, 0x51, 0x08, 0x7a, 0x08, 0x7a, 0x08, 0xa3, 0x08, 0xa3, +0x08, 0xcc, 0x08, 0xcc, 0x08, 0xf5, 0x08, 0xf5, 0x09, 0x1e, 0x09, +0x1e, 0x09, 0x47, 0x09, 0x47, 0x09, 0x70, 0x09, 0x70, 0x09, 0x99, +0x09, 0x99, 0x09, 0xc2, 0x09, 0xc2, 0x09, 0xeb, 0x09, 0xeb, 0x0a, +0x14, 0x0a, 0x14, 0x0a, 0x3d, 0x0a, 0x3d, 0x0a, 0x66, 0x0a, 0x66, +0x0a, 0x8f, 0x0a, 0x8f, 0x0a, 0xb8, 0x0a, 0xb8, 0x0a, 0xe1, 0x0a, +0xe1, 0x0b, 0x0a, 0x0b, 0x0a, 0x0b, 0x33, 0x0b, 0x33, 0x0b, 0x5c, +0x0b, 0x5c, 0x0b, 0x85, 0x0b, 0x85, 0x0b, 0xae, 0x0b, 0xae, 0x0b, +0xd7, 0x0b, 0xd7, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x28, 0x0c, 0x28, +0x0c, 0x51, 0x0c, 0x51, 0x0c, 0x7a, 0x0c, 0x7a, 0x0c, 0xa3, 0x0c, +0xa3, 0x0c, 0xcc, 0x0c, 0xcc, 0x0c, 0xf5, 0x0c, 0xf5, 0x0d, 0x1e, +0x0d, 0x1e, 0x0d, 0x47, 0x0d, 0x47, 0x0d, 0x70, 0x0d, 0x70, 0x0d, +0x99, 0x0d, 0x99, 0x0d, 0xc2, 0x0d, 0xc2, 0x0d, 0xeb, 0x0d, 0xeb, +0x0e, 0x14, 0x0e, 0x14, 0x0e, 0x3d, 0x0e, 0x3d, 0x0e, 0x66, 0x0e, +0x66, 0x0e, 0x8f, 0x0e, 0x8f, 0x0e, 0xb8, 0x0e, 0xb8, 0x0e, 0xe1, +0x0e, 0xe1, 0x0f, 0x0a, 0x0f, 0x0a, 0x0f, 0x33, 0x0f, 0x33, 0x0f, +0x5c, 0x0f, 0x5c, 0x0f, 0x85, 0x0f, 0x85, 0x0f, 0xae, 0x0f, 0xae, +0x0f, 0xd7, 0x0f, 0xd7, 0x0f, 0xff, 0x0f, 0xff +}; + diff --git a/sound/oss/yss225.h b/sound/oss/yss225.h new file mode 100644 index 000000000000..56d8b6b5e432 --- /dev/null +++ b/sound/oss/yss225.h @@ -0,0 +1,24 @@ +#ifndef __yss255_h__ +#define __yss255_h__ + +extern unsigned char page_zero[256]; +extern unsigned char page_one[256]; +extern unsigned char page_two[128]; +extern unsigned char page_three[128]; +extern unsigned char page_four[128]; +extern unsigned char page_six[192]; +extern unsigned char page_seven[256]; +extern unsigned char page_zero_v2[96]; +extern unsigned char page_one_v2[96]; +extern unsigned char page_two_v2[48]; +extern unsigned char page_three_v2[48]; +extern unsigned char page_four_v2[48]; +extern unsigned char page_seven_v2[96]; +extern unsigned char mod_v2[304]; +extern unsigned char coefficients[364]; +extern unsigned char coefficients2[56]; +extern unsigned char coefficients3[404]; + + +#endif /* __ys225_h__ */ + -- cgit v1.2.3-59-g8ed1b